@jtff/miztemplate-lib 3.1.7 → 3.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- env.info('*** MOOSE GITHUB Commit Hash ID: 2023-11-19T12:40:22+01:00-522eb8b256f9edfbcb2c8ecfe66861f659c6403c ***')
1
+ env.info('*** MOOSE GITHUB Commit Hash ID: 2024-01-01T00:38:59+01:00-8dcd22f18c9bf740c53b69c7ba3901a9ada0bb2d ***')
2
2
  env.info('*** MOOSE STATIC INCLUDE START *** ')
3
3
  ENUMS={}
4
4
  env.setErrorMessageBoxEnabled(false)
@@ -379,6 +379,7 @@ Galaxy="C-5",
379
379
  Hawkeye="E-2D",
380
380
  Sentry="E-3A",
381
381
  Stratotanker="KC-135",
382
+ Gasstation="KC-135MPRS",
382
383
  Extender="KC-10",
383
384
  Orion="P-3C",
384
385
  Viking="S-3B",
@@ -416,6 +417,12 @@ Reaper="MQ-9",
416
417
  Predator="MQ-1A",
417
418
  }
418
419
  }
420
+ ENUMS.Link16Power={
421
+ none=0,
422
+ low=1,
423
+ medium=2,
424
+ high=3,
425
+ }
419
426
  ENUMS.Storage={
420
427
  weapons={
421
428
  missiles={},
@@ -1277,21 +1284,33 @@ end
1277
1284
  end
1278
1285
  end
1279
1286
  function UTILS.PrintTableToLog(table,indent)
1287
+ local text="\n"
1280
1288
  if not table then
1281
- BASE:E("No table passed!")
1282
- return
1289
+ env.warning("No table passed!")
1290
+ return nil
1283
1291
  end
1284
1292
  if not indent then indent=0 end
1285
1293
  for k,v in pairs(table)do
1294
+ if string.find(k," ")then k='"'..k..'"'end
1286
1295
  if type(v)=="table"then
1287
- BASE:I(string.rep(" ",indent)..tostring(k).." = {")
1288
- UTILS.PrintTableToLog(v,indent+1)
1289
- BASE:I(string.rep(" ",indent).."}")
1296
+ env.info(string.rep(" ",indent)..tostring(k).." = {")
1297
+ text=text..string.rep(" ",indent)..tostring(k).." = {\n"
1298
+ text=text..tostring(UTILS.PrintTableToLog(v,indent+1)).."\n"
1299
+ env.info(string.rep(" ",indent).."},")
1300
+ text=text..string.rep(" ",indent).."},\n"
1301
+ else
1302
+ local value
1303
+ if tostring(v)=="true"or tostring(v)=="false"or tonumber(v)~=nil then
1304
+ value=v
1290
1305
  else
1291
- BASE:I(string.rep(" ",indent)..tostring(k).." = "..tostring(v))
1306
+ value='"'..tostring(v)..'"'
1292
1307
  end
1308
+ env.info(string.rep(" ",indent)..tostring(k).." = "..tostring(value)..",\n")
1309
+ text=text..string.rep(" ",indent)..tostring(k).." = "..tostring(value)..",\n"
1293
1310
  end
1294
1311
  end
1312
+ return text
1313
+ end
1295
1314
  function UTILS.TableShow(tbl,loc,indent,tableshow_tbls)
1296
1315
  tableshow_tbls=tableshow_tbls or{}
1297
1316
  loc=loc or""
@@ -1817,9 +1836,15 @@ end
1817
1836
  function UTILS.VecSubstract(a,b)
1818
1837
  return{x=a.x-b.x,y=a.y-b.y,z=a.z-b.z}
1819
1838
  end
1839
+ function UTILS.VecSubtract(a,b)
1840
+ return UTILS.VecSubstract(a,b)
1841
+ end
1820
1842
  function UTILS.Vec2Substract(a,b)
1821
1843
  return{x=a.x-b.x,y=a.y-b.y}
1822
1844
  end
1845
+ function UTILS.Vec2Subtract(a,b)
1846
+ return UTILS.Vec2Substract(a,b)
1847
+ end
1823
1848
  function UTILS.VecAdd(a,b)
1824
1849
  return{x=a.x+b.x,y=a.y+b.y,z=a.z+b.z}
1825
1850
  end
@@ -2288,15 +2313,15 @@ if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1217)==1)then
2288
2313
  BASE:T(unit_name.." side door is open")
2289
2314
  return true
2290
2315
  end
2291
- if string.find(type_name,"Bell-47")then
2316
+ if type_name=="Bell-47"then
2292
2317
  BASE:T(unit_name.." door is open")
2293
2318
  return true
2294
2319
  end
2295
- if string.find(type_name,"UH-60L")and(unit:getDrawArgumentValue(401)==1 or unit:getDrawArgumentValue(402)==1)then
2320
+ if type_name=="UH-60L"and(unit:getDrawArgumentValue(401)==1 or unit:getDrawArgumentValue(402)==1)then
2296
2321
  BASE:T(unit_name.." cargo door is open")
2297
2322
  return true
2298
2323
  end
2299
- if string.find(type_name,"UH-60L")and(unit:getDrawArgumentValue(38)==1 or unit:getDrawArgumentValue(400)==1)then
2324
+ if type_name=="UH-60L"and(unit:getDrawArgumentValue(38)>0 or unit:getDrawArgumentValue(400)==1)then
2300
2325
  BASE:T(unit_name.." front door(s) are open")
2301
2326
  return true
2302
2327
  end
@@ -2479,7 +2504,7 @@ filename=path.."\\"..filename
2479
2504
  end
2480
2505
  local exists=UTILS.CheckFileExists(Path,Filename)
2481
2506
  if not exists then
2482
- BASE:E(string.format("ERROR: File %s does not exist!",filename))
2507
+ BASE:I(string.format("ERROR: File %s does not exist!",filename))
2483
2508
  return false
2484
2509
  end
2485
2510
  local file=assert(io.open(filename,"rb"))
@@ -2991,6 +3016,300 @@ point_four:LineToAll(point_three,coalition,color,alpha,lineType)
2991
3016
  circle_center_fix_four:CircleToAll(UTILS.NMToMeters(turn_radius),coalition,color,alpha,nil,0,lineType)
2992
3017
  circle_center_two_three:CircleToAll(UTILS.NMToMeters(turn_radius),coalition,color,alpha,nil,0,lineType)
2993
3018
  end
