Version 2.4.3

New FireCtrl,, lua2json bug work-around
This commit is contained in:
Christian Franz 2025-02-13 08:19:09 +01:00
parent 3a87f7b784
commit dff5faa06e
19 changed files with 27707 additions and 26044 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,5 +1,5 @@
FARPZones = {}
FARPZones.version = "2.2.0"
FARPZones.version = "2.3.0"
FARPZones.verbose = false
--[[--
Version History
@ -26,6 +26,8 @@ FARPZones.verbose = false
2.1.0 - integration with camp: needs repairs, produceResourceVehicles()
2.1.1 - loading a farp from data respaws all defenders and resource vehicles
2.2.0 - changing a FARP's owner invokes SSBClient if it is loaded
2.3.0 - new attributes redCap!, blueCap! captured! and farpMethod
- send out signals
--]]--
@ -209,6 +211,19 @@ function FARPZones.createFARPFromZone(aZone)
theFarp.hidden = aZone:getBoolFromZoneProperty("hidden", false)
theFarp.showTitle = aZone:getBoolFromZoneProperty("showTitle", true)
theFarp.neutralProduction = aZone:getBoolFromZoneProperty("neutralProduction", false)
-- capture signals
if aZone:hasProperty("redCap!") then
theFarp.redCap = aZone:getStringFromZoneProperty("redCap!", "none")
end
if aZone:hasProperty("blueCap!") then
theFarp.blueCap = aZone:getStringFromZoneProperty("blueCap!")
end
if aZone:hasProperty("captured!") then
theFarp.captured = aZone:getStringFromZoneProperty("captured!", "none")
end
theFarp.outMethod = aZone:getStringFromZoneProperty("farpMethod", "inc")
return theFarp
end
@ -415,7 +430,7 @@ function FARPZones.somethingHappened(event)
local ID = event.id
--trigger.action.outText("FZ: something happened", 30)
local aFarp = event.place
local aFarp = event.place -- place is type Airbase
local zonedFarp = FARPZones.getFARPZoneForFARP(aFarp)
if not zonedFarp then
@ -447,6 +462,18 @@ function FARPZones.somethingHappened(event)
if newOwner == 2 then blueRed = "Blue" end
trigger.action.outText("FARP " .. zonedFarp.zone.name .. " captured by " .. blueRed .."!", 30)
trigger.action.outSound("Quest Snare 3.wav")
-- send out signals
if zonedFarp.redCap and newOwner == 1 then
cfxZones.pollFlag(zonedFarp.redCap, zonedFarp.outMethod, zonedFarp.zone)
end
if zonedFarp.blueCap and newOwner == 2 then
cfxZones.pollFlag(zonedFarp.blueCap, zonedFarp.outMethod, zonedFarp.zone)
end
if zonedFarp.captured then
cfxZones.pollFlag(zonedFarp.captured, zonedFarp.outMethod, zonedFarp.zone)
end
zonedFarp.owner = newOwner
zonedFarp.zone.owner = newOwner
-- update color in map

View File

@ -99,7 +99,8 @@ function LZ.createLZWithZone(theZone)
if LZ.verbose or theZone.verbose then
trigger.action.outText("+++LZ: new LZ <".. theZone.name ..">", 30)
end
trigger.action.outText("zone <" .. theZone.name .. "> type of radius is <" .. type(theZone.radius) .. ">, val = " .. tonumber(theZone.radius), 30)
end
function LZ.nameMatchForArray(theName, theArray, wildcard)
@ -226,9 +227,13 @@ function LZ:onEvent(event)
local theUnit = event.initiator
if not Unit.isExist(theUnit) then return end
local p = theUnit:getPoint()
for idx, aZone in pairs(LZ.LZs) do
-- see if inside the zone
if LZ.verbose then
trigger.action.outText("+++LZ: zone <" .. aZone.name .. "> for unit <" .. theUnit:getName() .. "> proccing", 30)
end
if true then return end
local inZone, percent, dist = cfxZones.pointInZone(p, aZone)
if inZone then
-- see if this unit interests us at all

View File

@ -1,15 +1,75 @@
WHpersistence = {}
WHpersistence.version = "1.0.0"
WHpersistence.version = "1.1.0"
WHpersistence.requiredLibs = {
"dcsCommon",
"cfxZones",
"persistence",
}
--[[--
Version History
1.0.0 - Initial version
1.1.0 - fixed an issue with net.lua2json
- enable dynamic spawns can ferry to other airfields
--]]--
--
-- load / save (game data)
-- Update & monitor
--
function WHpersistence.saveData()
local theData = {}
WHpersistence.lastState = nil
function WHpersistence.update()
timer.scheduleFunction(WHpersistence.update, nil, timer.getTime() + 1/WHpersistence.ups)
trigger.action.outText("+++WHp: start update", 30)
local newState = WHpersistence.getCurrentState()
-- now look for discrepacies to last state
local oldState = WHpersistence.lastState
-- iterate all bases
local hasChange = false
for name, inv in pairs(newState) do
local oldBaseInv = oldState[name]
-- WH has three entries: liquids, weapon and aircraft
-- compare iarcraft stats
if not inv.aircraft then
trigger.action.outText("+++WHp: NEW STATE: no aircraft data for <" .. name .. ">", 30)
else
for ref, num in pairs(inv.aircraft) do
oldNum = oldBaseInv.aircraft[ref]
trigger.action.outText("AF <" .. name .. ">, AC: <" .. ref .. "> -- " .. num .. ".", 30)
if oldNum ~= num then
if not oldNum then oldNum = "NIL" end
trigger.action.outText("+++WHp: WH <" .. name .. ">: aircraft type <" .. ref .. "> old num <" .. num .. ">, new <" .. oldNum .. ">", 30)
hasChange = true
end
end
end
end
-- reverse: has a plane left?
for name, inv in pairs(oldState) do
local oldBaseInv = newState[name]
-- WH has three entries: liquids, weapon and aircraft
-- compare iarcraft stats
if not inv.aircraft then
trigger.action.outText("+++WHp: OLD STATE: no aircraft data for <" .. name .. ">", 30)
else
for ref, num in pairs(inv.aircraft) do
oldNum = oldBaseInv.aircraft[ref]
if not oldNum then
trigger.action.outText("+++WHp: WH <" .. name .. ">: aircraft type <" .. ref .. "> REMOVED ENTIRELY", 30)
hasChange = true
end
end
end
end
if hasChange then
trigger.action.outText("+++WHp: has change", 30)
end
WHpersistence.lastState = newState
end
function WHpersistence.getCurrentState()
local theWH = {}
-- generate all WH data from all my airfields
local allMyBase = world:getAirbases()
@ -17,9 +77,33 @@ function WHpersistence.saveData()
local name = theBase:getName()
local WH = theBase:getWarehouse()
local inv = WH:getInventory()
-- transcribe for bug in lua2json
local l0 = 0; local l1 = 0; local l2 = 0; local l3 = 0
if inv.liquids[0] then l0 = inv.liquids[0] end
if inv.liquids[1] then l1 = inv.liquids[1] end
if inv.liquids[2] then l2 = inv.liquids[2] end
if inv.liquids[3] then l3 = inv.liquids[3] end
bLiq = {["A"] = l0, -- lua2json can't handle a[0]
["B"] = l1,
["C"] = l2,
["D"] = l3,}
inv.bLiq = bLiq
theWH[name] = inv
end
theData.theWH = theWH
trigger.action.outText("+++WHp: read state", 30)
return theWH
end
--
-- load / save (game data)
--
function WHpersistence.saveData()
local theData = {}
local theWH = {}
theData.theWH = WHpersistence.getCurrentState() -- theWH
if WHpersistence.verbose then
trigger.action.outText("+++WHp: saving data", 30)
end
return theData, WHpersistence.sharedData -- second val currently nil
end
@ -33,23 +117,36 @@ function WHpersistence.loadData()
end
return
end
if WHpersistence.verbose then
trigger.action.outText("+++WHp: restoring from file", 30)
end
local origState = WHpersistence.getCurrentState()
-- set up all warehouses from data loaded
-- WARNING: if original was set, but saved emptpy,
-- we must now erase the original!
for name, inv in pairs(theData.theWH) do
trigger.action.outText("+++restoring <" .. name .. ">", 30)
-- trigger.action.outText("+++restoring WH <" .. name .. ">", 30)
local theBase = Airbase.getByName(name)
if theBase then
local theWH = theBase:getWarehouse()
if theWH then
-- we go through weapon, liquids and aircraft
for idx, liq in pairs(inv.liquids) do
theWH:setLiquidAmount(idx, liq)
trigger.action.outText(name .. ": Liq <" .. idx .. "> : <" .. liq .. ">", 30)
end
-- do liqids "manually"
if inv.bLiq then
theWH:setLiquidAmount(0, inv.bLiq["A"])
theWH:setLiquidAmount(1, inv.bLiq["B"])
theWH:setLiquidAmount(2, inv.bLiq["C"])
theWH:setLiquidAmount(3, inv.bLiq["D"])
else
trigger.action.outText("+++WHPersistence: WARNING - legacy save data, will not set liquids for <" .. name .. ">", 30)
end
for ref, num in pairs(inv.weapon) do
theWH:setItem(ref, num)
end
for ref, num in pairs(inv.aircraft) do
theWH:setItem(ref, num)
if WHpersistence.verbose then trigger.action.outText(name .. ": Setting # of Aircraft <" .. ref .. "> to <" .. num .. ">", 30) end
end
else
trigger.action.outText(name .. ": no warehouse")
@ -58,6 +155,28 @@ function WHpersistence.loadData()
trigger.action.outText(name .. ": no airbase")
end
end
local newInv = theData.theWH
for name, inv in pairs(origState) do
local bInv = newInv[name] -- from file!
local theBase = Airbase.getByName(name)
local theWH = theBase:getWarehouse() -- in case we neet to change
if theWH then
for ref, num in pairs(inv.weapon) do
if not bInv.weapon[ref] then
theWH:setItem(ref, 0)
if WHpersistence.verbose then trigger.action.outText(name .. ": Weapon <" .. ref .. "> : REMOVED", 30) end
end
end
for ref, num in pairs(inv.aircraft) do
if not bInv.aircraft[ref] then
theWH:setItem(ref, 0)
if WHpersistence.verbose then trigger.action.outText(name .. ": Aircraft <" .. ref .. "> removed", 30) end
end
end
else
trigger.action.outText(name .. " can't access this airbase", 30)
end
end
end
--
-- config
@ -68,6 +187,8 @@ function WHpersistence.readConfigZone()
theZone = cfxZones.createSimpleZone("WHpersistenceConfig")
end
WHpersistence.verbose = theZone.verbose
WHpersistence.monitor = theZone:getBoolFromZoneProperty("monitor", false)
WHpersistence.ups = theZone:getNumberFromZoneProperty("ups", 0.1) -- every 10 seconds
end
--
-- GO
@ -77,7 +198,7 @@ function WHpersistence.start()
trigger.action.outText("cfx WHpersistence requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx Raise Flag", WHpersistence.requiredLibs)then return false end
if not dcsCommon.libCheck("cfx WH Persistence", WHpersistence.requiredLibs)then return false end
WHpersistence.readConfigZone()
if persistence then
callbacks = {}
@ -86,6 +207,11 @@ function WHpersistence.start()
-- now load my data
WHpersistence.loadData()
end
if WHpersistence.monitor then
WHpersistence.lastState = WHpersistence.getCurrentState()
timer.scheduleFunction(WHpersistence.update, nil, timer.getTime() + 1/WHpersistence.ups)
end
trigger.action.outText("cfx WHpersistence v" .. WHpersistence.version .. " started.", 30)
return true
end

