@jtff/miztemplate-lib 3.1.7 → 3.1.8

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: 2023-12-10T14:37:41+01:00-c089e56060974539e58a346665f7536a2898c4cf ***')
2
2
  env.info('*** MOOSE STATIC INCLUDE START *** ')
3
3
  ENUMS={}
4
4
  env.setErrorMessageBoxEnabled(false)
@@ -416,6 +416,12 @@ Reaper="MQ-9",
416
416
  Predator="MQ-1A",
417
417
  }
418
418
  }
419
+ ENUMS.Link16Power={
420
+ none=0,
421
+ low=1,
422
+ medium=2,
423
+ high=3,
424
+ }
419
425
  ENUMS.Storage={
420
426
  weapons={
421
427
  missiles={},
@@ -1277,20 +1283,32 @@ end
1277
1283
  end
1278
1284
  end
1279
1285
  function UTILS.PrintTableToLog(table,indent)
1286
+ local text="\n"
1280
1287
  if not table then
1281
- BASE:E("No table passed!")
1282
- return
1288
+ env.warning("No table passed!")
1289
+ return nil
1283
1290
  end
1284
1291
  if not indent then indent=0 end
1285
1292
  for k,v in pairs(table)do
1293
+ if string.find(k," ")then k='"'..k..'"'end
1286
1294
  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).."}")
1295
+ env.info(string.rep(" ",indent)..tostring(k).." = {")
1296
+ text=text..string.rep(" ",indent)..tostring(k).." = {\n"
1297
+ text=text..tostring(UTILS.PrintTableToLog(v,indent+1)).."\n"
1298
+ env.info(string.rep(" ",indent).."},")
1299
+ text=text..string.rep(" ",indent).."},\n"
1290
1300
  else
1291
- BASE:I(string.rep(" ",indent)..tostring(k).." = "..tostring(v))
1301
+ local value
1302
+ if tostring(v)=="true"or tostring(v)=="false"or tonumber(v)~=nil then
1303
+ value=v
1304
+ else
1305
+ value='"'..tostring(v)..'"'
1306
+ end
1307
+ env.info(string.rep(" ",indent)..tostring(k).." = "..tostring(value)..",\n")
1308
+ text=text..string.rep(" ",indent)..tostring(k).." = "..tostring(value)..",\n"
1292
1309
  end
1293
1310
  end
1311
+ return text
1294
1312
  end
1295
1313
  function UTILS.TableShow(tbl,loc,indent,tableshow_tbls)
1296
1314
  tableshow_tbls=tableshow_tbls or{}
@@ -1817,9 +1835,15 @@ end
1817
1835
  function UTILS.VecSubstract(a,b)
1818
1836
  return{x=a.x-b.x,y=a.y-b.y,z=a.z-b.z}
1819
1837
  end
1838
+ function UTILS.VecSubtract(a,b)
1839
+ return UTILS.VecSubstract(a,b)
1840
+ end
1820
1841
  function UTILS.Vec2Substract(a,b)
1821
1842
  return{x=a.x-b.x,y=a.y-b.y}
1822
1843
  end
1844
+ function UTILS.Vec2Subtract(a,b)
1845
+ return UTILS.Vec2Substract(a,b)
1846
+ end
1823
1847
  function UTILS.VecAdd(a,b)
1824
1848
  return{x=a.x+b.x,y=a.y+b.y,z=a.z+b.z}
1825
1849
  end
@@ -2479,7 +2503,7 @@ filename=path.."\\"..filename
2479
2503
  end
2480
2504
  local exists=UTILS.CheckFileExists(Path,Filename)
2481
2505
  if not exists then
2482
- BASE:E(string.format("ERROR: File %s does not exist!",filename))
2506
+ BASE:I(string.format("ERROR: File %s does not exist!",filename))
2483
2507
  return false
2484
2508
  end
2485
2509
  local file=assert(io.open(filename,"rb"))
@@ -2991,6 +3015,300 @@ point_four:LineToAll(point_three,coalition,color,alpha,lineType)
2991
3015
  circle_center_fix_four:CircleToAll(UTILS.NMToMeters(turn_radius),coalition,color,alpha,nil,0,lineType)
2992
3016
  circle_center_two_three:CircleToAll(UTILS.NMToMeters(turn_radius),coalition,color,alpha,nil,0,lineType)
2993
3017
  end
