mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
925 lines
33 KiB
Lua
925 lines
33 KiB
Lua
limitedAirframes = {}
|
|
limitedAirframes.version = "1.4.0"
|
|
limitedAirframes.verbose = false
|
|
limitedAirframes.enabled = true -- can be turned off
|
|
limitedAirframes.userCanToggle = true -- F10 menu?
|
|
limitedAirframes.onlyOwnSide = true -- F10 query only shows own side count, for later expansion
|
|
limitedAirframes.maxRed = -1 -- -1 == infinite
|
|
limitedAirframes.maxBlue = -1 -- = infinite
|
|
limitedAirframes.redWinsFlag = "999"
|
|
limitedAirframes.blueWinsFlag = "998"
|
|
limitedAirframes.method = "inc"
|
|
limitedAirframes.warningSound = "Quest Snare 3.wav"
|
|
limitedAirframes.loseSound = "Death PIANO.wav"
|
|
limitedAirframes.winSound = "Triumphant Victory.wav"
|
|
|
|
limitedAirframes.requiredLibs = {
|
|
"dcsCommon", -- common is of course needed for everything
|
|
-- pretty stupid to check for this since we
|
|
-- need common to invoke the check, but anyway
|
|
"cfxZones", -- Zones, of course for safe landings
|
|
"cfxPlayer",
|
|
}
|
|
|
|
--[[-- VERSION HISTORY
|
|
- 1.0.0 - initial version
|
|
- 1.0.1 - out text to coalition for switch
|
|
- less verbose
|
|
- win/lose sound by coalition
|
|
- 1.0.2 - corrected some to to-->for Groups typos
|
|
- 1.0.3 - renamed to 'pilot' instead of airframe:
|
|
pilotSafe attribute
|
|
- fixed MP bug, switched to EventMonII code
|
|
- 1.0.4 - added CSAR integration: create CSAR and callback
|
|
- safe ditch only at less than 7 kmh and 10m agl
|
|
- 1.0.5 - replaced 5 (crash) check for helos only
|
|
- 1.0.6 - changed alt and speed tests to inAir
|
|
reduced verbosity
|
|
made reporting of new units for coalition side only
|
|
- 1.0.7 - if unlimited pilots it says so when you return one
|
|
to base
|
|
- 1.0.8 - now can query remaining pilots
|
|
- 1.0.9 - better formatted remaining pilots
|
|
- 1.1.0 - module manager
|
|
- separated out settings
|
|
- hand change in pilotsafe zones that can be landed in
|
|
- 1.2.0 - limitedAirframesConfig zone
|
|
- 1.3.0 - added network dead override logic via unitFlownByPlayer
|
|
- 1.4.0 - DML integration, verbosity, clean-up, QoL improvements
|
|
redSafe, blueSafe with attribute, backward compatible
|
|
currRed
|
|
|
|
|
|
--]]--
|
|
|
|
-- limitedAirframes manages the number of available player airframes
|
|
-- per scenario and side. Each time a player crashes the plane
|
|
-- outside of safe zones, the number is decreased for that side
|
|
-- when the number reaches -1 or smaller, other side wins
|
|
-- !!!Only affects player planes!!
|
|
|
|
-- *** EXTENDS ZONES ***
|
|
-- safe zones must have a property "pilotSafe"
|
|
-- - pilotSafe - this is a zone to safely change airframes in
|
|
-- - can also carry 'red' or 'blue' to enable
|
|
-- - redSafe (optional, defaults to true)
|
|
-- - blueSafe (optional, defaults to true)
|
|
-- set to "false" or "no" to disallow that side to change
|
|
-- airframes even when safer
|
|
-- if zone can change ownership, player's coalition
|
|
-- is checked against current zone ownership
|
|
-- zone owner.
|
|
|
|
-- when red wins due to blue frame loss, flag 999 is set to true
|
|
-- when blue wins due to red frame loss, flag 998 is set to true
|
|
-- set a mission trigger to end mission if you want to end mission
|
|
-- or simply keep running, and a CHEATER! message will flash
|
|
-- every time the losing side enters a new aircraft
|
|
|
|
limitedAirframes.safeZones = {} -- safezones are zones where a crash or change plane does not
|
|
-- these zones are created by adding an 'pilotSafe' attribute
|
|
|
|
limitedAirframes.myEvents = {5, 9, 30, 6, 20, 21, 15 } -- 5 = crash, 9 - dead, 30 - unit lost, 6 - eject, 20 - enter unit, 21 - leave unit, 15 - birth
|
|
|
|
-- guarantee a min of 2 seconds between events
|
|
-- for this we save last event per player
|
|
limitedAirframes.lastEvents = {}
|
|
-- each time a plane crashes or is abandoned check
|
|
-- that it's a player unit
|
|
-- inside a crash free zone
|
|
-- update the side's airframe credit
|
|
|
|
limitedAirframes.currRed = 0
|
|
limitedAirframes.currBlue = 0
|
|
|
|
-- we record all unit names that contain a player
|
|
-- so that we can check against these when we receive
|
|
-- an ejection event. We also keep a list of players
|
|
-- for good measure and their status
|
|
limitedAirframes.playerUnits = {}
|
|
limitedAirframes.players = {}
|
|
limitedAirframes.unitFlownByPlayer = {} -- to detect dead after
|
|
-- 21 (player left unit) we store on 15 (birth)
|
|
-- which unit a player occupies. if player
|
|
-- then levaes and dead has a mismatch, we resolve
|
|
-- by not calling dead.
|
|
-- works if eject does not call player left unit
|
|
-- unit[unitname] = playername. if nil, no longer
|
|
-- occupied by player.
|
|
|
|
limitedAirframes.theCommand = nil
|
|
|
|
--
|
|
-- READ CONFIG ZONE TO OVERRIDE SETTING
|
|
--
|
|
function limitedAirframes.readConfigZone()
|
|
-- note: must match exactly!!!!
|
|
local theZone = cfxZones.getZoneByName("limitedAirframesConfig")
|
|
if not theZone then
|
|
if limitedAirframes.verbose then
|
|
trigger.action.outText("+++limA: NO config zone!", 30)
|
|
end
|
|
theZone = cfxZones.createSimpleZone("limitedAirframesConfig")
|
|
end
|
|
-- remember me
|
|
limitedAirframes.config = theZone
|
|
|
|
limitedAirframes.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
|
|
|
if limitedAirframes.verbose then
|
|
trigger.action.outText("+++limA: found config zone!", 30)
|
|
end
|
|
|
|
-- ok, for each property, load it if it exists
|
|
if cfxZones.hasProperty(theZone, "enabled") then
|
|
limitedAirframes.enabled = cfxZones.getBoolFromZoneProperty(theZone, "enabled", true)
|
|
end
|
|
|
|
if cfxZones.hasProperty(theZone, "userCanToggle") then
|
|
limitedAirframes.userCanToggle = cfxZones.getBoolFromZoneProperty(theZone, "userCanToggle", true)
|
|
end
|
|
|
|
|
|
if cfxZones.hasProperty(theZone, "maxRed") then
|
|
limitedAirframes.maxRed = cfxZones.getNumberFromZoneProperty(theZone, "maxRed", -1)
|
|
end
|
|
|
|
if cfxZones.hasProperty(theZone, "maxBlue") then
|
|
limitedAirframes.maxBlue = cfxZones.getNumberFromZoneProperty(theZone, "maxBlue", -1)
|
|
end
|
|
|
|
limitedAirframes.numRed = cfxZones.getStringFromZoneProperty(theZone, "#red", "*none")
|
|
limitedAirframes.numBlue = cfxZones.getStringFromZoneProperty(theZone, "#blue", "*none")
|
|
|
|
|
|
limitedAirframes.redWinsFlag = cfxZones.getStringFromZoneProperty(theZone, "redWins!", "*none")
|
|
|
|
if cfxZones.hasProperty(theZone, "redWinsFlag!") then
|
|
limitedAirframes.redWinsFlag = cfxZones.getStringFromZoneProperty(theZone, "redWinsFlag!", "*none")
|
|
end
|
|
|
|
limitedAirframes.blueWinsFlag = cfxZones.getStringFromZoneProperty(theZone, "blueWins!", "*none")
|
|
if cfxZones.hasProperty(theZone, "blueWinsFlag!") then
|
|
limitedAirframes.blueWinsFlag = cfxZones.getStringFromZoneProperty(theZone, "blueWinsFlag!", "*none")
|
|
end
|
|
|
|
limitedAirframes.method = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
|
|
|
|
if cfxZones.hasProperty(theZone, "warningSound") then
|
|
limitedAirframes.warningSound = cfxZones.getStringFromZoneProperty(theZone, "warningSound", "none")
|
|
end
|
|
|
|
if cfxZones.hasProperty(theZone, "winSound") then
|
|
limitedAirframes.winSound = cfxZones.getStringFromZoneProperty(theZone, "winSound", "none")
|
|
end
|
|
|
|
if cfxZones.hasProperty(theZone, "loseSound") then
|
|
limitedAirframes.loseSound = cfxZones.getStringFromZoneProperty(theZone, "loseSound", "none")
|
|
end
|
|
end
|
|
|
|
--
|
|
-- UNIT AND PLAYER HANDLING
|
|
--
|
|
function limitedAirframes.isKnownUnitName(uName)
|
|
if limitedAirframes.playerUnits[uName] then return true end
|
|
return false
|
|
end
|
|
|
|
function limitedAirframes.getKnownUnitPilotByUnitName(uName)
|
|
if limitedAirframes.isKnownUnitName(uName) then
|
|
return limitedAirframes.playerUnits[uName]
|
|
end
|
|
trigger.action.outText("+++lim: WARNING: " .. uName .. " is unknown!", 30)
|
|
return "***Error"
|
|
end
|
|
|
|
function limitedAirframes.getKnownUnitPilotByUnit(theUnit)
|
|
return limitedAirframes.getKnownUnitPilotByUnitName(theUnit:getName())
|
|
end
|
|
|
|
-- addPlayerUnit adds a unit as a known player unit
|
|
-- and also adds the player if unknown
|
|
function limitedAirframes.addPlayerUnit(theUnit)
|
|
local theSide = theUnit:getCoalition()
|
|
local uName = theUnit:getName()
|
|
if not uName then uName = "**XXXX**" end
|
|
local pName = theUnit:getPlayerName()
|
|
if not pName then pName = "**????**" end
|
|
limitedAirframes.updatePlayer(pName, "alive")
|
|
|
|
local desc = "unit <" .. uName .. "> controlled by <" .. pName .. ">"
|
|
if not(limitedAirframes.isKnownUnitName(uName)) then
|
|
--desc = "+++lim: added ".. desc .. " to list of known player units"
|
|
else
|
|
if limitedAirframes.playerUnits[uName] == pName then
|
|
desc = "player unit <".. uName .. "> controlled by <".. limitedAirframes.playerUnits[uName].."> re-seated"
|
|
else
|
|
desc = "Updated player unit <".. uName .. "> from <".. limitedAirframes.playerUnits[uName].."> to <" .. pName ..">"
|
|
end
|
|
end
|
|
limitedAirframes.playerUnits[uName] = pName
|
|
trigger.action.outTextForCoalition(theSide, desc, 30)
|
|
end
|
|
|
|
function limitedAirframes.killPlayer(pName)
|
|
limitedAirframes.updatePlayer(pName, "dead")
|
|
--trigger.action.outText("+++lim: PILOT LOST: " .. pName .. ", NO CSAR", 30)
|
|
end
|
|
|
|
function limitedAirframes.killPlayerInUnit(theUnit)
|
|
limitedAirframes.updatePlayerInUnit(theUnit, "dead")
|
|
--trigger.action.outText("+++lim: PILOT LOST, NO CSAR", 30)
|
|
end
|
|
|
|
function limitedAirframes.updatePlayerInUnit(theUnit, status)
|
|
local uName = theUnit:getName()
|
|
if not limitedAirframes.isKnownUnitName(uName) then
|
|
trigger.action.outText("+++lim: WARNING: updatePlayerInUnit to " .. status .. " with unknown pilot for plane", 30)
|
|
return
|
|
end
|
|
local pName = limitedAirframes.getKnownUnitPilotByUnitName(uName)
|
|
limitedAirframes.updatePlayer(pName, status)
|
|
end
|
|
|
|
function limitedAirframes.updatePlayer(pName, status)
|
|
if not pName then
|
|
trigger.action.outText("+++limA: WARNING - NIL pName in updatePlayer for status " .. status, 30)
|
|
return
|
|
end
|
|
local desc = ""
|
|
if not limitedAirframes.players[pName] then
|
|
desc = "+++limA: NEW player " .. pName .. ": " .. status
|
|
else
|
|
if limitedAirframes.players[pName] ~= status then
|
|
desc = "+++limA: CHANGE player " .. pName .. " " .. limitedAirframes.players[pName] .. " -> " .. status
|
|
else
|
|
desc = "+++limA: player " .. pName .. " no change (" .. status .. ")"
|
|
end
|
|
end
|
|
|
|
limitedAirframes.players[pName] = status
|
|
-- if desc then trigger.action.outText(desc, 30) end
|
|
end
|
|
|
|
function limitedAirframes.getStatusOfPlayerInUnit(theUnit)
|
|
local uName = theUnit:getName()
|
|
if not limitedAirframes.isKnownUnitName(uName) then
|
|
trigger.action.outText("+++lim: WARNING get player status for unknown pilot in plane " .. uName, 30)
|
|
return nil
|
|
end
|
|
local pName = limitedAirframes.getKnownUnitPilotByUnitName(uName)
|
|
return limitedAirframes.getStatusOfPlayerNamed(pName)
|
|
end
|
|
|
|
function limitedAirframes.getStatusOfPlayerNamed(pName)
|
|
return limitedAirframes.players[pName]
|
|
end
|
|
|
|
--
|
|
-- E V E N T H A N D L I N G
|
|
--
|
|
function limitedAirframes.XXXisKnownPlayerUnit(theUnit)
|
|
if not theUnit then return false end
|
|
local aName = theUnit:getName()
|
|
if limitedAirframes.playerUnitNames[aName] ~= nil then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function limitedAirframes.isInteresting(eventID)
|
|
-- return true if we are interested in this event, false else
|
|
for key, evType in pairs(limitedAirframes.myEvents) do
|
|
if evType == eventID then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function limitedAirframes.preProcessor(event)
|
|
-- make sure it has an initiator
|
|
if not event.initiator then return false end -- no initiator
|
|
local theUnit = event.initiator
|
|
local uName = theUnit:getName()
|
|
|
|
|
|
if event.id == 6 then -- Eject, plane already divorced from player
|
|
if limitedAirframes.isKnownUnitName(uName) then
|
|
return true
|
|
end
|
|
return false -- no longer of interest
|
|
end
|
|
|
|
if event.id == 5 then -- crash, plane no longer attached to player
|
|
if limitedAirframes.isKnownUnitName(uName) then
|
|
return true
|
|
end
|
|
return false -- no longer of interest
|
|
end
|
|
|
|
|
|
if not cfxPlayer.isPlayerUnit(theUnit) then
|
|
-- not a player unit. Events 5 and 6 have been
|
|
-- handled before, so we can safely ignore
|
|
return false
|
|
end
|
|
|
|
-- exclude all ground units
|
|
local theGroup = theUnit:getGroup()
|
|
local cat = theGroup:getCategory()
|
|
if cat == Group.Category.GROUND then
|
|
return false
|
|
end
|
|
|
|
-- only return true if defined as interesting
|
|
return limitedAirframes.isInteresting(event.id)
|
|
end
|
|
|
|
function limitedAirframes.postProcessor(event)
|
|
-- don't do anything
|
|
end
|
|
|
|
function limitedAirframes.somethingHappened(event)
|
|
-- when this is invoked, the preprocessor guarantees that
|
|
-- we have:
|
|
-- * an interesting event
|
|
-- * unit is valid and was a player's unit
|
|
|
|
-- the events that are relevant for pilot loss are:
|
|
-- * player entered: set pilot 'alive' state, maybe add new
|
|
-- unit and pilot to db of players and units
|
|
-- * pilot died - decrease pilot count, set 'dead' state
|
|
-- * eject - decrease pilot count, 'MIA' state, csar possible
|
|
-- * player left - when pilot status 'alive' - check safe zone
|
|
-- - outside safe zone, csar possible, set 'MIA'
|
|
-- - when pilot anything but 'alive' - ignore
|
|
|
|
if not event.initiator then
|
|
trigger.action.outText("limAir: ***WARNING: event (" .. ID .. "): no initiator, should not have been procced", 30)
|
|
return
|
|
end
|
|
|
|
local theUnit = event.initiator
|
|
local unitName = theUnit:getName()
|
|
local ID = event.id
|
|
local myType = theUnit:getTypeName()
|
|
|
|
|
|
if ID == 20 then -- 20 ENTER UNIT
|
|
local pName = limitedAirframes.getKnownUnitPilotByUnit(theUnit)
|
|
if not pName then pName = "***UNKNOWN***" end
|
|
return
|
|
end
|
|
|
|
if ID == 15 then -- birth - this one is called in network, 20 is too unreliable
|
|
-- can set where birthed: runway, parking, air etc.
|
|
limitedAirframes.addPlayerUnit(theUnit) -- will also update player and player status to 'alive'
|
|
-- now procc a 'cheater' since we entered a new airframe/pilot
|
|
limitedAirframes.checkPlayerFrameAvailability(event)
|
|
local playerName = theUnit:getPlayerName()
|
|
limitedAirframes.unitFlownByPlayer[unitName] = playerName
|
|
-- TODO: make sure this is the ONLY plane the player
|
|
-- is registered under, and mark mismatches
|
|
if limitedAirframes.verbose then
|
|
trigger.action.outText("limAir: 15 -- player " .. playerName .. " now in " .. unitName, 30)
|
|
end
|
|
return
|
|
end
|
|
|
|
-- make sure unit's player pilot is known
|
|
if not limitedAirframes.getKnownUnitPilotByUnitName(unitName) then
|
|
trigger.action.outText("limAir: ***WARNING: Ignored player event (" .. ID .. "): unable to retrieve player name for " .. unitName, 30)
|
|
return -- plane no longer of interest cant retrieve pilot -- BUG!!!
|
|
end
|
|
|
|
|
|
-- event 6 - eject - plane divorced but player pilot is known
|
|
if ID == 6 then -- eject
|
|
limitedAirframes.pilotEjected(event)
|
|
return
|
|
end
|
|
|
|
-- event 5 - crash - plane divorced but player pilot is known
|
|
-- if pilot is still alive, this should now cause a pilot lost event if we are a helicopter
|
|
|
|
if ID == 5 then -- crash
|
|
-- as of new processing, no longer relevant
|
|
-- limitedAirframes.airFrameCrashed(event)
|
|
-- helicopters do not call died when helo
|
|
-- crashes. so check if we are still seated=alive
|
|
-- and call pilot dead then
|
|
-- for some reason, pilot died also may not be called
|
|
-- so if pilot is still alive and not MIA, he's now dead.
|
|
-- forget the helo check, this now applies to all
|
|
|
|
|
|
local pStatus = limitedAirframes.getStatusOfPlayerInUnit(theUnit)
|
|
if pStatus == "alive" then
|
|
-- this frame was carrrying a live player
|
|
limitedAirframes.pilotDied(theUnit)
|
|
return
|
|
else
|
|
if limitedAirframes.verbose then
|
|
trigger.action.outText("limAir: Crash of airframe detected - but player status wasn't alive (" .. pStatus .. ")", 30)
|
|
end
|
|
return
|
|
end
|
|
end
|
|
|
|
-- removed dual 21 detection here
|
|
|
|
if ID == 21 then
|
|
-- remove pilot name from unit name
|
|
limitedAirframes.unitFlownByPlayer[unitName] = nil
|
|
--trigger.action.outText("limAir: 21 -- unit " .. unitName .. " unoccupied", 30)
|
|
if limitedAirframes.verbose then
|
|
trigger.action.outText("limAir: 21 (player left) for unit " .. unitName , 30)
|
|
end
|
|
-- player left unit. Happens twice
|
|
-- check if player alive, else we have a ditch.
|
|
limitedAirframes.handlePlayerLeftUnit(event)
|
|
return
|
|
end
|
|
|
|
|
|
if ID == 9 then -- died
|
|
--trigger.action.outText("limAir: 9 (PILOT DEAD) for unit " .. unitName , 30)
|
|
local thePilot = limitedAirframes.unitFlownByPlayer[unitName]
|
|
if not thePilot then
|
|
if limitedAirframes.verbose then
|
|
trigger.action.outText("+++limAir: 9 O'RIDE -- unit " .. unitName .. " was legally vacated before!", 30)
|
|
end
|
|
return
|
|
end
|
|
limitedAirframes.pilotDied(theUnit)
|
|
return
|
|
end
|
|
|
|
if ID == 30 then -- unit lost
|
|
return
|
|
end
|
|
|
|
trigger.action.outText("limAir: WARNING unhandled: " .. ID .. " for player unit " .. theUnit:getName() .. " of type " .. myType, 30)
|
|
end
|
|
|
|
--
|
|
-- HANDLE VARIOUS SITUATIONS
|
|
--
|
|
|
|
function limitedAirframes.handlePlayerLeftUnit(event)
|
|
local theUnit = event.initiator
|
|
-- make sure the pilot is alive
|
|
if limitedAirframes.getStatusOfPlayerInUnit(theUnit) ~= "alive" then
|
|
-- was already handled. simply exit
|
|
local pName = limitedAirframes.getKnownUnitPilotByUnitName(theUnit:getName())
|
|
local pStatus = limitedAirframes.getStatusOfPlayerInUnit(theUnit)
|
|
-- player was already dead and has been accounted for
|
|
--trigger.action.outText("limAir: Change Plane for player <" .. pName .. "> with status <" .. pStatus .. "> procced.", 30)
|
|
return
|
|
end
|
|
|
|
-- check if the unit was inside a safe zone
|
|
-- if so, graceful exit
|
|
local uPos = theUnit:getPoint()
|
|
local meInside = cfxZones.getZonesContainingPoint(uPos, limitedAirframes.safeZones)
|
|
local mySide = theUnit:getCoalition()
|
|
--local speed = dcsCommon.getUnitSpeed(theUnit) -- this can cause problems with carriers, so check if below
|
|
--local agl = dcsCommon.getUnitAGL(theUnit) -- this will cause problems with FARP and carriers.
|
|
-- we now check the inAir
|
|
local isInAir = theUnit:inAir()
|
|
--trigger.action.outTextForCoalition(mySide, "limAir: safe check for Pilot " .. theUnit:getPlayerName() .. ": agl=" .. agl .. ", speed = " .. speed .. ", air status = " .. dcsCommon.bool2YesNo(isInAir), 30)
|
|
|
|
for i=1, #meInside do
|
|
-- I'm inside all these zones. We look for the first
|
|
-- that saves me
|
|
local theSafeZone = meInside[i]
|
|
local isSafe = false
|
|
if mySide == 1 then
|
|
isSafe = theSafeZone.redSafe
|
|
elseif mySide == 2 then
|
|
isSafe = theSafeZone.blueSafe
|
|
else
|
|
isSafe = true
|
|
end
|
|
|
|
if theSafeZone.owner then
|
|
-- owned zone. olny allow in neutral or owned by same side
|
|
isSafe = isSafe and (mySide == theSafeZone.owner or theSafeZone.owner == 0)
|
|
if limitedAirframes.verbose then
|
|
trigger.action.outText("+++limA: " .. theSafeZone.name .. " ownership: myside = " .. mySide .. " zone owner is " .. theSafeZone.owner, 30)
|
|
end
|
|
else
|
|
|
|
end
|
|
|
|
-- check we are at rest below 10m height. agl may give
|
|
-- misreadings on carriers and FARPs, while speed may read
|
|
-- wrongly on carriers (tbd). we now use isInAir to determine if
|
|
-- we ditched while in flight
|
|
--if speed > 2 or agl > 10 then isSafe = false end
|
|
--if not isInAir then return false end -- why *not* isInAir???
|
|
|
|
-- for matter of fact, why did we return ANYTHING???
|
|
-- there may be a bug here
|
|
-- maybe it should be "if isInAir then isSafe = false"??
|
|
if isInAir then isSafe = false end
|
|
|
|
if isSafe then
|
|
-- if limitedAirframes.verbose then
|
|
trigger.action.outTextForCoalition(mySide, "Pilot " .. theUnit:getPlayerName() .. " left unit " .. theUnit:getName() .. " legally in zone " .. theSafeZone.name, 30)
|
|
-- end
|
|
-- remove from known player planes
|
|
-- no more limitedAirframes.removePlayerUnit(theUnit)
|
|
return;
|
|
end
|
|
end
|
|
|
|
-- ditched outside safe harbour
|
|
-- if limitedAirframes.verbose then
|
|
trigger.action.outTextForCoalition(mySide, "Pilot " .. theUnit:getPlayerName() .. " DITCHED unit " .. theUnit:getName() .. " -- PILOT is considered MIA", 30)
|
|
-- end
|
|
|
|
limitedAirframes.pilotLost(theUnit)
|
|
if csarManager and csarManager.airframeDitched then
|
|
csarManager.airframeDitched(theUnit)
|
|
end
|
|
|
|
limitedAirframes.updatePlayerInUnit(theUnit, "MIA") -- cosmetic only
|
|
limitedAirframes.createCSAR(theUnit)
|
|
end
|
|
|
|
|
|
|
|
function limitedAirframes.pilotEjected(event)
|
|
local theUnit = event.initiator
|
|
-- do we want to check location?
|
|
-- no. if the user ejects, plane is done for
|
|
local theSide = theUnit:getCoalition()
|
|
local pilot = limitedAirframes.getKnownUnitPilotByUnit(theUnit)
|
|
local uName = theUnit:getName()
|
|
trigger.action.outTextForCoalition(theSide, "Pilot <" .. pilot .. "> ejected from " .. uName .. ", now MIA", 30)
|
|
|
|
local hasLostTheWar = limitedAirframes.pilotLost(theUnit)
|
|
|
|
limitedAirframes.updatePlayerInUnit(theUnit, "MIA") -- cosmetic only
|
|
-- create CSAR if applicable
|
|
if not hasLostTheWar then
|
|
limitedAirframes.createCSAR(theUnit)
|
|
end
|
|
end
|
|
|
|
function limitedAirframes.pilotDied(theUnit)
|
|
--limitedAirframes.killPlayerInUnit(theUnit)
|
|
local theSide = theUnit:getCoalition()
|
|
local pilot = limitedAirframes.getKnownUnitPilotByUnit(theUnit)
|
|
local uName = theUnit:getName()
|
|
trigger.action.outTextForCoalition(theSide, "Pilot <" .. pilot .. "> is confirmed KIA while controlling " .. uName, 30)
|
|
limitedAirframes.pilotLost(theUnit)
|
|
end
|
|
|
|
function limitedAirframes.pilotLost(theUnit)
|
|
-- returns true if lost the war
|
|
-- MUST NOT MESSAGE PILOT STATUS AS MIA CAN ALSO BE SET
|
|
-- first DELETE THE UNIT FROM player-owned unit table
|
|
-- so an empty crash after eject/death will not be counted as two losses
|
|
limitedAirframes.killPlayerInUnit(theUnit)
|
|
|
|
-- now see if we are enabled to limit airframes
|
|
if not limitedAirframes.enabled then return false end
|
|
|
|
-- find out which side lost the airframe and message side
|
|
local theSide = theUnit:getCoalition()
|
|
local pilot = limitedAirframes.getKnownUnitPilotByUnit(theUnit)
|
|
local uName = theUnit:getName()
|
|
--trigger.action.outTextForCoalition(theSide, "Pilot <" .. pilot .. "> is confirmed KIA while controlling " .. uName, 30)
|
|
|
|
if theSide == 1 then -- red
|
|
theOtherSide = 2
|
|
if limitedAirframes.maxRed < 0 then return false end -- disabled/infinite
|
|
|
|
limitedAirframes.currRed = limitedAirframes.currRed - 1
|
|
-- pass it along
|
|
cfxZones.setFlagValueMult(limitedAirframes.numRed, limitedAirframes.currRed, limitedAirframes.config)
|
|
|
|
if limitedAirframes.currRed == 0 then
|
|
trigger.action.outTextForCoalition(theSide, "\nYou have lost almost all of your pilots.\n\nWARNING: Losing any more pilots WILL FAIL THE MISSION\n", 30)
|
|
trigger.action.outSoundForCoalition(theSide, limitedAirframes.warningSound)--"Quest Snare 3.wav")
|
|
return false
|
|
end
|
|
|
|
if limitedAirframes.currRed < 0 then
|
|
-- red have lost all airframes
|
|
trigger.action.outText("\nREDFORCE has lost all of their pilots.\n\nBLUEFORCE WINS!\n", 30)
|
|
trigger.action.outSoundForCoalition(theSide, limitedAirframes.loseSound) --"Death PIANO.wav")
|
|
trigger.action.outSoundForCoalition(theOtherSide, limitedAirframes.winSound)--"Triumphant Victory.wav")
|
|
-- trigger.action.setUserFlag(limitedAirframes.blueWinsFlag, 1 )
|
|
cfxZones.pollFlag(limitedAirframes.blueWinsFlag, limitedAirframes.method, limitedAirframes.config)
|
|
return true
|
|
end
|
|
|
|
|
|
elseif theSide == 2 then -- blue
|
|
theOtherSide = 1
|
|
if limitedAirframes.maxBlue < 0 then return false end -- disabled/infinite
|
|
limitedAirframes.currBlue = limitedAirframes.currBlue - 1
|
|
-- pass it along
|
|
cfxZones.setFlagValueMult(limitedAirframes.numBlue, limitedAirframes.currBlue, limitedAirframes.config)
|
|
|
|
if limitedAirframes.currBlue == 0 then
|
|
trigger.action.outTextForCoalition(theSide, "\nYou have lost almost all of your pilots.\n\nWARNING: Losing any more pilots WILL FAIL THE MISSION\n", 30)
|
|
trigger.action.outSoundForCoalition(theSide, limitedAirframes.warningSound)--"Quest Snare 3.wav")
|
|
return false
|
|
end
|
|
if limitedAirframes.currBlue < 0 then
|
|
-- red have lost all airframes
|
|
trigger.action.outText("\nBLUEFORCE has lost all of their pilots.\n\nREDFORCE WINS!\n", 30)
|
|
-- trigger.action.setUserFlag(limitedAirframes.redWinsFlag, 1 )
|
|
cfxZones.pollFlag(limitedAirframes.redWinsFlag, limitedAirframes.method, limitedAirframes.config)
|
|
trigger.action.outSoundForCoalition(theSide, limitedAirframes.loseSound)--"Death PIANO.wav")
|
|
trigger.action.outSoundForCoalition(theOtherSide, limitedAirframes.winSound)--"Triumphant Victory.wav")
|
|
return true
|
|
end
|
|
trigger.action.outSoundForCoalition(theSide, limitedAirframes.warningSound)--"Quest Snare 3.wav")
|
|
trigger.action.outTextForCoalition(theSide, "You have lost a pilot! Remaining: " .. limitedAirframes.currBlue, 30)
|
|
end
|
|
return false
|
|
end
|
|
|
|
function limitedAirframes.checkPlayerFrameAvailability(event)
|
|
local theUnit = event.initiator
|
|
local theSide = theUnit:getCoalition()
|
|
if theSide == 1 then -- red
|
|
if limitedAirframes.maxRed < 0 then return end -- disabled/infinite
|
|
if limitedAirframes.currRed < 0 then
|
|
-- red have lost all airframes
|
|
trigger.action.outText("\nREDFORCE is a CHEATER!\n", 30)
|
|
return
|
|
end
|
|
elseif theSide == 2 then -- blue
|
|
if limitedAirframes.maxBlue < 0 then return end -- disabled/infinite
|
|
if limitedAirframes.currBlue < 0 then
|
|
-- red have lost all airframes
|
|
trigger.action.outText("\nBLUEFORCE is a CHEATER!\n", 30)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function limitedAirframes.createCSAR(theUnit)
|
|
-- only do this if we have installed CSAR Manager
|
|
if csarManager and csarManager.createCSARforUnit then
|
|
csarManager.createCSARforUnit(theUnit,
|
|
limitedAirframes.getKnownUnitPilotByUnit(theUnit),
|
|
100)
|
|
end
|
|
end
|
|
|
|
-- start up
|
|
|
|
function limitedAirframes.addSafeZone(aZone)
|
|
if not aZone then
|
|
trigger.action.outText("WARNING: NIL Zone in addSafeZone", 30)
|
|
return
|
|
end
|
|
|
|
-- transfer properties if they exist
|
|
-- blueSafe, redSafe: what side this is safe for, default = yes
|
|
|
|
-- add zone to my list
|
|
limitedAirframes.safeZones[aZone] = aZone
|
|
|
|
-- deprecated old code. new code contains 'red, blue' in value for pilotsafe
|
|
local safeSides = cfxZones.getStringFromZoneProperty(aZone, "pilotsafe", "")
|
|
safeSides = safeSides:lower()
|
|
if dcsCommon.containsString(safeSides, "red") or dcsCommon.containsString(safeSides, "blue") then
|
|
aZone.redSafe = dcsCommon.containsString(safeSides, "red")
|
|
aZone.blueSafe = dcsCommon.containsString(safeSides, "blue")
|
|
else
|
|
--aZone.redSafe = true
|
|
aZone.redSafe = cfxZones.getBoolFromZoneProperty(aZone, "redSafe", true)
|
|
--aZone.blueSafe = true
|
|
aZone.blueSafe = cfxZones.getBoolFromZoneProperty(aZone, "blueSafe", true)
|
|
end
|
|
|
|
if limitedAirframes.verbose or aZone.verbose then
|
|
if aZone.redSafe then
|
|
trigger.action.outText("+++limA: <" .. aZone.name .. "> is safe for RED pilots", 30)
|
|
end
|
|
if aZone.blueSafe then
|
|
trigger.action.outText("+++limA: <" .. aZone.name .. "> is safe for BLUE pilots", 30)
|
|
end
|
|
|
|
trigger.action.outText("+++limA: added safeZone " .. aZone.name, 30)
|
|
|
|
end
|
|
end
|
|
|
|
|
|
|
|
--
|
|
-- COMMAND & CONFIGURATION
|
|
--
|
|
function limitedAirframes.setCommsMenu()
|
|
local desc = "Pilot Count (Currently ON)"
|
|
local desc2 = "Turn OFF Pilot Count (Cheat)?"
|
|
if not limitedAirframes.enabled then
|
|
desc = "Pilot Count (Currently OFF)"
|
|
desc2 = "ENABLE Pilot Count"
|
|
end
|
|
-- remove previous version
|
|
if limitedAirframes.rootMenu then
|
|
missionCommands.removeItem(limitedAirframes.theScore)
|
|
missionCommands.removeItem(limitedAirframes.theCommand)
|
|
missionCommands.removeItem(limitedAirframes.rootMenu)
|
|
end
|
|
limitedAirframes.theCommand = nil
|
|
limitedAirframes.rootMenu = nil
|
|
|
|
-- add current version menu and command
|
|
limitedAirframes.rootMenu = missionCommands.addSubMenu(desc, nil)
|
|
|
|
limitedAirframes.theScore = missionCommands.addCommand("How many airframes left?" , limitedAirframes.rootMenu, limitedAirframes.redirectAirframeScore, {"none"})
|
|
|
|
limitedAirframes.theCommand = missionCommands.addCommand(desc2 , limitedAirframes.rootMenu, limitedAirframes.redirectToggleAirFrames, {"none"})
|
|
|
|
end
|
|
|
|
function limitedAirframes.redirectAirframeScore(args)
|
|
timer.scheduleFunction(limitedAirframes.doAirframeScore, args, timer.getTime() + 0.1)
|
|
end
|
|
|
|
function limitedAirframes.doAirframeScore(args)
|
|
local redRemaining = "unlimited"
|
|
if limitedAirframes.maxRed >= 0 then
|
|
redRemaining = limitedAirframes.currRed .. " of " .. limitedAirframes.maxRed
|
|
if limitedAirframes.currRed < 1 then
|
|
redRemaining = "no"
|
|
end
|
|
end
|
|
|
|
local blueRemaining = "unlimited"
|
|
if limitedAirframes.maxBlue >= 0 then
|
|
blueRemaining = limitedAirframes.currBlue .. " of " .. limitedAirframes.maxBlue
|
|
if limitedAirframes.currBlue < 1 then
|
|
blueRemaining = "no"
|
|
end
|
|
end
|
|
|
|
local msg = "\nRED has " .. redRemaining .. " pilots left,\nBLUE has " .. blueRemaining .. " pilots left\n"
|
|
trigger.action.outText(msg, 30, true)
|
|
trigger.action.outSound(limitedAirframes.warningSound)--"Quest Snare 3.wav")
|
|
end
|
|
|
|
function limitedAirframes.redirectToggleAirFrames(args)
|
|
timer.scheduleFunction(limitedAirframes.doToggleAirFrames, args, timer.getTime() + 0.1)
|
|
end
|
|
|
|
function limitedAirframes.doToggleAirFrames(args)
|
|
limitedAirframes.enabled = not limitedAirframes.enabled
|
|
limitedAirframes.setCommsMenu()
|
|
local desc = "\n\nPilot Count rule NOW IN EFFECT\n\n"
|
|
|
|
if limitedAirframes.enabled then
|
|
trigger.action.outSound(limitedAirframes.warningSound)--"Quest Snare 3.wav")
|
|
else
|
|
desc = "\n\nYou cowardly disabled Pilot Count\n\n"
|
|
trigger.action.outSound(limitedAirframes.loseSound)--"Death PIANO.wav")
|
|
end
|
|
trigger.action.outText(desc, 30)
|
|
limitedAirframes.setCommsMenu()
|
|
end
|
|
|
|
--
|
|
-- CSAR CALLBACK (called by CSAR Manager)
|
|
--
|
|
|
|
function limitedAirframes.pilotsRescued(theCoalition, success, numRescued, notes)
|
|
local availablePilots = 0
|
|
if theCoalition == 1 then -- red
|
|
limitedAirframes.currRed = limitedAirframes.currRed + numRescued
|
|
-- pass it along
|
|
cfxZones.setFlagValueMult(limitedAirframes.numRed, limitedAirframes.currRed, limitedAirframes.config)
|
|
|
|
if limitedAirframes.currRed > limitedAirframes.maxRed then
|
|
limitedAirframes.currRed = limitedAirframes.maxRed
|
|
end
|
|
availablePilots = limitedAirframes.currRed
|
|
if limitedAirframes.maxRed < 0 then
|
|
availablePilots = "unlimited"
|
|
end
|
|
end
|
|
|
|
if theCoalition == 2 then -- blue
|
|
limitedAirframes.currBlue = limitedAirframes.currBlue + numRescued
|
|
-- pass it along
|
|
cfxZones.setFlagValueMult(limitedAirframes.numBlue, limitedAirframes.currBlue, limitedAirframes.config)
|
|
|
|
|
|
if limitedAirframes.currBlue > limitedAirframes.maxBlue then
|
|
limitedAirframes.currBlue = limitedAirframes.maxBlue
|
|
end
|
|
availablePilots = limitedAirframes.currBlue
|
|
if limitedAirframes.maxBlue < 0 then
|
|
availablePilots = "unlimited"
|
|
end
|
|
end
|
|
trigger.action.outTextForCoalition(theCoalition, "\nPilots returned to flight line, you now have " .. availablePilots..".\n", 30)
|
|
trigger.action.outSoundForCoalition(theCoalition, limitedAirframes.warningSound)--"Quest Snare 3.wav")
|
|
end
|
|
|
|
--
|
|
-- START
|
|
--
|
|
|
|
function limitedAirframes.start()
|
|
if not dcsCommon.libCheck("cfx Limited Airframes",
|
|
limitedAirframes.requiredLibs) then
|
|
return false
|
|
end
|
|
|
|
-- override config settings if defined as zone
|
|
limitedAirframes.readConfigZone()
|
|
|
|
-- set output flags
|
|
cfxZones.setFlagValueMult(limitedAirframes.numBlue, limitedAirframes.currBlue, limitedAirframes.config)
|
|
cfxZones.setFlagValueMult(limitedAirframes.numRed, limitedAirframes.currRed, limitedAirframes.config)
|
|
|
|
-- collect all zones that are airframe safe
|
|
local afsZones = cfxZones.zonesWithProperty("pilotSafe")
|
|
|
|
-- now add all zones to my zones table, and init additional info
|
|
-- from properties
|
|
for k, aZone in pairs(afsZones) do
|
|
limitedAirframes.addSafeZone(aZone)
|
|
end
|
|
|
|
-- check that sides with limited airframes also have at least one
|
|
-- pilotsafe zone
|
|
if limitedAirframes.maxRed > 0 then
|
|
local safeAndSound = false
|
|
for idx, theZone in pairs(limitedAirframes.safeZones) do
|
|
if theZone.redSafe then safeAndSound = true end
|
|
end
|
|
if not safeAndSound then
|
|
trigger.action.outText("+++limA: WARNING - RED has no safe zone to change air frames", 30)
|
|
end
|
|
end
|
|
if limitedAirframes.maxBlue > 0 then
|
|
local safeAndSound = false
|
|
for idx, theZone in pairs(limitedAirframes.safeZones) do
|
|
if theZone.blueSafe then safeAndSound = true end
|
|
end
|
|
if not safeAndSound then
|
|
trigger.action.outText("+++limA: WARNING - BLUE has no safe zone to change air frames", 30)
|
|
end
|
|
end
|
|
|
|
-- connect player callback
|
|
-- install callbacks for airframe-related events
|
|
dcsCommon.addEventHandler(limitedAirframes.somethingHappened, limitedAirframes.preProcessor, limitedAirframes.postProcessor)
|
|
|
|
|
|
-- set current values
|
|
limitedAirframes.currRed = limitedAirframes.maxRed
|
|
limitedAirframes.currBlue = limitedAirframes.maxBlue
|
|
|
|
-- collect active player unit names
|
|
local allPlayerUnits = cfxPlayer.getAllExistingPlayerUnitsRaw()
|
|
for i=1, #allPlayerUnits do
|
|
local aUnit = allPlayerUnits[i]
|
|
limitedAirframes.addPlayerUnit(aUnit)
|
|
-- trigger.action.outText("limAir: detected active player unit " .. aUnit:getName(), 30)
|
|
end
|
|
|
|
|
|
-- allow configuration menu
|
|
if limitedAirframes.userCanToggle then
|
|
limitedAirframes.setCommsMenu()
|
|
end
|
|
|
|
-- connect to csarManager if present
|
|
if csarManager and csarManager.installCallback then
|
|
csarManager.installCallback(limitedAirframes.pilotsRescued)
|
|
trigger.action.outText("+++limA: connected to csar manager", 30)
|
|
else
|
|
trigger.action.outText("+++limA: NO CSAR integration", 30)
|
|
end
|
|
|
|
-- say hi
|
|
trigger.action.outText("cf/x Limited Airframes v" .. limitedAirframes.version .. " started: R:".. limitedAirframes.maxRed .. "/B:" .. limitedAirframes.maxBlue, 30)
|
|
return true
|
|
end
|
|
|
|
if not limitedAirframes.start() then
|
|
limitedAirframes = nil
|
|
trigger.action.outText("cf/x Limited Airframes aborted: missing libraries", 30)
|
|
end
|
|
|
|
|
|
|
|
--[[--
|
|
safe ditch: check airspeed and altitude. ditch only counts if less than 10m and 2 kts
|
|
report number of airframes left via second instance in switch off menu
|
|
so it can report only one side
|
|
--]]-- |