RANGE 2.0

This commit is contained in:
Frank
2019-05-01 17:27:30 +02:00
parent 43c95b6ab5
commit 43193cf7f6
3 changed files with 312 additions and 87 deletions
+1 -1
View File
@@ -243,7 +243,7 @@ function FOX2:onafterStart(From, Event, To)
self:TraceLevel(2)
end
self:__Status(-1)
self:__Status(-10)
end
--- On after Stop event. Stops the missile trainer and unhandles events.
+277 -79
View File
@@ -43,6 +43,7 @@
-- @type RANGE
-- @field #string ClassName Name of the Class.
-- @field #boolean Debug If true, debug info is send as messages on the screen.
-- @field #string id String id of range for output in DCS log.
-- @field #string rangename Name of the range.
-- @field Core.Point#COORDINATE location Coordinate of the range location.
-- @field #number rangeradius Radius of range defining its total size for e.g. smoking bomb impact points and sending radio messages. Default 5 km.
@@ -76,6 +77,7 @@
-- @field #boolean trackrockets If true (default), all rocket types are tracked and impact point to closest bombing target is evaluated.
-- @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 autosafe If true, automatically save results every X seconds.
-- @extends Core.Base#BASE
--- 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.
@@ -212,6 +214,7 @@
RANGE={
ClassName = "RANGE",
Debug=false,
id=nil,
rangename=nil,
location=nil,
rangeradius=5000,
@@ -245,6 +248,7 @@ RANGE={
trackrockets=true,
trackmissiles=true,
defaultsmokebomb=true,
autosave=false,
}
--- Default range parameters.
@@ -284,6 +288,7 @@ RANGE.TargetType={
-- @field #boolean messages Display info messages.
-- @field #string unitname Name of player aircraft unit.
-- @field #string playername Name of player.
-- @field #string airframe Aircraft type name.
--- Bomb target data.
-- @type RANGE.BombTarget
@@ -313,7 +318,8 @@ RANGE.TargetType={
-- @field #number radial Radial in degrees.
-- @field #string weapon Name of the weapon.
-- @field #string quality Hit quality.
-- @field #RANGE.PlayerData player Player data.
-- @field #string player Player name.
-- @field #string airframe Aircraft type of player.
-- @field #number time Time via timer.getAbsTime() in seconds of impact.
--- Global list of all defined range names.
@@ -328,10 +334,6 @@ RANGE.MenuF10={}
-- @field #table MenuF10Root Root menu on mission level.
RANGE.MenuF10Root=nil
--- Some ID to identify who we are in output of the DCS.log file.
-- @field #string id
RANGE.id="RANGE | "
--- Range script version.
-- @field #string version
RANGE.version="2.0.0"
@@ -366,9 +368,12 @@ function RANGE:New(rangename)
--TODO: make sure that the range name is not given twice. This would lead to problems in the F10 radio menu.
self.rangename=rangename or "Practice Range"
-- Log id.
self.id=string.format("RANGE %s | ", self.rangename)
-- Debug info.
local text=string.format("RANGE script version %s - creating new RANGE object of name: %s.", RANGE.version, self.rangename)
self:I(RANGE.id..text)
local text=string.format("Script version %s - creating new RANGE object %s.", RANGE.version, self.rangename)
self:I(self.id..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
-- Defaults
@@ -383,6 +388,8 @@ function RANGE:New(rangename)
self:AddTransition("Stopped", "Start", "Running") -- Start RANGE script.
self:AddTransition("*", "Status", "*") -- Status of RANGE script.
self:AddTransition("*", "Impact", "*") -- Impact of bomb/rocket/missile.
self:AddTransition("*", "Save", "*") -- Save player results.
self:AddTransition("*", "Load", "*") -- Load player results.
------------------------
--- Pseudo Functions ---
@@ -418,12 +425,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.
--- 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.
--- On after "Impact" event user function. Called when a bomb/rocket/missile impacted.
-- @function [parent=#RANGE] OnAfterImpact
@@ -432,6 +441,7 @@ 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.
-- Return object.
return self
@@ -479,7 +489,7 @@ function RANGE:onafterStart()
if self.location==nil then
local text=string.format("ERROR! No range location found. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets)
self:E(RANGE.id..text)
self:E(self.id..text)
return
end
@@ -490,19 +500,19 @@ function RANGE:onafterStart()
-- Starting range.
local text=string.format("Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets)
self:I(RANGE.id..text)
self:I(self.id..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
-- Event handling.
if self.eventmoose then
-- Events are handled my MOOSE.
self:T(RANGE.id.."Events are handled by MOOSE.")
self:T(self.id.."Events are handled by MOOSE.")
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Hit)
self:HandleEvent(EVENTS.Shot)
else
-- Events are handled directly by DCS.
self:T(RANGE.id.."Events are handled directly by DCS.")
self:T(self.id.."Events are handled directly by DCS.")
world.addEventHandler(self)
end
@@ -529,6 +539,7 @@ function RANGE:onafterStart()
self.rangezone:SmokeZone(SMOKECOLOR.White)
end
self:__Status(-60)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -562,6 +573,22 @@ function RANGE:SetMessageTimeDuration(time)
return self
end
--- Automatically safe player results to disc.
-- @param #RANGE self
-- @return #RANGE self
function RANGE:SetAutosafeOn()
self.autosafe=true
return self
end
--- Switch off auto safe player results.
-- @param #RANGE self
-- @return #RANGE self
function RANGE:SetAutosafeOff()
self.autosafe=false
return self
end
--- Set messages to examiner. The examiner will receive messages from all clients.
-- @param #RANGE self
-- @param #string examinergroupname Name of the group of the examiner.
@@ -777,20 +804,20 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe
if _isstatic==true then
-- Add static object.
self:T(RANGE.id..string.format("Adding STATIC object %s as strafe target #%d.", _name, _i))
self:T(self.id..string.format("Adding STATIC object %s as strafe target #%d.", _name, _i))
unit=STATIC:FindByName(_name, false)
elseif _isstatic==false then
-- Add unit object.
self:T(RANGE.id..string.format("Adding UNIT object %s as strafe target #%d.", _name, _i))
self:T(self.id..string.format("Adding UNIT object %s as strafe target #%d.", _name, _i))
unit=UNIT:FindByName(_name)
else
-- Neither unit nor static object with this name could be found.
local text=string.format("ERROR! Could not find ANY strafe target object with name %s.", _name)
self:E(RANGE.id..text)
self:E(self.id..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
end
@@ -810,7 +837,7 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe
-- Check if at least one target could be found.
if ntargets==0 then
local text=string.format("ERROR! No strafe target could be found when calling RANGE:AddStrafePit() for range %s", self.rangename)
self:E(RANGE.id..text)
self:E(self.id..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
return
end
@@ -880,7 +907,7 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe
-- Debug info
local text=string.format("Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f", _name, ntargets, heading, l, w, goodpass, foulline)
self:T(RANGE.id..text)
self:T(self.id..text)
MESSAGE:New(text, 5):ToAllIf(self.Debug)
return self
@@ -952,14 +979,14 @@ function RANGE:AddBombingTargets(targetnames, goodhitrange, randommove)
if _isstatic==true then
local _static=STATIC:FindByName(name)
self:T2(RANGE.id..string.format("Adding static bombing target %s with hit range %d.", name, goodhitrange, false))
self:T2(self.id..string.format("Adding static bombing target %s with hit range %d.", name, goodhitrange, false))
self:AddBombingTargetUnit(_static, goodhitrange)
elseif _isstatic==false then
local _unit=UNIT:FindByName(name)
self:T2(RANGE.id..string.format("Adding unit bombing target %s with hit range %d.", name, goodhitrange, randommove))
self:T2(self.id..string.format("Adding unit bombing target %s with hit range %d.", name, goodhitrange, randommove))
self:AddBombingTargetUnit(_unit, goodhitrange)
else
self:E(RANGE.id..string.format("ERROR! Could not find bombing target %s.", name))
self:E(self.id..string.format("ERROR! Could not find bombing target %s.", name))
end
end
@@ -992,11 +1019,11 @@ function RANGE:AddBombingTargetUnit(unit, goodhitrange, randommove)
-- Debug or error output.
if _isstatic==true then
self:T(RANGE.id..string.format("Adding STATIC bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring(randommove)))
self:T(self.id..string.format("Adding STATIC bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring(randommove)))
elseif _isstatic==false then
self:T(RANGE.id..string.format("Adding UNIT bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring(randommove)))
self:T(self.id..string.format("Adding UNIT bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring(randommove)))
else
self:E(RANGE.id..string.format("ERROR! No bombing target with name %s could be found. Carefully check all UNIT and STATIC names defined in the mission editor!", name))
self:E(self.id..string.format("ERROR! No bombing target with name %s could be found. Carefully check all UNIT and STATIC names defined in the mission editor!", name))
end
-- Get max speed of unit in km/h.
@@ -1090,7 +1117,7 @@ function RANGE:GetFoullineDistance(namepit, namefoulline)
elseif _staticpit==false then
pit=UNIT:FindByName(namepit)
else
self:E(RANGE.id..string.format("ERROR! Pit object %s could not be found in GetFoullineDistance function. Check the name in the ME.", namepit))
self:E(self.id..string.format("ERROR! Pit object %s could not be found in GetFoullineDistance function. Check the name in the ME.", namepit))
end
-- Get the unit or static foul line object.
@@ -1100,7 +1127,7 @@ function RANGE:GetFoullineDistance(namepit, namefoulline)
elseif _staticfoul==false then
foul=UNIT:FindByName(namefoulline)
else
self:E(RANGE.id..string.format("ERROR! Foul line object %s could not be found in GetFoullineDistance function. Check the name in the ME.", namefoulline))
self:E(self.id..string.format("ERROR! Foul line object %s could not be found in GetFoullineDistance function. Check the name in the ME.", namefoulline))
end
-- Get the distance between the two objects.
@@ -1108,10 +1135,10 @@ function RANGE:GetFoullineDistance(namepit, namefoulline)
if pit~=nil and foul~=nil then
fouldist=pit:GetCoordinate():Get2DDistance(foul:GetCoordinate())
else
self:E(RANGE.id..string.format("ERROR! Foul line distance could not be determined. Check pit object name %s and foul line object name %s in the ME.", namepit, namefoulline))
self:E(self.id..string.format("ERROR! Foul line distance could not be determined. Check pit object name %s and foul line object name %s in the ME.", namepit, namefoulline))
end
self:T(RANGE.id..string.format("Foul line distance = %.1f m.", fouldist))
self:T(self.id..string.format("Foul line distance = %.1f m.", fouldist))
return fouldist
end
@@ -1162,12 +1189,12 @@ function RANGE:onEvent(Event)
end
-- Event info.
self:T3(RANGE.id..string.format("EVENT: Event in onEvent with ID = %s", tostring(Event.id)))
self:T3(RANGE.id..string.format("EVENT: Ini unit = %s" , tostring(EventData.IniUnitName)))
self:T3(RANGE.id..string.format("EVENT: Ini group = %s" , tostring(EventData.IniGroupName)))
self:T3(RANGE.id..string.format("EVENT: Ini player = %s" , tostring(_playername)))
self:T3(RANGE.id..string.format("EVENT: Tgt unit = %s" , tostring(EventData.TgtUnitName)))
self:T3(RANGE.id..string.format("EVENT: Wpn type = %s" , tostring(EventData.WeaponTypeName)))
self:T3(self.id..string.format("EVENT: Event in onEvent with ID = %s", tostring(Event.id)))
self:T3(self.id..string.format("EVENT: Ini unit = %s" , tostring(EventData.IniUnitName)))
self:T3(self.id..string.format("EVENT: Ini group = %s" , tostring(EventData.IniGroupName)))
self:T3(self.id..string.format("EVENT: Ini player = %s" , tostring(_playername)))
self:T3(self.id..string.format("EVENT: Tgt unit = %s" , tostring(EventData.TgtUnitName)))
self:T3(self.id..string.format("EVENT: Wpn type = %s" , tostring(EventData.WeaponTypeName)))
-- Call event Birth function.
if Event.id==world.event.S_EVENT_BIRTH and _playername then
@@ -1196,9 +1223,9 @@ function RANGE:OnEventBirth(EventData)
local _unitName=EventData.IniUnitName
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(RANGE.id.."BIRTH: unit = "..tostring(EventData.IniUnitName))
self:T3(RANGE.id.."BIRTH: group = "..tostring(EventData.IniGroupName))
self:T3(RANGE.id.."BIRTH: player = "..tostring(_playername))
self:T3(self.id.."BIRTH: unit = "..tostring(EventData.IniUnitName))
self:T3(self.id.."BIRTH: group = "..tostring(EventData.IniGroupName))
self:T3(self.id.."BIRTH: player = "..tostring(_playername))
if _unit and _playername then
@@ -1209,7 +1236,7 @@ function RANGE:OnEventBirth(EventData)
-- Debug output.
local text=string.format("Player %s, callsign %s entered unit %s (UID %d) of group %s (GID %d)", _playername, _callsign, _unitName, _uid, _group:GetName(), _gid)
self:E(RANGE.id..text)
self:T(self.id..text)
MESSAGE:New(text, 5):ToAllIf(self.Debug)
-- Reset current strafe status.
@@ -1228,6 +1255,7 @@ function RANGE:OnEventBirth(EventData)
self.PlayerSettings[_playername].messages=true
self.PlayerSettings[_playername].unitname=_unitName
self.PlayerSettings[_playername].playername=_playername
self.PlayerSettings[_playername].airframe=EventData.IniUnit:GetTypeName()
-- Start check in zone timer.
if self.planes[_uid] ~= true then
@@ -1245,9 +1273,9 @@ function RANGE:OnEventHit(EventData)
self:F({eventhit = EventData})
-- Debug info.
self:T3(RANGE.id.."HIT: Ini unit = "..tostring(EventData.IniUnitName))
self:T3(RANGE.id.."HIT: Ini group = "..tostring(EventData.IniGroupName))
self:T3(RANGE.id.."HIT: Tgt target = "..tostring(EventData.TgtUnitName))
self:T3(self.id.."HIT: Ini unit = "..tostring(EventData.IniUnitName))
self:T3(self.id.."HIT: Ini group = "..tostring(EventData.IniGroupName))
self:T3(self.id.."HIT: Tgt target = "..tostring(EventData.TgtUnitName))
-- Player info
local _unitName = EventData.IniUnitName
@@ -1295,7 +1323,7 @@ function RANGE:OnEventHit(EventData)
local _d=_currentTarget.zone.foulline
local text=string.format("%s, Invalid hit!\nYou already passed foul line distance of %d m for target %s.", self:_myname(_unitName), _d, targetname)
self:_DisplayMessageToGroup(_unit, text)
self:T2(RANGE.id..text)
self:T2(self.id..text)
_currentTarget.pastfoulline=true
end
end
@@ -1357,12 +1385,12 @@ function RANGE:OnEventShot(EventData)
local weaponcategory=desc.category
-- Debug info.
self:T(RANGE.id.."EVENT SHOT: Range "..self.rangename)
self:T(RANGE.id.."EVENT SHOT: Ini unit = "..EventData.IniUnitName)
self:T(RANGE.id.."EVENT SHOT: Ini group = "..EventData.IniGroupName)
self:T(RANGE.id.."EVENT SHOT: Weapon type = ".._weapon)
self:T(RANGE.id.."EVENT SHOT: Weapon name = ".._weaponName)
self:T(RANGE.id.."EVENT SHOT: Weapon cate = "..weaponcategory)
self:T(self.id.."EVENT SHOT: Range "..self.rangename)
self:T(self.id.."EVENT SHOT: Ini unit = "..EventData.IniUnitName)
self:T(self.id.."EVENT SHOT: Ini group = "..EventData.IniGroupName)
self:T(self.id.."EVENT SHOT: Weapon type = ".._weapon)
self:T(self.id.."EVENT SHOT: Weapon name = ".._weaponName)
self:T(self.id.."EVENT SHOT: Weapon cate = "..weaponcategory)
-- Special cases:
--local _viggen=string.match(_weapon, "ROBOT") or string.match(_weapon, "RB75") or string.match(_weapon, "BK90") or string.match(_weapon, "RB15") or string.match(_weapon, "RB04")
@@ -1387,7 +1415,7 @@ function RANGE:OnEventShot(EventData)
-- Distance player to range.
if _unit and _playername then
dPR=_unit:GetCoordinate():Get2DDistance(self.location)
self:T(RANGE.id..string.format("Range %s, player %s, player-range distance = %d km.", self.rangename, _playername, dPR/1000))
self:T(self.id..string.format("Range %s, player %s, player-range distance = %d km.", self.rangename, _playername, dPR/1000))
end
-- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons.
@@ -1397,7 +1425,7 @@ function RANGE:OnEventShot(EventData)
local playerData=self.PlayerSettings[_playername] --#RANGE.PlayerData
-- Tracking info and init of last bomb position.
self:T(RANGE.id..string.format("RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName()))
self:T(self.id..string.format("RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName()))
-- Init bomb position.
local _lastBombPos = {x=0,y=0,z=0}
@@ -1411,7 +1439,7 @@ function RANGE:OnEventShot(EventData)
return _ordnance:getPoint()
end)
self:T2(RANGE.id..string.format("Range %s: Bomb still in air: %s", self.rangename, tostring(_status)))
self:T2(self.id..string.format("Range %s: Bomb still in air: %s", self.rangename, tostring(_status)))
if _status then
----------------------------
@@ -1500,19 +1528,20 @@ function RANGE:OnEventShot(EventData)
local _results=self.bombPlayerResults[_playername]
local result={} --#RANGE.BombResult
result.name=_closetTarget.name
result.name=_closetTarget.name or "unknown"
result.distance=_distance
result.radial=_closeCoord:HeadingTo(impactcoord)
result.weapon=_weaponName
result.weapon=_weaponName or "unknown"
result.quality=_hitquality
result.player=playerData
result.player=playerData.playername
result.time=timer.getAbsTime()
result.airframe=playerData.airframe
-- Add to table.
table.insert(_results, result)
-- Call impact.
self:Impact(result)
self:Impact(result, playerData)
elseif insidezone then
@@ -1525,7 +1554,7 @@ function RANGE:OnEventShot(EventData)
end
--Terminate the timer
self:T(RANGE.id..string.format("Range %s, player %s: Terminating bomb track timer.", self.rangename, _playername))
self:T(self.id..string.format("Range %s, player %s: Terminating bomb track timer.", self.rangename, _playername))
return nil
end -- _status check
@@ -1533,7 +1562,7 @@ function RANGE:OnEventShot(EventData)
end -- end function trackBomb
-- Weapon is not yet "alife" just yet. Start timer in one second.
self:T(RANGE.id..string.format("Range %s, player %s: Tracking of weapon starts in 0.1 seconds.", self.rangename, _playername))
self:T(self.id..string.format("Range %s, player %s: Tracking of weapon starts in 0.1 seconds.", self.rangename, _playername))
timer.scheduleFunction(trackBomb, EventData.weapon, timer.getTime()+0.1)
end --if _track (string.match) and player-range distance < threshold.
@@ -1544,13 +1573,34 @@ end
-- FSM Functions
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check spawn queue and spawn aircraft if necessary.
-- @param #RANGE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function RANGE:onafterStatus(From, Event, To)
self:I(self.id..string.format("Range status: %s", self:GetState()))
--self:_CheckMissileStatus()
-- Save results.
if self.autosafe then
self:Save()
end
self:__Status(-60)
end
--- Function called after bomb impact on range.
-- @param #RANGE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #RANGE.BombResult result Result of bomb impact.
function RANGE:onafterImpact(From, Event, To, result)
-- @param #RANGE.PlayerData player
function RANGE:onafterImpact(From, Event, To, result, player)
-- Only display target name if there is more than one bomb target.
local targetname=nil
@@ -1559,7 +1609,7 @@ function RANGE:onafterImpact(From, Event, To, result)
end
-- Send message to player.
local text=string.format("%s, impact %03d° for %d ft", result.player.playername, result.radial, UTILS.MetersToFeet(result.distance))
local text=string.format("%s, impact %03d° for %d ft", player.playername, result.radial, UTILS.MetersToFeet(result.distance))
if targetname then
text=text..string.format(" from bulls of target %s.")
else
@@ -1568,7 +1618,7 @@ function RANGE:onafterImpact(From, Event, To, result)
text=text..string.format(" %s hit.", result.quality)
-- Unit.
local unit=UNIT:FindByName(result.player.unitname)
local unit=UNIT:FindByName(player.unitname)
-- Send message.
self:_DisplayMessageToGroup(unit, text, nil, true)
@@ -1576,6 +1626,156 @@ function RANGE:onafterImpact(From, Event, To, result)
end
--- Function called before save event. Checks that io and lfs are desanitized.
-- @param #RANGE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function RANGE:onbeforeSave(From, Event, To)
if io and lfs then
return true
else
self:E(self.id..string.format("WARNING: io and/or lfs not desanitized. Cannot save player results."))
return false
end
end
--- Function called after save.
-- @param #RANGE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function RANGE:onafterSave(From, Event, To)
local function _savefile(filename, data)
local f=io.open(filename, "wb")
if f then
f:write(data)
f:close()
self:I(self.id..string.format("Saving player results to file %s", tostring(filename)))
else
self:E(self.id..string.format("ERROR: Could not save results to file %s", tostring(filename)))
end
end
-- Path.
local path=lfs.writedir()
-- Set file name.
local filename=path..string.format("\\RANGE-%s_BombingResults.csv", self.rangename)
-- Header line.
local scores="Name,Pass,Target,Distance,Radial,Quality,Weapon,Airframe,Mission Time"
-- Loop over all players.
for playername,results in pairs(self.bombPlayerResults) do
-- Loop over player grades table.
for i,_result in pairs(results) do
local result=_result --#RANGE.BombResult
local distance=result.distance
local weapon=result.weapon
local target=result.name
local radial=result.radial
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)
end
end
_savefile(filename, scores)
end
--- Function called before save event. Checks that io and lfs are desanitized.
-- @param #RANGE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function RANGE:onbeforeLoad(From, Event, To)
if io and lfs then
return true
else
self:E(self.id..string.format("WARNING: io and/or lfs not desanitized. Cannot load player results."))
return false
end
end
--- On after "Load" event. Loads results of all players from file.
-- @param #RANGE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function RANGE:onafterLoad(From, Event, To)
--- Function that load data from a file.
local function _loadfile(filename)
local f=io.open(filename, "rb")
if f then
--self:I(self.id..string.format("Loading player results from file %s", tostring(filename)))
local data=f:read("*all")
f:close()
return data
else
self:E(self.id..string.format("ERROR: Could not load player results from file %s", tostring(filename)))
return nil
end
end
-- Path.
local path=lfs.writedir()
-- Set file name.
local filename=path..string.format("\\RANGE-%s_BombingResults.csv", self.rangename)
-- Info message.
local text=string.format("Loading player bomb results from file %s", filename)
self:I(self.id..text)
-- Load asset data from file.
local data=_loadfile(filename)
if data then
-- Split by line break.
local results=UTILS.Split(data,"\n")
-- Remove first header line.
table.remove(results, 1)
-- Init player scores table.
self.bombPlayerResults={}
-- Loop over all lines.
for _,_result in pairs(results) do
-- Parameters are separated by commata.
local resultdata=UTILS.Split(_result, ",")
-- Grade table
local result={} --#RANGE.BombResult
-- Player name.
local playername=resultdata[1]
result.player=playername
-- Results data.
result.name=tostring(resultdata[3])
result.distance=tonumber(resultdata[4])
result.radial=tonumber(resultdata[5])
result.quality=tostring(resultdata[6])
result.weapon=tostring(resultdata[7])
result.airframe=tostring(resultdata[8])
result.time=UTILS.ClockToSeconds(resultdata[9] or "00:00:00")
-- Create player array if necessary.
self.bombPlayerResults[playername]=self.bombPlayerResults[playername] or {}
-- Add result to table.
table.insert(self.bombPlayerResults[playername], result)
end
end
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Display Messages
@@ -1731,24 +1931,22 @@ function RANGE:_DisplayMyBombingResults(_unitName)
-- Loop over results.
local _bestMsg = ""
local _count = 1
for _,_result in pairs(_results) do
for i,_result in pairs(_results) do
local result=_result --#RANGE.BombResult
-- Message with name, weapon and distance.
_message = _message.."\n"..string.format("[%d] %d m - %s - %s - %s hit", _count, _result.distance, _result.name, _result.weapon, _result.quality)
_message = _message.."\n"..string.format("[%d] %d m %03d° - %s - %s - %s hit", i, result.distance, result.radial, result.name, result.weapon, result.quality)
-- Store best/first result.
if _bestMsg == "" then
_bestMsg = string.format("%d m - %s - %s - %s hit",_result.distance,_result.name,_result.weapon, _result.quality)
_bestMsg = string.format("%d m %03d° - %s - %s - %s hit", result.distance, result.radial, result.name, result.weapon, result.quality)
end
-- Best 10 runs only.
if _count == self.ndisplayresult then
if i==self.ndisplayresult then
break
end
-- Increase counter.
_count = _count+1
end
-- Message.
@@ -1897,7 +2095,7 @@ function RANGE:_DisplayRangeInfo(_unitname)
self:_DisplayMessageToGroup(unit, text, nil, true, true)
-- Debug output.
self:T2(RANGE.id..text)
self:T2(self.id..text)
end
end
end
@@ -1970,7 +2168,7 @@ function RANGE:_DisplayStrafePits(_unitname)
end
local mycoord=coord:ToStringA2G(_unit, _settings)
_text=_text..string.format("\n- %s: %s - heading %03d",_strafepit.name, mycoord, heading)
_text=_text..string.format("\n- %s: %s - heading %03d°",_strafepit.name, mycoord, heading)
end
self:_DisplayMessageToGroup(_unit,_text, nil, true, true)
@@ -2038,9 +2236,9 @@ function RANGE:_DisplayRangeWeather(_unitname)
self:_DisplayMessageToGroup(unit, text, nil, true, true)
-- Debug output.
self:T2(RANGE.id..text)
self:T2(self.id..text)
else
self:T(RANGE.id..string.format("ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname))
self:T(self.id..string.format("ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname))
end
end
@@ -2080,7 +2278,7 @@ function RANGE:_CheckInZone(_unitName)
-- Debug output
local text=string.format("Checking still in zone. Unit = %s, player = %s in zone = %s. alt = %d, delta heading = %d", _unitName, _playername, tostring(unitinzone), unitalt, deltaheading)
self:T2(RANGE.id..text)
self:T2(self.id..text)
-- Check if player is in strafe zone and below max alt.
if unitinzone then
@@ -2171,7 +2369,7 @@ function RANGE:_CheckInZone(_unitName)
-- Debug info.
local text=string.format("Checking zone %s. Unit = %s, player = %s in zone = %s. alt = %d, delta heading = %d", _targetZone.name, _unitName, _playername, tostring(unitinzone), unitalt, deltaheading)
self:T2(RANGE.id..text)
self:T2(self.id..text)
-- Player is inside zone.
if unitinzone then
@@ -2296,10 +2494,10 @@ function RANGE:_AddF10Commands(_unitName)
missionCommands.addCommandForGroup(_gid, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName)
end
else
self:T(RANGE.id.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName)
self:T(self.id.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName)
end
else
self:T(RANGE.id.."Player unit does not exist in AddF10Menu() function. Unit name: ".._unitName)
self:T(self.id.."Player unit does not exist in AddF10Menu() function. Unit name: ".._unitName)
end
end
@@ -2368,7 +2566,7 @@ function RANGE:_GetAmmo(unitname)
if ammotable ~= nil then
local weapons=#ammotable
self:T2(RANGE.id..string.format("Number of weapons %d.", weapons))
self:T2(self.id..string.format("Number of weapons %d.", weapons))
for w=1,weapons do
@@ -2382,11 +2580,11 @@ function RANGE:_GetAmmo(unitname)
ammo=ammo+Nammo
local text=string.format("Player %s has %d rounds ammo of type %s", playername, Nammo, Tammo)
self:T(RANGE.id..text)
self:T(self.id..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
else
local text=string.format("Player %s has %d ammo of type %s", playername, Nammo, Tammo)
self:T(RANGE.id..text)
self:T(self.id..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
end
end
@@ -2797,20 +2995,20 @@ function RANGE:_CheckStatic(name)
-- If static is not yet in MOOSE DB, we add it. Can happen for cargo statics!
if not _MOOSEstatic then
self:T(RANGE.id..string.format("Adding DCS static to MOOSE database. Name = %s.", name))
self:T(self.id..string.format("Adding DCS static to MOOSE database. Name = %s.", name))
_DATABASE:AddStatic(name)
end
return true
else
self:T3(RANGE.id..string.format("No static object with name %s exists.", name))
self:T3(self.id..string.format("No static object with name %s exists.", name))
end
-- Check if a unit has this name.
if UNIT:FindByName(name) then
return false
else
self:T3(RANGE.id..string.format("No unit object with name %s exists.", name))
self:T3(self.id..string.format("No unit object with name %s exists.", name))
end
-- If not unit or static exist, we return nil.
+34 -7
View File
@@ -227,6 +227,7 @@
-- @field #string trappath Path where to save the trap sheets.
-- @field #string trapprefix File prefix for trap sheet files.
-- @field #number initialmaxalt Max altitude in meters to register in the inital zone.
-- @field #boolean welcome If true, display welcome message to player.
-- @extends Core.Fsm#FSM
--- Be the boss!
@@ -1225,6 +1226,7 @@ AIRBOSS = {
trappath = nil,
trapprefix = nil,
initialmaxalt = nil,
welcome = nil,
}
--- Aircraft types capable of landing on carrier (human+AI).
@@ -1671,7 +1673,7 @@ AIRBOSS.MenuF10Root=nil
--- Airboss class version.
-- @field #string version
AIRBOSS.version="0.9.9.9"
AIRBOSS.version="1.0.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -1890,6 +1892,9 @@ function AIRBOSS:New(carriername, alias)
self:SetMenuSmokeZones()
self:SetMenuSingleCarrier(false)
-- Welcome players.
self:SetWelcomePlayers(true)
-- Init carrier parameters.
if self.carriertype==AIRBOSS.CarrierType.STENNIS then
self:_InitStennis()
@@ -2224,6 +2229,18 @@ end
-- USER API Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set welcome messages for players.
-- @param #AIRBOSS self
-- @param #boolean switch If true, display welcome message to player.
-- @return #AIRBOSS self
function AIRBOSS:SetWelcomePlayers(switch)
self.welcome=switch
return self
end
--- Set carrier controlled area (CCA).
-- This is a large zone around the carrier, which is constantly updated wrt the carrier position.
-- @param #AIRBOSS self
@@ -7126,7 +7143,9 @@ function AIRBOSS:_NewPlayer(unitname)
self.playerscores[playername]=self.playerscores[playername] or {}
-- Welcome player message.
self:MessageToPlayer(playerData, string.format("Welcome, %s %s!", playerData.difficulty, playerData.name), string.format("AIRBOSS %s", self.alias), "", 5)
if self.welcome then
self:MessageToPlayer(playerData, string.format("Welcome, %s %s!", playerData.difficulty, playerData.name), string.format("AIRBOSS %s", self.alias), "", 5)
end
end
@@ -17071,15 +17090,11 @@ function AIRBOSS:onafterSave(From, Event, To, path, filename)
filename=path.."\\"..filename
end
-- Info
local text=string.format("Saving player LSO grades to file %s", filename)
MESSAGE:New(text,30):ToAllIf(self.Debug)
self:I(self.lid..text)
-- Header line
local scores="Name,Pass,Points Final,Points Pass,Grade,Details,Wire,Tgroove,Case,Wind,Modex,Airframe,Carrier Type,Carrier Name,Theatre,Mission Time,Mission Date,OS Date\n"
-- Loop over all players.
local n=0
for playername,grades in pairs(self.playerscores) do
-- Loop over player grades table.
@@ -17106,9 +17121,14 @@ function AIRBOSS:onafterSave(From, Event, To, path, filename)
scores=scores..string.format("%s,%d,%s,%.1f,%s,%s,%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
playername, i, finalscore, grade.points, grade.grade, grade.details, wire, Tgroove, grade.case,
grade.wind, grade.modex, grade.airframe, grade.carriertype, grade.carriername, grade.theatre, grade.mitime, grade.midate, grade.osdate)
n=n+1
end
end
-- Info
local text=string.format("Saving %d player LSO grades to file %s", n, filename)
self:I(self.lid..text)
-- Save file.
_savefile(filename, scores)
end
@@ -17219,6 +17239,7 @@ function AIRBOSS:onafterLoad(From, Event, To, path, filename)
self.playerscores={}
-- Loop over all lines.
local n=0
for _,gradeline in pairs(playergrades) do
-- Parameters are separated by commata.
@@ -17264,10 +17285,16 @@ function AIRBOSS:onafterLoad(From, Event, To, path, filename)
-- Add grade to table.
table.insert(self.playerscores[playername], grade)
n=n+1
-- Debug info.
self:T2({playername, self.playerscores[playername]})
end
-- Info message.
local text=string.format("Loaded %d player LSO grades from file %s", n, filename)
self:I(self.lid..text)
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------