3018
+ function UTILS.TimeNow()
3019
+ return UTILS.SecondsToClock(timer.getAbsTime(),false,false)
3020
+ end
3021
+ function UTILS.TimeDifferenceInSeconds(start_time,end_time)
3022
+ return UTILS.ClockToSeconds(end_time)-UTILS.ClockToSeconds(start_time)
3023
+ end
3024
+ function UTILS.TimeLaterThan(time_string)
3025
+ if timer.getAbsTime()>UTILS.ClockToSeconds(time_string)then
3026
+ return true
3027
+ end
3028
+ return false
3029
+ end
3030
+ function UTILS.TimeBefore(time_string)
3031
+ if timer.getAbsTime()<UTILS.ClockToSeconds(time_string)then
3032
+ return true
3033
+ end
3034
+ return false
3035
+ end
3036
+ function UTILS.CombineTimeStrings(time_string_01,time_string_02)
3037
+ local hours1,minutes1,seconds1=time_string_01:match("(%d+):(%d+):(%d+)")
3038
+ local hours2,minutes2,seconds2=time_string_02:match("(%d+):(%d+):(%d+)")
3039
+ local total_seconds=tonumber(seconds1)+tonumber(seconds2)+tonumber(minutes1)*60+tonumber(minutes2)*60+tonumber(hours1)*3600+tonumber(hours2)*3600
3040
+ total_seconds=total_seconds%(24*3600)
3041
+ if total_seconds<0 then
3042
+ total_seconds=total_seconds+24*3600
3043
+ end
3044
+ local hours=math.floor(total_seconds/3600)
3045
+ total_seconds=total_seconds-hours*3600
3046
+ local minutes=math.floor(total_seconds/60)
3047
+ local seconds=total_seconds%60
3048
+ return string.format("%02d:%02d:%02d",hours,minutes,seconds)
3049
+ end
3050
+ function UTILS.SubtractTimeStrings(time_string_01,time_string_02)
3051
+ local hours1,minutes1,seconds1=time_string_01:match("(%d+):(%d+):(%d+)")
3052
+ local hours2,minutes2,seconds2=time_string_02:match("(%d+):(%d+):(%d+)")
3053
+ local total_seconds=tonumber(seconds1)-tonumber(seconds2)+tonumber(minutes1)*60-tonumber(minutes2)*60+tonumber(hours1)*3600-tonumber(hours2)*3600
3054
+ total_seconds=total_seconds%(24*3600)
3055
+ if total_seconds<0 then
3056
+ total_seconds=total_seconds+24*3600
3057
+ end
3058
+ local hours=math.floor(total_seconds/3600)
3059
+ total_seconds=total_seconds-hours*3600
3060
+ local minutes=math.floor(total_seconds/60)
3061
+ local seconds=total_seconds%60
3062
+ return string.format("%02d:%02d:%02d",hours,minutes,seconds)
3063
+ end
3064
+ function UTILS.TimeBetween(start_time,end_time)
3065
+ return UTILS.TimeLaterThan(start_time)and UTILS.TimeBefore(end_time)
3066
+ end
3067
+ function UTILS.PercentageChance(chance)
3068
+ chance=chance or math.random(0,100)
3069
+ chance=UTILS.Clamp(chance,0,100)
3070
+ local percentage=math.random(0,100)
3071
+ if percentage<chance then
3072
+ return true
3073
+ end
3074
+ return false
3075
+ end
3076
+ function UTILS.Clamp(value,min,max)
3077
+ if value<min then value=min end
3078
+ if value>max then value=max end
3079
+ return value
3080
+ end
3081
+ function UTILS.ClampAngle(value)
3082
+ if value>360 then return value-360 end
3083
+ if value<0 then return value+360 end
3084
+ return value
3085
+ end
3086
+ function UTILS.RemapValue(value,old_min,old_max,new_min,new_max)
3087
+ new_min=new_min or 0
3088
+ new_max=new_max or 100
3089
+ local old_range=old_max-old_min
3090
+ local new_range=new_max-new_min
3091
+ local percentage=(value-old_min)/old_range
3092
+ return(new_range*percentage)+new_min
3093
+ end
3094
+ function UTILS.RandomPointInTriangle(pt1,pt2,pt3)
3095
+ local pt={math.random(),math.random()}
3096
+ table.sort(pt)
3097
+ local s=pt[1]
3098
+ local t=pt[2]-pt[1]
3099
+ local u=1-pt[2]
3100
+ return{x=s*pt1.x+t*pt2.x+u*pt3.x,
3101
+ y=s*pt1.y+t*pt2.y+u*pt3.y}
3102
+ end
3103
+ function UTILS.AngleBetween(angle,min,max)
3104
+ angle=(360+(angle%360))%360
3105
+ min=(360+min%360)%360
3106
+ max=(360+max%360)%360
3107
+ if min<max then return min<=angle and angle<=max end
3108
+ return min<=angle or angle<=max
3109
+ end
3110
+ function UTILS.WriteJSON(data,file_path)
3111
+ package.path=package.path..";.\\Scripts\\?.lua"
3112
+ local JSON=require("json")
3113
+ local pretty_json_text=JSON:encode_pretty(data)
3114
+ local write_file=io.open(file_path,"w")
3115
+ write_file:write(pretty_json_text)
3116
+ write_file:close()
3117
+ end
3118
+ function UTILS.ReadJSON(file_path)
3119
+ package.path=package.path..";.\\Scripts\\?.lua"
3120
+ local JSON=require("json")
3121
+ local read_file=io.open(file_path,"r")
3122
+ local contents=read_file:read("*a")
3123
+ io.close(read_file)
3124
+ return JSON:decode(contents)
3125
+ end
3126
+ function UTILS.GetZoneProperties(zone_name)
3127
+ local return_table={}
3128
+ for _,zone in pairs(env.mission.triggers.zones)do
3129
+ if zone["name"]==zone_name then
3130
+ if table.length(zone["properties"])>0 then
3131
+ for _,property in pairs(zone["properties"])do
3132
+ return_table[property["key"]]=property["value"]
3133
+ end
3134
+ return return_table
3135
+ else
3136
+ BASE:I(string.format("%s doesn't have any properties",zone_name))
3137
+ return{}
3138
+ end
3139
+ end
3140
+ end
3141
+ end
3142
+ function UTILS.RotatePointAroundPivot(point,pivot,angle)
3143
+ local radians=math.rad(angle)
3144
+ local x=point.x-pivot.x
3145
+ local y=point.y-pivot.y
3146
+ local rotated_x=x*math.cos(radians)-y*math.sin(radians)
3147
+ local rotatex_y=x*math.sin(radians)+y*math.cos(radians)
3148
+ local original_x=rotated_x+pivot.x
3149
+ local original_y=rotatex_y+pivot.y
3150
+ return{x=original_x,y=original_y}
3151
+ end
3152
+ function UTILS.UniqueName(base)
3153
+ base=base or""
3154
+ local ran=tostring(math.random(0,1000000))
3155
+ if base==""then
3156
+ return ran
3157
+ end
3158
+ return base.."_"..ran
3159
+ end
3160
+ function string.startswith(str,value)
3161
+ return string.sub(str,1,string.len(value))==value
3162
+ end
3163
+ function string.endswith(str,value)
3164
+ return value==""or str:sub(-#value)==value
3165
+ end
3166
+ function string.split(input,separator)
3167
+ local parts={}
3168
+ for part in input:gmatch("[^"..separator.."]+")do
3169
+ table.insert(parts,part)
3170
+ end
3171
+ return parts
3172
+ end
3173
+ function string.contains(str,value)
3174
+ return string.match(str,value)
3175
+ end
3176
+ function table.contains(tbl,element)
3177
+ if element==nil or tbl==nil then return false end
3178
+ local index=1
3179
+ while tbl[index]do
3180
+ if tbl[index]==element then
3181
+ return true
3182
+ end
3183
+ index=index+1
3184
+ end
3185
+ return false
3186
+ end
3187
+ function table.contains_key(tbl,key)
3188
+ if tbl[key]~=nil then return true else return false end
3189
+ end
3190
+ function table.insert_unique(tbl,element)
3191
+ if element==nil or tbl==nil then return end
3192
+ if not table.contains(tbl,element)then
3193
+ table.insert(tbl,element)
3194
+ end
3195
+ end
3196
+ function table.remove_by_value(tbl,element)
3197
+ local indices_to_remove={}
3198
+ local index=1
3199
+ for _,value in pairs(tbl)do
3200
+ if value==element then
3201
+ table.insert(indices_to_remove,index)
3202
+ end
3203
+ index=index+1
3204
+ end
3205
+ for _,idx in pairs(indices_to_remove)do
3206
+ table.remove(tbl,idx)
3207
+ end
3208
+ end
3209
+ function table.remove_key(table,key)
3210
+ local element=table[key]
3211
+ table[key]=nil
3212
+ return element
3213
+ end
3214
+ function table.index_of(table,element)
3215
+ for i,v in ipairs(table)do
3216
+ if v==element then
3217
+ return i
3218
+ end
3219
+ end
3220
+ return nil
3221
+ end
3222
+ function table.length(T)
3223
+ local count=0
3224
+ for _ in pairs(T)do count=count+1 end
3225
+ return count
3226
+ end
3227
+ function table.slice(tbl,first,last)
3228
+ local sliced={}
3229
+ local start=first or 1
3230
+ local stop=last or table.length(tbl)
3231
+ local count=1
3232
+ for key,value in pairs(tbl)do
3233
+ if count>=start and count<=stop then
3234
+ sliced[key]=value
3235
+ end
3236
+ count=count+1
3237
+ end
3238
+ return sliced
3239
+ end
3240
+ function table.count_value(tbl,value)
3241
+ local count=0
3242
+ for _,item in pairs(tbl)do
3243
+ if item==value then count=count+1 end
3244
+ end
3245
+ return count
3246
+ end
3247
+ function table.combine(t1,t2)
3248
+ if t1==nil and t2==nil then
3249
+ BASE:E("Both tables were empty!")
3250
+ end
3251
+ if t1==nil then return t2 end
3252
+ if t2==nil then return t1 end
3253
+ for i=1,#t2 do
3254
+ t1[#t1+1]=t2[i]
3255
+ end
3256
+ return t1
3257
+ end
3258
+ function table.merge(t1,t2)
3259
+ for k,v in pairs(t2)do
3260
+ if(type(v)=="table")and(type(t1[k]or false)=="table")then
3261
+ table.merge(t1[k],t2[k])
3262
+ else
3263
+ t1[k]=v
3264
+ end
3265
+ end
3266
+ return t1
3267
+ end
3268
+ function table.add(tbl,item)
3269
+ tbl[#tbl+1]=item
3270
+ end
3271
+ function table.shuffle(tbl)
3272
+ local new_table={}
3273
+ for _,value in ipairs(tbl)do
3274
+ local pos=math.random(1,#new_table+1)
3275
+ table.insert(new_table,pos,value)
3276
+ end
3277
+ return new_table
3278
+ end
3279
+ function table.find_key_value_pair(tbl,key,value)
3280
+ for k,v in pairs(tbl)do
3281
+ if type(v)=="table"then
3282
+ local result=table.find_key_value_pair(v,key,value)
3283
+ if result~=nil then
3284
+ return result
3285
+ end
3286
+ elseif k==key and v==value then
3287
+ return tbl
3288
+ end
3289
+ end
3290
+ return nil
3291
+ end
3292
+ function UTILS.DecimalToOctal(Number)
3293
+ if Number<8 then return Number end
3294
+ local number=tonumber(Number)
3295
+ local octal=""
3296
+ local n=1
3297
+ while number>7 do
3298
+ local number1=number%8
3299
+ octal=string.format("%d",number1)..octal
3300
+ local number2=math.abs(number/8)
3301
+ if number2<8 then
3302
+ octal=string.format("%d",number2)..octal
3303
+ end
3304
+ number=number2
3305
+ n=n+1
3306
+ end
3307
+ return tonumber(octal)
3308
+ end
3309
+ function UTILS.OctalToDecimal(Number)
3310
+ return tonumber(Number,8)
3311
+ end
2994
3312
  PROFILER={
2995
3313
  ClassName="PROFILER",
2996
3314
  Counters={},
@@ -6618,7 +6936,7 @@ if Event.weapon then
6618
6936
  Event.Weapon=Event.weapon
6619
6937
  Event.WeaponName=Event.Weapon:getTypeName()
6620
6938
  Event.WeaponUNIT=CLIENT:Find(Event.Weapon,'',true)
6621
- Event.WeaponPlayerName=Event.WeaponUNIT and Event.Weapon:getPlayerName()
6939
+ Event.WeaponPlayerName=Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName()
6622
6940
  Event.WeaponCoalition=Event.WeaponUNIT and Event.Weapon:getCoalition()
6623
6941
  Event.WeaponCategory=Event.WeaponUNIT and Event.Weapon:getDesc().category
6624
6942
  Event.WeaponTypeName=Event.WeaponUNIT and Event.Weapon:getTypeName()
@@ -8166,7 +8484,13 @@ if Delay and Delay>0 then
8166
8484
  self:ScheduleOnce(Delay,ZONE_BASE.UndrawZone,self)
8167
8485
  else
8168
8486
  if self.DrawID then
8487
+ if type(self.DrawID)~="table"then
8169
8488
  UTILS.RemoveMark(self.DrawID)
8489
+ else
8490
+ for _,mark_id in pairs(self.DrawID)do
8491
+ UTILS.RemoveMark(mark_id)
8492
+ end
8493
+ end
8170
8494
  end
8171
8495
  end
8172
8496
  return self
@@ -8881,8 +9205,68 @@ local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2())
8881
9205
  self:T3({PointVec2})
8882
9206
  return PointVec2
8883
9207
  end
9208
+ _ZONE_TRIANGLE={
9209
+ ClassName="ZONE_TRIANGLE",
9210
+ Points={},
9211
+ Coords={},
9212
+ CenterVec2={x=0,y=0},
9213
+ SurfaceArea=0,
9214
+ DrawIDs={}
9215
+ }
9216
+ function _ZONE_TRIANGLE:New(p1,p2,p3)
9217
+ local self=BASE:Inherit(self,ZONE_BASE:New())
9218
+ self.Points={p1,p2,p3}
9219
+ local center_x=(p1.x+p2.x+p3.x)/3
9220
+ local center_y=(p1.y+p2.y+p3.y)/3
9221
+ self.CenterVec2={x=center_x,y=center_y}
9222
+ for _,pt in pairs({p1,p2,p3})do
9223
+ table.add(self.Coords,COORDINATE:NewFromVec2(pt))
9224
+ end
9225
+ self.SurfaceArea=math.abs((p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y))*0.5
9226
+ return self
9227
+ end
9228
+ function _ZONE_TRIANGLE:ContainsPoint(pt,points)
9229
+ points=points or self.Points
9230
+ local function sign(p1,p2,p3)
9231
+ return(p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y)
9232
+ end
9233
+ local d1=sign(pt,self.Points[1],self.Points[2])
9234
+ local d2=sign(pt,self.Points[2],self.Points[3])
9235
+ local d3=sign(pt,self.Points[3],self.Points[1])
9236
+ local has_neg=(d1<0)or(d2<0)or(d3<0)
9237
+ local has_pos=(d1>0)or(d2>0)or(d3>0)
9238
+ return not(has_neg and has_pos)
9239
+ end
9240
+ function _ZONE_TRIANGLE:GetRandomVec2(points)
9241
+ points=points or self.Points
9242
+ local pt={math.random(),math.random()}
9243
+ table.sort(pt)
9244
+ local s=pt[1]
9245
+ local t=pt[2]-pt[1]
9246
+ local u=1-pt[2]
9247
+ return{x=s*points[1].x+t*points[2].x+u*points[3].x,
9248
+ y=s*points[1].y+t*points[2].y+u*points[3].y}
9249
+ end
9250
+ function _ZONE_TRIANGLE:Draw(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
9251
+ Coalition=Coalition or-1
9252
+ Color=Color or{1,0,0}
9253
+ Alpha=Alpha or 1
9254
+ FillColor=FillColor or Color
9255
+ if not FillColor then UTILS.DeepCopy(Color)end
9256
+ FillAlpha=FillAlpha or Alpha
9257
+ if not FillAlpha then FillAlpha=1 end
9258
+ for i=1,#self.Coords do
9259
+ local c1=self.Coords[i]
9260
+ local c2=self.Coords[i%#self.Coords+1]
9261
+ table.add(self.DrawIDs,c1:LineToAll(c2,Coalition,Color,Alpha,LineType,ReadOnly))
9262
+ end
9263
+ return self.DrawIDs
9264
+ end
8884
9265
  ZONE_POLYGON_BASE={
8885
9266
  ClassName="ZONE_POLYGON_BASE",
9267
+ _Triangles={},
9268
+ SurfaceArea=0,
9269
+ DrawID={}
8886
9270
  }
8887
9271
  function ZONE_POLYGON_BASE:New(ZoneName,PointsArray)
8888
9272
  local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName))
@@ -8894,9 +9278,84 @@ self._.Polygon[i]={}
8894
9278
  self._.Polygon[i].x=PointsArray[i].x
8895
9279
  self._.Polygon[i].y=PointsArray[i].y
8896
9280
  end
9281
+ self._Triangles=self:_Triangulate()
9282
+ self.SurfaceArea=self:_CalculateSurfaceArea()
8897
9283
  end
8898
9284
  return self
8899
9285
  end
9286
+ function ZONE_POLYGON_BASE:_Triangulate()
9287
+ local points=self._.Polygon
9288
+ local triangles={}
9289
+ local function get_orientation(shape_points)
9290
+ local sum=0
9291
+ for i=1,#shape_points do
9292
+ local j=i%#shape_points+1
9293
+ sum=sum+(shape_points[j].x-shape_points[i].x)*(shape_points[j].y+shape_points[i].y)
9294
+ end
9295
+ return sum>=0 and"clockwise"or"counter-clockwise"
9296
+ end
9297
+ local function ensure_clockwise(shape_points)
9298
+ local orientation=get_orientation(shape_points)
9299
+ if orientation=="counter-clockwise"then
9300
+ local reversed={}
9301
+ for i=#shape_points,1,-1 do
9302
+ table.insert(reversed,shape_points[i])
9303
+ end
9304
+ return reversed
9305
+ end
9306
+ return shape_points
9307
+ end
9308
+ local function is_clockwise(p1,p2,p3)
9309
+ local cross_product=(p2.x-p1.x)*(p3.y-p1.y)-(p2.y-p1.y)*(p3.x-p1.x)
9310
+ return cross_product<0
9311
+ end
9312
+ local function divide_recursively(shape_points)
9313
+ if#shape_points==3 then
9314
+ table.insert(triangles,_ZONE_TRIANGLE:New(shape_points[1],shape_points[2],shape_points[3]))
9315
+ elseif#shape_points>3 then
9316
+ for i,p1 in ipairs(shape_points)do
9317
+ local p2=shape_points[(i%#shape_points)+1]
9318
+ local p3=shape_points[(i+1)%#shape_points+1]
9319
+ local triangle=_ZONE_TRIANGLE:New(p1,p2,p3)
9320
+ local is_ear=true
9321
+ if not is_clockwise(p1,p2,p3)then
9322
+ is_ear=false
9323
+ else
9324
+ for _,point in ipairs(shape_points)do
9325
+ if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then
9326
+ is_ear=false
9327
+ break
9328
+ end
9329
+ end
9330
+ end
9331
+ if is_ear then
9332
+ local is_valid_triangle=true
9333
+ for _,point in ipairs(points)do
9334
+ if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then
9335
+ is_valid_triangle=false
9336
+ break
9337
+ end
9338
+ end
9339
+ if is_valid_triangle then
9340
+ table.insert(triangles,triangle)
9341
+ local remaining_points={}
9342
+ for j,point in ipairs(shape_points)do
9343
+ if point~=p2 then
9344
+ table.insert(remaining_points,point)
9345
+ end
9346
+ end
9347
+ divide_recursively(remaining_points)
9348
+ break
9349
+ end
9350
+ else
9351
+ end
9352
+ end
9353
+ end
9354
+ end
9355
+ points=ensure_clockwise(points)
9356
+ divide_recursively(points)
9357
+ return triangles
9358
+ end
8900
9359
  function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array)
8901
9360
  self._.Polygon={}
8902
9361
  for i=1,#Vec2Array do
@@ -8904,6 +9363,8 @@ self._.Polygon[i]={}
8904
9363
  self._.Polygon[i].x=Vec2Array[i].x
8905
9364
  self._.Polygon[i].y=Vec2Array[i].y
8906
9365
  end
9366
+ self._Triangles=self:_Triangulate()
9367
+ self.SurfaceArea=self:_CalculateSurfaceArea()
8907
9368
  return self
8908
9369
  end
8909
9370
  function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array)
@@ -8913,8 +9374,17 @@ self._.Polygon[i]={}
8913
9374
  self._.Polygon[i].x=Vec3Array[i].x
8914
9375
  self._.Polygon[i].y=Vec3Array[i].z
8915
9376
  end
9377
+ self._Triangles=self:_Triangulate()
9378
+ self.SurfaceArea=self:_CalculateSurfaceArea()
8916
9379
  return self
8917
9380
  end
9381
+ function ZONE_POLYGON_BASE:_CalculateSurfaceArea()
9382
+ local area=0
9383
+ for _,triangle in pairs(self._Triangles)do
9384
+ area=area+triangle.SurfaceArea
9385
+ end
9386
+ return area
9387
+ end
8918
9388
  function ZONE_POLYGON_BASE:GetVec2()
8919
9389
  self:F(self.ZoneName)
8920
9390
  local Bounds=self:GetBoundingSquare()
@@ -8997,32 +9467,42 @@ i=i+1
8997
9467
  end
8998
9468
  return self
8999
9469
  end
9000
- function ZONE_POLYGON_BASE:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
9470
+ function ZONE_POLYGON_BASE:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,IncludeTriangles)
9001
9471
  if self._.Polygon and#self._.Polygon>=3 then
9002
- local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1])
9003
9472
  Coalition=Coalition or self:GetDrawCoalition()
9004
9473
  self:SetDrawCoalition(Coalition)
9005
9474
  Color=Color or self:GetColorRGB()
9006
9475
  Alpha=Alpha or 1
9007
9476
  self:SetColor(Color,Alpha)
9008
9477
  FillColor=FillColor or self:GetFillColorRGB()
9009
- if not FillColor then UTILS.DeepCopy(Color)end
9478
+ if not FillColor then
9479
+ UTILS.DeepCopy(Color)
9480
+ end
9010
9481
  FillAlpha=FillAlpha or self:GetFillColorAlpha()
9011
- if not FillAlpha then FillAlpha=0.15 end
9482
+ if not FillAlpha then
9483
+ FillAlpha=0.15
9484
+ end
9012
9485
  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)
9486
+ IncludeTriangles=IncludeTriangles or false
9487
+ if IncludeTriangles then
9488
+ for _,triangle in pairs(self._Triangles)do
9489
+ local draw_ids=triangle:Draw()
9490
+ table.combine(self.DrawID,draw_ids)
9491
+ end
9018
9492
  else
9019
- local Coordinates=self:GetVerticiesCoordinates()
9020
- table.remove(Coordinates,1)
9021
- self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
9493
+ local coords=self:GetVerticiesCoordinates()
9494
+ for i=1,#coords do
9495
+ local c1=coords[i]
9496
+ local c2=coords[i%#coords+1]
9497
+ table.add(self.DrawID,c1:LineToAll(c2,Coalition,Color,Alpha,LineType,ReadOnly))
9498
+ end
9022
9499
  end
9023
9500
  end
9024
9501
  return self
9025
9502
  end
9503
+ function ZONE_POLYGON_BASE:GetSurfaceArea()
9504
+ return self.SurfaceArea
9505
+ end
9026
9506
  function ZONE_POLYGON_BASE:GetRadius()
9027
9507
  local center=self:GetVec2()
9028
9508
  local radius=0
@@ -9131,17 +9611,18 @@ local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
9131
9611
  return InZone
9132
9612
  end
9133
9613
  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
9614
+ local weights={}
9615
+ for _,triangle in pairs(self._Triangles)do
9616
+ weights[triangle]=triangle.SurfaceArea/self.SurfaceArea
9617
+ end
9618
+ local random_weight=math.random()
9619
+ local accumulated_weight=0
9620
+ for triangle,weight in pairs(weights)do
9621
+ accumulated_weight=accumulated_weight+weight
9622
+ if accumulated_weight>=random_weight then
9623
+ return triangle:GetRandomVec2()
9140
9624
  end
9141
- n=n+1
9142
9625
  end
9143
- self:E("Could not find a random point in the polygon zone!")
9144
- return nil
9145
9626
  end
9146
9627
  function ZONE_POLYGON_BASE:GetRandomPointVec2()
9147
9628
  self:F2()
@@ -9220,6 +9701,7 @@ i=i+1
9220
9701
  end
9221
9702
  return self
9222
9703
  end
9704
+ do
9223
9705
  ZONE_POLYGON={
9224
9706
  ClassName="ZONE_POLYGON",
9225
9707
  }
@@ -9244,6 +9726,36 @@ self:F({GroupName,ZoneGroup,self._.Polygon})
9244
9726
  _EVENTDISPATCHER:CreateEventNewZone(self)
9245
9727
  return self
9246
9728
  end
9729
+ function ZONE_POLYGON:NewFromDrawing(DrawingName)
9730
+ local points={}
9731
+ for _,layer in pairs(env.mission.drawings.layers)do
9732
+ for _,object in pairs(layer["objects"])do
9733
+ if object["name"]==DrawingName then
9734
+ if(object["primitiveType"]=="Line"and object["closed"]==true)or(object["polygonMode"]=="free")then
9735
+ for _,point in UTILS.spairs(object["points"])do
9736
+ local p={x=object["mapX"]+point["x"],
9737
+ y=object["mapY"]+point["y"]}
9738
+ table.add(points,p)
9739
+ end
9740
+ elseif object["polygonMode"]=="rect"then
9741
+ local angle=object["angle"]
9742
+ local half_width=object["width"]/2
9743
+ local half_height=object["height"]/2
9744
+ local center={x=object["mapX"],y=object["mapY"]}
9745
+ local p1=UTILS.RotatePointAroundPivot({x=center.x-half_height,y=center.y+half_width},center,angle)
9746
+ local p2=UTILS.RotatePointAroundPivot({x=center.x+half_height,y=center.y+half_width},center,angle)
9747
+ local p3=UTILS.RotatePointAroundPivot({x=center.x+half_height,y=center.y-half_width},center,angle)
9748
+ local p4=UTILS.RotatePointAroundPivot({x=center.x-half_height,y=center.y-half_width},center,angle)
9749
+ points={p1,p2,p3,p4}
9750
+ else
9751
+ end
9752
+ end
9753
+ end
9754
+ end
9755
+ local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(DrawingName,points))
9756
+ _EVENTDISPATCHER:CreateEventNewZone(self)
9757
+ return self
9758
+ end
9247
9759
  function ZONE_POLYGON:FindByName(ZoneName)
9248
9760
  local ZoneFound=_DATABASE:FindZone(ZoneName)
9249
9761
  return ZoneFound
@@ -9430,6 +9942,7 @@ end
9430
9942
  function ZONE_POLYGON:IsNoneInZone()
9431
9943
  return self:CountScannedCoalitions()==0
9432
9944
  end
9945
+ end
9433
9946
  do
9434
9947
  ZONE_ELASTIC={
9435
9948
  ClassName="ZONE_ELASTIC",
@@ -9523,6 +10036,124 @@ table.remove(h,#h)
9523
10036
  return h
9524
10037
  end
9525
10038
  end
10039
+ ZONE_OVAL={
10040
+ ClassName="OVAL",
10041
+ ZoneName="",
10042
+ MajorAxis=nil,
10043
+ MinorAxis=nil,
10044
+ Angle=0,
10045
+ DrawPoly=nil
10046
+ }
10047
+ function ZONE_OVAL:New(name,vec2,major_axis,minor_axis,angle)
10048
+ self=BASE:Inherit(self,ZONE_BASE:New())
10049
+ self.ZoneName=name
10050
+ self.CenterVec2=vec2
10051
+ self.MajorAxis=major_axis
10052
+ self.MinorAxis=minor_axis
10053
+ self.Angle=angle or 0
10054
+ _DATABASE:AddZone(name,self)
10055
+ return self
10056
+ end
10057
+ function ZONE_OVAL:NewFromDrawing(DrawingName)
10058
+ self=BASE:Inherit(self,ZONE_BASE:New(DrawingName))
10059
+ for _,layer in pairs(env.mission.drawings.layers)do
10060
+ for _,object in pairs(layer["objects"])do
10061
+ if string.find(object["name"],DrawingName,1,true)then
10062
+ if object["polygonMode"]=="oval"then
10063
+ self.CenterVec2={x=object["mapX"],y=object["mapY"]}
10064
+ self.MajorAxis=object["r1"]
10065
+ self.MinorAxis=object["r2"]
10066
+ self.Angle=object["angle"]
10067
+ end
10068
+ end
10069
+ end
10070
+ end
10071
+ _DATABASE:AddZone(DrawingName,self)
10072
+ return self
10073
+ end
10074
+ function ZONE_OVAL:GetMajorAxis()
10075
+ return self.MajorAxis
10076
+ end
10077
+ function ZONE_OVAL:GetMinorAxis()
10078
+ return self.MinorAxis
10079
+ end
10080
+ function ZONE_OVAL:GetAngle()
10081
+ return self.Angle
10082
+ end
10083
+ function ZONE_OVAL:GetVec2()
10084
+ return self.CenterVec2
10085
+ end
10086
+ function ZONE_OVAL:IsVec2InZone(vec2)
10087
+ local cos,sin=math.cos,math.sin
10088
+ local dx=vec2.x-self.CenterVec2.x
10089
+ local dy=vec2.y-self.CenterVec2.y
10090
+ local rx=dx*cos(self.Angle)+dy*sin(self.Angle)
10091
+ local ry=-dx*sin(self.Angle)+dy*cos(self.Angle)
10092
+ return rx*rx/(self.MajorAxis*self.MajorAxis)+ry*ry/(self.MinorAxis*self.MinorAxis)<=1
10093
+ end
10094
+ function ZONE_OVAL:GetBoundingSquare()
10095
+ local min_x=self.CenterVec2.x-self.MajorAxis
10096
+ local min_y=self.CenterVec2.y-self.MinorAxis
10097
+ local max_x=self.CenterVec2.x+self.MajorAxis
10098
+ local max_y=self.CenterVec2.y+self.MinorAxis
10099
+ return{
10100
+ {x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y}
10101
+ }
10102
+ end
10103
+ function ZONE_OVAL:PointsOnEdge(num_points)
10104
+ num_points=num_points or 40
10105
+ local points={}
10106
+ local dtheta=2*math.pi/num_points
10107
+ for i=0,num_points-1 do
10108
+ local theta=i*dtheta
10109
+ local x=self.CenterVec2.x+self.MajorAxis*math.cos(theta)*math.cos(self.Angle)-self.MinorAxis*math.sin(theta)*math.sin(self.Angle)
10110
+ local y=self.CenterVec2.y+self.MajorAxis*math.cos(theta)*math.sin(self.Angle)+self.MinorAxis*math.sin(theta)*math.cos(self.Angle)
10111
+ table.insert(points,{x=x,y=y})
10112
+ end
10113
+ return points
10114
+ end
10115
+ function ZONE_OVAL:GetRandomVec2()
10116
+ local theta=math.rad(self.Angle)
10117
+ local random_point=math.sqrt(math.random())
10118
+ local phi=math.random()*2*math.pi
10119
+ local x_c=random_point*math.cos(phi)
10120
+ local y_c=random_point*math.sin(phi)
10121
+ local x_e=x_c*self.MajorAxis
10122
+ local y_e=y_c*self.MinorAxis
10123
+ local rx=(x_e*math.cos(theta)-y_e*math.sin(theta))+self.CenterVec2.x
10124
+ local ry=(x_e*math.sin(theta)+y_e*math.cos(theta))+self.CenterVec2.y
10125
+ return{x=rx,y=ry}
10126
+ end
10127
+ function ZONE_OVAL:GetRandomPointVec2()
10128
+ return POINT_VEC2:NewFromVec2(self:GetRandomVec2())
10129
+ end
10130
+ function ZONE_OVAL:GetRandomPointVec3()
10131
+ return POINT_VEC2:NewFromVec3(self:GetRandomVec2())
10132
+ end
10133
+ function ZONE_OVAL:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType)
10134
+ Coalition=Coalition or self:GetDrawCoalition()
10135
+ self:SetDrawCoalition(Coalition)
10136
+ Color=Color or self:GetColorRGB()
10137
+ Alpha=Alpha or 1
10138
+ self:SetColor(Color,Alpha)
10139
+ FillColor=FillColor or self:GetFillColorRGB()
10140
+ if not FillColor then
10141
+ UTILS.DeepCopy(Color)
10142
+ end
10143
+ FillAlpha=FillAlpha or self:GetFillColorAlpha()
10144
+ if not FillAlpha then
10145
+ FillAlpha=0.15
10146
+ end
10147
+ LineType=LineType or 1
10148
+ self:SetFillColor(FillColor,FillAlpha)
10149
+ self.DrawPoly=ZONE_POLYGON:NewFromPointsArray(self.ZoneName,self:PointsOnEdge(80))
10150
+ self.DrawPoly:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType)
10151
+ end
10152
+ function ZONE_OVAL:UndrawZone()
10153
+ if self.DrawPoly then
10154
+ self.DrawPoly:UndrawZone()
10155
+ end
10156
+ end
9526
10157
  do
9527
10158
  ZONE_AIRBASE={
9528
10159
  ClassName="ZONE_AIRBASE",
@@ -10834,7 +11465,10 @@ self:T3({LastObject})
10834
11465
  return LastObject
10835
11466
  end
10836
11467
  function SET_BASE:GetRandom()
10837
- local tablemax=table.maxn(self.Index)
11468
+ local tablemax=0
11469
+ for _,_ind in pairs(self.Index)do
11470
+ tablemax=tablemax+1
11471
+ end
10838
11472
  local RandomItem=self.Set[self.Index[math.random(1,tablemax)]]
10839
11473
  self:T3({RandomItem})
10840
11474
  return RandomItem
@@ -11983,46 +12617,40 @@ self:F({MaxThreatLevelA2G=MaxThreatLevelA2G,MaxThreatText=MaxThreatText})
11983
12617
  return MaxThreatLevelA2G,MaxThreatText
11984
12618
  end
11985
12619
  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()
12620
+ local function GetSetVec3(units)
12621
+ local x=0
12622
+ local y=0
12623
+ local z=0
12624
+ local n=0
12625
+ for _,unit in pairs(units)do
12626
+ local vec3=nil
12627
+ if unit and unit:IsAlive()then
12628
+ vec3=unit:GetVec3()
11990
12629
  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
12630
+ if vec3 then
12631
+ x=x+vec3.x
12632
+ y=y+vec3.y
12633
+ z=z+vec3.z
12634
+ n=n+1
12017
12635
  end
12018
12636
  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})