View File

@ -1,11 +1,19 @@
airtank = {}
airtank.version = "1.0.0"
airtank.version = "1.0.1"
-- Module to extinguish fires controlled by the 'inferno' module.
-- For 'airtank' fire extinguisher aircraft modules.
airtank.requiredLibs = {
"dcsCommon",
"cfxZones",
}
--[[--
Version History
1.0.0 - Initial release
1.0.1 - removed attachTo: bug
--]]--
airtank.tanks = {} -- player data by GROUP name, will break with multi-unit groups
-- tank attributes
-- pumpArmed -- for hovering fills.
@ -192,9 +200,20 @@ function airtank.installMenusForUnit(theUnit) -- assumes all unfit types are wee
local pRoot = airtank.roots[gName]
if pRoot then
missionCommands.removeItemForGroup(gID, pRoot)
pRoot = nil
end
-- handle main menu
local mainMenu = nil
if airtank.mainMenu then
mainMenu = radioMenu.getMainMenuFor(airtank.mainMenu)
end
-- now add the airtank menu
pRoot = missionCommands.addSubMenuForGroup(gID, airtank.menuName, airtank.mainMenu)
pRoot = missionCommands.addSubMenuForGroup(gID, airtank.menuName, mainMenu)
if airtank.verbose and airtank.mainMenu then
trigger.action.outText("+++airT: attaching airtank menu of group <" .. gName .. "> to existing root", 30)
end
airtank.roots[gName] = pRoot -- save for later
local args = {gName, uName, gID, uType, pName}
-- menus:
@ -535,7 +554,10 @@ function airtank.readConfigZone()
if radioMenu then -- requires optional radio menu to have loaded
local mainMenu = radioMenu.mainMenus[attachTo]
if mainMenu then
airtank.mainMenu = mainMenu
airtank.mainMenu = mainMenu -- the zone.
if airtank.verbose or theZone.verbose then
trigger.action.outText("+++airT: attached to menu from zone <" .. theZone.name .. ">", 30)
end
else
trigger.action.outText("+++airtank: cannot find super menu <" .. attachTo .. ">", 30)
end

View File