3019
+ function UTILS.TimeNow()
3020
+ return UTILS.SecondsToClock(timer.getAbsTime(),false,false)
3021
+ end
3022
+ function UTILS.TimeDifferenceInSeconds(start_time,end_time)
3023
+ return UTILS.ClockToSeconds(end_time)-UTILS.ClockToSeconds(start_time)
3024
+ end
3025
+ function UTILS.TimeLaterThan(time_string)
3026
+ if timer.getAbsTime()>UTILS.ClockToSeconds(time_string)then
3027
+ return true
3028
+ end
3029
+ return false
3030
+ end
3031
+ function UTILS.TimeBefore(time_string)
3032
+ if timer.getAbsTime()<UTILS.ClockToSeconds(time_string)then
3033
+ return true
3034
+ end
3035
+ return false
3036
+ end
3037
+ function UTILS.CombineTimeStrings(time_string_01,time_string_02)
3038
+ local hours1,minutes1,seconds1=time_string_01:match("(%d+):(%d+):(%d+)")
3039
+ local hours2,minutes2,seconds2=time_string_02:match("(%d+):(%d+):(%d+)")
3040
+ local total_seconds=tonumber(seconds1)+tonumber(seconds2)+tonumber(minutes1)*60+tonumber(minutes2)*60+tonumber(hours1)*3600+tonumber(hours2)*3600
3041
+ total_seconds=total_seconds%(24*3600)
3042
+ if total_seconds<0 then
3043
+ total_seconds=total_seconds+24*3600
3044
+ end
3045
+ local hours=math.floor(total_seconds/3600)
3046
+ total_seconds=total_seconds-hours*3600
3047
+ local minutes=math.floor(total_seconds/60)
3048
+ local seconds=total_seconds%60
3049
+ return string.format("%02d:%02d:%02d",hours,minutes,seconds)
3050
+ end
3051
+ function UTILS.SubtractTimeStrings(time_string_01,time_string_02)
3052
+ local hours1,minutes1,seconds1=time_string_01:match("(%d+):(%d+):(%d+)")
3053
+ local hours2,minutes2,seconds2=time_string_02:match("(%d+):(%d+):(%d+)")
3054
+ local total_seconds=tonumber(seconds1)-tonumber(seconds2)+tonumber(minutes1)*60-tonumber(minutes2)*60+tonumber(hours1)*3600-tonumber(hours2)*3600
3055
+ total_seconds=total_seconds%(24*3600)
3056
+ if total_seconds<0 then
3057
+ total_seconds=total_seconds+24*3600
3058
+ end
3059
+ local hours=math.floor(total_seconds/3600)
3060
+ total_seconds=total_seconds-hours*3600
3061
+ local minutes=math.floor(total_seconds/60)
3062
+ local seconds=total_seconds%60
3063
+ return string.format("%02d:%02d:%02d",hours,minutes,seconds)
3064
+ end
3065
+ function UTILS.TimeBetween(start_time,end_time)
3066
+ return UTILS.TimeLaterThan(start_time)and UTILS.TimeBefore(end_time)
3067
+ end
3068
+ function UTILS.PercentageChance(chance)
3069
+ chance=chance or math.random(0,100)
3070
+ chance=UTILS.Clamp(chance,0,100)
3071
+ local percentage=math.random(0,100)
3072
+ if percentage<chance then
3073
+ return true
3074
+ end
3075
+ return false
3076
+ end
3077
+ function UTILS.Clamp(value,min,max)
3078
+ if value<min then value=min end
3079
+ if value>max then value=max end
3080
+ return value
3081
+ end
3082
+ function UTILS.ClampAngle(value)
3083
+ if value>360 then return value-360 end
3084
+ if value<0 then return value+360 end
3085
+ return value
3086
+ end
3087
+ function UTILS.RemapValue(value,old_min,old_max,new_min,new_max)
3088
+ new_min=new_min or 0
3089
+ new_max=new_max or 100
3090
+ local old_range=old_max-old_min
3091
+ local new_range=new_max-new_min
3092
+ local percentage=(value-old_min)/old_range
3093
+ return(new_range*percentage)+new_min
3094
+ end
3095
+ function UTILS.RandomPointInTriangle(pt1,pt2,pt3)
3096
+ local pt={math.random(),math.random()}
3097
+ table.sort(pt)
3098
+ local s=pt[1]
3099
+ local t=pt[2]-pt[1]
3100
+ local u=1-pt[2]
3101
+ return{x=s*pt1.x+t*pt2.x+u*pt3.x,
3102
+ y=s*pt1.y+t*pt2.y+u*pt3.y}
3103
+ end
3104
+ function UTILS.AngleBetween(angle,min,max)
3105
+ angle=(360+(angle%360))%360
3106
+ min=(360+min%360)%360
3107
+ max=(360+max%360)%360
3108
+ if min<max then return min<=angle and angle<=max end
3109
+ return min<=angle or angle<=max
3110
+ end
3111
+ function UTILS.WriteJSON(data,file_path)
3112
+ package.path=package.path..";.\\Scripts\\?.lua"
3113
+ local JSON=require("json")
3114
+ local pretty_json_text=JSON:encode_pretty(data)
3115
+ local write_file=io.open(file_path,"w")
3116
+ write_file:write(pretty_json_text)
3117
+ write_file:close()
3118
+ end
3119
+ function UTILS.ReadJSON(file_path)
3120
+ package.path=package.path..";.\\Scripts\\?.lua"
3121
+ local JSON=require("json")
3122
+ local read_file=io.open(file_path,"r")
3123
+ local contents=read_file:read("*a")
3124
+ io.close(read_file)
3125
+ return JSON:decode(contents)
3126
+ end
3127
+ function UTILS.GetZoneProperties(zone_name)
3128
+ local return_table={}
3129
+ for _,zone in pairs(env.mission.triggers.zones)do
3130
+ if zone["name"]==zone_name then
3131
+ if table.length(zone["properties"])>0 then
3132
+ for _,property in pairs(zone["properties"])do
3133
+ return_table[property["key"]]=property["value"]
3134
+ end
3135
+ return return_table
3136
+ else
3137
+ BASE:I(string.format("%s doesn't have any properties",zone_name))
3138
+ return{}
3139
+ end
3140
+ end
3141
+ end
3142
+ end
3143
+ function UTILS.RotatePointAroundPivot(point,pivot,angle)
3144
+ local radians=math.rad(angle)
3145
+ local x=point.x-pivot.x
3146
+ local y=point.y-pivot.y
3147
+ local rotated_x=x*math.cos(radians)-y*math.sin(radians)
3148
+ local rotatex_y=x*math.sin(radians)+y*math.cos(radians)
3149
+ local original_x=rotated_x+pivot.x
3150
+ local original_y=rotatex_y+pivot.y
3151
+ return{x=original_x,y=original_y}
3152
+ end
3153
+ function UTILS.UniqueName(base)
3154
+ base=base or""
3155
+ local ran=tostring(math.random(0,1000000))
3156
+ if base==""then
3157
+ return ran
3158
+ end
3159
+ return base.."_"..ran
3160
+ end
3161
+ function string.startswith(str,value)
3162
+ return string.sub(str,1,string.len(value))==value
3163
+ end
3164
+ function string.endswith(str,value)
3165
+ return value==""or str:sub(-#value)==value
3166
+ end
3167
+ function string.split(input,separator)
3168
+ local parts={}
3169
+ for part in input:gmatch("[^"..separator.."]+")do
3170
+ table.insert(parts,part)
3171
+ end
3172
+ return parts
3173
+ end
3174
+ function string.contains(str,value)
3175
+ return string.match(str,value)
3176
+ end
3177
+ function table.contains(tbl,element)
3178
+ if element==nil or tbl==nil then return false end
3179
+ local index=1
3180
+ while tbl[index]do
3181
+ if tbl[index]==element then
3182
+ return true
3183
+ end
3184
+ index=index+1
3185
+ end
3186
+ return false
3187
+ end
3188
+ function table.contains_key(tbl,key)
3189
+ if tbl[key]~=nil then return true else return false end
3190
+ end
3191
+ function table.insert_unique(tbl,element)
3192
+ if element==nil or tbl==nil then return end
3193
+ if not table.contains(tbl,element)then
3194
+ table.insert(tbl,element)
3195
+ end
3196
+ end
3197
+ function table.remove_by_value(tbl,element)
3198
+ local indices_to_remove={}
3199
+ local index=1
3200
+ for _,value in pairs(tbl)do
3201
+ if value==element then
3202
+ table.insert(indices_to_remove,index)
3203
+ end
3204
+ index=index+1
3205
+ end
3206
+ for _,idx in pairs(indices_to_remove)do
3207
+ table.remove(tbl,idx)
3208
+ end
3209
+ end
3210
+ function table.remove_key(table,key)
3211
+ local element=table[key]
3212
+ table[key]=nil
3213
+ return element
3214
+ end
3215
+ function table.index_of(table,element)
3216
+ for i,v in ipairs(table)do
3217
+ if v==element then
3218
+ return i
3219
+ end
3220
+ end
3221
+ return nil
3222
+ end
3223
+ function table.length(T)
3224
+ local count=0
3225
+ for _ in pairs(T)do count=count+1 end
3226
+ return count
3227
+ end
3228
+ function table.slice(tbl,first,last)
3229
+ local sliced={}
3230
+ local start=first or 1
3231
+ local stop=last or table.length(tbl)
3232
+ local count=1
3233
+ for key,value in pairs(tbl)do
3234
+ if count>=start and count<=stop then
3235
+ sliced[key]=value
3236
+ end
3237
+ count=count+1
3238
+ end
3239
+ return sliced
3240
+ end
3241
+ function table.count_value(tbl,value)
3242
+ local count=0
3243
+ for _,item in pairs(tbl)do
3244
+ if item==value then count=count+1 end
3245
+ end
3246
+ return count
3247
+ end
3248
+ function table.combine(t1,t2)
3249
+ if t1==nil and t2==nil then
3250
+ BASE:E("Both tables were empty!")
3251
+ end
3252
+ if t1==nil then return t2 end
3253
+ if t2==nil then return t1 end
3254
+ for i=1,#t2 do
3255
+ t1[#t1+1]=t2[i]
3256
+ end
3257
+ return t1
3258
+ end
3259
+ function table.merge(t1,t2)
3260
+ for k,v in pairs(t2)do
3261
+ if(type(v)=="table")and(type(t1[k]or false)=="table")then
3262
+ table.merge(t1[k],t2[k])
3263
+ else
3264
+ t1[k]=v
3265
+ end
3266
+ end
3267
+ return t1
3268
+ end
3269
+ function table.add(tbl,item)
3270
+ tbl[#tbl+1]=item
3271
+ end
3272
+ function table.shuffle(tbl)
3273
+ local new_table={}
3274
+ for _,value in ipairs(tbl)do
3275
+ local pos=math.random(1,#new_table+1)
3276
+ table.insert(new_table,pos,value)
3277
+ end
3278
+ return new_table
3279
+ end
3280
+ function table.find_key_value_pair(tbl,key,value)
3281
+ for k,v in pairs(tbl)do
3282
+ if type(v)=="table"then
3283
+ local result=table.find_key_value_pair(v,key,value)
3284
+ if result~=nil then
3285
+ return result
3286
+ end
3287
+ elseif k==key and v==value then
3288
+ return tbl
3289
+ end
3290
+ end
3291
+ return nil
3292
+ end
3293
+ function UTILS.DecimalToOctal(Number)
3294
+ if Number<8 then return Number end
3295
+ local number=tonumber(Number)
3296
+ local octal=""
3297
+ local n=1
3298
+ while number>7 do
3299
+ local number1=number%8
3300
+ octal=string.format("%d",number1)..octal
3301
+ local number2=math.abs(number/8)
3302
+ if number2<8 then
3303
+ octal=string.format("%d",number2)..octal
3304
+ end
3305
+ number=number2
3306
+ n=n+1
3307
+ end
3308
+ return tonumber(octal)
3309
+ end
3310
+ function UTILS.OctalToDecimal(Number)
3311
+ return tonumber(Number,8)
3312
+ end
2994
3313
  PROFILER={
2995
3314
  ClassName="PROFILER",
2996
3315
  Counters={},
@@ -6618,7 +6937,7 @@ if Event.weapon then
6618
6937
  Event.Weapon=Event.weapon
6619
6938
  Event.WeaponName=Event.Weapon:getTypeName()
6620
6939
  Event.WeaponUNIT=CLIENT:Find(Event.Weapon,'',true)
6621
- Event.WeaponPlayerName=Event.WeaponUNIT and Event.Weapon:getPlayerName()
6940
+ Event.WeaponPlayerName=Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName()
6622
6941
  Event.WeaponCoalition=Event.WeaponUNIT and Event.Weapon:getCoalition()
6623
6942
  Event.WeaponCategory=Event.WeaponUNIT and Event.Weapon:getDesc().category
6624
6943
  Event.WeaponTypeName=Event.WeaponUNIT and Event.Weapon:getTypeName()
@@ -8166,7 +8485,13 @@ if Delay and Delay>0 then
8166
8485
  self:ScheduleOnce(Delay,ZONE_BASE.UndrawZone,self)
8167
8486
  else
8168
8487
  if self.DrawID then
8488
+ if type(self.DrawID)~="table"then
8169
8489
  UTILS.RemoveMark(self.DrawID)
8490
+ else
8491
+ for _,mark_id in pairs(self.DrawID)do
8492
+ UTILS.RemoveMark(mark_id)
8493
+ end
8494
+ end
8170
8495
  end
8171
8496
  end
8172
8497
  return self
@@ -8881,8 +9206,81 @@ local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2())
8881
9206
  self:T3({PointVec2})
8882
9207
  return PointVec2
8883
9208
  end
9209
+ _ZONE_TRIANGLE={
9210
+ ClassName="ZONE_TRIANGLE",
9211
+ Points={},
9212
+ Coords={},
9213
+ CenterVec2={x=0,y=0},
9214
+ SurfaceArea=0,
9215
+ DrawID={}
9216
+ }
9217
+ function _ZONE_TRIANGLE:New(p1,p2,p3)
9218
+ local self=BASE:Inherit(self,ZONE_BASE:New())
9219
+ self.Points={p1,p2,p3}
9220
+ local center_x=(p1.x+p2.x+p3.x)/3
9221
+ local center_y=(p1.y+p2.y+p3.y)/3
9222
+ self.CenterVec2={x=center_x,y=center_y}
9223
+ for _,pt in pairs({p1,p2,p3})do
9224
+ table.add(self.Coords,COORDINATE:NewFromVec2(pt))
9225
+ end
9226
+ self.SurfaceArea=math.abs((p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y))*0.5
9227
+ return self
9228
+ end
9229
+ function _ZONE_TRIANGLE:ContainsPoint(pt,points)
9230
+ points=points or self.Points
9231
+ local function sign(p1,p2,p3)
9232
+ return(p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y)
9233
+ end
9234
+ local d1=sign(pt,self.Points[1],self.Points[2])
9235
+ local d2=sign(pt,self.Points[2],self.Points[3])
9236
+ local d3=sign(pt,self.Points[3],self.Points[1])
9237
+ local has_neg=(d1<0)or(d2<0)or(d3<0)
9238
+ local has_pos=(d1>0)or(d2>0)or(d3>0)
9239
+ return not(has_neg and has_pos)
9240
+ end
9241
+ function _ZONE_TRIANGLE:GetRandomVec2(points)
9242
+ points=points or self.Points
9243
+ local pt={math.random(),math.random()}
9244
+ table.sort(pt)
9245
+ local s=pt[1]
9246
+ local t=pt[2]-pt[1]
9247
+ local u=1-pt[2]
9248
+ return{x=s*points[1].x+t*points[2].x+u*points[3].x,
9249
+ y=s*points[1].y+t*points[2].y+u*points[3].y}
9250
+ end
9251
+ function _ZONE_TRIANGLE:Draw(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
9252
+ Coalition=Coalition or-1
9253
+ Color=Color or{1,0,0}
9254
+ Alpha=Alpha or 1
9255
+ FillColor=FillColor or Color
9256
+ if not FillColor then UTILS.DeepCopy(Color)end
9257
+ FillAlpha=FillAlpha or Alpha
9258
+ if not FillAlpha then FillAlpha=1 end
9259
+ for i=1,#self.Coords do
9260
+ local c1=self.Coords[i]
9261
+ local c2=self.Coords[i%#self.Coords+1]
9262
+ local id=c1:LineToAll(c2,Coalition,Color,Alpha,LineType,ReadOnly)
9263
+ self.DrawID[#self.DrawID+1]=id
9264
+ end
9265
+ local newID=self.Coords[1]:MarkupToAllFreeForm({self.Coords[2],self.Coords[3]},Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
9266
+ self.DrawID[#self.DrawID+1]=newID
9267
+ return self.DrawID
9268
+ end
9269
+ function _ZONE_TRIANGLE:Fill(Coalition,FillColor,FillAlpha,ReadOnly)
9270
+ Coalition=Coalition or-1
9271
+ FillColor=FillColor
9272
+ FillAlpha=FillAlpha
9273
+ local newID=self.Coords[1]:MarkupToAllFreeForm({self.Coords[2],self.Coords[3]},Coalition,nil,nil,FillColor,FillAlpha,0,nil)
9274
+ self.DrawID[#self.DrawID+1]=newID
9275
+ return self.DrawID
9276
+ end
8884
9277
  ZONE_POLYGON_BASE={
8885
9278
  ClassName="ZONE_POLYGON_BASE",
9279
+ _Triangles={},
9280
+ SurfaceArea=0,
9281
+ DrawID={},
9282
+ FillTriangles={},
9283
+ Borderlines={},
8886
9284
  }
8887
9285
  function ZONE_POLYGON_BASE:New(ZoneName,PointsArray)
8888
9286
  local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName))
@@ -8894,9 +9292,84 @@ self._.Polygon[i]={}
8894
9292
  self._.Polygon[i].x=PointsArray[i].x
8895
9293
  self._.Polygon[i].y=PointsArray[i].y
8896
9294
  end
9295
+ self._Triangles=self:_Triangulate()
9296
+ self.SurfaceArea=self:_CalculateSurfaceArea()
8897
9297
  end
8898
9298
  return self
8899
9299
  end
9300
+ function ZONE_POLYGON_BASE:_Triangulate()
9301
+ local points=self._.Polygon
9302
+ local triangles={}
9303
+ local function get_orientation(shape_points)
9304
+ local sum=0
9305
+ for i=1,#shape_points do
9306
+ local j=i%#shape_points+1
9307
+ sum=sum+(shape_points[j].x-shape_points[i].x)*(shape_points[j].y+shape_points[i].y)
9308
+ end
9309
+ return sum>=0 and"clockwise"or"counter-clockwise"
9310
+ end
9311
+ local function ensure_clockwise(shape_points)
9312
+ local orientation=get_orientation(shape_points)
9313
+ if orientation=="counter-clockwise"then
9314
+ local reversed={}
9315
+ for i=#shape_points,1,-1 do
9316
+ table.insert(reversed,shape_points[i])
9317
+ end
9318
+ return reversed
9319
+ end
9320
+ return shape_points
9321
+ end
9322
+ local function is_clockwise(p1,p2,p3)
9323
+ local cross_product=(p2.x-p1.x)*(p3.y-p1.y)-(p2.y-p1.y)*(p3.x-p1.x)
9324
+ return cross_product<0
9325
+ end
9326
+ local function divide_recursively(shape_points)
9327
+ if#shape_points==3 then
9328
+ table.insert(triangles,_ZONE_TRIANGLE:New(shape_points[1],shape_points[2],shape_points[3]))
9329
+ elseif#shape_points>3 then
9330
+ for i,p1 in ipairs(shape_points)do
9331
+ local p2=shape_points[(i%#shape_points)+1]
9332
+ local p3=shape_points[(i+1)%#shape_points+1]
9333
+ local triangle=_ZONE_TRIANGLE:New(p1,p2,p3)
9334
+ local is_ear=true
9335
+ if not is_clockwise(p1,p2,p3)then
9336
+ is_ear=false
9337
+ else
9338
+ for _,point in ipairs(shape_points)do
9339
+ if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then
9340
+ is_ear=false
9341
+ break
9342
+ end
9343
+ end
9344
+ end
9345
+ if is_ear then
9346
+ local is_valid_triangle=true
9347
+ for _,point in ipairs(points)do
9348
+ if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then
9349
+ is_valid_triangle=false
9350
+ break
9351
+ end
9352
+ end
9353
+ if is_valid_triangle then
9354
+ table.insert(triangles,triangle)
9355
+ local remaining_points={}
9356
+ for j,point in ipairs(shape_points)do
9357
+ if point~=p2 then
9358
+ table.insert(remaining_points,point)
9359
+ end
9360
+ end
9361
+ divide_recursively(remaining_points)
9362
+ break
9363
+ end
9364
+ else
9365
+ end
9366
+ end
9367
+ end
9368
+ end
9369
+ points=ensure_clockwise(points)
9370
+ divide_recursively(points)
9371
+ return triangles
9372
+ end
8900
9373
  function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array)
8901
9374
  self._.Polygon={}
8902
9375
  for i=1,#Vec2Array do
@@ -8904,6 +9377,8 @@ self._.Polygon[i]={}
8904
9377
  self._.Polygon[i].x=Vec2Array[i].x
8905
9378
  self._.Polygon[i].y=Vec2Array[i].y
8906
9379
  end
9380
+ self._Triangles=self:_Triangulate()
9381
+ self.SurfaceArea=self:_CalculateSurfaceArea()
8907
9382
  return self
8908
9383
  end
8909
9384
  function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array)
@@ -8913,8 +9388,17 @@ self._.Polygon[i]={}
8913
9388
  self._.Polygon[i].x=Vec3Array[i].x
8914
9389
  self._.Polygon[i].y=Vec3Array[i].z
8915
9390
  end
9391
+ self._Triangles=self:_Triangulate()
9392
+ self.SurfaceArea=self:_CalculateSurfaceArea()
8916
9393
  return self
8917
9394
  end
9395
+ function ZONE_POLYGON_BASE:_CalculateSurfaceArea()
9396
+ local area=0
9397
+ for _,triangle in pairs(self._Triangles)do
9398
+ area=area+triangle.SurfaceArea
9399
+ end
9400
+ return area
9401
+ end
8918
9402
  function ZONE_POLYGON_BASE:GetVec2()
8919
9403
  self:F(self.ZoneName)
8920
9404
  local Bounds=self:GetBoundingSquare()
@@ -8997,32 +9481,81 @@ i=i+1
8997
9481
  end
8998
9482
  return self
8999
9483
  end
9000
- function ZONE_POLYGON_BASE:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
9484
+ function ZONE_POLYGON_BASE:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,IncludeTriangles)
9001
9485
  if self._.Polygon and#self._.Polygon>=3 then
9002
- local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1])
9003
9486
  Coalition=Coalition or self:GetDrawCoalition()
9004
9487
  self:SetDrawCoalition(Coalition)
9005
9488
  Color=Color or self:GetColorRGB()
9006
- Alpha=Alpha or 1
9007
- self:SetColor(Color,Alpha)
9489
+ Alpha=Alpha or self:GetColorAlpha()
9008
9490
  FillColor=FillColor or self:GetFillColorRGB()
9009
- if not FillColor then UTILS.DeepCopy(Color)end
9010
9491
  FillAlpha=FillAlpha or self:GetFillColorAlpha()
9011
- if not FillAlpha then FillAlpha=0.15 end
9012
- self:SetFillColor(FillColor,FillAlpha)
9013
- if#self._.Polygon==4 then
9014
- local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2])
9015
- local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3])
9016
- local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4])
9017
- self.DrawID=coordinate:QuadToAll(Coord2,Coord3,Coord4,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
9018
- else
9019
- local Coordinates=self:GetVerticiesCoordinates()
9020
- table.remove(Coordinates,1)
9021
- self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
9492
+ if FillColor then
9493
+ self:ReFill(FillColor,FillAlpha)
9494
+ end
9495
+ if Color then
9496
+ self:ReDrawBorderline(Color,Alpha,LineType)
9497
+ end
9498
+ end
9499
+ if false then
9500
+ local coords=self:GetVerticiesCoordinates()
9501
+ local coord=coords[1]
9502
+ table.remove(coords,1)
9503
+ coord:MarkupToAllFreeForm(coords,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,"Drew Polygon")
9504
+ if true then
9505
+ return
9506
+ end
9507
+ end
9508
+ return self
9509
+ end
9510
+ function ZONE_POLYGON_BASE:ReFill(Color,Alpha)
9511
+ local color=Color or self:GetFillColorRGB()or{1,0,0}
9512
+ local alpha=Alpha or self:GetFillColorAlpha()or 1
9513
+ local coalition=self:GetDrawCoalition()or-1
9514
+ if#self.FillTriangles>0 then
9515
+ for _,triangle in pairs(self._Triangles)do
9516
+ triangle:UndrawZone()
9517
+ end
9518
+ for _,_value in pairs(self.FillTriangles)do
9519
+ table.remove_by_value(self.DrawID,_value)
9520
+ end
9521
+ self.FillTriangles=nil
9522
+ self.FillTriangles={}
9523
+ end
9524
+ for _,triangle in pairs(self._Triangles)do
9525
+ local draw_ids=triangle:Fill(coalition,color,alpha,nil)
9526
+ self.FillTriangles=draw_ids
9527
+ table.combine(self.DrawID,draw_ids)
9528
+ end
9529
+ return self
9530
+ end
9531
+ function ZONE_POLYGON_BASE:ReDrawBorderline(Color,Alpha,LineType)
9532
+ local color=Color or self:GetFillColorRGB()or{1,0,0}
9533
+ local alpha=Alpha or self:GetFillColorAlpha()or 1
9534
+ local coalition=self:GetDrawCoalition()or-1
9535
+ local linetype=LineType or 1
9536
+ if#self.Borderlines>0 then
9537
+ for _,MarkID in pairs(self.Borderlines)do
9538
+ trigger.action.removeMark(MarkID)
9539
+ end
9540
+ for _,_value in pairs(self.Borderlines)do
9541
+ table.remove_by_value(self.DrawID,_value)
9022
9542
  end
9543
+ self.Borderlines=nil
9544
+ self.Borderlines={}
9545
+ end
9546
+ local coords=self:GetVerticiesCoordinates()
9547
+ for i=1,#coords do
9548
+ local c1=coords[i]
9549
+ local c2=coords[i%#coords+1]
9550
+ local newID=c1:LineToAll(c2,coalition,color,alpha,linetype,nil)
9551
+ self.DrawID[#self.DrawID+1]=newID
9552
+ self.Borderlines[#self.Borderlines+1]=newID
9023
9553
  end
9024
9554
  return self
9025
9555
  end
9556
+ function ZONE_POLYGON_BASE:GetSurfaceArea()
9557
+ return self.SurfaceArea
9558
+ end
9026
9559
  function ZONE_POLYGON_BASE:GetRadius()
9027
9560
  local center=self:GetVec2()
9028
9561
  local radius=0
@@ -9131,17 +9664,18 @@ local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
9131
9664
  return InZone
9132
9665
  end
9133
9666
  function ZONE_POLYGON_BASE:GetRandomVec2()
9134
- local BS=self:GetBoundingSquare()
9135
- local Nmax=1000;local n=0
9136
- while n<Nmax do
9137
- local Vec2={x=math.random(BS.x1,BS.x2),y=math.random(BS.y1,BS.y2)}
9138
- if self:IsVec2InZone(Vec2)then
9139
- return Vec2
9667
+ local weights={}
9668
+ for _,triangle in pairs(self._Triangles)do
9669
+ weights[triangle]=triangle.SurfaceArea/self.SurfaceArea
9670
+ end
9671
+ local random_weight=math.random()
9672
+ local accumulated_weight=0
9673
+ for triangle,weight in pairs(weights)do
9674
+ accumulated_weight=accumulated_weight+weight
9675
+ if accumulated_weight>=random_weight then
9676
+ return triangle:GetRandomVec2()
9140
9677
  end
9141
- n=n+1
9142
9678
  end
9143
- self:E("Could not find a random point in the polygon zone!")
9144
- return nil
9145
9679
  end
9146
9680
  function ZONE_POLYGON_BASE:GetRandomPointVec2()
9147
9681
  self:F2()
@@ -9198,6 +9732,7 @@ Radius=Radius or 1000
9198
9732
  Alpha=Alpha or 1
9199
9733
  Segments=Segments or 10
9200
9734
  Closed=Closed or false
9735
+ local Limit
9201
9736
  local i=1
9202
9737
  local j=#self._.Polygon
9203
9738
  if(Closed)then
@@ -9220,6 +9755,7 @@ i=i+1
9220
9755
  end
9221
9756
  return self
9222
9757
  end
9758
+ do
9223
9759
  ZONE_POLYGON={
9224
9760
  ClassName="ZONE_POLYGON",
9225
9761
  }
@@ -9244,6 +9780,36 @@ self:F({GroupName,ZoneGroup,self._.Polygon})
9244
9780
  _EVENTDISPATCHER:CreateEventNewZone(self)
9245
9781
  return self
9246
9782
  end
9783
+ function ZONE_POLYGON:NewFromDrawing(DrawingName)
9784
+ local points={}
9785
+ for _,layer in pairs(env.mission.drawings.layers)do
9786
+ for _,object in pairs(layer["objects"])do
9787
+ if object["name"]==DrawingName then
9788
+ if(object["primitiveType"]=="Line"and object["closed"]==true)or(object["polygonMode"]=="free")then
9789
+ for _,point in UTILS.spairs(object["points"])do
9790
+ local p={x=object["mapX"]+point["x"],
9791
+ y=object["mapY"]+point["y"]}
9792
+ table.add(points,p)
9793
+ end
9794
+ elseif object["polygonMode"]=="rect"then
9795
+ local angle=object["angle"]
9796
+ local half_width=object["width"]/2
9797
+ local half_height=object["height"]/2
9798
+ local center={x=object["mapX"],y=object["mapY"]}
9799
+ local p1=UTILS.RotatePointAroundPivot({x=center.x-half_height,y=center.y+half_width},center,angle)
9800
+ local p2=UTILS.RotatePointAroundPivot({x=center.x+half_height,y=center.y+half_width},center,angle)
9801
+ local p3=UTILS.RotatePointAroundPivot({x=center.x+half_height,y=center.y-half_width},center,angle)
9802
+ local p4=UTILS.RotatePointAroundPivot({x=center.x-half_height,y=center.y-half_width},center,angle)
9803
+ points={p1,p2,p3,p4}
9804
+ else
9805
+ end
9806
+ end
9807
+ end
9808
+ end
9809
+ local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(DrawingName,points))
9810
+ _EVENTDISPATCHER:CreateEventNewZone(self)
9811
+ return self
9812
+ end
9247
9813
  function ZONE_POLYGON:FindByName(ZoneName)
9248
9814
  local ZoneFound=_DATABASE:FindZone(ZoneName)
9249
9815
  return ZoneFound
@@ -9430,6 +9996,7 @@ end
9430
9996
  function ZONE_POLYGON:IsNoneInZone()
9431
9997
  return self:CountScannedCoalitions()==0
9432
9998
  end
9999
+ end
9433
10000
  do
9434
10001
  ZONE_ELASTIC={
9435
10002
  ClassName="ZONE_ELASTIC",
@@ -9523,6 +10090,124 @@ table.remove(h,#h)
9523
10090
  return h
9524
10091
  end
9525
10092
  end
10093
+ ZONE_OVAL={
10094
+ ClassName="OVAL",
10095
+ ZoneName="",
10096
+ MajorAxis=nil,
10097
+ MinorAxis=nil,
10098
+ Angle=0,
10099
+ DrawPoly=nil
10100
+ }
10101
+ function ZONE_OVAL:New(name,vec2,major_axis,minor_axis,angle)
10102
+ self=BASE:Inherit(self,ZONE_BASE:New())
10103
+ self.ZoneName=name
10104
+ self.CenterVec2=vec2
10105
+ self.MajorAxis=major_axis
10106
+ self.MinorAxis=minor_axis
10107
+ self.Angle=angle or 0
10108
+ _DATABASE:AddZone(name,self)
10109
+ return self
10110
+ end
10111
+ function ZONE_OVAL:NewFromDrawing(DrawingName)
10112
+ self=BASE:Inherit(self,ZONE_BASE:New(DrawingName))
10113
+ for _,layer in pairs(env.mission.drawings.layers)do
10114
+ for _,object in pairs(layer["objects"])do
10115
+ if string.find(object["name"],DrawingName,1,true)then
10116
+ if object["polygonMode"]=="oval"then
10117
+ self.CenterVec2={x=object["mapX"],y=object["mapY"]}
10118
+ self.MajorAxis=object["r1"]
10119
+ self.MinorAxis=object["r2"]
10120
+ self.Angle=object["angle"]
10121
+ end
10122
+ end
10123
+ end
10124
+ end
10125
+ _DATABASE:AddZone(DrawingName,self)
10126
+ return self
10127
+ end
10128
+ function ZONE_OVAL:GetMajorAxis()
10129
+ return self.MajorAxis
10130
+ end
10131
+ function ZONE_OVAL:GetMinorAxis()
10132
+ return self.MinorAxis
10133
+ end
10134
+ function ZONE_OVAL:GetAngle()
10135
+ return self.Angle
10136
+ end
10137
+ function ZONE_OVAL:GetVec2()
10138
+ return self.CenterVec2
10139
+ end
10140
+ function ZONE_OVAL:IsVec2InZone(vec2)
10141
+ local cos,sin=math.cos,math.sin
10142
+ local dx=vec2.x-self.CenterVec2.x
10143
+ local dy=vec2.y-self.CenterVec2.y
10144
+ local rx=dx*cos(self.Angle)+dy*sin(self.Angle)
10145
+ local ry=-dx*sin(self.Angle)+dy*cos(self.Angle)
10146
+ return rx*rx/(self.MajorAxis*self.MajorAxis)+ry*ry/(self.MinorAxis*self.MinorAxis)<=1
10147
+ end
10148
+ function ZONE_OVAL:GetBoundingSquare()
10149
+ local min_x=self.CenterVec2.x-self.MajorAxis
10150
+ local min_y=self.CenterVec2.y-self.MinorAxis
10151
+ local max_x=self.CenterVec2.x+self.MajorAxis
10152
+ local max_y=self.CenterVec2.y+self.MinorAxis
10153
+ return{
10154
+ {x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y}
10155
+ }
10156
+ end
10157
+ function ZONE_OVAL:PointsOnEdge(num_points)
10158
+ num_points=num_points or 40
10159
+ local points={}
10160
+ local dtheta=2*math.pi/num_points
10161
+ for i=0,num_points-1 do
10162
+ local theta=i*dtheta
10163
+ local x=self.CenterVec2.x+self.MajorAxis*math.cos(theta)*math.cos(self.Angle)-self.MinorAxis*math.sin(theta)*math.sin(self.Angle)
10164
+ local y=self.CenterVec2.y+self.MajorAxis*math.cos(theta)*math.sin(self.Angle)+self.MinorAxis*math.sin(theta)*math.cos(self.Angle)
10165
+ table.insert(points,{x=x,y=y})
10166
+ end
10167
+ return points
10168
+ end
10169
+ function ZONE_OVAL:GetRandomVec2()
10170
+ local theta=math.rad(self.Angle)
10171
+ local random_point=math.sqrt(math.random())
10172
+ local phi=math.random()*2*math.pi
10173
+ local x_c=random_point*math.cos(phi)
10174
+ local y_c=random_point*math.sin(phi)
10175
+ local x_e=x_c*self.MajorAxis
10176
+ local y_e=y_c*self.MinorAxis
10177
+ local rx=(x_e*math.cos(theta)-y_e*math.sin(theta))+self.CenterVec2.x
10178
+ local ry=(x_e*math.sin(theta)+y_e*math.cos(theta))+self.CenterVec2.y
10179
+ return{x=rx,y=ry}
10180
+ end
10181
+ function ZONE_OVAL:GetRandomPointVec2()
10182
+ return POINT_VEC2:NewFromVec2(self:GetRandomVec2())
10183
+ end
10184
+ function ZONE_OVAL:GetRandomPointVec3()
10185
+ return POINT_VEC2:NewFromVec3(self:GetRandomVec2())
10186
+ end
10187
+ function ZONE_OVAL:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType)
10188
+ Coalition=Coalition or self:GetDrawCoalition()
10189
+ self:SetDrawCoalition(Coalition)
10190
+ Color=Color or self:GetColorRGB()
10191
+ Alpha=Alpha or 1
10192
+ self:SetColor(Color,Alpha)
10193
+ FillColor=FillColor or self:GetFillColorRGB()
10194
+ if not FillColor then
10195
+ UTILS.DeepCopy(Color)
10196
+ end
10197
+ FillAlpha=FillAlpha or self:GetFillColorAlpha()
10198
+ if not FillAlpha then
10199
+ FillAlpha=0.15
10200
+ end
10201
+ LineType=LineType or 1
10202
+ self:SetFillColor(FillColor,FillAlpha)
10203
+ self.DrawPoly=ZONE_POLYGON:NewFromPointsArray(self.ZoneName,self:PointsOnEdge(80))
10204
+ self.DrawPoly:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType)
10205
+ end
10206
+ function ZONE_OVAL:UndrawZone()
10207
+ if self.DrawPoly then
10208
+ self.DrawPoly:UndrawZone()
10209
+ end
10210
+ end
9526
10211
  do
9527
10212
  ZONE_AIRBASE={
9528
10213
  ClassName="ZONE_AIRBASE",
@@ -9877,6 +10562,23 @@ table.remove(points,#points)
9877
10562
  self:I(string.format("Register ZONE: %s (Polygon (free) drawing with %d vertices)",ZoneName,#points))
9878
10563
  local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,points)
9879
10564
  Zone:SetColor({1,0,0},0.15)
10565
+ Zone:SetFillColor({1,0,0},0.15)
10566
+ if objectData.colorString then
10567
+ local color=string.gsub(objectData.colorString,"^0x","")
10568
+ local r=tonumber(string.sub(color,1,2),16)/255
10569
+ local g=tonumber(string.sub(color,3,4),16)/255
10570
+ local b=tonumber(string.sub(color,5,6),16)/255
10571
+ local a=tonumber(string.sub(color,7,8),16)/255
10572
+ Zone:SetColor({r,g,b},a)
10573
+ end
10574
+ if objectData.fillColorString then
10575
+ local color=string.gsub(objectData.colorString,"^0x","")
10576
+ local r=tonumber(string.sub(color,1,2),16)/255
10577
+ local g=tonumber(string.sub(color,3,4),16)/255
10578
+ local b=tonumber(string.sub(color,5,6),16)/255
10579
+ local a=tonumber(string.sub(color,7,8),16)/255
10580
+ Zone:SetFillColor({r,g,b},a)
10581
+ end
9880
10582
  self.ZONENAMES[ZoneName]=ZoneName
9881
10583
  self:AddZone(ZoneName,Zone)
9882
10584
  elseif objectData.polygonMode and objectData.polygonMode=="rect"then
@@ -9892,6 +10594,22 @@ points[4]={x=vec2.x-h/2,y=vec2.y-w/2}
9892
10594
  self:I(string.format("Register ZONE: %s (Polygon (rect) drawing with %d vertices)",ZoneName,#points))
9893
10595
  local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,points)
9894
10596
  Zone:SetColor({1,0,0},0.15)
10597
+ if objectData.colorString then
10598
+ local color=string.gsub(objectData.colorString,"^0x","")
10599
+ local r=tonumber(string.sub(color,1,2),16)/255
10600
+ local g=tonumber(string.sub(color,3,4),16)/255
10601
+ local b=tonumber(string.sub(color,5,6),16)/255
10602
+ local a=tonumber(string.sub(color,7,8),16)/255
10603
+ Zone:SetColor({r,g,b},a)
10604
+ end
10605
+ if objectData.fillColorString then
10606
+ local color=string.gsub(objectData.colorString,"^0x","")
10607
+ local r=tonumber(string.sub(color,1,2),16)/255
10608
+ local g=tonumber(string.sub(color,3,4),16)/255
10609
+ local b=tonumber(string.sub(color,5,6),16)/255
10610
+ local a=tonumber(string.sub(color,7,8),16)/255
10611
+ Zone:SetFillColor({r,g,b},a)
10612
+ end
9895
10613
  self.ZONENAMES[ZoneName]=ZoneName
9896
10614
  self:AddZone(ZoneName,Zone)
9897
10615
  elseif objectData.lineMode and(objectData.lineMode=="segments"or objectData.lineMode=="segment"or objectData.lineMode=="free")and objectData.points and#objectData.points>=2 then
@@ -10697,6 +11415,26 @@ self.CallScheduler=SCHEDULER:New(self)
10697
11415
  self:SetEventPriority(2)
10698
11416
  return self
10699
11417
  end
11418
+ function SET_BASE:FilterFunction(ConditionFunction,...)
11419
+ local condition={}
11420
+ condition.func=ConditionFunction
11421
+ condition.arg={}
11422
+ if arg then
11423
+ condition.arg=arg
11424
+ end
11425
+ if not self.Filter.Functions then self.Filter.Functions={}end
11426
+ table.insert(self.Filter.Functions,condition)
11427
+ return self
11428
+ end
11429
+ function SET_BASE:_EvalFilterFunctions(Object)
11430
+ for _,_condition in pairs(self.Filter.Functions or{})do
11431
+ local condition=_condition
11432
+ if condition.func(Object,unpack(condition.arg))==false then
11433
+ return false
11434
+ end
11435
+ end
11436
+ return true
11437
+ end
10700
11438
  function SET_BASE:Clear(TriggerEvent)
10701
11439
  for Name,Object in pairs(self.Set)do
10702
11440
  self:Remove(Name,not TriggerEvent)
@@ -10834,7 +11572,10 @@ self:T3({LastObject})
10834
11572
  return LastObject
10835
11573
  end
10836
11574
  function SET_BASE:GetRandom()
10837
- local tablemax=table.maxn(self.Index)
11575
+ local tablemax=0
11576
+ for _,_ind in pairs(self.Index)do
11577
+ tablemax=tablemax+1
11578
+ end
10838
11579
  local RandomItem=self.Set[self.Index[math.random(1,tablemax)]]
10839
11580
  self:T3({RandomItem})
10840
11581
  return RandomItem
@@ -11057,6 +11798,7 @@ Categories=nil,
11057
11798
  Countries=nil,
11058
11799
  GroupPrefixes=nil,
11059
11800
  Zones=nil,
11801
+ Functions=nil,
11060
11802
  },
11061
11803
  FilterMeta={
11062
11804
  Coalitions={
@@ -11506,7 +12248,7 @@ MGroupActive=true
11506
12248
  end
11507
12249
  MGroupInclude=MGroupInclude and MGroupActive
11508
12250
  end
11509
- if self.Filter.Coalitions then
12251
+ if self.Filter.Coalitions and MGroupInclude then
11510
12252
  local MGroupCoalition=false
11511
12253
  for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
11512
12254
  self:T3({"Coalition:",MGroup:GetCoalition(),self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
@@ -11516,7 +12258,7 @@ end
11516
12258
  end
11517
12259
  MGroupInclude=MGroupInclude and MGroupCoalition
11518
12260
  end
11519
- if self.Filter.Categories then
12261
+ if self.Filter.Categories and MGroupInclude then
11520
12262
  local MGroupCategory=false
11521
12263
  for CategoryID,CategoryName in pairs(self.Filter.Categories)do
11522
12264
  self:T3({"Category:",MGroup:GetCategory(),self.FilterMeta.Categories[CategoryName],CategoryName})
@@ -11526,7 +12268,7 @@ end
11526
12268
  end
11527
12269
  MGroupInclude=MGroupInclude and MGroupCategory
11528
12270
  end
11529
- if self.Filter.Countries then
12271
+ if self.Filter.Countries and MGroupInclude then
11530
12272
  local MGroupCountry=false
11531
12273
  for CountryID,CountryName in pairs(self.Filter.Countries)do
11532
12274
  self:T3({"Country:",MGroup:GetCountry(),CountryName})
@@ -11536,7 +12278,7 @@ end
11536
12278
  end
11537
12279
  MGroupInclude=MGroupInclude and MGroupCountry
11538
12280
  end
11539
- if self.Filter.GroupPrefixes then
12281
+ if self.Filter.GroupPrefixes and MGroupInclude then
11540
12282
  local MGroupPrefix=false
11541
12283
  for GroupPrefixId,GroupPrefix in pairs(self.Filter.GroupPrefixes)do
11542
12284
  self:T3({"Prefix:",string.find(MGroup:GetName(),GroupPrefix,1),GroupPrefix})
@@ -11546,7 +12288,7 @@ end
11546
12288
  end
11547
12289
  MGroupInclude=MGroupInclude and MGroupPrefix
11548
12290
  end
11549
- if self.Filter.Zones then
12291
+ if self.Filter.Zones and MGroupInclude then
11550
12292
  local MGroupZone=false
11551
12293
  for ZoneName,Zone in pairs(self.Filter.Zones)do
11552
12294
  if MGroup:IsInZone(Zone)then
@@ -11555,6 +12297,11 @@ end
11555
12297
  end
11556
12298
  MGroupInclude=MGroupInclude and MGroupZone
11557
12299
  end
12300
+ if self.Filter.Functions and MGroupInclude then
12301
+ local MGroupFunc=false
12302
+ MGroupFunc=self:_EvalFilterFunctions(MGroup)
12303
+ MGroupInclude=MGroupInclude and MGroupFunc
12304
+ end
11558
12305
  self:T2(MGroupInclude)
11559
12306
  return MGroupInclude
11560
12307
  end
@@ -11595,6 +12342,7 @@ Types=nil,
11595
12342
  Countries=nil,
11596
12343
  UnitPrefixes=nil,
11597
12344
  Zones=nil,
12345
+ Functions=nil,
11598
12346
  },
11599
12347
  FilterMeta={
11600
12348
  Coalitions={
@@ -11983,46 +12731,40 @@ self:F({MaxThreatLevelA2G=MaxThreatLevelA2G,MaxThreatText=MaxThreatText})
11983
12731
  return MaxThreatLevelA2G,MaxThreatText
11984
12732
  end
11985
12733
  function SET_UNIT:GetCoordinate()
11986
- local Coordinate=nil
11987
- local unit=self:GetRandom()
11988
- if self:Count()==1 and unit then
11989
- return unit:GetCoordinate()
12734
+ local function GetSetVec3(units)
12735
+ local x=0
12736
+ local y=0
12737
+ local z=0
12738
+ local n=0
12739
+ for _,unit in pairs(units)do
12740
+ local vec3=nil
12741
+ if unit and unit:IsAlive()then
12742
+ vec3=unit:GetVec3()
11990
12743
  end
11991
- if unit then
11992
- local Coordinate=unit:GetCoordinate()
11993
- local x1=Coordinate.x
11994
- local x2=Coordinate.x
11995
- local y1=Coordinate.y
11996
- local y2=Coordinate.y
11997
- local z1=Coordinate.z
11998
- local z2=Coordinate.z
11999
- local MaxVelocity=0
12000
- local AvgHeading=nil
12001
- local MovingCount=0
12002
- for UnitName,UnitData in pairs(self:GetAliveSet())do
12003
- local Unit=UnitData
12004
- local Coordinate=Unit:GetCoordinate()
12005
- x1=(Coordinate.x<x1)and Coordinate.x or x1
12006
- x2=(Coordinate.x>x2)and Coordinate.x or x2
12007
- y1=(Coordinate.y<y1)and Coordinate.y or y1
12008
- y2=(Coordinate.y>y2)and Coordinate.y or y2
12009
- z1=(Coordinate.y<z1)and Coordinate.z or z1
12010
- z2=(Coordinate.y>z2)and Coordinate.z or z2
12011
- local Velocity=Coordinate:GetVelocity()
12012
- if Velocity~=0 then
12013
- MaxVelocity=(MaxVelocity<Velocity)and Velocity or MaxVelocity
12014
- local Heading=Coordinate:GetHeading()
12015
- AvgHeading=AvgHeading and(AvgHeading+Heading)or Heading
12016
- MovingCount=MovingCount+1
12744
+ if vec3 then
12745
+ x=x+vec3.x
12746
+ y=y+vec3.y
12747
+ z=z+vec3.z
12748
+ n=n+1
12017
12749
  end
12018
12750
  end
12019
- AvgHeading=AvgHeading and(AvgHeading/MovingCount)
12020
- Coordinate.x=(x2-x1)/2+x1
12021
- Coordinate.y=(y2-y1)/2+y1
12022
- Coordinate.z=(z2-z1)/2+z1
12023
- Coordinate:SetHeading(AvgHeading)
12024
- Coordinate:SetVelocity(MaxVelocity)
12025
- self:F({Coordinate=Coordinate})
12751
+ if n>0 then
12752
+ local Vec3={x=x/n,y=y/n,z=z/n}
12753
+ return Vec3
12754
+ end
12755
+ return nil
12756
+ end
12757
+ local Coordinate=nil
12758
+ local Vec3=GetSetVec3(self.Set)
12759
+ if Vec3 then
12760
+ Coordinate=COORDINATE:NewFromVec3(Vec3)
12761
+ end
12762
+ if Coordinate then
12763
+ local heading=self:GetHeading()or 0
12764
+ local velocity=self:GetVelocity()or 0
12765
+ Coordinate:SetHeading(heading)
12766
+ Coordinate:SetVelocity(velocity)
12767
+ self:I(UTILS.PrintTableToLog(Coordinate))
12026
12768
  end
12027
12769
  return Coordinate
12028
12770
  end
@@ -12142,7 +12884,7 @@ MUnitActive=true
12142
12884
  end
12143
12885
  MUnitInclude=MUnitInclude and MUnitActive
12144
12886
  end
12145
- if self.Filter.Coalitions then
12887
+ if self.Filter.Coalitions and MUnitInclude then
12146
12888
  local MUnitCoalition=false
12147
12889
  for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
12148
12890
  self:F({"Coalition:",MUnit:GetCoalition(),self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
@@ -12152,7 +12894,7 @@ end
12152
12894
  end
12153
12895
  MUnitInclude=MUnitInclude and MUnitCoalition
12154
12896
  end
12155
- if self.Filter.Categories then
12897
+ if self.Filter.Categories and MUnitInclude then
12156
12898
  local MUnitCategory=false
12157
12899
  for CategoryID,CategoryName in pairs(self.Filter.Categories)do
12158
12900
  self:T3({"Category:",MUnit:GetDesc().category,self.FilterMeta.Categories[CategoryName],CategoryName})
@@ -12162,7 +12904,7 @@ end
12162
12904
  end
12163
12905
  MUnitInclude=MUnitInclude and MUnitCategory
12164
12906
  end
12165
- if self.Filter.Types then
12907
+ if self.Filter.Types and MUnitInclude then
12166
12908
  local MUnitType=false
12167
12909
  for TypeID,TypeName in pairs(self.Filter.Types)do
12168
12910
  self:T3({"Type:",MUnit:GetTypeName(),TypeName})
@@ -12172,7 +12914,7 @@ end
12172
12914
  end
12173
12915
  MUnitInclude=MUnitInclude and MUnitType
12174
12916
  end
12175
- if self.Filter.Countries then
12917
+ if self.Filter.Countries and MUnitInclude then
12176
12918
  local MUnitCountry=false
12177
12919
  for CountryID,CountryName in pairs(self.Filter.Countries)do
12178
12920
  self:T3({"Country:",MUnit:GetCountry(),CountryName})
@@ -12182,7 +12924,7 @@ end
12182
12924
  end
12183
12925
  MUnitInclude=MUnitInclude and MUnitCountry
12184
12926
  end
12185
- if self.Filter.UnitPrefixes then
12927
+ if self.Filter.UnitPrefixes and MUnitInclude then
12186
12928
  local MUnitPrefix=false
12187
12929
  for UnitPrefixId,UnitPrefix in pairs(self.Filter.UnitPrefixes)do
12188
12930
  self:T3({"Prefix:",string.find(MUnit:GetName(),UnitPrefix,1),UnitPrefix})
@@ -12192,7 +12934,7 @@ end
12192
12934
  end
12193
12935
  MUnitInclude=MUnitInclude and MUnitPrefix
12194
12936
  end
12195
- if self.Filter.RadarTypes then
12937
+ if self.Filter.RadarTypes and MUnitInclude then
12196
12938
  local MUnitRadar=false
12197
12939
  for RadarTypeID,RadarType in pairs(self.Filter.RadarTypes)do
12198
12940
  self:T3({"Radar:",RadarType})
@@ -12205,7 +12947,7 @@ end
12205
12947
  end
12206
12948
  MUnitInclude=MUnitInclude and MUnitRadar
12207
12949
  end
12208
- if self.Filter.SEAD then
12950
+ if self.Filter.SEAD and MUnitInclude then
12209
12951
  local MUnitSEAD=false
12210
12952
  if MUnit:HasSEAD()==true then
12211
12953
  self:T3("SEAD Found")
@@ -12214,7 +12956,7 @@ end
12214
12956
  MUnitInclude=MUnitInclude and MUnitSEAD
12215
12957
  end
12216
12958
  end
12217
- if self.Filter.Zones then
12959
+ if self.Filter.Zones and MUnitInclude then
12218
12960
  local MGroupZone=false
12219
12961
  for ZoneName,Zone in pairs(self.Filter.Zones)do
12220
12962
  self:T3("Zone:",ZoneName)
@@ -12224,6 +12966,10 @@ end
12224
12966
  end
12225
12967
  MUnitInclude=MUnitInclude and MGroupZone
12226
12968
  end
12969
+ if self.Filter.Functions and MUnitInclude then
12970
+ local MUnitFunc=self:_EvalFilterFunctions(MUnit)
12971
+ MUnitInclude=MUnitInclude and MUnitFunc
12972
+ end
12227
12973
  self:T2(MUnitInclude)
12228
12974
  return MUnitInclude
12229
12975
  end
@@ -12974,7 +13720,7 @@ MClientActive=true
12974
13720
  end
12975
13721
  MClientInclude=MClientInclude and MClientActive
12976
13722
  end
12977
- if self.Filter.Coalitions then
13723
+ if self.Filter.Coalitions and MClientInclude then
12978
13724
  local MClientCoalition=false
12979
13725
  for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
12980
13726
  local ClientCoalitionID=_DATABASE:GetCoalitionFromClientTemplate(MClientName)
@@ -12986,7 +13732,7 @@ end
12986
13732
  self:T({"Evaluated Coalition",MClientCoalition})
12987
13733
  MClientInclude=MClientInclude and MClientCoalition
12988
13734
  end
12989
- if self.Filter.Categories then
13735
+ if self.Filter.Categories and MClientInclude then
12990
13736
  local MClientCategory=false
12991
13737
  for CategoryID,CategoryName in pairs(self.Filter.Categories)do
12992
13738
  local ClientCategoryID=_DATABASE:GetCategoryFromClientTemplate(MClientName)
@@ -12998,7 +13744,7 @@ end
12998
13744
  self:T({"Evaluated Category",MClientCategory})
12999
13745
  MClientInclude=MClientInclude and MClientCategory
13000
13746
  end
13001
- if self.Filter.Types then
13747
+ if self.Filter.Types and MClientInclude then
13002
13748
  local MClientType=false
13003
13749
  for TypeID,TypeName in pairs(self.Filter.Types)do
13004
13750
  self:T3({"Type:",MClient:GetTypeName(),TypeName})
@@ -13009,7 +13755,7 @@ end
13009
13755
  self:T({"Evaluated Type",MClientType})
13010
13756
  MClientInclude=MClientInclude and MClientType
13011
13757
  end
13012
- if self.Filter.Countries then
13758
+ if self.Filter.Countries and MClientInclude then
13013
13759
  local MClientCountry=false
13014
13760
  for CountryID,CountryName in pairs(self.Filter.Countries)do
13015
13761
  local ClientCountryID=_DATABASE:GetCountryFromClientTemplate(MClientName)
@@ -13021,7 +13767,7 @@ end
13021
13767
  self:T({"Evaluated Country",MClientCountry})
13022
13768
  MClientInclude=MClientInclude and MClientCountry
13023
13769
  end
13024
- if self.Filter.ClientPrefixes then
13770
+ if self.Filter.ClientPrefixes and MClientInclude then
13025
13771
  local MClientPrefix=false
13026
13772
  for ClientPrefixId,ClientPrefix in pairs(self.Filter.ClientPrefixes)do
13027
13773
  self:T3({"Prefix:",string.find(MClient.UnitName,ClientPrefix,1),ClientPrefix})
@@ -13032,7 +13778,7 @@ end
13032
13778
  self:T({"Evaluated Prefix",MClientPrefix})
13033
13779
  MClientInclude=MClientInclude and MClientPrefix
13034
13780
  end
13035
- if self.Filter.Zones then
13781
+ if self.Filter.Zones and MClientInclude then
13036
13782
  local MClientZone=false
13037
13783
  for ZoneName,Zone in pairs(self.Filter.Zones)do
13038
13784
  self:T3("Zone:",ZoneName)
@@ -13043,7 +13789,7 @@ end
13043
13789
  end
13044
13790
  MClientInclude=MClientInclude and MClientZone
13045
13791
  end
13046
- if self.Filter.Playernames then
13792
+ if self.Filter.Playernames and MClientInclude then
13047
13793
  local MClientPlayername=false
13048
13794
  local playername=MClient:GetPlayerName()or"Unknown"
13049
13795
  for _,_Playername in pairs(self.Filter.Playernames)do
@@ -13054,7 +13800,7 @@ end
13054
13800
  self:T({"Evaluated Playername",MClientPlayername})
13055
13801
  MClientInclude=MClientInclude and MClientPlayername
13056
13802
  end
13057
- if self.Filter.Callsigns then
13803
+ if self.Filter.Callsigns and MClientInclude then
13058
13804
  local MClientCallsigns=false
13059
13805
  local callsign=MClient:GetCallsign()
13060
13806
  for _,_Callsign in pairs(self.Filter.Callsigns)do
@@ -13065,6 +13811,10 @@ end
13065
13811
  self:T({"Evaluated Callsign",MClientCallsigns})
13066
13812
  MClientInclude=MClientInclude and MClientCallsigns
13067
13813
  end
13814
+ if self.Filter.Functions and MClientInclude then
13815
+ local MClientFunc=self:_EvalFilterFunctions(MClient)
13816
+ MClientInclude=MClientInclude and MClientFunc
13817
+ end
13068
13818
  end
13069
13819
  self:T2(MClientInclude)
13070
13820
  return MClientInclude
@@ -13384,7 +14134,6 @@ return AirbaseFound
13384
14134
  end
13385
14135
  function SET_AIRBASE:GetRandomAirbase()
13386
14136
  local RandomAirbase=self:GetRandom()
13387
- self:F({RandomAirbase=RandomAirbase:GetName()})
13388
14137
  return RandomAirbase
13389
14138
  end
13390
14139
  function SET_AIRBASE:FilterCoalitions(Coalitions)
@@ -13474,7 +14223,7 @@ end
13474
14223
  self:T({"Evaluated Coalition",MAirbaseCoalition})
13475
14224
  MAirbaseInclude=MAirbaseInclude and MAirbaseCoalition
13476
14225
  end
13477
- if self.Filter.Categories then
14226
+ if self.Filter.Categories and MAirbaseInclude then
13478
14227
  local MAirbaseCategory=false
13479
14228
  for CategoryID,CategoryName in pairs(self.Filter.Categories)do
13480
14229
  local AirbaseCategoryID=_DATABASE:GetCategoryFromAirbase(MAirbaseName)
@@ -14557,7 +15306,7 @@ MGroupActive=true
14557
15306
  end
14558
15307
  MGroupInclude=MGroupInclude and MGroupActive
14559
15308
  end
14560
- if self.Filter.Coalitions then
15309
+ if self.Filter.Coalitions and MGroupInclude then
14561
15310
  local MGroupCoalition=false
14562
15311
  for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
14563
15312
  if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MGroup:GetCoalition()then
@@ -14566,7 +15315,7 @@ end
14566
15315
  end
14567
15316
  MGroupInclude=MGroupInclude and MGroupCoalition
14568
15317
  end
14569
- if self.Filter.Categories then
15318
+ if self.Filter.Categories and MGroupInclude then
14570
15319
  local MGroupCategory=false
14571
15320
  for CategoryID,CategoryName in pairs(self.Filter.Categories)do
14572
15321
  if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MGroup:GetCategory()then
@@ -14575,7 +15324,7 @@ end
14575
15324
  end
14576
15325
  MGroupInclude=MGroupInclude and MGroupCategory
14577
15326
  end
14578
- if self.Filter.Countries then
15327
+ if self.Filter.Countries and MGroupInclude then
14579
15328
  local MGroupCountry=false
14580
15329
  for CountryID,CountryName in pairs(self.Filter.Countries)do
14581
15330
  if country.id[CountryName]==MGroup:GetCountry()then
@@ -14584,7 +15333,7 @@ end
14584
15333
  end
14585
15334
  MGroupInclude=MGroupInclude and MGroupCountry
14586
15335
  end
14587
- if self.Filter.GroupPrefixes then
15336
+ if self.Filter.GroupPrefixes and MGroupInclude then
14588
15337
  local MGroupPrefix=false
14589
15338
  for GroupPrefixId,GroupPrefix in pairs(self.Filter.GroupPrefixes)do
14590
15339
  if string.find(MGroup:GetName(),GroupPrefix:gsub("-","%%-"),1)then
@@ -15950,11 +16699,14 @@ Color,FillColor,LineType,ReadOnly,Text or"")
15950
16699
  else
15951
16700
  local s=string.format("trigger.action.markupToAll(7, %d, %d,",Coalition,MarkID)
15952
16701
  for _,vec in pairs(vecs)do
15953
- s=s..string.format("%s,",UTILS._OneLineSerialize(vec))
16702
+ s=s..string.format("{x=%.1f, y=%.1f, z=%.1f},",vec.x,vec.y,vec.z)
15954
16703
  end
15955
- s=s..string.format("%s, %s, %s, %s",UTILS._OneLineSerialize(Color),UTILS._OneLineSerialize(FillColor),tostring(LineType),tostring(ReadOnly))
15956
- if Text and Text~=""then
15957
- s=s..string.format(", \"%s\"",Text)
16704
+ s=s..string.format("{%.3f, %.3f, %.3f, %.3f},",Color[1],Color[2],Color[3],Color[4])
16705
+ s=s..string.format("{%.3f, %.3f, %.3f, %.3f},",FillColor[1],FillColor[2],FillColor[3],FillColor[4])
16706
+ s=s..string.format("%d,",LineType or 1)
16707
+ s=s..string.format("%s",tostring(ReadOnly))
16708
+ if Text and type(Text)=="string"and string.len(Text)>0 then
16709
+ s=s..string.format(", \"%s\"",tostring(Text))
15958
16710
  end
15959
16711
  s=s..")"
15960
16712
  local success=UTILS.DoString(s)
@@ -16213,6 +16965,9 @@ end
16213
16965
  end
16214
16966
  return BRAANATO
16215
16967
  end
16968
+ function COORDINATE.GetBullseyeCoordinate(Coalition)
16969
+ return COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition))
16970
+ end
16216
16971
  function COORDINATE:ToStringBULLS(Coalition,Settings,MagVar)
16217
16972
  local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition))
16218
16973
  local DirectionVec3=BullsCoordinate:GetDirectionVec3(self)
@@ -16261,6 +17016,36 @@ local lat,lon=coord.LOtoLL(self:GetVec3())
16261
17016
  local MGRS=coord.LLtoMGRS(lat,lon)
16262
17017
  return"MGRS "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy)
16263
17018
  end
17019
+ function COORDINATE:NewFromMGRSString(MGRSString)
17020
+ local myparts=UTILS.Split(MGRSString," ")
17021
+ local northing=tostring(myparts[5])or""
17022
+ local easting=tostring(myparts[4])or""
17023
+ if string.len(easting)<5 then easting=easting..string.rep("0",5-string.len(easting))end
17024
+ if string.len(northing)<5 then northing=northing..string.rep("0",5-string.len(northing))end
17025
+ local MGRS={
17026
+ UTMZone=myparts[2],
17027
+ MGRSDigraph=myparts[3],
17028
+ Easting=easting,
17029
+ Northing=northing,
17030
+ }
17031
+ local lat,lon=coord.MGRStoLL(MGRS)
17032
+ local point=coord.LLtoLO(lat,lon,0)
17033
+ local coord=COORDINATE:NewFromVec2({x=point.x,y=point.z})
17034
+ return coord
17035
+ end
17036
+ function COORDINATE:NewFromMGRS(UTMZone,MGRSDigraph,Easting,Northing)
17037
+ if string.len(Easting)<5 then Easting=Easting..string.rep("0",5-string.len(Easting))end
17038
+ if string.len(Northing)<5 then Northing=Northing..string.rep("0",5-string.len(Northing))end
17039
+ local MGRS={
17040
+ UTMZone=UTMZone,
17041
+ MGRSDigraph=MGRSDigraph,
17042
+ Easting=Easting,
17043
+ Northing=Northing,
17044
+ }
17045
+ local lat,lon=coord.MGRStoLL(MGRS)
17046
+ local point=coord.LLtoLO(lat,lon,0)
17047
+ local coord=COORDINATE:NewFromVec2({x=point.x,y=point.z})
17048
+ end
16264
17049
  function COORDINATE:ToStringFromRP(ReferenceCoord,ReferenceName,Controllable,Settings,MagVar)
16265
17050
  self:F2({ReferenceCoord=ReferenceCoord,ReferenceName=ReferenceName})
16266
17051
  local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
@@ -16792,7 +17577,7 @@ end
16792
17577
  if CoalitionSide then
16793
17578
  if self.MessageDuration~=0 then
16794
17579
  self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
16795
- trigger.action.outTextForCoalition(CoalitionSide,self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
17580
+ trigger.action.outTextForCoalition(CoalitionSide,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
16796
17581
  end
16797
17582
  end
16798
17583
  self.CoalitionSide=CoalitionSide
@@ -16848,7 +17633,6 @@ _MESSAGESRS.Culture=Culture or"en-GB"
16848
17633
  _MESSAGESRS.MSRS:SetGender(Gender)
16849
17634
  _MESSAGESRS.Gender=Gender or"female"
16850
17635
  _MESSAGESRS.MSRS:SetGoogle(PathToCredentials)
16851
- _MESSAGESRS.google=PathToCredentials
16852
17636
  _MESSAGESRS.MSRS:SetLabel(Label or"MESSAGE")
16853
17637
  _MESSAGESRS.label=Label or"MESSAGE"
16854
17638
  _MESSAGESRS.MSRS:SetPort(Port or 5002)
@@ -17439,6 +18223,7 @@ self.SpawnInitModexPrefix=nil
17439
18223
  self.SpawnInitModexPostfix=nil
17440
18224
  self.SpawnInitAirbase=nil
17441
18225
  self.TweakedTemplate=false
18226
+ self.SpawnRandomCallsign=false
17442
18227
  self.SpawnGroups={}
17443
18228
  else
17444
18229
  error("SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'")
@@ -17728,6 +18513,18 @@ self:_RandomizeZones(SpawnGroupID)
17728
18513
  end
17729
18514
  return self
17730
18515
  end
18516
+ function SPAWN:InitRandomizeCallsign()
18517
+ self.SpawnRandomCallsign=true
18518
+ return self
18519
+ end
18520
+ function SPAWN:InitCallSign(ID,Name,Minor,Major)
18521
+ self.SpawnInitCallSign=true
18522
+ self.SpawnInitCallSignID=ID or 1
18523
+ self.SpawnInitCallSignMinor=Minor or 1
18524
+ self.SpawnInitCallSignMajor=Major or 1
18525
+ self.SpawnInitCallSignName=string.lower(Name)or"enfield"
18526
+ return self
18527
+ end
17731
18528
  function SPAWN:InitPositionCoordinate(Coordinate)
17732
18529
  self:T({self.SpawnTemplatePrefix,Coordinate:GetVec2()})
17733
18530
  self:InitPositionVec2(Coordinate:GetVec2())
@@ -18849,19 +19646,133 @@ SpawnTemplate.units[UnitID].name=string.format('%s#%03d-%02d',UnitPrefix,SpawnIn
18849
19646
  SpawnTemplate.units[UnitID].unitId=nil
18850
19647
  end
18851
19648
  end
19649
+ if self.SpawnRandomCallsign and SpawnTemplate.units[1].callsign then
19650
+ if type(SpawnTemplate.units[1].callsign)~="number"then
19651
+ local min=1
19652
+ local max=8
19653
+ local ctable=CALLSIGN.Aircraft
19654
+ if string.find(SpawnTemplate.units[1].type,"A-10",1,true)then
19655
+ max=12
19656
+ end
19657
+ if string.find(SpawnTemplate.units[1].type,"18",1,true)then
19658
+ min=9
19659
+ max=20
19660
+ ctable=CALLSIGN.F18
19661
+ end
19662
+ if string.find(SpawnTemplate.units[1].type,"16",1,true)then
19663
+ min=9
19664
+ max=20
19665
+ ctable=CALLSIGN.F16
19666
+ end
19667
+ if SpawnTemplate.units[1].type=="F-15E"then
19668
+ min=9
19669
+ max=18
19670
+ ctable=CALLSIGN.F15E
19671
+ end
19672
+ local callsignnr=math.random(min,max)
19673
+ local callsignname="Enfield"
19674
+ for name,value in pairs(ctable)do
19675
+ if value==callsignnr then
19676
+ callsignname=name
19677
+ end
19678
+ end
19679
+ for UnitID=1,#SpawnTemplate.units do
19680
+ SpawnTemplate.units[UnitID].callsign[1]=callsignnr
19681
+ SpawnTemplate.units[UnitID].callsign[2]=UnitID
19682
+ SpawnTemplate.units[UnitID].callsign[3]="1"
19683
+ SpawnTemplate.units[UnitID].callsign["name"]=tostring(callsignname)..tostring(UnitID).."1"
19684
+ end
19685
+ else
19686
+ for UnitID=1,#SpawnTemplate.units do
19687
+ SpawnTemplate.units[UnitID].callsign=math.random(1,999)
19688
+ end
19689
+ end
19690
+ end
19691
+ if self.SpawnInitCallSign then
19692
+ for UnitID=1,#SpawnTemplate.units do
19693
+ local Callsign=SpawnTemplate.units[UnitID].callsign
19694
+ if Callsign and type(Callsign)~="number"then
19695
+ SpawnTemplate.units[UnitID].callsign[1]=self.SpawnInitCallSignID
19696
+ SpawnTemplate.units[UnitID].callsign[2]=self.SpawnInitCallSignMinor
19697
+ SpawnTemplate.units[UnitID].callsign[3]=self.SpawnInitCallSignMajor
19698
+ SpawnTemplate.units[UnitID].callsign["name"]=string.format("%s%d%d",self.SpawnInitCallSignName,self.SpawnInitCallSignMinor,self.SpawnInitCallSignMajor)
19699
+ end
19700
+ end
19701
+ end
18852
19702
  for UnitID=1,#SpawnTemplate.units do
18853
19703
  local Callsign=SpawnTemplate.units[UnitID].callsign
18854
19704
  if Callsign then
18855
- if type(Callsign)~="number"then
19705
+ if type(Callsign)~="number"and not self.SpawnInitCallSign then
18856
19706
  Callsign[2]=((SpawnIndex-1)%10)+1
18857
19707
  local CallsignName=SpawnTemplate.units[UnitID].callsign["name"]
18858
19708
  CallsignName=string.match(CallsignName,"^(%a+)")
18859
19709
  local CallsignLen=CallsignName:len()
19710
+ SpawnTemplate.units[UnitID].callsign[2]=UnitID
18860
19711
  SpawnTemplate.units[UnitID].callsign["name"]=CallsignName:sub(1,CallsignLen)..SpawnTemplate.units[UnitID].callsign[2]..SpawnTemplate.units[UnitID].callsign[3]
18861
- else
19712
+ elseif type(Callsign)=="number"then
18862
19713
  SpawnTemplate.units[UnitID].callsign=Callsign+SpawnIndex
18863
19714
  end
18864
19715
  end
19716
+ local AddProps=SpawnTemplate.units[UnitID].AddPropAircraft
19717
+ if AddProps then
19718
+ if SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 then
19719
+ if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16)~=nil then
19720
+ local octal=SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16
19721
+ local decimal=UTILS.OctalToDecimal(octal)+UnitID-1
19722
+ SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",UTILS.DecimalToOctal(decimal))
19723
+ else
19724
+ local STN=math.floor(UTILS.RandomGaussian(4088/2,nil,1000,4088))
19725
+ STN=STN+UnitID-1
19726
+ local OSTN=UTILS.DecimalToOctal(STN)
19727
+ SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",OSTN)
19728
+ end
19729
+ end
19730
+ if SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN then
19731
+ if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN)~=nil then
19732
+ local octal=SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN
19733
+ local decimal=UTILS.OctalToDecimal(octal)+UnitID-1
19734
+ SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",UTILS.DecimalToOctal(decimal))
19735
+ else
19736
+ local STN=math.floor(UTILS.RandomGaussian(504/2,nil,100,504))
19737
+ STN=STN+UnitID-1
19738
+ local OSTN=UTILS.DecimalToOctal(STN)
19739
+ SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",OSTN)
19740
+ end
19741
+ end
19742
+ if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber and type(Callsign)~="number"then
19743
+ SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber=SpawnTemplate.units[UnitID].callsign[2]..SpawnTemplate.units[UnitID].callsign[3]
19744
+ end
19745
+ if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel and type(Callsign)~="number"then
19746
+ local CallsignName=SpawnTemplate.units[UnitID].callsign["name"]
19747
+ CallsignName=string.match(CallsignName,"^(%a+)")
19748
+ local label="NY"
19749
+ if not string.find(CallsignName," ")then
19750
+ label=string.upper(string.match(CallsignName,"^%a")..string.match(CallsignName,"%a$"))
19751
+ end
19752
+ SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel=label
19753
+ end
19754
+ if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.settings then
19755
+ SpawnTemplate.units[UnitID].datalinks.Link16.settings.flightLead=UnitID==1 and true or false
19756
+ end
19757
+ if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.SADL and SpawnTemplate.units[UnitID].datalinks.SADL.settings then
19758
+ SpawnTemplate.units[UnitID].datalinks.SADL.settings.flightLead=UnitID==1 and true or false
19759
+ end
19760
+ end
19761
+ end
19762
+ for UnitID=1,#SpawnTemplate.units do
19763
+ if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.network then
19764
+ local team={}
19765
+ local isF16=string.find(SpawnTemplate.units[UnitID].type,"F-16",1,true)and true or false
19766
+ for ID=1,#SpawnTemplate.units do
19767
+ local member={}
19768
+ member.missionUnitId=ID
19769
+ if isF16 then
19770
+ member.TDOA=true
19771
+ end
19772
+ table.insert(team,member)
19773
+ end
19774
+ SpawnTemplate.units[UnitID].datalinks.Link16.network.teamMembers=team
19775
+ end
18865
19776
  end
18866
19777
  self:T3({"Template:",SpawnTemplate})
18867
19778
  return SpawnTemplate
@@ -23057,32 +23968,35 @@ self:SetOption(AI.Option.Air.id.PROHIBIT_AB,Prohibit)
23057
23968
  end
23058
23969
  return self
23059
23970
  end
23060
- function CONTROLLABLE:OptionECM_Never()
23971
+ function CONTROLLABLE:OptionECM(ECMvalue)
23061
23972
  self:F2({self.ControllableName})
23973
+ local DCSControllable=self:GetDCSObject()
23974
+ if DCSControllable then
23975
+ local Controller=self:_GetController()
23062
23976
  if self:IsAir()then
23063
- self:SetOption(AI.Option.Air.id.ECM_USING,0)
23977
+ Controller:setOption(AI.Option.Air.id.ECM_USING,ECMvalue or 1)
23978
+ end
23064
23979
  end
23065
23980
  return self
23066
23981
  end
23067
- function CONTROLLABLE:OptionECM_OnlyLockByRadar()
23982
+ function CONTROLLABLE:OptionECM_Never()
23068
23983
  self:F2({self.ControllableName})
23069
- if self:IsAir()then
23070
- self:SetOption(AI.Option.Air.id.ECM_USING,1)
23984
+ self:OptionECM(0)
23985
+ return self
23071
23986
  end
23987
+ function CONTROLLABLE:OptionECM_OnlyLockByRadar()
23988
+ self:F2({self.ControllableName})
23989
+ self:OptionECM(1)
23072
23990
  return self
23073
23991
  end
23074
23992
  function CONTROLLABLE:OptionECM_DetectedLockByRadar()
23075
23993
  self:F2({self.ControllableName})
23076
- if self:IsAir()then
23077
- self:SetOption(AI.Option.Air.id.ECM_USING,2)
23078
- end
23994
+ self:OptionECM(2)
23079
23995
  return self
23080
23996
  end
23081
23997
  function CONTROLLABLE:OptionECM_AlwaysOn()
23082
23998
  self:F2({self.ControllableName})
23083
- if self:IsAir()then
23084
- self:SetOption(AI.Option.Air.id.ECM_USING,3)
23085
- end
23999
+ self:OptionECM(3)
23086
24000
  return self
23087
24001
  end
23088
24002
  function CONTROLLABLE:WayPointInitialize(WayPoints)
@@ -23166,7 +24080,7 @@ if DCSControllable then
23166
24080
  local Controller=self:_GetController()
23167
24081
  if Controller then
23168
24082
  if self:IsAir()then
23169
- self:SetOption(AI.Option.Air.val.MISSILE_ATTACK,range)
24083
+ self:SetOption(AI.Option.Air.id.MISSILE_ATTACK,range)
23170
24084
  end
23171
24085
  end
23172
24086
  return self
@@ -23191,12 +24105,19 @@ return self
23191
24105
  end
23192
24106
  return nil
23193
24107
  end
23194
- function CONTROLLABLE:RelocateGroundRandomInRadius(speed,radius,onroad,shortcut,formation)
24108
+ function CONTROLLABLE:RelocateGroundRandomInRadius(speed,radius,onroad,shortcut,formation,onland)
23195
24109
  self:F2({self.ControllableName})
23196
24110
  local _coord=self:GetCoordinate()
23197
24111
  local _radius=radius or 500
23198
24112
  local _speed=speed or 20
23199
24113
  local _tocoord=_coord:GetRandomCoordinateInRadius(_radius,100)
24114
+ if onland then
24115
+ for i=1,50 do
24116
+ local island=_tocoord:GetSurfaceType()==land.SurfaceType.LAND and true or false
24117
+ if island then break end
24118
+ _tocoord=_coord:GetRandomCoordinateInRadius(_radius,100)
24119
+ end
24120
+ end
23200
24121
  local _onroad=onroad or true
23201
24122
  local _grptsk={}
23202
24123
  local _candoroad=false
@@ -24100,7 +25021,7 @@ local Task={
24100
25021
  table.insert(TaskAerobatics.params["maneuversSequency"],Task)
24101
25022
  return TaskAerobatics
24102
25023
  end
24103
- function CONTROLLABLE:PatrolRaceTrack(Point1,Point2,Altitude,Speed,Formation,Delay)
25024
+ function CONTROLLABLE:PatrolRaceTrack(Point1,Point2,Altitude,Speed,Formation,AGL,Delay)
24104
25025
  local PatrolGroup=self
24105
25026
  if not self:IsInstanceOf("GROUP")then
24106
25027
  PatrolGroup=self:GetGroup()
@@ -24114,8 +25035,10 @@ end
24114
25035
  local FromCoord=PatrolGroup:GetCoordinate()
24115
25036
  local ToCoord=Point1:GetCoordinate()
24116
25037
  if Altitude then
24117
- FromCoord:SetAltitude(Altitude)
24118
- ToCoord:SetAltitude(Altitude)
25038
+ local asl=true
25039
+ if AGL then asl=false end
25040
+ FromCoord:SetAltitude(Altitude,asl)
25041
+ ToCoord:SetAltitude(Altitude,asl)
24119
25042
  end
24120
25043
  local Route={}
24121
25044
  Route[#Route+1]=FromCoord:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,description,timeReFuAr)
@@ -24688,6 +25611,7 @@ if vec3 then
24688
25611
  local coord=COORDINATE:NewFromVec3(vec3)
24689
25612
  local Heading=self:GetHeading()
24690
25613
  coord.Heading=Heading
25614
+ return coord
24691
25615
  else
24692
25616
  BASE:E({"Cannot GetAverageCoordinate",Group=self,Alive=self:IsAlive()})
24693
25617
  return nil
@@ -25664,6 +26588,60 @@ local tankertask=self:EnRouteTaskTanker()
25664
26588
  self:PushTask(tankertask,delay+2)
25665
26589
  return self
25666
26590
  end
26591
+ function GROUP:GetGroupSTN()
26592
+ local tSTN={}
26593
+ local units=self:GetUnits()
26594
+ local gname=self:GetName()
26595
+ gname=string.gsub(gname,"(#%d+)$","")
26596
+ local report=REPORT:New()
26597
+ report:Add("Link16 S/TN Report")
26598
+ report:Add("Group: "..gname)
26599
+ report:Add("==================")
26600
+ for _,_unit in pairs(units)do
26601
+ local unit=_unit
26602
+ if unit and unit:IsAlive()then
26603
+ local STN,VCL,VCN,Lead=unit:GetSTN()
26604
+ local name=unit:GetName()
26605
+ tSTN[name]={
26606
+ STN=STN,
26607
+ VCL=VCL,
26608
+ VCN=VCN,
26609
+ Lead=Lead,
26610
+ }
26611
+ local lead=Lead==true and"(*)"or""
26612
+ report:Add(string.format("| %s%s %s %s",tostring(VCL),tostring(VCN),tostring(STN),lead))
26613
+ end
26614
+ end
26615
+ report:Add("==================")
26616
+ local text=report:Text()
26617
+ return tSTN,text
26618
+ end
26619
+ function GROUP:IsSAM()
26620
+ local issam=false
26621
+ local units=self:GetUnits()
26622
+ for _,_unit in pairs(units or{})do
26623
+ local unit=_unit
26624
+ if unit:HasSEAD()and unit:IsGround()and(not unit:HasAttribute("Mobile AAA"))then
26625
+ issam=true
26626
+ break
26627
+ end
26628
+ end
26629
+ return issam
26630
+ end
26631
+ function GROUP:IsAAA()
26632
+ local issam=false
26633
+ local units=self:GetUnits()
26634
+ for _,_unit in pairs(units or{})do
26635
+ local unit=_unit
26636
+ local desc=unit:GetDesc()or{}
26637
+ local attr=desc.attributes or{}
26638
+ if unit:HasSEAD()then return false end
26639
+ if attr["AAA"]or attr["SAM related"]then
26640
+ issam=true
26641
+ end
26642
+ end
26643
+ return issam
26644
+ end
25667
26645
  UNIT={
25668
26646
  ClassName="UNIT",
25669
26647
  UnitName=nil,
@@ -26458,6 +27436,30 @@ local name=self.UnitName
26458
27436
  local skill=_DATABASE.Templates.Units[name].Template.skill or"Random"
26459
27437
  return skill
26460
27438
  end
27439
+ function UNIT:GetSTN()
27440
+ self:F2(self.UnitName)
27441
+ local STN=nil
27442
+ local VCL=nil
27443
+ local VCN=nil
27444
+ local FGL=false
27445
+ local template=self:GetTemplate()
27446
+ if template.AddPropAircraft then
27447
+ if template.AddPropAircraft.STN_L16 then
27448
+ STN=template.AddPropAircraft.STN_L16
27449
+ elseif template.AddPropAircraft.SADL_TN then
27450
+ STN=template.AddPropAircraft.SADL_TN
27451
+ end
27452
+ VCN=template.AddPropAircraft.VoiceCallsignNumber
27453
+ VCL=template.AddPropAircraft.VoiceCallsignLabel
27454
+ end
27455
+ if template.datalinks and template.datalinks.Link16 and template.datalinks.Link16.settings then
27456
+ FGL=template.datalinks.Link16.settings.flightLead
27457
+ end
27458
+ if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then
27459
+ FGL=template.datalinks.SADL.settings.flightLead
27460
+ end
27461
+ return STN,VCL,VCN,FGL
27462
+ end
26461
27463
  CLIENT={
26462
27464
  ClassName="CLIENT",
26463
27465
  ClientName=nil,
@@ -26981,6 +27983,13 @@ AIRBASE.Normandy={
26981
27983
  ["Broglie"]="Broglie",
26982
27984
  ["Bernay_Saint_Martin"]="Bernay Saint Martin",
26983
27985
  ["Saint_Andre_de_lEure"]="Saint-Andre-de-lEure",
27986
+ ["Biggin_Hill"]="Biggin Hill",
27987
+ ["Manston"]="Manston",
27988
+ ["Detling"]="Detling",
27989
+ ["Lympne"]="Lympne",
27990
+ ["Abbeville_Drucat"]="Abbeville Drucat",
27991
+ ["Merville_Calonne"]="Merville Calonne",
27992
+ ["Saint_Omer_Wizernes"]="Saint-Omer Wizernes",
26984
27993
  }
26985
27994
  AIRBASE.PersianGulf={
26986
27995
  ["Abu_Dhabi_International_Airport"]="Abu Dhabi Intl",
@@ -28501,6 +29510,13 @@ self.launcherUnit=UNIT:Find(self.launcher)
28501
29510
  end
28502
29511
  self.coordinate=COORDINATE:NewFromVec3(self.launcher:getPoint())
28503
29512
  self.lid=string.format("[%s] %s | ",self.typeName,self.name)
29513
+ if self.launcherUnit then
29514
+ self.releaseHeading=self.launcherUnit:GetHeading()
29515
+ self.releaseAltitudeASL=self.launcherUnit:GetAltitude()
29516
+ self.releaseAltitudeAGL=self.launcherUnit:GetAltitude(true)
29517
+ self.releaseCoordinate=self.launcherUnit:GetCoordinate()
29518
+ self.releasePitch=self.launcherUnit:GetPitch()
29519
+ end
28504
29520
  self:SetTimeStepTrack()
28505
29521
  self:SetDistanceInterceptPoint()
28506
29522
  local text=string.format("Weapon v%s\nName=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d, Launcher=%s",
@@ -28646,6 +29662,26 @@ end
28646
29662
  function WEAPON:GetImpactCoordinate()
28647
29663
  return self.impactCoord
28648
29664
  end
29665
+ function WEAPON:GetReleaseHeading(AccountForMagneticInclination)
29666
+ AccountForMagneticInclination=AccountForMagneticInclination or true
29667
+ if AccountForMagneticInclination then return UTILS.ClampAngle(self.releaseHeading-UTILS.GetMagneticDeclination())else return UTILS.ClampAngle(self.releaseHeading)end
29668
+ end
29669
+ function WEAPON:GetReleaseAltitudeASL()
29670
+ return self.releaseAltitudeASL
29671
+ end
29672
+ function WEAPON:GetReleaseAltitudeAGL()
29673
+ return self.releaseAltitudeAGL
29674
+ end
29675
+ function WEAPON:GetReleaseCoordinate()
29676
+ return self.releaseCoordinate
29677
+ end
29678
+ function WEAPON:GetReleasePitch()
29679
+ return self.releasePitch
29680
+ end
29681
+ function WEAPON:GetImpactHeading(AccountForMagneticInclination)
29682
+ AccountForMagneticInclination=AccountForMagneticInclination or true
29683
+ if AccountForMagneticInclination then return UTILS.ClampAngle(self.impactHeading-UTILS.GetMagneticDeclination())else return self.impactHeading end
29684
+ end
28649
29685
  function WEAPON:InAir()
28650
29686
  local inAir=nil
28651
29687
  if self.weapon then
@@ -28717,6 +29753,7 @@ if status then
28717
29753
  self.pos3=pos3
28718
29754
  self.vec3=UTILS.DeepCopy(self.pos3.p)
28719
29755
  self.coordinate:UpdateFromVec3(self.vec3)
29756
+ self.last_velocity=self.weapon:getVelocity()
28720
29757
  self.tracking=true
28721
29758
  if self.trackFunc then
28722
29759
  self.trackFunc(self,unpack(self.trackArg))
@@ -28744,6 +29781,7 @@ self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters",d))
28744
29781
  end
28745
29782
  self.impactVec3=ip or self.vec3
28746
29783
  self.impactCoord=COORDINATE:NewFromVec3(self.vec3)
29784
+ self.impactHeading=UTILS.VecHdg(self.last_velocity)
28747
29785
  if self.impactMark then
28748
29786
  self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s",self.name,self.typeName,self.launcherName))
28749
29787
  end
@@ -29376,7 +30414,7 @@ Reported={},
29376
30414
  }
29377
30415
  function CARGO:New(Type,Name,Weight,LoadRadius,NearRadius)
29378
30416
  local self=BASE:Inherit(self,FSM:New())
29379
- self:F({Type,Name,Weight,LoadRadius,NearRadius})
30417
+ self:T({Type,Name,Weight,LoadRadius,NearRadius})
29380
30418
  self:SetStartState("UnLoaded")
29381
30419
  self:AddTransition({"UnLoaded","Boarding"},"Board","Boarding")
29382
30420
  self:AddTransition("Boarding","Boarding","Boarding")
@@ -29521,7 +30559,7 @@ function CARGO:IsDeployed()
29521
30559
  return self.Deployed
29522
30560
  end
29523
30561
  function CARGO:Spawn(PointVec2)
29524
- self:F()
30562
+ self:T()
29525
30563
  end
29526
30564
  function CARGO:Flare(FlareColor)
29527
30565
  if self:IsUnLoaded()then
@@ -29571,7 +30609,7 @@ function CARGO:GetLoadRadius()
29571
30609
  return self.LoadRadius
29572
30610
  end
29573
30611
  function CARGO:IsInLoadRadius(Coordinate)
29574
- self:F({Coordinate,LoadRadius=self.LoadRadius})
30612
+ self:T({Coordinate,LoadRadius=self.LoadRadius})
29575
30613
  local Distance=0
29576
30614
  if self:IsUnLoaded()then
29577
30615
  local CargoCoordinate=self.CargoObject:GetCoordinate()
@@ -29584,7 +30622,7 @@ end
29584
30622
  return false
29585
30623
  end
29586
30624
  function CARGO:IsInReportRadius(Coordinate)
29587
- self:F({Coordinate})
30625
+ self:T({Coordinate})
29588
30626
  local Distance=0
29589
30627
  if self:IsUnLoaded()then
29590
30628
  Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate())
@@ -29686,7 +30724,7 @@ ClassName="CARGO_REPRESENTABLE"
29686
30724
  }
29687
30725
  function CARGO_REPRESENTABLE:New(CargoObject,Type,Name,LoadRadius,NearRadius)
29688
30726
  local self=BASE:Inherit(self,CARGO:New(Type,Name,0,LoadRadius,NearRadius))
29689
- self:F({Type,Name,LoadRadius,NearRadius})
30727
+ self:T({Type,Name,LoadRadius,NearRadius})
29690
30728
  local Desc=CargoObject:GetDesc()
29691
30729
  self:T({Desc=Desc})
29692
30730
  local Weight=math.random(80,120)
@@ -29701,7 +30739,7 @@ self:SetWeight(Weight)
29701
30739
  return self
29702
30740
  end
29703
30741
  function CARGO_REPRESENTABLE:Destroy()
29704
- self:F({CargoName=self:GetName()})
30742
+ self:T({CargoName=self:GetName()})
29705
30743
  return self
29706
30744
  end
29707
30745
  function CARGO_REPRESENTABLE:RouteTo(ToPointVec2,Speed)
@@ -29719,12 +30757,12 @@ local CoordinateZone=ZONE_RADIUS:New("Zone",self:GetCoordinate():GetVec2(),500)
29719
30757
  CoordinateZone:Scan({Object.Category.UNIT})
29720
30758
  for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do
29721
30759
  local NearUnit=UNIT:Find(DCSUnit)
29722
- self:F({NearUnit=NearUnit})
30760
+ self:T({NearUnit=NearUnit})
29723
30761
  local NearUnitCoalition=NearUnit:GetCoalition()
29724
30762
  local CargoCoalition=self:GetCoalition()
29725
30763
  if NearUnitCoalition==CargoCoalition then
29726
30764
  local Attributes=NearUnit:GetDesc()
29727
- self:F({Desc=Attributes})
30765
+ self:T({Desc=Attributes})
29728
30766
  if NearUnit:HasAttribute("Trucks")then
29729
30767
  MESSAGE:New(Message,20,NearUnit:GetCallsign().." reporting - Cargo "..self:GetName()):ToGroup(TaskGroup)
29730
30768
  break
@@ -29739,7 +30777,7 @@ ClassName="CARGO_REPORTABLE"
29739
30777
  }
29740
30778
  function CARGO_REPORTABLE:New(Type,Name,Weight,LoadRadius,NearRadius)
29741
30779
  local self=BASE:Inherit(self,CARGO:New(Type,Name,Weight,LoadRadius,NearRadius))
29742
- self:F({Type,Name,Weight,LoadRadius,NearRadius})
30780
+ self:T({Type,Name,Weight,LoadRadius,NearRadius})
29743
30781
  return self
29744
30782
  end
29745
30783
  function CARGO_REPORTABLE:MessageToGroup(Message,TaskGroup,Name)
@@ -29752,13 +30790,13 @@ ClassName="CARGO_PACKAGE"
29752
30790
  }
29753
30791
  function CARGO_PACKAGE:New(CargoCarrier,Type,Name,Weight,LoadRadius,NearRadius)
29754
30792
  local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoCarrier,Type,Name,Weight,LoadRadius,NearRadius))
29755
- self:F({Type,Name,Weight,LoadRadius,NearRadius})
30793
+ self:T({Type,Name,Weight,LoadRadius,NearRadius})
29756
30794
  self:T(CargoCarrier)
29757
30795
  self.CargoCarrier=CargoCarrier
29758
30796
  return self
29759
30797
  end
29760
30798
  function CARGO_PACKAGE:onafterOnBoard(From,Event,To,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle)
29761
- self:F()
30799
+ self:T()
29762
30800
  self.CargoInAir=self.CargoCarrier:InAir()
29763
30801
  self:T(self.CargoInAir)
29764
30802
  if not self.CargoInAir then
@@ -29776,7 +30814,7 @@ end
29776
30814
  self:Boarded(CargoCarrier,Speed,BoardDistance,LoadDistance,Angle)
29777
30815
  end
29778
30816
  function CARGO_PACKAGE:IsNear(CargoCarrier)
29779
- self:F()
30817
+ self:T()
29780
30818
  local CargoCarrierPoint=CargoCarrier:GetCoordinate()
29781
30819
  local Distance=CargoCarrierPoint:Get2DDistance(self.CargoCarrier:GetCoordinate())
29782
30820
  self:T(Distance)
@@ -29787,7 +30825,7 @@ return false
29787
30825
  end
29788
30826
  end
29789
30827
  function CARGO_PACKAGE:onafterOnBoarded(From,Event,To,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle)
29790
- self:F()
30828
+ self:T()
29791
30829
  if self:IsNear(CargoCarrier)then
29792
30830
  self:__Load(1,CargoCarrier,Speed,LoadDistance,Angle)
29793
30831
  else
@@ -29795,7 +30833,7 @@ self:__Boarded(1,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle)
29795
30833
  end
29796
30834
  end
29797
30835
  function CARGO_PACKAGE:onafterUnBoard(From,Event,To,CargoCarrier,Speed,UnLoadDistance,UnBoardDistance,Radius,Angle)
29798
- self:F()
30836
+ self:T()
29799
30837
  self.CargoInAir=self.CargoCarrier:InAir()
29800
30838
  self:T(self.CargoInAir)
29801
30839
  if not self.CargoInAir then
@@ -29814,7 +30852,7 @@ end
29814
30852
  self:__UnBoarded(1,CargoCarrier,Speed)
29815
30853
  end
29816
30854
  function CARGO_PACKAGE:onafterUnBoarded(From,Event,To,CargoCarrier,Speed)
29817
- self:F()
30855
+ self:T()
29818
30856
  if self:IsNear(CargoCarrier)then
29819
30857
  self:__UnLoad(1,CargoCarrier,Speed)
29820
30858
  else
@@ -29822,7 +30860,7 @@ self:__UnBoarded(1,CargoCarrier,Speed)
29822
30860
  end
29823
30861
  end
29824
30862
  function CARGO_PACKAGE:onafterLoad(From,Event,To,CargoCarrier,Speed,LoadDistance,Angle)
29825
- self:F()
30863
+ self:T()
29826
30864
  self.CargoCarrier=CargoCarrier
29827
30865
  local StartPointVec2=self.CargoCarrier:GetPointVec2()
29828
30866
  local CargoCarrierHeading=self.CargoCarrier:GetHeading()
@@ -29835,7 +30873,7 @@ local TaskRoute=self.CargoCarrier:TaskRoute(Points)
29835
30873
  self.CargoCarrier:SetTask(TaskRoute,1)
29836
30874
  end
29837
30875
  function CARGO_PACKAGE:onafterUnLoad(From,Event,To,CargoCarrier,Speed,Distance,Angle)
29838
- self:F()
30876
+ self:T()
29839
30877
  local StartPointVec2=self.CargoCarrier:GetPointVec2()
29840
30878
  local CargoCarrierHeading=self.CargoCarrier:GetHeading()
29841
30879
  local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
@@ -29860,7 +30898,7 @@ self:SetEventPriority(5)
29860
30898
  return self
29861
30899
  end
29862
30900
  function CARGO_UNIT:onenterUnBoarding(From,Event,To,ToPointVec2,NearRadius)
29863
- self:F({From,Event,To,ToPointVec2,NearRadius})
30901
+ self:T({From,Event,To,ToPointVec2,NearRadius})
29864
30902
  local Angle=180
29865
30903
  local Speed=60
29866
30904
  local DeployDistance=9
@@ -29883,7 +30921,7 @@ self.CargoObject:ReSpawnAt(ToPointVec2,CargoDeployHeading)
29883
30921
  else
29884
30922
  self.CargoObject:ReSpawnAt(FromPointVec2,CargoDeployHeading)
29885
30923
  end
29886
- self:F({"CargoUnits:",self.CargoObject:GetGroup():GetName()})
30924
+ self:T({"CargoUnits:",self.CargoObject:GetGroup():GetName()})
29887
30925
  self.CargoCarrier=nil
29888
30926
  local Points={}
29889
30927
  Points[#Points+1]=FromPointVec2:WaypointGround(Speed,"Vee")
@@ -29899,7 +30937,7 @@ end
29899
30937
  end
29900
30938
  end
29901
30939
  function CARGO_UNIT:onleaveUnBoarding(From,Event,To,ToPointVec2,NearRadius)
29902
- self:F({From,Event,To,ToPointVec2,NearRadius})
30940
+ self:T({From,Event,To,ToPointVec2,NearRadius})
29903
30941
  local Angle=180
29904
30942
  local Speed=10
29905
30943
  local Distance=5
@@ -29908,7 +30946,7 @@ return true
29908
30946
  end
29909
30947
  end
29910
30948
  function CARGO_UNIT:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius)
29911
- self:F({From,Event,To,ToPointVec2,NearRadius})
30949
+ self:T({From,Event,To,ToPointVec2,NearRadius})
29912
30950
  self.CargoInAir=self.CargoObject:InAir()
29913
30951
  self:T(self.CargoInAir)
29914
30952
  if not self.CargoInAir then
@@ -29916,7 +30954,7 @@ end
29916
30954
  self:__UnLoad(1,ToPointVec2,NearRadius)
29917
30955
  end
29918
30956
  function CARGO_UNIT:onenterUnLoaded(From,Event,To,ToPointVec2)
29919
- self:F({ToPointVec2,From,Event,To})
30957
+ self:T({ToPointVec2,From,Event,To})
29920
30958
  local Angle=180
29921
30959
  local Speed=10
29922
30960
  local Distance=5
@@ -29937,7 +30975,7 @@ self.OnUnLoadedCallBack=nil
29937
30975
  end
29938
30976
  end
29939
30977
  function CARGO_UNIT:onafterBoard(From,Event,To,CargoCarrier,NearRadius,...)
29940
- self:F({From,Event,To,CargoCarrier,NearRadius=NearRadius})
30978
+ self:T({From,Event,To,CargoCarrier,NearRadius=NearRadius})
29941
30979
  self.CargoInAir=self.CargoObject:InAir()
29942
30980
  local Desc=self.CargoObject:GetDesc()
29943
30981
  local MaxSpeed=Desc.speedMaxOffRoad
@@ -29971,8 +31009,8 @@ end
29971
31009
  end
29972
31010
  end
29973
31011
  function CARGO_UNIT:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...)
29974
- self:F({From,Event,To,CargoCarrier:GetName(),NearRadius=NearRadius})
29975
- self:F({IsAlive=self.CargoObject:IsAlive()})
31012
+ self:T({From,Event,To,CargoCarrier:GetName(),NearRadius=NearRadius})
31013
+ self:T({IsAlive=self.CargoObject:IsAlive()})
29976
31014
  if CargoCarrier and CargoCarrier:IsAlive()then
29977
31015
  if(CargoCarrier:IsAir()and not CargoCarrier:InAir())or true then
29978
31016
  local NearRadius=NearRadius or CargoCarrier:GetBoundingRadius(NearRadius)+5
@@ -30010,11 +31048,11 @@ self:CancelBoarding(CargoCarrier,NearRadius,...)
30010
31048
  self.CargoObject:SetCommand(self.CargoObject:CommandStopRoute(true))
30011
31049
  end
30012
31050
  else
30013
- self:E("Something is wrong")
31051
+ self:T("Something is wrong")
30014
31052
  end
30015
31053
  end
30016
31054
  function CARGO_UNIT:onenterLoaded(From,Event,To,CargoCarrier)
30017
- self:F({From,Event,To,CargoCarrier})
31055
+ self:T({From,Event,To,CargoCarrier})
30018
31056
  self.CargoCarrier=CargoCarrier
30019
31057
  if self.CargoObject then
30020
31058
  self.CargoObject:Destroy(false)
@@ -30041,7 +31079,7 @@ ClassName="CARGO_SLINGLOAD"
30041
31079
  }
30042
31080
  function CARGO_SLINGLOAD:New(CargoStatic,Type,Name,LoadRadius,NearRadius)
30043
31081
  local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoStatic,Type,Name,nil,LoadRadius,NearRadius))
30044
- self:F({Type,Name,NearRadius})
31082
+ self:T({Type,Name,NearRadius})
30045
31083
  self.CargoObject=CargoStatic
30046
31084
  _EVENTDISPATCHER:CreateEventNewCargo(self)
30047
31085
  self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead)
@@ -30152,7 +31190,7 @@ ClassName="CARGO_CRATE"
30152
31190
  }
30153
31191
  function CARGO_CRATE:New(CargoStatic,Type,Name,LoadRadius,NearRadius)
30154
31192
  local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoStatic,Type,Name,nil,LoadRadius,NearRadius))