12637
+ if n>0 then
12638
+ local Vec3={x=x/n,y=y/n,z=z/n}
12639
+ return Vec3
12640
+ end
12641
+ return nil
12642
+ end
12643
+ local Coordinate=nil
12644
+ local Vec3=GetSetVec3(self.Set)
12645
+ if Vec3 then
12646
+ Coordinate=COORDINATE:NewFromVec3(Vec3)
12647
+ end
12648
+ if Coordinate then
12649
+ local heading=self:GetHeading()or 0
12650
+ local velocity=self:GetVelocity()or 0
12651
+ Coordinate:SetHeading(heading)
12652
+ Coordinate:SetVelocity(velocity)
12653
+ self:I(UTILS.PrintTableToLog(Coordinate))
12026
12654
  end
12027
12655
  return Coordinate
12028
12656
  end
@@ -16213,6 +16841,9 @@ end
16213
16841
  end
16214
16842
  return BRAANATO
16215
16843
  end
16844
+ function COORDINATE.GetBullseyeCoordinate(Coalition)
16845
+ return COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition))
16846
+ end
16216
16847
  function COORDINATE:ToStringBULLS(Coalition,Settings,MagVar)
16217
16848
  local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition))
16218
16849
  local DirectionVec3=BullsCoordinate:GetDirectionVec3(self)