@ -22,7 +22,7 @@ VERSION HISTORY
--]]--
--
-- CURRENTLY REQUIRES SINGLE-UNIT PLAYER GROUPS
-- REQUIRES CLONEZONES MODULE TO BE RUNNING, BUT NOT TO BE LOADED ON START
-- REQUIRES CLONEZONES MODULE
--
camp.camps = {} -- all camps on the map
camp.roots = {} -- all player group comms roots
@ -35,9 +35,7 @@ function camp.getMyCurrentCamp(theUnit) -- returns first hit player is in
local coa = theUnit:getCoalition()
local p = theUnit:getPoint()
for idx, theCamp in pairs(camp.camps) do
if theCamp.owner == coa and theCamp:pointInZone(p) then
return theCamp
end
if theCamp.owner == coa and theCamp:pointInZone(p) then return theCamp end
end
return nil
end
@ -45,18 +43,14 @@ end
function camp.getCampsForCoa(coa)
local myCamps = {}
for idx, theCamp in pairs(camp.camps) do
if theCamp.owner == coa then
table.insert(myCamps, theCamp)
end
if theCamp.owner == coa then table.insert(myCamps, theCamp) end
end
return myCamps
end
function camp.createCampWithZone(theZone)
-- look for all cloners inside my zone
if theZone.verbose or camp.verbose then
trigger.action.outText("+++camp: processing <" .. theZone.name .. ">, owner is <" .. theZone.owner .. ">", 30)
end
if theZone.verbose or camp.verbose then trigger.action.outText("+++camp: processing <" .. theZone.name .. ">, owner is <" .. theZone.owner .. ">", 30) end
local allZones = cfxZones.getAllZonesInsideZone(theZone)
local cloners = {}
@ -65,19 +59,12 @@ function camp.createCampWithZone(theZone)
for idx, aZone in pairs(allZones) do
if aZone:hasProperty("nocamp") then
-- this zone cannot be part of a camp
elseif aZone:hasProperty("cloner") then
-- this is a clone zone and part of my camp
table.insert(cloners, aZone)
if not aZone:hasProperty("blueOnly") then
table.insert(redCloners, aZone)
end
if not aZone:hasProperty("redOnly") then
table.insert(blueCloners, aZone)
end
if theZone.verbose or camp.verbose then
trigger.action.outText("Cloner <" .. aZone.name .. "> is part of camp <" .. theZone.name .. ">", 30)
end
if not aZone:hasProperty("blueOnly") then table.insert(redCloners, aZone) end
if not aZone:hasProperty("redOnly") then table.insert(blueCloners, aZone) end
if theZone.verbose or camp.verbose then trigger.action.outText("Cloner <" .. aZone.name .. "> is part of camp <" .. theZone.name .. ">", 30) end
end
end
if #cloners < 1 then
@ -96,9 +83,7 @@ function camp.createCampWithZone(theZone)
theZone.upgradeCost = theZone:getNumberFromZoneProperty("upgradeCost", 3 * theZone.repairCost)
if theZone:hasProperty("FARP") then
theZone.isAlsoFARP = true
if theZone.verbose or camp.verbose then
trigger.action.outText("+++camp: <" .. theZone.name .. "> has FARP attached", 30)
end
if theZone.verbose or camp.verbose then trigger.action.outText("+++camp: <" .. theZone.name .. "> has FARP attached", 30) end
end
end
@ -107,22 +92,18 @@ end
--
function camp.update()
-- call me in a second to poll triggers
timer.scheduleFunction(camp.update, {}, timer.getTime() + 1/camp.ups)
-- timer.scheduleFunction(camp.update, {}, timer.getTime() + 1/camp.ups)
end
function camp:onEvent(theEvent)
if not theEvent then return end
if not theEvent.initiator then return end
local theUnit = theEvent.initiator
-- if not theUnit.getName then return end
-- if not theUnit.getPlayerName then return end
if not cfxMX.isDynamicPlayer(theUnit) then return end
local id = theEvent.id
if id == 15 then -- birth
camp.lateProcessPlayer(theUnit)
if camp.verbose then
trigger.action.outText("camp: late player processing for <" .. theUnit:getName() .. ">", 30)
end
if camp.verbose then trigger.action.outText("camp: late player processing for <" .. theUnit:getName() .. ">", 30) end
end
end
@ -153,12 +134,6 @@ function camp.processPlayers()
for idx, gData in pairs(cfxMX.playerGroupByName) do
gID = gData.groupId
gName = gData.name
--[[-- local theRoot = missionCommands.addSubMenuForGroup(gID, "Funds / Repairs / Upgrades")
camp.roots[gName] = theRoot
local c00 = missionCommands.addCommandForGroup(gID, "Theatre Overview", theRoot, camp.redirectTFunds, {gName, gID, "tfunds"})
local c0 = missionCommands.addCommandForGroup(gID, "Local Funds & Status Overview", theRoot, camp.redirectFunds, {gName, gID, "funds"})
local c1 = missionCommands.addCommandForGroup(gID, "REPAIRS: Purchase local repairs", theRoot, camp.redirectRepairs, {gName, gID, "repair"})
local c2 = missionCommands.addCommandForGroup(gID, "UPGRADE: Purchase local upgrades", theRoot, camp.redirectUpgrades, {gName, gID, "upgrade"}) --]]--
camp.installComsFor(gID, gName)
end
end
@ -199,40 +174,22 @@ function camp.doTFunds(args)
if theZone.repairable and theZone.upgradable then
msg = msg .. "" .. theZone.repairCost .. "" .. theZone.upgradeCost .. ")"
if camp.zoneNeedsRepairs(theZone, coa) then
msg = msg .. " requests repairs and"
else
msg = msg .. " is running and"
end
if camp.zoneNeedsRepairs(theZone, coa) then msg = msg .. " requests repairs and" else msg = msg .. " is running and" end
if camp.zoneNeedsUpgrades(theZone, coa) then
msg = msg .. " can be upgraded"
else
msg = msg .. " is fully upgraded"
end
if camp.zoneNeedsUpgrades(theZone, coa) then msg = msg .. " can be upgraded" else msg = msg .. " is fully upgraded" end
elseif theZone.repairable then
if camp.zoneNeedsRepairs(theZone, coa) then
msg = msg .. " needs repairs (§" .. theZone.repairCost .. ")"
else
msg = msg .. " is fully operational"
end
if camp.zoneNeedsRepairs(theZone, coa) then msg = msg .. " needs repairs (§" .. theZone.repairCost .. ")" else msg = msg .. " is fully operational" end
elseif theZone.upgradable then
if camp.zoneNeedsUpgrades(theZone, coa) then
msg = msg .. " can be upgraded (§" .. theZone.upgradeCost .. ")"
else
msg = msg .. " is fully upgraded"
end
if camp.zoneNeedsUpgrades(theZone, coa) then msg = msg .. " can be upgraded (§" .. theZone.upgradeCost .. ")" else msg = msg .. " is fully upgraded" end
else
-- can be neither repaired nor upgraded
msg = msg .. " is owned"
end
end
end
if income > 0 then
msg = msg .. "\n\nTotal Income: §" .. income
end
if income > 0 then msg = msg .. "\n\nTotal Income: §" .. income end
msg = msg .. "\n"
trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, camp.actionSound)
@ -262,13 +219,8 @@ function camp.doFunds(args)
return
end
if camp.zoneNeedsRepairs(theZone, coa) then
msg = msg .. "\nZone <" .. theZone.name .. "> needs repairs (§" .. theZone.repairCost .. " per repair)\n"
elseif theZone.repairable then
msg = msg .. "\nZone <" .. theZone.name .. "> has no outstanding repairs.\n"
else
-- say nothing
end
if camp.zoneNeedsRepairs(theZone, coa) then msg = msg .. "\nZone <" .. theZone.name .. "> needs repairs (§" .. theZone.repairCost .. " per repair)\n"
elseif theZone.repairable then msg = msg .. "\nZone <" .. theZone.name .. "> has no outstanding repairs.\n" end
if camp.zoneNeedsUpgrades(theZone, coa) then
msg = msg .. "\nZone <" .. theZone.name .. "> can be upgraded (§" .. theZone.upgradeCost .. " per upgrade)\n"
elseif theZone.upgradable then

View File