30155
- self:F({Type,Name,NearRadius})
31193
+ self:T({Type,Name,NearRadius})
30156
31194
  self.CargoObject=CargoStatic
30157
31195
  _EVENTDISPATCHER:CreateEventNewCargo(self)
30158
31196
  self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead)
@@ -30255,21 +31293,21 @@ end
30255
31293
  return Alive
30256
31294
  end
30257
31295
  function CARGO_CRATE:RouteTo(Coordinate)
30258
- self:F({Coordinate=Coordinate})
31296
+ self:T({Coordinate=Coordinate})
30259
31297
  end
30260
31298
  function CARGO_CRATE:IsNear(CargoCarrier,NearRadius)
30261
- self:F({NearRadius=NearRadius})
31299
+ self:T({NearRadius=NearRadius})
30262
31300
  return self:IsNear(CargoCarrier:GetCoordinate(),NearRadius)
30263
31301
  end
30264
31302
  function CARGO_CRATE:Respawn()
30265
- self:F({"Respawning crate "..self:GetName()})
31303
+ self:T({"Respawning crate "..self:GetName()})
30266
31304
  if self.CargoObject then
30267
31305
  self.CargoObject:ReSpawn()
30268
31306
  self:__Reset(-0.1)
30269
31307
  end
30270
31308
  end