@@ -16792,7 +17423,7 @@ end
16792
17423
  if CoalitionSide then
16793
17424
  if self.MessageDuration~=0 then
16794
17425
  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)
17426
+ trigger.action.outTextForCoalition(CoalitionSide,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
16796
17427
  end
16797
17428
  end
16798
17429
  self.CoalitionSide=CoalitionSide
@@ -17439,6 +18070,7 @@ self.SpawnInitModexPrefix=nil
17439
18070
  self.SpawnInitModexPostfix=nil
17440
18071
  self.SpawnInitAirbase=nil
17441
18072
  self.TweakedTemplate=false
18073
+ self.SpawnRandomCallsign=false
17442
18074
  self.SpawnGroups={}
17443
18075
  else
17444
18076
  error("SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'")
@@ -17728,6 +18360,18 @@ self:_RandomizeZones(SpawnGroupID)
17728
18360
  end
17729
18361
  return self
17730
18362
  end
18363
+ function SPAWN:InitRandomizeCallsign()
18364
+ self.SpawnRandomCallsign=true
18365
+ return self
18366
+ end
18367
+ function SPAWN:InitCallSign(ID,Name,Minor,Major)
18368
+ self.SpawnInitCallSign=true
18369
+ self.SpawnInitCallSignID=ID or 1
18370
+ self.SpawnInitCallSignMinor=Minor or 1
18371
+ self.SpawnInitCallSignMajor=Major or 1
18372
+ self.SpawnInitCallSignName=string.lower(Name)or"enfield"
18373
+ return self
18374
+ end
17731
18375
  function SPAWN:InitPositionCoordinate(Coordinate)
17732
18376
  self:T({self.SpawnTemplatePrefix,Coordinate:GetVec2()})
17733
18377
  self:InitPositionVec2(Coordinate:GetVec2())
@@ -18849,19 +19493,133 @@ SpawnTemplate.units[UnitID].name=string.format('%s#%03d-%02d',UnitPrefix,SpawnIn
18849
19493
  SpawnTemplate.units[UnitID].unitId=nil
18850
19494
  end
18851
19495
  end
19496
+ if self.SpawnRandomCallsign and SpawnTemplate.units[1].callsign then
19497
+ if type(SpawnTemplate.units[1].callsign)~="number"then
19498
+ local min=1
19499
+ local max=8
19500
+ local ctable=CALLSIGN.Aircraft
19501
+ if string.find(SpawnTemplate.units[1].type,"A-10",1,true)then
19502
+ max=12
19503
+ end
19504
+ if string.find(SpawnTemplate.units[1].type,"18",1,true)then
19505
+ min=9
19506
+ max=20
19507
+ ctable=CALLSIGN.F18
19508
+ end
19509
+ if string.find(SpawnTemplate.units[1].type,"16",1,true)then
19510
+ min=9
19511
+ max=20
19512
+ ctable=CALLSIGN.F16
19513
+ end
19514
+ if SpawnTemplate.units[1].type=="F-15E"then
19515
+ min=9
19516
+ max=18
19517
+ ctable=CALLSIGN.F15E
19518
+ end
19519
+ local callsignnr=math.random(min,max)
19520
+ local callsignname="Enfield"
19521
+ for name,value in pairs(ctable)do
19522
+ if value==callsignnr then
19523
+ callsignname=name
19524
+ end
19525
+ end
19526
+ for UnitID=1,#SpawnTemplate.units do
19527
+ SpawnTemplate.units[UnitID].callsign[1]=callsignnr
19528
+ SpawnTemplate.units[UnitID].callsign[2]=UnitID
19529
+ SpawnTemplate.units[UnitID].callsign[3]="1"
19530
+ SpawnTemplate.units[UnitID].callsign["name"]=tostring(callsignname)..tostring(UnitID).."1"
19531
+ end
19532
+ else
19533
+ for UnitID=1,#SpawnTemplate.units do
19534
+ SpawnTemplate.units[UnitID].callsign=math.random(1,999)
19535
+ end
19536
+ end
19537
+ end
19538
+ if self.SpawnInitCallSign then
19539
+ for UnitID=1,#SpawnTemplate.units do
19540
+ local Callsign=SpawnTemplate.units[UnitID].callsign
19541
+ if Callsign and type(Callsign)~="number"then
19542
+ SpawnTemplate.units[UnitID].callsign[1]=self.SpawnInitCallSignID
19543
+ SpawnTemplate.units[UnitID].callsign[2]=self.SpawnInitCallSignMinor
19544
+ SpawnTemplate.units[UnitID].callsign[3]=self.SpawnInitCallSignMajor
19545
+ SpawnTemplate.units[UnitID].callsign["name"]=string.format("%s%d%d",self.SpawnInitCallSignName,self.SpawnInitCallSignMinor,self.SpawnInitCallSignMajor)
19546
+ end
19547
+ end
19548
+ end
18852
19549
  for UnitID=1,#SpawnTemplate.units do
18853
19550
  local Callsign=SpawnTemplate.units[UnitID].callsign
18854
19551
  if Callsign then
18855
- if type(Callsign)~="number"then
19552
+ if type(Callsign)~="number"and not self.SpawnInitCallSign then
18856
19553
  Callsign[2]=((SpawnIndex-1)%10)+1
18857
19554
  local CallsignName=SpawnTemplate.units[UnitID].callsign["name"]
18858
19555
  CallsignName=string.match(CallsignName,"^(%a+)")
18859
19556
  local CallsignLen=CallsignName:len()
19557
+ SpawnTemplate.units[UnitID].callsign[2]=UnitID
18860
19558
  SpawnTemplate.units[UnitID].callsign["name"]=CallsignName:sub(1,CallsignLen)..SpawnTemplate.units[UnitID].callsign[2]..SpawnTemplate.units[UnitID].callsign[3]
18861
- else
19559
+ elseif type(Callsign)=="number"then
18862
19560
  SpawnTemplate.units[UnitID].callsign=Callsign+SpawnIndex
18863
19561
  end
18864
19562
  end
19563
+ local AddProps=SpawnTemplate.units[UnitID].AddPropAircraft
19564
+ if AddProps then
19565
+ if SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 then
19566
+ if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16)~=nil then
19567
+ local octal=SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16
19568
+ local decimal=UTILS.OctalToDecimal(octal)+UnitID-1
19569
+ SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",UTILS.DecimalToOctal(decimal))
19570
+ else
19571
+ local STN=math.floor(UTILS.RandomGaussian(4088/2,nil,1000,4088))
19572
+ STN=STN+UnitID-1
19573
+ local OSTN=UTILS.DecimalToOctal(STN)
19574
+ SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",OSTN)
19575
+ end
19576
+ end
19577
+ if SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN then
19578
+ if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN)~=nil then
19579
+ local octal=SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN
19580
+ local decimal=UTILS.OctalToDecimal(octal)+UnitID-1
19581
+ SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",UTILS.DecimalToOctal(decimal))
19582
+ else
19583
+ local STN=math.floor(UTILS.RandomGaussian(504/2,nil,100,504))
19584
+ STN=STN+UnitID-1
19585
+ local OSTN=UTILS.DecimalToOctal(STN)
19586
+ SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",OSTN)
19587
+ end
19588
+ end
19589
+ if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber and type(Callsign)~="number"then
19590
+ SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber=SpawnTemplate.units[UnitID].callsign[2]..SpawnTemplate.units[UnitID].callsign[3]
19591
+ end
19592
+ if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel and type(Callsign)~="number"then
19593
+ local CallsignName=SpawnTemplate.units[UnitID].callsign["name"]
19594
+ CallsignName=string.match(CallsignName,"^(%a+)")
19595
+ local label="NY"
19596
+ if not string.find(CallsignName," ")then
19597
+ label=string.upper(string.match(CallsignName,"^%a")..string.match(CallsignName,"%a$"))
19598
+ end
19599
+ SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel=label
19600
+ end
19601
+ if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.settings then
19602
+ SpawnTemplate.units[UnitID].datalinks.Link16.settings.flightLead=UnitID==1 and true or false
19603
+ end
19604
+ if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.SADL and SpawnTemplate.units[UnitID].datalinks.SADL.settings then
19605
+ SpawnTemplate.units[UnitID].datalinks.SADL.settings.flightLead=UnitID==1 and true or false
19606
+ end
19607
+ end
19608
+ end
19609
+ for UnitID=1,#SpawnTemplate.units do
19610
+ if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.network then
19611
+ local team={}
19612
+ local isF16=string.find(SpawnTemplate.units[UnitID].type,"F-16",1,true)and true or false
19613
+ for ID=1,#SpawnTemplate.units do
19614
+ local member={}
19615
+ member.missionUnitId=ID
19616
+ if isF16 then
19617
+ member.TDOA=true
19618
+ end
19619
+ table.insert(team,member)
19620
+ end
19621
+ SpawnTemplate.units[UnitID].datalinks.Link16.network.teamMembers=team
19622
+ end
18865
19623
  end
