diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 1d42491a2..984f7b528 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -252,8 +252,6 @@ do -- SETTINGS self:SetMessageTime( MESSAGE.Type.Overview, 60 ) self:SetMessageTime( MESSAGE.Type.Update, 15 ) self:SetEraModern() - --self:SetMenutextShort(true) - --self:SetMenuStatic(true) return self else local Settings = _DATABASE:GetPlayerSettings( PlayerName ) @@ -270,14 +268,14 @@ do -- SETTINGS -- @param #SETTINGS self -- @param #boolean onoff If *true* use short menu texts. If *false* long ones (default). function SETTINGS:SetMenutextShort(onoff) - self.MenuShort = onoff + _SETTINGS.MenuShort = onoff end --- Set menu to be static. -- @param #SETTINGS self -- @param #boolean onoff If *true* menu is static. If *false* menu will be updated after changes (default). function SETTINGS:SetMenuStatic(onoff) - self.MenuStatic = onoff + _SETTINGS.MenuStatic = onoff end --- Sets the SETTINGS metric. @@ -496,8 +494,8 @@ do -- SETTINGS -- A2G Coordinate System ------- - local text="A2G Coordinate System XXX" - if self.MenuShort then + local text="A2G Coordinate System" + if _SETTINGS.MenuShort then text="A2G Coordinates" end local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) @@ -505,7 +503,7 @@ do -- SETTINGS -- Set LL DMS if not self:IsA2G_LL_DMS() then local text="Lat/Lon Degree Min Sec (LL DMS)" - if self.MenuShort then + if _SETTINGS.MenuShort then text="LL DMS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime ) @@ -514,7 +512,7 @@ do -- SETTINGS -- Set LL DDM if not self:IsA2G_LL_DDM() then local text="Lat/Lon Degree Dec Min (LL DDM)" - if self.MenuShort then + if _SETTINGS.MenuShort then text="LL DDM" end MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime ) @@ -525,7 +523,7 @@ do -- SETTINGS local text1="LL DDM Accuracy 1" local text2="LL DDM Accuracy 2" local text3="LL DDM Accuracy 3" - if self.MenuShort then + if _SETTINGS.MenuShort then text1="LL DDM" end MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime ) @@ -536,7 +534,7 @@ do -- SETTINGS -- Set BR. if not self:IsA2G_BR() then local text="Bearing, Range (BR)" - if self.MenuShort then + if _SETTINGS.MenuShort then text="BR" end MENU_GROUP_COMMAND:New( MenuGroup, text , A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime ) @@ -545,7 +543,7 @@ do -- SETTINGS -- Set MGRS. if not self:IsA2G_MGRS() then local text="Military Grid (MGRS)" - if self.MenuShort then + if _SETTINGS.MenuShort then text="MGRS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime ) @@ -565,14 +563,14 @@ do -- SETTINGS ------- local text="A2A Coordinate System" - if self.MenuShort then + if _SETTINGS.MenuShort then text="A2A Coordinates" end local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) if not self:IsA2A_LL_DMS() then local text="Lat/Lon Degree Min Sec (LL DMS)" - if self.MenuShort then + if _SETTINGS.MenuShort then text="LL DMS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime ) @@ -580,7 +578,7 @@ do -- SETTINGS if not self:IsA2A_LL_DDM() then local text="Lat/Lon Degree Dec Min (LL DDM)" - if self.MenuShort then + if _SETTINGS.MenuShort then text="LL DDM" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime ) @@ -594,7 +592,7 @@ do -- SETTINGS if not self:IsA2A_BULLS() then local text="Bullseye (BULLS)" - if self.MenuShort then + if _SETTINGS.MenuShort then text="Bulls" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime ) @@ -602,7 +600,7 @@ do -- SETTINGS if not self:IsA2A_BRAA() then local text="Bearing Range Altitude Aspect (BRAA)" - if self.MenuShort then + if _SETTINGS.MenuShort then text="BRAA" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime ) @@ -610,7 +608,7 @@ do -- SETTINGS if not self:IsA2A_MGRS() then local text="Military Grid (MGRS)" - if self.MenuShort then + if _SETTINGS.MenuShort then text="MGRS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime ) @@ -625,14 +623,14 @@ do -- SETTINGS end local text="Measures and Weights System" - if self.MenuShort then + if _SETTINGS.MenuShort then text="Unit System" end local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) if self:IsMetric() then local text="Imperial (Miles,Feet)" - if self.MenuShort then + if _SETTINGS.MenuShort then text="Imperial" end MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime ) @@ -640,14 +638,14 @@ do -- SETTINGS if self:IsImperial() then local text="Metric (Kilometers,Meters)" - if self.MenuShort then + if _SETTINGS.MenuShort then text="Metric" end MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime ) end local text="Messages and Reports" - if self.MenuShort then + if _SETTINGS.MenuShort then text="Messages & Reports" end local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) @@ -805,7 +803,7 @@ do -- SETTINGS if not self:IsA2A_LL_DMS() or _SETTINGS.MenuStatic then local text="Lat/Lon Degree Min Sec (LL DMS)" - if self.MenuShort then + if _SETTINGS.MenuShort then text="LL DMS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" ) @@ -983,7 +981,7 @@ do -- SETTINGS BASE:E( {self, PlayerUnit:GetName(), A2GSystem} ) self.A2GSystem = A2GSystem MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup ) - if self.MenuStatic==false then + if _SETTINGS.MenuStatic==false then self:RemovePlayerMenu(PlayerUnit) self:SetPlayerMenu(PlayerUnit) end @@ -993,7 +991,7 @@ do -- SETTINGS function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem ) self.A2ASystem = A2ASystem MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup ) - if self.MenuStatic==false then + if _SETTINGS.MenuStatic==false then self:RemovePlayerMenu(PlayerUnit) self:SetPlayerMenu(PlayerUnit) end @@ -1003,7 +1001,7 @@ do -- SETTINGS function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy ) self.LL_Accuracy = LL_Accuracy MESSAGE:New( string.format( "Settings: LL format accuracy set to %d decimal places for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup ) - if self.MenuStatic==false then + if _SETTINGS.MenuStatic==false then self:RemovePlayerMenu(PlayerUnit) self:SetPlayerMenu(PlayerUnit) end @@ -1013,7 +1011,7 @@ do -- SETTINGS function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy ) self.MGRS_Accuracy = MGRS_Accuracy MESSAGE:New( string.format( "Settings: MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup ) - if self.MenuStatic==false then + if _SETTINGS.MenuStatic==false then self:RemovePlayerMenu(PlayerUnit) self:SetPlayerMenu(PlayerUnit) end @@ -1023,7 +1021,7 @@ do -- SETTINGS function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW ) self.Metric = MW MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup ) - if self.MenuStatic==false then + if _SETTINGS.MenuStatic==false then self:RemovePlayerMenu(PlayerUnit) self:SetPlayerMenu(PlayerUnit) end diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 8e241a58e..d3fc4d1aa 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -9,24 +9,34 @@ -- -- [476th - Air Weapons Range Objects mod](http://www.476vfightergroup.com/downloads.php?do=file&id=287) is highly recommended for this class. -- --- ## Features: +-- **Main Features:** -- -- * Impact points of bombs, rockets and missiles are recorded and distance to closest range target is measured and reported to the player. -- * Number of hits on strafing passes are counted and reported. Also the percentage of hits w.r.t fired shots is evaluated. -- * Results of all bombing and strafing runs are stored and top 10 results can be displayed. -- * Range targets can be marked by smoke. --- * Range can be illuminated by illumination bombs for night practices. +-- * Range can be illuminated by illumination bombs for night missions. -- * Bomb, rocket and missile impact points can be marked by smoke. -- * Direct hits on targets can trigger flares. -- * Smoke and flare colors can be adjusted for each player via radio menu. -- * Range information and weather report at the range can be reported via radio menu. --- --- More information and examples can be found below. +-- * Persistence: Bombing range results can be saved to disk and loaded the next time the mission is started. +-- * Range control voice overs (>40) for hit assessment. -- -- === -- --- ### [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) --- ### [MOOSE - On the Range - Demonstration Video](https://www.youtube.com/watch?v=kIXcxNB9_3M) +-- ## Youtube Videos: + -- +-- * [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) +-- * [MOOSE - On the Range - Demonstration Video](https://www.youtube.com/watch?v=kIXcxNB9_3M) +-- +-- === +-- +-- ## Missions: Example missions will be added later. +-- +-- === +-- +-- ## Sound files: Check out the pinned messages in the Moose discord *#func-range* channel. -- -- === -- @@ -79,10 +89,23 @@ -- @field #boolean trackmissiles If true (default), all missile types are tracked and impact point to closest bombing target is evaluated. -- @field #boolean defaultsmokebomb If true, initialize player settings to smoke bomb. -- @field #boolean autosave If true, automatically save results every X seconds. --- @extends Core.Base#BASE +-- @field #number instructorfreq Frequency on which the range control transmitts. +-- @field Core.RadioQueue#RADIOQUEUE instructor Instructor radio queue. +-- @field #number rangecontrolfreq Frequency on which the range control transmitts. +-- @field Core.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue. +-- @field #string soundpath Path inside miz file where the sound files are located. Default is "Range Soundfiles/". +-- @extends Core.Fsm#FSM ---- Enables a mission designer to easily set up practice ranges in DCS. A new RANGE object can be created with the @{#RANGE.New}(rangename) contructor. --- The parameter "rangename" defines the name of the range. It has to be unique since this is also the name displayed in the radio menu. +--- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven +-- +-- === +-- +-- ![Banner Image](..\Presentations\RANGE\RANGE_Main.png) +-- +-- # The Range Concept +-- +-- The RANGE class enables a mission designer to easily set up practice ranges in DCS. A new RANGE object can be created with the @{#RANGE.New}(*rangename*) contructor. +-- The parameter *rangename* defines the name of the range. It has to be unique since this is also the name displayed in the radio menu. -- -- Generally, a range consists of strafe pits and bombing targets. For strafe pits the number of hits for each pass is counted and tabulated. -- For bombing targets, the distance from the impact point of the bomb, rocket or missile to the closest range target is measured and tabulated. @@ -97,7 +120,8 @@ -- If that is not done, the script is not started correctly. This can be checked by looking at the radio menues. If the mission was entered correctly, -- there should be an "On the Range" menu items in the "F10. Other..." menu. -- --- ## Strafe Pits +-- # Strafe Pits +-- -- Each strafe pit can consist of multiple targets. Often one finds two or three strafe targets next to each other. -- -- A strafe pit can be added to the range by the @{#RANGE.AddStrafePit}(*targetnames, boxlength, boxwidth, heading, inverseheading, goodpass, foulline*) function. @@ -117,7 +141,8 @@ -- Finally, a valid approach has to be performed below a certain maximum altitude. The default is 914 meters (3000 ft) AGL. This is a parameter valid for all -- strafing pits of the range and can be adjusted by the @{#RANGE.SetMaxStrafeAlt}(maxalt) function. -- --- ## Bombing targets +-- # Bombing targets +-- -- One ore multiple bombing targets can be added to the range by the @{#RANGE.AddBombingTargets}(targetnames, goodhitrange, randommove) function. -- -- * The first parameter *targetnames* has to be a lua table, which contains the names of @{Wrapper.Unit} and/or @{Static} objects defined in the mission editor. @@ -126,10 +151,18 @@ -- * If final (optional) parameter "*randommove*" can be enabled to create moving targets. If this parameter is set to true, the units of this bombing target will randomly move within the range zone. -- Note that there might be quirks since DCS units can get stuck in buildings etc. So it might be safer to manually define a route for the units in the mission editor if moving targets are desired. -- +-- ## Adding Groups +-- -- Another possibility to add bombing targets is the @{#RANGE.AddBombingTargetGroup}(*group, goodhitrange, randommove*) function. Here the parameter *group* is a MOOSE @{Wrapper.Group} object -- and **all** units in this group are defined as bombing targets. +-- +-- ## Specifying Coordinates +-- +-- It is also possible to specify coordinates rather than unit or static objects as bombing target locations. This has the advantage, that even when the unit/static object is dead, the specified +-- coordinate will still be a valid impact point. This can be done via the @{#RANGE.AddBombingTargetCoordinate}(*coord*, *name*, *goodhitrange*) function. -- --- ## Fine Tuning +-- # Fine Tuning +-- -- Many range parameters have good default values. However, the mission designer can change these settings easily with the supplied user functions: -- -- * @{#RANGE.SetMaxStrafeAlt}() sets the max altitude for valid strafing runs. @@ -144,22 +177,61 @@ -- * @{#RANGE.TrackRocketsON}() or @{#RANGE.TrackRocketsOFF}() can be used to enable/disable tracking and evaluating of all rocket types a player fires. -- * @{#RANGE.TrackMissilesON}() or @{#RANGE.TrackMissilesOFF}() can be used to enable/disable tracking and evaluating of all missile types a player fires. -- --- ## Radio Menu +-- # Radio Menu +-- -- Each range gets a radio menu with various submenus where each player can adjust his individual settings or request information about the range or his scores. -- --- The main range menu can be found at "F10. Other..." --> "Fxx. On the Range..." --> "F1. Your Range Name...". +-- The main range menu can be found at "F10. Other..." --> "F*X*. On the Range..." --> "F1. ...". -- -- The range menu contains the following submenues: +-- +-- ![Banner Image](..\Presentations\RANGE\Menu_Main.png) -- --- * "F1. Mark Targets": Various ways to mark targets. --- * "F2. My Settings": Player specific settings. --- * "F3. Stats" Player: statistics and scores. --- * "Range Information": Information about the range, such as bearing and range. Also range and player specific settings are displayed. --- * "Weather Report": Temperature, wind and QFE pressure information is provided. +-- * "F1. Statistics...": Range results of all players and personal stats. +-- * "F2. Mark Targets": Mark range targets by smoke or flares. +-- * "F3. My Settings" Personal settings. +-- * "F4. Range Info": Information about the range, such as bearing and range. +-- +-- ## F1 Statistics +-- +-- ![Banner Image](..\Presentations\RANGE\Menu_Stats.png) +-- +-- ## F2 Mark Targets +-- +-- ![Banner Image](..\Presentations\RANGE\Menu_Stats.png) +-- +-- ## F3 My Settings +-- +-- ![Banner Image](..\Presentations\RANGE\Menu_MySettings.png) +-- +-- ## F4 Range Info +-- +-- ![Banner Image](..\Presentations\RANGE\Menu_RangeInfo.png) +-- +-- # Voice Overs +-- +-- Voice over sound files can be downloaded from the Moose Discord. Check the pinned messages in the *#func-range* channel. +-- +-- Instructor radio will inform players when they enter or exit the range zone and provide the radio frequency of the range control for hit assessment. +-- This can be enabled via the @{#RANGE.SetInstructorRadio}(*frequency*) functions, where *frequency* is the AM frequency in MHz. +-- +-- The range control can be enabled via the @{#RANGE.SetRangeControl}(*frequency*) functions, where *frequency* is the AM frequency in MHz. +-- +-- By default, the sound files are placed in the "Range Soundfiles/" folder inside the mission (.miz) file. Another folder can be specified via the @{#RANGE.SetSoundfilesPath}(*path*) function. +-- +-- # Persistence +-- +-- To automatically save bombing results to disk, use the @{#RANGE.SetAutosave}() function. Bombing results will be saved as csv file in your "Saved Games\DCS.openbeta\Logs" directory. +-- Each range has a separate file, which is named "RANGE-<*RangeName*>_BombingResults.csv". +-- +-- The next time you start the mission, these results are also automatically loaded. +-- +-- Strafing results are currently **not** saved. -- --- ## Examples +-- # Examples -- --- ### Goldwater Range +-- ## Goldwater Range +-- -- This example shows hot to set up the [Barry M. Goldwater range](https://en.wikipedia.org/wiki/Barry_M._Goldwater_Air_Force_Range). -- It consists of two strafe pits each has two targets plus three bombing targets. -- @@ -190,7 +262,7 @@ -- -- The [476th - Air Weapons Range Objects mod](http://www.476vfightergroup.com/downloads.php?do=file&id=287) is (implicitly) used in this example. -- --- ## Debugging +-- # Debugging -- -- In case you have problems, it is always a good idea to have a look at your DCS log file. You find it in your "Saved Games" folder, so for example in -- C:\Users\\Saved Games\DCS\Logs\dcs.log @@ -213,44 +285,49 @@ -- -- @field #RANGE RANGE={ - ClassName = "RANGE", - Debug=false, - id=nil, - rangename=nil, - location=nil, - messages=true, - rangeradius=5000, - rangezone=nil, - strafeTargets={}, - bombingTargets={}, - nbombtargets=0, - nstrafetargets=0, - MenuAddedTo = {}, - planes = {}, - strafeStatus = {}, + ClassName = "RANGE", + Debug = false, + id = nil, + rangename = nil, + location = nil, + messages = true, + rangeradius = 5000, + rangezone = nil, + strafeTargets = {}, + bombingTargets = {}, + nbombtargets = 0, + nstrafetargets = 0, + MenuAddedTo = {}, + planes = {}, + strafeStatus = {}, strafePlayerResults = {}, - bombPlayerResults = {}, - PlayerSettings = {}, - dtBombtrack=0.005, - BombtrackThreshold=25000, - Tmsg=30, - examinergroupname=nil, - examinerexclusive=nil, - strafemaxalt=914, - ndisplayresult=10, - BombSmokeColor=SMOKECOLOR.Red, - StrafeSmokeColor=SMOKECOLOR.Green, - StrafePitSmokeColor=SMOKECOLOR.White, - illuminationminalt=500, - illuminationmaxalt=1000, - scorebombdistance=1000, - TdelaySmoke=3.0, - eventmoose=true, - trackbombs=true, - trackrockets=true, - trackmissiles=true, - defaultsmokebomb=true, - autosave=false, + bombPlayerResults = {}, + PlayerSettings = {}, + dtBombtrack = 0.005, + BombtrackThreshold = 25000, + Tmsg = 30, + examinergroupname = nil, + examinerexclusive = nil, + strafemaxalt = 914, + ndisplayresult = 10, + BombSmokeColor = SMOKECOLOR.Red, + StrafeSmokeColor = SMOKECOLOR.Green, + StrafePitSmokeColor = SMOKECOLOR.White, + illuminationminalt = 500, + illuminationmaxalt = 1000, + scorebombdistance = 1000, + TdelaySmoke = 3.0, + eventmoose = true, + trackbombs = true, + trackrockets = true, + trackmissiles = true, + defaultsmokebomb = true, + autosave = false, + instructorfreq = nil, + instructor = nil, + rangecontrolfreq = nil, + rangecontrol = nil, + soundpath = "Range Soundfiles/" } --- Default range parameters. @@ -325,6 +402,105 @@ RANGE.TargetType={ -- @field #string player Player name. -- @field #string airframe Aircraft type of player. -- @field #number time Time via timer.getAbsTime() in seconds of impact. +-- @field #string date OS date. + +--- Sound file data. +-- @type RANGE.Soundfile +-- @field #string filename Name of the file +-- @field #number duration Duration in seconds. + +--- Sound files. +-- @type RANGE.Sound +-- @field #RANGE.Soundfile RC0 +-- @field #RANGE.Soundfile RC1 +-- @field #RANGE.Soundfile RC2 +-- @field #RANGE.Soundfile RC3 +-- @field #RANGE.Soundfile RC4 +-- @field #RANGE.Soundfile RC5 +-- @field #RANGE.Soundfile RC6 +-- @field #RANGE.Soundfile RC7 +-- @field #RANGE.Soundfile RC8 +-- @field #RANGE.Soundfile RC9 +-- @field #RANGE.Soundfile RCAccuracy +-- @field #RANGE.Soundfile RCDegrees +-- @field #RANGE.Soundfile RCExcellentHit +-- @field #RANGE.Soundfile RCExcellentPass +-- @field #RANGE.Soundfile RCFeet +-- @field #RANGE.Soundfile RCFor +-- @field #RANGE.Soundfile RCGoodHit +-- @field #RANGE.Soundfile RCGoodPass +-- @field #RANGE.Soundfile RCHitsOnTarget +-- @field #RANGE.Soundfile RCImpact +-- @field #RANGE.Soundfile RCIneffectiveHit +-- @field #RANGE.Soundfile RCIneffectivePass +-- @field #RANGE.Soundfile RCInvalidHit +-- @field #RANGE.Soundfile RCLeftStrafePitTooQuickly +-- @field #RANGE.Soundfile RCPercent +-- @field #RANGE.Soundfile RCPoorHit +-- @field #RANGE.Soundfile RCPoorPass +-- @field #RANGE.Soundfile RCRollingInOnStrafeTarget +-- @field #RANGE.Soundfile RCTotalRoundsFired +-- @field #RANGE.Soundfile RCWeaponImpactedTooFar +-- @field #RANGE.Soundfile IR0 +-- @field #RANGE.Soundfile IR1 +-- @field #RANGE.Soundfile IR2 +-- @field #RANGE.Soundfile IR3 +-- @field #RANGE.Soundfile IR4 +-- @field #RANGE.Soundfile IR5 +-- @field #RANGE.Soundfile IR6 +-- @field #RANGE.Soundfile IR7 +-- @field #RANGE.Soundfile IR8 +-- @field #RANGE.Soundfile IR9 +-- @field #RANGE.Soundfile IRDecimal +-- @field #RANGE.Soundfile IRMegaHertz +-- @field #RANGE.Soundfile IREnterRange +-- @field #RANGE.Soundfile IRExitRange +RANGE.Sound = { + RC0={filename="RC-0.ogg", duration=0.60}, + RC1={filename="RC-1.ogg", duration=0.47}, + RC2={filename="RC-2.ogg", duration=0.43}, + RC3={filename="RC-3.ogg", duration=0.50}, + RC4={filename="RC-4.ogg", duration=0.58}, + RC5={filename="RC-5.ogg", duration=0.54}, + RC6={filename="RC-6.ogg", duration=0.61}, + RC7={filename="RC-7.ogg", duration=0.53}, + RC8={filename="RC-8.ogg", duration=0.34}, + RC9={filename="RC-9.ogg", duration=0.54}, + RCAccuracy={filename="RC-Accuracy.ogg", duration=0.67}, + RCDegrees={filename="RC-Degrees.ogg", duration=0.59}, + RCExcellentHit={filename="RC-ExcellentHit.ogg", duration=0.76}, + RCExcellentPass={filename="RC-ExcellentPass.ogg", duration=0.89}, + RCFeet={filename="RC-Feet.ogg", duration=0.49}, + RCFor={filename="RC-For.ogg", duration=0.64}, + RCGoodHit={filename="RC-GoodHit.ogg", duration=0.52}, + RCGoodPass={filename="RC-GoodPass.ogg", duration=0.62}, + RCHitsOnTarget={filename="RC-HitsOnTarget.ogg", duration=0.88}, + RCImpact={filename="RC-Impact.ogg", duration=0.61}, + RCIneffectiveHit={filename="RC-IneffectiveHit.ogg", duration=0.86}, + RCIneffectivePass={filename="RC-IneffectivePass.ogg", duration=0.99}, + RCInvalidHit={filename="RC-InvalidHit.ogg", duration=2.97}, + RCLeftStrafePitTooQuickly={filename="RC-LeftStrafePitTooQuickly.ogg", duration=3.09}, + RCPercent={filename="RC-Percent.ogg", duration=0.56}, + RCPoorHit={filename="RC-PoorHit.ogg", duration=0.54}, + RCPoorPass={filename="RC-PoorPass.ogg", duration=0.68}, + RCRollingInOnStrafeTarget={filename="RC-RollingInOnStrafeTarget.ogg", duration=1.38}, + RCTotalRoundsFired={filename="RC-TotalRoundsFired.ogg", duration=1.22}, + RCWeaponImpactedTooFar={filename="RC-WeaponImpactedTooFar.ogg", duration=3.73}, + IR0={filename="IR-0.ogg", duration=0.55}, + IR1={filename="IR-1.ogg", duration=0.41}, + IR2={filename="IR-2.ogg", duration=0.37}, + IR3={filename="IR-3.ogg", duration=0.41}, + IR4={filename="IR-4.ogg", duration=0.37}, + IR5={filename="IR-5.ogg", duration=0.43}, + IR6={filename="IR-6.ogg", duration=0.55}, + IR7={filename="IR-7.ogg", duration=0.43}, + IR8={filename="IR-8.ogg", duration=0.38}, + IR9={filename="IR-9.ogg", duration=0.55}, + IRDecimal={filename="IR-Decimal.ogg", duration=0.54}, + IRMegaHertz={filename="IR-MegaHertz.ogg", duration=0.87}, + IREnterRange={filename="IR-EnterRange.ogg", duration=4.83}, + IRExitRange={filename="IR-ExitRange.ogg", duration=3.10}, +} --- Global list of all defined range names. -- @field #table Names @@ -340,7 +516,7 @@ RANGE.MenuF10Root=nil --- Range script version. -- @field #string version -RANGE.version="2.1.2" +RANGE.version="2.2.0" --TODO list: --TODO: Verbosity level for messages. @@ -431,14 +607,14 @@ function RANGE:New(rangename) -- @function [parent=#RANGE] Impact -- @param #RANGE self -- @param #RANGE.BombResult result Data of bombing run. - -- @param #RANGE.Playerdata player Data of player settings etc. + -- @param #RANGE.PlayerData player Data of player settings etc. --- Triggers the FSM delayed event "Impact". -- @function [parent=#RANGE] __Impact -- @param #RANGE self -- @param #number delay Delay in seconds before the function is called. -- @param #RANGE.BombResult result Data of the bombing run. - -- @param #RANGE.Playerdata player Data of player settings etc. + -- @param #RANGE.PlayerData player Data of player settings etc. --- On after "Impact" event user function. Called when a bomb/rocket/missile impacted. -- @function [parent=#RANGE] OnAfterImpact @@ -447,18 +623,18 @@ function RANGE:New(rangename) -- @param #string Event Event. -- @param #string To To state. -- @param #RANGE.BombResult result Data of the bombing run. - -- @param #RANGE.Playerdata player Data of player settings etc. + -- @param #RANGE.PlayerData player Data of player settings etc. --- Triggers the FSM event "EnterRange". -- @function [parent=#RANGE] EnterRange -- @param #RANGE self - -- @param #RANGE.Playerdata player Data of player settings etc. + -- @param #RANGE.PlayerData player Data of player settings etc. --- Triggers the FSM delayed event "EnterRange". -- @function [parent=#RANGE] __EnterRange -- @param #RANGE self -- @param #number delay Delay in seconds before the function is called. - -- @param #RANGE.Playerdata player Data of player settings etc. + -- @param #RANGE.PlayerData player Data of player settings etc. --- On after "EnterRange" event user function. Called when a player enters the range zone. -- @function [parent=#RANGE] OnAfterEnterRange @@ -466,18 +642,18 @@ function RANGE:New(rangename) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param #RANGE.Playerdata player Data of player settings etc. + -- @param #RANGE.PlayerData player Data of player settings etc. --- Triggers the FSM event "ExitRange". -- @function [parent=#RANGE] ExitRange -- @param #RANGE self - -- @param #RANGE.Playerdata player Data of player settings etc. + -- @param #RANGE.PlayerData player Data of player settings etc. --- Triggers the FSM delayed event "ExitRange". -- @function [parent=#RANGE] __ExitRange -- @param #RANGE self -- @param #number delay Delay in seconds before the function is called. - -- @param #RANGE.Playerdata player Data of player settings etc. + -- @param #RANGE.PlayerData player Data of player settings etc. --- On after "ExitRange" event user function. Called when a player leaves the range zone. -- @function [parent=#RANGE] OnAfterExitRange @@ -485,7 +661,7 @@ function RANGE:New(rangename) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param #RANGE.Playerdata player Data of player settings etc. + -- @param #RANGE.PlayerData player Data of player settings etc. -- Return object. return self @@ -573,6 +749,63 @@ function RANGE:onafterStart() end end + + -- Init range control. + if self.rangecontrolfreq then + + -- Radio queue. + self.rangecontrol=RADIOQUEUE:New(self.rangecontrolfreq) + + -- Init numbers. + self.rangecontrol:SetDigit(0, RANGE.Sound.RC0.filename, RANGE.Sound.RC0.duration, self.soundpath) + self.rangecontrol:SetDigit(1, RANGE.Sound.RC1.filename, RANGE.Sound.RC1.duration, self.soundpath) + self.rangecontrol:SetDigit(2, RANGE.Sound.RC2.filename, RANGE.Sound.RC2.duration, self.soundpath) + self.rangecontrol:SetDigit(3, RANGE.Sound.RC3.filename, RANGE.Sound.RC3.duration, self.soundpath) + self.rangecontrol:SetDigit(4, RANGE.Sound.RC4.filename, RANGE.Sound.RC4.duration, self.soundpath) + self.rangecontrol:SetDigit(5, RANGE.Sound.RC5.filename, RANGE.Sound.RC5.duration, self.soundpath) + self.rangecontrol:SetDigit(6, RANGE.Sound.RC6.filename, RANGE.Sound.RC6.duration, self.soundpath) + self.rangecontrol:SetDigit(7, RANGE.Sound.RC7.filename, RANGE.Sound.RC7.duration, self.soundpath) + self.rangecontrol:SetDigit(8, RANGE.Sound.RC8.filename, RANGE.Sound.RC8.duration, self.soundpath) + self.rangecontrol:SetDigit(9, RANGE.Sound.RC9.filename, RANGE.Sound.RC9.duration, self.soundpath) + + -- Set location where the messages are transmitted from. + self.rangecontrol:SetSenderCoordinate(self.location) + + -- Start range control radio queue. + self.rangecontrol:Start(1, 0.1) + + -- Init range control. + if self.instructorfreq then + + -- Radio queue. + self.instructor=RADIOQUEUE:New(self.instructorfreq) + + -- Init numbers. + self.instructor:SetDigit(0, RANGE.Sound.IR0.filename, RANGE.Sound.IR0.duration, self.soundpath) + self.instructor:SetDigit(1, RANGE.Sound.IR1.filename, RANGE.Sound.IR1.duration, self.soundpath) + self.instructor:SetDigit(2, RANGE.Sound.IR2.filename, RANGE.Sound.IR2.duration, self.soundpath) + self.instructor:SetDigit(3, RANGE.Sound.IR3.filename, RANGE.Sound.IR3.duration, self.soundpath) + self.instructor:SetDigit(4, RANGE.Sound.IR4.filename, RANGE.Sound.IR4.duration, self.soundpath) + self.instructor:SetDigit(5, RANGE.Sound.IR5.filename, RANGE.Sound.IR5.duration, self.soundpath) + self.instructor:SetDigit(6, RANGE.Sound.IR6.filename, RANGE.Sound.IR6.duration, self.soundpath) + self.instructor:SetDigit(7, RANGE.Sound.IR7.filename, RANGE.Sound.IR7.duration, self.soundpath) + self.instructor:SetDigit(8, RANGE.Sound.IR8.filename, RANGE.Sound.IR8.duration, self.soundpath) + self.instructor:SetDigit(9, RANGE.Sound.IR9.filename, RANGE.Sound.IR9.duration, self.soundpath) + + -- Set location where the messages are transmitted from. + self.instructor:SetSenderCoordinate(self.location) + + -- Start instructor radio queue. + self.instructor:Start(1, 0.1) + + end + + end + + -- Load prev results. + if self.autosave then + self:Load() + end -- Debug mode: smoke all targets and range zone. if self.Debug then @@ -831,6 +1064,34 @@ function RANGE:TrackMissilesOFF() end +--- Enable range control and set frequency. +-- @param #RANGE self +-- @param #number frequency Frequency in MHz. Default 256 MHz. +-- @return #RANGE self +function RANGE:SetRangeControl(frequency) + self.rangecontrolfreq=frequency or 256 + return self +end + +--- Enable instructor radio and set frequency. +-- @param #RANGE self +-- @param #number frequency Frequency in MHz. Default 305 MHz. +-- @return #RANGE self +function RANGE:SetInstructorRadio(frequency) + self.instructorfreq=frequency or 305 + return self +end + +--- Set sound files folder within miz file. +-- @param #RANGE self +-- @param #string path Path for sound files. Default "ATIS Soundfiles/". Mind the slash "/" at the end! +-- @return #RANGE self +function RANGE:SetSoundfilesPath(path) + self.soundpath=tostring(path or "Range Soundfiles/") + self:I(self.lid..string.format("Setting sound files path to %s", self.soundpath)) + return self +end + --- Add new strafe pit. For a strafe pit, hits from guns are counted. One pit can consist of several units. -- Note, an approach is only valid, if the player enters via a zone in front of the pit, which defined by boxlength and boxheading. -- Furthermore, the player must not be too high and fly in the direction of the pit to make a valid target apporoach. @@ -1565,7 +1826,7 @@ function RANGE:OnEventShot(EventData) if _distance == nil or _temp < _distance then _distance = _temp _closetTarget = _bombtarget - _closeCoord=targetcoord + _closeCoord=targetcoord if _distance <= 0.5*_bombtarget.goodhitrange then _hitquality = "EXCELLENT" elseif _distance <= _bombtarget.goodhitrange then @@ -1609,8 +1870,12 @@ function RANGE:OnEventShot(EventData) elseif insidezone then -- Send message. - local _message=string.format("%s, weapon fell more than %.1f km away from nearest range target. No score!", _callsign, self.scorebombdistance/1000) + local _message=string.format("%s, weapon impacted too far from nearest range target (>%.1f km). No score!", _callsign, self.scorebombdistance/1000) self:_DisplayMessageToGroup(_unit, _message, nil, false) + + if self.rangecontrol then + self.rangecontrol:NewTransmission(RANGE.Sound.RCWeaponImpactedTooFar.filename, RANGE.Sound.RCWeaponImpactedTooFar.duration, self.soundpath, nil, nil, _message, self.subduration) + end else self:T(self.id.."Weapon impacted outside range zone.") @@ -1649,15 +1914,49 @@ function RANGE:onafterStatus(From, Event, To) -- Check player status. self:_CheckPlayers() - -- Save results. - if self.autosave then - self:Save() - end - -- Check back in ~10 seconds. self:__Status(-10) end +--- Function called after player enters the range zone. +-- @param #RANGE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #RANGE.PlayerData player Player data. +function RANGE:onafterEnterRange(From, Event, To, player) + + if self.instructor and self.rangecontrol then + + -- Range control radio frequency split. + local RF=UTILS.Split(string.format("%.3f", self.rangecontrolfreq), ".") + + -- Radio message that player entered the range + self.instructor:NewTransmission(RANGE.Sound.IREnterRange.filename, RANGE.Sound.IREnterRange.duration, self.soundpath) + self.instructor:Number2Transmission(RF[1]) + if tonumber(RF[2])>0 then + self.instructor:NewTransmission(RANGE.Sound.IRDecimal.filename, RANGE.Sound.IRDecimal.duration, self.soundpath) + self.instructor:Number2Transmission(RF[2]) + end + self.instructor:NewTransmission(RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath) + end + +end + +--- Function called after player leaves the range zone. +-- @param #RANGE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #RANGE.PlayerData player Player data. +function RANGE:onafterExitRange(From, Event, To, player) + + if self.instructor then + self.instructor:NewTransmission(RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath) + end + +end + --- Function called after bomb impact on range. -- @param #RANGE self @@ -1665,7 +1964,7 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #RANGE.BombResult result Result of bomb impact. --- @param #RANGE.PlayerData player +-- @param #RANGE.PlayerData player Player data table. function RANGE:onafterImpact(From, Event, To, result, player) -- Only display target name if there is more than one bomb target. @@ -1682,6 +1981,25 @@ function RANGE:onafterImpact(From, Event, To, result, player) text=text.."." end text=text..string.format(" %s hit.", result.quality) + + if self.rangecontrol then + self.rangecontrol:NewTransmission(RANGE.Sound.RCImpact.filename, RANGE.Sound.RCImpact.duration, self.soundpath, nil, nil, text, self.subduration) + self.rangecontrol:Number2Transmission(string.format("%03d", result.radial), nil, 0.1) + self.rangecontrol:NewTransmission(RANGE.Sound.RCDegrees.filename, RANGE.Sound.RCDegrees.duration, self.soundpath) + self.rangecontrol:NewTransmission(RANGE.Sound.RCFor.filename, RANGE.Sound.RCFor.duration, self.soundpath) + self.rangecontrol:Number2Transmission(string.format("%d", UTILS.MetersToFeet(result.distance))) + self.rangecontrol:NewTransmission(RANGE.Sound.RCFeet.filename, RANGE.Sound.RCFeet.duration, self.soundpath) + if result.quality=="POOR" then + self.rangecontrol:NewTransmission(RANGE.Sound.RCPoorHit.filename, RANGE.Sound.RCPoorHit.duration, self.soundpath, nil, 0.5) + elseif result.quality=="INEFFECTIVE" then + self.rangecontrol:NewTransmission(RANGE.Sound.RCIneffectiveHit.filename, RANGE.Sound.RCIneffectiveHit.duration, self.soundpath, nil, 0.5) + elseif result.quality=="GOOD" then + self.rangecontrol:NewTransmission(RANGE.Sound.RCGoodHit.filename, RANGE.Sound.RCGoodHit.duration, self.soundpath, nil, 0.5) + elseif result.quality=="EXCELLENT" then + self.rangecontrol:NewTransmission(RANGE.Sound.RCExcellentHit.filename, RANGE.Sound.RCExcellentHit.duration, self.soundpath, nil, 0.5) + end + + end -- Unit. local unit=UNIT:FindByName(player.unitname) @@ -1689,6 +2007,11 @@ function RANGE:onafterImpact(From, Event, To, result, player) -- Send message. self:_DisplayMessageToGroup(unit, text, nil, true) self:T(self.id..text) + + -- Save results. + if self.autosave then + self:Save() + end end @@ -1746,7 +2069,11 @@ function RANGE:onafterSave(From, Event, To) local quality=result.quality local time=UTILS.SecondsToClock(result.time) local airframe=result.airframe - scores=scores..string.format("\n%s,%d,%s,%.2f,%03d,%s,%s,%s,%s", playername, i, target, distance, radial, quality, weapon, airframe, time) + local date="n/a" + if os then + date=os.date() + end + scores=scores..string.format("\n%s,%d,%s,%.2f,%03d,%s,%s,%s,%s,%s", playername, i, target, distance, radial, quality, weapon, airframe, time, date) end end @@ -1783,7 +2110,7 @@ function RANGE:onafterLoad(From, Event, To) f:close() return data else - self:E(self.id..string.format("ERROR: Could not load player results from file %s", tostring(filename))) + self:E(self.id..string.format("WARNING: Could not load player results from file %s. File might not exist just yet.", tostring(filename))) return nil end end @@ -1833,6 +2160,7 @@ function RANGE:onafterLoad(From, Event, To) result.weapon=tostring(resultdata[7]) result.airframe=tostring(resultdata[8]) result.time=UTILS.ClockToSeconds(resultdata[9] or "00:00:00") + result.date=resultdata[10] or "n/a" -- Create player array if necessary. self.bombPlayerResults[playername]=self.bombPlayerResults[playername] or {} @@ -2334,8 +2662,8 @@ function RANGE:_CheckPlayers() ------------------------------ if not playersettings.inzone then - self:EnterRange(playersettings) playersettings.inzone=true + self:EnterRange(playersettings) end else @@ -2345,8 +2673,8 @@ function RANGE:_CheckPlayers() ------------------------------- if playersettings.inzone==true then - self:ExitRange(playersettings) playersettings.inzone=false + self:ExitRange(playersettings) end end @@ -2411,6 +2739,10 @@ function RANGE:_CheckInZone(_unitName) -- Send message. self:_DisplayMessageToGroup(_unit, _msg, nil, true) + + if self.rangecontrol then + self.rangecontrol:NewTransmission(RANGE.Sound.RCLeftStrafePitTooQuickly.filename, RANGE.Sound.RCLeftStrafePitTooQuickly.duration, self.soundpath) + end else @@ -2419,16 +2751,21 @@ function RANGE:_CheckInZone(_unitName) -- Result. local _result = self.strafeStatus[_unitID] + local _sound = nil --#RANGE.Soundfile -- Judge this pass. Text is displayed on summary. if _result.hits >= _result.zone.goodPass*2 then _result.text = "EXCELLENT PASS" + _sound=RANGE.Sound.RCExcellentPass elseif _result.hits >= _result.zone.goodPass then _result.text = "GOOD PASS" + _sound=RANGE.Sound.RCGoodPass elseif _result.hits >= _result.zone.goodPass/2 then _result.text = "INEFFECTIVE PASS" + _sound=RANGE.Sound.RCIneffectivePass else _result.text = "POOR PASS" + _sound=RANGE.Sound.RCPoorPass end -- Calculate accuracy of run. Number of hits wrt number of rounds fired. @@ -2439,13 +2776,28 @@ function RANGE:_CheckInZone(_unitName) end -- Message text. - local _text=string.format("%s, %s with %d hits on target %s.", self:_myname(_unitName), _result.text, _result.hits, _result.zone.name) + local _text=string.format("%s, hits on target %s: %d", self:_myname(_unitName), _result.zone.name, _result.hits) if shots and accur then _text=_text..string.format("\nTotal rounds fired %d. Accuracy %.1f %%.", shots, accur) end + _text=_text..string.format("\n%s", _result.text) -- Send message. self:_DisplayMessageToGroup(_unit, _text) + + -- Voice over. + if self.rangecontrol then + self.rangecontrol:NewTransmission(RANGE.Sound.RCHitsOnTarget.filename, RANGE.Sound.RCHitsOnTarget.duration, self.soundpath) + self.rangecontrol:Number2Transmission(string.format("%d", _result.hits)) + if shots and accur then + self.rangecontrol:NewTransmission(RANGE.Sound.RCTotalRoundsFired.filename, RANGE.Sound.RCTotalRoundsFired.duration, self.soundpath, nil, 0.2) + self.rangecontrol:Number2Transmission(string.format("%d", shots), nil, 0.2) + self.rangecontrol:NewTransmission(RANGE.Sound.RCAccuracy.filename, RANGE.Sound.RCAccuracy.duration, self.soundpath, nil, 0.2) + self.rangecontrol:Number2Transmission(string.format("%d", UTILS.Round(accur, 0))) + self.rangecontrol:NewTransmission(RANGE.Sound.RCPercent.filename, RANGE.Sound.RCPercent.duration, self.soundpath) + end + self.rangecontrol:NewTransmission(_sound.filename, _sound.duration, self.soundpath, nil, 0.5) + end -- Set strafe status to nil. self.strafeStatus[_unitID] = nil @@ -2492,6 +2844,10 @@ function RANGE:_CheckInZone(_unitName) -- Rolling in! local _msg=string.format("%s, rolling in on strafe pit %s.", self:_myname(_unitName), _targetZone.name) + + if self.rangecontrol then + self.rangecontrol:NewTransmission(RANGE.Sound.RCRollingInOnStrafeTarget.filename, RANGE.Sound.RCRollingInOnStrafeTarget.duration, self.soundpath) + end -- Send message. self:_DisplayMessageToGroup(_unit, _msg, 10, true) @@ -2998,7 +3354,7 @@ function RANGE:_SmokeStrafeTargetBoxes(unitname) for _,_target in pairs(self.strafeTargets) do local zone=_target.polygon --Core.Zone#ZONE - zone:SmokeZone(self.StrafePitSmokeColor) + zone:SmokeZone(self.StrafePitSmokeColor, 4) for _,_point in pairs(_target.smokepoints) do _point:SmokeOrange() --Corners are smoked orange. end @@ -3191,7 +3547,8 @@ function RANGE:_myname(unitname) local pname=unit:GetPlayerName() local csign=unit:GetCallsign() - return string.format("%s (%s)", csign, pname) + --return string.format("%s (%s)", csign, pname) + return string.format("%s", pname) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 86c021b18..16679951c 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -80,7 +80,7 @@ -- @field #boolean windtrue Report true (from) heading of wind. Default is magnetic. -- @extends Core.Fsm#FSM ---- Be informed! +--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde -- -- === -- @@ -339,10 +339,10 @@ ATIS.Alphabet = { --- Runway correction for converting true to magnetic heading. -- @type ATIS.RunwayM2T --- @field #number Caucasus 0� (East). --- @field #number Nevada +12� (East). --- @field #number Normandy -10� (West). --- @field #number PersianGulf +2� (East). +-- @field #number Caucasus 0° (East). +-- @field #number Nevada +12° (East). +-- @field #number Normandy -10° (West). +-- @field #number PersianGulf +2° (East). ATIS.RunwayM2T={ Caucasus=0, Nevada=12, @@ -523,7 +523,7 @@ ATIS.version="0.4.0" -- @return #ATIS self function ATIS:New(airbasename, frequency, modulation) - -- Inherit everything from WAREHOUSE class. + -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #ATIS self.airbasename=airbasename @@ -650,7 +650,7 @@ end --- Set magnetic runway headings as depicted on the runway, e.g. 13 for 130. -- @param #ATIS self --- @param #table headings Magnetic headings. Inverse (-180�) headings are added automatically. +-- @param #table headings Magnetic headings. Inverse (-180°) headings are added automatically. -- @return #ATIS self function ATIS:SetRunwayHeadingsMagnetic(headings) if type(headings)=="table" then @@ -728,12 +728,12 @@ end -- -- To get *true* from *magnetic* heading one has to add easterly or substract westerly variation, e.g -- --- A magnetic heading of 180� corresponds to a true heading of +-- A magnetic heading of 180° corresponds to a true heading of -- --- * 186� on the Caucaus map --- * 192� on the Nevada map --- * 170� on the Normany map --- * 182� on the Persian Gulf map +-- * 186° on the Caucaus map +-- * 192° on the Nevada map +-- * 170° on the Normany map +-- * 182° on the Persian Gulf map -- -- Likewise, to convert *magnetic* into *true* heading, one has to substract easterly and add westerly variation. -- @@ -922,7 +922,7 @@ function ATIS:onafterStatus(From, Event, To) local text=string.format("State %s", fsmstate) self:I(self.lid..text) - self:__Status(60) + self:__Status(-60) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -941,7 +941,7 @@ function ATIS:onafterCheckQueue(From, Event, To) end -- Check back in 5 seconds. - self:__CheckQueue(5) + self:__CheckQueue(-5) end --- Broadcast ATIS radio message. @@ -1070,10 +1070,10 @@ function ATIS:onafterBroadcast(From, Event, To) --- Temperature --- ------------------- - -- Temperature in �C (or �F). + -- Temperature in °C (or °F). local temperature=coord:GetTemperature(height) - -- Convert to �F. + -- Convert to °F. if self.TDegF then temperature=UTILS.CelciusToFarenheit(temperature) end @@ -1553,7 +1553,7 @@ end --- Get runway from user supplied magnetic heading. -- @param #ATIS self -- @param #number windfrom Wind direction (from) in degrees. --- @return #string Runway magnetic heading divided by ten (and rounded). Eg, "13" for 130�. +-- @return #string Runway magnetic heading divided by ten (and rounded). Eg, "13" for 130°. function ATIS:GetMagneticRunway(windfrom) local diffmin=nil @@ -1588,7 +1588,7 @@ function ATIS:GetNavPoint(navpoints, runway) else local nr=tonumber(nav.runway) local r=tonumber(runway) - if math.abs(nr-r)<=2 then --We allow an error of +-20� here. + if math.abs(nr-r)<=2 then --We allow an error of +-20° here. return nav end end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index cd3ab7363..4f95ca31e 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1688,7 +1688,7 @@ AIRBOSS.MenuF10Root=nil --- Airboss class version. -- @field #string version -AIRBOSS.version="1.0.8" +AIRBOSS.version="1.0.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1954,22 +1954,29 @@ function AIRBOSS:New(carriername, alias) end -- Smoke zones. - if false then - local case=2 + if true then + local case=3 self.holdingoffset=30 + --[[ self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red, 5) self:_GetZoneLineup():SmokeZone(SMOKECOLOR.Green, 5) self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.White, 45) self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange, 45) + ]] self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue, 45) self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue, 45) - self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Red, 45) + self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Blue, 45) self:_GetZoneCorridor(case):SmokeZone(SMOKECOLOR.Green, 45) self:_GetZoneHolding(case, 1):SmokeZone(SMOKECOLOR.White, 45) + self:_GetZoneHolding(case, 2):SmokeZone(SMOKECOLOR.White, 45) self:_GetZoneInitial(case):SmokeZone(SMOKECOLOR.Orange, 45) - self:_GetZoneCommence(case):SmokeZone(SMOKECOLOR.Red, 45) + + self:_GetZoneCommence(case, 1):SmokeZone(SMOKECOLOR.Red, 45) + self:_GetZoneCommence(case, 2):SmokeZone(SMOKECOLOR.Red, 45) + --[[ self:_GetZoneAbeamLandingSpot():SmokeZone(SMOKECOLOR.Red, 5) self:_GetZoneLandingSpot():SmokeZone(SMOKECOLOR.Red, 5) + ]] end -- Carrier parameter debug tests. @@ -5715,14 +5722,16 @@ function AIRBOSS:_GetNextMarshalFight() end -- Check if conditions are right. - if stack==1 and flight.holding~=nil and Tmarshal>=TmarshalMin then - if flight.ai then - -- Return AI flight. - return flight - else - -- Check for human player if they are already commencing. - if flight.step~=AIRBOSS.PatternStep.COMMENCING then + if flight.holding~=nil and Tmarshal>=TmarshalMin then + if flight.case==1 and stack==1 or flight.case>1 then + if flight.ai then + -- Return AI flight. return flight + else + -- Check for human player if they are already commencing. + if flight.step~=AIRBOSS.PatternStep.COMMENCING then + return flight + end end end end @@ -6863,8 +6872,8 @@ function AIRBOSS:_CollapseMarshalStack(flight, nopattern) for _,_flight in pairs(self.Qmarshal) do local mflight=_flight --#AIRBOSS.PlayerData - -- Only collapse stack of which the flight left. CASE II/III stack is the same. - if (case==1 and mflight.case==1) or (case>1 and mflight.case>1) then + -- Only collapse stack of which the flight left. CASE II/III stacks are not collapsed. + if (case==1 and mflight.case==1) then --or (case>1 and mflight.case>1) then -- Get current flag/stack value. local mstack=mflight.flag @@ -6968,6 +6977,96 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) -- Recovery case. case=case or self.case + + if case==1 then + return self:_GetFreeStack_Old(ai, case, empty) + end + + -- Max number of stacks available. + local nmaxstacks=100 + if case==1 then + nmaxstacks=self.Nmaxmarshal + end + + -- Assume up to two (human) flights per stack. All are free. + local stack={} + for i=1,nmaxstacks do + stack[i]=self.NmaxStack -- Number of human flights per stack. + end + + local nmax=1 + + -- Loop over all flights in marshal stack. + for _,_flight in pairs(self.Qmarshal) do + local flight=_flight --#AIRBOSS.FlightGroup + + -- Check that the case is right. + if flight.case==case then + + -- Get stack of flight. + local n=flight.flag + + if n>nmax then + nmax=n + end + + if n>0 then + if flight.ai or flight.case>1 then + stack[n]=0 -- AI get one stack on their own. Also CASE II/III get one stack each. + else + stack[n]=stack[n]-1 + end + else + self:E(string.format("ERROR: Flight %s in marshal stack has stack value <= 0. Stack value is %d.", flight.groupname, n)) + end + + end + end + + local nfree=nil + if stack[nmax]==0 then + -- Max occupied stack is completely full! + if case==1 then + if nmax>=nmaxstacks then + -- Already all Case I stacks are occupied ==> wait outside 10 NM zone. + nfree=nil + else + -- Return next free stack. + nfree=nmax+1 + end + else + -- Case II/III return next stack + nfree=nmax+1 + end + + elseif stack[nmax]==self.NmaxStack then + -- Max occupied stack is completely empty! This should happen only when there is no other flight in the marshal queue. + self:E(self.lid..string.format("ERROR: Max occupied stack is empty. Should not happen! Nmax=%d, stack[nmax]=%d", nmax, stack[nmax])) + nfree=nmax + else + -- Max occupied stack is partly full. + if ai or empty or case>1 then + nfree=nmax+1 + else + nfree=nmax + end + + end + + self:I(self.lid..string.format("Returning free stack %s", tostring(nfree))) + return nfree +end + +--- Get next free Marshal stack. Depending on AI/human and recovery case. +-- @param #AIRBOSS self +-- @param #boolean ai If true, get a free stack for an AI flight group. +-- @param #number case Recovery case. Default current (self) case in progress. +-- @param #boolean empty Return lowest stack that is completely empty. +-- @return #number Lowest free stack available for the given case or nil if all Case I stacks are taken. +function AIRBOSS:_GetFreeStack_Old(ai, case, empty) + + -- Recovery case. + case=case or self.case -- Max number of stacks available. local nmaxstacks=100 @@ -9008,7 +9107,7 @@ function AIRBOSS:_Commencing(playerData, zonecheck) if zonecheck then -- Get auto commence zone. - local zoneCommence=self:_GetZoneCommence(playerData.case) + local zoneCommence=self:_GetZoneCommence(playerData.case, playerData.flag) -- Check if unit is in the zone. local inzone=playerData.unit:IsInZone(zoneCommence) @@ -10943,8 +11042,9 @@ end --- Get zone where player are automatically commence when enter. -- @param #AIRBOSS self -- @param #number case Recovery case. +-- @param #number stack Stack for Case II/III as we commence from stack>=1. -- @return Core.Zone#ZONE Holding zone. -function AIRBOSS:_GetZoneCommence(case) +function AIRBOSS:_GetZoneCommence(case, stack) -- Commence zone. local zone @@ -10981,12 +11081,11 @@ function AIRBOSS:_GetZoneCommence(case) else -- Case II/III + + stack=stack or 1 - -- We simply take the corridor for now. But a bit shorter. Holding starts at 21 and add 2 NM box as commence. - --zone=self:_GetZoneCorridor(case, 23) - - -- Total length. - local l=21 + -- Start point at 21 NM for stack=1. + local l=20+stack -- Offset angle local offset=self:GetRadial(case, false, true) @@ -17333,7 +17432,7 @@ function AIRBOSS:_MarkMarshalZone(_unitName, flare) local zoneHolding=self:_GetZoneHolding(case, stack) -- Get Case I commence zone at three position. - local zoneThree=self:_GetZoneCommence(case) + local zoneThree=self:_GetZoneCommence(case, stack) -- Pattern alitude. local patternalt=self:_GetMarshalAltitude(stack, case)