@ -1,5 +1,5 @@
cfxZones = {}
cfxZones.version = "4.5.1"
cfxZones.version = "4.5.2"
-- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable
@ -40,7 +40,8 @@ cfxZones.version = "4.5.1"
- rnd in bool can have = xxx param for percentage
- getSmokeColorNumberFromZoneProperty()
-4.5.1 - moved processSimpleZoneDynamics to common
-4.5.2 - NEW getAllZoneProperties()
- guard agains DCS radius stored as sting (WTF, ED?)
--]]--
--
@ -170,13 +171,13 @@ function cfxZones.readFromDCS(clearfirst)
if zoneType == 0 then
-- circular zone
newZone.isCircle = true
newZone.radius = dcsZone.radius
newZone.radius = tonumber(dcsZone.radius)
newZone.maxRadius = newZone.radius -- same for circular
elseif zoneType == 2 then
-- polyZone
newZone.isPoly = true
newZone.radius = dcsZone.radius -- radius is still written in DCS, may change later. The radius has no meaning and is the last radius written before zone changed to poly.
newZone.radius = tonumber(dcsZone.radius) -- radius is still written in DCS, may change later. The radius has no meaning and is the last radius written before zone changed to poly.
-- note that newZone.point is only inside the tone for
-- convex polys, and DML only correctly works with convex polys
-- now transfer all point in the poly
@ -460,24 +461,6 @@ function cfxZones.createRandomPointInPopulatedZone(theZone, radius, maxTries)
return p, dx, dz
end
--[[--
function dmlZone:createRandomPointInPopulatedZone(radius, maxTries)
if not maxTries then maxTries = 20 end
local cnt = 0
local p, dx, dz
p, dx, dz = self:createRandomPointInZone() -- p is x, 0, z
repeat
local hits = cfxZones.objectsInRange(p, radius)
if hits < 1 then return p, dx, dz end
-- move to the right by radius
p.z = p.z + radius
dz = dz + radius
cnt = cnt + 1
trigger.action.outText("failed try " .. cnt, 30)
until cnt > maxTries
return p, dx, dz
end
--]]--
function cfxZones.objectHandler(theObject, theCollector) -- for world.search
table.insert(theCollector, theObject)
return true
@ -1635,7 +1618,6 @@ function cfxZones.doPollFlag(theFlag, method, theZone) -- no OOP equivalent
end
function cfxZones.pollFlag(theFlag, method, theZone)
--trigger.action.outText("enter pollflag for flag <" .. theFlag .. "> of zone <" .. theZone.name .. ">", 30)
local allFlags = {}
if dcsCommon.containsString(theFlag, ",") then
if cfxZones.verbose then
@ -2157,6 +2139,7 @@ function cfxZones.getAllZoneProperties(theZone, caseInsensitive, numbersOnly) --
local theKey = "dummy"
if string.len(theProp.key) > 0 then theKey = theProp.key end
if caseInsensitive then theKey = theKey:upper() end
theKey = dcsCommon.trim(theKey)
local v = theProp.value
if numbersOnly then
v = tonumber(v)
@ -2171,6 +2154,29 @@ function dmlZone:getAllZoneProperties(caseInsensitive, numbersOnly)
return cfxZones.getAllZoneProperties(self, caseInsensitive, numbersOnly)
end
function cfxZones.getAllPropertyNames(theZone, caseInsensitive) -- return as array
if not caseInsensitive then caseInsensitive = false end
if not theZone then return {} end
local dcsProps = theZone.properties -- zone properties in dcs format
local props = {}
-- dcs has all properties as array with values .key and .value
-- so convert them into a dictionary
for i=1, #dcsProps do
local theProp = dcsProps[i]
local theKey = "dummy"
if string.len(theProp.key) > 0 then theKey = tostring(theProp.key) end
if caseInsensitive then theKey = theKey:upper() end
theKey = dcsCommon.trim(theKey)
table.insert(props, theKey)
end
return props
end
function dmlZone:getAllPropertyNames(caseInsensitive)
return cfxZones.getAllZoneProperties(self, caseInsensitive)
end
function cfxZones.extractPropertyFromDCS(theKey, theProperties)
-- trim
theKey = dcsCommon.trim(theKey)
@ -2815,26 +2821,6 @@ end
function dmlZone:getSmokeColorStringFromZoneProperty(theProperty, default) -- smoke as 'red', 'green', or 1..5
return cfxZones.getSmokeColorStringFromZoneProperty(self, theProperty, default)
--[[
if not default then default = "red" end
local s = self:getStringFromZoneProperty(theProperty, default)
s = s:lower()
s = dcsCommon.trim(s)
-- check numbers
if (s == "0") then return "green" end
if (s == "1") then return "red" end
if (s == "2") then return "white" end
if (s == "3") then return "orange" end
if (s == "4") then return "blue" end
if s == "green" or
s == "red" or
s == "white" or
s == "orange" or
s == "blue" then return s end
return default
--]]
end
function cfxZones.getSmokeColorNumberFromZoneProperty(theZone, theProperty, default) -- smoke as 'red', 'green', or 1..5
@ -2892,28 +2878,7 @@ end
function dmlZone:getFlareColorStringFromZoneProperty(theProperty, default) -- smoke as 'red', 'green', or 1..5
return cfxZones.getFlareColorStringFromZoneProperty(self, theProperty, default)
--[[--
if not default then default = "red" end
local s = self:getStringFromZoneProperty(theProperty, default)
s = s:lower()
s = dcsCommon.trim(s)
-- check numbers
if (s == "rnd") then return "random" end
if (s == "0") then return "green" end
if (s == "1") then return "red" end
if (s == "2") then return "white" end
if (s == "3") then return "yellow" end
if (s == "-1") then return "random" end
if s == "green" or
s == "red" or
s == "white" or
s == "yellow" or
s == "random" then
return s end
return default
--]]--
end
--
@ -2937,36 +2902,7 @@ end
function cfxZones.processSimpleZoneDynamics(inMsg, theZone, timeFormat, imperialUnits)
local p = theZone:getPoint()
return dcsCommon.processTimeLocWildCards(inMsg, p, timeFormat, imperialUnits)
--[[--
if not inMsg then return "<nil inMsg>" end
-- replace <t> with current mission time HMS
local absSecs = timer.getAbsTime()-- + env.mission.start_time
while absSecs > 86400 do
absSecs = absSecs - 86400 -- subtract out all days
end
if not timeFormat then timeFormat = "<:h>:<:m>:<:s>" end
local timeString = dcsCommon.processHMS(timeFormat, absSecs)
local outMsg = inMsg:gsub("<t>", timeString)
-- replace <lat> with lat of zone point and <lon> with lon of zone point
-- and <mgrs> with mgrs coords of zone point
local currPoint = cfxZones.getPoint(theZone)
local lat, lon = coord.LOtoLL(currPoint)
lat, lon = dcsCommon.latLon2Text(lat, lon)
local alt = land.getHeight({x = currPoint.x, y = currPoint.z})
if imperialUnits then
alt = math.floor(alt * 3.28084) -- feet
else
alt = math.floor(alt) -- meters
end
outMsg = outMsg:gsub("<lat>", lat)
outMsg = outMsg:gsub("<lon>", lon)
outMsg = outMsg:gsub("<ele>", alt)
local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
outMsg = outMsg:gsub("<mgrs>", mgrs)
return outMsg
--]]--
end
-- process <v: flag>, <rsp: flag> <rrnd>

409
modules/fireCtrl.lua Normal file
View File

