mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
1.1.6
Bug fixes autoCsar Beta williePete Beta
This commit is contained in:
parent
46712cba20
commit
4c0b0e8bed
Binary file not shown.
Binary file not shown.
@ -1,5 +1,5 @@
|
||||
LZ = {}
|
||||
LZ.version = "1.0.0"
|
||||
LZ.version = "1.1.0"
|
||||
LZ.verbose = false
|
||||
LZ.ups = 1
|
||||
LZ.requiredLibs = {
|
||||
@ -14,6 +14,7 @@ LZ.LZs = {}
|
||||
|
||||
Version History
|
||||
1.0.0 - initial version
|
||||
1.1.0 - persistence
|
||||
|
||||
--]]--
|
||||
|
||||
@ -131,13 +132,12 @@ end
|
||||
-- Misc Processing
|
||||
--
|
||||
function LZ.unitIsInterestingForZone(theUnit, theZone)
|
||||
--trigger.action.outText("enter isInterestingB4pause for <" .. theUnit:getName() .. ">", 40)
|
||||
|
||||
-- see if zone is interested in this unit.
|
||||
if theZone.isPaused then
|
||||
return false
|
||||
end
|
||||
-- trigger.action.outText("enter isinteresting for <" .. theUnit:getName() .. ">", 40)
|
||||
|
||||
if theZone.lzPlayerOnly then
|
||||
if not dcsCommon.isPlayerUnit(theUnit) then
|
||||
if theZone.verbose or LZ.verbose then
|
||||
@ -198,11 +198,7 @@ function LZ.unitIsInterestingForZone(theUnit, theZone)
|
||||
else
|
||||
-- we can return true since player and coa mismatch
|
||||
-- have already been filtered
|
||||
--[[-- -- neither type, unit, nor group
|
||||
local theGroup = theUnit:getGroup()
|
||||
local coa = theGroup:getCoalition()
|
||||
--
|
||||
--]]--
|
||||
|
||||
return true -- theZone.coalition == coa end
|
||||
end
|
||||
|
||||
@ -224,18 +220,10 @@ function LZ:onEvent(event)
|
||||
return
|
||||
end
|
||||
|
||||
--if LZ.verbose or true then
|
||||
-- trigger.action.outText("+++LZ: on event proccing", 30)
|
||||
--end
|
||||
|
||||
local theUnit = event.initiator
|
||||
if not Unit.isExist(theUnit) then return end
|
||||
local p = theUnit:getPoint()
|
||||
|
||||
--if LZ.verbose or true then
|
||||
-- trigger.action.outText("+++LZ: before iterating zones", 30)
|
||||
--end
|
||||
|
||||
for idx, aZone in pairs(LZ.LZs) do
|
||||
-- see if inside the zone
|
||||
local inZone, percent, dist = cfxZones.pointInZone(p, aZone)
|
||||
@ -258,7 +246,7 @@ function LZ:onEvent(event)
|
||||
end
|
||||
end -- if interesting
|
||||
else
|
||||
if LZ.verbose or true then
|
||||
if LZ.verbose or aZone.verbose then
|
||||
--trigger.action.outText("+++LZ: unit <" .. theUnit:getName() .. "> not in zone <" .. aZone.name .. ">", 30)
|
||||
end
|
||||
|
||||
@ -292,13 +280,58 @@ function LZ.update()
|
||||
|
||||
end
|
||||
|
||||
--
|
||||
-- LOAD / SAVE
|
||||
--
|
||||
function LZ.saveData()
|
||||
local theData = {}
|
||||
local allLZ = {}
|
||||
for idx, theLZ in pairs(LZ.LZs) do
|
||||
local theName = theLZ.name
|
||||
local LZData = {}
|
||||
LZData.isPaused = theLZ.isPaused
|
||||
|
||||
allLZ[theName] = LZData
|
||||
end
|
||||
theData.allLZ = allLZ
|
||||
return theData
|
||||
end
|
||||
|
||||
function LZ.loadData()
|
||||
if not persistence then return end
|
||||
local theData = persistence.getSavedDataForModule("LZ")
|
||||
if not theData then
|
||||
if LZ.verbose then
|
||||
trigger.action.outText("+++LZ persistence: no save data received, skipping.", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local allLZ = theData.allLZ
|
||||
if not allLZ then
|
||||
if LZ.verbose then
|
||||
trigger.action.outText("+++LZ persistence: no LZ data, skipping", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
for theName, theData in pairs(allLZ) do
|
||||
local theLZ = LZ.getLZByName(theName)
|
||||
if theLZ then
|
||||
theLZ.isPaused = theData.isPaused
|
||||
else
|
||||
trigger.action.outText("+++LZ: persistence: cannot synch LZ <" .. theName .. ">, skipping", 40)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Config & Start
|
||||
--
|
||||
function LZ.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("LZConfig")
|
||||
if not theZone then
|
||||
theZone = cfxZones.createSimpleZone(LZConfig)
|
||||
theZone = cfxZones.createSimpleZone("LZConfig")
|
||||
if LZ.verbose then
|
||||
trigger.action.outText("+++LZ: NO config zone!", 30)
|
||||
end
|
||||
@ -336,6 +369,16 @@ function LZ.start()
|
||||
-- connect event handler
|
||||
world.addEventHandler(LZ)
|
||||
|
||||
-- load any saved data
|
||||
if persistence then
|
||||
-- sign up for persistence
|
||||
callbacks = {}
|
||||
callbacks.persistData = LZ.saveData
|
||||
persistence.registerModule("LZ", callbacks)
|
||||
-- now load my data
|
||||
LZ.loadData()
|
||||
end
|
||||
|
||||
-- start update
|
||||
LZ.update()
|
||||
|
||||
|
||||
112
modules/autoCSAR.lua
Normal file
112
modules/autoCSAR.lua
Normal file
@ -0,0 +1,112 @@
|
||||
autoCSAR = {}
|
||||
autoCSAR.version = "1.0.0"
|
||||
autoCSAR.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
}
|
||||
autoCSAR.killDelay = 2 * 60
|
||||
autoCSAR.counter = 31 -- any nuber is good, to kick-off counting
|
||||
--[[--
|
||||
VERSION HISTORY
|
||||
1.0.0 - Initial Version
|
||||
--]]--
|
||||
|
||||
function autoCSAR.removeGuy(args)
|
||||
local theGuy = args.theGuy
|
||||
if theGuy and theGuy:isExist() then
|
||||
Unit.destroy(theGuy)
|
||||
end
|
||||
end
|
||||
|
||||
function autoCSAR.createNewCSAR(theUnit)
|
||||
if not csarManager then
|
||||
trigger.action.outText("+++aCSAR: CSAR Manager not loaded, aborting", 30)
|
||||
-- return
|
||||
end
|
||||
-- enter with unit from landing_after_eject event
|
||||
-- unit has no group
|
||||
local coa = theUnit:getCoalition()
|
||||
if coa == 0 then -- neutral
|
||||
trigger.action.outText("Neutal Pilot made it safely to ground.", 30)
|
||||
return
|
||||
end
|
||||
if coa == 1 and not autoCSAR.redCSAR then
|
||||
return -- we don't do red
|
||||
end
|
||||
if coa == 2 and not autoCSAR.blueCSAR then
|
||||
return -- no blue rescue
|
||||
end
|
||||
|
||||
-- for later expansion
|
||||
local theGroup = theUnit:getGroup()
|
||||
if theGroup then
|
||||
trigger.action.outText("We have a group for <" .. theUnit:getName() .. ">", 30)
|
||||
end
|
||||
|
||||
-- create a CSAR mission now
|
||||
csarManager.createCSARForParachutist(theUnit, "Xray-" .. autoCSAR.counter)
|
||||
autoCSAR.counter = autoCSAR.counter + 1
|
||||
|
||||
-- schedule removal of pilot
|
||||
local args = {}
|
||||
args.theGuy = theUnit
|
||||
timer.scheduleFunction(autoCSAR.removeGuy, args, timer.getTime() + autoCSAR.killDelay)
|
||||
end
|
||||
|
||||
function autoCSAR:onEvent(event)
|
||||
if event.id == 31 then -- landing_after_eject
|
||||
if event.initiator then
|
||||
autoCSAR.createNewCSAR(event.initiator)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function autoCSAR.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("autoCSARConfig")
|
||||
if not theZone then
|
||||
theZone = cfxZones.createSimpleZone("autoCSARConfig")
|
||||
if autoCSAR.verbose then
|
||||
trigger.action.outText("+++aCSAR: NO config zone!", 30)
|
||||
end
|
||||
end
|
||||
|
||||
autoCSAR.redCSAR = cfxZones.getBoolFromZoneProperty(theZone, "red", true)
|
||||
if cfxZones.hasProperty(theZone, "redCSAR") then
|
||||
autoCSAR.redCSAR = cfxZones.getBoolFromZoneProperty(theZone, "redCSAR", true)
|
||||
end
|
||||
|
||||
autoCSAR.blueCSAR = cfxZones.getBoolFromZoneProperty(theZone, "blue", true)
|
||||
if cfxZones.hasProperty(theZone, "blueCSAR") then
|
||||
autoCSAR.blueCSAR = cfxZones.getBoolFromZoneProperty(theZone, "blueCSAR", true)
|
||||
end
|
||||
|
||||
if autoCSAR.verbose then
|
||||
trigger.action.outText("+++aCSAR: read config", 30)
|
||||
end
|
||||
end
|
||||
|
||||
function autoCSAR.start()
|
||||
-- lib check
|
||||
if not dcsCommon.libCheck then
|
||||
trigger.action.outText("cfx autoCSAR requires dcsCommon", 30)
|
||||
return false
|
||||
end
|
||||
if not dcsCommon.libCheck("cfx autoCSAR", autoCSAR.requiredLibs) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- read config
|
||||
autoCSAR.readConfigZone()
|
||||
|
||||
-- connect event handler
|
||||
world.addEventHandler(autoCSAR)
|
||||
|
||||
trigger.action.outText("cfx autoCSAR v" .. autoCSAR.version .. " started.", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
-- let's go!
|
||||
if not autoCSAR.start() then
|
||||
trigger.action.outText("cfx autoCSAR aborted: missing libraries", 30)
|
||||
autoCSAR = nil
|
||||
end
|
||||
@ -1,5 +1,5 @@
|
||||
cfxArtilleryZones = {}
|
||||
cfxArtilleryZones.version = "2.2.1"
|
||||
cfxArtilleryZones.version = "2.2.2"
|
||||
cfxArtilleryZones.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
@ -30,6 +30,7 @@ cfxArtilleryZones.verbose = false
|
||||
- code cleanup
|
||||
2.2.0 - DML Watchflag integration
|
||||
2.2.1 - minor code clean-up
|
||||
2.2.2 - new doParametricFireAt()
|
||||
|
||||
Artillery Target Zones *** EXTENDS ZONES ***
|
||||
Target Zones for artillery. Can determine which zones are in range and visible and then handle artillery barrage to this zone
|
||||
@ -258,6 +259,37 @@ function cfxArtilleryZones.doBoom(args)
|
||||
cfxArtilleryZones.invokeCallbacksFor('impact', args.zone, data)
|
||||
end
|
||||
|
||||
function cfxArtilleryZones.doParametricFireAt(aPoint, accuracy, shellNum, shellBaseStrength, shellVariance, transitionTime)
|
||||
-- accuracy is meters from center
|
||||
if not aPoint then return end
|
||||
if not accuracy then accuracy = 100 end
|
||||
if not shellNum then shellNum = 17 end
|
||||
if not shellBaseStrength then shellBaseStrength = 500 end
|
||||
if not shellVariance then shellVariance = 0.2 end
|
||||
if not transitionTime then transitionTime = 17 end
|
||||
|
||||
local alt = land.getHeight({x=aPoint.x, y=aPoint.z})
|
||||
local center = {x=aPoint.x, y=alt, z=aPoint.z}
|
||||
|
||||
for i=1, shellNum do
|
||||
local thePoint = dcsCommon.randomPointInCircle(accuracy, 0, center.x, center.z)
|
||||
thePoint.y = land.getHeight({x=thePoint.x, y=thePoint.z})
|
||||
local boomArgs = {}
|
||||
local strVar = shellBaseStrength * shellVariance
|
||||
strVar = strVar * (2 * dcsCommon.randomPercent() - 1.0) -- go from -1 to 1
|
||||
|
||||
boomArgs.strength = shellBaseStrength + strVar
|
||||
thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z}) + 1 -- elevate to ground height + 1
|
||||
boomArgs.point = thePoint
|
||||
boomArgs.zone = aZone
|
||||
local timeVar = 5 * (2 * dcsCommon.randomPercent() - 1.0) -- +/- 1.5 seconds
|
||||
if timeVar < 0 then timeVar = -timeVar end
|
||||
|
||||
timer.scheduleFunction(cfxArtilleryZones.doBoom, boomArgs, timer.getTime() + transitionTime + timeVar)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function cfxArtilleryZones.doFireAt(aZone, maxDistFromCenter)
|
||||
if type(aZone) == "string" then
|
||||
local mZone = cfxArtilleryZones.findArtilleryZoneNamed(aZone)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxHeloTroops = {}
|
||||
cfxHeloTroops.version = "2.2.0"
|
||||
cfxHeloTroops.version = "2.3.0"
|
||||
cfxHeloTroops.verbose = false
|
||||
cfxHeloTroops.autoDrop = true
|
||||
cfxHeloTroops.autoPickup = false
|
||||
@ -22,7 +22,9 @@ cfxHeloTroops.pickupRange = 100 -- meters
|
||||
-- -- (re?) connected readConfigZone (wtf?)
|
||||
-- -- persistence support
|
||||
-- -- made legalTroops entrirely optional and defer to dcsComon else
|
||||
|
||||
-- 2.3.0 -- interface with owned zones and playerScore when
|
||||
-- -- combat-dropping troops into non-owned owned zone.
|
||||
-- -- prevent auto-load from pre-empting loading csar troops
|
||||
--
|
||||
-- cfxHeloTroops -- a module to pick up and drop infantry. Can be used with any helo,
|
||||
-- might be used to configure to only certain
|
||||
@ -531,6 +533,15 @@ function cfxHeloTroops.filterTroopsByType(unitsToLoad)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- check if we are about to pre-empt a CSAR mission
|
||||
if csarManager then
|
||||
if csarManager.isCSARTarget(group) then
|
||||
-- this one is managed by csarManager,
|
||||
-- don't load it for helo troops
|
||||
pass = false
|
||||
end
|
||||
end
|
||||
|
||||
if pass then
|
||||
table.insert(filteredGroups, aTeam)
|
||||
end
|
||||
@ -578,12 +589,43 @@ function cfxHeloTroops.redirectDeployTroops(args)
|
||||
timer.scheduleFunction(cfxHeloTroops.doDeployTroops, args, timer.getTime() + 0.1)
|
||||
end
|
||||
|
||||
function cfxHeloTroops.scoreWhenCapturing(theUnit)
|
||||
if theUnit and Unit.isExist(theUnit) and theUnit.getPlayerName then
|
||||
-- see if wer are inside a non-alinged zone
|
||||
-- and this includes a neutral zone
|
||||
local coa = theUnit:getCoalition()
|
||||
local p = theUnit:getPoint()
|
||||
local theGroup = theUnit:getGroup()
|
||||
local ID = theGroup:getID()
|
||||
local nearestZone, dist = cfxOwnedZones.getNearestOwnedZoneToPoint(p)
|
||||
if nearestZone and dist < nearestZone.radius then
|
||||
-- we are inside an owned zone!
|
||||
if nearestZone.owner ~= coa then
|
||||
-- yup, combat drop!
|
||||
local theScore = cfxHeloTroops.combatDropScore
|
||||
local pName = theUnit:getPlayerName()
|
||||
if pName then
|
||||
cfxPlayerScore.updateScoreForPlayer(pName, theScore)
|
||||
cfxPlayerScore.logFeatForPlayer(pName, "Combat Troop Insertion at " .. nearestZone.name, coa)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function cfxHeloTroops.doDeployTroops(args)
|
||||
local conf = args[1]
|
||||
local what = args[2]
|
||||
-- deploy the troops I have on board in formation
|
||||
cfxHeloTroops.deployTroopsFromHelicopter(conf)
|
||||
|
||||
-- interface with playerscore if we dropped
|
||||
-- inside an enemy-owned zone
|
||||
if cfxPlayerScore and cfxOwnedZones then
|
||||
local theUnit = conf.unit
|
||||
cfxHeloTroops.scoreWhenCapturing(theUnit)
|
||||
end
|
||||
|
||||
-- set own troops to 0 and erase type string
|
||||
conf.troopsOnBoardNum = 0
|
||||
conf.troopsOnBoard = {}
|
||||
@ -840,7 +882,7 @@ function cfxHeloTroops.readConfigZone()
|
||||
cfxHeloTroops.autoDrop = cfxZones.getBoolFromZoneProperty(theZone, "autoDrop", false)
|
||||
cfxHeloTroops.autoPickup = cfxZones.getBoolFromZoneProperty(theZone, "autoPickup", false)
|
||||
cfxHeloTroops.pickupRange = cfxZones.getNumberFromZoneProperty(theZone, "pickupRange", 100)
|
||||
|
||||
cfxHeloTroops.combatDropScore = cfxZones.getNumberFromZoneProperty(theZone, "combatDropScore", 200)
|
||||
end
|
||||
|
||||
--
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxMX = {}
|
||||
cfxMX.version = "1.2.3"
|
||||
cfxMX.version = "1.2.4"
|
||||
cfxMX.verbose = false
|
||||
--[[--
|
||||
Mission data decoder. Access to ME-built mission structures
|
||||
@ -23,7 +23,7 @@ cfxMX.verbose = false
|
||||
- playerUnitByName
|
||||
1.2.3 - groupTypeByName
|
||||
- groupCoalitionByName
|
||||
|
||||
1.2.4 - playerUnit2Group cross index
|
||||
--]]--
|
||||
cfxMX.groupNamesByID = {}
|
||||
cfxMX.groupIDbyName = {}
|
||||
@ -40,6 +40,7 @@ cfxMX.allStaticByName = {}
|
||||
|
||||
cfxMX.playerGroupByName = {} -- returns data only if a player is in group
|
||||
cfxMX.playerUnitByName = {} -- returns data only if this is a player unit
|
||||
cfxMX.playerUnit2Group = {} -- returns a group data for player units.
|
||||
|
||||
function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal)
|
||||
if not fetchOriginal then fetchOriginal = false end
|
||||
@ -240,6 +241,7 @@ function cfxMX.createCrossReferences()
|
||||
-- player unit
|
||||
cfxMX.playerUnitByName[unit_data.name] = unit_data
|
||||
cfxMX.playerGroupByName[aName] = group_data -- inefficient, but works
|
||||
cfxMX.playerUnit2Group[unit_data.name] = group_data
|
||||
end -- if unit skill client
|
||||
end -- if has skill
|
||||
end -- for all units
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxOwnedZones = {}
|
||||
cfxOwnedZones.version = "1.2.1"
|
||||
cfxOwnedZones.version = "1.2.2"
|
||||
cfxOwnedZones.verbose = false
|
||||
cfxOwnedZones.announcer = true
|
||||
cfxOwnedZones.name = "cfxOwnedZones"
|
||||
@ -46,6 +46,8 @@ cfxOwnedZones.name = "cfxOwnedZones"
|
||||
- conq+1 --> conquered!
|
||||
- no cfxGroundTroop bug (no delay)
|
||||
1.2.1 - fix in load to correctly re-establish all attackers for subsequent save
|
||||
1.2.2 - redCap! and blueCap!
|
||||
|
||||
|
||||
--]]--
|
||||
cfxOwnedZones.requiredLibs = {
|
||||
@ -218,11 +220,20 @@ function cfxOwnedZones.addOwnedZone(aZone)
|
||||
local paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false)
|
||||
aZone.paused = paused
|
||||
|
||||
if cfxZones.hasProperty(aZone, "conquered!") then
|
||||
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conquered!", "*<cfxnone>")
|
||||
if cfxZones.hasProperty(aZone, "conq+1") then
|
||||
elseif cfxZones.hasProperty(aZone, "conq+1") then
|
||||
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conq+1", "*<cfxnone>")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(aZone, "redCap!") then
|
||||
aZone.redCap = cfxZones.getStringFromZoneProperty(aZone, "redCap!", "none")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(aZone, "blueCap!") then
|
||||
aZone.blueCap = cfxZones.getStringFromZoneProperty(aZone, "blueCap!", "none")
|
||||
end
|
||||
|
||||
aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false)
|
||||
aZone.untargetable = cfxZones.getBoolFromZoneProperty(aZone, "untargetable", false)
|
||||
aZone.hidden = cfxZones.getBoolFromZoneProperty(aZone, "hidden", false)
|
||||
@ -517,12 +528,19 @@ function cfxOwnedZones.zoneConquered(aZone, theSide, formerOwner) -- 0 = neutral
|
||||
trigger.action.outSoundForCoalition(1, "Death BRASS.wav")
|
||||
end
|
||||
end
|
||||
-- increase conq flag
|
||||
-- if aZone.conqueredFlag then
|
||||
-- local lastVal = trigger.misc.getUserFlag(aZone.conqueredFlag)
|
||||
-- trigger.action.setUserFlag(aZone.conqueredFlag, lastVal + 1)
|
||||
|
||||
if aZone.conqueredFlag then
|
||||
cfxZones.pollFlag(aZone.conqueredFlag, "inc", aZone)
|
||||
-- end
|
||||
end
|
||||
|
||||
if theSide == 1 and aZone.redCap then
|
||||
cfxZones.pollFlag(aZone.redCap, "inc", aZone)
|
||||
end
|
||||
|
||||
if theSide == 2 and aZone.blueCap then
|
||||
cfxZones.pollFlag(aZone.blueCap, "inc", aZone)
|
||||
end
|
||||
|
||||
-- invoke callbacks now
|
||||
cfxOwnedZones.invokeConqueredCallbacks(aZone, theSide, formerOwner)
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxPlayerScore = {}
|
||||
cfxPlayerScore.version = "1.4.0"
|
||||
cfxPlayerScore.version = "1.5.1"
|
||||
cfxPlayerScore.badSound = "Death BRASS.wav"
|
||||
cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
|
||||
cfxPlayerScore.announcer = true
|
||||
@ -27,9 +27,13 @@ cfxPlayerScore.announcer = true
|
||||
- removed dependency to cfxPlayer
|
||||
1.4.0 - persistence support
|
||||
- better unit-->static switch support for generic type kill
|
||||
|
||||
1.5.0 - added feats to score
|
||||
- feats API
|
||||
- logFeatForPlayer(playerName, theFeat, coa)
|
||||
1.5.1 - init feats before reading
|
||||
|
||||
--]]--
|
||||
|
||||
cfxPlayerScore.requiredLibs = {
|
||||
"dcsCommon", -- this is doing score keeping
|
||||
"cfxZones", -- zones for config
|
||||
@ -148,8 +152,10 @@ function cfxPlayerScore.getPlayerScore(playerName)
|
||||
thePlayerScore = {}
|
||||
thePlayerScore.name = playerName
|
||||
thePlayerScore.score = 0 -- score
|
||||
thePlayerScore.killTypes = {} -- the type strings killed
|
||||
thePlayerScore.killTypes = {} -- the type strings killed, dict <typename> <numkilla>
|
||||
thePlayerScore.totalKills = 0 -- number of kills total
|
||||
thePlayerScore.featTypes = {} -- dict <featname> <number> of other things player did
|
||||
thePlayerScore.totalFeats = 0
|
||||
end
|
||||
return thePlayerScore
|
||||
end
|
||||
@ -184,12 +190,51 @@ function cfxPlayerScore.logKillForPlayer(playerName, theUnit)
|
||||
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
|
||||
end
|
||||
|
||||
function cfxPlayerScore.logFeatForPlayer(playerName, theFeat, coa)
|
||||
if not theFeat then return end
|
||||
if not playerName then return end
|
||||
-- access player's record. will alloc if new by itself
|
||||
local thePlayerScore = cfxPlayerScore.getPlayerScore(playerName)
|
||||
if not thePlayerScore.featTypes then thePlayerScore.featTypes = {} end
|
||||
local featCount = thePlayerScore.featTypes[theFeat]
|
||||
if featCount == nil then
|
||||
featCount = 0
|
||||
end
|
||||
featCount = featCount + 1
|
||||
thePlayerScore.totalFeats = thePlayerScore.totalFeats + 1
|
||||
thePlayerScore.featTypes[theFeat] = featCount
|
||||
|
||||
if coa then
|
||||
trigger.action.outTextForCoalition(coa, playerName .. " achieved " .. theFeat, 30)
|
||||
trigger.action.outSoundForCoalition(coa, cfxPlayerScore.scoreSound)
|
||||
end
|
||||
cfxPlayerScore.setPlayerScore(playerName, thePlayerScore)
|
||||
end
|
||||
|
||||
function cfxPlayerScore.playerScore2text(thePlayerScore)
|
||||
local desc = thePlayerScore.name .. " - score: ".. thePlayerScore.score .. " - kills: " .. thePlayerScore.totalKills .. "\n"
|
||||
-- now go through all killSide
|
||||
if dcsCommon.getSizeOfTable(thePlayerScore.killTypes) < 1 then
|
||||
desc = desc .. " - NONE -\n"
|
||||
end
|
||||
for theType, quantity in pairs(thePlayerScore.killTypes) do
|
||||
desc = desc .. " - " .. theType .. ": " .. quantity .. "\n"
|
||||
end
|
||||
|
||||
-- now enumerate all feats
|
||||
if not thePlayerScore.featTypes then thePlayerScore.featTypes = {} end
|
||||
|
||||
desc = desc .. "\nOther Accomplishments:\n"
|
||||
if dcsCommon.getSizeOfTable(thePlayerScore.featTypes) < 1 then
|
||||
desc = desc .. " - NONE -\n"
|
||||
end
|
||||
for theFeat, quantity in pairs(thePlayerScore.featTypes) do
|
||||
desc = desc .. " - " .. theFeat
|
||||
if quantity > 1 then
|
||||
desc = desc .. " (x" .. quantity .. ")"
|
||||
end
|
||||
desc = desc .. "\n"
|
||||
end
|
||||
return desc
|
||||
end
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxReconMode = {}
|
||||
cfxReconMode.version = "2.1.2"
|
||||
cfxReconMode.version = "2.1.3"
|
||||
cfxReconMode.verbose = false -- set to true for debug info
|
||||
cfxReconMode.reconSound = "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav" -- to be played when somethiong discovered
|
||||
|
||||
@ -18,6 +18,7 @@ cfxReconMode.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
}
|
||||
cfxReconMode.name = "cfxReconMode" -- to be compatible with test flags
|
||||
|
||||
--[[--
|
||||
VERSION HISTORY
|
||||
@ -82,6 +83,7 @@ VERSION HISTORY
|
||||
2.1.2 - imperialUnits for elevation
|
||||
- <ele> wildcard in message format
|
||||
- fix for mgrs bug in message (zone coords, not unit)
|
||||
2.1.3 - added cfxReconMode.name to allow direct acces with test zone flag
|
||||
|
||||
cfxReconMode is a script that allows units to perform reconnaissance
|
||||
missions and, after detecting units, marks them on the map with
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxZones = {}
|
||||
cfxZones.version = "2.8.5"
|
||||
cfxZones.version = "2.8.7"
|
||||
|
||||
-- cf/x zone management module
|
||||
-- reads dcs zones and makes them accessible and mutable
|
||||
@ -92,6 +92,9 @@ cfxZones.version = "2.8.5"
|
||||
- 2.8.5 - createGroundUnitsInZoneForCoalition() now always passes back a copy of the group data
|
||||
- data also contains cty = country and cat = category for easy spawn
|
||||
- getFlagValue additional zone name guards
|
||||
- 2.8.6 - fix in getFlagValue for missing delay
|
||||
- 2.8.7 - update isPointInsideZone(thePoint, theZone, radiusIncrease) - new radiusIncrease
|
||||
- isPointInsideZone() returns delta as well
|
||||
|
||||
--]]--
|
||||
cfxZones.verbose = false
|
||||
@ -507,17 +510,19 @@ function cfxZones.isPointInsidePoly(thePoint, poly)
|
||||
return true
|
||||
end;
|
||||
|
||||
function cfxZones.isPointInsideZone(thePoint, theZone)
|
||||
function cfxZones.isPointInsideZone(thePoint, theZone, radiusIncrease)
|
||||
-- radiusIncrease only works for circle zones
|
||||
if not radiusIncrease then radiusIncrease = 0 end
|
||||
local p = {x=thePoint.x, y = 0, z = thePoint.z} -- zones have no altitude
|
||||
if (theZone.isCircle) then
|
||||
local zp = cfxZones.getPoint(theZone)
|
||||
local d = dcsCommon.dist(p, theZone.point)
|
||||
return d < theZone.radius
|
||||
return d < theZone.radius + radiusIncrease, d
|
||||
end
|
||||
|
||||
if (theZone.isPoly) then
|
||||
--trigger.action.outText("zne: isPointInside: " .. theZone.name .. " is Polyzone!", 30)
|
||||
return (cfxZones.isPointInsidePoly(p, theZone.poly))
|
||||
return (cfxZones.isPointInsidePoly(p, theZone.poly)), 0 -- always returns delta 0
|
||||
end
|
||||
|
||||
trigger.action.outText("isPointInsideZone: Unknown zone type for " .. outerZone.name, 10)
|
||||
@ -1324,7 +1329,7 @@ end
|
||||
function cfxZones.getFlagValue(theFlag, theZone)
|
||||
local zoneName = "<dummy>"
|
||||
if not theZone or not theZone.name then
|
||||
trigger.action.outText("+++Zne: no zone or zone name on getFlagValue")
|
||||
trigger.action.outText("+++Zne: no zone or zone name on getFlagValue", 30)
|
||||
else
|
||||
zoneName = theZone.name -- for flag wildcards
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
csarManager = {}
|
||||
csarManager.version = "2.1.3"
|
||||
csarManager.version = "2.2.0"
|
||||
csarManager.verbose = false
|
||||
csarManager.ups = 1
|
||||
|
||||
@ -41,6 +41,14 @@ csarManager.ups = 1
|
||||
- 2.1.3 - theMassObject now local
|
||||
- winch pickup now also adds weight so they can be returned
|
||||
- made some improvements to performance by making vars local
|
||||
- 2.2.0 - interface for autoCSAR
|
||||
createDownedPilot() - added existingUnit option
|
||||
createCSARMissionData() - added existinUnit option
|
||||
- when no config zone, runs through empty zone
|
||||
- actionSound
|
||||
- integration with playerScore
|
||||
- score global and per-mission
|
||||
- isCSARTarget API
|
||||
|
||||
|
||||
--]]--
|
||||
@ -53,79 +61,13 @@ csarManager.requiredLibs = {
|
||||
"cargoSuper",
|
||||
-- "cfxCommander", -- needed if you want to hand-create CSAR missions
|
||||
}
|
||||
|
||||
-- *** DOES NOT EXTEND ZONES *** BUT USES OWN STRUCT
|
||||
-- *** extends zones for csarMission zones though
|
||||
|
||||
--[[--
|
||||
CSAR MANAGER
|
||||
============
|
||||
|
||||
This module can create and manage CSAR missions, i.e.
|
||||
create a unit on the ground, mark it on the map, handle
|
||||
if the unit is killed, create enemies in the vicinity
|
||||
|
||||
It will install a menu in any troop helicopter as
|
||||
determined by dcsCommon.isTroopCarrier() with the
|
||||
option to list available csar mission. for each created mission
|
||||
it will give range and frequency for ADF
|
||||
When a helicopter is in range, it will set smoke to better
|
||||
visually identify the location.
|
||||
|
||||
When the helicopter lands close enough to a downed pilot,
|
||||
the pilot is picket up automatically. Their weight is added
|
||||
to the unit, so it may overload!
|
||||
|
||||
When the helicopter than lands in a CSARBASE Zone, the mission is
|
||||
a success and a success callback is invoked automatically for
|
||||
all picked up groups. All zones that have the CSARBASE property are
|
||||
CSAR Bases, but their coalition must be either neutral or match the
|
||||
one of the unit that landed
|
||||
|
||||
On start, it scans all zones for a CSAR property, and creates
|
||||
a CSAR mission with data taken from the properties in the
|
||||
zone so you can easily create CSAR missions in ME
|
||||
|
||||
WARNING: ASSUMES SINGLE UNIT PLAYER GROUPS
|
||||
==========================================
|
||||
|
||||
Main Interface
|
||||
- createCSARMission(location, side, numCrew, mark, clearing, timeout)
|
||||
creates a csar mission that can be tracked.
|
||||
location is the position on the map
|
||||
side is the side the unit is on (neutal is for any side)
|
||||
numCrew the number of people (1-4)
|
||||
mark true if marked on map
|
||||
clearing will create a clearing
|
||||
timeout - time in seconds until pilots die. timer stops on pickup
|
||||
RETURNS true, "ok" -- false, "fail reason" (string)
|
||||
|
||||
- createCSARAdversaries(location, side, numEnemies, radius, maxRadius)
|
||||
creates some random infantery randomized on a circle around the location
|
||||
location - center, usually where the downed pilot is
|
||||
side - side of the enemy red/blue
|
||||
numEnemies - number of infantry
|
||||
radius[, maxRadius] distance of the enemy troops
|
||||
|
||||
- in ME, create at least one zone with a property named "CSARBASE" for
|
||||
each side that supports csar missions. This is where the players
|
||||
can drop off pilots that they rescued. If you have no CSARBASE zone
|
||||
defined, you'll receive a warning for that side when you attempt a
|
||||
rescue
|
||||
|
||||
- in ME you can place zones with a CSAR attribute that will generate
|
||||
a scar mission. Further attributes are "coalition" (red/blue), "name" (any name you like) and "freq" (for elt ADR, leave empty for random)
|
||||
|
||||
NOTE:
|
||||
CSARBASE is compatible with the FARP Attribute of
|
||||
FARP Zones
|
||||
-- integrates automatically with playerScore if installed
|
||||
|
||||
|
||||
--]]--
|
||||
--
|
||||
-- OPTIONS
|
||||
--
|
||||
csarManager.useSmoke = false -- smoke is a performance killer, so you can turn it off
|
||||
csarManager.useSmoke = true -- smoke is a performance killer, so you can turn it off
|
||||
csarManager.smokeColor = 4 -- when using smoke
|
||||
|
||||
|
||||
@ -158,16 +100,24 @@ csarManager.csarCompleteCB = {}
|
||||
--
|
||||
-- CREATING A CSAR
|
||||
--
|
||||
function csarManager.createDownedPilot(theMission)
|
||||
function csarManager.createDownedPilot(theMission, existingUnit)
|
||||
|
||||
if not cfxCommander then
|
||||
trigger.action.outText("+++CSAR: can't create mission, module cfxCommander is missing.", 30)
|
||||
return
|
||||
end
|
||||
|
||||
if not existingUnit then
|
||||
local aLocation = {}
|
||||
local aHeading = 0 -- in rads
|
||||
local newTargetZone = theMission.zone
|
||||
if newTargetZone.radius > 1 then
|
||||
aLocation, aHeading = dcsCommon.randomPointOnPerimeter(newTargetZone.radius / 2 + 3, newTargetZone.point.x, newTargetZone.point.z)
|
||||
else
|
||||
aLocation.x = newTargetZone.point.x
|
||||
aLocation.z = newTargetZone.point.z
|
||||
aHeading = math.random(360)/360 * 2 * 3.1415
|
||||
end
|
||||
|
||||
local theBoyGroup = dcsCommon.createSingleUnitGroup(theMission.name,
|
||||
"Soldier M4 GRG", -- "Soldier M4 GRG",
|
||||
@ -189,7 +139,9 @@ function csarManager.createDownedPilot(theMission)
|
||||
else
|
||||
trigger.action.outText("+++csar: FAILED to create csar!", 30)
|
||||
end
|
||||
|
||||
else
|
||||
theMission.group = existingUnit:getGroup()
|
||||
end
|
||||
|
||||
-- we now use commands to send radio transmissions
|
||||
local ADF = 20 + math.random(90)
|
||||
@ -202,7 +154,7 @@ function csarManager.createDownedPilot(theMission)
|
||||
cfxCommander.scheduleCommands(theCommands, 2) -- in 2 seconds, so unit has time to percolate through DCS
|
||||
end
|
||||
|
||||
function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew, timeLimit, mapMarker, inRadius)
|
||||
function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew, timeLimit, mapMarker, inRadius, parashootUnit) -- if parashootUnit is set, will not allocate new
|
||||
-- create a type
|
||||
if not timeLimit then timeLimit = -1 end
|
||||
if not point then return nil end
|
||||
@ -228,7 +180,7 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
|
||||
newMission.freq = freq -- if nil will make random
|
||||
|
||||
-- allocate units
|
||||
csarManager.createDownedPilot(newMission)
|
||||
csarManager.createDownedPilot(newMission, parashootUnit)
|
||||
|
||||
-- update counter and return
|
||||
csarManager.missionID = csarManager.missionID + 1
|
||||
@ -262,6 +214,13 @@ function csarManager.removeMissionForGroup(theDownedGroup)
|
||||
end
|
||||
csarManager.openMissions = newMissions -- this is the new batch
|
||||
end
|
||||
|
||||
function csarManager.isCSARTarget(theGroup)
|
||||
for idx, theMission in pairs(csarManager.openMissions) do
|
||||
if theMission.group == theGroup then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
--
|
||||
-- UNIT CONFIG
|
||||
--
|
||||
@ -308,7 +267,9 @@ end
|
||||
|
||||
function csarManager.removeConfigForUnitNamed(aName)
|
||||
if not aName then return end
|
||||
if csarManager.unitConfigs[aName] then csarManager.unitConfigs[aName] = nil end
|
||||
if csarManager.unitConfigs[aName] then
|
||||
csarManager.unitConfigs[aName] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
@ -376,6 +337,25 @@ end
|
||||
--
|
||||
|
||||
function csarManager.successMission(who, where, theMission)
|
||||
-- who is
|
||||
-- where is
|
||||
-- theMission is mission table
|
||||
|
||||
-- playerScore integration
|
||||
if cfxPlayerScore then
|
||||
local theScore = theMission.score
|
||||
if not theScore then theScore = csarManager.rescueScore end
|
||||
|
||||
local theUnit = Unit.getByName(who)
|
||||
if theUnit and theUnit.getPlayerName then
|
||||
local pName = theUnit:getPlayerName()
|
||||
if pName then
|
||||
cfxPlayerScore.updateScoreForPlayer(pName, theScore)
|
||||
cfxPlayerScore.logFeatForPlayer(pName, "Evacuated " .. theMission.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
trigger.action.outTextForCoalition(theMission.side,
|
||||
who .. " successfully evacuated " .. theMission.name .. " to " .. where .. "!",
|
||||
30)
|
||||
@ -385,7 +365,7 @@ function csarManager.successMission(who, where, theMission)
|
||||
|
||||
csarManager.invokeCallbacks(theMission.side, true, 1, "success")
|
||||
|
||||
trigger.action.outSoundForCoalition(theMission.side, "Quest Snare 3.wav")
|
||||
trigger.action.outSoundForCoalition(theMission.side, csarManager.actionSound) -- "Quest Snare 3.wav")
|
||||
|
||||
if csarManager.csarRedDelivered and theMission.side == 1 then
|
||||
cfxZones.pollFlag(csarManager.csarRedDelivered, "inc", csarManager.configZone)
|
||||
@ -536,7 +516,7 @@ function csarManager.heloLanded(theUnit)
|
||||
theMassObject)
|
||||
end
|
||||
if didPickup then
|
||||
trigger.action.outSoundForCoalition(mySide, "Quest Snare 3.wav")
|
||||
trigger.action.outSoundForCoalition(mySide, csarManager.actionSound) -- "Quest Snare 3.wav")
|
||||
end
|
||||
-- reset unit's weight based on people on board
|
||||
local totalMass = cargoSuper.calculateTotalMassFor(myName)
|
||||
@ -756,7 +736,7 @@ function csarManager.doListCSARRequests(args)
|
||||
report = report .. "\n"
|
||||
|
||||
trigger.action.outTextForGroup(conf.id, report, 30)
|
||||
trigger.action.outSoundForGroup(conf.id, "Quest Snare 3.wav")
|
||||
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav")
|
||||
end
|
||||
|
||||
function csarManager.redirectStatusCarrying(args)
|
||||
@ -789,7 +769,7 @@ function csarManager.doStatusCarrying(args)
|
||||
report = report .. "\n"
|
||||
|
||||
trigger.action.outTextForGroup(conf.id, report, 30)
|
||||
trigger.action.outSoundForGroup(conf.id, "Quest Snare 3.wav")
|
||||
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav")
|
||||
end
|
||||
|
||||
function csarManager.redirectUnloadOne(args)
|
||||
@ -807,14 +787,14 @@ function csarManager.unloadOne(args)
|
||||
if theUnit:inAir() then
|
||||
report = "STRONGLY recommend we land first, Sir!"
|
||||
trigger.action.outTextForGroup(conf.id, report, 30)
|
||||
trigger.action.outSoundForGroup(conf.id, "Quest Snare 3.wav")
|
||||
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav")
|
||||
return
|
||||
end
|
||||
|
||||
if #conf.troopsOnBoard < 1 then
|
||||
report = "No evacuees on board."
|
||||
trigger.action.outTextForGroup(conf.id, report, 30)
|
||||
trigger.action.outSoundForGroup(conf.id, "Quest Snare 3.wav")
|
||||
trigger.action.outSoundForGroup(conf.id, csarManager.actionSound) -- "Quest Snare 3.wav")
|
||||
|
||||
else
|
||||
-- simulate a crash but for one unit
|
||||
@ -829,7 +809,7 @@ function csarManager.unloadOne(args)
|
||||
--TODO: remove weight for this pilot!
|
||||
|
||||
trigger.action.outTextForCoalition(theSide, myName .. " has aborted evacuating " .. msn.name .. ". New CSAR available.", 30)
|
||||
trigger.action.outSoundForCoalition(theSide, "Quest Snare 3.wav")
|
||||
trigger.action.outSoundForCoalition(theSide, csarManager.actionSound) -- "Quest Snare 3.wav")
|
||||
|
||||
-- recalc weight
|
||||
trigger.action.setUnitInternalCargo(myName, 10 + #conf.troopsOnBoard * csarManager.pilotWeight) -- 10 kg as empty + per-unit time people
|
||||
@ -933,7 +913,7 @@ function csarManager.updateCSARMissions()
|
||||
else
|
||||
local msg = aMission.name .. " confirmed KIA, repeat KIA. Abort CSAR."
|
||||
trigger.action.outTextForCoalition(aMission.side, msg, 30)
|
||||
trigger.action.outSoundForCoalition(aMission.side, "Quest Snare 3.wav")
|
||||
trigger.action.outSoundForCoalition(aMission.side, csarManager.actionSound) -- "Quest Snare 3.wav")
|
||||
end
|
||||
end
|
||||
csarManager.openMissions = newMissions -- this is the new batch
|
||||
@ -981,7 +961,7 @@ function csarManager.update() -- every second
|
||||
end
|
||||
msg = msg .. "\n"
|
||||
trigger.action.outTextForGroup(uID, msg, 30)
|
||||
trigger.action.outSoundForGroup(uID, "Quest Snare 3.wav")
|
||||
trigger.action.outSoundForGroup(uID, csarManager.actionSound) -- "Quest Snare 3.wav")
|
||||
table.insert(csarMission.messagedUnits, uName) -- remember that we messaged them so we don't do again
|
||||
end
|
||||
-- also pop smoke if not popped already, or more than 5 minutes ago
|
||||
@ -1046,7 +1026,7 @@ function csarManager.update() -- every second
|
||||
end
|
||||
|
||||
trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
|
||||
trigger.action.outSoundForGroup(uID, "Quest Snare 3.wav")
|
||||
trigger.action.outSoundForGroup(uID, csarManager.actionSound) --"Quest Snare 3.wav")
|
||||
|
||||
return -- we only ever rescue one
|
||||
end -- hovered long enough
|
||||
@ -1104,7 +1084,7 @@ end
|
||||
--
|
||||
-- create a CSAR Mission for a unit
|
||||
--
|
||||
function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent)
|
||||
function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent, score) -- invoked with aircraft as theUnit, usually still in air
|
||||
if not silent then silent = false end
|
||||
if not radius then radius = 1000 end
|
||||
if not pilotName then pilotName = "Eddie" end
|
||||
@ -1121,7 +1101,7 @@ function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent)
|
||||
if surf == 2 or surf == 3 then
|
||||
if not silent then
|
||||
trigger.action.outTextForCoalition(coal, "Bad chute! Bad chute! ".. pilotName .. " did not survive ejection out of their " .. theUnit:getTypeName(), 30)
|
||||
trigger.action.outSoundForGroup(coal, "Quest Snare 3.wav")
|
||||
trigger.action.outSoundForGroup(coal, csarManager.actionSound) -- "Quest Snare 3.wav")
|
||||
end
|
||||
return
|
||||
end
|
||||
@ -1137,13 +1117,26 @@ function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent)
|
||||
1,
|
||||
nil,
|
||||
nil)
|
||||
theMission.score = score
|
||||
csarManager.addMission(theMission)
|
||||
if not silent then
|
||||
trigger.action.outTextForCoalition(coal, "MAYDAY MAYDAY MAYDAY! ".. pilotName .. " in " .. theUnit:getTypeName() .. " ejected, report good chute. Prepare CSAR!", 30)
|
||||
trigger.action.outSoundForGroup(coal, "Quest Snare 3.wav")
|
||||
trigger.action.outSoundForGroup(coal, csarManager.actionSound) -- "Quest Snare 3.wav")
|
||||
end
|
||||
end
|
||||
|
||||
function csarManager.createCSARForParachutist(theUnit, name) -- invoked with parachute guy on ground as theUnit
|
||||
local coa = theUnit:getCoalition()
|
||||
local pos = theUnit:getPoint()
|
||||
-- unit DOES NOT HAVE GROUP!!!
|
||||
-- create a CSAR mission now
|
||||
local theMission = csarManager.createCSARMissionData(pos, coa, nil, name, nil, nil, nil, 0.1, nil)
|
||||
csarManager.addMission(theMission)
|
||||
-- if not silent then
|
||||
trigger.action.outTextForCoalition(coa, "MAYDAY MAYDAY MAYDAY! ".. name .. " requesting extraction after eject!", 30)
|
||||
trigger.action.outSoundForGroup(coa, csarManager.actionSound) -- "Quest Snare 3.wav")
|
||||
-- end
|
||||
end
|
||||
|
||||
--
|
||||
-- csar (mission) zones
|
||||
@ -1199,6 +1192,9 @@ function csarManager.readCSARZone(theZone)
|
||||
theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "score") then
|
||||
theZone.score = cfxZones.getNumberFromZoneProperty(theZone, "score", 100)
|
||||
end
|
||||
|
||||
if (not deferred) then
|
||||
local theMission = csarManager.createCSARMissionData(
|
||||
@ -1240,9 +1236,6 @@ end
|
||||
-- Init & Start
|
||||
--
|
||||
|
||||
|
||||
|
||||
|
||||
function csarManager.invokeCallbacks(theCoalition, success, numRescued, notes)
|
||||
-- invoke anyone who wants to know that a group
|
||||
-- of people was rescued.
|
||||
@ -1261,7 +1254,7 @@ function csarManager.readConfigZone()
|
||||
if csarManager.verbose then
|
||||
trigger.action.outText("+++csar: NO config zone!", 30)
|
||||
end
|
||||
return
|
||||
theZone = cfxZones.createSimpleZone("csarManagerConfig")
|
||||
end
|
||||
csarManager.configZone = theZone -- save for flag banging compatibility
|
||||
|
||||
@ -1284,7 +1277,7 @@ function csarManager.readConfigZone()
|
||||
|
||||
if cfxZones.hasProperty(theZone, "csarDelivered!") then
|
||||
csarManager.csarDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarDelivered!", "*<none>")
|
||||
trigger.action.outText("+++csar: will bang csarDelivered: <" .. csarManager.csarDelivered .. ">", 30)
|
||||
--trigger.action.outText("+++csar: will bang csarDelivered: <" .. csarManager.csarDelivered .. ">", 30)
|
||||
end
|
||||
|
||||
csarManager.rescueRadius = cfxZones.getNumberFromZoneProperty(theZone, "rescueRadius", 70) --70 -- must land within 50m to rescue
|
||||
@ -1295,6 +1288,9 @@ function csarManager.readConfigZone()
|
||||
csarManager.beaconSound = cfxZones.getStringFromZoneProperty(theZone, "beaconSound", "Radio_beacon_of_distress_on_121.ogg") --"Radio_beacon_of_distress_on_121,5_MHz.ogg"
|
||||
csarManager.pilotWeight = cfxZones.getNumberFromZoneProperty(theZone, "pilotWeight", 120) -- 120
|
||||
|
||||
csarManager.rescueScore = cfxZones.getNumberFromZoneProperty(theZone, "rescueScore", 100)
|
||||
|
||||
csarManager.actionSound = cfxZones.getStringFromZoneProperty(theZone, "actionSound", "Quest Snare 3.wav")
|
||||
csarManager.vectoring = cfxZones.getBoolFromZoneProperty(theZone, "vectoring", true)
|
||||
|
||||
if csarManager.verbose then
|
||||
@ -1373,4 +1369,5 @@ end
|
||||
|
||||
- when unloading one by menu, update weight!!!
|
||||
|
||||
-- allow neutral pick-up
|
||||
--]]--
|
||||
@ -1,5 +1,5 @@
|
||||
dcsCommon = {}
|
||||
dcsCommon.version = "2.7.4"
|
||||
dcsCommon.version = "2.7.5"
|
||||
--[[-- VERSION HISTORY
|
||||
2.2.6 - compassPositionOfARelativeToB
|
||||
- clockPositionOfARelativeToB
|
||||
@ -101,6 +101,9 @@ dcsCommon.version = "2.7.4"
|
||||
2.7.3 - new string2Array()
|
||||
- additional guard for isPlayerUnit
|
||||
2.7.4 - new array2string()
|
||||
2.7.5 - new bitAND32()
|
||||
- new LSR()
|
||||
- new num2bin()
|
||||
|
||||
--]]--
|
||||
|
||||
@ -2720,6 +2723,62 @@ function dcsCommon.getSceneryObjectInZoneByName(theName, theZone) -- DCS ZONE!!!
|
||||
return nil
|
||||
end
|
||||
|
||||
--
|
||||
-- bitwise operators
|
||||
--
|
||||
function dcsCommon.bitAND32(a, b)
|
||||
if not a then a = 0 end
|
||||
if not b then b = 0 end
|
||||
local z = 0
|
||||
local e = 1
|
||||
for i = 0, 31 do
|
||||
local a1 = a % 2 -- 0 or 1
|
||||
local b1 = b % 2 -- 0 or 1
|
||||
if a1 == 1 and b1 == 1 then
|
||||
a = a - 1 -- remove bit
|
||||
b = b - 1
|
||||
z = z + e
|
||||
else
|
||||
if a1 == 1 then a = a - 1 end -- remove bit
|
||||
if b1 == 1 then b = b - 1 end
|
||||
end
|
||||
a = a / 2 -- shift right
|
||||
b = b / 2
|
||||
e = e * 2 -- raise e by 1
|
||||
end
|
||||
return z
|
||||
end
|
||||
|
||||
function dcsCommon.num2bin(a)
|
||||
if not a then a = 0 end
|
||||
local z = ""
|
||||
for i = 0, 31 do
|
||||
local a1 = a % 2 -- 0 or 1
|
||||
if a1 == 1 then
|
||||
a = a - 1 -- remove bit
|
||||
z = "1"..z
|
||||
else
|
||||
z = "0"..z
|
||||
end
|
||||
a = a / 2 -- shift right
|
||||
end
|
||||
return z
|
||||
end
|
||||
|
||||
function dcsCommon.LSR(a, num)
|
||||
if not a then a = 0 end
|
||||
if not num then num = 16 end
|
||||
for i = 1, num do
|
||||
local a1 = a % 2 -- 0 or 1
|
||||
if a1 == 1 then
|
||||
a = a - 1 -- remove bit
|
||||
end
|
||||
a = a / 2 -- shift right
|
||||
end
|
||||
return a
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- INIT
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
fireFX = {}
|
||||
fireFX.version = "1.0.0"
|
||||
fireFX.version = "1.1.0"
|
||||
fireFX.verbose = false
|
||||
fireFX.ups = 1
|
||||
fireFX.requiredLibs = {
|
||||
@ -8,6 +8,13 @@ fireFX.requiredLibs = {
|
||||
}
|
||||
fireFX.fx = {}
|
||||
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - Initial version
|
||||
1.1.0 - persistence
|
||||
|
||||
--]]--
|
||||
|
||||
function fireFX.addFX(theZone)
|
||||
table.insert(fireFX.fx, theZone)
|
||||
end
|
||||
@ -127,13 +134,62 @@ function fireFX.update()
|
||||
|
||||
end
|
||||
|
||||
--
|
||||
-- LOAD / SAVE
|
||||
--
|
||||
function fireFX.saveData()
|
||||
local theData = {}
|
||||
local allFX = {}
|
||||
for idx, theFX in pairs(fireFX.fx) do
|
||||
local theName = theFX.name
|
||||
local FXData = {}
|
||||
FXData.burning = theFX.burning
|
||||
|
||||
allFX[theName] = FXData
|
||||
end
|
||||
theData.allFX = allFX
|
||||
return theData
|
||||
end
|
||||
|
||||
function fireFX.loadData()
|
||||
if not persistence then return end
|
||||
local theData = persistence.getSavedDataForModule("fireFX")
|
||||
if not theData then
|
||||
if fireFX.verbose then
|
||||
trigger.action.outText("+++ffx persistence: no save data received, skipping.", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local allFX = theData.allFX
|
||||
if not allFX then
|
||||
if fireFX.verbose then
|
||||
trigger.action.outText("+++ffx persistence: no fire FX data, skipping", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
for theName, theData in pairs(allFX) do
|
||||
local theFX = fireFX.getFXByName(theName)
|
||||
if theFX then
|
||||
if theData.burning then
|
||||
fireFX.startTheFire(theFX)
|
||||
end
|
||||
theFX.inited = true -- ensure no onStart overwrite
|
||||
else
|
||||
trigger.action.outText("+++ffx: persistence: cannot synch fire FX <" .. theName .. ">, skipping", 40)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Config & Start
|
||||
--
|
||||
function fireFX.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("fireFXConfig")
|
||||
if not theZone then
|
||||
theZone = cfxZones.createSimpleZone(LZConfig)
|
||||
theZone = cfxZones.createSimpleZone("fireFX")
|
||||
if fireFX.verbose then
|
||||
trigger.action.outText("+++ffx: NO config zone!", 30)
|
||||
end
|
||||
@ -165,9 +221,20 @@ function fireFX.start()
|
||||
fireFX.addFX(aZone) -- add to list
|
||||
end
|
||||
|
||||
-- load any saved data
|
||||
if persistence then
|
||||
-- sign up for persistence
|
||||
callbacks = {}
|
||||
callbacks.persistData = fireFX.saveData
|
||||
persistence.registerModule("fireFX", callbacks)
|
||||
-- now load my data
|
||||
fireFX.loadData()
|
||||
end
|
||||
|
||||
-- handle onStart
|
||||
for idx, theZone in pairs(fireFX.fx) do
|
||||
if theZone.fxOnStart then
|
||||
if (not theZone.inited) and (theZone.fxOnStart) then
|
||||
-- only if we did not init them with loaded data
|
||||
fireFX.startTheFire(theZone)
|
||||
end
|
||||
end
|
||||
|
||||
@ -52,6 +52,8 @@ limitedAirframes.requiredLibs = {
|
||||
- 1.4.1 - removed dependency to cfxPlayer
|
||||
- 1.5.0 - persistence support
|
||||
- 1.5.1 - new "announcer" attribute
|
||||
- 1.5.2 - integration with autoCSAR: prevent limitedAF from creating csar
|
||||
when autoCSAR is active
|
||||
|
||||
--]]--
|
||||
|
||||
@ -665,6 +667,9 @@ end
|
||||
|
||||
|
||||
function limitedAirframes.createCSAR(theUnit)
|
||||
-- override if autoCSAR is installed
|
||||
if autoCSAR then return end
|
||||
|
||||
-- only do this if we have installed CSAR Manager
|
||||
if csarManager and csarManager.createCSARforUnit then
|
||||
csarManager.createCSARforUnit(theUnit,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
messenger = {}
|
||||
messenger.version = "2.0.0"
|
||||
messenger.version = "2.0.1"
|
||||
messenger.verbose = false
|
||||
messenger.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@ -42,6 +42,7 @@ messenger.messengers = {}
|
||||
- messageError
|
||||
- unit
|
||||
- group
|
||||
2.0.1 - config optimization
|
||||
|
||||
--]]--
|
||||
|
||||
@ -480,7 +481,7 @@ function messenger.readConfigZone()
|
||||
if messenger.verbose then
|
||||
trigger.action.outText("+++msgr: NO config zone!", 30)
|
||||
end
|
||||
return
|
||||
theZone = cfxZones.createSimpleZone("messengerConfig")
|
||||
end
|
||||
|
||||
messenger.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
pulseFlags = {}
|
||||
pulseFlags.version = "1.3.0"
|
||||
pulseFlags.version = "1.3.1"
|
||||
pulseFlags.verbose = false
|
||||
pulseFlags.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@ -36,6 +36,7 @@ pulseFlags.requiredLibs = {
|
||||
- 1.2.3 deprecated paused/pulsePaused
|
||||
returned onStart, defaulting to true
|
||||
- 1.3.0 persistence
|
||||
- 1.3.1 typos corrected
|
||||
|
||||
--]]--
|
||||
|
||||
@ -326,7 +327,7 @@ function pulseFlags.loadData()
|
||||
local theData = persistence.getSavedDataForModule("pulseFlags")
|
||||
if not theData then
|
||||
if pulseFlags.verbose then
|
||||
trigger.action.outText("+++pulF Persistence: no save date received, skipping.", 30)
|
||||
trigger.action.outText("+++pulF Persistence: no save data received, skipping.", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
radioTrigger = {}
|
||||
radioTrigger.version = "1.0.0"
|
||||
radioTrigger.version = "1.0.1"
|
||||
radioTrigger.verbose = false
|
||||
radioTrigger.ups = 1
|
||||
radioTrigger.requiredLibs = {
|
||||
@ -11,6 +11,7 @@ radioTrigger.radioTriggers = {}
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - initial version
|
||||
1.0.1 - guarding rtOut to not bang on flags when not set
|
||||
|
||||
--]]--
|
||||
|
||||
@ -50,8 +51,9 @@ function radioTrigger.createRadioTriggerWithZone(theZone)
|
||||
end
|
||||
|
||||
-- out flag
|
||||
if cfxZones.hasProperty(theZone, "out!") then
|
||||
theZone.rtOutFlag = cfxZones.getStringFromZoneProperty(theZone, "out!", "*<none>")
|
||||
if cfxZones.hasProperty(theZone, "rtOut!") then
|
||||
elseif cfxZones.hasProperty(theZone, "rtOut!") then
|
||||
theZone.rtOutFlag = cfxZones.getStringFromZoneProperty(theZone, "rtOut!", "*<none>")
|
||||
end
|
||||
|
||||
@ -66,8 +68,9 @@ end
|
||||
--
|
||||
function radioTrigger.process(theZone)
|
||||
-- we are triggered, simply poll the out flag
|
||||
if theZone.rtOutFlag then
|
||||
cfxZones.pollFlag(theZone.rtOutFlag, theZone.rtMethod, theZone)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
|
||||
648
modules/williePete.lua
Normal file
648
modules/williePete.lua
Normal file
@ -0,0 +1,648 @@
|
||||
williePete = {}
|
||||
williePete.version = "1.0.0"
|
||||
williePete.ups = 10 -- we update at 10 fps, so accuracy of a
|
||||
-- mach two missile is within 33 meters, with interpolation even less
|
||||
|
||||
williePete.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
"cfxMX",
|
||||
}
|
||||
|
||||
williePete.willies = {}
|
||||
williePete.wpZones = {}
|
||||
williePete.playerGUIs = {} -- used for unit guis
|
||||
|
||||
williePete.smokeWeapons = {"HYDRA_70_M274","HYDRA_70_MK61","HYDRA_70_MK1","HYDRA_70_WTU1B","BDU_45B","BDU_33","BDU_45","BDU_45LGB","BDU_50HD","BDU_50LD","BDU_50LGB","C_8CM"}
|
||||
|
||||
function williePete.addWillie(theWillie)
|
||||
table.insert(williePete.willies, theWillie)
|
||||
end
|
||||
|
||||
function williePete.addWPZone(theZone)
|
||||
table.insert(williePete.wpZones, theZone)
|
||||
end
|
||||
|
||||
function williePete.closestCheckInTgtZoneForCoa(point, coa)
|
||||
-- returns the closest zone that point is inside.
|
||||
-- first tries directly, then, if none found,
|
||||
-- with added check-in radius
|
||||
local lPoint = {x=point.x, y=0, z=point.z}
|
||||
local currDelta = math.huge
|
||||
local closestZone = nil
|
||||
-- first, we try if outright inside
|
||||
for zName, zData in pairs(williePete.wpZones) do
|
||||
if zData.coalition == coa then
|
||||
-- local zPoint = cfxZones.getPoint(zData)
|
||||
local inZone, delta = cfxZones.isPointInsideZone(lPoint, zData)
|
||||
if inZone and (delta < currDelta) then
|
||||
currDelta = delta
|
||||
closestZone = zData
|
||||
end
|
||||
end
|
||||
end
|
||||
-- if we got one, we return that zone
|
||||
if closestZone then return closestZone, currDelta end
|
||||
|
||||
for zName, zData in pairs(williePete.wpZones) do
|
||||
if zData.coalition == coa then
|
||||
-- local zPoint = cfxZones.getPoint(zData)
|
||||
local inZone, delta = cfxZones.isPointInsideZone(lPoint, zData, zData.checkInRange)
|
||||
if inZone and (delta < currDelta) then
|
||||
currDelta = delta
|
||||
closestZone = zData
|
||||
end
|
||||
end
|
||||
end
|
||||
if closestZone then return closestZone, currDelta end
|
||||
|
||||
return nil, -1
|
||||
|
||||
end
|
||||
|
||||
function williePete.getClosestZoneForCoa(point, coa)
|
||||
local lPoint = {x=point.x, y=0, z=point.z}
|
||||
local currDelta = math.huge
|
||||
local closestZone = nil
|
||||
for zName, zData in pairs(williePete.wpZones) do
|
||||
if zData.coalition == coa then
|
||||
local zPoint = cfxZones.getPoint(zData)
|
||||
local delta = dcsCommon.dist(lPoint, zPoint) -- emulate flag compare
|
||||
if (delta < currDelta) then
|
||||
currDelta = delta
|
||||
closestZone = zData
|
||||
end
|
||||
else
|
||||
-- trigger.outText("Zone <" .. zData.name .. ">, coa <" .. zData.coalition .. "> does not match <" .. coa .. ">", 30)
|
||||
end
|
||||
end
|
||||
return closestZone, currDelta
|
||||
end
|
||||
|
||||
|
||||
function williePete.createWPZone(aZone)
|
||||
aZone.coalition = cfxZones.getCoalitionFromZoneProperty(aZone, "wpTarget", 0) -- side that marks it on map, and who fires arty
|
||||
aZone.shellStrength = cfxZones.getNumberFromZoneProperty(aZone, "shellStrength", 500) -- power of shells (strength)
|
||||
aZone.shellNum = cfxZones.getNumberFromZoneProperty(aZone, "shellNum", 17) -- number of shells in bombardment
|
||||
aZone.transitionTime = cfxZones.getNumberFromZoneProperty(aZone, "transitionTime", 20) -- average time of travel for projectiles
|
||||
aZone.coolDown = cfxZones.getNumberFromZoneProperty(aZone, "coolDown", 180) -- cooldown after arty fire, used to set readyTime
|
||||
aZone.baseAccuracy = cfxZones.getNumberFromZoneProperty(aZone, "baseAccuracy", 50)
|
||||
|
||||
aZone.readyTime = 0 -- if readyTime > now we are not ready
|
||||
aZone.trackingPlayer = nil -- name player's unit who is being tracked for wp. may not be neccessary
|
||||
aZone.checkedIn = {} -- dict of all planes currently checked in
|
||||
aZone.trackingEndsTime = 0 -- if now > trackingends, we remove player and send a message
|
||||
|
||||
aZone.wpTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "wpTriggerMethod", "change")
|
||||
|
||||
aZone.FACTypes = cfxZones.getStringFromZoneProperty(aZone, "facTypes", "all")
|
||||
|
||||
aZone.checkInRange = cfxZones.getNumberFromZoneProperty(aZone, "checkInRange", williePete.checkInRange) -- default to my default
|
||||
|
||||
aZone.ackSound = cfxZones.getStringFromZoneProperty(aZone, "ackSound", williePete.ackSound)
|
||||
aZone.guiSound = cfxZones.getStringFromZoneProperty(aZone, "guiSound", williePete.guiSound)
|
||||
|
||||
if cfxZones.hasProperty(aZone, "triggerMethod") then
|
||||
aZone.wpTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
|
||||
end
|
||||
|
||||
if aZone.verbose then
|
||||
trigger.action.outText("Added wpTarget zone <" .. aZone.name .. ">", 30)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- player management
|
||||
--
|
||||
function williePete.startPlayerGUI()
|
||||
-- scan all mx players
|
||||
-- note: currently assumes single-player groups
|
||||
-- in preparation of single-player 'commandForUnit'
|
||||
for uName, uData in pairs(cfxMX.playerUnitByName) do
|
||||
local unitInfo = {}
|
||||
-- try and access each unit even if we know that the
|
||||
-- unit does not exist in-game right now
|
||||
local gData = cfxMX.playerUnit2Group[uName]
|
||||
local gName = gData.name
|
||||
local coa = cfxMX.groupCoalitionByName[gName]
|
||||
local theType = uData.type
|
||||
|
||||
if williePete.verbose then
|
||||
trigger.action.outText("unit <" .. uName .. ">: type <" .. theType .. "> coa <" .. coa .. ">, group <" .. gName .. ">", 30)
|
||||
end
|
||||
|
||||
unitInfo.name = uName -- needed for reverse-lookup
|
||||
unitInfo.coa = coa
|
||||
unitInfo.gID = gData.groupId
|
||||
unitInfo.uID = uData.unitId
|
||||
unitInfo.theType = theType
|
||||
unitInfo.cat = cfxMX.groupTypeByName[gName]
|
||||
-- now check type against willie pete config for allowable types
|
||||
local pass = false
|
||||
for idx, aType in pairs(williePete.facTypes) do
|
||||
if aType == "ALL" then pass = true end
|
||||
if aType == "ANY" then pass = true end
|
||||
if aType == theType then pass = true end
|
||||
if dcsCommon.stringStartsWith(aType, "HEL") and unitInfo.cat == "helicopter" then pass = true end
|
||||
if dcsCommon.stringStartsWith(aType, "PLAN") and unitInfo.cat == "plane" then pass = true end
|
||||
end
|
||||
|
||||
if pass then -- we install a menu for this group
|
||||
-- we may not want check in stuff, but it could be cool
|
||||
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, "FAC")
|
||||
unitInfo.checkIn = missionCommands.addCommandForGroup(unitInfo.gID, "Check In", unitInfo.root, williePete.redirectCheckIn, unitInfo)
|
||||
end
|
||||
|
||||
-- store it
|
||||
williePete.playerGUIs[uName] = unitInfo
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- BOOM
|
||||
--
|
||||
|
||||
--
|
||||
-- BOOM command
|
||||
--
|
||||
function williePete.doBoom(args)
|
||||
trigger.action.explosion(args.point, args.strength)
|
||||
data = {}
|
||||
data.point = args.point
|
||||
data.strength = args.strength
|
||||
-- cfxArtilleryZones.invokeCallbacksFor('impact', args.zone, data)
|
||||
end
|
||||
|
||||
function williePete.doParametricFireAt(aPoint, accuracy, shellNum, shellBaseStrength, shellVariance, transitionTime)
|
||||
if williePete.verbose then
|
||||
trigger.action.outText("fire with accuracy <" .. accuracy .. "> shellNum <" .. shellNum .. "> baseStren <" .. shellBaseStrength .. "> variance <" .. shellVariance .. ">, ttime <" .. transitionTime .. ">", 30)
|
||||
end
|
||||
|
||||
-- accuracy is meters from center
|
||||
if not aPoint then return end
|
||||
if not accuracy then accuracy = 100 end
|
||||
if not shellNum then shellNum = 17 end
|
||||
if not shellBaseStrength then shellBaseStrength = 500 end
|
||||
if not shellVariance then shellVariance = 0.2 end
|
||||
if not transitionTime then transitionTime = 17 end
|
||||
|
||||
local alt = land.getHeight({x=aPoint.x, y=aPoint.z})
|
||||
local center = {x=aPoint.x, y=alt, z=aPoint.z}
|
||||
|
||||
for i=1, shellNum do
|
||||
local thePoint = dcsCommon.randomPointInCircle(accuracy, 0, center.x, center.z)
|
||||
thePoint.y = land.getHeight({x=thePoint.x, y=thePoint.z})
|
||||
local boomArgs = {}
|
||||
local strVar = shellBaseStrength * shellVariance
|
||||
strVar = strVar * (2 * dcsCommon.randomPercent() - 1.0) -- go from -1 to 1
|
||||
|
||||
boomArgs.strength = shellBaseStrength + strVar
|
||||
thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z}) + 1 -- elevate to ground height + 1
|
||||
boomArgs.point = thePoint
|
||||
boomArgs.zone = aZone
|
||||
local timeVar = 5 * (2 * dcsCommon.randomPercent() - 1.0) -- +/- 1.5 seconds
|
||||
if timeVar < 0 then timeVar = -timeVar end
|
||||
|
||||
timer.scheduleFunction(williePete.doBoom, boomArgs, timer.getTime() + transitionTime + timeVar)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- COMMS
|
||||
--
|
||||
|
||||
function williePete.redirectCheckIn(unitInfo)
|
||||
timer.scheduleFunction(williePete.doCheckIn, unitInfo, timer.getTime() + 0.1)
|
||||
end
|
||||
|
||||
function williePete.doCheckIn(unitInfo)
|
||||
--trigger.action.outText("check-in received", 30)
|
||||
local theUnit = Unit.getByName(unitInfo.name)
|
||||
local p = theUnit:getPoint()
|
||||
local theZone, dist = williePete.closestCheckInTgtZoneForCoa(p, unitInfo.coa)
|
||||
|
||||
if not theZone then
|
||||
theZone, dist = williePete.getClosestZoneForCoa(p, unitInfo.coa)
|
||||
if not theZone then
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "No target zone in range.", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
|
||||
return
|
||||
end
|
||||
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Too far from target zone, closest target zone is " .. theZone.name, 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
|
||||
return
|
||||
end
|
||||
|
||||
-- we are now checked in to zone -- unless we are already checked in
|
||||
if theZone.checkedIn[unitInfo.name] then
|
||||
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.name .. ", " .. theZone.name .. ", we heard you the first time, proceed.", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
|
||||
return
|
||||
end
|
||||
|
||||
-- we now check in
|
||||
theZone.checkedIn[unitInfo.name] = unitInfo
|
||||
|
||||
-- add the 'Target marked' menu
|
||||
unitInfo.targetMarked = missionCommands.addCommandForGroup(unitInfo.gID, "Target Marked", unitInfo.root, williePete.redirectTargetMarked, unitInfo)
|
||||
-- remove 'check in'
|
||||
missionCommands.removeItemForGroup(unitInfo.gID, unitInfo.checkIn)
|
||||
unitInfo.checkIn = nil
|
||||
-- add 'check out'
|
||||
unitInfo.checkOut = missionCommands.addCommandForGroup(unitInfo.gID, "Check Out", unitInfo.root, williePete.redirectCheckOut, unitInfo)
|
||||
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Roger " .. unitInfo.name .. ", " .. theZone.name .. " tracks you, standing by for target data.", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, theZone.guiSound)
|
||||
end
|
||||
|
||||
function williePete.redirectCheckOut(unitInfo)
|
||||
timer.scheduleFunction(williePete.doCheckOut, unitInfo, timer.getTime() + 0.1)
|
||||
end
|
||||
|
||||
function williePete.doCheckOut(unitInfo)
|
||||
--trigger.action.outText("check-out received", 30)
|
||||
|
||||
-- check out of all zones
|
||||
local wasCheckedIn = false
|
||||
local fromZone = ""
|
||||
for idx, theZone in pairs(williePete.wpZones) do
|
||||
if theZone.checkedIn[unitInfo.name] then
|
||||
wasCheckedIn = true
|
||||
fromZone = theZone.name
|
||||
end
|
||||
theZone.checkedIn[unitInfo.name] = nil
|
||||
end
|
||||
if not wasCheckedIn then
|
||||
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.name .. ", roger cecked-out. Good hunting!", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
|
||||
else
|
||||
trigger.action.outTextForGroup(unitInfo.gID, unitInfo.name .. "has checked out of " .. fromZone ..".", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
|
||||
end
|
||||
|
||||
-- remove checkOut and targetMarked
|
||||
missionCommands.removeItemForGroup(unitInfo.gID, unitInfo.checkOut)
|
||||
unitInfo.checkOut = nil
|
||||
missionCommands.removeItemForGroup(unitInfo.gID, unitInfo.targetMarked)
|
||||
unitInfo.targetMarked = nil
|
||||
|
||||
-- add check in
|
||||
unitInfo.checkIn = missionCommands.addCommandForGroup(unitInfo.gID, "Check In", unitInfo.root, williePete.redirectCheckIn, unitInfo)
|
||||
end
|
||||
|
||||
|
||||
function williePete.redirectTargetMarked(unitInfo)
|
||||
timer.scheduleFunction(williePete.doTargetMarked, unitInfo, timer.getTime() + 0.1)
|
||||
end
|
||||
|
||||
function williePete.rogerDodger(args)
|
||||
local unitInfo = args[1]
|
||||
local theZone = args[2]
|
||||
|
||||
trigger.action.outTextForCoalition(unitInfo.coa, "Roger " .. unitInfo.name .. ", good copy, firing.", 30)
|
||||
trigger.action.outSoundForCoalition(unitInfo.coa, theZone.ackSound)
|
||||
end
|
||||
|
||||
function williePete.doTargetMarked(unitInfo)
|
||||
--trigger.action.outText("mark received", 30)
|
||||
-- first, check if we are past the time-out
|
||||
local now = timer.getTime()
|
||||
|
||||
-- now check if zone matches check-in
|
||||
if not unitInfo.expiryTime or unitInfo.expiryTime < now then
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Target mark stale or ambiguous, set fresh mark", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
|
||||
return
|
||||
end
|
||||
|
||||
-- now, check if the zone is ready to receive
|
||||
if not unitInfo.wpInZone or not unitInfo.pos then
|
||||
-- should not happen, but better safe than sorry
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Lost sight of target location, set new mark", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
|
||||
return
|
||||
end
|
||||
|
||||
local tgtZone = unitInfo.wpInZone
|
||||
-- see if we are checked into that zone
|
||||
if not tgtZone.checkedIn[unitInfo.name] then
|
||||
-- zones don't match
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Say again " .. unitInfo.name .. ", we have crosstalk. Try and reset coms", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, williePete.guiSound)
|
||||
return
|
||||
end
|
||||
|
||||
-- see if zone is ready to receive
|
||||
local timeRemaining = math.floor(tgtZone.readyTime - now)
|
||||
if timeRemaining > 0 then
|
||||
-- zone not ready
|
||||
trigger.action.outTextForGroup(unitInfo.gID, "Stand by " .. unitInfo.name .. ", artillery not ready. Expect " .. timeRemaining + math.random(1, 5) .. " seconds.", 30)
|
||||
trigger.action.outSoundForGroup(unitInfo.gID, tgtZone.guiSound)
|
||||
return
|
||||
end
|
||||
|
||||
-- if we get here, we are fire at mark
|
||||
local alt = math.floor(land.getHeight({x = unitInfo.pos.x, y = unitInfo.pos.z}))
|
||||
local grid = coord.LLtoMGRS(coord.LOtoLL(unitInfo.pos))
|
||||
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
|
||||
local theLoc = mgrs
|
||||
trigger.action.outTextForCoalition(unitInfo.coa, tgtZone.name ..", " .. unitInfo.name .." is transmitting target location. Fire at " .. theLoc .. ", elevation " .. alt .. " meters, target marked.", 30)
|
||||
trigger.action.outSoundForCoalition(unitInfo.coa, tgtZone.guiSound)
|
||||
timer.scheduleFunction(williePete.rogerDodger, {unitInfo, tgtZone},timer.getTime() + math.random(2, 5))
|
||||
|
||||
-- collect zone's fire params & fire
|
||||
local shellStrength = tgtZone.shellStrength
|
||||
local shellNum = tgtZone.shellNum
|
||||
local transitionTime = tgtZone.transitionTime
|
||||
local accuracy = tgtZone.baseAccuracy
|
||||
|
||||
williePete.doParametricFireAt(unitInfo.pos, accuracy, shellNum, shellStrength, 0.2, transitionTime)
|
||||
|
||||
-- set zone's cooldown
|
||||
tgtZone.readyTime = now + tgtZone.coolDown
|
||||
-- erase player's wp mark
|
||||
unitInfo.wpInZone = nil
|
||||
unitInfo.pos = nil
|
||||
end
|
||||
-- return true if a zone is actively tracking theUnit to place
|
||||
-- a wp
|
||||
|
||||
function williePete.zoneIsTracking(theUnit)
|
||||
local uName = theUnit:getName()
|
||||
for idx, theZone in pairs(williePete.wpZones) do
|
||||
if theZone.checkedIn[uName] then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function williePete.isWP(theWeapon)
|
||||
local theDesc = theWeapon:getTypeName()
|
||||
for idx, wpw in pairs(williePete.smokeWeapons) do
|
||||
if theDesc == wpw then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function williePete:onEvent(event)
|
||||
if not event.initiator then
|
||||
--trigger.action.outText("onEvent - " .. event.id .. ": no initiator",30)
|
||||
return
|
||||
end
|
||||
if not event.weapon then
|
||||
--trigger.action.outText("onEvent - " .. event.id .. ": no WEAPON",30)
|
||||
return
|
||||
end
|
||||
|
||||
local theUnit = event.initiator
|
||||
local pType = "(AI)"
|
||||
if theUnit.getPlayerName then pType = "(" .. theUnit:getName() .. ")" end
|
||||
|
||||
if event.id == 1 then -- S_EVENT_SHOT
|
||||
-- initiator is who fired. maybe want to test if player
|
||||
|
||||
--trigger.action.outText(theUnit:getName() .. " " .. pType .. " fired " .. event.weapon:getTypeName() .. ".", 30)
|
||||
|
||||
if not williePete.isWP(event.weapon) then
|
||||
--trigger.action.outText("<" .. event.weapon:getTypeName() .. "> not a smoke weapon", 30)
|
||||
return
|
||||
end
|
||||
|
||||
-- make sure that whoever fired it is being tracked by
|
||||
-- a zone
|
||||
if not williePete.zoneIsTracking(theUnit) then
|
||||
--trigger.action.outText("<" .. event.weapon:getTypeName() .. "> fired while not being tracked by zone", 30)
|
||||
return
|
||||
end
|
||||
|
||||
-- assuming it's a willie, let's track it
|
||||
local theWillie = {}
|
||||
theWillie.firedBy = theUnit:getName()
|
||||
theWillie.theUnit = theUnit
|
||||
theWillie.weapon = event.weapon
|
||||
theWillie.wt = theWillie.weapon:getTypeName()
|
||||
theWillie.pos = theWillie.weapon:getPoint()
|
||||
theWillie.v = theWillie.weapon:getVelocity()
|
||||
|
||||
williePete.addWillie(theWillie)
|
||||
end
|
||||
|
||||
if event.id == 2 then -- hit
|
||||
local what = "something"
|
||||
if event.target then what = event.target:getName() end
|
||||
--trigger.action.outText("Weapon " .. event.weapon:getTypeName() .. " fired by unit ".. theUnit:getName() .. " " .. pType .. " hit " .. what, 30)
|
||||
-- may need to remove willie from willies
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- test if a projectile hit ground inside a wp zone
|
||||
function williePete.isInside(theWillie)
|
||||
local thePoint = theWillie.pos
|
||||
local theUnitName = theWillie.firedBy -- may be dead already, but who cares
|
||||
local theUnit = Unit.getByName(theUnitName)
|
||||
if not theUnit then return false end -- unit dead
|
||||
if not Unit.isExist(theUnit) then return false end -- dito
|
||||
|
||||
local thePlayer = williePete.playerGUIs[theUnitName]
|
||||
if not thePlayer then return nil end
|
||||
for idx, theZone in pairs(williePete.wpZones) do
|
||||
if cfxZones.pointInZone(thePoint, theZone) then
|
||||
-- we are inside. but is this the right coalition?
|
||||
if thePlayer.coa == theZone.coalition then
|
||||
--trigger.action.outText("Willie in " .. theZone.name, 30)
|
||||
return theZone
|
||||
else
|
||||
--trigger.action.outText("Willie wrong coa", 30)
|
||||
end
|
||||
-- if we want to allow neutral zones (doens't make sense)
|
||||
-- add another guard below
|
||||
else
|
||||
--trigger.action.outText("willie outside " .. theZone.name, 30)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- update
|
||||
|
||||
function williePete.projectileHit(theWillie)
|
||||
-- interpolate pos: half time between updates times last velocity
|
||||
local vmod = dcsCommon.vMultScalar(theWillie.v, 0.5 / williePete.ups)
|
||||
theWillie.pos = dcsCommon.vAdd(theWillie.pos, vmod)
|
||||
--trigger.action.outText("Willie " .. theWillie.wt .. " expired at " .. dcsCommon.point2text(theWillie.pos) .. " interpolated.", 30)
|
||||
|
||||
-- reset last mark for player
|
||||
local thePlayer = williePete.playerGUIs[theWillie.firedBy]
|
||||
thePlayer.pos = nil
|
||||
thePlayer.wpInZone = nil
|
||||
|
||||
-- check if this is within a wpZones
|
||||
local theZone = williePete.isInside(theWillie)
|
||||
if not theZone then
|
||||
if williePete.verbose then
|
||||
trigger.action.outText("+++wp: wp expired outside zone", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- if we receive a zone, we know that the player's
|
||||
-- coalition matches the one of the zone
|
||||
thePlayer.expiryTime = timer.getTime() + williePete.wpMaxTime -- set timeout in which player can give fire command
|
||||
thePlayer.pos = theWillie.pos -- remember the loc
|
||||
thePlayer.wpInZone = theZone -- remember the zone
|
||||
|
||||
-- mark point with smoke blue
|
||||
--dcsCommon.markPointWithSmoke(theWillie.pos, 4)
|
||||
|
||||
if cfxArtilleryZones then
|
||||
--cfxArtilleryZones.doParametricFireAt(theWillie.pos, 50, 10)
|
||||
else
|
||||
-- mark point with smoke blue
|
||||
--dcsCommon.markPointWithSmoke(theWillie.pos, 4)
|
||||
end
|
||||
end
|
||||
|
||||
function williePete.updateWP()
|
||||
timer.scheduleFunction(williePete.updateWP, {}, timer.getTime() + 1/williePete.ups)
|
||||
|
||||
local nextPete = {}
|
||||
for idx, theWillie in pairs(williePete.willies) do
|
||||
-- check if it still exists
|
||||
if Weapon.isExist(theWillie.weapon) then
|
||||
-- update loc, proceed to next round
|
||||
theWillie.pos = theWillie.weapon:getPoint()
|
||||
theWillie.v = theWillie.weapon:getVelocity()
|
||||
table.insert(nextPete, theWillie)
|
||||
else
|
||||
-- weapon disappeared: it has hit something
|
||||
-- but unguided rockets do not create an event for that
|
||||
williePete.projectileHit(theWillie)
|
||||
-- no longer propagates to next round
|
||||
end
|
||||
end
|
||||
williePete.willies = nextPete
|
||||
end
|
||||
|
||||
function williePete.playerUpdate()
|
||||
timer.scheduleFunction(williePete.playerUpdate, {}, timer.getTime() + 2) -- check 30 times a minute
|
||||
-- zone still checked in updates for zones
|
||||
for idx, theZone in pairs(williePete.wpZones) do
|
||||
-- make sure any unit checked in is still inside
|
||||
-- the zone that they checked in, or they are checked out
|
||||
--local zp = cfxZones.getPoint(theZone)
|
||||
for idy, unitInfo in pairs(theZone.checkedIn) do
|
||||
-- make sure unit still exists
|
||||
local dropUnit = true
|
||||
local theUnit = Unit.getByName(unitInfo.name)
|
||||
if theUnit and Unit.isExist(theUnit) then
|
||||
local up = theUnit:getPoint()
|
||||
up.y = 0
|
||||
local isInside, dist = cfxZones.isPointInsideZone(up, theZone, theZone.checkInRange)
|
||||
|
||||
if isInside then
|
||||
dropUnit = false
|
||||
end
|
||||
end
|
||||
if dropUnit then
|
||||
-- remove from zone check-in
|
||||
-- williePete.doCheckOut(unitInfo)
|
||||
timer.scheduleFunction(williePete.doCheckOut, unitInfo, timer.getTime() + 0.1) -- to not muck up iteration
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- menu updates for all players
|
||||
end
|
||||
|
||||
--
|
||||
-- Config & Start
|
||||
--
|
||||
function williePete.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("wpConfig")
|
||||
if not theZone then
|
||||
if williePete.verbose then
|
||||
trigger.action.outText("+++wp: NO config zone!", 30)
|
||||
end
|
||||
theZone = cfxZones.createSimpleZone("wpConfig")
|
||||
end
|
||||
|
||||
local facTypes = cfxZones.getStringFromZoneProperty(theZone, "facTypes", "all")
|
||||
facTypes = string.upper(facTypes)
|
||||
|
||||
-- make this an array
|
||||
local allTypes = {}
|
||||
if dcsCommon.containsString(facTypes, ",") then
|
||||
allTypes = dcsCommon.splitString(facTypes, ",")
|
||||
else
|
||||
table.insert(allTypes, facTypes)
|
||||
end
|
||||
williePete.facTypes = dcsCommon.trimArray(allTypes)
|
||||
|
||||
-- how long a wp is active. must not be more than 5 minutes
|
||||
williePete.wpMaxTime = cfxZones.getNumberFromZoneProperty(theZone, "wpMaxTime", 3 * 60)
|
||||
|
||||
-- default check-in range, added to target zone's range and used
|
||||
-- for auto-check-out
|
||||
williePete.checkInRange = cfxZones.getNumberFromZoneProperty(theZone, "checkInRange", 10000) -- 10 km outside
|
||||
|
||||
williePete.ackSound = cfxZones.getStringFromZoneProperty(theZone, "ackSound", "some")
|
||||
williePete.guiSound = cfxZones.getStringFromZoneProperty(theZone, "guiSound", "some")
|
||||
|
||||
williePete.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
|
||||
if williePete.verbose then
|
||||
trigger.action.outText("+++msgr: read config", 30)
|
||||
end
|
||||
end
|
||||
|
||||
function williePete.start()
|
||||
if not dcsCommon.libCheck("cfx williePete",
|
||||
williePete.requiredLibs) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- read config
|
||||
williePete.readConfigZone()
|
||||
|
||||
-- collect all wp target zones
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("wpTarget")
|
||||
|
||||
for k, aZone in pairs(attrZones) do
|
||||
williePete.createWPZone(aZone) -- process attribute and add to zone
|
||||
williePete.addWPZone(aZone) -- remember it so we can smoke it
|
||||
end
|
||||
|
||||
-- add event handler
|
||||
world.addEventHandler(williePete)
|
||||
|
||||
-- initialize all players from MX
|
||||
williePete.startPlayerGUI()
|
||||
|
||||
-- start updates
|
||||
williePete.updateWP() -- for tracking wp, at ups
|
||||
williePete.playerUpdate() -- for tracking players, at 1/s
|
||||
|
||||
|
||||
trigger.action.outText("williePete v" .. williePete.version .. " loaded.", 30)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- let's go
|
||||
if not williePete.start() then
|
||||
trigger.action.outText("cf/x Willie Pete aborted: missing libraries", 30)
|
||||
williePete = nil
|
||||
end
|
||||
|
||||
--[[--
|
||||
Mechanics:
|
||||
- unit checks in with arty zone. if not in range of arty zone + safe dist, error 'not in range' is returned, else <artillery zone: status> is sent. <status can be ready, firing, or reloading>. Zone will advise on status change when checked in.
|
||||
- if unit leaves arty zone + safe dist, <leaving zone> is displayed and <checkout> is invoked
|
||||
- unit can check out any time
|
||||
- when checked in, any wp hitting the ground is remembered if still inside target zone
|
||||
- player then gives 'target marked'
|
||||
- if artillery on cooldown, or wp not inside zone error, else fire sequence starts, and cooldown starts for entire zone
|
||||
|
||||
--]]--
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user