18866
19624
  self:T3({"Template:",SpawnTemplate})
18867
19625
  return SpawnTemplate
@@ -23166,7 +23924,7 @@ if DCSControllable then
23166
23924
  local Controller=self:_GetController()
23167
23925
  if Controller then
23168
23926
  if self:IsAir()then
23169
- self:SetOption(AI.Option.Air.val.MISSILE_ATTACK,range)
23927
+ self:SetOption(AI.Option.Air.id.MISSILE_ATTACK,range)
23170
23928
  end
23171
23929
  end
23172
23930
  return self
@@ -24688,6 +25446,7 @@ if vec3 then
24688
25446
  local coord=COORDINATE:NewFromVec3(vec3)
24689
25447
  local Heading=self:GetHeading()
24690
25448
  coord.Heading=Heading
25449
+ return coord
24691
25450
  else
24692
25451
  BASE:E({"Cannot GetAverageCoordinate",Group=self,Alive=self:IsAlive()})
24693
25452
  return nil
@@ -25664,6 +26423,34 @@ local tankertask=self:EnRouteTaskTanker()
25664
26423
  self:PushTask(tankertask,delay+2)
25665
26424
  return self
25666
26425
  end
26426
+ function GROUP:GetGroupSTN()
26427
+ local tSTN={}
26428
+ local units=self:GetUnits()
26429
+ local gname=self:GetName()
26430
+ gname=string.gsub(gname,"(#%d+)$","")
26431
+ local report=REPORT:New()
26432
+ report:Add("Link16 S/TN Report")
26433
+ report:Add("Group: "..gname)
26434
+ report:Add("==================")
26435
+ for _,_unit in pairs(units)do
26436
+ local unit=_unit
26437
+ if unit and unit:IsAlive()then
26438
+ local STN,VCL,VCN,Lead=unit:GetSTN()
26439
+ local name=unit:GetName()
26440
+ tSTN[name]={
26441
+ STN=STN,
26442
+ VCL=VCL,
26443
+ VCN=VCN,
26444
+ Lead=Lead,
26445
+ }
26446
+ local lead=Lead==true and"(*)"or""
26447
+ report:Add(string.format("| %s%s %s %s",tostring(VCL),tostring(VCN),tostring(STN),lead))
26448
+ end
26449
+ end
26450
+ report:Add("==================")
26451
+ local text=report:Text()
26452
+ return tSTN,text
26453
+ end
25667
26454
  UNIT={
25668
26455
  ClassName="UNIT",
25669
26456
  UnitName=nil,
@@ -26458,6 +27245,30 @@ local name=self.UnitName
26458
27245
  local skill=_DATABASE.Templates.Units[name].Template.skill or"Random"
26459
27246
  return skill
26460
27247
  end
27248
+ function UNIT:GetSTN()
27249
+ self:F2(self.UnitName)
27250
+ local STN=nil
27251
+ local VCL=nil
27252
+ local VCN=nil
27253
+ local FGL=false
27254
+ local template=self:GetTemplate()
27255
+ if template.AddPropAircraft then
27256
+ if template.AddPropAircraft.STN_L16 then
27257
+ STN=template.AddPropAircraft.STN_L16
27258
+ elseif template.AddPropAircraft.SADL_TN then
27259
+ STN=template.AddPropAircraft.SADL_TN
27260
+ end
27261
+ VCN=template.AddPropAircraft.VoiceCallsignNumber
27262
+ VCL=template.AddPropAircraft.VoiceCallsignLabel
27263
+ end
27264
+ if template.datalinks and template.datalinks.Link16 and template.datalinks.Link16.settings then
27265
+ FGL=template.datalinks.Link16.settings.flightLead
27266
+ end
27267
+ if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then
27268
+ FGL=template.datalinks.SADL.settings.flightLead
27269
+ end
27270
+ return STN,VCL,VCN,FGL
27271
+ end
26461
27272
  CLIENT={
26462
27273
  ClassName="CLIENT",
26463
27274
  ClientName=nil,
@@ -26981,6 +27792,13 @@ AIRBASE.Normandy={
26981
27792
  ["Broglie"]="Broglie",
26982
27793
  ["Bernay_Saint_Martin"]="Bernay Saint Martin",
26983
27794
  ["Saint_Andre_de_lEure"]="Saint-Andre-de-lEure",
27795
+ ["Biggin_Hill"]="Biggin Hill",
27796
+ ["Manston"]="Manston",
27797
+ ["Detling"]="Detling",
27798
+ ["Lympne"]="Lympne",
27799
+ ["Abbeville_Drucat"]="Abbeville Drucat",
27800
+ ["Merville_Calonne"]="Merville Calonne",
27801
+ ["Saint_Omer_Wizernes"]="Saint-Omer Wizernes",
26984
27802
  }
26985
27803
  AIRBASE.PersianGulf={
26986
27804
  ["Abu_Dhabi_International_Airport"]="Abu Dhabi Intl",
@@ -28501,6 +29319,13 @@ self.launcherUnit=UNIT:Find(self.launcher)
28501
29319
  end
28502
29320
  self.coordinate=COORDINATE:NewFromVec3(self.launcher:getPoint())
28503
29321
  self.lid=string.format("[%s] %s | ",self.typeName,self.name)
29322
+ if self.launcherUnit then
29323
+ self.releaseHeading=self.launcherUnit:GetHeading()
29324
+ self.releaseAltitudeASL=self.launcherUnit:GetAltitude()
29325
+ self.releaseAltitudeAGL=self.launcherUnit:GetAltitude(true)
29326
+ self.releaseCoordinate=self.launcherUnit:GetCoordinate()
29327
+ self.releasePitch=self.launcherUnit:GetPitch()
29328
+ end
28504
29329
  self:SetTimeStepTrack()
28505
29330
  self:SetDistanceInterceptPoint()
28506
29331
  local text=string.format("Weapon v%s\nName=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d, Launcher=%s",
@@ -28646,6 +29471,26 @@ end
28646
29471
  function WEAPON:GetImpactCoordinate()
28647
29472
  return self.impactCoord
28648
29473
  end
29474
+ function WEAPON:GetReleaseHeading(AccountForMagneticInclination)
29475
+ AccountForMagneticInclination=AccountForMagneticInclination or true
29476
+ if AccountForMagneticInclination then return UTILS.ClampAngle(self.releaseHeading-UTILS.GetMagneticDeclination())else return UTILS.ClampAngle(self.releaseHeading)end
29477
+ end
29478
+ function WEAPON:GetReleaseAltitudeASL()
29479
+ return self.releaseAltitudeASL
29480
+ end
29481
+ function WEAPON:GetReleaseAltitudeAGL()
29482
+ return self.releaseAltitudeAGL
29483
+ end
29484
+ function WEAPON:GetReleaseCoordinate()
29485
+ return self.releaseCoordinate
29486
+ end
29487
+ function WEAPON:GetReleasePitch()
29488
+ return self.releasePitch
29489
+ end
29490
+ function WEAPON:GetImpactHeading(AccountForMagneticInclination)
29491
+ AccountForMagneticInclination=AccountForMagneticInclination or true
29492
+ if AccountForMagneticInclination then return UTILS.ClampAngle(self.impactHeading-UTILS.GetMagneticDeclination())else return self.impactHeading end
29493
+ end
28649
29494
  function WEAPON:InAir()
28650
29495
  local inAir=nil
28651
29496
  if self.weapon then
@@ -28717,6 +29562,7 @@ if status then
28717
29562
  self.pos3=pos3
28718
29563
  self.vec3=UTILS.DeepCopy(self.pos3.p)
28719
29564
  self.coordinate:UpdateFromVec3(self.vec3)
29565
+ self.last_velocity=self.weapon:getVelocity()
28720
29566
  self.tracking=true
28721
29567
  if self.trackFunc then
28722
29568
  self.trackFunc(self,unpack(self.trackArg))
@@ -28744,6 +29590,7 @@ self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters",d))
28744
29590
  end
28745
29591
  self.impactVec3=ip or self.vec3
28746
29592
  self.impactCoord=COORDINATE:NewFromVec3(self.vec3)
29593
+ self.impactHeading=UTILS.VecHdg(self.last_velocity)
28747
29594
  if self.impactMark then
28748
29595
  self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s",self.name,self.typeName,self.launcherName))
28749
29596
  end
@@ -33504,7 +34351,7 @@ AirbaseNames=nil,
33504
34351
  }
33505
34352
  function ATC_GROUND:New(Airbases,AirbaseList)
33506
34353
  local self=BASE:Inherit(self,BASE:New())
33507
- self:E({self.ClassName,Airbases})
34354
+ self:T({self.ClassName,Airbases})
33508
34355
  self.Airbases=Airbases
33509
34356
  self.AirbaseList=AirbaseList
33510
34357
  self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart()
@@ -33583,7 +34430,7 @@ function(Client)
33583
34430
  if Client:IsAlive()then
33584
34431
  local IsOnGround=Client:InAir()==false
33585
34432
  for AirbaseID,AirbaseMeta in pairs(self.Airbases)do
33586
- self:E(AirbaseID,AirbaseMeta.KickSpeed)
34433
+ self:T(AirbaseID,AirbaseMeta.KickSpeed)
33587
34434
  if AirbaseMeta.Monitor==true and Client:IsInZone(AirbaseMeta.ZoneBoundary)then
33588
34435
  local NotInRunwayZone=true
33589
34436
  for ZoneRunwayID,ZoneRunway in pairs(AirbaseMeta.ZoneRunways)do
@@ -33592,7 +34439,7 @@ end
33592
34439
  if NotInRunwayZone then
33593
34440
  if IsOnGround then
33594
34441
  local Taxi=Client:GetState(self,"Taxi")
33595
- self:E(Taxi)
34442
+ self:T(Taxi)
33596
34443
  if Taxi==false then
33597
34444
  local Velocity=VELOCITY:New(AirbaseMeta.KickSpeed or self.KickSpeed)
33598
34445
  Client:Message("Welcome to "..AirbaseID..". The maximum taxiing speed is "..
@@ -33712,12 +34559,18 @@ KickSpeed=nil,
33712
34559
  }
33713
34560
  function ATC_GROUND_UNIVERSAL:New(AirbaseList)
33714
34561
  local self=BASE:Inherit(self,BASE:New())
33715
- self:E({self.ClassName})
34562
+ self:T({self.ClassName})
33716
34563
  self.Airbases={}
33717
34564
  for _name,_ in pairs(_DATABASE.AIRBASES)do
33718
34565
  self.Airbases[_name]={}
33719
34566
  end
33720
34567
  self.AirbaseList=AirbaseList
34568
+ if not self.AirbaseList then
34569
+ self.AirbaseList={}
34570
+ for _name,_ in pairs(_DATABASE.AIRBASES)do
34571
+ self.AirbaseList[_name]=_name
34572
+ end
34573
+ end
33721
34574
  self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart()
33722
34575
  for AirbaseID,Airbase in pairs(self.Airbases)do
33723
34576
  if Airbase.ZoneBoundary then
@@ -33816,12 +34669,13 @@ self:SetMaximumKickSpeed(UTILS.MiphToMps(MaximumKickSpeedMiph),Airbase)
33816
34669
  return self
33817
34670
  end
33818
34671
  function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
34672
+ self:I("_AirbaseMonitor")
33819
34673
  self.SetClient:ForEachClient(
33820
34674
  function(Client)
33821
34675
  if Client:IsAlive()then
33822
34676
  local IsOnGround=Client:InAir()==false
33823
34677
  for AirbaseID,AirbaseMeta in pairs(self.Airbases)do
33824
- self:E(AirbaseID,AirbaseMeta.KickSpeed)
34678
+ self:T(AirbaseID,AirbaseMeta.KickSpeed)
33825
34679
  if AirbaseMeta.Monitor==true and Client:IsInZone(AirbaseMeta.ZoneBoundary)then
33826
34680
  local NotInRunwayZone=true
33827
34681
  if AirbaseMeta.ZoneRunways then
@@ -33833,7 +34687,7 @@ end
33833
34687
  if NotInRunwayZone then
33834
34688
  if IsOnGround then
33835
34689
  local Taxi=Client:GetState(self,"Taxi")
33836
- self:E(Taxi)
34690
+ self:T(Taxi)
33837
34691
  if Taxi==false then
33838
34692
  local Velocity=VELOCITY:New(AirbaseMeta.KickSpeed or self.KickSpeed)
33839
34693
  Client:Message("Welcome to "..AirbaseID..". The maximum taxiing speed is "..
@@ -33945,7 +34799,7 @@ return true
33945
34799
  end
33946
34800
  function ATC_GROUND_UNIVERSAL:Start(RepeatScanSeconds)
33947
34801
  RepeatScanSeconds=RepeatScanSeconds or 0.05
33948
- self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
34802
+ self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
33949
34803
  return self
33950
34804
  end
33951
34805
  ATC_GROUND_CAUCASUS={
@@ -33959,7 +34813,7 @@ return self
33959
34813
  end
33960
34814
  function ATC_GROUND_CAUCASUS:Start(RepeatScanSeconds)
33961
34815
  RepeatScanSeconds=RepeatScanSeconds or 0.05
33962
- self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
34816
+ self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
33963
34817
  end
33964
34818
  ATC_GROUND_NEVADA={
33965
34819
  ClassName="ATC_GROUND_NEVADA",
@@ -33972,7 +34826,7 @@ return self
33972
34826
  end
33973
34827
  function ATC_GROUND_NEVADA:Start(RepeatScanSeconds)
33974
34828
  RepeatScanSeconds=RepeatScanSeconds or 0.05
33975
- self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
34829
+ self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
33976
34830
  end
33977
34831
  ATC_GROUND_NORMANDY={
33978
34832
  ClassName="ATC_GROUND_NORMANDY",
@@ -33985,7 +34839,7 @@ return self
33985
34839
  end
33986
34840
  function ATC_GROUND_NORMANDY:Start(RepeatScanSeconds)
33987
34841
  RepeatScanSeconds=RepeatScanSeconds or 0.05
33988
- self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
34842
+ self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
33989
34843
  end
33990
34844
  ATC_GROUND_PERSIANGULF={
33991
34845
  ClassName="ATC_GROUND_PERSIANGULF",
@@ -33997,20 +34851,20 @@ self:SetMaximumKickSpeedKmph(150)
33997
34851
  end
33998
34852
  function ATC_GROUND_PERSIANGULF:Start(RepeatScanSeconds)
33999
34853
  RepeatScanSeconds=RepeatScanSeconds or 0.05
34000
- self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
34854
+ self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
34001
34855
  end
34002
34856
  ATC_GROUND_MARIANAISLANDS={
34003
34857
  ClassName="ATC_GROUND_MARIANAISLANDS",
34004
34858
  }
34005
34859
  function ATC_GROUND_MARIANAISLANDS:New(AirbaseNames)
34006
- local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(self.Airbases,AirbaseNames))
34860
+ local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames))
34007
34861
  self:SetKickSpeedKmph(50)
34008
34862
  self:SetMaximumKickSpeedKmph(150)
34009
34863
  return self
34010
34864
  end
34011
34865
  function ATC_GROUND_MARIANAISLANDS:Start(RepeatScanSeconds)
34012
34866
  RepeatScanSeconds=RepeatScanSeconds or 0.05
34013
- self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
34867
+ self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
34014
34868
  end
34015
34869
  do
34016
34870
  DETECTION_BASE={
@@ -34163,6 +35017,28 @@ DetectionAccepted=false
34163
35017
  end
34164
35018
  end
34165
35019
  end
35020
+ if self.RadarBlur then
35021
+ MESSAGE:New("Radar Blur",10):ToLogIf(self.debug):ToAllIf(self.verbose)
35022
+ local minheight=self.RadarBlurMinHeight or 250
35023
+ local thresheight=self.RadarBlurThresHeight or 90
35024
+ local thresblur=self.RadarBlurThresBlur or 85
35025
+ local dist=math.floor(Distance)
35026
+ if dist<=20 then
35027
+ thresheight=(((dist*dist)/400)*thresheight)
35028
+ thresblur=(((dist*dist)/400)*thresblur)
35029
+ end
35030
+ local fheight=math.floor(math.random(1,10000)/100)
35031
+ local fblur=math.floor(math.random(1,10000)/100)
35032
+ local unit=UNIT:FindByName(DetectedObjectName)
35033
+ if unit and unit:IsAlive()then
35034
+ local AGL=unit:GetAltitude(true)
35035
+ MESSAGE:New("Unit "..DetectedObjectName.." is at "..math.floor(AGL).."m. Distance "..math.floor(Distance).."km.",10):ToLogIf(self.debug):ToAllIf(self.verbose)
35036
+ MESSAGE:New(string.format("fheight = %d/%d | fblur = %d/%d",fheight,thresheight,fblur,thresblur),10):ToLogIf(self.debug):ToAllIf(self.verbose)
35037
+ if fblur>thresblur then DetectionAccepted=false end
35038
+ if AGL<=minheight and fheight<thresheight then DetectionAccepted=false end
35039
+ MESSAGE:New("Detection Accepted = "..tostring(DetectionAccepted),10):ToLogIf(self.debug):ToAllIf(self.verbose)
35040
+ end
35041
+ end
34166
35042
  if not self.DetectedObjects[DetectedObjectName]and TargetIsVisible and self.DistanceProbability then
34167
35043
  local DistanceFactor=Distance/4
34168
35044
  local DistanceProbabilityReversed=(1-self.DistanceProbability)*DistanceFactor
@@ -34323,6 +35199,13 @@ self._.FilterCategories[FilterCategories]=FilterCategories
34323
35199
  end
34324
35200
  return self
34325
35201
  end
35202
+ function DETECTION_BASE:SetRadarBlur(minheight,thresheight,thresblur)
35203
+ self.RadarBlur=true
35204
+ self.RadarBlurMinHeight=minheight or 250
35205
+ self.RadarBlurThresHeight=thresheight or 90
35206
+ self.RadarBlurThresBlur=thresblur or 85
35207
+ return self
35208
+ end
34326
35209
  end
34327
35210
  do
34328
35211
  function DETECTION_BASE:SetRefreshTimeInterval(RefreshTimeInterval)
@@ -39953,6 +40836,10 @@ end
39953
40836
  return self
39954
40837
  end
39955
40838
  function RANGE:SetSRSRangeControl(frequency,modulation,voice,culture,gender,relayunitname)
40839
+ if not self.instructmsrs then
40840
+ self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeControl!")
40841
+ return self
40842
+ end
39956
40843
  self.rangecontrolfreq=frequency or 256
39957
40844
  self.controlmsrs:SetFrequencies(self.rangecontrolfreq)
39958
40845
  self.controlmsrs:SetModulations(modulation or radio.modulation.AM)
@@ -39968,6 +40855,10 @@ end
39968
40855
  return self
39969
40856
  end
39970
40857
  function RANGE:SetSRSRangeInstructor(frequency,modulation,voice,culture,gender,relayunitname)
40858
+ if not self.instructmsrs then
40859
+ self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeInstructor!")
40860
+ return self
40861
+ end
39971
40862
  self.instructorfreq=frequency or 305
39972
40863
  self.instructmsrs:SetFrequencies(self.instructorfreq)
39973
40864
  self.instructmsrs:SetModulations(modulation or radio.modulation.AM)
@@ -48276,11 +49167,22 @@ local _assetattribute
48276
49167
  local _assetcategory
48277
49168
  local _assetairstart=false
48278
49169
  if _nassets>0 then
49170
+ local asset=_assets[1]
48279
49171
  _assetattribute=_assets[1].attribute
48280
49172
  _assetcategory=_assets[1].category
48281
49173
  _assetairstart=_assets[1].takeoffType and _assets[1].takeoffType==COORDINATE.WaypointType.TurningPoint or false
48282
49174
  if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
48283
49175
  if self.airbase and self.airbase:GetCoalition()==self:GetCoalition()then
49176
+ if self.airbase.storage then
49177
+ local nS=self.airbase.storage:GetAmount(asset.unittype)
49178
+ local nA=asset.nunits*request.nasset
49179
+ if nS<nA then
49180
+ 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)",
49181
+ self.alias,nS,asset.unittype,nA,request.nasset)
49182
+ self:_InfoMessage(text,5)
49183
+ return false
49184
+ end
49185
+ end
48284
49186
  if self:IsRunwayOperational()or _assetairstart then
48285
49187
  if _assetairstart then
48286
49188
  else
@@ -48342,6 +49244,7 @@ local text=string.format("Warehouse %s: Request denied! Not close enough to spaw
48342
49244
  self:_InfoMessage(text,5)
48343
49245
  return false
48344
49246
  end
49247
+ elseif _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
48345
49248
  end
48346
49249
  end
48347
49250
  request.cargoassets=_assets
@@ -61653,7 +62556,7 @@ DELIMITER="Punto",
61653
62556
  }
61654
62557
  ATIS.locale="en"
61655
62558
  _ATIS={}
61656
- ATIS.version="0.10.3"
62559
+ ATIS.version="0.10.4"
61657
62560
  function ATIS:New(AirbaseName,Frequency,Modulation)
61658
62561
  local self=BASE:Inherit(self,FSM:New())
61659
62562
  self.airbasename=AirbaseName
@@ -61962,7 +62865,16 @@ self:E(self.lid..string.format("EXPERIMENTAL: Starting ATIS for Helipad %s! SRS
61962
62865
  self.ATISforFARPs=true
61963
62866
  self.useSRS=true
61964
62867
  end
62868
+ if type(self.frequency)=="table"then
62869
+ local frequency=table.concat(self.frequency,"/")
62870
+ local modulation=self.modulation
62871
+ if type(self.modulation)=="table"then
62872
+ modulation=table.concat(self.modulation,"/")
62873
+ end
62874
+ self:I(self.lid..string.format("Starting ATIS v%s for airbase %s on %s MHz Modulation=%s",ATIS.version,self.airbasename,frequency,modulation))
62875
+ else
61965
62876
  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))
62877
+ end
61966
62878
  if not self.useSRS then
61967
62879
  self.radioqueue=RADIOQUEUE:New(self.frequency,self.modulation,string.format("ATIS %s",self.airbasename))
61968
62880
  self.radioqueue:SetSenderCoordinate(self.airbase:GetCoordinate())
@@ -61994,7 +62906,17 @@ if ru then
61994
62906
  relayunitstatus=tostring(ru:IsAlive())
61995
62907
  end
61996
62908
  end
61997
- local text=string.format("State %s: Freq=%.3f MHz %s",fsmstate,self.frequency,UTILS.GetModulationName(self.modulation))
62909
+ local text=""
62910
+ if type(self.frequency)=="table"then
62911
+ local frequency=table.concat(self.frequency,"/")
62912
+ local modulation=self.modulation
62913
+ if type(self.modulation)=="table"then
62914
+ modulation=table.concat(self.modulation,"/")
62915
+ end
62916
+ text=string.format("State %s: Freq=%s MHz %s",fsmstate,frequency,modulation)
62917
+ else
62918
+ text=string.format("State %s: Freq=%.3f MHz %s",fsmstate,self.frequency,UTILS.GetModulationName(self.modulation))
62919
+ end
61998
62920
  if self.useSRS then
61999
62921
  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
62922
  else
@@ -62879,7 +63801,17 @@ function ATIS:UpdateMarker(information,runact,wind,altimeter,temperature)
62879
63801
  if self.markerid then
62880
63802
  self.airbase:GetCoordinate():RemoveMark(self.markerid)
62881
63803
  end
62882
- local text=string.format("ATIS on %.3f %s, %s:\n",self.frequency,UTILS.GetModulationName(self.modulation),tostring(information))
63804
+ local text=""
63805
+ if type(self.frequency)=="table"then
63806
+ local frequency=table.concat(self.frequency,"/")
63807
+ local modulation=self.modulation
63808
+ if type(modulation)=="table"then
63809
+ modulation=table.concat(self.modulation,"/")
63810
+ end
63811
+ text=string.format("ATIS on %s %s, %s:\n",tostring(frequency),tostring(modulation),tostring(information))
63812
+ else
63813
+ text=string.format("ATIS on %.3f %s, %s:\n",self.frequency,UTILS.GetModulationName(self.modulation),tostring(information))
63814
+ end
62883
63815
  text=text..string.format("%s\n",tostring(runact))
62884
63816
  text=text..string.format("%s\n",tostring(wind))
62885
63817
  text=text..string.format("%s\n",tostring(altimeter))
@@ -63343,7 +64275,7 @@ CTLD.UnitTypeCapabilities={
63343
64275
  ["AH-64D_BLK_II"]={type="AH-64D_BLK_II",crates=false,troops=true,cratelimit=0,trooplimit=2,length=17,cargoweightlimit=200},
63344
64276
  ["Bronco-OV-10A"]={type="Bronco-OV-10A",crates=false,troops=true,cratelimit=0,trooplimit=5,length=13,cargoweightlimit=1450},
63345
64277
  }
63346
- CTLD.version="1.0.42"
64278
+ CTLD.version="1.0.44"
63347
64279
  function CTLD:New(Coalition,Prefixes,Alias)
63348
64280
  local self=BASE:Inherit(self,FSM:New())
63349
64281
  BASE:T({Coalition,Prefixes,Alias})
@@ -63388,6 +64320,8 @@ self:AddTransition("*","TroopsRTB","*")
63388
64320
  self:AddTransition("*","CratesDropped","*")
63389
64321
  self:AddTransition("*","CratesBuild","*")
63390
64322
  self:AddTransition("*","CratesRepaired","*")
64323
+ self:AddTransition("*","CratesBuildStarted","*")
64324
+ self:AddTransition("*","CratesRepairStarted","*")
63391
64325
  self:AddTransition("*","Load","*")
63392
64326
  self:AddTransition("*","Save","*")
63393
64327
  self:AddTransition("*","Stop","Stopped")
@@ -63807,6 +64741,7 @@ local desttimer=TIMER:New(function()NearestGroup:Destroy(false)end,self)
63807
64741
  desttimer:Start(self.repairtime-1)
63808
64742
  local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,object,true,NearestGroup:GetCoordinate())
63809
64743
  buildtimer:Start(self.repairtime)
64744
+ self:__CratesRepairStarted(1,Group,Unit)
63810
64745
  else
63811
64746
  if not Engineering then
63812
64747
  self:_SendMessage("Can't repair this unit with "..build.Name,10,false,Group)
@@ -64131,6 +65066,34 @@ self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddis
64131
65066
  end
64132
65067
  return self
64133
65068
  end
65069
+ function CTLD:_RemoveCratesNearby(_group,_unit)
65070
+ self:T(self.lid.." _RemoveCratesNearby")
65071
+ local finddist=self.CrateDistance or 35
65072
+ local crates,number=self:_FindCratesNearby(_group,_unit,finddist,true)
65073
+ if number>0 then
65074
+ local text=REPORT:New("Removing Crates Found Nearby:")
65075
+ text:Add("------------------------------------------------------------")
65076
+ for _,_entry in pairs(crates)do
65077
+ local entry=_entry
65078
+ local name=entry:GetName()
65079
+ local dropped=entry:WasDropped()
65080
+ if dropped then
65081
+ text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass))
65082
+ else
65083
+ text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass))
65084
+ end
65085
+ entry:GetPositionable():Destroy(false)
65086
+ end
65087
+ if text:GetCount()==1 then
65088
+ text:Add(" N O N E")
65089
+ end
65090
+ text:Add("------------------------------------------------------------")
65091
+ self:_SendMessage(text:Text(),30,true,_group)
65092
+ else
65093
+ self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group)
65094
+ end
65095
+ return self
65096
+ end
64134
65097
  function CTLD:_GetDistance(_point1,_point2)