@ -0,0 +1,409 @@
fireCtrl = {}
fireCtrl.version = "1.1.0"
fireCtrl.requiredLibs = {
"dcsCommon",
"cfxZones",
"airtank",
"inferno",
}
fireCtrl.heroes = {} -- dict by pname: score
fireCtrl.checkins = {} -- dict by pname: checked in if we persist
fireCtrl.roots = {}
--[[--
Version History
1.0.0 - Initial version (unreleased)
1.1.0 - Added attachTo:
- Added checkIn
- center name
- newFire sound
- bail out if not enough fires
- re-liting with sparks
- sparks attribute
- various sound attributes, default to action sound
- notifications attribute
- cleanup
- UI attribute
--]]--
function fireCtrl.checkinPlayer(pName, gName, uName, uType)
local theGroup = Group.getByName(gName)
if not theGroup then return end
gID = theGroup:getID()
local msg = ""
if not fireCtrl.checkins[pName] then
msg = "\nWelcome "
if fireCtrl.heroes[pName] then
msg = msg .. "back "
else
fireCtrl.heroes[pName] = 0
end
msg = msg .. "to " .. dcsCommon.getMapName() .. ", " .. pName .. ", your " .. uType .. " is ready.\nGood luck and godspeed!\n"
fireCtrl.checkins[pName] = timer.getTime()
else
msg = "\n" .. pName .. ", your " .. uType .. " is ready.\n"
end
if fireCtrl.checkIn then
trigger.action.outTextForGroup(gID, msg, 30, true)
trigger.action.outSoundForGroup(gID, fireCtrl.actionSound)
end
end
function fireCtrl: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 fireCtrl.verbose then
trigger.action.outText("fireCtrl: 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
fireCtrl.installMenusForUnit(theUnit)
if fireCtrl.verbose then
trigger.action.outText("+++fCtl: new player airtank <" .. uName .. "> type <" .. uType .. "> for <" .. pName .. ">", 30)
end
fireCtrl.checkinPlayer(pName, gName, uName, uType)
return
end
end
function fireCtrl.installMenusForUnit(theUnit) -- assumes all unit types are weeded out
if not fireCtrl.UI then return end
-- 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 = fireCtrl.roots[gName]
if pRoot then
missionCommands.removeItemForGroup(gID, pRoot)
pRoot = nil
end
-- handle main menu
local mainMenu = nil
if fireCtrl.mainMenu then
mainMenu = radioMenu.getMainMenuFor(fireCtrl.mainMenu)
end
-- now add fireCtrl menu
pRoot = missionCommands.addSubMenuForGroup(gID, fireCtrl.menuName, mainMenu)
fireCtrl.roots[gName] = pRoot -- save for later
local args = {gName, uName, gID, uType, pName}
-- menus:
-- report - all current open fires
-- action - scoreboard
-- arm release -- get ready to drop. auto-release when alt is below 30m
local m1 = missionCommands.addCommandForGroup(gID , "Fire Report" , pRoot, fireCtrl.redirectStatus, args)
local mx2 = missionCommands.addCommandForGroup(gID , "Action Report" , pRoot, fireCtrl.redirectAction, args)
end
--
-- comms
--
function fireCtrl.redirectStatus (args)
timer.scheduleFunction(fireCtrl.doStatus, args, timer.getTime() + 0.1)
end
function fireCtrl.redirectAction (args)
timer.scheduleFunction(fireCtrl.doAction, args, timer.getTime() + 0.1)
end
function fireCtrl.doStatus(args)
-- trigger.action.outText("status call", 30)
local gName = args[1]
local uName = args[2]
local gID = args[3]
local uType = args[4]
local pName = args[5]
local theUnit = Unit.getByName(uName)
if not theUnit then return end
local up = theUnit:getPoint()
up.y = 0
local msg = "\nFire emergencies requesting aerial support:\n"
local count = 0
for name, theZone in pairs(inferno.zones) do
if theZone.burning then
local p = theZone:getPoint()
local level = theZone.maxSpread
if level > 1000 or level < 0 then level = 1 end
count = count + 1
if count < 10 then msg = msg .. " " end
msg = msg .. count .. ". Type " .. level .. " "
if twn and towns then
local name, data, dist = twn.closestTownTo(p)
local mdist= dist * 0.539957
dist = math.floor(dist/100) / 10
mdist = math.floor(mdist/100) / 10
local bear = dcsCommon.compassPositionOfARelativeToB(p, data.p)
msg = msg .. dist .. "km/" .. mdist .."nm " .. bear .. " of " .. name
success = true
else
msg = msg .. "***TWN ERR***"
end
local b = dcsCommon.bearingInDegreesFromAtoB(up, p)
local d = dcsCommon.distFlat(up, p) * 0.000539957
d = math.floor(d * 10) / 10
msg = msg .. ", bearing " .. b .. ", " .. d .. "nm\n"
end
end
if count < 1 then
msg = msg .. "\n All is well, blue skies, no fires.\n"
end
msg = msg .. "\n"
trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, fireCtrl.listSound)
end
function fireCtrl.doAction(args)
-- sort heroes by their points, and rank them
local h = {}
for name, num in pairs(fireCtrl.heroes) do
local ele = {}
ele.name = name
ele.num = num
ele.rank = fireCtrl.num2rank(num)
table.insert(h, ele)
end
-- table.sort(table, function (e1, e2) return e1.atr < e2.atr end )
table.sort(h, function (e1, e2) return e1.num > e2.num end)
-- now create the top twenty
local msg = "\nThe Book Of Embers recognizes:\n"
local count = 0
for idx, ele in pairs(h) do
count = count + 1
if count < 21 then
if count < 10 then msg = msg .. " " end
msg = msg .. count .. ". " .. ele.rank .. " " .. ele.name .. " (" .. ele.num .. ")\n"
end
end
if count < 1 then
msg = msg .. "\n *** The Book Is Empty ***\n"
end
trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, fireCtrl.scoreSound)
end
function fireCtrl.num2rank(num)
if num < 10 then return "Probie" end
if num < 25 then return "Firefighter" end
if num < 50 then return "Elltee" end
if num < 100 then return "Chief" end
if num < 200 then return "Local Hero" end
if num < 1000 then return "Fire Hero of " .. dcsCommon.getMapName() .. "(" .. math.floor (num/200) .. ")" end
return "Avatar of Hephaestus"
end
--
-- update
--
function fireCtrl.pickUnlit()
local linearTable = dcsCommon.enumerateTable(inferno.zones)
local theZone
local tries = 0
repeat
theZone = dcsCommon.pickRandom(linearTable)
tries = tries + 1
until (not theZone.burning) or (tries > 100)
if tries > 100 then
trigger.action.outText("fireCtrl: no unlit zones available", 30)
return nil
end
return theZone
end
function fireCtrl.startFire()
local theZone = fireCtrl.pickUnlit()
if not theZone then return end
inferno.ignite(theZone)
if fireCtrl.verbose or theZone.verbose then
trigger.action.outText("+++fCtl: started fire in <" .. theZone.name .. ">", 30)
end
end
function fireCtrl.update()
timer.scheduleFunction(fireCtrl.update, {}, timer.getTime() + 1/fireCtrl.ups)
-- check the numbers of fires burning
local f = 0
local cells = 0
for idx, theZone in pairs(inferno.zones) do
if theZone.burning then f = f + 1 end
if theZone.maxSpread > 1000 then
cells = cells + #theZone.goodCells
else
cells = cells + theZone.maxSpread + 1
end
end
if f < fireCtrl.minFires then -- start 3!!!! fires
fireCtrl.startFire() -- start at least one fire
if fireCtrl.sparks > 1 then
for i=1, fireCtrl.sparks-1 do
if math.random(100) > 50 then fireCtrl.startFire() end
end
end
if fireCtrl.notifications then
trigger.action.outText("\nAll stations, " .. fireCtrl.centerName .. " One. Local fire dispatch have reported a large fire cell and are requesting aerial support. Please check in with " .. fireCtrl.centerName .. " for details.\n", 30)
trigger.action.outSound(fireCtrl.newFire)
end
end
end
--
-- callbacks
--
function fireCtrl.extCB(theZone)
local p = theZone:getPoint()
local name = "<" .. theZone.name .. ">"
if twn and towns then
name = twn.closestTownTo(p)
end
local msg = "\nInferno at " .. name .. " has been extinguished. Our thanks go to\n"
local heroes = theZone.heroes
local hasOne = false
if heroes then
local awarded = theZone.maxSpread
if awarded < 1 or awarded > 9999 then
awarded = 1
end
for name, count in pairs(heroes) do
hasOne = true
msg = msg .. " - " .. name .. " (" .. count .. " successful drops)\n"
-- award everyone points based on maxSprad
if not fireCtrl.heroes[name] then
fireCtrl.heroes[name] = awarded
else
fireCtrl.heroes[name] = fireCtrl.heroes[name] + awarded
end
end
end
if not hasOne then msg = msg .. "\n(no one)\n" end
trigger.action.outText(msg, 30)
trigger.action.outSound(fireCtrl.actionSound)
end
--
-- load / save (game data)
--
function fireCtrl.saveData()
local theData = {}
-- save current heroes. simple clone
local theHeroes = dcsCommon.clone(fireCtrl.heroes)
theData.theHeroes = theHeroes
return theData, fireCtrl.sharedData -- second val only if shared
end
function fireCtrl.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("fireCtrl", fireCtrl.sharedData)
if not theData then
if fireCtrl.verbose then
trigger.action.outText("+++fireCtrl: no save date received, skipping.", 30)
end
return
end
local theHeroes = theData.theHeroes
fireCtrl.heroes = theHeroes
end
--
-- start and config
--
function fireCtrl.readConfigZone()
fireCtrl.name = "fireCtrlConfig" -- make compatible with dml zones
local theZone = cfxZones.getZoneByName("fireCtrlConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("fireCtrlConfig")
end
fireCtrl.verbose = theZone.verbose
fireCtrl.ups = theZone:getNumberFromZoneProperty("ups", 1/20)
fireCtrl.actionSound = theZone:getStringFromZoneProperty("actionSound", "none")
fireCtrl.listSound = theZone:getStringFromZoneProperty("listSound", fireCtrl.actionSound)
fireCtrl.scoreSound = theZone:getStringFromZoneProperty("scoreSound", fireCtrl.listSound)
fireCtrl.centerName = theZone:getStringFromZoneProperty("centerName", "Highperch")
fireCtrl.newFire = theZone:getStringFromZoneProperty("newFire", "firefight new fire alice.ogg")
fireCtrl.menuName = theZone:getStringFromZoneProperty("menuName", "Contact " .. fireCtrl.centerName)
fireCtrl.minFires = theZone:getNumberFromZoneProperty("minFires", 3)
fireCtrl.sparks = theZone:getNumberFromZoneProperty("sparks", 3)
fireCtrl.notifications = theZone:getBoolFromZoneProperty("notifications", true)
fireCtrl.UI = theZone:getBoolFromZoneProperty("UI", true)
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
fireCtrl.mainMenu = mainMenu
else
trigger.action.outText("+++fireCtrl: cannot find super menu <" .. attachTo .. ">", 30)
end
else
trigger.action.outText("+++fireCtrl: REQUIRES radioMenu to run before inferno. 'AttachTo:' ignored.", 30)
end
end
fireCtrl.checkIn = theZone:getBoolFromZoneProperty("checkIn", true)
-- shared data persistence interface
if theZone:hasProperty("sharedData") then
fireCtrl.sharedData = theZone:getStringFromZoneProperty("sharedData", "cfxNameMissing")
end
end
function fireCtrl.start()
if not dcsCommon.libCheck then
trigger.action.outText("cfx fireCtrl requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx fireCtrl", fireCtrl.requiredLibs) then
return false
end
-- read config
fireCtrl.readConfigZone()
if dcsCommon.getSizeOfTable(inferno.zones) < fireCtrl.minFires then
trigger.action.outText("+++fCtl: too few inferno zones (" .. fireCtrl.minFires .. " required). fireCtrl aborted.", 30)
return false
end
-- connect event handler
world.addEventHandler(fireCtrl)
-- install inferno extinguished CB
inferno.installExtinguishedCB(fireCtrl.extCB)
-- now load all save data
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = fireCtrl.saveData
persistence.registerModule("fireCtrl", callbacks)
-- now load my data
fireCtrl.loadData()
end
-- start update
timer.scheduleFunction(fireCtrl.update, {}, timer.getTime() + 3)
-- say Hi!
trigger.action.outText("cf/x fireCtrl v" .. fireCtrl.version .. " started.", 30)
return true
end
if not fireCtrl.start() then
trigger.action.outText("fireCtrl failed to start up", 30)
fireCtrl = nil
end

View File

@ -1,5 +1,5 @@
guardianAngel = {}
guardianAngel.version = "4.0.0"
guardianAngel.version = "4.0.1"
guardianAngel.ups = 10 -- hard-coded!! missile track
guardianAngel.name = "Guardian Angel" -- just in case someone accesses .name
guardianAngel.requiredLibs = {
@ -23,6 +23,7 @@ guardianAngel.requiredLibs = {
- once per second sanctuary calc
- expanded getWatchedUnitByName to include inSanctuary
- sanctuary zones use coalition
4.0.1 - code hardening (DCS)
--]]--
guardianAngel.active = true -- can be turned on / off
@ -347,6 +348,7 @@ function guardianAngel:onEvent(event)
-- get cat, this returns 1 for unit (as it should, so we can get
-- group, or if it's really a unit, which returns 0 for aircraft
if not theUnit.getCategory then return end
if not theUnit.getGroup then return end -- ED silly stuff
local theGroup = theUnit:getGroup()
if not theGroup then return end
local gCat = theGroup:getCategory()

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {}
cfxHeloTroops.version = "4.2.1"
cfxHeloTroops.version = "4.2.2"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@ -21,6 +21,7 @@ cfxHeloTroops.requestRange = 500 -- meters
- support for drivable
4.2.1 - increased verbosity
- also supports 'pickupRang" for reverse-compatibility with manual typo.
4.2.2 - support for attachTo:
--]]--
cfxHeloTroops.minTime = 3 -- seconds beween tandings
@ -299,7 +300,11 @@ function cfxHeloTroops.setCommsMenu(theUnit)
conf.unit = theUnit -- link back
-- if we don't have an F-10 menu, create one
if not (conf.myMainMenu) then
conf.myMainMenu = missionCommands.addSubMenuForGroup(id, 'Airlift Troops')
local mainMenu = nil
if cfxHeloTroops.mainMenu then
mainMenu = radioMenu.getMainMenuFor(cfxHeloTroops.mainMenu)
end
conf.myMainMenu = missionCommands.addSubMenuForGroup(id, 'Airlift Troops', mainMenu)
end
-- clear out existing commands, add new
cfxHeloTroops.clearCommsSubmenus(conf)
@ -1021,6 +1026,20 @@ function cfxHeloTroops.readConfigZone()
tc = dcsCommon.splitString(tc, ",")
cfxHeloTroops.troopCarriers = dcsCommon.trimArray(tc)
end
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
cfxHeloTroops.mainMenu = mainMenu
else
trigger.action.outText("+++heloT: cannot find super menu <" .. attachTo .. ">", 30)
end
else
trigger.action.outText("+++heloT: REQUIRES radioMenu to run before cfxHeloTroops. 'AttachTo:' ignored.", 30)
end
end
end
--

