mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 2.3.1
- Code hardening - Experimental: inferno and airtank modules
This commit is contained in:
parent
f7a8705aa5
commit
695303940f
Binary file not shown.
Binary file not shown.
@ -560,6 +560,10 @@ end
|
|||||||
-- Start
|
-- Start
|
||||||
--
|
--
|
||||||
function FARPZones.releaseFARPS()
|
function FARPZones.releaseFARPS()
|
||||||
|
if FARPZones.verbose then
|
||||||
|
trigger.action.outText("FARPz: releasing FARP ownership to mission in progress", 30)
|
||||||
|
end
|
||||||
|
|
||||||
for idx, aFarp in pairs(FARPZones.lockup) do
|
for idx, aFarp in pairs(FARPZones.lockup) do
|
||||||
aFarp:autoCapture(true)
|
aFarp:autoCapture(true)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
LZ = {}
|
LZ = {}
|
||||||
LZ.version = "1.1.0"
|
LZ.version = "1.2.1"
|
||||||
LZ.verbose = false
|
LZ.verbose = false
|
||||||
LZ.ups = 1
|
LZ.ups = 1
|
||||||
LZ.requiredLibs = {
|
LZ.requiredLibs = {
|
||||||
@ -15,7 +15,8 @@ LZ.LZs = {}
|
|||||||
Version History
|
Version History
|
||||||
1.0.0 - initial version
|
1.0.0 - initial version
|
||||||
1.1.0 - persistence
|
1.1.0 - persistence
|
||||||
|
1.2.0 - dcs 2024-07-11 and dcs 2024-07-22 updates (new events)
|
||||||
|
1.2.1 - theZone --> aZone typo at input management
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
function LZ.addLZ(theZone)
|
function LZ.addLZ(theZone)
|
||||||
@ -216,7 +217,9 @@ function LZ:onEvent(event)
|
|||||||
|
|
||||||
-- only interested in S_EVENT_TAKEOFF and events
|
-- only interested in S_EVENT_TAKEOFF and events
|
||||||
if event.id ~= world.event.S_EVENT_TAKEOFF and
|
if event.id ~= world.event.S_EVENT_TAKEOFF and
|
||||||
event.id ~= world.event.S_EVENT_LAND then
|
event.id ~= world.event.S_EVENT_LAND and
|
||||||
|
event.id ~= world.event.S_EVENT_RUNWAY_TAKEOFF and
|
||||||
|
event.id ~= world.event.S_EVENT_RUNWAY_TOUCH then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -231,14 +234,20 @@ function LZ:onEvent(event)
|
|||||||
-- see if this unit interests us at all
|
-- see if this unit interests us at all
|
||||||
if LZ.unitIsInterestingForZone(theUnit, aZone) then
|
if LZ.unitIsInterestingForZone(theUnit, aZone) then
|
||||||
-- interesting unit in zone triggered the event
|
-- interesting unit in zone triggered the event
|
||||||
if aZone.lzDeparted and event.id == world.event.S_EVENT_TAKEOFF then
|
if aZone.lzDeparted and
|
||||||
|
(event.id == world.event.S_EVENT_TAKEOFF or
|
||||||
|
event.id == world.event.S_EVENT_RUNWAY_TAKEOFF)
|
||||||
|
then
|
||||||
if LZ.verbose or aZone.verbose then
|
if LZ.verbose or aZone.verbose then
|
||||||
trigger.action.outText("+++LZ: detected departure from <" .. aZone.name .. ">", 30)
|
trigger.action.outText("+++LZ: detected departure from <" .. aZone.name .. ">", 30)
|
||||||
end
|
end
|
||||||
cfxZones.pollFlag(aZone.lzDeparted, aZone.lzMethod, aZone)
|
cfxZones.pollFlag(aZone.lzDeparted, aZone.lzMethod, aZone)
|
||||||
end
|
end
|
||||||
|
|
||||||
if aZone.lzLanded and event.id == world.event.S_EVENT_LAND then
|
if aZone.lzLanded and
|
||||||
|
(event.id == world.event.S_EVENT_LAND or
|
||||||
|
event.id == world.event.S_EVENT_RUNWAY_TOUCH)
|
||||||
|
then
|
||||||
if LZ.verbose or aZone.verbose then
|
if LZ.verbose or aZone.verbose then
|
||||||
trigger.action.outText("+++LZ: detected landing in <" .. aZone.name .. ">", 30)
|
trigger.action.outText("+++LZ: detected landing in <" .. aZone.name .. ">", 30)
|
||||||
end
|
end
|
||||||
@ -264,14 +273,14 @@ function LZ.update()
|
|||||||
for idx, aZone in pairs(LZ.LZs) do
|
for idx, aZone in pairs(LZ.LZs) do
|
||||||
-- see if we are being paused or unpaused
|
-- see if we are being paused or unpaused
|
||||||
if cfxZones.testZoneFlag(aZone, aZone.lzPause, aZone.LZTriggerMethod, "lzLastPause") then
|
if cfxZones.testZoneFlag(aZone, aZone.lzPause, aZone.LZTriggerMethod, "lzLastPause") then
|
||||||
if LZ.verbose or theZone.verbose then
|
if LZ.verbose or aZone.verbose then
|
||||||
trigger.action.outText("+++LZ: triggered pause? for <".. aZone.name ..">", 30)
|
trigger.action.outText("+++LZ: triggered pause? for <".. aZone.name ..">", 30)
|
||||||
end
|
end
|
||||||
aZone.isPaused = true
|
aZone.isPaused = true
|
||||||
end
|
end
|
||||||
|
|
||||||
if cfxZones.testZoneFlag(aZone, aZone.lzContinue, aZone.LZTriggerMethod, "lzLastContinue") then
|
if cfxZones.testZoneFlag(aZone, aZone.lzContinue, aZone.LZTriggerMethod, "lzLastContinue") then
|
||||||
if LZ.verbose or theZone.verbose then
|
if LZ.verbose or aZone.verbose then
|
||||||
trigger.action.outText("+++LZ: triggered continue? for <".. aZone.name ..">", 30)
|
trigger.action.outText("+++LZ: triggered continue? for <".. aZone.name ..">", 30)
|
||||||
end
|
end
|
||||||
aZone.isPaused = false
|
aZone.isPaused = false
|
||||||
|
|||||||
631
modules/airtank.lua
Normal file
631
modules/airtank.lua
Normal file
@ -0,0 +1,631 @@
|
|||||||
|
airtank = {}
|
||||||
|
airtank.version = "0.9.9"
|
||||||
|
-- Module to extinguish fires controlled by the 'inferno' module.
|
||||||
|
-- For 'airtank' fire extinguisher aircraft modules.
|
||||||
|
airtank.requiredLibs = {
|
||||||
|
"dcsCommon",
|
||||||
|
"cfxZones",
|
||||||
|
}
|
||||||
|
airtank.tanks = {} -- player data by GROUP name, will break with multi-unit groups
|
||||||
|
-- tank attributes
|
||||||
|
-- pumpArmed -- for hovering fills.
|
||||||
|
-- armed -- for triggering drops below trigger alt-
|
||||||
|
-- dropping -- if drop has triggered
|
||||||
|
-- carrying -- how mach retardant / fluid am I carrying?
|
||||||
|
-- capacity -- how much can I carry
|
||||||
|
|
||||||
|
-- uName
|
||||||
|
-- pName
|
||||||
|
-- theUnit
|
||||||
|
-- gID
|
||||||
|
-- lastDeparture -- timestamp to avoid double notifications
|
||||||
|
-- lastLanding -- timestamp to avoid double dip
|
||||||
|
|
||||||
|
airtank.zones = {} -- come here to refill your tanks
|
||||||
|
|
||||||
|
airtank.roots = {} -- roots for player by group name
|
||||||
|
airtank.mainMenu = nil -- handles attachTo:
|
||||||
|
|
||||||
|
airtank.types = {"Mi-8MT", "UH-1H", "Mi-24P", "OH58D", "CH-47Fbl1"} -- which helicopters/planes can firefight and get menu. can be amended with config and zone
|
||||||
|
airtank.capacities = {
|
||||||
|
["UH-1H"] = 1200,
|
||||||
|
["Mi-8MT"] = 4000,
|
||||||
|
["Mi-24P"] = 2400,
|
||||||
|
["CH-47Fbl1"] = 9000,
|
||||||
|
["OH58D"] = 500,
|
||||||
|
} -- how much each helo can carry. default is 500kg, can be amended with zone
|
||||||
|
airtank.pumpSpeed = 100 -- liters/kg per second, for all helos same
|
||||||
|
airtank.dropSpeed = 1000 -- liters per second, for all helos same
|
||||||
|
airtank.releaseAlt = 100 -- m = 90 ft
|
||||||
|
airtank.pumpAlt = 10 -- m = 30 feet
|
||||||
|
--
|
||||||
|
-- airtank zones - land inside to refill. can have limited capa
|
||||||
|
--
|
||||||
|
function airtank.addZone(theZone)
|
||||||
|
airtank.zones[theZone.name] = theZone
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.readZone(theZone)
|
||||||
|
theZone.capacity = theZone:getNumberFromZoneProperty("airtank", 99999999) -- should be enough
|
||||||
|
theZone.amount = theZone:getNumberFromZoneProperty("amount", theZone.capacity)
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.refillWithZone(theZone, data)
|
||||||
|
local theUnit = data.theUnit
|
||||||
|
local wanted = data.capacity - data.carrying
|
||||||
|
if theZone.amount > wanted then
|
||||||
|
theZone.amount = theZone.amount - wanted
|
||||||
|
data.carrying = data.capacity
|
||||||
|
trigger.action.outTextForGroup(data.gID, "Roger, " .. data.uName .. ", topped up tanks, you now carry " .. data.carrying .. "kg of flame retardant.", 30)
|
||||||
|
trigger.action.outSoundForGroup(data.gID, airtank.actionSound)
|
||||||
|
trigger.action.setUnitInternalCargo(data.uName, data.carrying + 10)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
trigger.action.outTextForGroup(data.gID, "Negative, " .. data.uName .. ", out of flame retardant.", 30)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- event handling
|
||||||
|
--
|
||||||
|
function airtank:onEvent(theEvent)
|
||||||
|
-- catch birth events of helos
|
||||||
|
if not theEvent then return end
|
||||||
|
local theUnit = theEvent.initiator
|
||||||
|
if not theUnit then return end
|
||||||
|
if not theUnit.getPlayerName then return end
|
||||||
|
local pName = theUnit:getPlayerName()
|
||||||
|
if not pName then return end
|
||||||
|
-- we have a player unit
|
||||||
|
if not dcsCommon.unitIsOfLegalType(theUnit, airtank.types) then
|
||||||
|
if airtank.verbose then
|
||||||
|
trigger.action.outText("aTnk: unit <" .. theUnit:getName() .. ">, type <" .. theUnit:getTypeName() .. "> not an airtank.", 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local uName = theUnit:getName()
|
||||||
|
local uType = theUnit:getTypeName()
|
||||||
|
local theGroup = theUnit:getGroup()
|
||||||
|
local gName = theGroup:getName()
|
||||||
|
|
||||||
|
if theEvent.id == 15 then -- birth
|
||||||
|
-- make sure this aircraft is legit
|
||||||
|
airtank.installMenusForUnit(theUnit)
|
||||||
|
local theData = airtank.newData(theUnit)
|
||||||
|
theData.pName = pName
|
||||||
|
airtank.tanks[gName] = theData
|
||||||
|
if airtank.verbose then
|
||||||
|
trigger.action.outText("+++aTnk: new player airtank <" .. uName .. "> type <" .. uType .. "> for <" .. pName .. ">", 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if theEvent.id == 4 or -- land
|
||||||
|
theEvent.id == 55 -- runway touch
|
||||||
|
then
|
||||||
|
-- see if landed inside a refill zone and
|
||||||
|
-- automatically top off if pumpArmed
|
||||||
|
local data = airtank.tanks[gName]
|
||||||
|
if data and data.lastLanding then return end -- no double dip
|
||||||
|
if data and data.pumpArmed then
|
||||||
|
data.lastLanding = timer.getTime()
|
||||||
|
data.lastDeparture = nil
|
||||||
|
local p = theUnit:getPoint()
|
||||||
|
for idx, theZone in pairs (airtank.zones) do
|
||||||
|
if theZone:pointInZone(p) then
|
||||||
|
if airtank.refillWithZone(theZone, data) then
|
||||||
|
data.armed = false
|
||||||
|
data.pumpArmed = false
|
||||||
|
return
|
||||||
|
end -- if refill received
|
||||||
|
end -- if in zone
|
||||||
|
end -- for zones
|
||||||
|
elseif data then
|
||||||
|
data.lastLanding = timer.getTime()
|
||||||
|
data.lastDeparture = nil
|
||||||
|
local p = theUnit:getPoint()
|
||||||
|
for idx, theZone in pairs (airtank.zones) do
|
||||||
|
if theZone:pointInZone(p) then
|
||||||
|
trigger.action.outTextForGroup(data.gID, "Welcome to " .. theZone.name .. ", " .. pName .. ", firefighting services are available.", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if theEvent.id == 3 or -- takeoff
|
||||||
|
theEvent.id == 54 -- runway takeoff
|
||||||
|
then
|
||||||
|
local data = airtank.tanks[gName]
|
||||||
|
local now = timer.getTime()
|
||||||
|
if data then
|
||||||
|
data.lastLanding = nil
|
||||||
|
-- suppress double take-off notifications for 20 seconds
|
||||||
|
if data.lastDeparture then -- and data.lastDeparture + 60 < now then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
data.lastDeparture = now
|
||||||
|
if data.carrying < data.capacity * 0.5 then
|
||||||
|
trigger.action.outTextForGroup(data.gID, "Good luck, " .. pName .. ", remember to top off your tanks before going in.", 30)
|
||||||
|
else
|
||||||
|
trigger.action.outTextForGroup(data.gID, "Good luck and godspeed, " .. pName .. "!", 30)
|
||||||
|
end
|
||||||
|
trigger.action.outSoundForGroup(data.gID, airtank.actionSound)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.newData(theUnit)
|
||||||
|
local theType = theUnit:getTypeName()
|
||||||
|
local data = {}
|
||||||
|
local capa = airtank.capacities[theType]
|
||||||
|
if not capa then capa = 500 end -- default capa.
|
||||||
|
data.capacity = capa
|
||||||
|
data.carrying = 0
|
||||||
|
data.pumpArmed = false
|
||||||
|
data.armed = false
|
||||||
|
data.dropping = false
|
||||||
|
data.uName = theUnit:getName()
|
||||||
|
data.pName = theUnit:getPlayerName()
|
||||||
|
data.theUnit = theUnit
|
||||||
|
local theGroup = theUnit:getGroup()
|
||||||
|
data.gID = theGroup:getID()
|
||||||
|
trigger.action.setUnitInternalCargo(data.uName, data.carrying + 10)
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
--
|
||||||
|
-- comms
|
||||||
|
--
|
||||||
|
function airtank.installMenusForUnit(theUnit) -- assumes all unfit types are weeded out
|
||||||
|
-- if already exists, remove old
|
||||||
|
if not theUnit then return end
|
||||||
|
if not Unit.isExist(theUnit) then return end
|
||||||
|
local theGroup = theUnit:getGroup()
|
||||||
|
local uName = theUnit:getName()
|
||||||
|
local uType = theUnit:getTypeName()
|
||||||
|
local pName = theUnit:getPlayerName()
|
||||||
|
local gName = theGroup:getName()
|
||||||
|
local gID = theGroup:getID()
|
||||||
|
local pRoot = airtank.roots[gName]
|
||||||
|
if pRoot then
|
||||||
|
missionCommands.removeItemForGroup(gID, pRoot)
|
||||||
|
end
|
||||||
|
-- now add the airtank menu
|
||||||
|
pRoot = missionCommands.addSubMenuForGroup(gID, airtank.menuName, airtank.mainMenu)
|
||||||
|
airtank.roots[gName] = pRoot -- save for later
|
||||||
|
local args = {gName, uName, gID, uType, pName}
|
||||||
|
-- menus:
|
||||||
|
-- status: loaded, capa, armed etc
|
||||||
|
-- ready pump -- turn off drop system and start sucking, if landing in zone will fully charge, else suck in water when hovering over water when low enough (below 10m)
|
||||||
|
-- arm release -- get ready to drop. auto-release when alt is below 30m
|
||||||
|
local m1 = missionCommands.addCommandForGroup(gID , "Tank Status" , pRoot, airtank.redirectStatus, args)
|
||||||
|
local mx2 = missionCommands.addCommandForGroup(gID , "MANUAL RELEASE" , pRoot, airtank.redirectManDrop, args)
|
||||||
|
local m2 = missionCommands.addCommandForGroup(gID , "*Arm*AUTODROP*trigger" , pRoot, airtank.redirectArmDrop, args)
|
||||||
|
local m3 = missionCommands.addCommandForGroup(gID , "Activate/Ready intake" , pRoot, airtank.redirectArmPump, args)
|
||||||
|
local m4 = missionCommands.addCommandForGroup(gID , "Secure ship" , pRoot, airtank.redirectSecure, args)
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.redirectStatus(args)
|
||||||
|
timer.scheduleFunction(airtank.doStatus, args, timer.getTime() + 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.doStatus(args)
|
||||||
|
local gName = args[1]
|
||||||
|
local uName = args[2]
|
||||||
|
local gID = args[3]
|
||||||
|
local uType = args[4]
|
||||||
|
local pName = args[5]
|
||||||
|
local ralm = airtank.releaseAlt
|
||||||
|
local ralf = math.floor(ralm * 3.28084)
|
||||||
|
local data = airtank.tanks[gName]
|
||||||
|
local remains = data.capacity - data.carrying
|
||||||
|
local msg = "\nAirtank <" .. uName .. "> (" .. uType .. "), commanded by " .. pName .. "\n capacity: " .. data.capacity .. "kg, carrying " .. data.carrying .. "kg (free " .. remains .. "kg)"
|
||||||
|
-- add info to nearest refuel zone?
|
||||||
|
if data.armed then msg = msg .. "\n\n *** RELEASE TRIGGER ARMED (" .. ralm .. "m/" .. ralf .. "ft AGL)***" end
|
||||||
|
ralm = airtank.pumpAlt
|
||||||
|
ralf = math.floor(ralm * 3.28084)
|
||||||
|
if data.pumpArmed then msg = msg .. "\n\n --- intake pumps ready (below " .. ralm .. "m/" .. ralf .. "ft AGL)" end
|
||||||
|
msg = msg .. "\n"
|
||||||
|
trigger.action.outTextForGroup(gID, msg, 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function airtank.redirectManDrop(args)
|
||||||
|
timer.scheduleFunction(airtank.doManDrop, args, timer.getTime() + 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.doManDrop(args)
|
||||||
|
local gName = args[1]
|
||||||
|
local uName = args[2]
|
||||||
|
local theUnit = Unit.getByName(uName)
|
||||||
|
local gID = args[3]
|
||||||
|
local uType = args[4]
|
||||||
|
local pName = args[5]
|
||||||
|
local data = airtank.tanks[gName]
|
||||||
|
local remains = data.capacity - data.carrying
|
||||||
|
local alt = dcsCommon.getUnitAGL(theUnit)
|
||||||
|
local ralm = math.floor(alt)
|
||||||
|
local ralf = math.floor(ralm * 3.28084)
|
||||||
|
local msg = ""
|
||||||
|
local agl = dcsCommon.getUnitAGL(theUnit)
|
||||||
|
if not theUnit:inAir() then
|
||||||
|
trigger.action.outTextForGroup(data.gID, "Please get into the air before releasing flame retardant.", 30)
|
||||||
|
trigger.action.outSoundForGroup(data.gID, airtank.actionSound)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if data.carrying < 1 then
|
||||||
|
msg = "\nRetard tanks empty. Your " .. uType .. " can carry up to " .. data.capacity .. "kg.\n"
|
||||||
|
data.armed = false
|
||||||
|
data.dropping = false
|
||||||
|
data.pumpArmed = false
|
||||||
|
elseif data.carrying < 100 then
|
||||||
|
msg = "\nTanks empty (" .. data.carrying .. "kg left), safeties are engaged.\n"
|
||||||
|
data.armed = false
|
||||||
|
data.dropping = false
|
||||||
|
data.pumpArmed = false
|
||||||
|
else
|
||||||
|
msg = "\n *** Opened drop valve at " .. ralm .. "m/" .. ralf .. "ft RALT.\n"
|
||||||
|
data.armed = false
|
||||||
|
data.pumpArmed = false
|
||||||
|
data.dropResults = {}
|
||||||
|
data.dropping = true
|
||||||
|
trigger.action.outTextForGroup(gID, msg, 30)
|
||||||
|
trigger.action.outSoundForGroup(gID, airtank.actionSound)
|
||||||
|
airtank.dropFor(data)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
trigger.action.outTextForGroup(gID, msg, 30)
|
||||||
|
trigger.action.outSoundForGroup(gID, airtank.actionSound)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function airtank.redirectArmDrop(args)
|
||||||
|
timer.scheduleFunction(airtank.doArmDrop, args, timer.getTime() + 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.doArmDrop(args)
|
||||||
|
local gName = args[1]
|
||||||
|
local uName = args[2]
|
||||||
|
local theUnit = Unit.getByName(uName)
|
||||||
|
local gID = args[3]
|
||||||
|
local uType = args[4]
|
||||||
|
local pName = args[5]
|
||||||
|
local data = airtank.tanks[gName]
|
||||||
|
local remains = data.capacity - data.carrying
|
||||||
|
local ralm = airtank.releaseAlt
|
||||||
|
local ralf = math.floor(ralm * 3.28084)
|
||||||
|
local msg = ""
|
||||||
|
local agl = dcsCommon.getUnitAGL(theUnit)
|
||||||
|
if data.carrying < 1 then
|
||||||
|
msg = "\nRetard tanks empty. Your " .. uType .. " can carry up to " .. data.capacity .. "kg.\n"
|
||||||
|
data.armed = false
|
||||||
|
data.dropping = false
|
||||||
|
data.pumpArmed = false
|
||||||
|
elseif agl < airtank.releaseAlt then
|
||||||
|
msg = "Get above " .. ralm .. "m/" .. ralf .. "ft ALG (radar) to arm trigger."
|
||||||
|
elseif data.carrying < 100 then
|
||||||
|
msg = "\nTank empty (" .. data.carrying .. "kg left), safeties are engaged.\n"
|
||||||
|
data.armed = false
|
||||||
|
data.dropping = false
|
||||||
|
data.pumpArmed = false
|
||||||
|
else
|
||||||
|
msg = "\n *** Release valve primed to trigger below " .. ralm .. "m/" .. ralf .. "ft RALT.\n\nRelease starts automatically at or below trigger altitude.\n"
|
||||||
|
data.armed = true
|
||||||
|
data.dropping = false
|
||||||
|
data.pumpArmed = false
|
||||||
|
end
|
||||||
|
trigger.action.outTextForGroup(gID, msg, 30)
|
||||||
|
trigger.action.outSoundForGroup(gID, airtank.actionSound)
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.redirectArmPump(args)
|
||||||
|
timer.scheduleFunction(airtank.doArmPump, args, timer.getTime() + 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.doArmPump(args)
|
||||||
|
local gName = args[1]
|
||||||
|
local uName = args[2]
|
||||||
|
local theUnit = Unit.getByName(uName)
|
||||||
|
local gID = args[3]
|
||||||
|
local uType = args[4]
|
||||||
|
local pName = args[5]
|
||||||
|
local data = airtank.tanks[gName]
|
||||||
|
local remains = data.capacity - data.carrying
|
||||||
|
local ralm = airtank.pumpAlt
|
||||||
|
local ralf = math.floor(ralm * 3.28084)
|
||||||
|
local msg = ""
|
||||||
|
-- if we are on the ground, check if we are inside a
|
||||||
|
-- zone that can refill us
|
||||||
|
if not theUnit:inAir() then
|
||||||
|
local p = theUnit:getPoint()
|
||||||
|
for idx, theZone in pairs (airtank.zones) do
|
||||||
|
if theZone:pointInZone(p) then
|
||||||
|
if airtank.refillWithZone(theZone, data) then
|
||||||
|
data.armed = false
|
||||||
|
data.pumpArmed = false
|
||||||
|
data.dropping = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
msg = "\n *** Intake valves ready, descend to " .. ralm .. "m/" .. ralf .. "ft RALT over water, or land at a firefighting supply base.\n"
|
||||||
|
data.armed = false
|
||||||
|
data.dropping = false
|
||||||
|
data.pumpArmed = true
|
||||||
|
|
||||||
|
trigger.action.outTextForGroup(gID, msg, 30)
|
||||||
|
trigger.action.outSoundForGroup(gID, airtank.actionSound)
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.redirectSecure(args)
|
||||||
|
timer.scheduleFunction(airtank.doSecure, args, timer.getTime() + 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.doSecure(args)
|
||||||
|
local gName = args[1]
|
||||||
|
local gID = args[3]
|
||||||
|
local data = airtank.tanks[gName]
|
||||||
|
local msg = ""
|
||||||
|
msg = "\n All valves secure and stored for cruise operation\n"
|
||||||
|
data.armed = false
|
||||||
|
data.dropping = false
|
||||||
|
data.pumpArmed = false
|
||||||
|
trigger.action.outTextForGroup(gID, msg, 30)
|
||||||
|
trigger.action.outSoundForGroup(gID, airtank.actionSound)
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- update
|
||||||
|
--
|
||||||
|
function airtank.dropFor(theData) -- drop onto ground/fire
|
||||||
|
local theUnit = theData.theUnit
|
||||||
|
local qty = airtank.dropSpeed
|
||||||
|
if qty > theData.carrying then qty = math.floor(theData.carrying) end
|
||||||
|
-- calculate position where it will hit
|
||||||
|
local alt = dcsCommon.getUnitAGL(theUnit)
|
||||||
|
if alt < 0 then alt = 0 end
|
||||||
|
local vel = theUnit:getVelocity() -- vec 3, we only need x and z to project the point where the water will impact (we ignore vel.z)
|
||||||
|
-- calculation: agl=height, no downward vel, will accelerate at G= 10 m/ss
|
||||||
|
-- i.e. t = sqrt(2*agl/10)
|
||||||
|
local agl = dcsCommon.getUnitAGL(theUnit)
|
||||||
|
local t = math.sqrt(0.2*agl)
|
||||||
|
local p = theUnit:getPoint()
|
||||||
|
local impact = {x = p.x + t * vel.x, y = 0, z = p.z + t * vel.z}
|
||||||
|
|
||||||
|
-- tell inferno about it, get some feedback
|
||||||
|
if inferno.waterDropped then
|
||||||
|
local diag = inferno.waterDropped(impact, qty, theData)
|
||||||
|
if diag then table.insert(theData.dropResults, diag) end
|
||||||
|
else
|
||||||
|
trigger.action.outText("WARNING: airtank can't find 'inferno' module.", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update what we have left in tank
|
||||||
|
theData.carrying = theData.carrying - qty
|
||||||
|
if theData.carrying < 0 then theData.carrying = 0 end
|
||||||
|
local ralm = math.floor(agl)
|
||||||
|
local ralf = math.floor(ralm * 3.28084)
|
||||||
|
|
||||||
|
local msg = "Dropping " .. qty .. "kg at RALT " .. ralm .. "m/" .. ralf .. "ft, " .. theData.carrying .. " kg remaining"
|
||||||
|
local snd = airtank.releaseSound
|
||||||
|
|
||||||
|
-- close vent if empty
|
||||||
|
if theData.carrying < 100 then
|
||||||
|
-- close hatch
|
||||||
|
theData.dropping = false
|
||||||
|
theData.armed = false
|
||||||
|
msg = msg .. ", CLOSING VENTS\n\n"
|
||||||
|
snd = airtank.actionSound
|
||||||
|
-- add all drop diagnoses
|
||||||
|
if #theData.dropResults < 1 then
|
||||||
|
msg = msg .. "No discernible results.\n"
|
||||||
|
else
|
||||||
|
msg = msg .. "Good delivey:\n"
|
||||||
|
for idx, res in pairs(theData.dropResults) do
|
||||||
|
msg = msg .. " - " .. res .. "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- set internal cargo
|
||||||
|
trigger.action.setUnitInternalCargo(theData.uName, theData.carrying + 10)
|
||||||
|
-- say how much we dropped and (if so) what we are over
|
||||||
|
trigger.action.outTextForGroup(theData.gID, msg, 30, true)
|
||||||
|
trigger.action.outSoundForGroup(theData.gID, snd)
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.updateDataFor(theData)
|
||||||
|
local theUnit = theData.theUnit
|
||||||
|
-- see if we are dropping
|
||||||
|
if theData.dropping then
|
||||||
|
-- valve is open
|
||||||
|
airtank.dropFor(theData) -- drop contents of tank, sets weight
|
||||||
|
elseif theData.armed then
|
||||||
|
-- see if we are below 10*trigger
|
||||||
|
local alt = dcsCommon.getUnitAGL(theUnit)
|
||||||
|
if alt < 10 * airtank.releaseAlt then
|
||||||
|
-- see if we trigger
|
||||||
|
if alt <= airtank.releaseAlt then
|
||||||
|
-- !! trigger flow
|
||||||
|
theData.dropResults = {}
|
||||||
|
theData.dropping = true
|
||||||
|
-- trigger.action.outText("allocated dropResults", 30)
|
||||||
|
theData.armed = false
|
||||||
|
airtank.dropFor(theData) -- sets weight
|
||||||
|
trigger.action.outSoundForGroup(theData.gID, airtank.releaseSound)
|
||||||
|
else
|
||||||
|
-- flash current alt and say when we will trigger
|
||||||
|
local calm = math.floor(alt)
|
||||||
|
local calf = math.floor(calm * 3.28084)
|
||||||
|
local ralm = airtank.releaseAlt
|
||||||
|
local ralf = math.floor(ralm * 3.28084)
|
||||||
|
trigger.action.outTextForGroup(theData.gID, "Current RALT " .. calm .. "m/" .. calf .. "ft, will release at " .. ralm .. "m/" .. ralf .. "ft", 30, true) -- erase all
|
||||||
|
trigger.action.outSoundForGroup(theData.gID, airtank.blipSound)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- see if the intake valve is open
|
||||||
|
elseif theData.pumpArmed then
|
||||||
|
p = theUnit:getPoint()
|
||||||
|
local sType = land.getSurfaceType({x=p.x, y=p.z})
|
||||||
|
if sType ~= 2 and sType ~= 3 then -- not over water
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local alt = dcsCommon.getUnitAGL(theUnit)
|
||||||
|
local calm = math.floor(alt)
|
||||||
|
local calf = math.floor(calm * 3.28084)
|
||||||
|
if alt < 5 * airtank.pumpAlt then
|
||||||
|
local msg = "RALT " .. calm .. "m/" .. calf .. "ft, "
|
||||||
|
if alt <= airtank.pumpAlt then -- in pump range
|
||||||
|
theData.carrying = theData.carrying + airtank.pumpSpeed
|
||||||
|
if theData.carrying > theData.capacity then theData.carrying = theData.capacity end
|
||||||
|
trigger.action.setUnitInternalCargo(theData.uName, theData.carrying + 10)
|
||||||
|
end
|
||||||
|
msg = msg .. theData.carrying .. "/" .. theData.capacity .. "kg"
|
||||||
|
if theData.carrying >= theData.capacity - 50 then
|
||||||
|
theData.pumpArmed = false
|
||||||
|
msg = msg .. " PUMP DISENGAGED"
|
||||||
|
end
|
||||||
|
trigger.action.outTextForGroup(theData.gID, msg, 30, true)
|
||||||
|
trigger.action.outSoundForGroup(theData.gID, airtank.pumpSound)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.update() -- update all firefighters
|
||||||
|
timer.scheduleFunction(airtank.update, {}, timer.getTime() + airtank.ups) -- next time
|
||||||
|
local filtered = {} -- filter existing only
|
||||||
|
for gName, data in pairs(airtank.tanks) do
|
||||||
|
local theUnit = data.theUnit
|
||||||
|
if Unit.isExist(theUnit) then
|
||||||
|
if theUnit:inAir() then
|
||||||
|
airtank.updateDataFor(data)
|
||||||
|
end
|
||||||
|
filtered[gName] = data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
airtank.tanks = filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- start and config
|
||||||
|
--
|
||||||
|
function airtank.readConfigZone()
|
||||||
|
airtank.name = "airtankConfig" -- make compatible with dml zones
|
||||||
|
local theZone = cfxZones.getZoneByName("airtankConfig")
|
||||||
|
if not theZone then
|
||||||
|
theZone = cfxZones.createSimpleZone("airtankConfig")
|
||||||
|
end
|
||||||
|
|
||||||
|
airtank.verbose = theZone.verbose
|
||||||
|
airtank.ups = theZone:getNumberFromZoneProperty("ups", 1)
|
||||||
|
airtank.menuName = theZone:getStringFromZoneProperty("menuName", "Firefighting")
|
||||||
|
|
||||||
|
airtank.actionSound = theZone:getStringFromZoneProperty("actionSound", "none")
|
||||||
|
airtank.blipSound = theZone:getStringFromZoneProperty("blipSound", airtank.actionSound)
|
||||||
|
airtank.releaseSound = theZone:getStringFromZoneProperty("releaseSound", airtank.actionSound)
|
||||||
|
airtank.pumpSound = theZone:getStringFromZoneProperty("pumpSound", airtank.actionSound)
|
||||||
|
|
||||||
|
if theZone:hasProperty("attachTo:") then
|
||||||
|
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
|
||||||
|
if radioMenu then -- requires optional radio menu to have loaded
|
||||||
|
local mainMenu = radioMenu.mainMenus[attachTo]
|
||||||
|
if mainMenu then
|
||||||
|
airtank.mainMenu = mainMenu
|
||||||
|
else
|
||||||
|
trigger.action.outText("+++airtank: cannot find super menu <" .. attachTo .. ">", 30)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
trigger.action.outText("+++airtank: REQUIRES radioMenu to run before inferno. 'AttachTo:' ignored.", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add own troop carriers
|
||||||
|
if theZone:hasProperty("airtanks") then
|
||||||
|
local tc = theZone:getStringFromZoneProperty("airtanks", "UH-1D")
|
||||||
|
tc = dcsCommon.splitString(tc, ",")
|
||||||
|
airtank.types = dcsCommon.trimArray(tc)
|
||||||
|
if airtank.verbose then
|
||||||
|
trigger.action.outText("+++aTnk: redefined air tanks to types:", 30)
|
||||||
|
for idx, aType in pairs(airtank.types) do
|
||||||
|
trigger.action.outText(aType, 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add capacities and types from airTankSpecs zone
|
||||||
|
local capaZone = cfxZones.getZoneByName("airTankSpecs")
|
||||||
|
if capaZone then
|
||||||
|
if airtank.verbose then
|
||||||
|
trigger.action.outText("aTnk: found and processing 'airTankSpecs' zone data.", 30)
|
||||||
|
end
|
||||||
|
-- read all into my types registry, replacing whatever is there
|
||||||
|
local rawCapa = cfxZones.getAllZoneProperties(capaZone)
|
||||||
|
local newCapas = airtank.processCapas(rawCapa)
|
||||||
|
-- now types to existing types if not already there
|
||||||
|
for aType, aCapa in pairs(newCapas) do
|
||||||
|
airtank.capacities[aType] = aCapa
|
||||||
|
dcsCommon.addToTableIfNew(airtank.types, aType)
|
||||||
|
if civAir.verbose then
|
||||||
|
trigger.action.outText("+++aTnk: processed aircraft <" .. aType .. "> for capacity <" .. aCapa .. ">", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function airtank.processCapas(rawIn)
|
||||||
|
local newCapas = {}
|
||||||
|
-- now iterate the input table, and generate new types and
|
||||||
|
-- liveries from it
|
||||||
|
for theType, capa in pairs (rawIn) do
|
||||||
|
if airtank.verbose then
|
||||||
|
trigger.action.outText("+++aTnk: processing type <" .. theType .. ">:<" .. capa .. ">", 30)
|
||||||
|
end
|
||||||
|
local pcapa = tonumber(capa)
|
||||||
|
if not pcapa then capa = 0 end
|
||||||
|
newCapas[theType] = pcapa
|
||||||
|
end
|
||||||
|
|
||||||
|
return newCapas
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function airtank.start()
|
||||||
|
if not dcsCommon.libCheck then
|
||||||
|
trigger.action.outText("cfx airtank requires dcsCommon", 30)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if not dcsCommon.libCheck("cfx airtank", airtank.requiredLibs) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- read config
|
||||||
|
airtank.readConfigZone()
|
||||||
|
|
||||||
|
-- process airtank Zones
|
||||||
|
local attrZones = cfxZones.getZonesWithAttributeNamed("airtank")
|
||||||
|
for k, aZone in pairs(attrZones) do
|
||||||
|
airtank.readZone(aZone) -- process attributes
|
||||||
|
airtank.addZone(aZone) -- add to list
|
||||||
|
end
|
||||||
|
|
||||||
|
-- connect event handler
|
||||||
|
world.addEventHandler(airtank)
|
||||||
|
|
||||||
|
-- start update
|
||||||
|
timer.scheduleFunction(airtank.update, {}, timer.getTime() + 1/airtank.ups)
|
||||||
|
|
||||||
|
-- say Hi!
|
||||||
|
trigger.action.outText("cf/x airtank v" .. airtank.version .. " started.", 30)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if not airtank.start() then
|
||||||
|
trigger.action.outText("airtank failed to start up")
|
||||||
|
airtank = nil
|
||||||
|
end
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
civHelo = {}
|
civHelo = {}
|
||||||
civHelo.version = "1.0.0"
|
civHelo.version = "1.0.1"
|
||||||
civHelo.requiredLibs = {
|
civHelo.requiredLibs = {
|
||||||
"dcsCommon", -- always
|
"dcsCommon", -- always
|
||||||
"cfxZones",
|
"cfxZones",
|
||||||
@ -7,7 +7,7 @@ civHelo.requiredLibs = {
|
|||||||
--[[--
|
--[[--
|
||||||
Version History
|
Version History
|
||||||
1.0.0 - Initial version
|
1.0.0 - Initial version
|
||||||
|
1.0.1 - livery hardening
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
civHelo.flights = {} -- currently active flights
|
civHelo.flights = {} -- currently active flights
|
||||||
@ -282,10 +282,24 @@ function civHelo.getType(theZone)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function civHelo.getLiveryForType(theType, theData)
|
function civHelo.getLiveryForType(theType, theData)
|
||||||
|
-- trigger.action.outText("picking liviery for helo type <" .. theType .. ">", 30)
|
||||||
if civHelo.liveries[theType] then
|
if civHelo.liveries[theType] then
|
||||||
local available = civHelo.liveries[theType]
|
local available = civHelo.liveries[theType]
|
||||||
local chosen = dcsCommon.pickRandom(available)
|
if available then
|
||||||
|
-- trigger.action.outText("We have a livery selection available for helo", 30)
|
||||||
|
else
|
||||||
|
-- trigger.action.outText("No liveries for type.", 30)
|
||||||
|
end
|
||||||
|
local chosen = dcsCommon.pickRandom(available)
|
||||||
|
if chosen then
|
||||||
|
-- trigger.action.outText("a fine livery choice: <" .. chosen .. "> for type <" .. theType .. ">", 30)
|
||||||
|
else
|
||||||
|
-- trigger.action.outText("No choice, no livery.", 30)
|
||||||
|
end
|
||||||
|
|
||||||
theData.livery_id = chosen
|
theData.livery_id = chosen
|
||||||
|
else
|
||||||
|
-- trigger.action.outText("No livery for type <" .. theType .. ">", 30)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -1050,17 +1050,13 @@ destinationReached! -- adds script to last waypoint to hit this signal, also ini
|
|||||||
dead! signal and cb. only applies to ground troops? can they disembark troops when hit?
|
dead! signal and cb. only applies to ground troops? can they disembark troops when hit?
|
||||||
attacked signal each time a unit is destroyed
|
attacked signal each time a unit is destroyed
|
||||||
importantType - type that must survive=
|
importantType - type that must survive=
|
||||||
coalition / masterOwner
|
coalition / masterOwner tie-in
|
||||||
isActive# 0/1
|
|
||||||
doWipe? to wipe all my convoys?
|
doWipe? to wipe all my convoys?
|
||||||
tacTypes = desinate units types that must survive. Upon start, ensure that at least one tac type is pressenr
|
tacTypes = desinate units types that must survive. Upon start, ensure that at least one tac type is pressenr
|
||||||
when arriving, verify that it still is, or fail earlier when all tactypes are destroyed.
|
when arriving, verify that it still is, or fail earlier when all tactypes are destroyed.
|
||||||
convoy status UI
|
csar integration. when losing a vehicle, pSCAR match (say, 50%), whole convoy pushes a HOLD order to defend until time runs out or csar mission picks up evacuee (pop task).
|
||||||
|
|
||||||
do:
|
do:
|
||||||
when escort engages, send notice
|
?mark source and dest of convoy on map for same side
|
||||||
when escort damaged, send notice
|
|
||||||
mark source and dest of convoy on map for same side
|
|
||||||
make routes interchangeable between convoys?
|
make routes interchangeable between convoys?
|
||||||
make inf units disembark when convoy attacked
|
make inf units disembark when convoy attacked
|
||||||
--]]--
|
--]]--
|
||||||
@ -1,5 +1,5 @@
|
|||||||
dcsCommon = {}
|
dcsCommon = {}
|
||||||
dcsCommon.version = "3.1.2"
|
dcsCommon.version = "3.1.3"
|
||||||
--[[-- VERSION HISTORY
|
--[[-- VERSION HISTORY
|
||||||
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
|
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
|
||||||
- point2text new intsOnly option
|
- point2text new intsOnly option
|
||||||
@ -29,19 +29,37 @@ dcsCommon.version = "3.1.2"
|
|||||||
3.1.0 - updates to events, DCS update 7-11 2024 hardening
|
3.1.0 - updates to events, DCS update 7-11 2024 hardening
|
||||||
3.1.1 - added Chinook to troop carriers
|
3.1.1 - added Chinook to troop carriers
|
||||||
3.1.2 - isTroopCarrier() hardening against DCS sillieness
|
3.1.2 - isTroopCarrier() hardening against DCS sillieness
|
||||||
|
3.1.3 - new dcsCommon.unitIsOfLegalType() analogue to isTroopCarrier
|
||||||
|
- new DCS Patch section
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
-- dcsCommon is a library of common lua functions
|
-- dcsCommon is a library of common lua functions
|
||||||
-- for easy access and simple mission programming
|
-- for easy access and simple mission programming
|
||||||
-- (c) 2021 - 2024 by Christian Franz and cf/x AG
|
-- (c) 2021 - 2024 by Christian Franz and cf/x AG
|
||||||
|
|
||||||
|
--
|
||||||
|
-- DCS API PATCHES FOR DCS-INTERNAL BUGS
|
||||||
|
--
|
||||||
|
|
||||||
|
Group.getByNameBase = Group.getByName -- thanks, Dzsekeb(?)
|
||||||
|
|
||||||
|
function Group.getByName(name) -- patch getByName to protect against empty groups
|
||||||
|
local g = Group.getByNameBase(name)
|
||||||
|
if not g then return nil end
|
||||||
|
if g:getSize() == 0 then return nil end
|
||||||
|
return g
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- DML FOUNDATION
|
||||||
|
--
|
||||||
dcsCommon.verbose = false -- set to true to see debug messages. Lots of them
|
dcsCommon.verbose = false -- set to true to see debug messages. Lots of them
|
||||||
dcsCommon.uuidStr = "uuid-"
|
dcsCommon.uuidStr = "uuid-"
|
||||||
dcsCommon.simpleUUID = 76543 -- a number to start. as good as any
|
dcsCommon.simpleUUID = 76543 -- a number to start. as good as any
|
||||||
|
|
||||||
-- globals
|
-- globals
|
||||||
dcsCommon.cbID = 0 -- callback id for simple callback scheduling
|
dcsCommon.cbID = 0 -- callback id for simple callback scheduling
|
||||||
dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P", "OH58D", "CH-47Fbl1"} -- Ka-50, Apache and Gazelle can't carry troops
|
dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P", "OH58D", "CH-47Fbl1"} -- Ka-50, Apache and Gazelle can't carry troops, the Kiowa can!
|
||||||
dcsCommon.coalitionSides = {0, 1, 2}
|
dcsCommon.coalitionSides = {0, 1, 2}
|
||||||
dcsCommon.maxCountry = 86 -- number of countries defined in total
|
dcsCommon.maxCountry = 86 -- number of countries defined in total
|
||||||
|
|
||||||
@ -2839,6 +2857,10 @@ function dcsCommon.isTroopCarrierType(theType, carriers)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function dcsCommon.unitIsOfLegalType(theUnit, types)
|
||||||
|
return dcsCommon.isTroopCarrier(theUnit, types)
|
||||||
|
end
|
||||||
|
|
||||||
function dcsCommon.isTroopCarrier(theUnit, carriers)
|
function dcsCommon.isTroopCarrier(theUnit, carriers)
|
||||||
-- return true if conf can carry troups
|
-- return true if conf can carry troups
|
||||||
if not theUnit then return false end
|
if not theUnit then return false end
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
cfxHeloTroops = {}
|
cfxHeloTroops = {}
|
||||||
cfxHeloTroops.version = "3.1.0"
|
cfxHeloTroops.version = "3.1.2"
|
||||||
cfxHeloTroops.verbose = false
|
cfxHeloTroops.verbose = false
|
||||||
cfxHeloTroops.autoDrop = true
|
cfxHeloTroops.autoDrop = true
|
||||||
cfxHeloTroops.autoPickup = false
|
cfxHeloTroops.autoPickup = false
|
||||||
@ -18,7 +18,8 @@ cfxHeloTroops.requestRange = 500 -- meters
|
|||||||
3.0.4 - also handles picking up troops with orders "captureandhold"
|
3.0.4 - also handles picking up troops with orders "captureandhold"
|
||||||
3.0.5 - worked around a new issues accessing a unit's name
|
3.0.5 - worked around a new issues accessing a unit's name
|
||||||
3.1.0 - compatible with DCS 2.9.6 dynamic spawning
|
3.1.0 - compatible with DCS 2.9.6 dynamic spawning
|
||||||
|
3.1.1 - deployTroopsFromHelicopter() captureandhold
|
||||||
|
3.1.2 - doLoadGroup - test if group is still alive edge case handling
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
|
|
||||||
@ -613,6 +614,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
|
|||||||
local moveFormation = conf.troopsOnBoard.moveFormation
|
local moveFormation = conf.troopsOnBoard.moveFormation
|
||||||
|
|
||||||
if not orders then orders = "guard" end
|
if not orders then orders = "guard" end
|
||||||
|
orders = string.lower(orders)
|
||||||
|
|
||||||
-- order processing: if the orders were pre-pended with "wait-"
|
-- order processing: if the orders were pre-pended with "wait-"
|
||||||
-- we now remove that, so after dropping they do what their
|
-- we now remove that, so after dropping they do what their
|
||||||
@ -646,7 +648,11 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
|
|||||||
-- and make them run to the wrong zone
|
-- and make them run to the wrong zone
|
||||||
dest = cfxGroundTroops.getClosestEnemyZone(troop)
|
dest = cfxGroundTroops.getClosestEnemyZone(troop)
|
||||||
troopData.destination = dest
|
troopData.destination = dest
|
||||||
trigger.action.outText("Inserting troops to capture zone <" .. dest.name .. ">", 30)
|
if dest then
|
||||||
|
trigger.action.outText("Inserting troops to capture zone <" .. dest.name .. ">", 30)
|
||||||
|
else
|
||||||
|
trigger.action.outText("+++heloT: WARNING: cap&hold: can't find a zone to cap.", 30)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
troop.destination = dest -- transfer target zone for attackzone oders
|
troop.destination = dest -- transfer target zone for attackzone oders
|
||||||
@ -678,6 +684,8 @@ end
|
|||||||
function cfxHeloTroops.doLoadGroup(args)
|
function cfxHeloTroops.doLoadGroup(args)
|
||||||
local conf = args[1]
|
local conf = args[1]
|
||||||
local group = args[2]
|
local group = args[2]
|
||||||
|
if not group then return end
|
||||||
|
if not Group.isExist(group) then return end -- edge case: group died in the past 0.1 seconds
|
||||||
conf.troopsOnBoard = {}
|
conf.troopsOnBoard = {}
|
||||||
-- all we need to do is disassemble the group into type
|
-- all we need to do is disassemble the group into type
|
||||||
conf.troopsOnBoard.types = dcsCommon.getGroupTypeString(group)
|
conf.troopsOnBoard.types = dcsCommon.getGroupTypeString(group)
|
||||||
|
|||||||
734
modules/inferno.lua
Normal file
734
modules/inferno.lua
Normal file
@ -0,0 +1,734 @@
|
|||||||
|
inferno = {}
|
||||||
|
inferno.version = "0.9.9"
|
||||||
|
inferno.requiredLibs = {
|
||||||
|
"dcsCommon",
|
||||||
|
"cfxZones",
|
||||||
|
}
|
||||||
|
--
|
||||||
|
-- Inferno models fires inside inferno zones. Fires can spread and
|
||||||
|
-- be extinguished by aircraft from the airtank module
|
||||||
|
-- (c) 2024 by Christian "cfrag" Franz
|
||||||
|
--
|
||||||
|
inferno.zones = {}
|
||||||
|
inferno.maxStages = 10 -- tied to fuel consumption, 1 burns small, maxStages at max size. A burning fire untended increases in stage by one each tick until it reaches maxStages
|
||||||
|
inferno.threshold = 4.9 -- when fire spreads to another field
|
||||||
|
inferno.fireExtinguishedCB = {} -- CB for other modules / scripts
|
||||||
|
|
||||||
|
--
|
||||||
|
-- CB
|
||||||
|
--
|
||||||
|
function inferno.installExtinguishedCB(theCB)
|
||||||
|
table.insert(inferno.fireExtinguishedCB, theCB)
|
||||||
|
end
|
||||||
|
|
||||||
|
function inferno.invokeFireExtinguishedCB(theZone)
|
||||||
|
for idx, cb in pairs(inferno.fireExtinguishedCB) do
|
||||||
|
cb(theZone, theZone.heroes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Reading zones
|
||||||
|
--
|
||||||
|
function inferno.addZone(theZone)
|
||||||
|
inferno.zones[theZone.name] = theZone
|
||||||
|
end
|
||||||
|
|
||||||
|
function inferno.buildGrid(theZone)
|
||||||
|
-- default for circular zone
|
||||||
|
local radius = theZone.radius
|
||||||
|
local side = radius * 2
|
||||||
|
local p = theZone:getPoint()
|
||||||
|
local minx = p.x - radius
|
||||||
|
local minz = p.z - radius
|
||||||
|
local xside = side
|
||||||
|
local zside = side
|
||||||
|
local xradius = radius
|
||||||
|
local zradius = radius
|
||||||
|
if theZone.isPoly then
|
||||||
|
-- build the params for a (rectangular) zone from a
|
||||||
|
-- quad zone, makes area an AABB (axis-aligned bounding box)
|
||||||
|
minx = theZone.bounds.ll.x
|
||||||
|
minz = theZone.bounds.ll.z
|
||||||
|
xside = theZone.bounds.ur.x - theZone.bounds.ll.x
|
||||||
|
zside = theZone.bounds.ur.z - theZone.bounds.ll.z
|
||||||
|
end
|
||||||
|
local cellx = theZone.cellSize -- square cells assumed
|
||||||
|
local cellz = theZone.cellSize
|
||||||
|
local numX = math.floor(xside / cellx)
|
||||||
|
if numX < 1 then numX = 1 end
|
||||||
|
if numX > 100 then
|
||||||
|
trigger.action.outText("***inferno: limited x from <" .. numX .. "> to 100", 30)
|
||||||
|
numX = 100
|
||||||
|
end
|
||||||
|
|
||||||
|
local numZ = math.floor(zside / cellz)
|
||||||
|
if numX > 100 then
|
||||||
|
trigger.action.outText("***inferno: limited z from <" .. numZ .. "> to 100", 30)
|
||||||
|
numZ = 100
|
||||||
|
end
|
||||||
|
if numZ < 1 then numZ = 1 end
|
||||||
|
if theZone.verbose then
|
||||||
|
trigger.action.outText("infernal zone <" .. theZone.name .. ">: cellSize <" .. theZone.cellSize .. "> --> x <" .. numX .. ">, z <" .. numZ .. ">", 30)
|
||||||
|
end
|
||||||
|
local grid = {}
|
||||||
|
local goodCells = {}
|
||||||
|
-- Remember that in DCS
|
||||||
|
-- X is North/South, with positive X being North/South
|
||||||
|
-- and Z is East/West with positve Z being EAST
|
||||||
|
local fCount = 0
|
||||||
|
theZone.burning = false
|
||||||
|
for x=1, numX do -- "up/down"
|
||||||
|
grid[x] = {}
|
||||||
|
for z=1, numZ do -- "left/right"
|
||||||
|
local ele = {}
|
||||||
|
-- calculate center for each cell
|
||||||
|
local xc = minx + (x-1) * cellx + cellx/2
|
||||||
|
local zc = minz + (z-1) * cellz + cellz/2
|
||||||
|
local xf = xc
|
||||||
|
local zf = zc
|
||||||
|
|
||||||
|
local lp = {x=xc, y=zc}
|
||||||
|
local yc = land.getHeight(lp)
|
||||||
|
ele.center = {x=xc, y=yc, z=zc}
|
||||||
|
if theZone.markCell then
|
||||||
|
dcsCommon.createStaticObjectForCoalitionAtLocation(0, ele.center, dcsCommon.uuid(theZone.name), "Black_Tyre_RF", 0, false)
|
||||||
|
|
||||||
|
dcsCommon.createStaticObjectForCoalitionAtLocation(0, {x=ele.center.x - cellx/2, y=0, z=ele.center.z - cellz/2}, dcsCommon.uuid(theZone.name), "Windsock", 0, false)
|
||||||
|
dcsCommon.createStaticObjectForCoalitionAtLocation(0, {x=ele.center.x + cellx/2, y=0, z=ele.center.z - cellz/2}, dcsCommon.uuid(theZone.name), "Windsock", 0, false)
|
||||||
|
dcsCommon.createStaticObjectForCoalitionAtLocation(0, {x=ele.center.x - cellx/2, y=0, z=ele.center.z + cellz/2}, dcsCommon.uuid(theZone.name), "Windsock", 0, false)
|
||||||
|
dcsCommon.createStaticObjectForCoalitionAtLocation(0, {x=ele.center.x + cellx/2, y=0, z=ele.center.z + cellz/2}, dcsCommon.uuid(theZone.name), "Windsock", 0, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
ele.fxpos = {x=xf, y=yc, z=zf}
|
||||||
|
ele.myType = land.getSurfaceType(lp) -- LAND=1, SHALLOW_WATER=2, WATER=3, ROAD=4, RUNWAY=5
|
||||||
|
-- we don not burn if a cell has shallow or deep water, or roads or runways
|
||||||
|
ele.inside = theZone:pointInZone(ele.center)
|
||||||
|
if theZone.freeBorder then
|
||||||
|
if x == 1 or x == numX then ele.inside = false end
|
||||||
|
if z == 1 or z == numZ then ele.inside = false end
|
||||||
|
end
|
||||||
|
ele.myStage = 0 -- not burning
|
||||||
|
if ele.inside and ele.myType == 1 then -- land only
|
||||||
|
-- create a position for the fire -- visual only
|
||||||
|
if theZone.stagger then
|
||||||
|
repeat
|
||||||
|
xf = xc + (0.5 - math.random()) * cellx
|
||||||
|
zf = zc + (0.5 - math.random()) * cellz
|
||||||
|
lp = {x=xf, y=zf}
|
||||||
|
until land.getSurfaceType(lp) == 1
|
||||||
|
ele.fxpos = {x=xf, y=yc, z=zf}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- place a fire on the cell
|
||||||
|
if theZone.fullBlaze then
|
||||||
|
ele.fxname = dcsCommon.uuid(theZone.name)
|
||||||
|
trigger.action.effectSmokeBig(ele.fxpos, 4, 0.5 , ele.fxname)
|
||||||
|
ele.myStage = inferno.maxStages
|
||||||
|
ele.fxsize = 4
|
||||||
|
theZone.burning = true
|
||||||
|
end
|
||||||
|
local sparkable = {x=x, z=z}
|
||||||
|
table.insert(goodCells, sparkable)
|
||||||
|
fCount = fCount + 1
|
||||||
|
else
|
||||||
|
ele.inside = false -- optim: not in poly or burnable
|
||||||
|
end
|
||||||
|
ele.fuel = theZone.fuel -- use rnd?
|
||||||
|
ele.eternal = theZone.eternal -- unlimited fuel
|
||||||
|
grid[x][z] = ele
|
||||||
|
end -- for z
|
||||||
|
end -- for x
|
||||||
|
if theZone.verbose then
|
||||||
|
trigger.action.outText("inferno: zone <" .. theZone.name .. "> has <" .. fCount .. "> hot spots", 30)
|
||||||
|
end
|
||||||
|
if fCount < 1 then
|
||||||
|
trigger.action.outText("WARNING: <" .. theZone.name .. "> has no good burn cells!", 30)
|
||||||
|
end
|
||||||
|
theZone.numX = numX
|
||||||
|
theZone.numZ = numZ
|
||||||
|
theZone.minx = minx
|
||||||
|
theZone.minz = minz
|
||||||
|
theZone.xside = xside
|
||||||
|
theZone.grid = grid
|
||||||
|
theZone.goodCells = goodCells
|
||||||
|
|
||||||
|
-- find bestCell from goodCells closest to center
|
||||||
|
local bestdist = math.huge
|
||||||
|
local bestCell = nil
|
||||||
|
for idx, aCell in pairs(goodCells) do
|
||||||
|
local x = aCell.x
|
||||||
|
local z = aCell.z
|
||||||
|
local ele = grid[x][z]
|
||||||
|
local cp = ele.center
|
||||||
|
local d = dcsCommon.dist(cp, p)
|
||||||
|
if d < bestdist then
|
||||||
|
bestCell = aCell
|
||||||
|
bestdist = d
|
||||||
|
end
|
||||||
|
end
|
||||||
|
theZone.bestCell = bestCell
|
||||||
|
end
|
||||||
|
|
||||||
|
function inferno.readZone(theZone)
|
||||||
|
theZone.cellSize = theZone:getNumberFromZoneProperty("cellSize", inferno.cellSize)
|
||||||
|
-- FUEL: amount of fuel to burn PER CELL. when at zero, fire goes out
|
||||||
|
-- expansion: make it a random range
|
||||||
|
theZone.fuel = theZone:getNumberFromZoneProperty("fuel", 100)
|
||||||
|
theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", false)
|
||||||
|
theZone.freeBorder = theZone:getBoolFromZoneProperty("freeBorder", true) -- ring zone with non-burning zones
|
||||||
|
theZone.eternal = theZone:getBoolFromZoneProperty("eternal", true)
|
||||||
|
theZone.stagger = theZone:getBoolFromZoneProperty("stagger", true) -- randomize inside cell
|
||||||
|
theZone.fullBlaze = theZone:getBoolFromZoneProperty("fullBlaze", false )
|
||||||
|
theZone.canSpread = theZone:getBoolFromZoneProperty("canSpread", true)
|
||||||
|
theZone.maxSpread = theZone:getNumberFromZoneProperty("maxSpread", 999999)
|
||||||
|
theZone.impactSmoke = theZone:getBoolFromZoneProperty("impactSmoke", inferno.impactSmoke)
|
||||||
|
theZone.markCell = theZone:getBoolFromZoneProperty("markCell", false)
|
||||||
|
inferno.buildGrid(theZone)
|
||||||
|
theZone.heroes = {} -- remembers all who dropped into zone
|
||||||
|
theZone.onStart = theZone:getBoolFromZoneProperty("onStart", false)
|
||||||
|
if theZone:hasProperty("ignite?") then
|
||||||
|
theZone.ignite = theZone:getStringFromZoneProperty("ignite?", "none")
|
||||||
|
theZone.lastIgnite = trigger.misc.getUserFlag(theZone.ignite)
|
||||||
|
end
|
||||||
|
if theZone:hasProperty("douse?") then
|
||||||
|
theZone.douse = theZone:getStringFromZoneProperty("douse?", "none")
|
||||||
|
theZone.lastDouse = trigger.misc.getUserFlag(theZone.douse)
|
||||||
|
end
|
||||||
|
if theZone:hasProperty("extinguished!") then
|
||||||
|
theZone.extinguished = theZone:getStringFromZoneProperty("extinguished", "none")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- API for water droppers
|
||||||
|
--
|
||||||
|
function inferno.surroundDelta(theZone, p, x, z)
|
||||||
|
if x < 1 then return math.huge end
|
||||||
|
if z < 1 then return math.huge end
|
||||||
|
if x > theZone.numX then return math.huge end
|
||||||
|
if z > theZone.numZ then return math.huge end
|
||||||
|
local ele = theZone.grid[x][z]
|
||||||
|
if not ele then return math.huge end
|
||||||
|
if not ele.inside then return math.huge end
|
||||||
|
if not ele.sparked then return math.huge end
|
||||||
|
if ele.myStage < 1 then return math.huge end
|
||||||
|
return dcsCommon.dist(p, ele.fxpos)
|
||||||
|
end
|
||||||
|
|
||||||
|
function inferno.waterInZone(theZone, p, amount)
|
||||||
|
-- water dropped (as point source) into a inferno zone.
|
||||||
|
-- find the cell that it was dropped in
|
||||||
|
local x = p.x - theZone.minx
|
||||||
|
local z = p.z - theZone.minz
|
||||||
|
local xc = math.floor(x / theZone.cellSize) + 1 -- square cells!
|
||||||
|
local zc = math.floor(z / theZone.cellSize) + 1
|
||||||
|
local ele = theZone.grid[xc][zc]
|
||||||
|
if not ele then
|
||||||
|
trigger.action.outText("Inferno: no ele for <" .. theZone.name .. ">: x<" .. x .. ">z<" .. z .. ">", 30)
|
||||||
|
return "NIL ele for x" .. xc .. ",z" .. zc .. " in " .. theZone.name
|
||||||
|
end
|
||||||
|
|
||||||
|
-- empty ele pre-proccing:
|
||||||
|
-- if not burning, see if we find a better burning cell nearby
|
||||||
|
if (not ele.sparked) or ele.extinguished then -- not burning, note that we do NOT test inside here!
|
||||||
|
local hitDelta = math.sqrt(2) * theZone.cellSize -- dcsCommon.dist(p, ele.center) + 0.5 * theZone.cellSize -- give others a chance
|
||||||
|
local bestDelta = hitDelta
|
||||||
|
local ofx = 0
|
||||||
|
local ofz = 0
|
||||||
|
for dx = -1, 1 do
|
||||||
|
for dz = -1, 1 do
|
||||||
|
if dx == 0 and dz == 0 then -- skip this one
|
||||||
|
else
|
||||||
|
local newDelta = inferno.surroundDelta(theZone, p, xc + dx, zc + dz)
|
||||||
|
if newDelta < bestDelta then
|
||||||
|
bestDelta = newDelta
|
||||||
|
ofx = dx
|
||||||
|
ofz = dz
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
xc = xc + ofx
|
||||||
|
zc = zc + ofz
|
||||||
|
ele = theZone.grid[xc][zc]
|
||||||
|
-- else
|
||||||
|
-- trigger.action.outText("inferno dropper: NO ALIGNMENT, beds are burning.", 30)
|
||||||
|
end
|
||||||
|
if theZone.impactSmoke then
|
||||||
|
if inferno.verbose then
|
||||||
|
trigger.action.smoke(ele.center, 1) -- red is ele center
|
||||||
|
end
|
||||||
|
trigger.action.smoke(p, 4) -- blue is actual impact
|
||||||
|
end
|
||||||
|
|
||||||
|
-- inside?
|
||||||
|
if ele.inside then
|
||||||
|
-- force this cell's eternal to OFF, now it consumes fuel
|
||||||
|
ele.eternal = false -- from now on, this cell burns own fuel
|
||||||
|
|
||||||
|
if not ele.sparked then
|
||||||
|
-- not burning. remove all fuel, make it
|
||||||
|
-- extinguished so it won't catch fire in the future
|
||||||
|
ele.extinguished = true -- will now negatively contribute
|
||||||
|
ele.fuel = 0
|
||||||
|
return "Good peripheral delivery, will prevent spread."
|
||||||
|
end
|
||||||
|
|
||||||
|
-- calculate dispersal of water. the higher, the more dispersed
|
||||||
|
-- and less fuel on the ground is 'removed'
|
||||||
|
local dispAmount = amount -- currently no dispersal, full amount hits ground
|
||||||
|
ele.fuel = ele.fuel - dispAmount
|
||||||
|
|
||||||
|
-- we can restage fx to smaller fire and reset stage
|
||||||
|
-- so fire consumes less fuel ?
|
||||||
|
-- NYI, later.
|
||||||
|
return "Direct delivery into fire cell!"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- not inside or a water tile
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function inferno.waterDropped(p, amount, data) -- if returns non-nil, has hit a cell
|
||||||
|
-- p is (x, 0, z) of where the water hits the ground
|
||||||
|
for name, theZone in pairs(inferno.zones) do
|
||||||
|
if theZone:pointInZone(p) then
|
||||||
|
if inferno.verbose then
|
||||||
|
trigger.action.outText("inferno: INSIDE <" .. theZone.name .. ">", 30)
|
||||||
|
end
|
||||||
|
-- if available, remember and increase the number of drops in
|
||||||
|
-- zone for player
|
||||||
|
if data and data.pName then
|
||||||
|
if not theZone.heroes then theZone.heroes = {} end
|
||||||
|
if theZone.heroes[data.pName] then
|
||||||
|
theZone.heroes[data.pName] = theZone.heroes[data.pName] + 1
|
||||||
|
else
|
||||||
|
theZone.heroes[data.pName] = 1
|
||||||
|
end
|
||||||
|
-- trigger.action.outText("registering <" .. data.pName .. "> for drop in <" .. theZone.name .. ">", 30)
|
||||||
|
|
||||||
|
-- else
|
||||||
|
-- trigger.action.outText("Not registered, no data", 30)
|
||||||
|
end
|
||||||
|
return inferno.waterInZone(theZone, p, amount)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if inferno.impactSmoke then
|
||||||
|
-- mark the position with a blue smoke
|
||||||
|
trigger.action.smoke(p, 4)
|
||||||
|
end
|
||||||
|
if inferno.verbose then
|
||||||
|
trigger.action.outText("water drop outside any inferno zone", 30)
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- IGNITE & DOUSE
|
||||||
|
--
|
||||||
|
function inferno.sparkCell(theZone, x, z)
|
||||||
|
local ele = theZone.grid[x][z]
|
||||||
|
if not ele.inside then
|
||||||
|
if theZone.verbose then
|
||||||
|
trigger.action.outText("ele x<" .. x .. ">z<" .. z .. "> is outside, no spark!", 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
false end
|
||||||
|
ele.fxname = dcsCommon.uuid(theZone.name)
|
||||||
|
trigger.action.effectSmokeBig(ele.fxpos, 1, 0.5 , ele.fxname)
|
||||||
|
ele.myStage = 1
|
||||||
|
ele.fxsize = 1
|
||||||
|
ele.sparked = true
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function inferno.ignite(theZone)
|
||||||
|
if theZone.burning then
|
||||||
|
-- later expansion: add more fires
|
||||||
|
-- will give error when fullblaze is set
|
||||||
|
trigger.action.outText("Zone <" .. theZone.name .. "> already burning", 30)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if theZone.verbose then
|
||||||
|
trigger.action.outText("igniting <" .. theZone.name .. ">", 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
local midNum = math.floor((#theZone.goodCells + 1)/ 2)
|
||||||
|
if midNum < 1 then midNum = 1 end
|
||||||
|
local midCell = theZone.bestCell
|
||||||
|
if not midCell then midCell = theZone.goodCells[midNum] end
|
||||||
|
if theZone.rndLoc then midCell = dcsCommon.pickRandom(theZone.goodCells) end
|
||||||
|
local x = midCell.x
|
||||||
|
local z = midCell.z
|
||||||
|
|
||||||
|
if inferno.sparkCell(theZone, x, z) then
|
||||||
|
if theZone.verbose then
|
||||||
|
trigger.action.outText("Sparking cell x<" .. x .. ">z<" .. z .. "> for <" .. theZone.name .. ">", 30)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
trigger.action.outText("Inferno: fire in <" .. theZone.name .. "> @ center x<" .. x .. ">z<" .. z .. "> didin't catch", 30)
|
||||||
|
end
|
||||||
|
theZone.hasSpread = 0 -- how many times we have spread
|
||||||
|
theZone.burning = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function inferno.startFire(theZone)
|
||||||
|
if theZone.burning then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
inferno.ignite(theZone)
|
||||||
|
end
|
||||||
|
|
||||||
|
function inferno.douseFire(theZone)
|
||||||
|
-- if not theZone.burning then
|
||||||
|
-- return
|
||||||
|
-- end
|
||||||
|
-- walk the grid, and kill all flames, set all eles
|
||||||
|
-- to end state
|
||||||
|
for x=1, theZone.numX do
|
||||||
|
for z=1, theZone.numZ do
|
||||||
|
local ele = theZone.grid[x][z]
|
||||||
|
if ele.inside then
|
||||||
|
if ele.fxname then
|
||||||
|
trigger.action.effectSmokeStop(ele.fxname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
inferno.buildGrid(theZone) -- prep next fire in here
|
||||||
|
theZone.heroes = {}
|
||||||
|
theZone.burning = false
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Fire Tick: progress/grow fire, burn fuel, expand conflagration etc.
|
||||||
|
--
|
||||||
|
function inferno.fireUpdate() -- update all burning fires
|
||||||
|
--[[--
|
||||||
|
every tick, we progress the fire status of all cells
|
||||||
|
fire stages (per cell):
|
||||||
|
< 1 : not burning, perhaps dying
|
||||||
|
0 : not burning, not smoking
|
||||||
|
-1..-5 : smoking. the closer to 0, less smoke
|
||||||
|
1..10 : burning, number = flame size, contib. heat and fuel consumption
|
||||||
|
--]]--
|
||||||
|
timer.scheduleFunction(inferno.fireUpdate, {}, timer.getTime() + inferno.fireTick) -- next time
|
||||||
|
for zName, theZone in pairs(inferno.zones) do
|
||||||
|
if theZone.fullBlaze then
|
||||||
|
-- do nothing, just testing layout
|
||||||
|
elseif theZone.burning then
|
||||||
|
inferno.burnOneTick(theZone) -- expand fuel, see if it spreads
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function inferno.burnOneTick(theZone)
|
||||||
|
-- iterate all cells and see if the fire spreads
|
||||||
|
local isBurning = false
|
||||||
|
local grid = theZone.grid
|
||||||
|
local newStage = {} -- new states
|
||||||
|
local numX = theZone.numX
|
||||||
|
local numZ = theZone.numZ
|
||||||
|
-- pass 1:
|
||||||
|
-- calculate new stages
|
||||||
|
for x = 1, numX do -- up
|
||||||
|
newStage[x] = {}
|
||||||
|
for z = 1, numZ do
|
||||||
|
local ele = grid[x][z]
|
||||||
|
if ele.inside then
|
||||||
|
local stage = ele.myStage
|
||||||
|
-- we will only continue burning if we have fuel
|
||||||
|
if ele.extinguished then
|
||||||
|
-- we are drenched and can't re-ignite
|
||||||
|
newStage[x][z] = 0
|
||||||
|
elseif ele.fuel > 0 then -- we have fuel
|
||||||
|
if stage > 0 then -- it's already burning
|
||||||
|
if not ele.eternal then
|
||||||
|
ele.fuel = ele.fuel - stage -- consume fuel if burning and not eternal
|
||||||
|
if theZone.verbose then
|
||||||
|
trigger.action.outText(stage .. " fuel consumed. remain: "..ele.fuel, 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
stage = stage + 1
|
||||||
|
if stage > inferno.maxStages then
|
||||||
|
stage = inferno.maxStages
|
||||||
|
end
|
||||||
|
newStage[x][z] = stage -- fire is growing
|
||||||
|
elseif stage < 0 then
|
||||||
|
-- fire is dying, can't be here if fuel > 0
|
||||||
|
newStage[x][z] = stage
|
||||||
|
else -- not burning. see if the surrounding sides are contributing
|
||||||
|
if theZone.canSpread and (theZone.hasSpread < theZone.maxSpread) then
|
||||||
|
local accu = 0
|
||||||
|
-- now do all surrounding 8 fields
|
||||||
|
-- NOTE: use wind direction to modify below if we use wind - NYI
|
||||||
|
accu = accu + inferno.contribute(x-1, z-1, theZone)
|
||||||
|
accu = accu + inferno.contribute(x, z-1, theZone)
|
||||||
|
accu = accu + inferno.contribute(x+1, z-1, theZone)
|
||||||
|
accu = accu + inferno.contribute(x-1, z, theZone)
|
||||||
|
accu = accu + inferno.contribute(x+1, z, theZone)
|
||||||
|
accu = accu + inferno.contribute(x-1, z+1, theZone)
|
||||||
|
accu = accu + inferno.contribute(x, z+1, theZone)
|
||||||
|
accu = accu + inferno.contribute(x+1, z+1, theZone)
|
||||||
|
accu = accu / 2 -- half intensity
|
||||||
|
-- 10% chance to spread when above threshold
|
||||||
|
if accu > inferno.threshold and math.random() < 0.1 then
|
||||||
|
stage = 1 -- start small fire
|
||||||
|
theZone.hasSpread = theZone.hasSpread + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
newStage[x][z] = stage
|
||||||
|
end
|
||||||
|
else -- fuel is spent let flames die down if they exist
|
||||||
|
if stage == 0 then -- wasn't burning before
|
||||||
|
newStage[x][z] = 0
|
||||||
|
else
|
||||||
|
if stage > 0 then
|
||||||
|
newStage[x][z] = stage - 1
|
||||||
|
if newStage[x][z] == 0 then
|
||||||
|
newStage[x][z] = - 5
|
||||||
|
end
|
||||||
|
else
|
||||||
|
newStage[x][z] = stage + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
newStage[x][z] = 0 -- outside, will always be 0
|
||||||
|
end
|
||||||
|
end -- for z
|
||||||
|
end -- for x
|
||||||
|
-- pass 2:
|
||||||
|
-- see what changed and handle accordingly
|
||||||
|
for x = 1, numX do -- up
|
||||||
|
for z = 1, numZ do
|
||||||
|
local ele = grid[x][z]
|
||||||
|
if ele.inside then
|
||||||
|
local stage = ele.myStage
|
||||||
|
|
||||||
|
local ns = newStage[x][z]
|
||||||
|
if not ns then ns = 0 end
|
||||||
|
if theZone.verbose and ele.sparked then
|
||||||
|
trigger.action.outText("x<" .. x .. ">z<" .. z .. "> - next stage is " .. ns, 30)
|
||||||
|
end
|
||||||
|
if ns ~= stage then
|
||||||
|
-- fire has changed: spread or dying down
|
||||||
|
if stage == 0 then -- fire has spread!
|
||||||
|
if theZone.verbose then
|
||||||
|
trigger.action.outText("Fire in <" .. theZone.name .. "> has spread to x<" .. x .. ">z<" .. z .. ">", 30)
|
||||||
|
end
|
||||||
|
ele.sparked = true
|
||||||
|
elseif ns == 0 then -- fire has died down fully
|
||||||
|
if theZone.verbose then
|
||||||
|
trigger.action.outText("Fire in <" .. theZone.name .. "> at x<" .. x .. ">z<" .. z .. "> has been extinguished", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle fire fx
|
||||||
|
-- determine fx number
|
||||||
|
local fx = 0
|
||||||
|
if stage > 0 then
|
||||||
|
fx = math.floor(stage / 2) -- 1..10--> 1..4
|
||||||
|
if fx < 1 then fx = 1 end
|
||||||
|
if fx > 4 then fx = 4 end
|
||||||
|
isBurning = true
|
||||||
|
elseif stage < 0 then
|
||||||
|
fx = 4-stage -- -5 .. -1 --> 6..10
|
||||||
|
if fx < 5 then fx = 5 end
|
||||||
|
if fx > 8 then fx = 8 end
|
||||||
|
isBurning = true -- keep as 'burning'
|
||||||
|
end
|
||||||
|
if fx ~= ele.fxsize then
|
||||||
|
if ele.fxname then
|
||||||
|
if theZone.verbose then
|
||||||
|
trigger.action.outText("removing old fx <" .. ele.fxsize .. "> [" .. ele.fxname .. "] for <" .. fx .. "> in <" .. theZone.name .. "> x<" .. x .. ">z<" .. z .. ">", 30)
|
||||||
|
end
|
||||||
|
-- remove old fx
|
||||||
|
trigger.action.effectSmokeStop(ele.fxname)
|
||||||
|
end
|
||||||
|
-- start new
|
||||||
|
if fx > 0 then
|
||||||
|
ele.fxname = dcsCommon.uuid(theZone.name)
|
||||||
|
trigger.action.effectSmokeBig(ele.fxpos, fx, 0.5 , ele.fxname)
|
||||||
|
else
|
||||||
|
if theZone.verbose then
|
||||||
|
trigger.action.outText("expiring <" .. theZone.name .. "> x<" .. x .. ">z<" .. z .. ">", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ele.fxsize = fx
|
||||||
|
end
|
||||||
|
-- save new stage
|
||||||
|
ele.myStage = ns
|
||||||
|
else
|
||||||
|
if not ele.sparked then
|
||||||
|
-- not yet ignited, ignore
|
||||||
|
elseif ele.extinguished then
|
||||||
|
-- ignore, we are already off
|
||||||
|
elseif stage ~= 0 then
|
||||||
|
-- still burning bright
|
||||||
|
isBurning = true
|
||||||
|
else
|
||||||
|
-- remove last fx
|
||||||
|
trigger.action.effectSmokeStop(ele.fxname)
|
||||||
|
-- clear this zone or add debris now?
|
||||||
|
ele.extinguished = true -- now can't re-ignite
|
||||||
|
end
|
||||||
|
end -- if ns <> stage
|
||||||
|
end -- if inside
|
||||||
|
end -- for z
|
||||||
|
end -- for x
|
||||||
|
if not isBurning then
|
||||||
|
trigger.action.outText("inferno in <" .. theZone.name .. "> has been fully extinguished", 30)
|
||||||
|
theZone.burning = false
|
||||||
|
if theZone.extinguished then
|
||||||
|
theZone:pollFlag(theZone.extinguished, "inc")
|
||||||
|
end
|
||||||
|
inferno.invokeFireExtinguishedCB(theZone)
|
||||||
|
-- also fully douse this one, so we can restart it later
|
||||||
|
inferno.douseFire(theZone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function inferno.contribute(x, z, theZone)
|
||||||
|
-- a cell starts burning if there is fuel
|
||||||
|
-- and the total contribution of all surrounding cells is
|
||||||
|
-- 10 or more, meaning that a 10 fire will ignite all surrounding
|
||||||
|
-- fields
|
||||||
|
-- bounds check
|
||||||
|
if x < 1 then return 0 end
|
||||||
|
if z < 1 then return 0 end
|
||||||
|
local numX = theZone.numX
|
||||||
|
if x > numX then return 0 end
|
||||||
|
local numZ = theZone.numZ
|
||||||
|
if z > numZ then return 0 end
|
||||||
|
local ele = theZone.grid[x][z]
|
||||||
|
if not ele.inside then return 0 end
|
||||||
|
if not ele.sparked then return 0 end -- not burning
|
||||||
|
if ele.extinguished then return -2 end -- water spill dampens
|
||||||
|
-- return stage that we are in if > 0
|
||||||
|
if ele.myStage >= 0 then
|
||||||
|
return ele.myStage -- mystage is positive int, "heat" 1..10
|
||||||
|
end
|
||||||
|
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
--
|
||||||
|
-- UPDATE
|
||||||
|
--
|
||||||
|
function inferno.update() -- for flag polling etc
|
||||||
|
timer.scheduleFunction(inferno.update, {}, timer.getTime() + 1/inferno.ups)
|
||||||
|
for idx, theZone in pairs (inferno.zones) do
|
||||||
|
if theZone.ignite and
|
||||||
|
theZone:testZoneFlag(theZone.ignite, "change", "lastIgnite") then
|
||||||
|
inferno.startFire(theZone)
|
||||||
|
end
|
||||||
|
|
||||||
|
if theZone.douse and theZone:testZoneFlag(theZone.douse, "change", "lastDouse") then
|
||||||
|
inferno.douseFire(theZone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- CONFIG & START
|
||||||
|
--
|
||||||
|
function inferno.readConfigZone()
|
||||||
|
inferno.name = "infernoConfig" -- make compatible with dml zones
|
||||||
|
local theZone = cfxZones.getZoneByName("infernoConfig")
|
||||||
|
if not theZone then
|
||||||
|
theZone = cfxZones.createSimpleZone("infernoConfig")
|
||||||
|
end
|
||||||
|
|
||||||
|
inferno.verbose = theZone.verbose
|
||||||
|
inferno.ups = theZone:getNumberFromZoneProperty("ups", 1)
|
||||||
|
inferno.fireTick = theZone:getNumberFromZoneProperty("fireTick", 10)
|
||||||
|
inferno.cellSize = theZone:getNumberFromZoneProperty("cellSize", 100)
|
||||||
|
inferno.menuName = theZone:getStringFromZoneProperty("menuName", "Firefighting")
|
||||||
|
inferno.impactSmoke = theZone:getBoolFromZoneProperty("impactSmoke", false)
|
||||||
|
|
||||||
|
if theZone:hasProperty("attachTo:") then
|
||||||
|
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
|
||||||
|
if radioMenu then -- requires optional radio menu to have loaded
|
||||||
|
local mainMenu = radioMenu.mainMenus[attachTo]
|
||||||
|
if mainMenu then
|
||||||
|
inferno.mainMenu = mainMenu
|
||||||
|
else
|
||||||
|
trigger.action.outText("+++inferno: cannot find super menu <" .. attachTo .. ">", 30)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
trigger.action.outText("+++inferno: REQUIRES radioMenu to run before inferno. 'AttachTo:' ignored.", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function inferno.start()
|
||||||
|
if not dcsCommon.libCheck then
|
||||||
|
trigger.action.outText("cfx inferno requires dcsCommon", 30)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if not dcsCommon.libCheck("cfx inferno", inferno.requiredLibs) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- read config
|
||||||
|
inferno.readConfigZone()
|
||||||
|
|
||||||
|
-- process inferno Zones
|
||||||
|
local attrZones = cfxZones.getZonesWithAttributeNamed("inferno")
|
||||||
|
for k, aZone in pairs(attrZones) do
|
||||||
|
inferno.readZone(aZone) -- process attributes
|
||||||
|
inferno.addZone(aZone) -- add to list
|
||||||
|
end
|
||||||
|
|
||||||
|
-- start update (DML)
|
||||||
|
timer.scheduleFunction(inferno.update, {}, timer.getTime() + 1/inferno.ups)
|
||||||
|
|
||||||
|
-- start fire tick update
|
||||||
|
timer.scheduleFunction(inferno.fireUpdate, {}, timer.getTime() + inferno.fireTick)
|
||||||
|
|
||||||
|
-- start all zones that have onstart
|
||||||
|
for gName, theZone in pairs(inferno.zones) do
|
||||||
|
if theZone.onStart then
|
||||||
|
inferno.ignite(theZone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- say Hi!
|
||||||
|
trigger.action.outText("cf/x inferno v" .. inferno.version .. " started.", 30)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if not inferno.start() then
|
||||||
|
trigger.action.outText("inferno failed to start up")
|
||||||
|
inferno = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[--
|
||||||
|
"ele" structure in grid
|
||||||
|
- fuel amount of fuel to burn. when < 0 the fire starves over the next cycles. By dumping water helicopters/planes reduce amount of available fuel
|
||||||
|
- extinguished if true, can't re-ignite
|
||||||
|
- myStage -- FSM for flames: >0 == burning, consumes fuel, <0 is starved of fuel and get smaller each tick
|
||||||
|
- fxname to reference flame fx
|
||||||
|
- eternal if true does not consume fuel. goes false when the first drop of water enters the cell from players
|
||||||
|
|
||||||
|
- center point of center
|
||||||
|
- fxpos - point in cell that has the fx
|
||||||
|
- mytype land type. only 1 burns
|
||||||
|
- inside is it inside the zone and can burn? true if so. used to make cells unburnable
|
||||||
|
- sparked if false this isn't burning
|
||||||
|
|
||||||
|
|
||||||
|
to do:
|
||||||
|
OK - callback for extinguis
|
||||||
|
OK - remember who contributes dropped inside successful and then receives ack when zone fully doused
|
||||||
|
|
||||||
|
Possible enhancements
|
||||||
|
- random range fuel
|
||||||
|
- wind for contribute (can precalc into table)
|
||||||
|
- boom in ignite
|
||||||
|
- clear after burn out
|
||||||
|
- leave debris after burn out, mabe place in ignite
|
||||||
|
--]]--
|
||||||
@ -1,5 +1,5 @@
|
|||||||
limitedAirframes = {}
|
limitedAirframes = {}
|
||||||
limitedAirframes.version = "1.6.0"
|
limitedAirframes.version = "1.7.0"
|
||||||
|
|
||||||
limitedAirframes.warningSound = "Quest Snare 3.wav"
|
limitedAirframes.warningSound = "Quest Snare 3.wav"
|
||||||
limitedAirframes.loseSound = "Death PIANO.wav"
|
limitedAirframes.loseSound = "Death PIANO.wav"
|
||||||
@ -11,33 +11,6 @@ limitedAirframes.requiredLibs = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
--[[-- VERSION HISTORY
|
--[[-- 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
|
|
||||||
- 1.4.1 - removed dependency to cfxPlayer
|
|
||||||
- 1.5.0 - persistence support
|
- 1.5.0 - persistence support
|
||||||
- 1.5.1 - new "announcer" attribute
|
- 1.5.1 - new "announcer" attribute
|
||||||
- 1.5.2 - integration with autoCSAR: prevent limitedAF from creating csar
|
- 1.5.2 - integration with autoCSAR: prevent limitedAF from creating csar
|
||||||
@ -49,28 +22,17 @@ limitedAirframes.requiredLibs = {
|
|||||||
- new hasUI attribute
|
- new hasUI attribute
|
||||||
- minor clean-up
|
- minor clean-up
|
||||||
- set numRed and numBlue on startup
|
- set numRed and numBlue on startup
|
||||||
|
1.7.0 - dcs jul-11, jul-22 bug prevention
|
||||||
|
- some cleanup
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
-- 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!!
|
|
||||||
|
|
||||||
-- 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
|
|
||||||
-- if zone can change ownership, player's coalition
|
|
||||||
-- is checked against current zone ownership
|
|
||||||
-- zone owner.
|
|
||||||
|
|
||||||
|
|
||||||
limitedAirframes.safeZones = {} -- safezones are zones where a crash or change plane does not
|
limitedAirframes.safeZones = {} -- safezones are zones where a crash or change plane does not
|
||||||
|
|
||||||
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
|
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
|
-- guarantee a minimum of 2 seconds between events
|
||||||
-- for this we save last event per player
|
-- for this we save last event per player
|
||||||
limitedAirframes.lastEvents = {}
|
limitedAirframes.lastEvents = {}
|
||||||
-- each time a plane crashes or is abandoned check
|
-- each time a plane crashes or is abandoned check
|
||||||
@ -196,10 +158,16 @@ end
|
|||||||
-- and also adds the player if unknown
|
-- and also adds the player if unknown
|
||||||
function limitedAirframes.addPlayerUnit(theUnit)
|
function limitedAirframes.addPlayerUnit(theUnit)
|
||||||
local theSide = theUnit:getCoalition()
|
local theSide = theUnit:getCoalition()
|
||||||
local uName = theUnit:getName()
|
local uName = "**XXXX**"
|
||||||
if not uName then uName = "**XXXX**" end
|
if theUnit.getName then
|
||||||
local pName = theUnit:getPlayerName()
|
uName = theUnit:getName()
|
||||||
if not pName then pName = "**????**" end
|
end
|
||||||
|
-- if not uName then uName = "**XXXX**" end
|
||||||
|
local pName = "**????**"
|
||||||
|
if theUnit.getPlayerName then
|
||||||
|
pName = theUnit:getPlayerName()
|
||||||
|
end
|
||||||
|
-- if not pName then pName = "**????**" end
|
||||||
limitedAirframes.updatePlayer(pName, "alive")
|
limitedAirframes.updatePlayer(pName, "alive")
|
||||||
|
|
||||||
local desc = "unit <" .. uName .. "> controlled by <" .. pName .. ">"
|
local desc = "unit <" .. uName .. "> controlled by <" .. pName .. ">"
|
||||||
@ -273,6 +241,7 @@ end
|
|||||||
--
|
--
|
||||||
-- E V E N T H A N D L I N G
|
-- E V E N T H A N D L I N G
|
||||||
--
|
--
|
||||||
|
--[[--
|
||||||
function limitedAirframes.XXXisKnownPlayerUnit(theUnit)
|
function limitedAirframes.XXXisKnownPlayerUnit(theUnit)
|
||||||
if not theUnit then return false end
|
if not theUnit then return false end
|
||||||
local aName = theUnit:getName()
|
local aName = theUnit:getName()
|
||||||
@ -281,6 +250,7 @@ function limitedAirframes.XXXisKnownPlayerUnit(theUnit)
|
|||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
--]]--
|
||||||
|
|
||||||
function limitedAirframes.isInteresting(eventID)
|
function limitedAirframes.isInteresting(eventID)
|
||||||
-- return true if we are interested in this event, false else
|
-- return true if we are interested in this event, false else
|
||||||
@ -294,6 +264,7 @@ function limitedAirframes.preProcessor(event)
|
|||||||
-- make sure it has an initiator
|
-- make sure it has an initiator
|
||||||
if not event.initiator then return false end -- no initiator
|
if not event.initiator then return false end -- no initiator
|
||||||
local theUnit = event.initiator
|
local theUnit = event.initiator
|
||||||
|
if not theUnit.getName then return false end -- DCS Jul-22 bug
|
||||||
local uName = theUnit:getName()
|
local uName = theUnit:getName()
|
||||||
|
|
||||||
|
|
||||||
@ -353,6 +324,7 @@ function limitedAirframes.somethingHappened(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local theUnit = event.initiator
|
local theUnit = event.initiator
|
||||||
|
if not theUnit.getName then return end
|
||||||
local unitName = theUnit:getName()
|
local unitName = theUnit:getName()
|
||||||
local ID = event.id
|
local ID = event.id
|
||||||
local myType = theUnit:getTypeName()
|
local myType = theUnit:getTypeName()
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
cfxObjectSpawnZones = {}
|
cfxObjectSpawnZones = {}
|
||||||
cfxObjectSpawnZones.version = "2.1.0"
|
cfxObjectSpawnZones.version = "2.1.1"
|
||||||
cfxObjectSpawnZones.requiredLibs = {
|
cfxObjectSpawnZones.requiredLibs = {
|
||||||
"dcsCommon", -- common is of course needed for everything
|
"dcsCommon", -- common is of course needed for everything
|
||||||
-- pretty stupid to check for this since we
|
-- pretty stupid to check for this since we
|
||||||
@ -15,7 +15,7 @@ cfxObjectSpawnZones.verbose = false
|
|||||||
version history
|
version history
|
||||||
2.0.0 - dmlZones
|
2.0.0 - dmlZones
|
||||||
2.1.0 - autoTurn attribute
|
2.1.0 - autoTurn attribute
|
||||||
|
2.1.1 - now spawns the correct country
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
-- respawn currently happens after theSpawns is deleted and cooldown seconds have passed
|
-- respawn currently happens after theSpawns is deleted and cooldown seconds have passed
|
||||||
@ -246,7 +246,8 @@ function cfxObjectSpawnZones.spawnObjectNTimes(aSpawner, theType, n, container)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- spawn in dcs
|
-- spawn in dcs
|
||||||
local theObject = coalition.addStaticObject(aSpawner.rawOwner, theStaticData) -- create in dcs
|
--local theObject = coalition.addStaticObject(aSpawner.rawOwner, theStaticData) -- create in dcs
|
||||||
|
local theObject = coalition.addStaticObject(aSpawner.country, theStaticData) -- create in dcs
|
||||||
table.insert(container, theObject) -- add to collection
|
table.insert(container, theObject) -- add to collection
|
||||||
if aSpawner.isCargo and aSpawner.managed then
|
if aSpawner.isCargo and aSpawner.managed then
|
||||||
if cfxCargoManager then
|
if cfxCargoManager then
|
||||||
@ -303,7 +304,8 @@ function cfxObjectSpawnZones.spawnObjectNTimes(aSpawner, theType, n, container)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- spawn in dcs
|
-- spawn in dcs
|
||||||
local theObject = coalition.addStaticObject(aSpawner.rawOwner, theStaticData) -- this will generate an event!
|
-- local theObject = coalition.addStaticObject(aSpawner.rawOwner, theStaticData) -- this will generate an event!
|
||||||
|
local theObject = coalition.addStaticObject(aSpawner.country, theStaticData) -- this will generate an event!
|
||||||
table.insert(container, theObject)
|
table.insert(container, theObject)
|
||||||
-- see if it is managed cargo
|
-- see if it is managed cargo
|
||||||
if aSpawner.isCargo and aSpawner.managed then
|
if aSpawner.isCargo and aSpawner.managed then
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
cfxOwnedZones = {}
|
cfxOwnedZones = {}
|
||||||
cfxOwnedZones.version = "1.3.0"
|
cfxOwnedZones.version = "1.3.0gamma"
|
||||||
cfxOwnedZones.verbose = false
|
cfxOwnedZones.verbose = false
|
||||||
cfxOwnedZones.announcer = true
|
cfxOwnedZones.announcer = true
|
||||||
cfxOwnedZones.name = "cfxOwnedZones"
|
cfxOwnedZones.name = "cfxOwnedZones"
|
||||||
@ -61,6 +61,7 @@ cfxOwnedZones.name = "cfxOwnedZones"
|
|||||||
- blueLost! zone output
|
- blueLost! zone output
|
||||||
- ownedBy direct zone output
|
- ownedBy direct zone output
|
||||||
- neutral! zone output
|
- neutral! zone output
|
||||||
|
1.3.0g - DML 2.x compatibility
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
cfxOwnedZones.requiredLibs = {
|
cfxOwnedZones.requiredLibs = {
|
||||||
@ -1193,7 +1194,7 @@ function cfxOwnedZones.loadData()
|
|||||||
end
|
end
|
||||||
theZone.owner = zData.owner
|
theZone.owner = zData.owner
|
||||||
theZone.state = zData.state
|
theZone.state = zData.state
|
||||||
if zData.conquered then
|
if zData.conquered and theZone.conqueredFlag then
|
||||||
cfxZones.setFlagValue(theZone.conqueredFlag, zData.conquered, theZone)
|
cfxZones.setFlagValue(theZone.conqueredFlag, zData.conquered, theZone)
|
||||||
end
|
end
|
||||||
-- update mark in map
|
-- update mark in map
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
cfxOwnedZones = {}
|
cfxOwnedZones = {}
|
||||||
cfxOwnedZones.version = "2.4.0"
|
cfxOwnedZones.version = "2.4.1"
|
||||||
cfxOwnedZones.verbose = false
|
cfxOwnedZones.verbose = false
|
||||||
cfxOwnedZones.announcer = true
|
cfxOwnedZones.announcer = true
|
||||||
cfxOwnedZones.name = "cfxOwnedZones"
|
cfxOwnedZones.name = "cfxOwnedZones"
|
||||||
@ -44,6 +44,7 @@ cfxOwnedZones.name = "cfxOwnedZones"
|
|||||||
- code clean-up
|
- code clean-up
|
||||||
2.3.1 - restored getNearestOwnedZoneToPoint
|
2.3.1 - restored getNearestOwnedZoneToPoint
|
||||||
2.4.0 - dmlZones masterOwner update
|
2.4.0 - dmlZones masterOwner update
|
||||||
|
2.4.1 - conquered flag now correctly guarded in loadData()
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
cfxOwnedZones.requiredLibs = {
|
cfxOwnedZones.requiredLibs = {
|
||||||
@ -761,7 +762,7 @@ function cfxOwnedZones.loadData()
|
|||||||
local theZone = cfxOwnedZones.getOwnedZoneByName(zName)
|
local theZone = cfxOwnedZones.getOwnedZoneByName(zName)
|
||||||
if theZone then
|
if theZone then
|
||||||
theZone.owner = zData.owner
|
theZone.owner = zData.owner
|
||||||
if zData.conquered then
|
if zData.conquered and theZone.conqueredFlag then
|
||||||
theZone:setFlagValue(theZone.conqueredFlag, zData.conquered)
|
theZone:setFlagValue(theZone.conqueredFlag, zData.conquered)
|
||||||
end
|
end
|
||||||
-- update mark in map
|
-- update mark in map
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
radioMenu = {}
|
radioMenu = {}
|
||||||
radioMenu.version = "4.0.0"
|
radioMenu.version = "4.0.1"
|
||||||
radioMenu.verbose = false
|
radioMenu.verbose = false
|
||||||
radioMenu.ups = 1
|
radioMenu.ups = 1
|
||||||
radioMenu.requiredLibs = {
|
radioMenu.requiredLibs = {
|
||||||
"dcsCommon", -- always
|
"dcsCommon", -- always
|
||||||
"cfxZones", -- Zones, of course
|
"cfxZones", -- Zones, of course
|
||||||
|
"cfxMX",
|
||||||
}
|
}
|
||||||
-- note: cfxMX is optional unless using types or groups attributes
|
-- note: cfxMX is optional unless using types or groups attributes
|
||||||
radioMenu.menus = {}
|
radioMenu.menus = {}
|
||||||
@ -18,6 +19,7 @@ radioMenu.lateGroups = {} -- dict by ID
|
|||||||
detect cyclic references
|
detect cyclic references
|
||||||
4.0.0 - added infrastructure for dynamic players (DCS 2.9.6)
|
4.0.0 - added infrastructure for dynamic players (DCS 2.9.6)
|
||||||
- added dynamic player support
|
- added dynamic player support
|
||||||
|
4.0.1 - MX no longer optional, so ask for it
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
function radioMenu.addRadioMenu(theZone)
|
function radioMenu.addRadioMenu(theZone)
|
||||||
|
|||||||
@ -930,14 +930,7 @@ function cfxReconMode.readConfigZone()
|
|||||||
-- note: must match exactly!!!!
|
-- note: must match exactly!!!!
|
||||||
local theZone = cfxZones.getZoneByName("reconModeConfig")
|
local theZone = cfxZones.getZoneByName("reconModeConfig")
|
||||||
if not theZone then
|
if not theZone then
|
||||||
if cfxReconMode.verbose then
|
|
||||||
trigger.action.outText("+++rcn: no config zone!", 30)
|
|
||||||
end
|
|
||||||
theZone = cfxZones.createSimpleZone("reconModeConfig")
|
theZone = cfxZones.createSimpleZone("reconModeConfig")
|
||||||
else
|
|
||||||
if cfxReconMode.verbose then
|
|
||||||
trigger.action.outText("+++rcn: found config zone!", 30)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
cfxReconMode.verbose = theZone.verbose --cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
cfxReconMode.verbose = theZone.verbose --cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||||
|
|||||||
@ -12,8 +12,17 @@ slotty.version = "1.1.0"
|
|||||||
Version history
|
Version history
|
||||||
1.0.0 - Initial version
|
1.0.0 - Initial version
|
||||||
1.1.0 - "noSlotty" global disable flag, anti-mirror SSB flag
|
1.1.0 - "noSlotty" global disable flag, anti-mirror SSB flag
|
||||||
|
1.2.0 - better (delayed) nilling for cfxSSB ocupied clients to
|
||||||
|
- avoid a race condition
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
function slotty.delayedSSBNil(args)
|
||||||
|
uName = args
|
||||||
|
if cfxSSBClient.verbose then
|
||||||
|
trigger.action.outText("slotty-->SSBClient: nilling <" .. args .. ">, now unoccupied", 30)
|
||||||
|
end
|
||||||
|
cfxSSBClient.occupiedUnits[uName] = nil
|
||||||
|
end
|
||||||
|
|
||||||
function slotty:onEvent(event)
|
function slotty:onEvent(event)
|
||||||
if not event.initiator then return end
|
if not event.initiator then return end
|
||||||
@ -42,7 +51,10 @@ function slotty:onEvent(event)
|
|||||||
|
|
||||||
-- interface with SSBClient for compatibility
|
-- interface with SSBClient for compatibility
|
||||||
if cfxSSBClient and cfxSSBClient.occupiedUnits then
|
if cfxSSBClient and cfxSSBClient.occupiedUnits then
|
||||||
cfxSSBClient.occupiedUnits[uName] = nil
|
-- to resolve a race condition, we schedule nilling the
|
||||||
|
-- ssbClientSlot in 1/2 second
|
||||||
|
-- cfxSSBClient.occupiedUnits[uName] = nil
|
||||||
|
timer.scheduleFunction(slotty.delayedSSBNil, uName, timer.getTime() + 0.5)
|
||||||
end
|
end
|
||||||
|
|
||||||
if isSP then
|
if isSP then
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
stopGap = {}
|
stopGap = {}
|
||||||
stopGap.version = "1.2.0"
|
stopGap.version = "1.3.0"
|
||||||
stopGap.verbose = false
|
stopGap.verbose = false
|
||||||
stopGap.ssbEnabled = true
|
stopGap.ssbEnabled = true
|
||||||
stopGap.ignoreMe = "-sg"
|
stopGap.ignoreMe = "-sg"
|
||||||
stopGap.spIgnore = "-sp" -- only single-player ignored
|
stopGap.spIgnore = "-sp" -- only single-player ignored
|
||||||
|
stopGap.noParking = false -- turn on to ignore all 'from parking'
|
||||||
stopGap.isMP = false
|
stopGap.isMP = false
|
||||||
stopGap.running = true
|
stopGap.running = true
|
||||||
stopGap.refreshInterval = -1 -- seconds to refresh all statics. -1 = never, 3600 = once every hour
|
stopGap.refreshInterval = -1 -- seconds to refresh all statics. -1 = never, 3600 = once every hour
|
||||||
@ -15,7 +16,7 @@ stopGap.requiredLibs = {
|
|||||||
"cfxMX",
|
"cfxMX",
|
||||||
}
|
}
|
||||||
--[[--
|
--[[--
|
||||||
Written and (c) 2023 by Christian Franz
|
Written and (c) 2023-2024 by Christian Franz
|
||||||
|
|
||||||
Replace all player units with static aircraft until the first time
|
Replace all player units with static aircraft until the first time
|
||||||
that a player slots into that plane. Static is then replaced with live player unit.
|
that a player slots into that plane. Static is then replaced with live player unit.
|
||||||
@ -55,6 +56,8 @@ stopGap.requiredLibs = {
|
|||||||
1.2.0 - DCS dynamic player spawn compatibility
|
1.2.0 - DCS dynamic player spawn compatibility
|
||||||
stopGaps only works with MX data, so we are good, no changes
|
stopGaps only works with MX data, so we are good, no changes
|
||||||
required
|
required
|
||||||
|
1.3.0 - carriers in shallow waters also no longer handled as viable
|
||||||
|
- noParking option
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
@ -102,11 +105,26 @@ function stopGap.isGroundStart(theGroup)
|
|||||||
if action == "Turning Point" then return false end
|
if action == "Turning Point" then return false end
|
||||||
if action == "Landing" then return false end
|
if action == "Landing" then return false end
|
||||||
if action == "From Runway" then return false end
|
if action == "From Runway" then return false end
|
||||||
|
if stopGap.noParking then
|
||||||
|
local loAct = string.lower(action)
|
||||||
|
if loAct == "from parking area" or
|
||||||
|
loAct == "from parking area hot" then
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("StopG: Player Group <" .. theGroup.name .. "> NOPARKING: [" .. action .. "] must be skipped.", 30)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
-- looks like aircraft is on the ground
|
-- looks like aircraft is on the ground
|
||||||
-- but is it in water (carrier)?
|
-- but is it in water (carrier)?
|
||||||
local u1 = theGroup.units[1]
|
local u1 = theGroup.units[1]
|
||||||
local sType = land.getSurfaceType(u1) -- has fields x and y
|
local sType = land.getSurfaceType(u1) -- has fields x and y
|
||||||
if sType == 3 then return false end
|
if sType == 3 or sType == 2 then
|
||||||
|
if stopGap.verbose then
|
||||||
|
trigger.action.outText("StopG: Player Group <" .. theGroup.name .. "> SEA BASED: [" .. action .. "], land type " .. sType .. " should be skipped.", 30)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end -- water & shallow water a no-go
|
||||||
if stopGap.verbose then
|
if stopGap.verbose then
|
||||||
trigger.action.outText("StopG: Player Group <" .. theGroup.name .. "> GROUND BASED: " .. action .. ", land type " .. sType, 30)
|
trigger.action.outText("StopG: Player Group <" .. theGroup.name .. "> GROUND BASED: " .. action .. ", land type " .. sType, 30)
|
||||||
end
|
end
|
||||||
@ -422,6 +440,7 @@ function stopGap.readConfigZone(theZone)
|
|||||||
stopGap.verbose = theZone.verbose
|
stopGap.verbose = theZone.verbose
|
||||||
stopGap.ssbEnabled = theZone:getBoolFromZoneProperty("ssb", true)
|
stopGap.ssbEnabled = theZone:getBoolFromZoneProperty("ssb", true)
|
||||||
stopGap.enabled = theZone:getBoolFromZoneProperty("onStart", true)
|
stopGap.enabled = theZone:getBoolFromZoneProperty("onStart", true)
|
||||||
|
stopGap.noParking = theZone:getBoolFromZoneProperty("noParking", false)
|
||||||
if theZone:hasProperty("on?") then
|
if theZone:hasProperty("on?") then
|
||||||
stopGap.turnOnFlag = theZone:getStringFromZoneProperty("on?", "*<none>")
|
stopGap.turnOnFlag = theZone:getStringFromZoneProperty("on?", "*<none>")
|
||||||
stopGap.lastTurnOnFlag = trigger.misc.getUserFlag(stopGap.turnOnFlag)
|
stopGap.lastTurnOnFlag = trigger.misc.getUserFlag(stopGap.turnOnFlag)
|
||||||
|
|||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user