64135
65098
  self:T(self.lid.." _GetDistance")
64136
65099
  if _point1 and _point2 then
@@ -64469,6 +65432,26 @@ else
64469
65432
  return false
64470
65433
  end
64471
65434
  end
65435
+ function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template)
65436
+ local Positions={}
65437
+ local template=_DATABASE:GetGroupTemplate(Template)
65438
+ UTILS.PrintTableToLog(template)
65439
+ local numbertroops=#template.units
65440
+ local newcenter=Coordinate:Translate(Radius,((Heading+270)%360))
65441
+ for i=1,360,math.floor(360/numbertroops)do
65442
+ local phead=((Heading+270+i)%360)
65443
+ local post=newcenter:Translate(Radius,phead)
65444
+ local pos1=post:GetVec2()
65445
+ local p1t={
65446
+ x=pos1.x,
65447
+ y=pos1.y,
65448
+ heading=phead,
65449
+ }
65450
+ table.insert(Positions,p1t)
65451
+ end
65452
+ UTILS.PrintTableToLog(Positions)
65453
+ return Positions
65454
+ end
64472
65455
  function CTLD:_UnloadTroops(Group,Unit)
64473
65456
  self:T(self.lid.." _UnloadTroops")
64474
65457
  local droppingatbase=false
@@ -64509,14 +65492,25 @@ factor=cargo:GetCratesNeeded()or 1
64509
65492
  zoneradius=Unit:GetVelocityMPS()or 100