30271
31309
  function CARGO_CRATE:onafterReset()
30272
- self:F({"Reset crate "..self:GetName()})
31310
+ self:T({"Reset crate "..self:GetName()})
30273
31311
  if self.CargoObject then
30274
31312
  self:SetDeployed(false)
30275
31313
  self:SetStartState("UnLoaded")
@@ -30298,7 +31336,7 @@ ClassName="CARGO_GROUP",
30298
31336
  }
30299
31337
  function CARGO_GROUP:New(CargoGroup,Type,Name,LoadRadius,NearRadius)
30300
31338
  local self=BASE:Inherit(self,CARGO_REPORTABLE:New(Type,Name,0,LoadRadius,NearRadius))
30301
- self:F({Type,Name,LoadRadius})
31339
+ self:T({Type,Name,LoadRadius})
30302
31340
  self.CargoSet=SET_CARGO:New()
30303
31341
  self.CargoGroup=CargoGroup
30304
31342
  self.Grouped=true
@@ -30342,7 +31380,7 @@ self:SetEventPriority(4)
30342
31380
  return self
30343
31381
  end
30344
31382
  function CARGO_GROUP:Respawn()
30345
- self:F({"Respawning"})
31383
+ self:T({"Respawning"})
30346
31384
  for CargoID,CargoData in pairs(self.CargoSet:GetSet())do