View File

@ -1,9 +1,14 @@
inferno = {}
inferno.version = "1.0.0"
inferno.version = "1.0.1"
inferno.requiredLibs = {
"dcsCommon",
"cfxZones",
}
--[[-- Version History
1.0.0 - Initial version
1.0.1 - cleanup
--]]--
--
-- Inferno models fires inside inferno zones. Fires can spread and
-- be extinguished by aircraft from the airtank module
@ -91,19 +96,9 @@ function inferno.buildGrid(theZone)
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
-- we do 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
@ -120,15 +115,6 @@ function inferno.buildGrid(theZone)
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
@ -180,7 +166,7 @@ function inferno.readZone(theZone)
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.fullBlaze = theZone:getBoolFromZoneProperty("fullBlaze", false )
theZone.canSpread = theZone:getBoolFromZoneProperty("canSpread", true)
theZone.maxSpread = theZone:getNumberFromZoneProperty("maxSpread", 999999)
theZone.impactSmoke = theZone:getBoolFromZoneProperty("impactSmoke", inferno.impactSmoke)
@ -201,7 +187,6 @@ function inferno.readZone(theZone)
end
end
--
-- API for water droppers
--
@ -234,7 +219,6 @@ function inferno.waterInZone(theZone, p, amount)
end
local ele = row[zc]
-- local ele = theZone.grid[xc][zc]
if not ele then
trigger.action.outText("Inferno: no ele for <" .. theZone.name .. ">: x<" .. x .. ">z<" .. z .. ">", 30)
trigger.action.outText("with xc = " .. xc .. ", numX 0 " .. theZone.numX .. ", zc = " .. zc .. ", numZ=" .. theZone.numZ, 30)
@ -264,8 +248,6 @@ function inferno.waterInZone(theZone, p, amount)
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
@ -332,16 +314,13 @@ function inferno.waterDropped(p, amount, data) -- if returns non-nil, has hit a
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
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)
@ -383,16 +362,11 @@ function inferno.ignite(theZone)
end
function inferno.startFire(theZone)
if theZone.burning then
return
end
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
@ -660,24 +634,7 @@ function inferno.readConfigZone()
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
--]]--
if theZone:hasProperty("fire#") then
inferno.fireNum = theZone:getStringFromZoneProperty("fire#", "none")
end

View File

@ -175,7 +175,7 @@ function persistence.saveTable(theTable, fileName, shared, append)
if not shared then shared = false end
net.log("persistence: before json conversion")
local theString = net.lua2json(theTable)
local theString = net.lua2json(theTable) -- WARNING! does not handle arrays with [0]!
net.log("persistence: json conversion complete")
if not theString then theString = "" end

View File

