2024-11-12 14:18:28 -06:00

1885 lines
58 KiB
Lua

-- CSAR Script for DCS Ciribob - 2015
-- Version 1.9.2 - 23/04/2018
-- DCS 1.5 Compatible - Needs Mist 4.0.55 or higher!
--
-- 4 Options:
-- 0 - No Limit - NO Aircraft disabling or pilot lives
-- 1 - Disable Aircraft when its down - Timeout to reenable aircraft
-- 2 - Disable Aircraft for Pilot when he's shot down -- timeout to reenable pilot for aircraft
-- 3 - Pilot Life Limit - No Aircraft Disabling
csar = {}
-- SETTINGS FOR MISSION DESIGNER vvvvvvvvvvvvvvvvvv
csar.csarUnits = {
"UH-1H Ford 1-1",
"UH-1H Ford 1-2",
"UH-1H Ford 1-3",
"UH-1H Ford 1-4",
"Mi-8MTV2 Ford 4-1",
"Mi-8MTV2 Ford 4-2",
"Mi-8MTV2 Ford 4-3",
"Mi-8MTV2 Ford 4-4",
"SA342M Ford 2-1",
"SA342M Ford 2-2",
"SA342Minigun Ford 2-3",
"SA342Minigun Ford 2-4",
"SA342 Mistral Ford 3-1",
"SA342 Mistral Ford 3-2",
"SA342L Ford 3-3",
"SA342L Ford 3-4",
"UH-1H Ford 6-1",
"UH-1H Ford 6-2",
"Mi-8MTV2 Ford 7-1",
"Mi-8MTV2 Ford 7-2",
"SA342M Ford 9-1",
"SA342M Ford 9-2",
"SA342 Mistral Ford 9-3",
"SA342L Ford 9-4",
"SA342Minigun Ford 9-5",
"TestBird",
} -- List of all the MEDEVAC _UNIT NAMES_ (the line where it says "Pilot" in the ME)!
csar.bluemash = {
"Unit #162",
"BlueMASH #2",
"BlueMASH #3",
"BlueMASH #4",
"BlueMASH #5",
"BlueMASH #6",
"BlueMASH #7",
"BlueMASH #8",
"BlueMASH #9",
"BlueMASH #10"
} -- The unit that serves as MASH for the blue side
csar.redmash = {
"RedMASH #1",
"RedMASH #2",
"RedMASH #3",
"RedMASH #4",
"RedMASH #5",
"RedMASH #6",
"RedMASH #7",
"RedMASH #8",
"RedMASH #9",
"RedMASH #10"
} -- The unit that serves as MASH for the red side
csar.csarMode = 0
-- 0 - No Limit - NO Aircraft disabling
-- 1 - Disable Aircraft when its down - Timeout to reenable aircraft
-- 2 - Disable Aircraft for Pilot when he's shot down -- timeout to reenable pilot for aircraft
-- 3 - Pilot Life Limit - No Aircraft Disabling -- timeout to reset lives?
csar.maxLives = 8 -- Maximum pilot lives
csar.countCSARCrash = false -- If you set to true, pilot lives count for CSAR and CSAR aircraft will count.
csar.reenableIfCSARCrashes = true -- If a CSAR heli crashes, the pilots are counted as rescued anyway. Set to false to Stop this
-- - I recommend you leave the option on below IF USING MODE 1 otherwise the
-- aircraft will be disabled for the duration of the mission
csar.disableAircraftTimeout = true -- Allow aircraft to be used after 20 minutes if the pilot isnt rescued
csar.disableTimeoutTime = 20 -- Time in minutes for TIMEOUT
csar.destructionHeight = 150 -- height in meters an aircraft will be destroyed at if the aircraft is disabled
csar.enableForAI = false -- set to false to disable AI units from being rescued.
csar.enableForRED = true -- enable for red side
csar.enableForBLUE = true -- enable for blue side
csar.enableSlotBlocking = false -- if set to true, you need to put the csarSlotBlockGameGUI.lua
-- in C:/Users/<YOUR USERNAME>/DCS/Scripts for 1.5 or C:/Users/<YOUR USERNAME>/DCS.openalpha/Scripts for 2.0
-- For missions using FLAGS and this script, the CSAR flags will NOT interfere with your mission :)
csar.bluesmokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue
csar.redsmokecolor = 1 -- Color of smokemarker for red side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue
csar.requestdelay = 2 -- Time in seconds before the survivors will request Medevac
csar.coordtype = 3 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates.
csar.coordaccuracy = 1 -- Precision of the reported coordinates, see MIST-docs at http://wiki.hoggit.us/view/GetMGRSString
-- only applies to _non_ bullseye coords
csar.immortalcrew = true -- Set to true to make wounded crew immortal
csar.invisiblecrew = true -- Set to true to make wounded crew insvisible
csar.messageTime = 30 -- Time to show the intial wounded message for in seconds
csar.loadDistance = 60 -- configure distance for pilot to get in helicopter in meters.
csar.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK!
csar.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase
-- SETTINGS FOR MISSION DESIGNER ^^^^^^^^^^^^^^^^^^^*
-- ***************************************************************
-- **************** Mission Editor Functions *********************
-- ***************************************************************
-----------------------------------------------------------------
-- Resets all life limits so everyone can spawn again. Usage:
-- csar.resetAllPilotLives()
--
function csar.resetAllPilotLives()
for x, _pilot in pairs(csar.pilotLives) do
trigger.action.setUserFlag("CSAR_PILOT" .. _pilot:gsub('%W', ''), csar.maxLives + 1)
end
csar.pilotLives = {}
env.info("Pilot Lives Reset!")
end
-----------------------------------------------------------------
-- Resets all life limits so everyone can spawn again. Usage:
-- csar.resetAllPilotLives()
--
function csar.resetPilotLife(_playerName)
csar.pilotLives[_playerName] = nil
trigger.action.setUserFlag("CSAR_PILOT" .. _playerName:gsub('%W', ''), csar.maxLives + 1)
env.info("Pilot life Reset!")
end
-- ***************************************************************
-- **************** BE CAREFUL BELOW HERE ************************
-- ***************************************************************
-- Sanity checks of mission designer
assert(mist ~= nil, "\n\n** HEY MISSION-DESIGNER! **\n\nMiST has not been loaded!\n\nMake sure MiST 4.0.57 or higher is running\n*before* running this script!\n")
csar.addedTo = {}
csar.downedPilotCounterRed = 0
csar.downedPilotCounterBlue = 0
csar.woundedGroups = {} -- contains the new group of units
csar.inTransitGroups = {} -- contain a table for each SAR with all units he has with the
-- original name of the killed group
csar.radioBeacons = {}
csar.smokeMarkers = {} -- tracks smoke markers for groups
csar.heliVisibleMessage = {} -- tracks if the first message has been sent of the heli being visible
csar.heliCloseMessage = {} -- tracks heli close message ie heli < 500m distance
csar.radioBeacons = {} -- all current beacons
csar.max_units = 6 --number of pilots that can be carried
csar.currentlyDisabled = {} --stored disabled aircraft
csar.hoverStatus = {} -- tracks status of a helis hover above a downed pilot
csar.pilotDisabled = {} -- tracks what aircraft a pilot is disabled for
csar.pilotLives = {} -- tracks how many lives a pilot has
csar.takenOff = {}
function csar.tableLength(T)
if T == nil then
return 0
end
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end
function csar.pilotsOnboard(_heliName)
local count = 0
if csar.inTransitGroups[_heliName] then
for _, _group in pairs(csar.inTransitGroups[_heliName]) do
count = count + 1
end
end
return count
end
-- Handles all world events
csar.eventHandler = {}
function csar.eventHandler:onEvent(_event)
local status, err = pcall(function(_event)
if _event == nil or _event.initiator == nil then
return false
elseif _event.id == 3 then -- taken offf
if _event.initiator:getName() then
csar.takenOff[_event.initiator:getName()] = true
end
return true
elseif _event.id == 15 then --player entered unit
if _event.initiator:getName() then
csar.takenOff[_event.initiator:getName()] = nil
end
-- if its a sar heli, re-add check status script
for _, _heliName in pairs(csar.csarUnits) do
if _heliName == _event.initiator:getName() then
-- add back the status script
for _woundedName, _groupInfo in pairs(csar.woundedGroups) do
if _groupInfo.side == _event.initiator:getCoalition() then
--env.info(string.format("Schedule Respawn %s %s",_heliName,_woundedName))
-- queue up script
-- Schedule timer to check when to pop smoke
timer.scheduleFunction(csar.checkWoundedGroupStatus, { _heliName, _woundedName }, timer.getTime() + 5)
end
end
end
end
if _event.initiator:getName() and _event.initiator:getPlayerName() then
env.info("Checking Unit - " .. _event.initiator:getName())
csar.checkDisabledAircraftStatus({ _event.initiator:getName(), _event.initiator:getPlayerName() })
end
return true
elseif (_event.id == 9) then
-- Pilot dead
env.info("Event unit - Pilot Dead")
local _unit = _event.initiator
if _unit == nil then
return -- error!
end
local _coalition = _unit:getCoalition()
if _coalition == 1 and not csar.enableForRED then
return --ignore!
end
if _coalition == 2 and not csar.enableForBLUE then
return --ignore!
end
-- Catch multiple events here?
if csar.takenOff[_event.initiator:getName()] == true or _unit:inAir() then
if csar.doubleEjection(_unit) then
return
end
trigger.action.outTextForCoalition(_unit:getCoalition(), "MAYDAY MAYDAY! " .. _unit:getTypeName() .. " shot down. No Chute!", 10)
csar.handleEjectOrCrash(_unit, true)
else
env.info("Pilot Hasnt taken off, ignore")
end
return
elseif world.event.S_EVENT_EJECTION == _event.id then
env.info("Event unit - Pilot Ejected")
local _unit = _event.initiator
if _unit == nil then
return -- error!
end
local _coalition = _unit:getCoalition()
if _coalition == 1 and not csar.enableForRED then
return --ignore!
end
if _coalition == 2 and not csar.enableForBLUE then
return --ignore!
end
-- TODO catch ejection on runway?
if csar.enableForAI == false and _unit:getPlayerName() == nil then
return
end
if csar.takenOff[_event.initiator:getName()] ~= true and not _unit:inAir() then
env.info("Pilot Hasnt taken off, ignore")
return -- give up, pilot hasnt taken off
end
if csar.doubleEjection(_unit) then
return
end
local _spawnedGroup = csar.spawnGroup(_unit)
csar.addSpecialParametersToGroup(_spawnedGroup)
trigger.action.outTextForCoalition(_unit:getCoalition(), "MAYDAY MAYDAY! " .. _unit:getTypeName() .. " shot down. Chute Spotted!", 10)
local _freq = csar.generateADFFrequency()
csar.addBeaconToGroup(_spawnedGroup:getName(), _freq)
--handle lives and plane disabling
csar.handleEjectOrCrash(_unit, false)
-- Generate DESCRIPTION text
local _text = " "
if _unit:getPlayerName() ~= nil then
_text = "Pilot " .. _unit:getPlayerName() .. " of " .. _unit:getName() .. " - " .. _unit:getTypeName()
else
_text = "AI Pilot of " .. _unit:getName() .. " - " .. _unit:getTypeName()
end
csar.woundedGroups[_spawnedGroup:getName()] = { side = _spawnedGroup:getCoalition(), originalUnit = _unit:getName(), frequency = _freq, desc = _text, player = _unit:getPlayerName() }
csar.initSARForPilot(_spawnedGroup, _freq)
return true
elseif world.event.S_EVENT_LAND == _event.id then
if _event.initiator:getName() then
csar.takenOff[_event.initiator:getName()] = nil
end
if csar.allowFARPRescue then
--env.info("Landing")
local _unit = _event.initiator
if _unit == nil then
env.info("Unit Nil on Landing")
return -- error!
end
csar.takenOff[_event.initiator:getName()] = nil
local _place = _event.place
if _place == nil then
env.info("Landing Place Nil")
return -- error!
end
-- Coalition == 3 seems to be a bug... unless it means contested?!
if _place:getCoalition() == _unit:getCoalition() or _place:getCoalition() == 0 or _place:getCoalition() == 3 then
csar.rescuePilots(_unit)
--env.info("Rescued")
-- env.info("Rescued by Landing")
else
-- env.info("Cant Rescue ")
env.info(string.format("airfield %d, unit %d", _place:getCoalition(), _unit:getCoalition()))
end
end
return true
end
end, _event)
if (not status) then
env.error(string.format("Error while handling event %s", err), false)
end
end
csar.lastCrash = {}
function csar.doubleEjection(_unit)
if csar.lastCrash[_unit:getName()] then
local _time = csar.lastCrash[_unit:getName()]
if timer.getTime() - _time < 10 then
env.info("Caught double ejection!")
return true
end
end
csar.lastCrash[_unit:getName()] = timer.getTime()
return false
end
function csar.handleEjectOrCrash(_unit, _crashed)
-- disable aircraft for ALL pilots
if csar.csarMode == 1 then
if csar.currentlyDisabled[_unit:getName()] ~= nil then
return --already ejected once!
end
-- --mark plane as broken and unflyable
if _unit:getPlayerName() ~= nil and csar.currentlyDisabled[_unit:getName()] == nil then
if csar.countCSARCrash == false then
for _, _heliName in pairs(csar.csarUnits) do
if _unit:getName() == _heliName then
-- IGNORE Crashed CSAR
return
end
end
end
csar.currentlyDisabled[_unit:getName()] = { timeout = (csar.disableTimeoutTime * 60) + timer.getTime(), desc = "", noPilot = _crashed, unitId = _unit:getID(), name = _unit:getName() }
-- disable aircraft
trigger.action.setUserFlag("CSAR_AIRCRAFT" .. _unit:getID(), 100)
env.info("Unit Disabled: " .. _unit:getName() .. " ID:" .. _unit:getID())
end
elseif csar.csarMode == 2 then -- disable aircraft for pilot
--csar.pilotDisabled
if _unit:getPlayerName() ~= nil and csar.pilotDisabled[_unit:getPlayerName() .. "_" .. _unit:getName()] == nil then
if csar.countCSARCrash == false then
for _, _heliName in pairs(csar.csarUnits) do
if _unit:getName() == _heliName then
-- IGNORE Crashed CSAR
return
end
end
end
csar.pilotDisabled[_unit:getPlayerName() .. "_" .. _unit:getName()] = { timeout = (csar.disableTimeoutTime * 60) + timer.getTime(), desc = "", noPilot = true, unitId = _unit:getID(), player = _unit:getPlayerName(), name = _unit:getName() }
-- disable aircraft
-- strip special characters from name gsub('%W','')
trigger.action.setUserFlag("CSAR_AIRCRAFT" .. _unit:getPlayerName():gsub('%W', '') .. "_" .. _unit:getID(), 100)
env.info("Unit Disabled for player : " .. _unit:getName())
end
elseif csar.csarMode == 3 then -- No Disable - Just reduce player lives
--csar.pilotDisabled
if _unit:getPlayerName() ~= nil then
if csar.countCSARCrash == false then
for _, _heliName in pairs(csar.csarUnits) do
if _unit:getName() == _heliName then
-- IGNORE Crashed CSAR
return
end
end
end
local _lives = csar.pilotLives[_unit:getPlayerName()]
if _lives == nil then
_lives = csar.maxLives + 1 --plus 1 because we'll use flag set to 1 to indicate NO MORE LIVES
end
csar.pilotLives[_unit:getPlayerName()] = _lives - 1
trigger.action.setUserFlag("CSAR_PILOT" .. _unit:getPlayerName():gsub('%W', ''), _lives - 1)
end
end
end
function csar.enableAircraft(_name, _playerName)
-- enable aircraft for ALL pilots
if csar.csarMode == 1 then
local _details = csar.currentlyDisabled[_name]
if _details ~= nil then
csar.currentlyDisabled[_name] = nil -- {timeout = (csar.disableTimeoutTime*60) + timer.getTime(),desc="",noPilot = _crashed,unitId=_unit:getID() }
--use flag to reenable
trigger.action.setUserFlag("CSAR_AIRCRAFT" .. _details.unitId, 0)
end
elseif csar.csarMode == 2 and _playerName ~= nil then -- enable aircraft for pilot
local _details = csar.pilotDisabled[_playerName .. "_" .. _name]
if _details ~= nil then
csar.pilotDisabled[_playerName .. "_" .. _name] = nil
trigger.action.setUserFlag("CSAR_AIRCRAFT" .. _playerName:gsub('%W', '') .. "_" .. _details.unitId, 0)
end
elseif csar.csarMode == 3 and _playerName ~= nil then -- No Disable - Just reduce player lives
-- give back life
local _lives = csar.pilotLives[_playerName]
if _lives == nil then
_lives = csar.maxLives + 1 --plus 1 because we'll use flag set to 1 to indicate NO MORE LIVES
else
_lives = _lives + 1 -- give back live!
if csar.maxLives + 1 <= _lives then
_lives = csar.maxLives + 1 --plus 1 because we'll use flag set to 1 to indicate NO MORE LIVES
end
end
csar.pilotLives[_playerName] = _lives
trigger.action.setUserFlag("CSAR_PILOT" .. _playerName:gsub('%W', ''), _lives)
end
end
function csar.reactivateAircraft()
timer.scheduleFunction(csar.reactivateAircraft, nil, timer.getTime() + 5)
-- disable aircraft for ALL pilots
if csar.csarMode == 1 then
for _unitName, _details in pairs(csar.currentlyDisabled) do
if timer.getTime() >= _details.timeout then
csar.enableAircraft(_unitName)
end
end
elseif csar.csarMode == 2 then -- disable aircraft for pilot
for _key, _details in pairs(csar.pilotDisabled) do
if timer.getTime() >= _details.timeout then
csar.enableAircraft(_details.name, _details.player)
end
end
elseif csar.csarMode == 3 then -- No Disable - Just reduce player lives
end
end
function csar.checkDisabledAircraftStatus(_args)
local _name = _args[1]
local _playerName = _args[2]
local _unit = Unit.getByName(_name)
--if its not the same user anymore, stop checking
if _unit ~= nil and _unit:getPlayerName() ~= nil and _playerName == _unit:getPlayerName() then
-- disable aircraft for ALL pilots
if csar.csarMode == 1 then
local _details = csar.currentlyDisabled[_unit:getName()]
if _details ~= nil then
local _time = _details.timeout - timer.getTime()
if _details.noPilot then
if csar.disableAircraftTimeout then
local _text = string.format("This aircraft cannot be flow as the pilot was killed in a crash. Reinforcements in %.2dM,%.2dS\n\nIt will be DESTROYED on takeoff!", (_time / 60), _time % 60)
--display message,
csar.displayMessageToSAR(_unit, _text, 10, true)
else
--display message,
csar.displayMessageToSAR(_unit, "This aircraft cannot be flown again as the pilot was killed in a crash\n\nIt will be DESTROYED on takeoff!", 10, true)
end
else
if csar.disableAircraftTimeout then
--display message,
csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued or reinforcements arrive before this aircraft can be flown again! Reinforcements in " .. string.format("%.2dM,%.2d", (_time / 60), _time % 60) .. "\n\nIt will be DESTROYED on takeoff!", 10, true)
else
--display message,
csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued before this aircraft can be flown again!\n\nIt will be DESTROYED on takeoff!", 10, true)
end
end
if csar.destroyUnit(_unit) then
return --plane destroyed
else
--check again in 10 seconds
timer.scheduleFunction(csar.checkDisabledAircraftStatus, _args, timer.getTime() + 10)
end
end
elseif csar.csarMode == 2 then -- disable aircraft for pilot
local _details = csar.pilotDisabled[_unit:getPlayerName() .. "_" .. _unit:getName()]
if _details ~= nil then
local _time = _details.timeout - timer.getTime()
if _details.noPilot then
if csar.disableAircraftTimeout then
local _text = string.format("This aircraft cannot be flow as the pilot was killed in a crash. Reinforcements in %.2dM,%.2dS\n\nIt will be DESTROYED on takeoff!", (_time / 60), _time % 60)
--display message,
csar.displayMessageToSAR(_unit, _text, 10, true)
else
--display message,
csar.displayMessageToSAR(_unit, "This aircraft cannot be flown again as the pilot was killed in a crash\n\nIt will be DESTROYED on takeoff!", 10, true)
end
else
if csar.disableAircraftTimeout then
--display message,
csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued or reinforcements arrive before this aircraft can be flown again! Reinforcements in " .. string.format("%.2dM,%.2d", (_time / 60), _time % 60) .. "\n\nIt will be DESTROYED on takeoff!", 10, true)
else
--display message,
csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued before this aircraft can be flown again!\n\nIt will be DESTROYED on takeoff!", 10, true)
end
end
if csar.destroyUnit(_unit) then
return --plane destroyed
else
--check again in 10 seconds
timer.scheduleFunction(csar.checkDisabledAircraftStatus, _args, timer.getTime() + 10)
end
end
elseif csar.csarMode == 3 then -- No Disable - Just reduce player lives
local _lives = csar.pilotLives[_unit:getPlayerName()]
if _lives == nil or _lives > 1 then
if _lives == nil then
_lives = csar.maxLives + 1
end
-- -1 for lives as we use 1 to indicate out of lives!
local _text = string.format("CSAR ACTIVE! \n\nYou have " .. (_lives - 1) .. " lives remaining. Make sure you eject!")
csar.displayMessageToSAR(_unit, _text, 20, true)
return
else
local _text = string.format("You have run out of LIVES! Lives will be reset on mission restart or when your pilot is rescued.\n\nThis aircraft will be DESTROYED on takeoff!")
--display message,
csar.displayMessageToSAR(_unit, _text, 10, true)
if csar.destroyUnit(_unit) then
return --plane destroyed
else
--check again in 10 seconds
timer.scheduleFunction(csar.checkDisabledAircraftStatus, _args, timer.getTime() + 10)
end
end
end
end
end
function csar.destroyUnit(_unit)
--destroy if the SAME player is still in the aircraft
-- if a new player got in it'll be destroyed in a bit anyways
if _unit ~= nil and _unit:getPlayerName() ~= nil then
if csar.heightDiff(_unit) > csar.destructionHeight then
csar.displayMessageToSAR(_unit, "**** Aircraft Destroyed as the pilot needs to be rescued or you have no lives! ****", 10, true)
--if we're off the ground then explode
trigger.action.explosion(_unit:getPoint(), 100);
return true
end
--_unit:destroy() destroy doesnt work for playes who arent the host in multiplayer
end
return false
end
function csar.heightDiff(_unit)
local _point = _unit:getPoint()
return _point.y - land.getHeight({ x = _point.x, y = _point.z })
end
csar.addBeaconToGroup = function(_woundedGroupName, _freq)
local _group = Group.getByName(_woundedGroupName)
if _group == nil then
--return frequency to pool of available
for _i, _current in ipairs(csar.usedVHFFrequencies) do
if _current == _freq then
table.insert(csar.freeVHFFrequencies, _freq)
table.remove(csar.usedVHFFrequencies, _i)
end
end
return
end
local _sound = "l10n/DEFAULT/" .. csar.radioSound
trigger.action.radioTransmission(_sound, _group:getUnit(1):getPoint(), 0, false, _freq, 1000)
timer.scheduleFunction(csar.refreshRadioBeacon, { _woundedGroupName, _freq }, timer.getTime() + 30)
end
csar.refreshRadioBeacon = function(_args)
csar.addBeaconToGroup(_args[1], _args[2])
end
csar.addSpecialParametersToGroup = function(_spawnedGroup)
-- Immortal code for alexej21
local _setImmortal = {
id = 'SetImmortal',
params = {
value = true
}
}
-- invisible to AI, Shagrat
local _setInvisible = {
id = 'SetInvisible',
params = {
value = true
}
}
local _controller = _spawnedGroup:getController()
if (csar.immortalcrew) then
Controller.setCommand(_controller, _setImmortal)
end
if (csar.invisiblecrew) then
Controller.setCommand(_controller, _setInvisible)
end
end
function csar.spawnGroup(_deadUnit)
local _id = mist.getNextGroupId()
local _groupName = "Downed Pilot #" .. _id
local _side = _deadUnit:getCoalition()
local _pos = _deadUnit:getPoint()
local _group = {
["visible"] = false,
["groupId"] = _id,
["hidden"] = false,
["units"] = {},
["name"] = _groupName,
["task"] = {},
}
if _side == 2 then
_group.units[1] = csar.createUnit(_pos.x + 50, _pos.z + 50, 120, "Soldier M4")
else
_group.units[1] = csar.createUnit(_pos.x + 50, _pos.z + 50, 120, "Infantry AK")
end
_group.category = Group.Category.GROUND;
_group.country = _deadUnit:getCountry();
local _spawnedGroup = Group.getByName(mist.dynAdd(_group).name)
-- Turn off AI
trigger.action.setGroupAIOff(_spawnedGroup)
return _spawnedGroup
end
function csar.createUnit(_x, _y, _heading, _type)
local _id = mist.getNextUnitId();
local _name = string.format("Wounded Pilot #%s", _id)
local _newUnit = {
["y"] = _y,
["type"] = _type,
["name"] = _name,
["unitId"] = _id,
["heading"] = _heading,
["playerCanDrive"] = false,
["skill"] = "Excellent",
["x"] = _x,
}
return _newUnit
end
function csar.initSARForPilot(_downedGroup, _freq)
local _leader = _downedGroup:getUnit(1)
local _coordinatesText = csar.getPositionOfWounded(_downedGroup)
local
_text = string.format("%s requests SAR at %s, beacon at %.2f KHz",
_leader:getName(), _coordinatesText, _freq / 1000)
local _randPercent = math.random(1, 100)
-- Loop through all the medevac units
for x, _heliName in pairs(csar.csarUnits) do
local _status, _err = pcall(function(_args)
local _unitName = _args[1]
local _woundedSide = _args[2]
local _medevacText = _args[3]
local _leaderPos = _args[4]
local _groupName = _args[5]
local _group = _args[6]
local _heli = csar.getSARHeli(_unitName)
-- queue up for all SAR, alive or dead, we dont know the side if they're dead or not spawned so check
--coalition in scheduled smoke
if _heli ~= nil then
-- Check coalition side
if (_woundedSide == _heli:getCoalition()) then
-- Display a delayed message
timer.scheduleFunction(csar.delayedHelpMessage, { _unitName, _medevacText, _groupName }, timer.getTime() + csar.requestdelay)
-- Schedule timer to check when to pop smoke
timer.scheduleFunction(csar.checkWoundedGroupStatus, { _unitName, _groupName }, timer.getTime() + 1)
end
else
--env.warning(string.format("Medevac unit %s not active", _heliName), false)
-- Schedule timer for Dead unit so when the unit respawns he can still pickup units
--timer.scheduleFunction(medevac.checkStatus, {_unitName,_groupName}, timer.getTime() + 5)
end
end, { _heliName, _leader:getCoalition(), _text, _leader:getPoint(), _downedGroup:getName(), _downedGroup })
if (not _status) then
env.warning(string.format("Error while checking with medevac-units %s", _err))
end
end
end
function csar.checkWoundedGroupStatus(_argument)
local _status, _err = pcall(function(_args)
local _heliName = _args[1]
local _woundedGroupName = _args[2]
local _woundedGroup = csar.getWoundedGroup(_woundedGroupName)
local _heliUnit = csar.getSARHeli(_heliName)
-- if wounded group is not here then message alread been sent to SARs
-- stop processing any further
if csar.woundedGroups[_woundedGroupName] == nil then
return
end
if _heliUnit == nil then
-- stop wounded moving, head back to smoke as target heli is DEAD
-- in transit cleanup
-- csar.inTransitGroups[_heliName] = nil
return
end
-- double check that this function hasnt been queued for the wrong side
if csar.woundedGroups[_woundedGroupName].side ~= _heliUnit:getCoalition() then
return --wrong side!
end
if csar.checkGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) then
local _woundedLeader = _woundedGroup[1]
local _lookupKeyHeli = _heliUnit:getID() .. "_" .. _woundedLeader:getID() --lookup key for message state tracking
local _distance = csar.getDistance(_heliUnit:getPoint(), _woundedLeader:getPoint())
if _distance < 3000 then
if csar.checkCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then
-- we're close, reschedule
timer.scheduleFunction(csar.checkWoundedGroupStatus, _args, timer.getTime() + 1)
end
else
csar.heliVisibleMessage[_lookupKeyHeli] = nil
--reschedule as units arent dead yet , schedule for a bit slower though as we're far away
timer.scheduleFunction(csar.checkWoundedGroupStatus, _args, timer.getTime() + 5)
end
end
end, _argument)
if not _status then
env.error(string.format("error checkWoundedGroupStatus %s", _err))
end
end
function csar.popSmokeForGroup(_woundedGroupName, _woundedLeader)
-- have we popped smoke already in the last 5 mins
local _lastSmoke = csar.smokeMarkers[_woundedGroupName]
if _lastSmoke == nil or timer.getTime() > _lastSmoke then
local _smokecolor
if (_woundedLeader:getCoalition() == 2) then
_smokecolor = csar.bluesmokecolor
else
_smokecolor = csar.redsmokecolor
end
trigger.action.smoke(_woundedLeader:getPoint(), _smokecolor)
csar.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time
end
end
function csar.pickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName)
local _woundedLeader = _woundedGroup[1]
-- GET IN!
local _heliName = _heliUnit:getName()
local _groups = csar.inTransitGroups[_heliName]
local _unitsInHelicopter = csar.pilotsOnboard(_heliName)
-- init table if there is none for this helicopter
if not _groups then
csar.inTransitGroups[_heliName] = {}
_groups = csar.inTransitGroups[_heliName]
end
-- if the heli can't pick them up, show a message and return
if _unitsInHelicopter + 1 > csar.max_units then
csar.displayMessageToSAR(_heliUnit, string.format("%s, %s. We're already crammed with %d guys! Sorry!",
_pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), 10)
return true
end
csar.inTransitGroups[_heliName][_woundedGroupName] =
{
originalUnit = csar.woundedGroups[_woundedGroupName].originalUnit,
woundedGroup = _woundedGroupName,
side = _heliUnit:getCoalition(),
desc = csar.woundedGroups[_woundedGroupName].desc,
player = csar.woundedGroups[_woundedGroupName].player,
}
Group.destroy(_woundedLeader:getGroup())
csar.displayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), 10)
timer.scheduleFunction(csar.scheduledSARFlight,
{
heliName = _heliUnit:getName(),
groupName = _woundedGroupName
},
timer.getTime() + 1)
return true
end
-- Helicopter is within 3km
function csar.checkCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName)
local _woundedLeader = _woundedGroup[1]
local _lookupKeyHeli = _heliUnit:getID() .. "_" .. _woundedLeader:getID() --lookup key for message state tracking
local _pilotName = csar.woundedGroups[_woundedGroupName].desc
local _woundedCount = 1
local _reset = true
csar.popSmokeForGroup(_woundedGroupName, _woundedLeader)
if csar.heliVisibleMessage[_lookupKeyHeli] == nil then
csar.displayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), 30)
--mark as shown for THIS heli and THIS group
csar.heliVisibleMessage[_lookupKeyHeli] = true
end
if (_distance < 500) then
if csar.heliCloseMessage[_lookupKeyHeli] == nil then
csar.displayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land or hover at the smoke.", _heliName, _pilotName), 10)
--mark as shown for THIS heli and THIS group
csar.heliCloseMessage[_lookupKeyHeli] = true
end
-- have we landed close enough?
if csar.inAir(_heliUnit) == false then
-- if you land on them, doesnt matter if they were heading to someone else as you're closer, you win! :)
if (_distance < csar.loadDistance) then
return csar.pickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName)
end
else
local _unitsInHelicopter = csar.pilotsOnboard(_heliName)
if csar.inAir(_heliUnit) and _unitsInHelicopter + 1 <= csar.max_units then
if _distance < 8.0 then
--check height!
local _height = _heliUnit:getPoint().y - _woundedLeader:getPoint().y
if _height <= 20.0 then
local _time = csar.hoverStatus[_lookupKeyHeli]
if _time == nil then
csar.hoverStatus[_lookupKeyHeli] = 10
_time = 10
else
_time = csar.hoverStatus[_lookupKeyHeli] - 1
csar.hoverStatus[_lookupKeyHeli] = _time
end
if _time > 0 then
csar.displayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", 10, true)
else
csar.hoverStatus[_lookupKeyHeli] = nil
return csar.pickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName)
end
_reset = false
else
csar.displayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", 5, true)
end
end
end
end
end
if _reset then
csar.hoverStatus[_lookupKeyHeli] = nil
end
return true
end
function csar.checkGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName)
-- check if unit has died or been picked up
if #_woundedGroup == 0 and _heliUnit ~= nil then
local inTransit = false
for _currentHeli, _groups in pairs(csar.inTransitGroups) do
if _groups[_woundedGroupName] then
local _group = _groups[_woundedGroupName]
if _group.side == _heliUnit:getCoalition() then
inTransit = true
csar.displayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), _heliUnit:getCoalition(), _heliName)
break
end
end
end
--display to all sar
if inTransit == false then
--DEAD
csar.displayToAllSAR(string.format("%s is KIA ", _woundedGroupName), _heliUnit:getCoalition(), _heliName)
end
-- medevac.displayMessageToSAR(_heliUnit, string.format("%s: %s is dead", _heliName,_woundedGroupName ),10)
--stops the message being displayed again
csar.woundedGroups[_woundedGroupName] = nil
return false
end
--continue
return true
end
function csar.scheduledSARFlight(_args)
local _status, _err = pcall(function(_args)
local _heliUnit = csar.getSARHeli(_args.heliName)
local _woundedGroupName = _args.groupName
if (_heliUnit == nil) then
--helicopter crashed?
-- Put intransit pilots back
--TODO possibly respawn the guys
if csar.reenableIfCSARCrashes then
local _rescuedGroups = csar.inTransitGroups[_args.heliName]
if _rescuedGroups ~= nil then
-- enable pilots again
for _, _rescueGroup in pairs(_rescuedGroups) do
csar.enableAircraft(_rescueGroup.originalUnit, _rescuedGroups.player)
end
end
end
csar.inTransitGroups[_args.heliName] = nil
return
end
if csar.inTransitGroups[_heliUnit:getName()] == nil or csar.inTransitGroups[_heliUnit:getName()][_woundedGroupName] == nil then
-- Groups already rescued
return
end
local _dist = csar.getClosetMASH(_heliUnit)
if _dist == -1 then
-- Can now rescue to FARP
-- Mash Dead
-- csar.inTransitGroups[_heliUnit:getName()][_woundedGroupName] = nil
-- csar.displayMessageToSAR(_heliUnit, string.format("%s: NO MASH! The pilot died of despair!", _heliUnit:getName()), 10)
return
end
if _dist < 200 and _heliUnit:inAir() == false then
csar.rescuePilots(_heliUnit)
return
end
-- end
--queue up
timer.scheduleFunction(csar.scheduledSARFlight,
{
heliName = _heliUnit:getName(),
groupName = _woundedGroupName
},
timer.getTime() + 1)
end, _args)
if (not _status) then
env.error(string.format("Error in scheduledSARFlight\n\n%s", _err))
end
end
function csar.rescuePilots(_heliUnit)
local _rescuedGroups = csar.inTransitGroups[_heliUnit:getName()]
if _rescuedGroups == nil then
-- Groups already rescued
return
end
csar.inTransitGroups[_heliUnit:getName()] = nil
local _txt = string.format("%s: The pilots have been taken to the\nmedical clinic. Good job!", _heliUnit:getName())
local _cur_value = trigger.misc.getUserFlag("9999")
trigger.action.setUserFlag("9999", _cur_value + 1)
-- enable pilots again
for _, _rescueGroup in pairs(_rescuedGroups) do
csar.enableAircraft(_rescueGroup.originalUnit, _rescueGroup.player)
end
csar.displayMessageToSAR(_heliUnit, _txt, 10)
-- env.info("Rescued")
end
function csar.getSARHeli(_unitName)
local _heli = Unit.getByName(_unitName)
if _heli ~= nil and _heli:isActive() and _heli:getLife() > 0 then
return _heli
end
return nil
end
-- Displays a request for medivac
function csar.delayedHelpMessage(_args)
local status, err = pcall(function(_args)
local _heliName = _args[1]
local _text = _args[2]
local _injuredGroupName = _args[3]
local _heli = csar.getSARHeli(_heliName)
if _heli ~= nil and #csar.getWoundedGroup(_injuredGroupName) > 0 then
csar.displayMessageToSAR(_heli, _text, csar.messageTime)
local _groupId = csar.getGroupId(_heli)
if _groupId then
trigger.action.outSoundForGroup(_groupId, "l10n/DEFAULT/CSAR.ogg")
end
else
env.info("No Active Heli or Group DEAD")
end
end, _args)
if (not status) then
env.error(string.format("Error in delayedHelpMessage "))
end
return nil
end
function csar.displayMessageToSAR(_unit, _text, _time, _clear)
local _groupId = csar.getGroupId(_unit)
if _groupId then
if _clear == true then
trigger.action.outTextForGroup(_groupId, _text, _time, _clear)
else
trigger.action.outTextForGroup(_groupId, _text, _time)
end
end
end
function csar.getWoundedGroup(_groupName)
local _status, _result = pcall(function(_groupName)
local _woundedGroup = {}
local _units = Group.getByName(_groupName):getUnits()
for _, _unit in pairs(_units) do
if _unit ~= nil and _unit:isActive() and _unit:getLife() > 0 then
table.insert(_woundedGroup, _unit)
end
end
return _woundedGroup
end, _groupName)
if (_status) then
return _result
else
--env.warning(string.format("getWoundedGroup failed! Returning 0.%s",_result), false)
return {} --return empty table
end
end
function csar.convertGroupToTable(_group)
local _unitTable = {}
for _, _unit in pairs(_group:getUnits()) do
if _unit ~= nil and _unit:getLife() > 0 then
table.insert(_unitTable, _unit:getName())
end
end
return _unitTable
end
function csar.getPositionOfWounded(_woundedGroup)
local _woundedTable = csar.convertGroupToTable(_woundedGroup)
local _coordinatesText = ""
if csar.coordtype == 0 then -- Lat/Long DMTM
_coordinatesText = string.format("%s", mist.getLLString({ units = _woundedTable, acc = csar.coordaccuracy, DMS = 0 }))
elseif csar.coordtype == 1 then -- Lat/Long DMS
_coordinatesText = string.format("%s", mist.getLLString({ units = _woundedTable, acc = csar.coordaccuracy, DMS = 1 }))
elseif csar.coordtype == 2 then -- MGRS
_coordinatesText = string.format("%s", mist.getMGRSString({ units = _woundedTable, acc = csar.coordaccuracy }))
elseif csar.coordtype == 3 then -- Bullseye Imperial
_coordinatesText = string.format("bullseye %s", mist.getBRString({ units = _woundedTable, ref = coalition.getMainRefPoint(_woundedGroup:getCoalition()), alt = 0 }))
else -- Bullseye Metric --(medevac.coordtype == 4)
_coordinatesText = string.format("bullseye %s", mist.getBRString({ units = _woundedTable, ref = coalition.getMainRefPoint(_woundedGroup:getCoalition()), alt = 0, metric = 1 }))
end
return _coordinatesText
end
-- Displays all active MEDEVACS/SAR
function csar.displayActiveSAR(_unitName)
local _msg = "Active MEDEVAC/SAR:"
local _heli = csar.getSARHeli(_unitName)
if _heli == nil then
return
end
local _heliSide = _heli:getCoalition()
local _csarList = {}
for _groupName, _value in pairs(csar.woundedGroups) do
local _woundedGroup = csar.getWoundedGroup(_groupName)
if #_woundedGroup > 0 and (_woundedGroup[1]:getCoalition() == _heliSide) then
local _coordinatesText = csar.getPositionOfWounded(_woundedGroup[1]:getGroup())
local _distance = csar.getDistance(_heli:getPoint(), _woundedGroup[1]:getPoint())
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %.3fKM ", _value.desc, _coordinatesText, _value.frequency / 1000, _distance / 1000.0) })
end
end
local function sortDistance(a, b)
return a.dist < b.dist
end
table.sort(_csarList, sortDistance)
for _, _line in pairs(_csarList) do
_msg = _msg .. "\n" .. _line.msg
end
csar.displayMessageToSAR(_heli, _msg, 20)
end
function csar.getClosetDownedPilot(_heli)
local _side = _heli:getCoalition()
local _closetGroup = nil
local _shortestDistance = -1
local _distance = 0
local _closetGroupInfo = nil
for _woundedName, _groupInfo in pairs(csar.woundedGroups) do
local _tempWounded = csar.getWoundedGroup(_woundedName)
-- check group exists and not moving to someone else
if #_tempWounded > 0 and (_tempWounded[1]:getCoalition() == _side) then
_distance = csar.getDistance(_heli:getPoint(), _tempWounded[1]:getPoint())
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then
_shortestDistance = _distance
_closetGroup = _tempWounded[1]
_closetGroupInfo = _groupInfo
end
end
end
return { pilot = _closetGroup, distance = _shortestDistance, groupInfo = _closetGroupInfo }
end
function csar.signalFlare(_unitName)
local _heli = csar.getSARHeli(_unitName)
if _heli == nil then
return
end
local _closet = csar.getClosetDownedPilot(_heli)
if _closet ~= nil and _closet.pilot ~= nil and _closet.distance < 8000.0 then
local _clockDir = csar.getClockDirection(_heli, _closet.pilot)
local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Signal Flare at your %s ", _closet.groupInfo.desc, _closet.groupInfo.frequency / 1000, _closet.distance, _clockDir)
csar.displayMessageToSAR(_heli, _msg, 20)
trigger.action.signalFlare(_closet.pilot:getPoint(), 1, 0)
else
csar.displayMessageToSAR(_heli, "No Pilots within 8KM", 20)
end
end
function csar.displayToAllSAR(_message, _side, _ignore)
for _, _unitName in pairs(csar.csarUnits) do
local _unit = csar.getSARHeli(_unitName)
if _unit ~= nil and _unit:getCoalition() == _side then
if _ignore == nil or _ignore ~= _unitName then
csar.displayMessageToSAR(_unit, _message, 10)
end
else
-- env.info(string.format("unit nil %s",_unitName))
end
end
end
function csar.getClosetMASH(_heli)
local _mashes = csar.bluemash
if (_heli:getCoalition() == 1) then
_mashes = csar.redmash
end
local _shortestDistance = -1
local _distance = 0
for _, _mashName in pairs(_mashes) do
local _mashUnit = Unit.getByName(_mashName)
if _mashUnit ~= nil and _mashUnit:isActive() and _mashUnit:getLife() > 0 then
_distance = csar.getDistance(_heli:getPoint(), _mashUnit:getPoint())
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then
_shortestDistance = _distance
end
end
end
if _shortestDistance ~= -1 then
return _shortestDistance
else
return -1
end
end
function csar.checkOnboard(_unitName)
local _unit = csar.getSARHeli(_unitName)
if _unit == nil then
return
end
--list onboard pilots
local _inTransit = csar.inTransitGroups[_unitName]
if _inTransit == nil or csar.tableLength(_inTransit) == 0 then
csar.displayMessageToSAR(_unit, "No Rescued Pilots onboard", 30)
else
local _text = "Onboard - RTB to FARP/Airfield or MASH: "
for _, _onboard in pairs(csar.inTransitGroups[_unitName]) do
_text = _text .. "\n" .. _onboard.desc
end
csar.displayMessageToSAR(_unit, _text, 30)
end
end
-- Adds menuitem to all medevac units that are active
function csar.addMedevacMenuItem()
-- Loop through all Medevac units
timer.scheduleFunction(csar.addMedevacMenuItem, nil, timer.getTime() + 5)
for _, _unitName in pairs(csar.csarUnits) do
local _unit = csar.getSARHeli(_unitName)
if _unit ~= nil then
local _groupId = csar.getGroupId(_unit)
if _groupId then
if csar.addedTo[tostring(_groupId)] == nil then
csar.addedTo[tostring(_groupId)] = true
local _rootPath = missionCommands.addSubMenuForGroup(_groupId, "CSAR")
missionCommands.addCommandForGroup(_groupId, "List Active CSAR", _rootPath, csar.displayActiveSAR,
_unitName)
missionCommands.addCommandForGroup(_groupId, "Check Onboard", _rootPath, csar.checkOnboard, _unitName)
missionCommands.addCommandForGroup(_groupId, "Request Signal Flare", _rootPath, csar.signalFlare, _unitName)
end
end
else
-- env.info(string.format("unit nil %s",_unitName))
end
end
return
end
--get distance in meters assuming a Flat world
function csar.getDistance(_point1, _point2)
local xUnit = _point1.x
local yUnit = _point1.z
local xZone = _point2.x
local yZone = _point2.z
local xDiff = xUnit - xZone
local yDiff = yUnit - yZone
return math.sqrt(xDiff * xDiff + yDiff * yDiff)
end
-- 200 - 400 in 10KHz
-- 400 - 850 in 10 KHz
-- 850 - 1250 in 50 KHz
function csar.generateVHFrequencies()
--ignore list
--list of all frequencies in KHZ that could conflict with
-- 191 - 1290 KHz, beacon range
local _skipFrequencies = {
745, --Astrahan
381,
384,
300.50,
312.5,
1175,
342,
735,
300.50,
353.00,
440,
795,
525,
520,
690,
625,
291.5,
300.50,
435,
309.50,
920,
1065,
274,
312.50,
580,
602,
297.50,
750,
485,
950,
214,
1025, 730, 995, 455, 307, 670, 329, 395, 770,
380, 705, 300.5, 507, 740, 1030, 515,
330, 309.5,
348, 462, 905, 352, 1210, 942, 435,
324,
320, 420, 311, 389, 396, 862, 680, 297.5,
920, 662,
866, 907, 309.5, 822, 515, 470, 342, 1182, 309.5, 720, 528,
337, 312.5, 830, 740, 309.5, 641, 312, 722, 682, 1050,
1116, 935, 1000, 430, 577
}
csar.freeVHFFrequencies = {}
csar.usedVHFFrequencies = {}
local _start = 200000
-- first range
while _start < 400000 do
-- skip existing NDB frequencies
local _found = false
for _, value in pairs(_skipFrequencies) do
if value * 1000 == _start then
_found = true
break
end
end
if _found == false then
table.insert(csar.freeVHFFrequencies, _start)
end
_start = _start + 10000
end
_start = 400000
-- second range
while _start < 850000 do
-- skip existing NDB frequencies
local _found = false
for _, value in pairs(_skipFrequencies) do
if value * 1000 == _start then
_found = true
break
end
end
if _found == false then
table.insert(csar.freeVHFFrequencies, _start)
end
_start = _start + 10000
end
_start = 850000
-- third range
while _start <= 1250000 do
-- skip existing NDB frequencies
local _found = false
for _, value in pairs(_skipFrequencies) do
if value * 1000 == _start then
_found = true
break
end
end
if _found == false then
table.insert(csar.freeVHFFrequencies, _start)
end
_start = _start + 50000
end
end
function csar.generateADFFrequency()
if #csar.freeVHFFrequencies <= 3 then
csar.freeVHFFrequencies = csar.usedVHFFrequencies
csar.usedVHFFrequencies = {}
end
local _vhf = table.remove(csar.freeVHFFrequencies, math.random(#csar.freeVHFFrequencies))
return _vhf
--- return {uhf=_uhf,vhf=_vhf}
end
function csar.inAir(_heli)
if _heli:inAir() == false then
return false
end
-- less than 5 cm/s a second so landed
-- BUT AI can hold a perfect hover so ignore AI
if mist.vec.mag(_heli:getVelocity()) < 0.05 and _heli:getPlayerName() ~= nil then
return false
end
return true
end
function csar.getClockDirection(_heli, _crate)
-- Source: Helicopter Script - Thanks!
local _position = _crate:getPosition().p -- get position of crate
local _playerPosition = _heli:getPosition().p -- get position of helicopter
local _relativePosition = mist.vec.sub(_position, _playerPosition)
local _playerHeading = mist.getHeading(_heli) -- the rest of the code determines the 'o'clock' bearing of the missile relative to the helicopter
local _headingVector = { x = math.cos(_playerHeading), y = 0, z = math.sin(_playerHeading) }
local _headingVectorPerpendicular = { x = math.cos(_playerHeading + math.pi / 2), y = 0, z = math.sin(_playerHeading + math.pi / 2) }
local _forwardDistance = mist.vec.dp(_relativePosition, _headingVector)
local _rightDistance = mist.vec.dp(_relativePosition, _headingVectorPerpendicular)
local _angle = math.atan2(_rightDistance, _forwardDistance) * 180 / math.pi
if _angle < 0 then
_angle = 360 + _angle
end
_angle = math.floor(_angle * 12 / 360 + 0.5)
if _angle == 0 then
_angle = 12
end
return _angle
end
function csar.getGroupId(_unit)
local _unitDB = mist.DBs.unitsById[tonumber(_unit:getID())]
if _unitDB ~= nil and _unitDB.groupId then
return _unitDB.groupId
end
return nil
end
function csar.spawnStartGroups(_marker)
local _id = mist.getNextGroupId()
local _groupName = "Downed Pilot #" .. _id
local _side = 2
local _pos = trigger.misc.getZone(_marker)
local _group = {
["visible"] = false,
["groupId"] = _id,
["hidden"] = false,
["units"] = {},
["name"] = _groupName,
["task"] = {},
}
if _side == 2 then
_group.units[1] = csar.createUnit(_pos.point.x + 50, _pos.point.z + 50, 120, "Soldier M4")
else
_group.units[1] = csar.createUnit(_pos.point.x + 50, _pos.point.z + 50, 120, "Infantry AK")
end
_group.category = Group.Category.GROUND;
_group.country = 2;
local _spawnedGroup = Group.getByName(mist.dynAdd(_group).name)
-- Turn off AI
trigger.action.setGroupAIOff(_spawnedGroup)
return _spawnedGroup
end
function csar.spawnStarter()
local marker_list = {
marker1 = {name = "CSARUnit1", player = "TracerFacer"},
marker2 = {name = "CSARUnit2", player = "Kocrachon"},
marker3 = {name = "CSARUnit3", player = "pyromaniac4002"},
marker4 = {name = "CSARUnit4", player = "Squid"},
marker5 = {name = "CSARUnit5", player = "Beanie"},
marker6 = {name = "CSARUnit6", player = "Vampyre"},
marker7 = {name = "CSARUnit7", player = "Mataman"},
marker8 = {name = "CSARUnit8", player = "Alphaskoom"},
marker9 = {name = "CSARUnit9", player = "TracerFacer"},
marker10 = {name = "CSARUnit10", player = "Molasses"},
marker11 = {name = "CSARUnit11", player = "Weaz3L"},
marker12 = {name = "CSARUnit12", player = "Narokuu"},
marker13 = {name = "CSARUnit13", player = "HerraMisi"},
marker14 = {name = "CSARUnit14", player = "LokiV7"},
marker15 = {name = "CSARUnit15", player = "Benny"},
marker16 = {name = "CSARUnit16", player = "Rusk"},
marker17 = {name = "CSARUnit17", player = "Tweety"},
marker18 = {name = "CSARUnit18", player = "Esonub"},
}
for marker, marker_data in pairs(marker_list) do
local _spawnedGroup = csar.spawnStartGroups(marker_data.name)
csar.addSpecialParametersToGroup(_spawnedGroup)
local _freq = csar.generateADFFrequency()
csar.addBeaconToGroup(_spawnedGroup:getName(), _freq)
_text = "F99th Member " .. marker_data.player
csar.woundedGroups[_spawnedGroup:getName()] = { side = 2, originalUnit = marker_data.player, frequency = _freq, desc = _text, player = marker_data.player }
csar.initSARForPilot(_spawnedGroup, _freq)
end
return true
end
csar.generateVHFrequencies()
csar.spawnStarter()
-- Schedule timer to add radio item
timer.scheduleFunction(csar.addMedevacMenuItem, nil, timer.getTime() + 5)
if csar.disableAircraftTimeout then
-- Schedule timer to reactivate things
timer.scheduleFunction(csar.reactivateAircraft, nil, timer.getTime() + 5)
end
world.addEventHandler(csar.eventHandler)
env.info("CSAR event handler added")
--save CSAR MODE
trigger.action.setUserFlag("CSAR_MODE", csar.csarMode)
-- disable aircraft
if csar.enableSlotBlocking then
trigger.action.setUserFlag("CSAR_SLOTBLOCK", 100)
env.info("CSAR Slot block enabled")
end