30347
31385
  local Cargo=CargoData
30348
31386
  Cargo:Destroy()
@@ -30383,7 +31421,7 @@ self.CargoObject=nil
30383
31421
  end
30384
31422
  end
30385
31423
  function CARGO_GROUP:Regroup()
30386
- self:F("Regroup")
31424
+ self:T("Regroup")
30387
31425
  if self.Grouped==false then
30388
31426
  self.Grouped=true
30389
31427
  local GroupTemplate=UTILS.DeepCopy(self.CargoTemplate)
@@ -30392,7 +31430,7 @@ GroupTemplate.groupId=nil
30392
31430
  GroupTemplate.units={}
30393
31431
  for CargoUnitName,CargoUnit in pairs(self.CargoSet:GetSet())do
30394
31432
  local CargoUnit=CargoUnit
30395
- self:F({CargoUnit:GetName(),UnLoaded=CargoUnit:IsUnLoaded()})
31433
+ self:T({CargoUnit:GetName(),UnLoaded=CargoUnit:IsUnLoaded()})
30396
31434
  if CargoUnit:IsUnLoaded()then
30397
31435
  CargoUnit.CargoObject:Destroy()
30398
31436
  GroupTemplate.units[#GroupTemplate.units+1]=self.CargoUnitTemplate[CargoUnitName]
@@ -30403,12 +31441,12 @@ GroupTemplate.units[#GroupTemplate.units].heading=CargoUnit:GetHeading()
30403
31441
  end
30404
31442
  end
30405
31443
  self.CargoGroup=GROUP:NewTemplate(GroupTemplate,GroupTemplate.CoalitionID,GroupTemplate.CategoryID,GroupTemplate.CountryID)
30406
- self:F({"Regroup",GroupTemplate})
31444
+ self:T({"Regroup",GroupTemplate})
30407
31445
  self.CargoObject=_DATABASE:Spawn(GroupTemplate)
30408
31446
  end
30409
31447
  end
30410
31448
  function CARGO_GROUP:OnEventCargoDead(EventData)
30411
- self:E(EventData)
31449
+ self:T(EventData)
30412
31450
  local Destroyed=false
30413
31451
  if self:IsDestroyed()or self:IsUnLoaded()or self:IsBoarding()or self:IsUnboarding()then
30414
31452
  Destroyed=true
@@ -30430,15 +31468,15 @@ end
30430
31468
  end
30431
31469
  if Destroyed then
30432
31470
  self:Destroyed()
30433
- self:E({"Cargo group destroyed"})
31471
+ self:T({"Cargo group destroyed"})
30434
31472
  end
30435
31473
  end
30436
31474
  function CARGO_GROUP:onafterBoard(From,Event,To,CargoCarrier,NearRadius,...)
30437
- self:F({CargoCarrier.UnitName,From,Event,To,NearRadius=NearRadius})
31475
+ self:T({CargoCarrier.UnitName,From,Event,To,NearRadius=NearRadius})
30438
31476
  NearRadius=NearRadius or self.NearRadius
30439
31477
  self.CargoSet:ForEach(
30440
31478
  function(Cargo,...)
30441
- self:F({"Board Unit",Cargo:GetName(),Cargo:IsDestroyed(),Cargo.CargoObject:IsAlive()})
31479
+ self:T({"Board Unit",Cargo:GetName(),Cargo:IsDestroyed(),Cargo.CargoObject:IsAlive()})
30442
31480
  local CargoGroup=Cargo.CargoObject
30443
31481
  CargoGroup:OptionAlarmStateGreen()
30444
31482
  Cargo:__Board(1,CargoCarrier,NearRadius,...)
@@ -30479,7 +31517,7 @@ if not Cancelled then
30479
31517
  if not Boarded then
30480
31518
  self:__Boarding(-5,CargoCarrier,NearRadius,...)
30481
31519
  else
30482
- self:F("Group Cargo is loaded")
31520
+ self:T("Group Cargo is loaded")
30483
31521
  self:__Load(1,CargoCarrier,...)
30484
31522
  end
30485
31523
  else
@@ -30490,7 +31528,7 @@ self:__Destroyed(1,CargoCarrier,NearRadius,...)
30490
31528
  end
30491
31529
  end
30492
31530
  function CARGO_GROUP:onafterUnBoard(From,Event,To,ToPointVec2,NearRadius,...)
30493
- self:F({From,Event,To,ToPointVec2,NearRadius})
31531
+ self:T({From,Event,To,ToPointVec2,NearRadius})
30494
31532
  NearRadius=NearRadius or 25
30495
31533
  local Timer=1
30496
31534
  if From=="Loaded"then
@@ -30599,12 +31637,12 @@ end
30599
31637
  )
30600
31638
  end
30601
31639
  function CARGO_GROUP:IsNear(CargoCarrier,NearRadius)
30602
- self:F({NearRadius=NearRadius})
31640
+ self:T({NearRadius=NearRadius})
30603
31641
  for _,Cargo in pairs(self.CargoSet:GetSet())do
30604
31642
  local Cargo=Cargo
30605
31643
  if Cargo:IsAlive()then
30606
31644
  if Cargo:IsNear(CargoCarrier:GetCoordinate(),NearRadius)then
30607
- self:F("Near")
31645
+ self:T("Near")
30608
31646
  return true
30609
31647
  end
30610
31648
  end
@@ -30626,7 +31664,7 @@ Distance=Coordinate:Get2DDistance(CargoCoordinate)
30626
31664
  else
30627
31665
  return false
30628
31666
  end
30629
- self:F({Distance=Distance,LoadRadius=self.LoadRadius})
31667
+ self:T({Distance=Distance,LoadRadius=self.LoadRadius})
30630
31668
  if Distance<=self.LoadRadius then
30631
31669
  return true
30632
31670
  else
@@ -30638,7 +31676,7 @@ end
30638
31676
  function CARGO_GROUP:IsInReportRadius(Coordinate)
30639
31677
  local Cargo=self:GetFirstAlive()
30640
31678
  if Cargo then
30641
- self:F({Cargo})
31679
+ self:T({Cargo})
30642
31680
  local Distance=0
30643
31681
  if Cargo:IsUnLoaded()then
30644
31682
  Distance=Coordinate:Get2DDistance(Cargo.CargoObject:GetCoordinate())
@@ -32121,7 +33159,7 @@ self:HandleEvent(EVENTS.Shot,self.HandleEventShot)
32121
33159
  self:SetStartState("Running")
32122
33160
  self:AddTransition("*","ManageEvasion","*")
32123
33161
  self:AddTransition("*","CalculateHitZone","*")
32124
- self:I("*** SEAD - Started Version 0.4.5")
33162
+ self:I("*** SEAD - Started Version 0.4.6")
32125
33163
  return self
32126
33164
  end
32127
33165
  function SEAD:UpdateSet(SEADGroupPrefixes)
@@ -32293,7 +33331,7 @@ if self.UseEmissionsOnOff then
32293
33331
  grp:EnableEmission(false)
32294
33332
  end
32295
33333
  grp:OptionAlarmStateGreen()
32296
- grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond")
33334
+ grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond",true)
32297
33335
  if self.UseCallBack then
32298
33336
  local object=self.CallBack
32299
33337
  object:SeadSuppressionStart(grp,name,attacker)
@@ -33504,7 +34542,7 @@ AirbaseNames=nil,
33504
34542
  }
33505
34543
  function ATC_GROUND:New(Airbases,AirbaseList)
33506
34544
  local self=BASE:Inherit(self,BASE:New())
33507
- self:E({self.ClassName,Airbases})
34545
+ self:T({self.ClassName,Airbases})
33508
34546
  self.Airbases=Airbases
33509
34547
  self.AirbaseList=AirbaseList
33510
34548
  self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart()
@@ -33583,7 +34621,7 @@ function(Client)
33583
34621
  if Client:IsAlive()then
33584
34622
  local IsOnGround=Client:InAir()==false
33585
34623
  for AirbaseID,AirbaseMeta in pairs(self.Airbases)do
33586
- self:E(AirbaseID,AirbaseMeta.KickSpeed)
34624
+ self:T(AirbaseID,AirbaseMeta.KickSpeed)
33587
34625
  if AirbaseMeta.Monitor==true and Client:IsInZone(AirbaseMeta.ZoneBoundary)then
33588
34626
  local NotInRunwayZone=true
33589
34627
  for ZoneRunwayID,ZoneRunway in pairs(AirbaseMeta.ZoneRunways)do
@@ -33592,7 +34630,7 @@ end
33592
34630
  if NotInRunwayZone then
33593
34631
  if IsOnGround then
33594
34632
  local Taxi=Client:GetState(self,"Taxi")
33595
- self:E(Taxi)
34633
+ self:T(Taxi)
33596
34634
  if Taxi==false then
33597
34635
  local Velocity=VELOCITY:New(AirbaseMeta.KickSpeed or self.KickSpeed)
33598
34636
  Client:Message("Welcome to "..AirbaseID..". The maximum taxiing speed is "..
@@ -33712,12 +34750,18 @@ KickSpeed=nil,
33712
34750
  }
33713
34751
  function ATC_GROUND_UNIVERSAL:New(AirbaseList)
33714
34752
  local self=BASE:Inherit(self,BASE:New())
33715
- self:E({self.ClassName})
34753
+ self:T({self.ClassName})
33716
34754
  self.Airbases={}
33717
34755
  for _name,_ in pairs(_DATABASE.AIRBASES)do
33718
34756
  self.Airbases[_name]={}
33719
34757
  end
33720
34758
  self.AirbaseList=AirbaseList
34759
+ if not self.AirbaseList then
34760
+ self.AirbaseList={}
34761
+ for _name,_ in pairs(_DATABASE.AIRBASES)do
34762
+ self.AirbaseList[_name]=_name
34763
+ end
34764
+ end
33721
34765
  self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart()
33722
34766
  for AirbaseID,Airbase in pairs(self.Airbases)do
33723
34767
  if Airbase.ZoneBoundary then
@@ -33816,12 +34860,13 @@ self:SetMaximumKickSpeed(UTILS.MiphToMps(MaximumKickSpeedMiph),Airbase)
33816
34860
  return self
33817
34861
  end
33818
34862
  function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
34863
+ self:I("_AirbaseMonitor")
33819
34864
  self.SetClient:ForEachClient(
33820
34865
  function(Client)
33821
34866
  if Client:IsAlive()then
33822
34867
  local IsOnGround=Client:InAir()==false
33823
34868
  for AirbaseID,AirbaseMeta in pairs(self.Airbases)do
33824
- self:E(AirbaseID,AirbaseMeta.KickSpeed)
34869
+ self:T(AirbaseID,AirbaseMeta.KickSpeed)
33825
34870
  if AirbaseMeta.Monitor==true and Client:IsInZone(AirbaseMeta.ZoneBoundary)then
33826
34871
  local NotInRunwayZone=true
33827
34872
  if AirbaseMeta.ZoneRunways then
@@ -33833,7 +34878,7 @@ end
33833
34878
  if NotInRunwayZone then
33834
34879
  if IsOnGround then
33835
34880
  local Taxi=Client:GetState(self,"Taxi")
33836
- self:E(Taxi)
34881
+ self:T(Taxi)
33837
34882
  if Taxi==false then
33838
34883
  local Velocity=VELOCITY:New(AirbaseMeta.KickSpeed or self.KickSpeed)
33839
34884
  Client:Message("Welcome to "..AirbaseID..". The maximum taxiing speed is "..
@@ -33945,7 +34990,7 @@ return true
33945
34990
  end
33946
34991
  function ATC_GROUND_UNIVERSAL:Start(RepeatScanSeconds)
33947
34992
  RepeatScanSeconds=RepeatScanSeconds or 0.05
33948
- self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
34993
+ self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
33949
34994
  return self
33950
34995
  end
33951
34996
  ATC_GROUND_CAUCASUS={
@@ -33959,7 +35004,7 @@ return self
33959
35004
  end
33960
35005
  function ATC_GROUND_CAUCASUS:Start(RepeatScanSeconds)
33961
35006
  RepeatScanSeconds=RepeatScanSeconds or 0.05
33962
- self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
35007
+ self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
33963
35008
  end
33964
35009
  ATC_GROUND_NEVADA={
33965
35010
  ClassName="ATC_GROUND_NEVADA",
@@ -33972,7 +35017,7 @@ return self
33972
35017
  end
33973
35018
  function ATC_GROUND_NEVADA:Start(RepeatScanSeconds)
33974
35019
  RepeatScanSeconds=RepeatScanSeconds or 0.05
33975
- self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
35020
+ self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
33976
35021
  end
33977
35022
  ATC_GROUND_NORMANDY={
33978
35023
  ClassName="ATC_GROUND_NORMANDY",
@@ -33985,7 +35030,7 @@ return self
33985
35030
  end
33986
35031
  function ATC_GROUND_NORMANDY:Start(RepeatScanSeconds)
33987
35032
  RepeatScanSeconds=RepeatScanSeconds or 0.05
33988
- self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
35033
+ self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
33989
35034
  end
33990
35035
  ATC_GROUND_PERSIANGULF={
33991
35036
  ClassName="ATC_GROUND_PERSIANGULF",
@@ -33997,20 +35042,20 @@ self:SetMaximumKickSpeedKmph(150)
33997
35042
  end
33998
35043
  function ATC_GROUND_PERSIANGULF:Start(RepeatScanSeconds)
33999
35044
  RepeatScanSeconds=RepeatScanSeconds or 0.05
34000
- self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
35045
+ self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
34001
35046
  end
34002
35047
  ATC_GROUND_MARIANAISLANDS={
34003
35048
  ClassName="ATC_GROUND_MARIANAISLANDS",
34004
35049
  }
34005
35050
  function ATC_GROUND_MARIANAISLANDS:New(AirbaseNames)
34006
- local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(self.Airbases,AirbaseNames))
35051
+ local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames))
34007
35052
  self:SetKickSpeedKmph(50)
34008
35053
  self:SetMaximumKickSpeedKmph(150)
34009
35054
  return self
34010
35055
  end
34011
35056
  function ATC_GROUND_MARIANAISLANDS:Start(RepeatScanSeconds)
34012
35057
  RepeatScanSeconds=RepeatScanSeconds or 0.05
34013
- self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
35058
+ self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
34014
35059
  end
34015
35060
  do
34016
35061
  DETECTION_BASE={
@@ -34163,6 +35208,28 @@ DetectionAccepted=false
34163
35208
  end
34164
35209
  end
34165
35210
  end
35211
+ if self.RadarBlur then
35212
+ MESSAGE:New("Radar Blur",10):ToLogIf(self.debug):ToAllIf(self.verbose)
35213
+ local minheight=self.RadarBlurMinHeight or 250
35214
+ local thresheight=self.RadarBlurThresHeight or 90
35215
+ local thresblur=self.RadarBlurThresBlur or 85
35216
+ local dist=math.floor(Distance)
35217
+ if dist<=self.RadarBlurClosing then
35218
+ thresheight=(((dist*dist)/self.RadarBlurClosingSquare)*thresheight)
35219
+ thresblur=(((dist*dist)/self.RadarBlurClosingSquare)*thresblur)
35220
+ end
35221
+ local fheight=math.floor(math.random(1,10000)/100)
35222
+ local fblur=math.floor(math.random(1,10000)/100)
35223
+ local unit=UNIT:FindByName(DetectedObjectName)
35224
+ if unit and unit:IsAlive()then
35225
+ local AGL=unit:GetAltitude(true)
35226
+ MESSAGE:New("Unit "..DetectedObjectName.." is at "..math.floor(AGL).."m. Distance "..math.floor(Distance).."km.",10):ToLogIf(self.debug):ToAllIf(self.verbose)
35227
+ MESSAGE:New(string.format("fheight = %d/%d | fblur = %d/%d",fheight,thresheight,fblur,thresblur),10):ToLogIf(self.debug):ToAllIf(self.verbose)
35228
+ if fblur>thresblur then DetectionAccepted=false end
35229
+ if AGL<=minheight and fheight<thresheight then DetectionAccepted=false end
35230
+ MESSAGE:New("Detection Accepted = "..tostring(DetectionAccepted),10):ToLogIf(self.debug):ToAllIf(self.verbose)
35231
+ end
35232
+ end
34166
35233
  if not self.DetectedObjects[DetectedObjectName]and TargetIsVisible and self.DistanceProbability then
34167
35234
  local DistanceFactor=Distance/4
34168
35235
  local DistanceProbabilityReversed=(1-self.DistanceProbability)*DistanceFactor
@@ -34323,6 +35390,15 @@ self._.FilterCategories[FilterCategories]=FilterCategories
34323
35390
  end
34324
35391
  return self
34325
35392
  end
35393
+ function DETECTION_BASE:SetRadarBlur(minheight,thresheight,thresblur,closing)
35394
+ self.RadarBlur=true
35395
+ self.RadarBlurMinHeight=minheight or 250
35396
+ self.RadarBlurThresHeight=thresheight or 90
35397
+ self.RadarBlurThresBlur=thresblur or 85
35398
+ self.RadarBlurClosing=closing or 20
35399
+ self.RadarBlurClosingSquare=self.RadarBlurClosing*self.RadarBlurClosing
35400
+ return self
35401
+ end
34326
35402
  end
34327
35403
  do
34328
35404
  function DETECTION_BASE:SetRefreshTimeInterval(RefreshTimeInterval)
@@ -37451,7 +38527,7 @@ if takeoff==RAT.wp.air then
37451
38527
  departure=departure:GetZone()
37452
38528
  end
37453
38529
  elseif self:_ZoneExists(_departure)then
37454
- departure=ZONE:New(_departure)
38530
+ departure=ZONE:FindByName(_departure)
37455
38531
  else
37456
38532
  local text=string.format("ERROR! Specified departure airport %s does not exist for %s.",_departure,self.alias)
37457
38533
  self:E(RAT.id..text)
@@ -37522,7 +38598,7 @@ if landing==RAT.wp.air or self.returnzone then
37522
38598
  destination=destination:GetZone()
37523
38599
  end
37524
38600
  elseif self:_ZoneExists(_destination)then
37525
- destination=ZONE:New(_destination)
38601
+ destination=ZONE:FindByName(_destination)
37526
38602
  else
37527
38603
  local text=string.format("ERROR: Specified destination airport/zone %s does not exist for %s!",_destination,self.alias)
37528
38604
  self:E(RAT.id.."ERROR: "..text)
@@ -37857,7 +38933,7 @@ end
37857
38933
  end
37858
38934
  elseif self:_ZoneExists(name)then
37859
38935
  if takeoff==RAT.wp.air then
37860
- dep=ZONE:New(name)
38936
+ dep=ZONE:FindByName(name)
37861
38937
  else
37862
38938
  self:E(RAT.id..string.format("ERROR! Takeoff is not in air. Cannot use %s as departure.",name))
37863
38939
  end
@@ -37929,7 +39005,7 @@ end
37929
39005
  end
37930
39006
  elseif self:_ZoneExists(name)then
37931
39007
  if landing==RAT.wp.air then
37932
- dest=ZONE:New(name)
39008
+ dest=ZONE:FindByName(name)
37933
39009
  else
37934
39010
  self:E(RAT.id..string.format("ERROR! Landing is not in air. Cannot use zone %s as destination!",name))
37935
39011
  end
@@ -38891,7 +39967,7 @@ end
38891
39967
  return false
38892
39968
  end
38893
39969
  function RAT:_ZoneExists(name)
38894
- local z=trigger.misc.getZone(name)
39970
+ local z=ZONE:FindByName(name)
38895
39971
  if z then
38896
39972
  return true
38897
39973
  end
@@ -39931,15 +41007,15 @@ self.trackmissiles=false
39931
41007
  return self
39932
41008
  end
39933
41009
  function RANGE:SetSRS(PathToSRS,Port,Coalition,Frequency,Modulation,Volume,PathToGoogleKey)
39934
- if PathToSRS then
41010
+ if PathToSRS or MSRS.path then
39935
41011
  self.useSRS=true
39936
- self.controlmsrs=MSRS:New(PathToSRS,Frequency or 256,Modulation or radio.modulation.AM,Volume or 1.0)
39937
- self.controlmsrs:SetPort(Port)
41012
+ self.controlmsrs=MSRS:New(PathToSRS or MSRS.path,Frequency or 256,Modulation or radio.modulation.AM,Volume or 1.0)
41013
+ self.controlmsrs:SetPort(Port or MSRS.port)
39938
41014
  self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE)
39939
41015
  self.controlmsrs:SetLabel("RANGEC")
39940
41016
  self.controlsrsQ=MSRSQUEUE:New("CONTROL")
39941
- self.instructmsrs=MSRS:New(PathToSRS,Frequency or 305,Modulation or radio.modulation.AM,Volume or 1.0)
39942
- self.instructmsrs:SetPort(Port)
41017
+ self.instructmsrs=MSRS:New(PathToSRS or MSRS.path,Frequency or 305,Modulation or radio.modulation.AM,Volume or 1.0)
41018
+ self.instructmsrs:SetPort(Port or MSRS.port)
39943
41019
  self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE)
39944
41020
  self.instructmsrs:SetLabel("RANGEI")
39945
41021
  self.instructsrsQ=MSRSQUEUE:New("INSTRUCT")
@@ -39953,6 +41029,10 @@ end
39953
41029
  return self
39954
41030
  end
39955
41031
  function RANGE:SetSRSRangeControl(frequency,modulation,voice,culture,gender,relayunitname)
41032
+ if not self.instructmsrs then
41033
+ self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeControl!")
41034
+ return self
41035
+ end
39956
41036
  self.rangecontrolfreq=frequency or 256
39957
41037
  self.controlmsrs:SetFrequencies(self.rangecontrolfreq)
39958
41038
  self.controlmsrs:SetModulations(modulation or radio.modulation.AM)
@@ -39968,6 +41048,10 @@ end
39968
41048
  return self
39969
41049
  end
39970
41050
  function RANGE:SetSRSRangeInstructor(frequency,modulation,voice,culture,gender,relayunitname)
41051
+ if not self.instructmsrs then
41052
+ self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeInstructor!")
41053
+ return self
41054
+ end
39971
41055
  self.instructorfreq=frequency or 305
39972
41056
  self.instructmsrs:SetFrequencies(self.instructorfreq)
39973
41057
  self.instructmsrs:SetModulations(modulation or radio.modulation.AM)
@@ -40220,6 +41304,7 @@ return fouldist
40220
41304
  end
40221
41305
  function RANGE:OnEventBirth(EventData)
40222
41306
  self:F({eventbirth=EventData})
41307
+ if not EventData.IniPlayerName then return end
40223
41308
  local _unitName=EventData.IniUnitName
40224
41309
  local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
40225
41310
  self:T3(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName))
@@ -40476,7 +41561,7 @@ function RANGE:onafterExitRange(From,Event,To,player)
40476
41561
  if self.instructor then
40477
41562
  if self.useSRS then
40478
41563
  local text="You left the bombing range zone. "
40479
- local r=math.random(2)
41564
+ local r=math.random(5)
40480
41565
  if r==1 then
40481
41566
  text=text.."Have a nice day!"
40482
41567
  elseif r==2 then
@@ -48276,11 +49361,22 @@ local _assetattribute
48276
49361
  local _assetcategory
48277
49362
  local _assetairstart=false
48278
49363
  if _nassets>0 then
49364
+ local asset=_assets[1]
48279
49365
  _assetattribute=_assets[1].attribute
48280
49366
  _assetcategory=_assets[1].category
48281
49367
  _assetairstart=_assets[1].takeoffType and _assets[1].takeoffType==COORDINATE.WaypointType.TurningPoint or false
48282
49368
  if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
48283
49369
  if self.airbase and self.airbase:GetCoalition()==self:GetCoalition()then
49370
+ if self.airbase.storage then
49371
+ local nS=self.airbase.storage:GetAmount(asset.unittype)
49372
+ local nA=asset.nunits*request.nasset
49373
+ if nS<nA then
49374
+ local text=string.format("Warehouse %s: Request denied! DCS Warehouse has only %d assets of type %s ==> NOT enough to spawn the requested %d asset units (%d groups)",
49375
+ self.alias,nS,asset.unittype,nA,request.nasset)
49376
+ self:_InfoMessage(text,5)
49377
+ return false
49378
+ end
49379
+ end
48284
49380
  if self:IsRunwayOperational()or _assetairstart then
48285
49381
  if _assetairstart then
48286
49382
  else
@@ -48342,6 +49438,7 @@ local text=string.format("Warehouse %s: Request denied! Not close enough to spaw
48342
49438
  self:_InfoMessage(text,5)
48343
49439
  return false
48344
49440
  end
49441
+ elseif _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
48345
49442
  end
48346
49443
  end
48347
49444
  request.cargoassets=_assets
@@ -50202,7 +51299,7 @@ MANTIS.SamData={
50202
51299
  ["SA-15"]={Range=11,Blindspot=0,Height=6,Type="Short",Radar="Tor 9A331"},
50203
51300
  ["SA-13"]={Range=5,Blindspot=0,Height=3,Type="Short",Radar="Strela"},
50204
51301
  ["Avenger"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="Avenger"},
50205
- ["Chaparrel"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Chaparral"},
51302
+ ["Chaparral"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Chaparral"},
50206
51303
  ["Linebacker"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="Linebacker"},
50207
51304
  ["Silkworm"]={Range=90,Blindspot=1,Height=0.2,Type="Long",Radar="Silkworm"},
50208
51305
  ["SA-10B"]={Range=75,Blindspot=0,Height=18,Type="Medium",Radar="SA-10B"},
@@ -50360,7 +51457,7 @@ end
50360
51457
  if self.HQ_Template_CC then
50361
51458
  self.HQ_CC=GROUP:FindByName(self.HQ_Template_CC)
50362
51459
  end
50363
- self.version="0.8.15"
51460
+ self.version="0.8.16"
50364
51461
  self:I(string.format("***** Starting MANTIS Version %s *****",self.version))
50365
51462
  self:SetStartState("Stopped")
50366
51463
  self:AddTransition("Stopped","Start","Running")
@@ -50621,7 +51718,7 @@ local HQGroup=self.HQ_CC
50621
51718
  if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive()then
50622
51719
  local _hqgrp=self.HQ_CC
50623
51720
  local text=self.lid.." Relocating HQ"
50624
- _hqgrp:RelocateGroundRandomInRadius(20,500,true,true)
51721
+ _hqgrp:RelocateGroundRandomInRadius(20,500,true,true,nil,true)
50625
51722
  end
50626
51723
  if self.autorelocateunits.EWR then
50627
51724
  local EWR_GRP=SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce()
@@ -50631,7 +51728,7 @@ if _grp:IsAlive()and _grp:IsGround()then
50631
51728
  local text=self.lid.." Relocating EWR ".._grp:GetName()
50632
51729
  local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
50633
51730
  if self.verbose then self:I(text)end
50634
- _grp:RelocateGroundRandomInRadius(20,500,true,true)
51731
+ _grp:RelocateGroundRandomInRadius(20,500,true,true,nil,true)
50635
51732
  end
50636
51733
  end
50637
51734
  end
@@ -51859,7 +52956,7 @@ HARD="TOPGUN Graduate",
51859
52956
  }
51860
52957
  AIRBOSS.MenuF10={}
51861
52958
  AIRBOSS.MenuF10Root=nil
51862
- AIRBOSS.version="1.3.2"
52959
+ AIRBOSS.version="1.3.3"
51863
52960
  function AIRBOSS:New(carriername,alias)
51864
52961
  local self=BASE:Inherit(self,FSM:New())
51865
52962
  self:F2({carriername=carriername,alias=alias})
@@ -52378,6 +53475,7 @@ self.SRS:SetGender(Gender or"male")
52378
53475
  self.SRS:SetPath(PathToSRS)
52379
53476
  self.SRS:SetPort(Port or 5002)
52380
53477
  self.SRS:SetLabel(self.AirbossRadio.alias or"AIRBOSS")
53478
+ self.SRS:SetCoordinate(self.carrier:GetCoordinate())
52381
53479
  if GoogleCreds then
52382
53480
  self.SRS:SetGoogle(GoogleCreds)
52383
53481
  end
@@ -55055,7 +56153,7 @@ self:E(self.lid.."ERROR: EventData=nil in event BIRTH!")
55055
56153
  self:E(EventData)
55056
56154
  return
55057
56155
  end
55058
- if EventData.IniUnit==nil then
56156
+ if EventData.IniUnit==nil and(not EventData.IniObjectCategory==Object.Category.STATIC)then
55059
56157
  self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!")
55060
56158
  self:E(EventData)
55061
56159
  return
@@ -56506,7 +57604,7 @@ text=text..string.format("\nWind Vx=%.1f Vy=%.1f Vz=%.1f m/s",wind.x,wind.y,wind
56506
57604
  end
56507
57605
  text=text..string.format("\nPitch=%.1f° | Roll=%.1f° | Yaw=%.1f°",pitch,roll,yaw)
56508
57606
  text=text..string.format("\nClimb Angle=%.1f° | Rate=%d ft/min",unit:GetClimbAngle(),velo.y*196.85)
56509
- local dist=self:_GetOptLandingCoordinate():Get3DDistance(playerData.unit)
57607
+ local dist=self:_GetOptLandingCoordinate():Get3DDistance(playerData.unit:GetVec3())
56510
57608
  local vplayer=playerData.unit:GetVelocityKMH()
56511
57609
  local vcarrier=self.carrier:GetVelocityKMH()
56512
57610
  local dv=math.abs(vplayer-vcarrier)
@@ -58370,7 +59468,7 @@ self.PilotRadio.alias="PILOT"
58370
59468
  self.PilotRadio.voice=Voice or MSRS.Voices.Microsoft.David
58371
59469
  self.PilotRadio.gender=Gender or"male"
58372
59470
  self.PilotRadio.culture=Culture or"en-US"
58373
- if(not Voice)and self.SRS and self.SRS.google then
59471
+ if(not Voice)and self.SRS and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then
58374
59472
  self.PilotRadio.voice=MSRS.Voices.Google.Standard.en_US_Standard_J
58375
59473
  end
58376
59474
  return self
@@ -61653,7 +62751,7 @@ DELIMITER="Punto",
61653
62751
  }
61654
62752
  ATIS.locale="en"
61655
62753
  _ATIS={}
61656
- ATIS.version="0.10.3"
62754
+ ATIS.version="1.0.0"
61657
62755
  function ATIS:New(AirbaseName,Frequency,Modulation)
61658
62756
  local self=BASE:Inherit(self,FSM:New())
61659
62757
  self.airbasename=AirbaseName
@@ -61962,7 +63060,16 @@ self:E(self.lid..string.format("EXPERIMENTAL: Starting ATIS for Helipad %s! SRS
61962
63060
  self.ATISforFARPs=true
61963
63061
  self.useSRS=true
61964
63062
  end
63063
+ if type(self.frequency)=="table"then
63064
+ local frequency=table.concat(self.frequency,"/")
63065
+ local modulation=self.modulation
63066
+ if type(self.modulation)=="table"then
63067
+ modulation=table.concat(self.modulation,"/")
63068
+ end
63069
+ self:I(self.lid..string.format("Starting ATIS v%s for airbase %s on %s MHz Modulation=%s",ATIS.version,self.airbasename,frequency,modulation))
63070
+ else
61965
63071
  self:I(self.lid..string.format("Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d",ATIS.version,self.airbasename,self.frequency,self.modulation))
63072
+ end
61966
63073
  if not self.useSRS then
61967
63074
  self.radioqueue=RADIOQUEUE:New(self.frequency,self.modulation,string.format("ATIS %s",self.airbasename))
61968
63075
  self.radioqueue:SetSenderCoordinate(self.airbase:GetCoordinate())
@@ -61994,7 +63101,17 @@ if ru then
61994
63101
  relayunitstatus=tostring(ru:IsAlive())
61995
63102
  end
61996
63103
  end
61997
- local text=string.format("State %s: Freq=%.3f MHz %s",fsmstate,self.frequency,UTILS.GetModulationName(self.modulation))
63104
+ local text=""
63105
+ if type(self.frequency)=="table"then
63106
+ local frequency=table.concat(self.frequency,"/")
63107
+ local modulation=self.modulation
63108
+ if type(self.modulation)=="table"then
63109
+ modulation=table.concat(self.modulation,"/")
63110
+ end
63111
+ text=string.format("State %s: Freq=%s MHz %s",fsmstate,frequency,modulation)
63112
+ else
63113
+ text=string.format("State %s: Freq=%.3f MHz %s",fsmstate,self.frequency,UTILS.GetModulationName(self.modulation))
63114
+ end
61998
63115
  if self.useSRS then
61999
63116
  text=text..string.format(", SRS path=%s (%s), gender=%s, culture=%s, voice=%s",tostring(self.msrs.path),tostring(self.msrs.port),tostring(self.msrs.gender),tostring(self.msrs.culture),tostring(self.msrs.voice))
62000
63117
  else
@@ -62879,7 +63996,17 @@ function ATIS:UpdateMarker(information,runact,wind,altimeter,temperature)
62879
63996
  if self.markerid then
62880
63997
  self.airbase:GetCoordinate():RemoveMark(self.markerid)
62881
63998
  end
62882
- local text=string.format("ATIS on %.3f %s, %s:\n",self.frequency,UTILS.GetModulationName(self.modulation),tostring(information))
63999
+ local text=""
64000
+ if type(self.frequency)=="table"then
64001
+ local frequency=table.concat(self.frequency,"/")
64002
+ local modulation=self.modulation
64003
+ if type(modulation)=="table"then
64004
+ modulation=table.concat(self.modulation,"/")
64005
+ end
64006
+ text=string.format("ATIS on %s %s, %s:\n",tostring(frequency),tostring(modulation),tostring(information))
64007
+ else
64008
+ text=string.format("ATIS on %.3f %s, %s:\n",self.frequency,UTILS.GetModulationName(self.modulation),tostring(information))
64009
+ end
62883
64010
  text=text..string.format("%s\n",tostring(runact))
62884
64011
  text=text..string.format("%s\n",tostring(wind))
62885
64012
  text=text..string.format("%s\n",tostring(altimeter))
@@ -63343,7 +64470,7 @@ CTLD.UnitTypeCapabilities={
63343
64470
  ["AH-64D_BLK_II"]={type="AH-64D_BLK_II",crates=false,troops=true,cratelimit=0,trooplimit=2,length=17,cargoweightlimit=200},
63344
64471
  ["Bronco-OV-10A"]={type="Bronco-OV-10A",crates=false,troops=true,cratelimit=0,trooplimit=5,length=13,cargoweightlimit=1450},
63345
64472
  }
63346
- CTLD.version="1.0.42"
64473
+ CTLD.version="1.0.45"
63347
64474
  function CTLD:New(Coalition,Prefixes,Alias)
63348
64475
  local self=BASE:Inherit(self,FSM:New())
63349
64476
  BASE:T({Coalition,Prefixes,Alias})
@@ -63388,6 +64515,8 @@ self:AddTransition("*","TroopsRTB","*")
63388
64515
  self:AddTransition("*","CratesDropped","*")
63389
64516
  self:AddTransition("*","CratesBuild","*")
63390
64517
  self:AddTransition("*","CratesRepaired","*")
64518
+ self:AddTransition("*","CratesBuildStarted","*")
64519
+ self:AddTransition("*","CratesRepairStarted","*")
63391
64520
  self:AddTransition("*","Load","*")
63392
64521
  self:AddTransition("*","Save","*")
63393
64522
  self:AddTransition("*","Stop","Stopped")
@@ -63807,6 +64936,7 @@ local desttimer=TIMER:New(function()NearestGroup:Destroy(false)end,self)
63807
64936
  desttimer:Start(self.repairtime-1)
63808
64937
  local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,object,true,NearestGroup:GetCoordinate())
63809
64938
  buildtimer:Start(self.repairtime)
64939
+ self:__CratesRepairStarted(1,Group,Unit)
63810
64940
  else
63811
64941
  if not Engineering then
63812
64942
  self:_SendMessage("Can't repair this unit with "..build.Name,10,false,Group)
@@ -64047,10 +65177,12 @@ realcargo=CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,crat
64047
65177
  table.insert(droppedcargo,realcargo)
64048
65178
  else
64049
65179
  realcargo=CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat)
64050
- Cargo:RemoveStock()
64051
65180
  end
64052
65181
  table.insert(self.Spawned_Cargo,realcargo)
64053
65182
  end
65183
+ if not(drop or pack)then
65184
+ Cargo:RemoveStock()
65185
+ end
64054
65186
  local text=string.format("Crates for %s have been positioned near you!",cratename)
64055
65187
  if drop then
64056
65188
  text=string.format("Crates for %s have been dropped!",cratename)
@@ -64131,6 +65263,34 @@ self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddis
64131
65263
  end
64132
65264
  return self
64133
65265
  end
65266
+ function CTLD:_RemoveCratesNearby(_group,_unit)
65267
+ self:T(self.lid.." _RemoveCratesNearby")
65268
+ local finddist=self.CrateDistance or 35
65269
+ local crates,number=self:_FindCratesNearby(_group,_unit,finddist,true)
65270
+ if number>0 then
65271
+ local text=REPORT:New("Removing Crates Found Nearby:")
65272
+ text:Add("------------------------------------------------------------")
65273
+ for _,_entry in pairs(crates)do
65274
+ local entry=_entry
65275
+ local name=entry:GetName()
65276
+ local dropped=entry:WasDropped()
65277
+ if dropped then
65278
+ text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass))
65279
+ else
65280
+ text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass))
65281
+ end
65282
+ entry:GetPositionable():Destroy(false)
65283
+ end
65284
+ if text:GetCount()==1 then
65285
+ text:Add(" N O N E")
65286
+ end
65287
+ text:Add("------------------------------------------------------------")
65288
+ self:_SendMessage(text:Text(),30,true,_group)
65289
+ else
65290
+ self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group)
65291
+ end
65292
+ return self
65293
+ end
64134
65294
  function CTLD:_GetDistance(_point1,_point2)