64510
65493
  end
64511
65494
  local zone=ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,zoneradius*factor)
64512
- local randomcoord=zone:GetRandomCoordinate(10,30*factor):GetVec2()
65495
+ local randomcoord=zone:GetRandomCoordinate(10,30*factor)
65496
+ local heading=Group:GetHeading()or 0
65497
+ if hoverunload or grounded then
65498
+ randomcoord=Group:GetCoordinate()
65499
+ local Angle=(heading+270)%360
65500
+ local offset=hoverunload and 1.5 or 5
65501
+ randomcoord:Translate(offset,Angle,nil,true)
65502
+ end
65503
+ local tempcount=0
64513
65504
  for _,_template in pairs(temptable)do
64514
65505
  self.TroopCounter=self.TroopCounter+1
65506
+ tempcount=tempcount+1
64515
65507
  local alias=string.format("%s-%d",_template,math.random(1,100000))
65508
+ local rad=2.5+tempcount
65509
+ local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template)
64516
65510
  self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
64517
- :InitRandomizeUnits(true,20,2)
64518
65511
  :InitDelayOff()
64519
- :SpawnFromVec2(randomcoord)
65512
+ :InitSetUnitAbsolutePositions(Positions)
65513
+ :SpawnFromVec2(randomcoord:GetVec2())
64520
65514
  self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],type)
