forked from funkyfranky/WeatherMark
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWeatherMark.lua
More file actions
511 lines (421 loc) · 20.3 KB
/
WeatherMark.lua
File metadata and controls
511 lines (421 loc) · 20.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- WeatherMark Script for DCS World
-- Version 1.0
-- By funkyfranky (2018)
--
-- Features:
-- ---------
-- * Offers easy access to weather data at all points of the map.
-- * Reports temperature, QFE pressure, wind direction and strength, wind strength classification according to Beaufort scale.
-- * Output of weather data in metric or imperial units.
-- * Optionally, the altitude can be specified at which the weather data is evaluated.
-- * Works with static and dynamic weather.
-- * Works with all current and future maps (Caucasus, NTTR, Normandy, PG, ...)
--
-- Prerequisite:
-- ------------
-- * This script requires DCS 2.5.1 or higher. Note that the script uses only the pure DCS API, i.e. NO other framework like MIST, MOOSE, etc required.
--
-- Load the script:
-- ----------------
-- 1.) Download the script and save it anywhere on your hard drive.
-- 2.) Open your mission in the mission editor.
-- 3.) At a new trigger:
-- * TYPE "4 MISSION START"
-- * ACTION "DO SCRIPT FILE"
-- * OPEN --> Browse to the location where you saved the script and click OK.
-- 4.) Save the mission and start it.
-- 5.) Have fun :)
--
-- Basic Usage:
-- ------------
-- 1.) Place a mark on the F10 map.
-- 2.) As text enter "weather report".
-- 3.) Click somewhere else on the map to submit the new text.
-- 4.) The original mark will disappear and a new mark with the weather data at the point the mark was set is created.
--
-- Options:
-- --------
-- Type "weather report, imperial" to get weather report in imperial units independent of default unit system.
-- Type "weather report, metric" to get weather report in metric units independent of default unit system.
-- Type "weather report, alt 1000" to get weather report at that location but at an ASL altitude of 1000 meters for feet, depending on default unit system.
-- Type "weather report, alt 1000, imperial" to get weather at that location at an altitude of 1000 feet independent of default unit system.
-- Type "weather report, alt 1000, metric" to get weather at that location at an altitude of 1000 meters independent of default unit system.
-- Type "weather set, metric" to set the default unit system to metric units.
-- Type "weather set, imperial" to set the default unit system to imperial units.
--
-- *** NOTE ***
-- * All keywords are CaSE inSenSITvE.
-- * Instead of "weather report" you can also type "weather request" or "weather mark". All three commands are equivalent.
-- * Commas are the speparators between options ==> They are IMPORTANT!
--
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Weathermark Table.
weathermark={}
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User settings. Choose main key phrase and default unit system.
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set default unit system. Possible values:
-- "metric" ==> Pressure in hPa and mmHg, Temperature in Celsius, Wind speed in meters per second, altitude in meters.
-- "imperial" ==> Pressure in hPa and inHg, Temperature in Fahrenheit, Wind speed in knots, altitude in feet.
weathermark.unitsystem="metric"
--- Key phrase to look for in the mark text which triggers the weather report.
weathermark.keyphrase="weather"
--- DCS bug regarding wrong marker vector components was fixed. If so, set to true!
weathermark.DCSbugfixed=false
--- Enable debug mode ==> give more output to DCS log file.
weathermark.Debug=false
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Version.
weathermark.version="1.0"
--- Identifier. All output in DCS.log will start with this.
weathermark.id="WeatherMark "
--- Enable/Disable error boxes displayed on screen.
env.setErrorMessageBoxEnabled(false)
--- Initial Marker id.
weathermark.markid=10000
--- Unit conversion factors.
weathermark.meter2feet=3.28084
weathermark.hPa2mmHg=0.7500615613030
weathermark.hPa2inHg=0.0295299830714
weathermark.mps2knots=1.94384
--- Enumerators.
weathermark.imperial="imperial"
weathermark.metric="metric"
-- Version info.
env.info(weathermark.id..string.format("Loading version %s", weathermark.version))
env.info(weathermark.id..string.format("Keyphrase = %s", weathermark.keyphrase))
env.info(weathermark.id..string.format("Unit system = %s", weathermark.unitsystem))
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler.
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Event handler.
weathermark.eventHandler={}
--- Handle world events.
function weathermark.eventHandler:onEvent(Event)
-- Only interested in S_EVENT_MARK_*
if Event == nil or Event.idx == nil then
return true
end
-- Debug output.
if Event.id==world.event.S_EVENT_MARK_ADDED then
weathermark.info(weathermark.id.."S_EVENT_MARK_ADDED")
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
weathermark.info(weathermark.id.."S_EVENT_MARK_CHANGE")
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
weathermark.info(weathermark.id.."S_EVENT_MARK_REMOVED")
end
weathermark.info(string.format("Event id = %s", tostring(Event.id)))
weathermark.info(string.format("Event time = %s", tostring(Event.time)))
weathermark.info(string.format("Event idx = %s", tostring(Event.idx)))
weathermark.info(string.format("Event coalition = %s", tostring(Event.coalition)))
weathermark.info(string.format("Event group id = %s", tostring(Event.groupID)))
weathermark.info(string.format("Event pos X = %s", tostring(Event.pos.x)))
weathermark.info(string.format("Event pos Y = %s", tostring(Event.pos.y)))
weathermark.info(string.format("Event pos Z = %s", tostring(Event.pos.z)))
if Event.initiator~=nil then
local _unitname=Event.initiator:getName()
weathermark.info(string.format("Event ini unit = %s", tostring(_unitname)))
end
weathermark.info(string.format("Event text = \n%s", tostring(Event.text)))
-- Call event function when a marker has changed, i.e. text was entered or changed.
if Event.id==world.event.S_EVENT_MARK_CHANGE then
weathermark._OnEventMarkChange(Event)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Function executed when a mark has changed. This happens when text is entered or changed.
function weathermark._OnEventMarkChange(Event)
-- Check if marker has a text and the "weater report" keyphrase.
if Event.text~=nil and Event.text:lower():find(weathermark.keyphrase) then
-- Convert (wrong x-->z, z-->x) vec3
-- TODO: This needs to be "fixed", once DCS gives the correct numbers for x and z.
local vec3
if weathermark.DCSbugfixed then
vec3={x=Event.pos.x, y=Event.pos.y, z=Event.pos.z}
else
vec3={x=Event.pos.z, y=Event.pos.y, z=Event.pos.x}
end
-- By default, alt of mark point is always 5 m! Adjust for the correct ASL height.
vec3.y=weathermark._GetLandHeight(vec3)
-- Analyse the mark point text and extract the keywords.
local _options=weathermark._MarkTextAnalysis(Event.text)
if _options then
-- Check options set commands and return.
if _options.set then
if _options.unitsystem then
weathermark.unitsystem=_options.unitsystem
weathermark.info(weathermark.id..string.format("Global option unitsystem changed to %s.", _options.unitsystem))
-- Delete old mark.
weathermark.info(weathermark.id..string.format("Removing mark # %d.", Event.idx))
trigger.action.removeMark(Event.idx)
end
return
end
else
-- None of the keywords matched.
return
end
-- Ajust manual altitude from meters to feet if unit system is imperial.
if _options.alt then
if (_options.unitsystem and _options.unitsystem=="imperial") or (_options.unitsystem==nil and weathermark.unitsystem=="imperial") then
_options.alt=_options.alt/weathermark.meter2feet
end
end
-- Get weather report text.
local _report = weathermark._WeatherReport(vec3, _options.alt, _options.unitsystem or weathermark.unitsystem)
-- Add a new mark with weather report info.
weathermark.markid=weathermark.markid+1
if Event.groupID > 0 then
weathermark.info(weathermark.id..string.format("Mark # %d added for group ID %d.", weathermark.markid, Event.groupID))
trigger.action.markToGroup(weathermark.markid, _report, vec3, Event.groupID, false, "Weather Mark added for own group.")
else
weathermark.info(weathermark.id..string.format("Mark # %d added for coalition %d.", weathermark.markid, Event.coalition))
trigger.action.markToCoalition(weathermark.markid, _report, vec3, Event.coalition, false, "Weather Mark added for own coalition.")
end
-- Delete old mark.
weathermark.info(weathermark.id..string.format("Removing mark # %d.", Event.idx))
trigger.action.removeMark(Event.idx)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Analyse the mark text and extract keywords.
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Extract keywords from mark text.
function weathermark._MarkTextAnalysis(text)
weathermark.info(weathermark.id..string.format("MarkTextAnalysis text:\n%s", text))
-- Option parameters extracted from the mark text.
local switch={}
switch.report=false
switch.set=false
-- Check for correct keywords.
if text:lower():find(weathermark.keyphrase.." report") or text:lower():find(weathermark.keyphrase.." request") or text:lower():find(weathermark.keyphrase.." mark") then
switch.report=true
elseif text:lower():find(weathermark.keyphrase.." set") then
switch.set=true
else
weathermark.info(weathermark.id..'WARNING: NEITHER "REPORT"/"REQUEST" nor "SET" keywords specified!')
return nil
end
-- keywords are split by ","
local keywords=weathermark._split(text, ",")
for _,keyphrase in pairs(keywords) do
-- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma.
local str=weathermark._split(keyphrase, " ")
local key=str[1]
local val=str[2]
if (switch.report or switch.set) and keyphrase:lower():find("imperial") then
switch.unitsystem="imperial"
weathermark.info(weathermark.id..string.format("Keyword unit = %s", switch.unitsystem))
elseif (switch.report or switch.set) and keyphrase:lower():find("metric") then
switch.unitsystem="metric"
weathermark.info(weathermark.id..string.format("Keyword unit = %s", switch.unitsystem))
elseif switch.report and key:lower():find("alt") then
-- Set altitude.
switch.alt=tonumber(val)
weathermark.info(weathermark.id..string.format("Keyword alt = %d", val))
end
end
return switch
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Main weather report.
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Weather Report. Report pressure QFE/QNH, temperature, wind at certain location.
function weathermark._WeatherReport(vec3, alt, unitsystem)
-- Debug output.
weathermark.info(weathermark.id..string.format("Weather report coordinates:"))
weathermark.info(weathermark.id..string.format("vec3 x = %s", tostring(vec3.x)))
weathermark.info(weathermark.id..string.format("vec3 y = %s", tostring(vec3.y)))
weathermark.info(weathermark.id..string.format("vec3 z = %s", tostring(vec3.z)))
weathermark.info(weathermark.id..string.format("alt = %s", tostring(alt)))
weathermark.info(weathermark.id..string.format("units = %s", tostring(unitsystem)))
-- Get Temperature [K] and Pressure [Pa] at vec3.
local T
local Pqfe
if alt then
-- At user specified altitude.
T,Pqfe=atmosphere.getTemperatureAndPressure({x=vec3.x, y=alt, z=vec3.z})
else
-- One meter above the surface.
T,Pqfe=atmosphere.getTemperatureAndPressure(vec3)
end
-- Get pressure at sea level.
local _,Pqnh=atmosphere.getTemperatureAndPressure({x=vec3.x, y=0, z=vec3.z})
-- Convert pressure from Pascal to hecto Pascal.
Pqfe=Pqfe/100
Pqnh=Pqnh/100
-- Pressure unit conversion hPa --> mmHg or inHg
local _Pqnh=string.format("%.1f mmHg", Pqnh * weathermark.hPa2mmHg)
local _Pqfe=string.format("%.1f mmHg", Pqfe * weathermark.hPa2mmHg)
-- Imperial units inch Hg.
if unitsystem=="imperial" then
_Pqnh=string.format("%.2f inHg", Pqnh * weathermark.hPa2inHg)
_Pqfe=string.format("%.2f inHg", Pqfe * weathermark.hPa2inHg)
end
-- Temperature unit conversion: Kelvin to Celsius or Fahrenheit.
T=T-273.15
local _T=string.format('%d°C', T)
if unitsystem=="imperial" then
_T=string.format('%d°F', weathermark._CelsiusToFahrenheit(T))
end
-- Get wind direction and speed.
local Dir,Vel=weathermark._GetWind(vec3, alt)
-- Get Beaufort wind scale.
local Bn,Bd=weathermark._BeaufortScale(Vel)
-- Formatted wind direction.
local Ds = string.format('%03d°', Dir)
-- Velocity in player units.
local Vs=string.format('%.1f m/s', Vel)
if unitsystem=="imperial" then
Vs=string.format("%.1f knots", Vel * weathermark.mps2knots)
end
-- Altitude.
local _alt=alt or vec3.y
local _Alt=string.format("%d m", _alt)
if unitsystem=="imperial" then
_Alt=string.format("%d ft", _alt * weathermark.meter2feet)
end
-- Weather report text.
-- NOTE, there is big problem with the text. If there are too many \n or the text is too long, DCS crashes!
-- This happens always when you have 5 or more \n or even with just 4 \n and for example one long line which alse breaks the text into another line.
local text=""
text=text..string.format("Altitude %s ASL\n",_Alt)
text=text..string.format("QFE %.1f hPa = %s\n", Pqfe,_Pqfe)
--text=text..string.format("QNH %.1f hPa = %s\n", Pqnh,_Pqnh)
text=text..string.format("Temperature %s\n",_T)
text=text..string.format("Wind from %s at %s (%s)", Ds, Vs, Bd)
weathermark.info(string.format("WeatherMark Report:\n%s", text))
return text
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Helper functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Debug output to dcs.log file.
function weathermark.info(text)
if weathermark.Debug then
env.info(text)
end
end
--- Returns the wind direction (from) and strength.
function weathermark._GetWind(vec3, height)
local point={x=vec3.x, y=vec3.y, z=vec3.z}
if height then
point.y=height
else
point.y=vec3.y
end
-- Get wind velocity vector.
local windvec3 = atmosphere.getWind(point)
local direction = math.deg(math.atan2(windvec3.z, windvec3.x))
if direction < 0 then
direction = direction + 360
end
-- Convert TO direction to FROM direction.
if direction > 180 then
direction = direction-180
else
direction = direction+180
end
-- Calc 2D strength.
local strength=math.sqrt((windvec3.x)^2+(windvec3.z)^2)
-- Debug output.
weathermark.info(string.format("Wind data: height = %s", tostring(height)))
weathermark.info(string.format("Wind data: vec3 x=%.1f y=%.1f, z=%.1f", vec3.x, vec3.y, vec3.z))
weathermark.info(string.format("Wind data: point x=%.1f y=%.1f, z=%.1f", point.x, point.y,point.z))
weathermark.info(string.format("Wind data: wind x=%.1f y=%.1f, z=%.1f", windvec3.x, windvec3.y,windvec3.z))
weathermark.info(string.format("Wind data: |v| = %.1f", strength))
weathermark.info(string.format("Wind data: ang = %.1f", direction))
-- Return wind direction and strength km/h.
return direction, strength, windvec3
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Return the height of the land at the coordinate.
function weathermark._GetLandHeight(vec3)
local vec2 = {x=vec3.x, y=vec3.z}
-- We add 1 m "safety margin" because data from getlandheight gives the surface and wind at or below the surface is zero!
return land.getHeight(vec2)+1
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Beaufort scale: returns Beaufort number and wind description as a function of wind speed in m/s.
function weathermark._BeaufortScale(speed)
local bn=nil
local bd=nil
if speed<0.51 then
bn=0
bd="Calm"
elseif speed<2.06 then
bn=1
bd="Light Air"
elseif speed<3.60 then
bn=2
bd="Light Breeze"
elseif speed<5.66 then
bn=3
bd="Gentle Breeze"
elseif speed<8.23 then
bn=4
bd="Moderate Breeze"
elseif speed<11.32 then
bn=5
bd="Fresh Breeze"
elseif speed<14.40 then
bn=6
bd="Strong Breeze"
elseif speed<17.49 then
bn=7
bd="Moderate Gale"
elseif speed<21.09 then
bn=8
bd="Fresh Gale"
elseif speed<24.69 then
bn=9
bd="Strong Gale"
elseif speed<28.81 then
bn=10
bd="Storm"
elseif speed<32.92 then
bn=11
bd="Violent Storm"
else
bn=12
bd="Hurricane"
end
return bn,bd
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Convert Celsius to Fahrenheit.
weathermark._CelsiusToFahrenheit = function(Celsius)
return Celsius * 9/5 + 32
end
--- Split string. C.f. http://stackoverflow.com/questions/1426954/split-string-in-lua
function weathermark._split(str, sep)
local result = {}
local regex = ("([^%s]+)"):format(sep)
for each in str:gmatch(regex) do
table.insert(result, each)
end
return result
end
--- Create wind profile.
function weathermark._WindvsAlt()
weathermark.info("FF Weather curve")
local alt=0
for i=1,2000 do
local dir,vel,vec3=weathermark._GetWind({x=0, y=0, z=0}, alt)
weathermark.info(string.format("%.3f;%.3f;%.1f;%.3f,%.3f;%.3f", alt, vel, dir, vec3.x, vec3.y, vec3.z))
alt=alt+10
end
end
--weathermark._WindvsAlt()
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add event handler.
-------------------------------------------------------------------------------------------------------------------------------------------------------------
world.addEventHandler(weathermark.eventHandler)
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------