64135
65295
  self:T(self.lid.." _GetDistance")
64136
65296
  if _point1 and _point2 then
@@ -64469,6 +65629,26 @@ else
64469
65629
  return false
64470
65630
  end
64471
65631
  end
65632
+ function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template)
65633
+ local Positions={}
65634
+ local template=_DATABASE:GetGroupTemplate(Template)
65635
+ UTILS.PrintTableToLog(template)
65636
+ local numbertroops=#template.units
65637
+ local newcenter=Coordinate:Translate(Radius,((Heading+270)%360))
65638
+ for i=1,360,math.floor(360/numbertroops)do
65639
+ local phead=((Heading+270+i)%360)
65640
+ local post=newcenter:Translate(Radius,phead)
65641
+ local pos1=post:GetVec2()
65642
+ local p1t={
65643
+ x=pos1.x,
65644
+ y=pos1.y,
65645
+ heading=phead,
65646
+ }
65647
+ table.insert(Positions,p1t)
65648
+ end
65649
+ UTILS.PrintTableToLog(Positions)
65650
+ return Positions
65651
+ end
64472
65652
  function CTLD:_UnloadTroops(Group,Unit)
64473
65653
  self:T(self.lid.." _UnloadTroops")
64474
65654
  local droppingatbase=false
@@ -64509,14 +65689,25 @@ factor=cargo:GetCratesNeeded()or 1
64509
65689
  zoneradius=Unit:GetVelocityMPS()or 100
64510
65690
  end
64511
65691
  local zone=ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,zoneradius*factor)
64512
- local randomcoord=zone:GetRandomCoordinate(10,30*factor):GetVec2()
65692
+ local randomcoord=zone:GetRandomCoordinate(10,30*factor)
65693
+ local heading=Group:GetHeading()or 0
65694
+ if hoverunload or grounded then
65695
+ randomcoord=Group:GetCoordinate()
65696
+ local Angle=(heading+270)%360
65697
+ local offset=hoverunload and 1.5 or 5
65698
+ randomcoord:Translate(offset,Angle,nil,true)
65699
+ end
65700
+ local tempcount=0
64513
65701
  for _,_template in pairs(temptable)do
64514
65702
  self.TroopCounter=self.TroopCounter+1
65703
+ tempcount=tempcount+1
64515
65704
  local alias=string.format("%s-%d",_template,math.random(1,100000))
65705
+ local rad=2.5+tempcount
65706
+ local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template)
64516
65707
  self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
64517
- :InitRandomizeUnits(true,20,2)
64518
65708
  :InitDelayOff()
64519
- :SpawnFromVec2(randomcoord)
65709
+ :InitSetUnitAbsolutePositions(Positions)
65710
+ :SpawnFromVec2(randomcoord:GetVec2())
64520
65711
  self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],type)
64521
65712
  end
64522
65713
  cargo:SetWasDropped(true)
@@ -64715,6 +65906,7 @@ if self.buildtime and self.buildtime>0 then
64715
65906
  local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate())
64716
65907
  buildtimer:Start(self.buildtime)
64717
65908
  self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group)
65909
+ self:__CratesBuildStarted(1,Group,Unit)
64718
65910
  else
64719
65911
  self:_BuildObjectFromCrates(Group,Unit,build)
64720
65912
  end
@@ -65008,6 +66200,7 @@ if cancrates then
65008
66200
  local loadmenu=MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates,self._LoadCratesNearby,self,_group,_unit)
65009
66201
  local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates)
65010
66202
  local packmenu=MENU_GROUP_COMMAND:New(_group,"Pack crates",topcrates,self._PackCratesNearby,self,_group,_unit)
66203
+ local removecratesmenu=MENU_GROUP:New(_group,"Remove crates",topcrates)
65011
66204
  if self.usesubcats then
65012
66205
  local subcatmenus={}
65013
66206
  for _name,_entry in pairs(self.subcats)do
@@ -65042,6 +66235,7 @@ menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrat
65042
66235
  end
65043
66236
  end
65044
66237
  listmenu=MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit)
66238
+ removecrates=MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu,self._RemoveCratesNearby,self,_group,_unit)
65045
66239
  local unloadmenu=MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates,self._UnloadCrates,self,_group,_unit)
65046
66240
  if not self.nobuildmenu then
65047
66241
  local buildmenu=MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates,self._BuildCrates,self,_group,_unit)
@@ -77079,6 +78273,15 @@ trigger.action.outSoundForUnit(Unit:GetID(),self.UserSoundFileName)
77079
78273
  end
77080
78274
  return self
77081
78275
  end
78276
+ function USERSOUND:ToClient(Client,Delay)
78277
+ Delay=Delay or 0
78278
+ if Delay>0 then
78279
+ SCHEDULER:New(nil,USERSOUND.ToClient,{self,Client},Delay)
78280
+ else
78281
+ trigger.action.outSoundForUnit(Client:GetID(),self.UserSoundFileName)
78282
+ end
78283
+ return self
78284
+ end
77082
78285
  end