@ -1,5 +1,5 @@
cfxPlayerScore = {}
cfxPlayerScore.version = "5.1.0"
cfxPlayerScore.version = "5.2.1"
cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers
cfxPlayerScore.firstSave = true -- to force overwrite
--[[-- VERSION HISTORY
@ -15,7 +15,10 @@ cfxPlayerScore.firstSave = true -- to force overwrite
- improved wildcard support
- event 20 for CA also supported
- "threading the needle" -- support for hit event and unit traceback
5.2.0 - PlayerScoreTable supports wildcards, e.g. "Batumi*"
5.2.1 - Event 20 (CA join) corrected typo
- wiping score on enter and birth
- more robust initscore
TODO: Kill event no longer invoked for map objetcs, attribute
to faction now, reverse invocation direction with PlayerScore
TODO: better wildcard support for kill events
@ -42,7 +45,8 @@ cfxPlayerScore.killZones = {} -- when set, kills only count here
-- typeScore: dictionary sorted by typeString for score
-- extend to add more types. It is used by unitType2score to
-- determine the base unit score
cfxPlayerScore.typeScore = {} -- ALL UPPERCASE NOW!!!
cfxPlayerScore.typeScore = {} -- ALL UPPERCASE
cfxPlayerScore.wildTypes = {} -- ALL UPPERCASE
cfxPlayerScore.lastPlayerLanding = {} -- timestamp, by player name
cfxPlayerScore.delayBetweenLandings = 30 -- seconds to count as separate landings, also set during take-off to prevent janky t/o to count.
cfxPlayerScore.aircraft = 50
@ -191,8 +195,18 @@ function cfxPlayerScore.cat2BaseScore(inCat)
trigger.action.outText("+++scr c2bs: unknown category for lookup: <" .. inCat .. ">, returning 1", 30)
return 1
end
function cfxPlayerScore.wildMatch(inName)
-- if inName starts the same as any wildcard, return score
for wName, wScore in pairs (cfxPlayerScore.wildTypes) do
if dcsCommon.stringStartsWith(inName, wName, true) then
if cfxPlayerScore.verbose then trigger.action.outText("+++PScr: wildmatch <" .. inName .. "> to <" .. wName .. ">, score <" .. wScore .. ">", 30) end
return wScore
end
end
return nil
end
function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group
function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group, go by type
if not inVictim then return 0 end
if not killSide then killSide = -1 end
local inName
@ -224,6 +238,7 @@ function cfxPlayerScore.object2score(inVictim, killSide) -- does not have group
-- try the type desc
local theType = inVictim:getTypeName()
if theType then objectScore = cfxPlayerScore.typeScore[theType:upper()] end
if not objectScore then objectScore = cfxPlayerScore.wildMatch(theType) end
end
if type(objectScore) == "string" then objectScore = tonumber(objectScore)end
if objectScore then return objectScore end
@ -277,16 +292,25 @@ function cfxPlayerScore.unit2score(inUnit)
-- simply extend by adding items to the typescore table.concat
-- we first try by unit name. This allows individual
-- named hi-value targets to have individual scores
local uScore
if vicName then uScore = cfxPlayerScore.typeScore[vicName:upper()] end
local uScore = nil
if vicName then
uScore = cfxPlayerScore.typeScore[vicName:upper()]
if not uScore then uScore = cfxPlayerScore.wildMatch(vicName) end
end
-- see if all members of group score
if (not uScore) then -- and vicGroup then
local grpName = cfxMX.spawnedUnitGroupNameByName[vicName]--vicGroup:getName()
if grpName then uScore = cfxPlayerScore.typeScore[grpName:upper()] end
if grpName then
uScore = cfxPlayerScore.typeScore[grpName:upper()]
if not uScore then uScore = cfxPlayerScore.wildMatch(grpName) end
end
end
if not uScore then
-- WE NOW TRY TO ACCESS BY VICTIM'S TYPE STRING
if vicType then uScore = cfxPlayerScore.typeScore[vicType:upper()] end
if vicType then
uScore = cfxPlayerScore.typeScore[vicType:upper()]
if not uScore then uScore = cfxPlayerScore.wildMatch(vicType) end
end
end
if type(uScore) == "string" then uScore = tonumber(uScore) end
if not uScore then uScore = 0 end
@ -299,7 +323,8 @@ end
function cfxPlayerScore.getPlayerScore(playerName)
local thePlayerScore = cfxPlayerScore.playerScore[playerName]
if not thePlayerScore then
thePlayerScore = {}
thePlayerScore = cfxPlayerScore.createNewPlayerScore(playerName)
--[[-- thePlayerScore = {}
thePlayerScore.name = playerName
thePlayerScore.score = 0 -- score
thePlayerScore.scoreaccu = 0 -- for deferred
@ -308,11 +333,43 @@ function cfxPlayerScore.getPlayerScore(playerName)
thePlayerScore.totalKills = 0 -- number of kills total
thePlayerScore.featTypes = {} -- dict <featname> <number> of other things player did
thePlayerScore.featQueue = {} -- when using deferred
thePlayerScore.totalFeats = 0
thePlayerScore.totalFeats = 0
--]]--
end
return thePlayerScore
end
function cfxPlayerScore.createNewPlayerScore(playerName)
local thePlayerScore = {}
thePlayerScore.name = playerName
thePlayerScore.score = 0 -- score
thePlayerScore.scoreaccu = 0 -- for deferred
thePlayerScore.killTypes = {} -- the type strings killed, dict <typename> <numkilla>
thePlayerScore.killQueue = {} -- when using deferred
thePlayerScore.totalKills = 0 -- number of kills total
thePlayerScore.featTypes = {} -- dict <featname> <number> of other things player did
thePlayerScore.featQueue = {} -- when using deferred
thePlayerScore.totalFeats = 0
return thePlayerScore
end
function cfxPlayerScore.wipeScore(playerName)
if cfxPlayerScore.verbose then trigger.action.outText("+++pScr: enter wipe score for player <" .. playerName .. ">", 30) end
if not cfxPlayerScore.playerScore[playerName] then return end
thePlayerScore = cfxPlayerScore.getPlayerScore(playerName)
local loss = false
if thePlayerScore.scoreaccu > 0 then loss = true end
if dcsCommon.getSizeOfTable(thePlayerScore.featQueue) > 0 then loss = true end
if dcsCommon.getSizeOfTable(thePlayerScore.killQueue) > 0 then loss = true end
thePlayerScore.scoreaccu = 0
thePlayerScore.killQueue = {}
thePlayerScore.featQueue = {}
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore) -- write back
if loss then
trigger.action.outText("Player " .. playerName .. " lost score.", 30) -- everyone sees this
end
end
function cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
cfxPlayerScore.playerScore[playerName] = thePlayerScore
end
@ -511,6 +568,7 @@ function cfxPlayerScore.isNamedUnit(theUnit)
if not theName then return false end
end
if cfxPlayerScore.typeScore[theName:upper()] then return true end
if cfxPlayerScore.wildMatch(theName) then return true end
return false
end
@ -806,9 +864,7 @@ function cfxPlayerScore.scheduledAward(args)
local playerName = args[1]
local unitName = args[2]
local theUnit = Unit.getByName(unitName)
if not theUnit or
not Unit.isExist(theUnit)
then
if not theUnit or (not Unit.isExist(theUnit)) then
-- unit is gone
trigger.action.outText("Player <" .. playerName .. "> lost score.", 30)
return
@ -890,6 +946,7 @@ function cfxPlayerScore.scheduledAward(args)
-- output score
desc = desc .. "\n"
if hasAward then trigger.action.outTextForCoalition(coa, desc, 30) end
-- do NOT kill the entire record, or accu kills and feats are gone too
end
function cfxPlayerScore.handlePlayerDeath(theEvent)
@ -903,6 +960,9 @@ function cfxPlayerScore.handlePlayerDeath(theEvent)
local pName = cfxPlayerScore.unit2player[uName]
if pName then
-- this was a player name with link still live.
-- cancel all scores accumulated
cfxPlayerScore.wipeScore(pName)
if cfxPlayerScore.planeLoss ~= 0 then
-- plane loss has IMMEDIATE consequences
cfxPlayerScore.updateScoreForPlayerImmediate(pName, cfxPlayerScore.planeLoss)
@ -943,10 +1003,10 @@ function cfxPlayerScore.isScoreEvent(theEvent)
if who then
name = "(inval initi)"
if who.getName then name = who:getName() end
if not name then -- WTF??? could be a weapon
name = "!nil getName!"
if not name or (#name < 1) then -- WTF??? could be a weapon
name = "!no getName!"
if who.getTypeName then name = who:getTypeName() end
if not name then name = "WTFer" end
if not name or (#name < 1) then name = "WTFer" end
end
end
@ -963,7 +1023,7 @@ function cfxPlayerScore.isScoreEvent(theEvent)
-- a hit event will save the last player to hit
-- so we can attribute with unit lost
if theEvent.id == 2 then -- hit processing
if cfxPlayerScore.verbose then trigger.action.outText("enter hit pre-processing", 30) end
-- if cfxPlayerScore.verbose then trigger.action.outText("enter hit pre-processing", 30) end
local who = theEvent.initiator
if not who.getPlayerName then return false end -- non-player originator
local pName = who:getPlayerName()
@ -981,7 +1041,10 @@ function cfxPlayerScore.isScoreEvent(theEvent)
-- check if this was FORMERLY a player plane
local theUnit = theEvent.initiator
if not theUnit.getName then return end -- fix for DCS update bug
if not theUnit.getName then
-- trigger.action.outText("+++pScr: no unit name, DCS err - abort.", 30)
return false
end -- fix for DCS update bug
local uName = theUnit:getName()
if cfxPlayerScore.unit2player[uName] then
-- this requires special IMMEDIATE handling when event is
@ -1017,6 +1080,7 @@ function cfxPlayerScore.isScoreEvent(theEvent)
-- credit unit and current person driving that unit
-- there is a small percentage that this is wrong
-- player, but that's an edge case
-- if no player inhabits killing unit any more, no score attributed
cfxPlayerScore.processKill(theUnit, who)
end
return false
@ -1054,8 +1118,11 @@ function cfxPlayerScore.isScoreEvent(theEvent)
-- enter unit / birth event for players initializes score if
-- not existed, and nils the queue. 20 creates compat with CA
if theEvent == 20 or -- enter unit
if theEvent.id == 20 or -- enter unit
theEvent.id == 15 then -- player birth
-- since we get into a new plane, erase all pending score
local pName = theUnit:getPlayerName()
cfxPlayerScore.wipeScore(pName)
-- link player with their unit
cfxPlayerScore.linkUnitWithPlayer(theUnit)
cfxPlayerScore.unitSpawnTime[uName] = timer.getTime() -- to detect 'early landing'
@ -1110,6 +1177,7 @@ end
function cfxPlayerScore.handleScoreEvent(theEvent)
cfxPlayerScore.currentEventUnit = theEvent.initiator
if cfxPlayerScore.verbose then trigger.action.outText("Set currentEventUnit to <" .. theEvent.initiator:getName() .. ">", 30) end
if theEvent.id == 28 then
-- kill from player detected.
cfxPlayerScore.killDetected(theEvent)
@ -1361,12 +1429,30 @@ function cfxPlayerScore.start()
if not dcsCommon.libCheck("cfx Player Score", cfxPlayerScore.requiredLibs)
then return false end
-- only read verbose flag
-- now read my config zone
local theZone = cfxZones.getZoneByName("playerScoreConfig")
if theZone then cfxPlayerScore.verbose = theZone.verbose end
-- read my score table
-- identify and process a score table zones
local theZone = cfxZones.getZoneByName("playerScoreTable")
if theZone then
if cfxPlayerScore.verbose then trigger.action.outText("+++pScr: has playerSocreTable", 30) end
-- read all into my types registry, replacing whatever is there
cfxPlayerScore.typeScore = theZone:getAllZoneProperties(true) -- true = get all properties in UPPER case
-- CASE INSENSITIVE!!!!!
-- now process all wildcarded types and add them to my wildTypes
cfxPlayerScore.wildTypes = {}
for theType, theScore in pairs(cfxPlayerScore.typeScore) do
if dcsCommon.stringEndsWith(theType, "*") then
local wcType = dcsCommon.removeEnding(theType, "*")
cfxPlayerScore.wildTypes[wcType] = theScore
if cfxPlayerScore.verbose then
trigger.action.outText("+++PScr: wildcard type/name <" .. wcType .. "> with score <" .. theScore .. "> registered", 30)
end
end
end
end
-- read score tiggers and values
@ -1398,10 +1484,11 @@ function cfxPlayerScore.start()
cfxPlayerScore.blueTriggerFlags[tName] = trigger.misc.getUserFlag(tName)
end
end
-- now read my config zone
-- now read my config zone. reading late
local theZone = cfxZones.getZoneByName("playerScoreConfig")
if not theZone then theZone = cfxZones.createSimpleZone("playerScoreConfig") end
cfxPlayerScore.readConfigZone(theZone)
-- read all scoreSafe zones
local safeZones = cfxZones.zonesWithProperty("scoreSafe")
for k, aZone in pairs(safeZones) do

View File

@ -1,5 +1,5 @@
pulseFlags = {}
pulseFlags.version = "2.0.2"
pulseFlags.version = "2.0.3"
pulseFlags.verbose = false
pulseFlags.requiredLibs = {
"dcsCommon", -- always
@ -15,6 +15,7 @@ pulseFlags.requiredLibs = {
using method on all outputs
- 2.0.1 activateZoneFlag now works correctly
- 2.0.2 fixed scheduledTime bug while persisting
- 2.0.3 now setting -1 (infinite) as pulses works correctly
--]]--
pulseFlags.pulses = {}
@ -56,14 +57,20 @@ function pulseFlags.createPulseWithZone(theZone)
theZone.pulses = -1 -- set to infinite
if theZone:hasProperty("pulses") then
local minP, maxP = theZone:getPositiveRangeFromZoneProperty("pulses", 1)
if minP == maxP then theZone.pulses = minP
local tt = theZone:getStringFromZoneProperty("pulses", -1)
tn = tonumber(tt) -- returns nil for a range
if tn then
if tn < 1 then theZone.pulses = -1 else theZone.pulses = tn end
else
theZone.pulses = cfxZones.randomInRange(minP, maxP)
local minP, maxP = theZone:getPositiveRangeFromZoneProperty("pulses", 1)
if minP == maxP then theZone.pulses = minP
else
theZone.pulses = cfxZones.randomInRange(minP, maxP)
end
end
end
if pulseFlags.verbose or theZone.verbose then
trigger.action.outText("+++pulF: zone <" .. theZone.name .. "> set to <" .. theZone.pulses .. "> pulses", 30)
if theZone.verbose or pulseFlag.verbose then
trigger.action.outText("+++pulF: set pulses in <" .. theZone.name .. "> to <" .. theZone.pulses .. ">", 30)
end
theZone.pulsesLeft = 0 -- will start new cycle

View File

@ -164,7 +164,8 @@ function cfxSmokeZone.start()
timer.scheduleFunction(cfxSmokeZone.checkFlags, {}, timer.getTime() + 1)
-- say hi
trigger.action.outText("cfx Smoke Zones v" .. cfxSmokeZone.version .. " started.", 30)
trigger.action.outText("cfx smoke zones v" .. cfxSmokeZone.version .. " started.", 30)
return true
end

View File

@ -232,7 +232,7 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide)
-- only return spawners with this side
-- note: this will NOT work with neutral players
hasMatch = false
reasons = reasons .. "[rawOwner] "
reasons = reasons .. "[rawOwner is <" .. aSpawner.rawOwner .. ">, need <" .. aSide .. ">] "
end
if not aSpawner.requestable then
@ -258,21 +258,14 @@ end
--
function cfxSpawnZones.verifySpawnOwnership(spawner)
-- returns false ONLY if masterSpawn disagrees
-- i.e. when masterOwner is not the same as spawner.rawOwner
if not spawner.masterZoneName then
--trigger.action.outText("spawner " .. spawner.name .. " no master, go!", 30)
if spawner.zone.verbose then trigger.action.outText("spawner " .. spawner.name .. " no masterOwner, go!", 30) end
return true
end -- no master owner, all ok
local myCoalition = spawner.rawOwner
-- local masterZone = cfxZones.getZoneByName(spawner.masterZoneName)
-- if not masterZone then
-- trigger.action.outText("spawner " .. spawner.name .. " DID NOT FIND MASTER ZONE <" .. spawner.masterZoneName .. ">", 30)
-- return false
-- end
local masterZone = spawner.masterZone
-- if not masterZone.owner then
--trigger.action.outText("spawner " .. spawner.name .. " - masterZone " .. masterZone.name .. " HAS NO OWNER????", 30)
-- return true
-- end
if spawner.zone.verbose then trigger.action.outText("spawner " .. spawner.name .. " has masterOwner <" .. masterZone.name .. ">", 30) end
if (myCoalition ~= masterZone:getCoalition()) then
-- can't spawn, surrounding area owned by enemy

Binary file not shown.