64521
65515
  end
64522
65516
  cargo:SetWasDropped(true)
@@ -64715,6 +65709,7 @@ if self.buildtime and self.buildtime>0 then
64715
65709
  local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate())
64716
65710
  buildtimer:Start(self.buildtime)
64717
65711
  self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group)
65712
+ self:__CratesBuildStarted(1,Group,Unit)
64718
65713
  else
64719
65714
  self:_BuildObjectFromCrates(Group,Unit,build)
64720
65715
  end
@@ -65008,6 +66003,7 @@ if cancrates then
65008
66003
  local loadmenu=MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates,self._LoadCratesNearby,self,_group,_unit)
65009
66004
  local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates)
65010
66005
  local packmenu=MENU_GROUP_COMMAND:New(_group,"Pack crates",topcrates,self._PackCratesNearby,self,_group,_unit)
66006
+ local removecratesmenu=MENU_GROUP:New(_group,"Remove crates",topcrates)
65011
66007
  if self.usesubcats then
65012
66008
  local subcatmenus={}
65013
66009
  for _name,_entry in pairs(self.subcats)do
@@ -65042,6 +66038,7 @@ menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrat
65042
66038
  end
65043
66039
  end
65044
66040
  listmenu=MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit)
66041
+ removecrates=MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu,self._RemoveCratesNearby,self,_group,_unit)
65045
66042
  local unloadmenu=MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates,self._UnloadCrates,self,_group,_unit)
65046
66043
  if not self.nobuildmenu then
65047
66044
  local buildmenu=MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates,self._BuildCrates,self,_group,_unit)
@@ -77949,8 +78946,9 @@ AltBackend=nil,
77949
78946
  ConfigFileName="Moose_MSRS.lua",
77950
78947
  ConfigFilePath="Config\\",
77951
78948
  ConfigLoaded=false,
78949
+ ttsprovider="Microsoft",
77952
78950
  }
77953
- MSRS.version="0.1.2"
78951
+ MSRS.version="0.1.3"
77954
78952
  MSRS.Voices={
77955
78953
  Microsoft={
77956
78954
  ["Hedda"]="Microsoft Hedda Desktop",
@@ -78071,8 +79069,7 @@ Backend.Vars.Volume=Volume
78071
79069
  Backend.Functions=Backend.Functions or{}
78072
79070
  return self:_NewAltBackend(Backend)
78073
79071
  end
78074
- local success=self:LoadConfigFile(nil,nil,self.ConfigLoaded)
78075
- if(not success)and(not self.ConfigLoaded)then
79072
+ if not self.ConfigLoaded then
78076
79073
  self:SetPath(PathToSRS)
78077
79074
  self:SetPort()
78078
79075
  self:SetFrequencies(Frequency)
@@ -78111,7 +79108,7 @@ while(self.path:sub(-1)=="/"or self.path:sub(-1)==[[\]])and n<=nmax do
78111
79108
  self.path=self.path:sub(1,#self.path-1)
78112
79109
  n=n+1
78113
79110
  end
78114
- self:I(string.format("SRS path=%s",self:GetPath()))
79111
+ self:T(string.format("SRS path=%s",self:GetPath()))
78115
79112
  end
78116
79113
  return self
78117
79114
  end
@@ -78217,6 +79214,7 @@ self.APIKey=PathToCredentials
78217
79214
  self.provider="gcloud"
78218
79215
  self.GRPCOptions.DefaultProvider="gcloud"
78219
79216
  self.GRPCOptions.gcloud.key=PathToCredentials
79217
+ self.ttsprovider="Google"
78220
79218
  end
78221
79219
  return self
78222
79220
  end
@@ -78229,6 +79227,14 @@ self.GRPCOptions.gcloud.key=APIKey
78229
79227
  end
78230
79228
  return self
78231
79229
  end
79230
+ function MSRS:SetTTSProviderGoogle()
79231
+ self.ttsprovider="Google"
79232
+ return self
79233
+ end
79234
+ function MSRS:SetTTSProviderMicrosoft()
79235
+ self.ttsprovider="Microsoft"
79236
+ return self
79237
+ end
78232
79238
  function MSRS:Help()
78233
79239
  local path=self:GetPath()or STTS.DIRECTORY
78234
79240
  local exe=STTS.EXECUTABLE or"DCS-SR-ExternalAudio.exe"
@@ -78414,16 +79420,22 @@ if coordinate then
78414
79420
  local lat,lon,alt=self:_GetLatLongAlt(coordinate)
78415
79421
  command=command..string.format(" -L %.4f -O %.4f -A %d",lat,lon,alt)
78416
79422
  end
78417
- if self.google then
79423
+ if self.google and self.ttsprovider=="Google"then
78418
79424
  command=command..string.format(' --ssml -G "%s"',self.google)
78419
79425
  end
78420
- self:I("MSRS command="..command)
79426
+ self:T("MSRS command="..command)
78421
79427
  return command
78422
79428
  end
78423
- function MSRS:LoadConfigFile(Path,Filename,ConfigLoaded)
79429
+ function MSRS:LoadConfigFile(Path,Filename)
79430
+ if lfs==nil then
79431
+ env.info("*****Note - lfs and os need to be desanitized for MSRS to work!")
79432
+ return false
79433
+ end
78424
79434
  local path=Path or lfs.writedir()..MSRS.ConfigFilePath
78425
79435
  local file=Filename or MSRS.ConfigFileName or"Moose_MSRS.lua"
78426
- if UTILS.CheckFileExists(path,file)and not ConfigLoaded then
79436
+ local pathandfile=path..file
79437
+ local filexsists=UTILS.FileExists(pathandfile)
79438
+ if filexsists and not MSRS.ConfigLoaded then
78427
79439
  assert(loadfile(path..file))()
78428
79440
  if MSRS_Config then
78429
79441
  if self then
@@ -78438,6 +79450,9 @@ end
78438
79450
  self.culture=MSRS_Config.Culture or"en-GB"
78439
79451
  self.gender=MSRS_Config.Gender or"male"
78440
79452
  self.google=MSRS_Config.Google
79453
+ if MSRS_Config.Provider then
79454
+ self.ttsprovider=MSRS_Config.Provider
79455
+ end
78441
79456
  self.Label=MSRS_Config.Label or"MSRS"
78442
79457
  self.voice=MSRS_Config.Voice
78443
79458
  if MSRS_Config.GRPC then
@@ -78462,6 +79477,9 @@ end
78462
79477
  MSRS.culture=MSRS_Config.Culture or"en-GB"
78463
79478
  MSRS.gender=MSRS_Config.Gender or"male"
78464
79479
  MSRS.google=MSRS_Config.Google
79480
+ if MSRS_Config.Provider then
79481
+ MSRS.ttsprovider=MSRS_Config.Provider
79482
+ end
78465
79483
  MSRS.Label=MSRS_Config.Label or"MSRS"
78466
79484
  MSRS.voice=MSRS_Config.Voice
78467
79485
  if MSRS_Config.GRPC then
@@ -78476,9 +79494,10 @@ end
78476
79494
  MSRS.ConfigLoaded=true
78477
79495
  end
78478
79496
  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)
79497
+ env.info("MSRS - Successfully loaded default configuration from disk!",false)
79498
+ end
79499
+ if not filexsists then
79500
+ env.info("MSRS - Cannot find default configuration file!",false)
78482
79501
  return false
78483
79502
  end
78484
79503
  return true
@@ -78811,6 +79830,7 @@ end
78811
79830
  self:_CheckRadioQueue(dt)
78812
79831
  end
78813
79832
  end
79833
+ MSRS.LoadConfigFile()
78814
79834
  COMMANDCENTER={
78815
79835
  ClassName="COMMANDCENTER",
78816
79836
  CommandCenterName="",