77083
78286
  do
77084
78287
  SOUNDBASE={
@@ -77121,18 +78324,25 @@ subtitle=nil,
77121
78324
  subduration=0,
77122
78325
  useSRS=false,
77123
78326
  }
77124
- function SOUNDFILE:New(FileName,Path,Duration)
78327
+ function SOUNDFILE:New(FileName,Path,Duration,UseSrs)
77125
78328
  local self=BASE:Inherit(self,BASE:New())
78329
+ self:F({FileName,Path,Duration,UseSrs})
77126
78330
  self:SetFileName(FileName)
78331
+ self:SetPlayWithSRS(UseSrs or false)
77127
78332
  self:SetPath(Path)
77128
78333
  self:SetDuration(Duration)
77129
- self:T(string.format("New SOUNDFILE: file name=%s, path=%s",self.filename,self.path))
77130
78334
  return self
77131
78335
  end
77132
78336
  function SOUNDFILE:SetPath(Path)
77133
- self.path=Path or"l10n/DEFAULT/"
77134
- if not Path and self.useSRS then
77135
- self.path=os.getenv('TMP').."\\DCS\\Mission\\l10n\\DEFAULT"
78337
+ self:F({Path})
78338
+ if not Path then
78339
+ if self.useSRS then
78340
+ self.path=lfs.tempdir().."Mission\\l10n\\DEFAULT"
78341
+ else
78342
+ self.path="l10n/DEFAULT/"
78343
+ end
78344
+ else
78345
+ self.path=Path
77136
78346
  end
77137
78347
  local nmax=1000;local n=1
77138
78348
  while(self.path:sub(-1)=="/"or self.path:sub(-1)==[[\]])and n<=nmax do
@@ -77140,6 +78350,7 @@ self.path=self.path:sub(1,#self.path-1)
77140
78350
  n=n+1
77141
78351
  end
77142
78352
  self.path=self.path.."/"
78353
+ self:T("self.path="..self.path)
77143
78354
  return self
77144
78355
  end
77145
78356
  function SOUNDFILE:GetPath()
@@ -77167,11 +78378,13 @@ local name=string.format("%s%s",path,filename)
77167
78378
  return name
77168
78379
  end
77169
78380
  function SOUNDFILE:SetPlayWithSRS(Switch)
78381
+ self:F({Switch})
77170
78382
  if Switch==true or Switch==nil then
77171
78383
  self.useSRS=true
77172
78384
  else
77173
78385
  self.useSRS=false
77174
78386
  end
78387
+ self:T("self.useSRS="..tostring(self.useSRS))
77175
78388
  return self
77176
78389
  end
77177
78390
  end
@@ -77935,6 +79148,7 @@ ClassName="MSRS",
77935
79148
  lid=nil,
77936
79149
  port=5002,
77937
79150
  name="MSRS",
79151
+ backend="srsexe",
77938
79152
  frequencies={},
77939
79153
  modulations={},
77940
79154
  coalition=0,
@@ -77944,13 +79158,14 @@ voice=nil,
77944
79158
  volume=1,
77945
79159
  speed=1,
77946
79160
  coordinate=nil,
79161
+ provider="win",
77947
79162
  Label="ROBOT",
77948
- AltBackend=nil,
77949
79163
  ConfigFileName="Moose_MSRS.lua",
77950
79164
  ConfigFilePath="Config\\",
77951
79165
  ConfigLoaded=false,
79166
+ poptions={},
77952
79167
  }
77953
- MSRS.version="0.1.2"
79168
+ MSRS.version="0.3.0"
77954
79169
  MSRS.Voices={
77955
79170
  Microsoft={
77956
79171
  ["Hedda"]="Microsoft Hedda Desktop",
@@ -78049,48 +79264,52 @@ Wavenet={
78049
79264
  },
78050
79265
  },
78051
79266
  }
78052
- MSRS.GRPCOptions={}
78053
- MSRS.GRPCOptions.gcloud={}
78054
- MSRS.GRPCOptions.win={}
78055
- MSRS.GRPCOptions.azure={}
78056
- MSRS.GRPCOptions.aws={}
78057
- MSRS.GRPCOptions.win.defaultVoice="Hedda"
78058
- MSRS.GRPCOptions.win.voice="Hedda"
78059
- MSRS.GRPCOptions.DefaultProvider="win"
78060
- function MSRS:New(PathToSRS,Frequency,Modulation,Volume,AltBackend)
79267
+ MSRS.Backend={
79268
+ SRSEXE="srsexe",
79269
+ GRPC="grpc",
79270
+ }
79271
+ MSRS.Provider={
79272
+ WINDOWS="win",
79273
+ GOOGLE="gcloud",
79274
+ AZURE="azure",
79275
+ AMAZON="aws",
79276
+ }
79277
+ function MSRS.uuid()
79278
+ local random=math.random
79279
+ local template='yxxx-xxxxxxxxxxxx'
79280
+ return string.gsub(template,'[xy]',function(c)
79281
+ local v=(c=='x')and random(0,0xf)or random(8,0xb)
79282
+ return string.format('%x',v)
79283
+ end)
79284
+ end
79285
+ function MSRS:New(Path,Frequency,Modulation,Backend)
79286
+ local self=BASE:Inherit(self,BASE:New())
79287
+ self:F({Path,Frequency,Modulation,Backend})
78061
79288
  Frequency=Frequency or 143
78062
79289
  Modulation=Modulation or radio.modulation.AM
78063
- local self=BASE:Inherit(self,BASE:New())
78064
- if type(AltBackend)=="table"or type(self.AltBackend)=="table"then
78065
- local Backend=UTILS.DeepCopy(AltBackend)or UTILS.DeepCopy(self.AltBackend)
78066
- Backend.Vars=Backend.Vars or{}
78067
- Backend.Vars.PathToSRS=PathToSRS
78068
- Backend.Vars.Frequency=UTILS.DeepCopy(Frequency)
78069
- Backend.Vars.Modulation=UTILS.DeepCopy(Modulation)
78070
- Backend.Vars.Volume=Volume
78071
- Backend.Functions=Backend.Functions or{}
78072
- return self:_NewAltBackend(Backend)
78073
- end
78074
- local success=self:LoadConfigFile(nil,nil,self.ConfigLoaded)
78075
- if(not success)and(not self.ConfigLoaded)then
78076
- self:SetPath(PathToSRS)
79290
+ self.lid=string.format("%s-%s | ","unknown",self.version)
79291
+ if not self.ConfigLoaded then
79292
+ self:SetPath(Path)
78077
79293
  self:SetPort()
78078
79294
  self:SetFrequencies(Frequency)
78079
79295
  self:SetModulations(Modulation)
78080
79296
  self:SetGender()
78081
79297
  self:SetCoalition()
78082
79298
  self:SetLabel()
78083
- self:SetVolume(Volume)
79299
+ self:SetVolume()
79300
+ self:SetBackend(Backend)
78084
79301
  else
78085
- if PathToSRS then
78086
- self:SetPath(PathToSRS)
79302
+ if Path then
79303
+ self:SetPath(Path)
78087
79304
  end
78088
79305
  if Frequency then
78089
79306
  self:SetFrequencies(Frequency)
79307
+ end
79308
+ if Modulation then
78090
79309
  self:SetModulations(Modulation)
78091
79310
  end
78092
- if Volume then
78093
- self:SetVolume(Volume)
79311
+ if Backend then
79312
+ self:SetBackend(Backend)
78094
79313
  end
78095
79314
  end
78096
79315
  self.lid=string.format("%s-%s | ",self.name,self.version)
@@ -78099,26 +79318,48 @@ self:E(self.lid.."***** ERROR - io or os NOT desanitized! MSRS will not work!")
78099
79318
  end
78100
79319
  return self
78101
79320
  end
78102
- function MSRS:SetPath(Path)
78103
- if Path==nil and not self.path then
78104
- self:E("ERROR: No path to SRS directory specified!")
78105
- return nil
79321
+ function MSRS:SetBackend(Backend)
79322
+ self:F({Backend=Backend})
79323
+ self.backend=Backend or MSRS.Backend.SRSEXE
79324
+ return self
78106
79325
  end
78107
- if Path then
78108
- self.path=Path
79326
+ function MSRS:SetBackendGRPC()
79327
+ self:F()
79328
+ self:SetBackend(MSRS.Backend.GRPC)
79329
+ return self
79330
+ end
79331
+ function MSRS:SetBackendSRSEXE()
79332
+ self:F()
79333
+ self:SetBackend(MSRS.Backend.SRSEXE)
79334
+ return self
79335
+ end
79336
+ function MSRS.SetDefaultBackend(Backend)
79337
+ self:F({Backend=Backend})
79338
+ MSRS.backend=Backend or MSRS.Backend.SRSEXE
79339
+ end
79340
+ function MSRS.SetDefaultBackendGRPC()
79341
+ self:F()
79342
+ MSRS.backend=MSRS.Backend.GRPC
79343
+ end
79344
+ function MSRS:GetBackend()
79345
+ return self.backend
79346
+ end
79347
+ function MSRS:SetPath(Path)
79348
+ self:F({Path=Path})
79349
+ self.path=Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
78109
79350
  local n=1;local nmax=1000
78110
79351
  while(self.path:sub(-1)=="/"or self.path:sub(-1)==[[\]])and n<=nmax do
78111
79352
  self.path=self.path:sub(1,#self.path-1)
78112
79353
  n=n+1
78113
79354
  end
78114
- self:I(string.format("SRS path=%s",self:GetPath()))
78115
- end
79355
+ self:F(string.format("SRS path=%s",self:GetPath()))
78116
79356
  return self
78117
79357
  end
78118
79358
  function MSRS:GetPath()
78119
79359
  return self.path
78120
79360
  end
78121
79361
  function MSRS:SetVolume(Volume)
79362
+ self:F({Volume=Volume})
78122
79363
  local volume=Volume or 1
78123
79364
  if volume>1 then volume=1 elseif volume<0 then volume=0 end
78124
79365
  self.volume=volume
@@ -78128,6 +79369,7 @@ function MSRS:GetVolume()
78128
79369
  return self.volume
78129
79370
  end
78130
79371
  function MSRS:SetLabel(Label)
79372
+ self:F({Label=Label})
78131
79373
  self.Label=Label or"ROBOT"
78132
79374
  return self
78133
79375
  end
@@ -78135,13 +79377,16 @@ function MSRS:GetLabel()
78135
79377
  return self.Label
78136
79378
  end
78137
79379
  function MSRS:SetPort(Port)
79380
+ self:F({Port=Port})
78138
79381
  self.port=Port or 5002
79382
+ self:T(string.format("SRS port=%s",self:GetPort()))
78139
79383
  return self
78140
79384
  end
78141
79385
  function MSRS:GetPort()
78142
79386
  return self.port
78143
79387
  end
78144
79388
  function MSRS:SetCoalition(Coalition)
79389
+ self:F({Coalition=Coalition})
78145
79390
  self.coalition=Coalition or 0
78146
79391
  return self
78147
79392
  end
@@ -78149,17 +79394,14 @@ function MSRS:GetCoalition()
78149
79394
  return self.coalition
78150
79395
  end
78151
79396
  function MSRS:SetFrequencies(Frequencies)
78152
- if type(Frequencies)~="table"then
78153
- Frequencies={Frequencies}
78154
- end
78155
- self.frequencies=Frequencies
79397
+ self:F(Frequencies)
79398
+ self.frequencies=UTILS.EnsureTable(Frequencies,false)
78156
79399
  return self
78157
79400
  end
78158
79401
  function MSRS:AddFrequencies(Frequencies)
78159
- if type(Frequencies)~="table"then
78160
- Frequencies={Frequencies}
78161
- end
78162
- for _,_freq in pairs(Frequencies)do
79402
+ self:F(Frequencies)
79403
+ for _,_freq in pairs(UTILS.EnsureTable(Frequencies,false))do
79404
+ self:T(self.lid..string.format("Adding frequency %s",tostring(_freq)))
78163
79405
  table.insert(self.frequencies,_freq)
78164
79406
  end
78165
79407
  return self
@@ -78168,17 +79410,15 @@ function MSRS:GetFrequencies()
78168
79410
  return self.frequencies
78169
79411
  end
78170
79412
  function MSRS:SetModulations(Modulations)
78171
- if type(Modulations)~="table"then
78172
- Modulations={Modulations}
78173
- end
78174
- self.modulations=Modulations
79413
+ self:F(Modulations)
79414
+ self.modulations=UTILS.EnsureTable(Modulations,false)
79415
+ self:T(self.lid.."Modulations:")
79416
+ self:T(self.modulations)
78175
79417
  return self
78176
79418
  end
78177
79419
  function MSRS:AddModulations(Modulations)
78178
- if type(Modulations)~="table"then
78179
- Modulations={Modulations}
78180
- end
78181
- for _,_mod in pairs(Modulations)do
79420
+ self:F(Modulations)
79421
+ for _,_mod in pairs(UTILS.EnsureTable(Modulations,false))do
78182
79422
  table.insert(self.modulations,_mod)
78183
79423
  end
78184
79424
  return self
@@ -78187,83 +79427,176 @@ function MSRS:GetModulations()
78187
79427
  return self.modulations
78188
79428
  end
78189
79429
  function MSRS:SetGender(Gender)
79430
+ self:F({Gender=Gender})
78190
79431
  Gender=Gender or"female"
78191
79432
  self.gender=Gender:lower()
78192
79433
  self:T("Setting gender to "..tostring(self.gender))
78193
79434
  return self
78194
79435
  end
78195
79436
  function MSRS:SetCulture(Culture)
79437
+ self:F({Culture=Culture})
78196
79438
  self.culture=Culture
78197
79439
  return self
78198
79440
  end
78199
79441
  function MSRS:SetVoice(Voice)
79442
+ self:F({Voice=Voice})
78200
79443
  self.voice=Voice
78201
79444
  return self
78202
79445
  end
78203
- function MSRS:SetDefaultVoice(Voice)
78204
- self.defaultVoice=Voice
78205
- local provider=self.provider or self.GRPCOptions.DefaultProvider or MSRS.GRPCOptions.DefaultProvider or"win"
78206
- self.GRPCOptions[provider].defaultVoice=Voice
79446
+ function MSRS:SetVoiceProvider(Voice,Provider)
79447
+ self:F({Voice=Voice,Provider=Provider})
79448
+ self.poptions=self.poptions or{}
79449
+ self.poptions[Provider or self:GetProvider()]=Voice
79450
+ return self
79451
+ end
79452
+ function MSRS:SetVoiceWindows(Voice)
79453
+ self:F({Voice=Voice})
79454
+ self:SetVoiceProvider(Voice or"Microsoft Hazel Desktop",MSRS.Provider.WINDOWS)
78207
79455
  return self
78208
79456
  end
79457
+ function MSRS:SetVoiceGoogle(Voice)
79458
+ self:F({Voice=Voice})
79459
+ self:SetVoiceProvider(Voice or MSRS.Voices.Google.Standard.en_GB_Standard_A,MSRS.Provider.GOOGLE)
79460
+ return self
79461
+ end
79462
+ function MSRS:SetVoiceAzure(Voice)
79463
+ self:F({Voice=Voice})
79464
+ self:SetVoiceProvider(Voice or"en-US-AriaNeural",MSRS.Provider.AZURE)
79465
+ return self
79466
+ end
79467
+ function MSRS:SetVoiceAmazon(Voice)
79468
+ self:F({Voice=Voice})
79469
+ self:SetVoiceProvider(Voice or"Brian",MSRS.Provider.AMAZON)
79470
+ return self
79471
+ end
79472
+ function MSRS:GetVoice(Provider)
79473
+ Provider=Provider or self.provider
79474
+ if Provider and self.poptions[Provider]and self.poptions[Provider].voice then
79475
+ return self.poptions[Provider].voice
79476
+ else
79477
+ return self.voice
79478
+ end
79479
+ end
78209
79480
  function MSRS:SetCoordinate(Coordinate)
79481
+ self:F(Coordinate)
78210
79482
  self.coordinate=Coordinate
78211
79483
  return self
78212
79484
  end
78213
79485
  function MSRS:SetGoogle(PathToCredentials)
79486
+ self:F({PathToCredentials=PathToCredentials})
78214
79487
  if PathToCredentials then
78215
- self.google=PathToCredentials
78216
- self.APIKey=PathToCredentials
78217
- self.provider="gcloud"
78218
- self.GRPCOptions.DefaultProvider="gcloud"
78219
- self.GRPCOptions.gcloud.key=PathToCredentials
79488
+ self.provider=MSRS.Provider.GOOGLE
79489
+ self:SetProviderOptionsGoogle(PathToCredentials,PathToCredentials)
78220
79490
  end
78221
79491
  return self
78222
79492
  end
78223
79493
  function MSRS:SetGoogleAPIKey(APIKey)
79494
+ self:F({APIKey=APIKey})
78224
79495
  if APIKey then
78225
- self.APIKey=APIKey
78226
- self.provider="gcloud"
78227
- self.GRPCOptions.DefaultProvider="gcloud"
78228
- self.GRPCOptions.gcloud.key=APIKey
79496
+ self.provider=MSRS.Provider.GOOGLE
79497
+ if self.poptions[MSRS.Provider.GOOGLE]then
79498
+ self.poptions[MSRS.Provider.GOOGLE].key=APIKey
79499
+ else
79500
+ self:SetProviderOptionsGoogle(nil,APIKey)
79501
+ end
79502
+ end
79503
+ return self
79504
+ end
79505
+ function MSRS:SetProvider(Provider)
79506
+ self:F({Provider=Provider})
79507
+ self.provider=Provider or MSRS.Provider.WINDOWS
79508
+ return self
79509
+ end
79510
+ function MSRS:GetProvider()
79511
+ return self.provider or MSRS.Provider.WINDOWS
79512
+ end
79513
+ function MSRS:SetProviderOptions(Provider,CredentialsFile,AccessKey,SecretKey,Region)
79514
+ self:F({Provider,CredentialsFile,AccessKey,SecretKey,Region})
79515
+ local option=MSRS._CreateProviderOptions(Provider,CredentialsFile,AccessKey,SecretKey,Region)
79516
+ if self then
79517
+ self.poptions=self.poptions or{}
79518
+ self.poptions[Provider]=option
79519
+ else
79520
+ MSRS.poptions=MSRS.poptions or{}
79521
+ MSRS.poptions[Provider]=option
79522
+ end
79523
+ return option
79524
+ end
79525
+ function MSRS._CreateProviderOptions(Provider,CredentialsFile,AccessKey,SecretKey,Region)
79526
+ self:F({Provider,CredentialsFile,AccessKey,SecretKey,Region})
79527
+ local option={}
79528
+ option.provider=Provider
79529
+ option.credentials=CredentialsFile
79530
+ option.key=AccessKey
79531
+ option.secret=SecretKey
79532
+ option.region=Region
79533
+ return option
79534
+ end
79535
+ function MSRS:SetProviderOptionsGoogle(CredentialsFile,AccessKey)
79536
+ self:F({CredentialsFile,AccessKey})
79537
+ self:SetProviderOptions(MSRS.Provider.GOOGLE,CredentialsFile,AccessKey)
79538
+ return self
79539
+ end
79540
+ function MSRS:SetProviderOptionsAmazon(AccessKey,SecretKey,Region)
79541
+ self:F({AccessKey,SecretKey,Region})
79542
+ self:SetProviderOptions(MSRS.Provider.AMAZON,nil,AccessKey,SecretKey,Region)
79543
+ return self
78229
79544
  end
79545
+ function MSRS:SetProviderOptionsAzure(AccessKey,Region)
79546
+ self:F({AccessKey,Region})
79547
+ self:SetProviderOptions(MSRS.Provider.AZURE,nil,AccessKey,nil,Region)
79548
+ return self
79549
+ end
79550
+ function MSRS:GetProviderOptions(Provider)
79551
+ return self.poptions[Provider or self.provider]or{}
79552
+ end
79553
+ function MSRS:SetTTSProviderGoogle()
79554
+ self:F()
79555
+ self:SetProvider(MSRS.Provider.GOOGLE)
79556
+ return self
79557
+ end
79558
+ function MSRS:SetTTSProviderMicrosoft()
79559
+ self:F()
79560
+ self:SetProvider(MSRS.Provider.WINDOWS)
79561
+ return self
79562
+ end
79563
+ function MSRS:SetTTSProviderAzure()
79564
+ self:F()
79565
+ self:SetProvider(MSRS.Provider.AZURE)
79566
+ return self
79567
+ end
79568
+ function MSRS:SetTTSProviderAmazon()
79569
+ self:F()
79570
+ self:SetProvider(MSRS.Provider.AMAZON)
78230
79571
  return self
78231
79572
  end
78232
79573
  function MSRS:Help()
78233
- local path=self:GetPath()or STTS.DIRECTORY
78234
- local exe=STTS.EXECUTABLE or"DCS-SR-ExternalAudio.exe"
78235
- local filename=os.getenv('TMP').."\\MSRS-help-"..STTS.uuid()..".txt"
79574
+ self:F()
79575
+ local path=self:GetPath()
79576
+ local exe="DCS-SR-ExternalAudio.exe"
79577
+ local filename=os.getenv('TMP').."\\MSRS-help-"..MSRS.uuid()..".txt"
78236
79578
  local command=string.format("%s/%s --help > %s",path,exe,filename)
78237
79579
  os.execute(command)
78238
79580
  local f=assert(io.open(filename,"rb"))
78239
79581
  local data=f:read("*all")
78240
79582
  f:close()
78241
- env.info("SRS STTS help output:")
79583
+ env.info("SRS help output:")
78242
79584
  env.info("======================================================================")
78243
79585
  env.info(data)
78244
79586
  env.info("======================================================================")
78245
79587
  return self
78246
79588
  end
78247
- function MSRS.SetDefaultBackend(Backend)
78248
- if type(Backend)=="table"then
78249
- MSRS.AltBackend=UTILS.DeepCopy(Backend)
78250
- else
78251
- return false
78252
- end
78253
- return true
78254
- end
78255
- function MSRS.ResetDefaultBackend()
78256
- MSRS.AltBackend=nil
78257
- return true
78258
- end
78259
- function MSRS.SetDefaultBackendGRPC()
78260
- return MSRS.SetDefaultBackend(MSRS_BACKEND_DCSGRPC)
78261
- end
78262
79589
  function MSRS:PlaySoundFile(Soundfile,Delay)
79590
+ self:F({Soundfile,Delay})
79591
+ local soundfile=Soundfile:GetName()
79592
+ local exists=UTILS.FileExists(soundfile)
79593
+ if not exists then
79594
+ self:E("ERROR: MSRS sound file does not exist! File="..soundfile)
79595
+ return self
79596
+ end
78263
79597
  if Delay and Delay>0 then
78264
79598
  self:ScheduleOnce(Delay,MSRS.PlaySoundFile,self,Soundfile,0)
78265
79599
  else
78266
- local soundfile=Soundfile:GetName()
78267
79600
  local command=self:_GetCommand()
78268
79601
  command=command..' --file="'..tostring(soundfile)..'"'
78269
79602
  self:_ExecCommand(command)
@@ -78271,42 +79604,53 @@ end
78271
79604
  return self
78272
79605
  end
78273
79606
  function MSRS:PlaySoundText(SoundText,Delay)
79607
+ self:F({SoundText,Delay})
78274
79608
  if Delay and Delay>0 then
78275
79609
  self:ScheduleOnce(Delay,MSRS.PlaySoundText,self,SoundText,0)
78276
79610
  else
79611
+ if self.backend==MSRS.Backend.GRPC then
79612
+ self:_DCSgRPCtts(SoundText.text,nil,SoundText.gender,SoundText.culture,SoundText.voice,SoundText.volume,SoundText.label,SoundText.coordinate)
79613
+ else
78277
79614
  local command=self:_GetCommand(nil,nil,nil,SoundText.gender,SoundText.voice,SoundText.culture,SoundText.volume,SoundText.speed)
78278
79615
  command=command..string.format(" --text=\"%s\"",tostring(SoundText.text))
78279
79616
  self:_ExecCommand(command)
78280
79617
  end
79618
+ end
78281
79619
  return self
78282
79620
  end
78283
79621
  function MSRS:PlayText(Text,Delay,Coordinate)
79622
+ self:F({Text,Delay,Coordinate})
78284
79623
  if Delay and Delay>0 then
78285
79624
  self:ScheduleOnce(Delay,MSRS.PlayText,self,Text,nil,Coordinate)
78286
79625
  else
78287
- local command=self:_GetCommand(nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,Coordinate)
78288
- command=command..string.format(" --text=\"%s\"",tostring(Text))
78289
- self:_ExecCommand(command)
79626
+ if self.backend==MSRS.Backend.GRPC then
79627
+ self:T(self.lid.."Transmitting")
79628
+ self:_DCSgRPCtts(Text,nil,nil,nil,nil,nil,nil,Coordinate)
79629
+ else
79630
+ self:PlayTextExt(Text,Delay,nil,nil,nil,nil,nil,nil,nil,Coordinate)
79631
+ end
78290
79632
  end
78291
79633
  return self
78292
79634
  end
78293
79635
  function MSRS:PlayTextExt(Text,Delay,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate)
79636
+ self:F({Text,Delay,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate})
78294
79637
  if Delay and Delay>0 then
78295
79638
  self:ScheduleOnce(Delay,MSRS.PlayTextExt,self,Text,0,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate)
78296
79639
  else
78297
- if Frequencies and type(Frequencies)~="table"then
78298
- Frequencies={Frequencies}
78299
- end
78300
- if Modulations and type(Modulations)~="table"then
78301
- Modulations={Modulations}
78302
- end
78303
- local command=self:_GetCommand(Frequencies,Modulations,nil,Gender,Voice,Culture,Volume,nil,nil,Label,Coordinate)
79640
+ Frequencies=Frequencies or self:GetFrequencies()
79641
+ Modulations=Modulations or self:GetModulations()
79642
+ if self.backend==MSRS.Backend.SRSEXE then
79643
+ local command=self:_GetCommand(UTILS.EnsureTable(Frequencies,false),UTILS.EnsureTable(Modulations,false),nil,Gender,Voice,Culture,Volume,nil,nil,Label,Coordinate)
78304
79644
  command=command..string.format(" --text=\"%s\"",tostring(Text))
78305
79645
  self:_ExecCommand(command)
79646
+ elseif self.backend==MSRS.Backend.GRPC then
79647
+ self:_DCSgRPCtts(Text,Frequencies,Gender,Culture,Voice,Volume,Label,Coordinate)
79648
+ end
78306
79649
  end
78307
79650
  return self
78308
79651
  end
78309
79652
  function MSRS:PlayTextFile(TextFile,Delay)
79653
+ self:F({TextFile,Delay})
78310
79654
  if Delay and Delay>0 then
78311
79655
  self:ScheduleOnce(Delay,MSRS.PlayTextFile,self,TextFile,0)
78312
79656
  else
@@ -78319,51 +79663,96 @@ local command=self:_GetCommand()
78319
79663
  command=command..string.format(" --textFile=\"%s\"",tostring(TextFile))
78320
79664
  self:T(string.format("MSRS TextFile command=%s",command))
78321
79665
  local l=string.len(command)
79666
+ self:T(string.format("Command length=%d",l))
78322
79667
  self:_ExecCommand(command)
78323
79668
  end
78324
79669
  return self
78325
79670
  end
78326
- function MSRS:_NewAltBackend(Backend)
78327
- BASE:T('Entering MSRS:_NewAltBackend()')
78328
- for funcName,funcDef in pairs(Backend.Functions)do
78329
- if type(funcDef)=='function'then
78330
- BASE:T('MSRS (re-)defining function MSRS:'..funcName)
78331
- self[funcName]=funcDef
79671
+ function MSRS:_GetLatLongAlt(Coordinate)
79672
+ self:F({Coordinate=Coordinate})
79673
+ local lat=0.0
79674
+ local lon=0.0
79675
+ local alt=0.0
79676
+ if Coordinate then
79677
+ lat,lon,alt=coord.LOtoLL(Coordinate)
79678
+ end
79679
+ return lat,lon,math.floor(alt)
79680
+ end
79681
+ function MSRS:_GetCommand(freqs,modus,coal,gender,voice,culture,volume,speed,port,label,coordinate)
79682
+ self:F({freqs,modus,coal,gender,voice,culture,volume,speed,port,label,coordinate})
79683
+ local path=self:GetPath()
79684
+ local exe="DCS-SR-ExternalAudio.exe"
79685
+ local fullPath=string.format("%s\\%s",path,exe)
79686
+ freqs=table.concat(freqs or self.frequencies,",")
79687
+ modus=table.concat(modus or self.modulations,",")
79688
+ coal=coal or self.coalition
79689
+ gender=gender or self.gender
79690
+ voice=voice or self:GetVoice(self.provider)or self.voice
79691
+ culture=culture or self.culture
79692
+ volume=volume or self.volume
79693
+ speed=speed or self.speed
79694
+ port=port or self.port
79695
+ label=label or self.Label
79696
+ coordinate=coordinate or self.coordinate
79697
+ modus=modus:gsub("0","AM")
79698
+ modus=modus:gsub("1","FM")
79699
+ local command=string.format('"%s\\%s" -f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"',path,exe,freqs,modus,coal,port,label,volume)
79700
+ if voice then
79701
+ command=command..string.format(" --voice=\"%s\"",tostring(voice))
79702
+ else
79703
+ if gender and gender~="female"then
79704
+ command=command..string.format(" -g %s",tostring(gender))
79705
+ end
79706
+ if culture and culture~="en-GB"then
79707
+ command=command..string.format(" -l %s",tostring(culture))
78332
79708
  end
78333
79709
  end
78334
- for varName,varVal in pairs(Backend.Vars)do
78335
- BASE:T('MSRS setting self.'..varName)
78336
- self[varName]=UTILS.DeepCopy(varVal)
79710
+ if coordinate then
79711
+ local lat,lon,alt=self:_GetLatLongAlt(coordinate)
79712
+ command=command..string.format(" -L %.4f -O %.4f -A %d",lat,lon,alt)
78337
79713
  end
78338
- if self._MSRSbackendInit and type(self._MSRSbackendInit)=='function'then
78339
- return self:_MSRSbackendInit()
79714
+ if self.provider==MSRS.Provider.GOOGLE then
79715
+ local pops=self:GetProviderOptions()
79716
+ command=command..string.format(' --ssml -G "%s"',pops.credentials)
79717
+ elseif self.provider==MSRS.Provider.WINDOWS then
79718
+ else
79719
+ self:E("ERROR: SRS only supports WINWOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as ")
78340
79720
  end
78341
- return self
79721
+ if not UTILS.FileExists(fullPath)then
79722
+ self:E("ERROR: MSRS SRS executable does not exist! FullPath="..fullPath)
79723
+ command="CommandNotFound"
79724
+ end
79725
+ self:T("MSRS command from _GetCommand="..command)
79726
+ return command
78342
79727
  end
78343
79728
  function MSRS:_ExecCommand(command)
78344
- self:T("SRS TTS command="..command)
78345
- local filename=os.getenv('TMP').."\\MSRS-"..STTS.uuid()..".bat"
79729
+ self:F({command=command})
79730
+ if string.find(command,"CommandNotFound")then return 0 end
79731
+ local batContent=command.." && exit"
79732
+ local filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".bat"
78346
79733
  local script=io.open(filename,"w+")
78347
- script:write(command.." && exit")
79734
+ script:write(batContent)
78348
79735
  script:close()
78349
- command=string.format('start /b "" "%s"',filename)
79736
+ self:T("MSRS batch file created: "..filename)
79737
+ self:T("MSRS batch content: "..batContent)
78350
79738
  local res=nil
78351
79739
  if true then
78352
- local filenvbs=os.getenv('TMP').."\\MSRS-"..STTS.uuid()..".vbs"
79740
+ local filenvbs=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".vbs"
78353
79741
  local script=io.open(filenvbs,"w+")
78354
79742
  script:write(string.format('Dim WinScriptHost\n'))
78355
79743
  script:write(string.format('Set WinScriptHost = CreateObject("WScript.Shell")\n'))
78356
79744
  script:write(string.format('WinScriptHost.Run Chr(34) & "%s" & Chr(34), 0\n',filename))
78357
79745
  script:write(string.format('Set WinScriptHost = Nothing'))
78358
79746
  script:close()
79747
+ self:T("MSRS vbs file created to start batch="..filenvbs)
78359
79748
  local runvbs=string.format('cscript.exe //Nologo //B "%s"',filenvbs)
78360
- self:T("MSRS execute command="..command)
78361
79749
  self:T("MSRS execute VBS command="..runvbs)
78362
79750
  res=os.execute(runvbs)
78363
79751
  timer.scheduleFunction(os.remove,filename,timer.getTime()+1)
78364
79752
  timer.scheduleFunction(os.remove,filenvbs,timer.getTime()+1)
79753
+ self:T("MSRS vbs and batch file removed")
78365
79754
  elseif false then
78366
- local filenvbs=os.getenv('TMP').."\\MSRS-"..STTS.uuid()..".vbs"
79755
+ local filenvbs=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".vbs"
78367
79756
  local script=io.open(filenvbs,"w+")
78368
79757
  script:write(string.format('Set oShell = CreateObject ("Wscript.Shell")\n'))
78369
79758
  script:write(string.format('Dim strArgs\n'))
@@ -78373,261 +79762,118 @@ script:close()
78373
79762
  local runvbs=string.format('cscript.exe //Nologo //B "%s"',filenvbs)
78374
79763
  res=os.execute(runvbs)
78375
79764
  else
79765
+ command=string.format('start /b "" "%s"',filename)
78376
79766
  self:T("MSRS execute command="..command)
78377
79767
  res=os.execute(command)
78378
79768
  timer.scheduleFunction(os.remove,filename,timer.getTime()+1)
78379
79769
  end
78380
79770
  return res
78381
79771
  end
78382
- function MSRS:_GetLatLongAlt(Coordinate)
78383
- local lat,lon,alt=coord.LOtoLL(Coordinate)
78384
- return lat,lon,math.floor(alt)
79772
+ function MSRS:_DCSgRPCtts(Text,Frequencies,Gender,Culture,Voice,Volume,Label,Coordinate)
79773
+ self:F("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts()")
79774
+ self:F({Text,Frequencies,Gender,Culture,Voice,Volume,Label,Coordinate})
79775
+ local options={}
79776
+ local ssml=Text or''
79777
+ Frequencies=UTILS.EnsureTable(Frequencies,true)or self:GetFrequencies()
79778
+ options.plaintext=Text
79779
+ options.srsClientName=Label or self.Label
79780
+ if self.coordinate then
79781
+ options.position={}
79782
+ options.position.lat,options.position.lon,options.position.alt=self:_GetLatLongAlt(self.coordinate)
78385
79783
  end
78386
- function MSRS:_GetCommand(freqs,modus,coal,gender,voice,culture,volume,speed,port,label,coordinate)
78387
- local path=self:GetPath()or STTS.DIRECTORY
78388
- local exe=STTS.EXECUTABLE or"DCS-SR-ExternalAudio.exe"
78389
- freqs=table.concat(freqs or self.frequencies,",")
78390
- modus=table.concat(modus or self.modulations,",")
78391
- coal=coal or self.coalition
78392
- gender=gender or self.gender
78393
- voice=voice or self.voice
78394
- culture=culture or self.culture
78395
- volume=volume or self.volume
78396
- speed=speed or self.speed
78397
- port=port or self.port
78398
- label=label or self.Label
78399
- coordinate=coordinate or self.coordinate
78400
- modus=modus:gsub("0","AM")
78401
- modus=modus:gsub("1","FM")
78402
- local command=string.format('"%s\\%s" -f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"',path,exe,freqs,modus,coal,port,label,volume)
78403
- if voice then
78404
- command=command..string.format(" --voice=\"%s\"",tostring(voice))
79784
+ options.coalition=UTILS.GetCoalitionName(self.coalition):lower()
79785
+ local provider=self.provider or MSRS.Provider.WINDOWS
79786
+ self:F({provider=provider})
79787
+ options.provider={}
79788
+ options.provider[provider]=self:GetProviderOptions(provider)
79789
+ Voice=Voice or self:GetVoice(self.provider)or self.voice
79790
+ if Voice then
79791
+ options.provider[provider].voice=Voice
78405
79792
  else
78406
- if gender and gender~="female"then
78407
- command=command..string.format(" -g %s",tostring(gender))
79793
+ local preTag,genderProp,langProp,postTag='','','',''
79794
+ local gender=""
79795
+ if self.gender then
79796
+ gender=string.format(' gender=\"%s\"',self.gender)
78408
79797
  end
78409
- if culture and culture~="en-GB"then
78410
- command=command..string.format(" -l %s",tostring(culture))
79798
+ local language=""
79799
+ if self.culture then
79800
+ language=string.format(' language=\"%s\"',self.culture)
78411
79801
  end
79802
+ if self.gender or self.culture then
79803
+ ssml=string.format("<voice%s%s>%s</voice>",gender,language,Text)
78412
79804
  end
78413
- if coordinate then
78414
- local lat,lon,alt=self:_GetLatLongAlt(coordinate)
78415
- command=command..string.format(" -L %.4f -O %.4f -A %d",lat,lon,alt)
78416
79805
  end
78417
- if self.google then
78418
- command=command..string.format(' --ssml -G "%s"',self.google)
79806
+ for _,freq in pairs(Frequencies)do
79807
+ self:F("Calling GRPC.tts with the following parameter:")
79808
+ self:F({ssml=ssml,freq=freq,options=options})
79809
+ self:F(options.provider[provider])
79810
+ GRPC.tts(ssml,freq*1e6,options)
78419
79811
  end
78420
- self:I("MSRS command="..command)
78421
- return command
78422
79812
  end
78423
- function MSRS:LoadConfigFile(Path,Filename,ConfigLoaded)
79813
+ function MSRS:LoadConfigFile(Path,Filename)
79814
+ if lfs==nil then
79815
+ env.info("*****Note - lfs and os need to be desanitized for MSRS to work!")
79816
+ return false
79817
+ end
78424
79818
  local path=Path or lfs.writedir()..MSRS.ConfigFilePath
78425
79819
  local file=Filename or MSRS.ConfigFileName or"Moose_MSRS.lua"
78426
- if UTILS.CheckFileExists(path,file)and not ConfigLoaded then
79820
+ local pathandfile=path..file
79821
+ local filexsists=UTILS.FileExists(pathandfile)
79822
+ if filexsists and not MSRS.ConfigLoaded then
79823
+ env.info("FF reading config file")
78427
79824
  assert(loadfile(path..file))()
78428
79825
  if MSRS_Config then
78429
- if self then
78430
- self.path=MSRS_Config.Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
78431
- self.port=MSRS_Config.Port or 5002
78432
- self.frequencies=MSRS_Config.Frequency or{127,243}
78433
- self.modulations=MSRS_Config.Modulation or{0,0}
78434
- self.coalition=MSRS_Config.Coalition or 0
79826
+ local Self=self or MSRS
79827
+ Self.path=MSRS_Config.Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
79828
+ Self.port=MSRS_Config.Port or 5002
79829
+ Self.backend=MSRS_Config.Backend or MSRS.Backend.SRSEXE
79830
+ Self.frequencies=MSRS_Config.Frequency or{127,243}
79831
+ Self.modulations=MSRS_Config.Modulation or{0,0}
79832
+ Self.coalition=MSRS_Config.Coalition or 0
78435
79833
  if MSRS_Config.Coordinate then
78436
- self.coordinate=COORDINATE:New(MSRS_Config.Coordinate[1],MSRS_Config.Coordinate[2],MSRS_Config.Coordinate[3])
78437
- end
78438
- self.culture=MSRS_Config.Culture or"en-GB"
78439
- self.gender=MSRS_Config.Gender or"male"
78440
- self.google=MSRS_Config.Google
78441
- self.Label=MSRS_Config.Label or"MSRS"
78442
- self.voice=MSRS_Config.Voice
78443
- if MSRS_Config.GRPC then
78444
- self.provider=MSRS_Config.GRPC.DefaultProvider
78445
- if MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider]then
78446
- self.APIKey=MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].key
78447
- self.defaultVoice=MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].defaultVoice
78448
- self.region=MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].secret
78449
- self.secret=MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].region
78450
- end
78451
- end
78452
- self.ConfigLoaded=true
78453
- else
78454
- MSRS.path=MSRS_Config.Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
78455
- MSRS.port=MSRS_Config.Port or 5002
78456
- MSRS.frequencies=MSRS_Config.Frequency or{127,243}
78457
- MSRS.modulations=MSRS_Config.Modulation or{0,0}
78458
- MSRS.coalition=MSRS_Config.Coalition or 0
78459
- if MSRS_Config.Coordinate then
78460
- MSRS.coordinate=COORDINATE:New(MSRS_Config.Coordinate[1],MSRS_Config.Coordinate[2],MSRS_Config.Coordinate[3])
79834
+ Self.coordinate=COORDINATE:New(MSRS_Config.Coordinate[1],MSRS_Config.Coordinate[2],MSRS_Config.Coordinate[3])
78461
79835
  end
78462
- MSRS.culture=MSRS_Config.Culture or"en-GB"
78463
- MSRS.gender=MSRS_Config.Gender or"male"
78464
- MSRS.google=MSRS_Config.Google
78465
- MSRS.Label=MSRS_Config.Label or"MSRS"
78466
- MSRS.voice=MSRS_Config.Voice
78467
- if MSRS_Config.GRPC then
78468
- MSRS.provider=MSRS_Config.GRPC.DefaultProvider
78469
- if MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider]then
78470
- MSRS.APIKey=MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].key
78471
- MSRS.defaultVoice=MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].defaultVoice
78472
- MSRS.region=MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].secret
78473
- MSRS.secret=MSRS_Config.GRPC[MSRS_Config.GRPC.DefaultProvider].region
79836
+ Self.culture=MSRS_Config.Culture or"en-GB"
79837
+ Self.gender=MSRS_Config.Gender or"male"
79838
+ Self.Label=MSRS_Config.Label or"MSRS"
79839
+ Self.voice=MSRS_Config.Voice
79840
+ Self.provider=MSRS_Config.Provider or MSRS.Provider.WINDOWS
79841
+ for _,provider in pairs(MSRS.Provider)do
79842
+ if MSRS_Config[provider]then
79843
+ Self.poptions[provider]=MSRS_Config[provider]
78474
79844
  end
78475
79845
  end
78476
- MSRS.ConfigLoaded=true
79846
+ Self.ConfigLoaded=true
78477
79847
  end
79848
+ env.info("MSRS - Successfully loaded default configuration from disk!",false)
78478
79849
  end
78479
- env.info("MSRS - Sucessfully loaded default configuration from disk!",false)
78480
- else
78481
- env.info("MSRS - Cannot load default configuration from disk!",false)
79850
+ if not filexsists then
79851
+ env.info("MSRS - Cannot find default configuration file!",false)
78482
79852
  return false
78483
79853
  end
78484
79854
  return true
78485
79855
  end
78486
- MSRS_BACKEND_DCSGRPC={}
78487
- MSRS_BACKEND_DCSGRPC.version=0.1
78488
- MSRS_BACKEND_DCSGRPC.Functions={}
78489
- MSRS_BACKEND_DCSGRPC.Vars={provider='win'}
78490
- MSRS_BACKEND_DCSGRPC.Functions._MSRSbackendInit=function(self)
78491
- BASE:I('Loaded MSRS DCS-gRPC alternate backend version '..self.AltBackend.version or'unspecified')
78492
- return self
78493
- end
78494
- MSRS_BACKEND_DCSGRPC.Functions.SetPath=function(self)
78495
- return self
78496
- end
78497
- MSRS_BACKEND_DCSGRPC.Functions.GetPath=function(self)
78498
- return''
78499
- end
78500
- MSRS_BACKEND_DCSGRPC.Functions.SetVolume=function(self)
78501
- BASE:I('NOTE: MSRS:SetVolume() not used with DCS-gRPC backend.')
78502
- return self
78503
- end
78504
- MSRS_BACKEND_DCSGRPC.Functions.GetVolume=function(self)
78505
- BASE:I('NOTE: MSRS:GetVolume() not used with DCS-gRPC backend.')
78506
- return 1
78507
- end
78508
- MSRS_BACKEND_DCSGRPC.Functions.SetGender=function(self,Gender)
78509
- if Gender then
78510
- self.gender=Gender:lower()
78511
- end
78512
- self:T("Setting gender to "..tostring(self.gender))
78513
- return self
78514
- end
78515
- MSRS_BACKEND_DCSGRPC.Functions.SetGoogle=function(self)
78516
- self.provider='gcloud'
78517
- return self
78518
- end
78519
- MSRS_BACKEND_DCSGRPC.Functions.SetAPIKey=function(self,key)
78520
- self.APIKey=key
78521
- return self
78522
- end
78523
- MSRS_BACKEND_DCSGRPC.Functions.SetDefaultVoice=function(self,voice)
78524
- self.defaultVoice=voice
78525
- return self
78526
- end
78527
- MSRS_BACKEND_DCSGRPC.Functions.SetAWS=function(self)
78528
- self.provider='aws'
78529
- return self
78530
- end
78531
- MSRS_BACKEND_DCSGRPC.Functions.SetAzure=function(self)
78532
- self.provider='azure'
78533
- return self
78534
- end
78535
- MSRS_BACKEND_DCSGRPC.Functions.SetWin=function(self)
78536
- self.provider='win'
78537
- return self
78538
- end
78539
- MSRS_BACKEND_DCSGRPC.Functions.Help=function(self)
78540
- env.info('For DCS-gRPC help, please see: https://github.com/DCS-gRPC/rust-server')
78541
- return self
78542
- end
78543
- MSRS_BACKEND_DCSGRPC.Functions.PlaySoundFile=function(self)
78544
- BASE:E("ERROR: MSRS:PlaySoundFile() is not supported by the DCS-gRPC backend.")
78545
- return self
78546
- end
78547
- MSRS_BACKEND_DCSGRPC.Functions.PlaySoundText=function(self,SoundText,Delay)
78548
- if Delay and Delay>0 then
78549
- self:ScheduleOnce(Delay,self.PlaySoundText,self,SoundText,0)
78550
- else
78551
- self:_DCSgRPCtts(tostring(SoundText.text))
78552
- end
78553
- return self
78554
- end
78555
- MSRS_BACKEND_DCSGRPC.Functions.PlayText=function(self,Text,Delay)
78556
- if Delay and Delay>0 then
78557
- self:ScheduleOnce(Delay,self.PlayText,self,Text,0)
78558
- else
78559
- self:_DCSgRPCtts(tostring(Text))
78560
- end
78561
- return self
78562
- end
78563
- MSRS_BACKEND_DCSGRPC.Functions.PlayTextExt=function(self,Text,Delay,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label)
78564
- if Delay and Delay>0 then
78565
- self:ScheduleOnce(Delay,self.PlayTextExt,self,Text,0,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label)
79856
+ function MSRS.getSpeechTime(length,speed,isGoogle)
79857
+ local maxRateRatio=3
79858
+ speed=speed or 1.0
79859
+ isGoogle=isGoogle or false
79860
+ local speedFactor=1.0
79861
+ if isGoogle then
79862
+ speedFactor=speed
78566
79863
  else
78567
- self:_DCSgRPCtts(tostring(Text),nil,Frequencies,Voice,Label)
78568
- end
78569
- return self
78570
- end
78571
- MSRS_BACKEND_DCSGRPC.Functions.PlayTextFile=function(self,TextFile,Delay)
78572
- BASE:E("ERROR: MSRS:PlayTextFile() is not supported by the DCS-gRPC backend.")
78573
- return self
78574
- end
78575
- MSRS_BACKEND_DCSGRPC.Functions._DCSgRPCtts=function(self,Text,Plaintext,Frequencies,Voice,Label)
78576
- BASE:T("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts()")
78577
- BASE:T({Text,Plaintext,Frequencies,Voice,Label})
78578
- local options=self.ProviderOptions or MSRS.ProviderOptions or{}
78579
- local ssml=Text or''
78580
- local XmitFrequencies=Frequencies or self.Frequency
78581
- if type(XmitFrequencies)~="table"then
78582
- XmitFrequencies={XmitFrequencies}
78583
- end
78584
- options.plaintext=Plaintext
78585
- options.srsClientName=Label or self.Label
78586
- options.position={}
78587
- if self.coordinate then
78588
- options.position.lat,options.position.lon,options.position.alt=self:_GetLatLongAlt(self.coordinate)
78589
- end
78590
- options.position.lat=options.position.lat or 0.0
78591
- options.position.lon=options.position.lon or 0.0
78592
- options.position.alt=options.position.alt or 0.0
78593
- if UTILS.GetCoalitionName(self.coalition)=='Blue'then
78594
- options.coalition='blue'
78595
- elseif UTILS.GetCoalitionName(self.coalition)=='Red'then
78596
- options.coalition='red'
78597
- end
78598
- local provider=self.provider or self.GRPCOptions.DefaultProvider or MSRS.GRPCOptions.DefaultProvider
78599
- options.provider={}
78600
- options.provider[provider]={}
78601
- if self.APIKey then
78602
- options.provider[provider].key=self.APIKey
78603
- end
78604
- if self.defaultVoice then
78605
- options.provider[provider].defaultVoice=self.defaultVoice
78606
- end
78607
- if self.voice then
78608
- options.provider[provider].voice=Voice or self.voice or self.defaultVoice
78609
- elseif ssml then
78610
- local preTag,genderProp,langProp,postTag='','','',''
78611
- if self.gender then
78612
- genderProp=' gender=\"'..self.gender..'\"'
78613
- end
78614
- if self.culture then
78615
- langProp=' language=\"'..self.culture..'\"'
79864
+ if speed~=0 then
79865
+ speedFactor=math.abs(speed)*(maxRateRatio-1)/10+1
78616
79866
  end
78617
- if self.culture or self.gender then
78618
- preTag='<voice'..langProp..genderProp..'>'
78619
- postTag='</voice>'
78620
- ssml=preTag..Text..postTag
79867
+ if speed<0 then
79868
+ speedFactor=1/speedFactor
78621
79869
  end
78622
79870
  end
78623
- for _,_freq in ipairs(XmitFrequencies)do
78624
- local freq=_freq*1000000
78625
- BASE:T("GRPC.tts")
78626
- BASE:T(ssml)
78627
- BASE:T(freq)
78628
- BASE:T({options})
78629
- GRPC.tts(ssml,freq,options)
79871
+ local wpm=math.ceil(100*speedFactor)
79872
+ local cps=math.floor((wpm*5)/60)
79873
+ if type(length)=="string"then
79874
+ length=string.len(length)
78630
79875
  end
79876
+ return length/cps
78631
79877
  end
78632
79878
  MSRSQUEUE={
78633
79879
  ClassName="MSRSQUEUE",
@@ -78688,7 +79934,7 @@ return nil
78688
79934
  end
78689
79935
  local transmission={}
78690
79936
  transmission.text=text
78691
- transmission.duration=duration or STTS.getSpeechTime(text)
79937
+ transmission.duration=duration or MSRS.getSpeechTime(text)
78692
79938
  transmission.msrs=msrs
78693
79939
  transmission.Tplay=tstart or timer.getAbsTime()
78694
79940
  transmission.subtitle=subtitle
@@ -78811,6 +80057,7 @@ end
78811
80057
  self:_CheckRadioQueue(dt)
78812
80058
  end
78813
80059
  end
80060
+ MSRS.LoadConfigFile()
78814
80061
  COMMANDCENTER={
78815
80062
  ClassName="COMMANDCENTER",
78816
80063
  CommandCenterName="",