Version 2.0.2

New scribe, names, better csarManager
This commit is contained in:
Christian Franz 2024-02-08 13:04:35 +01:00
parent 38d6487de7
commit 61f33561fc
14 changed files with 2139 additions and 150 deletions

Binary file not shown.

Binary file not shown.

View File

@ -18,7 +18,8 @@ VERSION HISTORY
interpolate hits on dead when looking at kills and projectile does
not exist
also sampling kill events
1.1.1 - fixed reading smoke color for zone
minor clean-up
--]]--
bombRange.bombs = {} -- live tracking
bombRange.collector = {} -- post-impact collections for 0.5 secs
@ -63,6 +64,7 @@ function bombRange.createRange(theZone) -- has bombRange attribte to mark it
theZone.reportName = theZone:getBoolFromZoneProperty("reportName", false)
theZone.smokeHits = theZone:getBoolFromZoneProperty("smokeHits", false)
theZone.smokeColor = theZone:getSmokeColorStringFromZoneProperty("smokeColor", "blue")
theZone.smokeColor = dcsCommon.smokeColor2Num(theZone.smokeColor)
theZone.flagHits = theZone:getBoolFromZoneProperty("flagHits", false)
theZone.flagType = theZone:getStringFromZoneProperty("flagType", "Red_Flag")
theZone.clipDist = theZone:getNumberFromZoneProperty("clipDist", 2000) -- when further way, the drop will be disregarded
@ -99,7 +101,6 @@ function bombRange.getPlayerData(name)
theData.totalHits = 0
theData.totalPercentage = 0 -- sum, must be divided by drops
bombRange.playerData[name] = theData
-- trigger.action.outText("created new player data for " .. name, 30)
end
return theData
end
@ -174,12 +175,11 @@ function bombRange.showStatsForPlayer(pName, gID, unitName)
end
trigger.action.outTextForGroup(gID, msg, 30)
end
--
-- unit UI
--
function bombRange.initCommsForUnit(theUnit)
local uName = theUnit:getName()
local pName = theUnit:getPlayerName()
@ -285,7 +285,6 @@ function bombRange.suspectedHit(weapon, target)
bombRange.impacted(b, target) -- use this for impact
theID = b.ID
hasfound = true
-- trigger.action.outText("susHit: filtering COLLECTED b <" .. b.name .. ">", 30)
end
end
if hasfound then
@ -305,8 +304,6 @@ function bombRange.suspectedHit(weapon, target)
b.pos = weapon:getPoint()
b.v = weapon:getVelocity()
bombRange.impacted(b, target)
-- trigger.action.outText("susHit: filtering live b <" .. b.name .. ">", 30)
else
table.insert(filtered, b)
end
@ -363,7 +360,6 @@ function bombRange.suspectedKill(target)
end
bombRange.bombs = filtered
if hasfound then
-- trigger.action.outText("protocol: removed LIVING weapon from roster after impacted() invocation for non-nil target in suspectedKill", 30)
return
end
@ -383,7 +379,6 @@ function bombRange.suspectedKill(target)
end
if hasfound then -- remove from collector, hit attributed
bombRange.collector[theID] = nil -- remove from collector
-- trigger.action.outText("protocol: removed COLL weapon from roster after impacted() invocation for non-nil target in suspectedKill", 30)
return
end
end
@ -478,14 +473,8 @@ function bombRange.impacted(weapon, target, finalPass)
if not targetName then targetName = target:getTypeName() end
end
-- local s = "Entering impacted() with weapon = <" .. weapon.name .. ">"
-- if target then
-- s = s .. " AND target = <" .. targetName .. ">"
-- end
-- when we enter, weapon has ipacted target - if target is non-nil
-- what we need to determine is if that target is inside a zone
local ipos = weapon.pos -- default to weapon location
if target then
ipos = target:getPoint() -- we make the target loc the impact point
@ -542,32 +531,7 @@ function bombRange.impacted(weapon, target, finalPass)
end
local impactInside = theRange:pointInZone(ipos)
--[[--
if target and (not impactInside) then
trigger.action.outText("Hit on target <" .. targetName .. "> outside of zone <" .. theRange.name .. ">. should exit unless final impact", 30)
-- find closest range to object that was hit
local closest = nil
local shortest = math.huge
local tp = target:getPoint()
for idx, aRange in pairs(bombRange.ranges) do
local zp = aRange:getPoint()
local zDist = dcsCommon.distFlat(zp, tp)
if zDist < shortest then
shortest = zDist
closest = aRange
end
end
trigger.action.outText("re-check: closest range to target now is <" .. closest.name ..">", 30)
if closest:pointInZone(tp) then
trigger.action.outText("target <" .. targetName .. "> is INSIDE this range, d = <" .. math.floor(shortest) .. ">", 30)
else
trigger.action.outText("targed indeed outside, d = <" .. math.floor(shortest) .. ">", 30)
end
if finalPass then trigger.action.outText("IS final pass.", 30) end
end
--]]--
if theRange.reporter and theRange.details then
local ipc = weapon.impacted
if not ipc then ipc = timer.getTime() end
@ -605,7 +569,6 @@ function bombRange.impacted(weapon, target, finalPass)
bombRange.addImpactForWeapon(weapon, true, percentage)
else
msg = "Outside target area"
-- if target then msg = msg .. " (EVEN THOUGH TGT = " .. target:getName() .. ")" end
if theRange.reportName then msg = msg .. " " .. theRange.name end
if theRange.details then msg = msg .. " (off-center by " .. math.floor(minDist *10)/10 .. " m)" end
msg = msg .. ", no hit."
@ -623,7 +586,6 @@ function bombRange.uncollect(theID)
if b then
bombRange.collector[theID] = nil
bombRange.impacted(b, nil, true) -- final pass
-- trigger.action.outText("(final impact)", 30)
end
end
@ -640,7 +602,6 @@ function bombRange.updateBombs()
else
-- put on collector to time out in 1 seconds to allow
-- asynch hits to still register for this weapon in MP
-- bombRange.impacted(theWeapon)
theWeapon.impacted = timer.getTime()
bombRange.collector[theWeapon.ID] = theWeapon --
timer.scheduleFunction(bombRange.uncollect, theWeapon.ID, timer.getTime() + 1)
@ -766,6 +727,6 @@ if not bombRange.start() then
bombRange = nil
end
--
-- To Do:
-- add persistence
--

View File

@ -1,5 +1,5 @@
cfxMX = {}
cfxMX.version = "2.0.0"
cfxMX.version = "2.0.1"
cfxMX.verbose = false
--[[--
Mission data decoder. Access to ME-built mission structures
@ -11,6 +11,8 @@ cfxMX.verbose = false
- train carve-outs for vehicles
2.0.0 - clean-up
- harmonized with cfxGroups
2.0.1 - groupHotByName
--]]--
cfxMX.groupNamesByID = {}
@ -19,6 +21,7 @@ cfxMX.unitIDbyName = {}
cfxMX.groupDataByName = {}
cfxMX.groupTypeByName = {} -- category of group: "helicopter", "plane", "ship"...
cfxMX.groupCoalitionByName = {}
cfxMX.groupHotByName = {}
cfxMX.countryByName ={} -- county of group named
cfxMX.linkByName = {}
cfxMX.allFixedByName = {}
@ -205,10 +208,17 @@ function cfxMX.createCrossReferences()
local aID = group_data.groupId
-- get linkUnit info if it exists
local linkUnit = nil
local isHot = false
if group_data and group_data.route and group_data.route and group_data.route.points[1] then
linkUnit = group_data.route.points[1].linkUnit
cfxMX.linkByName[aName] = linkUnit
local action = group_data.route.points[1].action
if action then
isHot = dcsCommon.stringEndsWith(action, "Hot")
end
end
cfxMX.groupHotByName[aName] = isHot
if group_data.units[1] and group_data.units[1].type == "Train" then
category = "train"
obj_type_name = "train"

View File

@ -1,5 +1,5 @@
csarManager = {}
csarManager.version = "3.0.0"
csarManager.version = "3.1.0"
csarManager.ups = 1
--[[-- VERSION HISTORY
@ -18,9 +18,19 @@ csarManager.ups = 1
- new event manager
- no longer single-proccing pilots
- can also handle aircraft - isTroopCarrier
- 3.1.0 - integration with scribe
- expanded internal API: newMissionCB, invoked at addMission
- expanded internal API: removedMissionCB
- added *rnd as option for csarName (requires "names")
- missions are sorted by distance
- mission timeLimit range implemented
- update handles time limit (pickup only)
- inflight status reflects time limit but will not time out
- pickupSound option
- lostSound option
INTEGRATES AUTOMATICALLY WITH playerScore IF INSTALLED
INTEGRATES WITH LIMITE AIRFRAMES IF INSTALLED
INTEGRATES WITH LIMITED AIRFRAMES IF INSTALLED
--]]--
-- modules that need to be loaded BEFORE I run
@ -55,7 +65,9 @@ csarManager.vectoring = true -- provide bearing and range
-- callbacks
--
csarManager.csarCompleteCB = {}
csarManager.csarCreatedCB = {}
csarManager.csarRemoveCB = {}
csarManager.csarPickupCB = {}
--
-- CREATING A CSAR
--
@ -84,7 +96,7 @@ function csarManager.createDownedPilot(theMission, existingUnit)
aLocation.z = newTargetZone.point.z
aHeading = math.random(360)/360 * 2 * 3.1415
end
theMission.locations = {}
local theBoyGroup = dcsCommon.createSingleUnitGroup(theMission.name,
"Soldier M4 GRG", -- "Soldier M4 GRG",
aLocation.x,
@ -96,12 +108,17 @@ function csarManager.createDownedPilot(theMission, existingUnit)
Group.Category.GROUND,
theBoyGroup)
if theBoyGroup then
table.insert(theMission.locations, aLocation)
else
trigger.action.outText("+++csar: FAILED to create csar!", 30)
end
else
theMission.group = existingUnit:getGroup()
local allUnits = theMission.group:getUnits()
for idx, aUnit in pairs(allUnits) do
local loc = aUnit:getPoint() -- warning: won't work if group newly allocated!
table.insert(theMission.locations, loc)
end
end
-- we now use commands to send radio transmissions
@ -117,10 +134,19 @@ end
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 timeLimit then timeLimit = -1 end
if not point then return nil end
local newMission = {}
newMission.side = theSide
-- if "names" module active, allow random names if name
-- equals "*rnd"
if names and names.uniqueFullName and name == "*rnd" then
name = names.uniqueFullName()
elseif name == "*rnd" then
trigger.action.outText("+++scar: '*rnd' name requires the 'name' module for randomization. Using 'John Smith'", 30)
name = "John Smith"
end
if dcsCommon.stringStartsWith(name, "downed ") then
-- remove "downed" - it will be added again later
name = dcsCommon.removePrefix(name, "downed ")
@ -129,7 +155,7 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
end
end
if not inRadius then inRadius = csarManager.rescueRadius end
newMission.name = name .. "-" .. csarManager.missionID -- make it uuid-capable
newMission.name = name .. " (ID#" .. csarManager.missionID .. ")" -- make it uuid-capable
if csarManager.addPrefix then
newMission.name = "downed " .. newMission.name
end
@ -145,22 +171,40 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew,
-- allocate units
csarManager.createDownedPilot(newMission, parashootUnit)
newMission.timeStamp = timer.getTime() -- now
-- set timeLimit if enabled
if timeLimit then
local theLimit = cfxZones.randomDelayFromPositiveRange(timeLimit[1], timeLimit[2]) * 60
-- trigger.action.outText("set time limit for mission to "..theLimit, 30)
newMission.expires = timer.getTime() + theLimit
end
-- update counter and return
csarManager.missionID = csarManager.missionID + 1
return newMission
end
function csarManager.addMission(theMission)
-- trigger.action.outText("enter addMission", 30)
table.insert(csarManager.openMissions, theMission)
csarManager.invokeNewMissionCallbacks(theMission)
end
function csarManager.removeMission(theMission)
function csarManager.removeMission(theMission, pickup)
if not pickup then pickup = false end
-- invoked when evacuee is PICKED UP!
if not theMission then return end
local newMissions = {}
for idx, aMission in pairs (csarManager.openMissions) do
if aMission ~= theMission then
table.insert(newMissions, aMission)
else
-- csarManager.invokeRemovedMissionCallbacks(theMission)
if pickup then
csarManager.invokePickUpCallbacks(theMission)
end
end
end
csarManager.openMissions = newMissions -- this is the new batch
@ -291,15 +335,24 @@ function csarManager.successMission(who, where, theMission)
end
end
-- scribe.integration
if scribe then
local theUnit = Unit.getByName(who)
if theUnit and theUnit.getPlayerName then
local pName = theUnit:getPlayerName()
scribe.playerRescueComplete(pName)
end
end
trigger.action.outTextForCoalition(theMission.side,
who .. " successfully evacuated " .. theMission.name .. " to " .. where .. "!",
30)
-- now call callback for coalition side
-- callback has format callback(coalition, success true/false, numberSaved, descriptionText)
csarManager.invokeCallbacks(theMission.side, true, 1, "success")
-- callback has format callback(coalition, success true/false, numberSaved, descriptionText, theMission)
csarManager.invokeCallbacks(theMission.side, true, 1, "success", theMission)
trigger.action.outSoundForCoalition(theMission.side, csarManager.actionSound) -- "Quest Snare 3.wav")
trigger.action.outSoundForCoalition(theMission.side, csarManager.successSound)
if csarManager.csarRedDelivered and theMission.side == 1 then
cfxZones.pollFlag(csarManager.csarRedDelivered, "inc", csarManager.configZone)
@ -324,7 +377,6 @@ function csarManager.heloLanded(theUnit)
conf.unit = theUnit
local theGroup = theUnit:getGroup()
conf.id = theGroup:getID()
--conf.id = theUnit:getID()
conf.currentState = 0
local thePoint = theUnit:getPoint()
local mySide = theUnit:getCoalition()
@ -435,7 +487,7 @@ function csarManager.heloLanded(theUnit)
args.unitName = myName
timer.scheduleFunction(csarManager.asynchSuccess, args, timer.getTime() + 3)
csarManager.removeMission(theMission)
csarManager.removeMission(theMission, true) -- picked up
table.insert(conf.troopsOnBoard, theMission)
theMission.group:destroy() -- will shut up radio as well
theMission.group = nil
@ -466,7 +518,7 @@ function csarManager.asynchSuccess(args)
end
function csarManager.asynchSound(args)
trigger.action.outSoundForCoalition(args.mySide, csarManager.actionSound)
trigger.action.outSoundForCoalition(args.mySide, csarManager.pickupSound)
end
--
--
@ -663,6 +715,7 @@ function csarManager.openMissionsForSide(theSide)
end
function csarManager.doListCSARRequests(args)
local now = timer.getTime()
local conf = args[1]
local param = args[2]
local theUnit = conf.unit
@ -675,14 +728,34 @@ function csarManager.doListCSARRequests(args)
if #openMissions < 1 then
report = report .. "\nNo requests, all crew are safe."
else
-- iterate through all troops onboard to get their status
-- iterate through all missions and calc distance
for idx, mission in pairs(openMissions) do
local d = dcsCommon.distFlat(point, mission.zone.point) * 0.000539957
d = math.floor(d * 10) / 10
mission.dist = d
end
-- sort openMissions by dist
table.sort(openMissions,
function (e1, e2) return e1.dist < e2.dist end
)
-- we may want to limit to n nearest missions
local maxM = #openMissions
if maxM > csarManager.maxMissions then maxM = csarManager.maxMissions end
for i=1,maxM do --in pairs(openMissions) do
local mission = openMissions[i]
local b = dcsCommon.bearingInDegreesFromAtoB(point, mission.zone.point)
local status = "alive"
if mission.expires then
delta = math.floor ((mission.expires - now) / 60)
if delta < 10 then status = "+deteriorating+" end
if delta < 5 then status = "*critical*" end
if csarManager.verbose then
status = status .. "[" .. delta .. "]" -- remove me
end
end
if csarManager.vectoring then
report = report .. "\n".. mission.name .. ", bearing " .. b .. ", " ..d .."nm, " .. " ADF " .. mission.freq * 10 .. " kHz - " .. status
report = report .. "\n".. mission.name .. ", bearing " .. b .. ", " ..mission.dist .."nm, " .. " ADF " .. mission.freq * 10 .. " kHz - " .. status
else
-- leave out vectoring
report = report .. "\n".. mission.name .. " ADF " .. mission.freq * 10 .. " kHz - " .. status
@ -707,6 +780,7 @@ function csarManager.doStatusCarrying(args)
local conf = args[1]
local param = args[2]
local theUnit = conf.unit
local now = timer.getTime()
-- build status report
local report = "\nCrew Rescue Status:\n"
@ -717,8 +791,19 @@ function csarManager.doStatusCarrying(args)
for i=1, #conf.troopsOnBoard do
local evacMission = conf.troopsOnBoard[i]
report = report .. "\n".. i .. ") " .. evacMission.name
if evacMission.expires then
delta = math.floor ((mission.expires - now) / 60)
if delta > 20 then
report = report .. " is hurt but stable"
elseif delta > 10 then
report = report .. " is badly hurt"
else
report = report .. " is in critical condition" -- or 'beat up, but will live'
end
else
report = report .. " is stable" -- or 'beat up, but will live'
end
end
report = report .. "\n\nTotal added weigth: " .. 10 + #conf.troopsOnBoard * csarManager.pilotWeight .. "kg"
end
@ -886,16 +971,31 @@ end
--
function csarManager.updateCSARMissions()
local newMissions = {}
local now = timer.getTime()
for idx, aMission in pairs (csarManager.openMissions) do
-- see if mission timed out
local now = timer.getTime()
local stillRunning = true
if aMission.expires then stillRunning = aMission.expires > now end
local stillAlive = dcsCommon.isGroupAlive(aMission.group)
-- now check if a timer was running to rescue this group
-- if dead, set stillAlive to false
if stillAlive then
if stillRunning and stillAlive then
table.insert(newMissions, aMission)
elseif stillAlive then
local msg = aMission.name .. " is no longer responding. Abort rescue."
trigger.action.outTextForCoalition(aMission.side, msg, 30)
trigger.action.outSoundForCoalition(aMission.side, csarManager.lostSound)
csarManager.invokeCallbacks(aMission.side, false, 1, "lost", aMission)
if aMission.group and Group.isExist(aMission.group) then
-- trigger.action.outText("removing group", 30)
Group.destroy(aMission.group)
end
else
local msg = aMission.name .. " confirmed KIA, repeat KIA. Abort CSAR."
trigger.action.outTextForCoalition(aMission.side, msg, 30)
trigger.action.outSoundForCoalition(aMission.side, csarManager.actionSound) -- "Quest Snare 3.wav")
trigger.action.outSoundForCoalition(aMission.side, csarManager.actionSound)
csarManager.invokeCallbacks(aMission.side, false, 1, "KIA", aMission)
end
end
csarManager.openMissions = newMissions -- this is the new batch
@ -926,12 +1026,11 @@ function csarManager.update() -- every second
-- now scan through all helo groups and see if they are close to a
-- CSAR zone and initiate the help sequence
-- local allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it!
local allPlayerUnits = dcsCommon.getAllExistingPlayersAndUnits() -- indexed by player name
--old -- contains per group a player record, use prime unit to access player's unit
for pname, aUnit in pairs(allPlayerUnits) do
if --aUnit:isExist() and
aUnit:inAir() and
if aUnit:inAir() and
dcsCommon.isTroopCarrier(aUnit, csarManager.troopCarriers)
then -- troop carrier and is flying
local uPoint = aUnit:getPoint()
@ -1044,7 +1143,7 @@ function csarManager.update() -- every second
end
--trigger.action.outTextForGroup(uID, hoverMsg, 30, true)
trigger.action.outSoundForGroup(uID, csarManager.actionSound)
trigger.action.outSoundForGroup(uID, csarManager.pickupSound)
--return -- we only ever rescue one
end -- hovered long enough
@ -1209,8 +1308,15 @@ function csarManager.readCSARZone(theZone)
if theZone.csarFreq < 0.01 then theZone.csarFreq = nil end
theZone.numCrew = 1
theZone.csarMapMarker = nil
theZone.timeLimit = theZone:getNumberFromZoneProperty("timeLimit", 0)
if theZone.timeLimit == 0 then theZone.timeLimit = nil else theZone.timeLimit = timeLimit * 60 end
if theZone:hasProperty("timeLimit") then
local tmin, tmax = theZone:getPositiveRangeFromZoneProperty("timeLimit", 1)
-- trigger.action.outText("Read time limit for <" .. theZone.name .. ">: <" .. tmin .. ">, <" .. tmax .. ">", 30)
theZone.timeLimit = {tmin, tmax}
else
theZone.timeLimit = nil
end
-- theZone.timeLimit = theZone:getNumberFromZoneProperty("timeLimit", 0)
-- if theZone.timeLimit == 0 then theZone.timeLimit = nil else theZone.timeLimit = timeLimit * 60 end
local deferred = theZone:getBoolFromZoneProperty("deferred", false)
@ -1255,7 +1361,7 @@ function csarManager.readCSARZone(theZone)
-- add to list of startable csar
if theZone.startCSAR then
csarManager.addCSARZone(theZone)
trigger.action.outText("csar: added <".. theZone.name .."> to deferred csar missions", 30)
-- trigger.action.outText("csar: added <".. theZone.name .."> to deferred csar missions", 30)
end
if deferred and not theZone.startCSAR then
@ -1276,11 +1382,29 @@ end
-- Init & Start
--
function csarManager.invokeCallbacks(theCoalition, success, numRescued, notes)
-- mission complete cs(coalition, success, numberRescued, notes, data)
function csarManager.invokeCallbacks(theCoalition, success, numRescued, notes, theMission)
-- invoke anyone who wants to know that a group
-- of people was rescued.
for idx, cb in pairs(csarManager.csarCompleteCB) do
cb(theCoalition, success, numRescued, notes)
cb(theCoalition, success, numRescued, notes, theMission)
end
end
-- mission created cb(theMission)
function csarManager.invokeNewMissionCallbacks(theMission)
--trigger.action.outText("enter invoke new mission cb", 30)
-- invoke anyone who wants to know that a new mission was created
for idx, cb in pairs(csarManager.csarCreatedCB) do
cb(theMission)
end
end
-- mission: picking up the evacuee
function csarManager.invokePickUpCallbacks(theMission)
-- invoke anyone who wants to know that a new mission was created
for idx, cb in pairs(csarManager.csarPickupCB) do
cb(theMission)
end
end
@ -1288,6 +1412,14 @@ function csarManager.installCallback(theCB)
table.insert(csarManager.csarCompleteCB, theCB)
end
function csarManager.installNewMissionCallback(theCB)
table.insert(csarManager.csarCreatedCB, theCB)
end
function csarManager.installPickupCallback(theCB)
table.insert(csarManager.csarPickupCB, theCB)
end
function csarManager.readConfigZone()
csarManager.name = "csarManagerConfig" -- compat with cfxZones
local theZone = cfxZones.getZoneByName("csarManagerConfig")
@ -1331,7 +1463,10 @@ function csarManager.readConfigZone()
csarManager.rescueScore = theZone:getNumberFromZoneProperty( "rescueScore", 100)
csarManager.actionSound = theZone:getStringFromZoneProperty( "actionSound", "Quest Snare 3.wav")
csarManager.successSound = theZone:getStringFromZoneProperty("successSound", csarManager.actionSound)
csarManager.pickupSound = theZone:getStringFromZoneProperty("pickupSound", csarManager.actionSound)
csarManager.vectoring = theZone:getBoolFromZoneProperty("vectoring", true)
csarManager.lostSound = theZone:getStringFromZoneProperty("lostSound", csarManager.actionSound)
-- add own troop carriers
if theZone:hasProperty("troopCarriers") then
@ -1348,6 +1483,7 @@ function csarManager.readConfigZone()
csarManager.addPrefix = theZone:getBoolFromZoneProperty("addPrefix", true)
csarManager.maxMissions = theZone:getNumberFromZoneProperty("maxMissions", 15)
if csarManager.verbose then
trigger.action.outText("+++csar: read config", 30)
end
@ -1415,4 +1551,6 @@ end
-- minFreq, maxFreq settings for config and mission-individual
-- may want to change if time limit was exceeded on return to tell
player that they did not survive the transport
--]]--

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "3.0.1"
dcsCommon.version = "3.0.2"
--[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option
@ -9,6 +9,8 @@ dcsCommon.version = "3.0.1"
- createGroundGroupWithUnits now supports liveries
- new getAllExistingPlayersAndUnits()
3.0.1 - clone: better handling of string type
3.0.2 - new getPlayerUnit()
3.0.3 - createStaticObjectForCoalitionInRandomRing() returns x and z
--]]--
-- dcsCommon is a library of common lua functions
@ -1934,7 +1936,7 @@ dcsCommon.version = "3.0.1"
function dcsCommon.createStaticObjectForCoalitionAtLocation(theCoalition, loc, name, objType, heading, dead)
if not heading then heading = math.random(360) * 3.1415 / 180 end
local theData = dcsCommon.createStaticObjectDataAt(loc, name, objType, heading, dead)
local theStatic = coalition.addStaticObject(theCoalition, theData)
local theStatic = coalition.addStaticObject(theCoalition, theData) -- warning! coalition is not country!
return theStatic
end
@ -1947,8 +1949,8 @@ dcsCommon.version = "3.0.1"
theData.x = p.x
theData.y = p.z
local theStatic = coalition.addStaticObject(theCoalition, theData)
return theStatic
local theStatic = coalition.addStaticObject(theCoalition, theData) -- warning! coalition is not country
return theStatic, p.x, p.z
end
@ -2703,6 +2705,19 @@ function dcsCommon.isTroopCarrier(theUnit, carriers)
return dcsCommon.isTroopCarrierType(uType, carriers)
end
function dcsCommon.getPlayerUnit(playerName)
if not playerName then return nil end
for idx, theSide in pairs(dcsCommon.coalitionSides) do
local thePlayers = coalition.getPlayers(theSide)
for idy, theUnit in pairs (thePlayers) do
if theUnit and theUnit:isExist() and theUnit.getPlayerName
and theUnit:getPlayerName() == playerName then
return theUnit
end
end
end
return nil
end
function dcsCommon.getAllExistingPlayerUnitsRaw()
local apu = {}

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {}
cfxHeloTroops.version = "3.0.0"
cfxHeloTroops.version = "3.0.2"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@ -37,6 +37,8 @@ cfxHeloTroops.requestRange = 500 -- meters
- harmonized spawning invocations across cloners and spawners
- dmlZones
- requestRange attribute
3.0.1 - fixed a bug with legalTroops attribute
3.0.2 - fixed a typo in in-air menu
--]]--
--
@ -337,7 +339,7 @@ function cfxHeloTroops.addAirborneMenu(conf)
-- let's begin by assuming no troops aboard
local commandTxt = "(To load troops, land in proximity to them)"
if conf.troopsOnBoardNum > 0 then
commandTxt = "(You are carrying " .. conf.troopsOnBoardNum .. " Assault Troops. Land to deploy them"
commandTxt = "(You are carrying " .. conf.troopsOnBoardNum .. " Assault Troops. Land to deploy them)"
end
local theCommand = missionCommands.addCommandForGroup(
conf.id,
@ -887,7 +889,7 @@ function cfxHeloTroops.readConfigZone()
if theZone:hasProperty("legalTroops") then
local theTypesString = theZone:getStringFromZoneProperty("legalTroops", "")
local unitTypes = dcsCommon.splitString(aSpawner.types, ",")
local unitTypes = dcsCommon.splitString(theTypesString, ",")
if #unitTypes < 1 then
unitTypes = {"Soldier AK", "Infantry AK", "Infantry AK ver2", "Infantry AK ver3", "Infantry AK Ins", "Soldier M249", "Soldier M4 GRG", "Soldier M4", "Soldier RPG", "Paratrooper AKS-74", "Paratrooper RPG-16", "Stinger comm dsr", "Stinger comm", "Soldier stinger", "SA-18 Igla-S comm", "SA-18 Igla-S manpad", "Igla manpad INS", "SA-18 Igla comm", "SA-18 Igla manpad",} -- default
else

View File

@ -1,5 +1,5 @@
messenger = {}
messenger.version = "3.0.0"
messenger.version = "3.1.0"
messenger.verbose = false
messenger.requiredLibs = {
"dcsCommon", -- always
@ -11,6 +11,8 @@ messenger.messengers = {}
2.3.0 - cfxZones OOP switch
2.3.1 - triggering message AFTER the on/off switches are tested
3.0.0 - removed messenger, in?, f? attributes, harmonized on messenger?
3.1.0 - msgGroup supports multiple groups, separated by comma
- msgUnit supports multiple units, separated by comma
--]]--
function messenger.addMessenger(theZone)
@ -166,18 +168,12 @@ end
-- reat attributes
--
function messenger.createMessengerWithZone(theZone)
-- start val - a range
local aMessage = theZone:getStringFromZoneProperty("message", "")
theZone.message = aMessage -- refactoring: messenger.preProcMessage(aMessage, theZone) removed
theZone.message = aMessage
theZone.spaceBefore = theZone:getBoolFromZoneProperty("spaceBefore", false)
theZone.spaceAfter = theZone:getBoolFromZoneProperty("spaceAfter", false)
theZone.soundFile = theZone:getStringFromZoneProperty("soundFile", "<none>")
theZone.clearScreen = theZone:getBoolFromZoneProperty("clearScreen", false)
theZone.duration = theZone:getNumberFromZoneProperty("duration", 30)
if theZone:hasProperty("messageDuration") then
theZone.duration = theZone:getNumberFromZoneProperty( "messageDuration", 30)
@ -188,27 +184,7 @@ function messenger.createMessengerWithZone(theZone)
if theZone:hasProperty("msgTriggerMethod") then
theZone.msgTriggerMethod = theZone:getStringFromZoneProperty("msgTriggerMethod", "change")
end
if theZone:hasProperty("f?") then
theZone.triggerMessagerFlag = theZone:getStringFromZoneProperty("f?", "none")
end
-- can also use in? for counting. we always use triggerMessagerFlag
if theZone:hasProperty("in?") then
theZone.triggerMessagerFlag = theZone:getStringFromZoneProperty("in?", "none")
end
--[[--
if theZone:hasProperty("messageOut?") then
theZone.triggerMessagerFlag = theZone:getStringFromZoneProperty("messageOut?", "none")
end
--]]--
-- try default only if no other is set
if not theZone.triggerMessagerFlag then
if not theZone:hasProperty("messenger?") then
trigger.action.outText("*** Note: messenger in <" .. theZone.name .. "> can't be triggered", 30)
end
theZone.triggerMessagerFlag = theZone:getStringFromZoneProperty( "messenger?", "none")
end
theZone.lastMessageTriggerValue = theZone:getFlagValue(theZone.triggerMessagerFlag) -- save last value
theZone.messageOff = theZone:getBoolFromZoneProperty("mute", false) --false
@ -235,15 +211,42 @@ function messenger.createMessengerWithZone(theZone)
end
if theZone:hasProperty("group") then
theZone.msgGroup = theZone:getStringFromZoneProperty("group", "<none>")
elseif theZone:hasProperty("msgGroup") then
theZone.msgGroup = theZone:getStringFromZoneProperty("msgGroup", "<none>")
-- now supporting multiple groups
local theGroups = {}
local rawGroups = theZone:getStringFromZoneProperty("group", "<none>")
if dcsCommon.containsString(rawGroups, ",") then
theGroups = dcsCommon.splitString(rawGroups, ",")
theGroups = dcsCommon.trimArray(theGroups)
theZone.msgGroup = theGroups
else
theZone.msgGroup = {rawGroups}
end
elseif theZone:hasProperty("msgGroup") then
local rawGroups = theZone:getStringFromZoneProperty("msgGroup", "<none>")
if dcsCommon.containsString(rawGroups, ",") then
theGroups = dcsCommon.splitString(rawGroups, ",")
theGroups = dcsCommon.trimArray(theGroups)
theZone.msgGroup = theGroups
else
theZone.msgGroup = {rawGroups,}
end
end
if theZone:hasProperty("unit") or theZone:hasProperty("msgUnit") then
local rawUnits = "err"
if theZone:hasProperty("unit") then
theZone.msgUnit = theZone:getStringFromZoneProperty("unit", "<none>")
elseif theZone:hasProperty("msgUnit") then
theZone.msgUnit = theZone:getStringFromZoneProperty("msgUnit", "<none>")
rawUnits = theZone:getStringFromZoneProperty("unit", "<none>")
else
rawUnits = theZone:getStringFromZoneProperty("msgUnit", "<none>")
end
if dcsCommon.containsString(rawUnits, ",") then
local theUnits = dcsCommon.splitString(rawUnits, ",")
theUnits = dcsCommon.trimArray(theUnits)
theZone.msgUnit = theUnits
else
theZone.msgUnit = {rawUnits,}
end
end
if (theZone.msgGroup and theZone.msgUnit) or
@ -337,7 +340,8 @@ function messenger.isTriggered(theZone)
end
trigger.action.outSoundForCoalition(theZone.msgCoalition, fileName)
elseif theZone.msgGroup then
local theGroup = Group.getByName(theZone.msgGroup)
for idx, aGroupName in pairs(theZone.msgGroup) do
local theGroup = Group.getByName(aGroupName)
if theGroup and Group.isExist(theGroup) then
local ID = theGroup:getID()
msg = messenger.dynamicGroupProcessing(msg, theZone, theGroup)
@ -346,8 +350,10 @@ function messenger.isTriggered(theZone)
end
trigger.action.outSoundForGroup(ID, fileName)
end
end
elseif theZone.msgUnit then
local theUnit = Unit.getByName(theZone.msgUnit)
for idx, aUnitName in pairs (theZone.msgUnit) do
local theUnit = Unit.getByName(aUnitName)
if theUnit and Unit.isExist(theUnit) then
local ID = theUnit:getID()
msg = messenger.dynamicUnitProcessing(msg, theZone, theUnit)
@ -356,6 +362,7 @@ function messenger.isTriggered(theZone)
end
trigger.action.outSoundForUnit(ID, fileName)
end
end
elseif theZone:getLinkedUnit() then
-- this only works if the zone is linked to a unit
-- and not using group or unit

1234
modules/names.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@ -136,7 +136,7 @@ end
function ownAll.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx ownAlll requires dcsCommon", 30)
trigger.action.outText("cfx ownAll requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx ownAll", ownAll.requiredLibs) then

View File

@ -30,7 +30,6 @@ persistence.requiredLibs = {
persistence.flagsToSave = {} -- simple table
persistence.callbacks = {} -- cbblocks, dictionary by name
--
-- modules register here
--
@ -76,7 +75,6 @@ function persistence.getSavedDataForModule(name)
return persistence.missionData[name] -- simply get the modules data block
end
--
-- Shared Data API
--
@ -113,7 +111,6 @@ function persistence.isDir(path) -- check if path is a directory
return success
end
--
-- Main save meths
--
@ -189,7 +186,6 @@ function persistence.saveTable(theTable, fileName, shared, append)
return true
end
function persistence.loadText(fileName) -- load file as text
if not persistence.active then return nil end
if not fileName then return nil end
@ -218,8 +214,6 @@ function persistence.loadTable(fileName) -- load file as table
return tab
end
--
-- Data Load on Start
--
@ -401,7 +395,6 @@ end
--
-- config & start
--
function persistence.collectFlagsFromZone(theZone)
local theFlags = theZone:getStringFromZoneProperty("saveFlags", "*dummy")
persistence.registerFlagsToSave(theFlags, theZone)
@ -409,7 +402,7 @@ end
function persistence.readConfigZone()
if not _G["lfs"] then
trigger.action.outText("+++persistence: DCS correctly not 'desanitized'. Persistence disabled", 30)
trigger.action.outText("+++persistence: DCS currently not 'desanitized'. Persistence disabled", 30)
return
end

View File

@ -1,5 +1,5 @@
radioMenu = {}
radioMenu.version = "2.2.0"
radioMenu.version = "2.2.1"
radioMenu.verbose = false
radioMenu.ups = 1
radioMenu.requiredLibs = {
@ -18,6 +18,7 @@ radioMenu.menus = {}
full wildcard support for ack and cooldown
2.1.1 - outMessage now works correctly
2.2.0 - clean-up
2.2.1 - corrected ackD
--]]--
function radioMenu.addRadioMenu(theZone)
@ -299,7 +300,7 @@ function radioMenu.createRadioMenuWithZone(theZone)
theZone.outValD = theZone:getStringFromZoneProperty("valD", 1)
end
if theZone:hasProperty("ackD") then
theZone.ackC = theZone:getStringFromZoneProperty("ackD", "Acknowledged: D")
theZone.ackD = theZone:getStringFromZoneProperty("ackD", "Acknowledged: D")
end
if theZone:hasProperty("removeMenu?") then

628
modules/scribe.lua Normal file
View File

@ -0,0 +1,628 @@
scribe = {}
scribe.version = "1.0.0"
scribe.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
"cfxMX",
}
--[[--
Player statistics package
VERSION HISTORY
1.0.0 Initial Version
--]]--
scribe.verbose = true
scribe.db = {} -- indexed by player name
scribe.playerUnits = {} -- indexed by unit name. for crash detection
--[[--
unitEntry:
ttime -- total time in seconds
airTime -- total air time
landings -- number of landings
lastLanding -- time of last landing OR DEPARTURE.
departures -- toital take-offs
crashes -- number of total crashes, deaths etc
--]]--
function scribe.createUnitEntry()
local theEntry = {}
theEntry.ttime = 0
theEntry.lastTime = 99999999 -- NOT math.huge because json
theEntry.landings = 0
theEntry.lastLanding = 99999999 -- not math.huge -- in the future
theEntry.departures = 0
theEntry.crashes = 0
theEntry.startUps = 0
theEntry.rescues = 0
return theEntry
end
--[[--
playerEntry:
units[] -- by type name indexed types the player has flown
-- each entry has
lastUnitName -- name of the unit player was last seen in
-- used to determine if player is still in-game
lastUnitType
isActive -- used to detect if player has left and close down record
--]]--
function scribe.createPlayerEntry(name)
local theEntry = {}
theEntry.playerName = name -- for easy access
local theUnit = dcsCommon.getPlayerUnit(name)
local theType = theUnit:getTypeName()
local unitName = theUnit:getName()
theEntry.units = {}
theEntry.lastUnitName = "cfxNone" -- don't ever use that name
theEntry.lastUnitType = "none"
theEntry.isActive = false -- is player in game?
return theEntry
end
function scribe.getPlayerNamed(name) -- lazy allocation
local theEntry = scribe.db[name]
if not theEntry then
theEntry = scribe.createPlayerEntry(name)
scribe.db[name] = theEntry
end
return theEntry
end
function scribe.sumPlayerEntry(theEntry, theField)
local sum = 0
for idx, aUnit in pairs(theEntry.units) do
if aUnit[theField] then sum = sum + aUnit[theField] end
end
return sum
end
function scribe.tickEntry(theEntry)
if not theEntry then return 0 end
local now = timer.getTime()
local uEntry = theEntry.units[theEntry.lastUnitType]
if not uEntry then return 0 end -- can happen on idling server that has reloaded. all last players have invalid last units
local delta = now - uEntry.lastTime
if delta < 0 then delta = 0 end
uEntry.lastTime = now
uEntry.ttime = uEntry.ttime + delta
return delta
end
function scribe.finalizeEntry(theEntry)
-- player no longer in game. finalize last entry
-- and make it inactive
theEntry.isActive = false
local delta = scribe.tickEntry(theEntry) -- on LAST flown aircraft
local uEntry = theEntry.units[theEntry.lastUnitType]
if uEntry then
uEntry.lastTime = 99999999 -- NOT math.huge
local deltaTime = dcsCommon.processHMS("<:h>:<:m>:<:s>", delta)
local fullTime = dcsCommon.processHMS("<:h>:<:m>:<:s>", uEntry.ttime)
if scribe.byePlayer then
trigger.action.outText("Player " .. theEntry.playerName .. " left " .. theEntry.lastUnitName .. " (a " .. theEntry.lastUnitType .. "), total time in aircraft " .. fullTime ..".", 30)
end
end
end
function scribe.entry2text(uEntry, totals)
-- validate uEntry, lazy init of missing fields
if not uEntry then uEntry = {} end
if not uEntry.ttime then uEntry.ttime = 0 end
if not uEntry.departures then uEntry.departures = 0 end
if not uEntry.landings then uEntry.landings = 0 end
if not uEntry.crashes then uEntry.crashes = 0 end
if not uEntry.startups then uEntry.startups = 0 end
if not uEntry.rescues then uEntry.rescues = 0 end
local t = ""
if not totals.ttime then totals.ttime = 0 end
t = t .. scribe.lTime .. " " .. dcsCommon.processHMS("<:h>:<:m>:<:s>", uEntry.ttime) .. " hrs"
totals.ttime = totals.ttime + uEntry.ttime
if scribe.departures then
t = t .. ", " .. scribe.lDeparture .. " " .. uEntry.departures
if not totals.departures then totals.departures = 0 end
totals.departures = totals.departures + uEntry.departures
end
if scribe.landings then
t = t .. ", " .. scribe.lLanding .. " " .. uEntry.landings
if not totals.landings then totals.landings = 0 end
totals.landings = totals.landings + uEntry.landings
end
if scribe.crashes then
t = t .. ", " .. scribe.lCrash .. " " .. uEntry.crashes
if not totals.crashes then totals.crashes = 0 end
totals.crashes = totals.crashes + uEntry.crashes
end
if scribe.startUps then
t = t .. ", " .. scribe.lStartUp .. " " .. uEntry.startUps
if not totals.startUps then totals.startUps = 0 end
totals.startUps = totals.startUps + uEntry.startUps
end
if scribe.rescues then
t = t .. ", " .. scribe.lRescue .. " " .. uEntry.rescues
if not totals.rescues then totals.rescues = 0 end
totals.rescues = totals.rescues + uEntry.rescues
end
return t
end
--
-- Event handling
--
function scribe.playerBirthedIn(playerName, theUnit)
-- access db
local theEntry = scribe.getPlayerNamed(playerName) -- can be new
local myType = theUnit:getTypeName()
local uName = theUnit:getName()
local theGroup = theUnit:getGroup()
local gID = theGroup:getID()
-- check if this player is still active
if theEntry.isActive then
-- do something to remedy this
scribe.finalizeEntry(theEntry)
end
-- check if player switched airframes
if theEntry.lastUnitName == uName and scribe.verbose then
trigger.action.outText("+++scb: player <" .. playerName .. "> reappeard in same unit <" .. uName .. ">", 30)
else
end
theEntry.lastUnitName = uName
theEntry.lastUnitType = myType
theEntry.isActive = true -- activate player
-- set us up to track this player in this unit
local myTypeEntry = theEntry.units[myType]
if not myTypeEntry then
myTypeEntry = scribe.createUnitEntry()
local uGroup = theUnit:getGroup()
local gName = uGroup:getName()
myTypeEntry.gName = gName
theEntry.units[myType] = myTypeEntry
end
myTypeEntry.lastTime = timer.getTime()
if scribe.verbose then
trigger.action.outText("+++scb: player <" .. playerName .. "> entered aircraft <" .. uName .. "> (a " .. myType .. ")", 30)
end
if scribe.greetPlayer then
local msg = "\nWelcome " .. theEntry.playerName .. " to your " .. myType .. ". Your stats currently are:\n\n"
msg = msg .. scribe.entry2data(theEntry) .. "\n"
trigger.action.outTextForGroup(gID, msg, 30)
end
end
function scribe.playerCrashed(playerName)
if scribe.verbose then
trigger.action.outText("+++scb: enter crash for <" .. playerName .. ">", 30)
end
local theEntry = scribe.getPlayerNamed(playerName)
if not theEntry.isActive then
if scribe.verbose then
trigger.action.outText("+++scb: player <" .. playerName .. "> CRASH event ignored: player not active", 30)
end
return
end
local uEntry = theEntry.units[theEntry.lastUnitType]
uEntry.crashes = uEntry.crashes + 1
scribe.finalizeEntry(theEntry)
end
function scribe.playerEjected(playerName)
if scribe.verbose then
trigger.action.outText("+++scb: enter eject for <" .. playerName .. ">, handing off to crash", 30)
end
-- counts as a crash
local theEntry = scribe.getPlayerNamed(playerName)
if not theEntry.isActive then
if scribe.verbose then
trigger.action.outText("+++scb: player <" .. playerName .. "> EJECT event ignored: player not active", 30)
end
return
end
scribe.playerCrashed(playerName)
end
function scribe.playerDied(playerName)
-- trigger.action.outText("+++scb: player <" .. playerName .. "> DEAD event, handing off to crashS", 30)
-- counts as a crash
local theEntry = scribe.getPlayerNamed(playerName)
if not theEntry.isActive then
if scribe.verbose then
trigger.action.outText("+++scb: player <" .. playerName .. "> DEAD event ignored: player not active", 30)
end
return
end
scribe.playerCrashed(playerName)
end
function scribe.engineStarted(playerName)
local theEntry = scribe.getPlayerNamed(playerName)
if not theEntry.isActive then
if scribe.verbose then
trigger.action.outText("+++scb: player <" .. playerName .. "> STARTUP event ignored: player not active", 30)
end
return
end
local uEntry = theEntry.units[theEntry.lastUnitType]
uEntry.startUps = uEntry.startUps + 1
if scribe.verbose then
trigger.action.outText("+++scb: startup registered for <" .. playerName .. ">.", 30)
end
end
function scribe.playerLanded(playerName)
local theEntry = scribe.getPlayerNamed(playerName)
if not theEntry.isActive then
if scribe.verbose then
trigger.action.outText("+++scb: player <" .. playerName .. "> landing event ignored: player not active", 30)
end
return
end
local uEntry = theEntry.units[theEntry.lastUnitType]
-- see if last landing is at least xx seconds old
local now = timer.getTime()
delta = now - uEntry.lastLanding
if delta > scribe.landingCD or delta < 0 then
uEntry.landings = uEntry.landings + 1
else
if scribe.verbose then
trigger.action.outText("+++scb: landing ignored: cooldown active", 30)
end
end
uEntry.lastLanding = now
end
function scribe.playerDeparted(playerName)
local theEntry = scribe.getPlayerNamed(playerName)
if not theEntry.isActive then
if scribe.verbose then
trigger.action.outText("+++scb: player <" .. playerName .. "> take-off event ignored: player not active", 30)
end
return
end
local uEntry = theEntry.units[theEntry.lastUnitType]
-- see if last landing is at least xx seconds old
local now = timer.getTime()
delta = now - uEntry.lastLanding -- we use laastLanding for BOTH!
if delta > scribe.landingCD or delta < 0 then
uEntry.departures = uEntry.departures + 1
else
if scribe.verbose then
trigger.action.outText("+++scb: departure ignored: cooldown active", 30)
end
end
uEntry.lastLanding = now -- also for Departures!
end
--
-- API
--
-- invoked from other modules
function scribe.playerRescueComplete(playerName)
local theEntry = scribe.getPlayerNamed(playerName)
if not theEntry.isActive then
if scribe.verbose then
trigger.action.outText("+++scb: player <" .. playerName .. "> rescue complete event ignored: player not active", 30)
end
return
end
local uEntry = theEntry.units[theEntry.lastUnitType]
if not uEntry then
-- this should not happen
trigger.action.outText("+scb: unknown unit for player <" .. playerName .. "> in recue complete. Ignored", 30)
return
end
if not uEntry.rescues then uEntry.rescues = 0 end
uEntry.rescues = uEntry.rescues + 1
end
function scribe:onEvent(theEvent)
if not theEvent.initiator then return end
local theUnit = theEvent.initiator
if not theUnit then return end
local uName = theUnit:getName()
if scribe.playerUnits[uName] and scribe.verbose then
trigger.action.outText("+++scb: event <" .. theEvent.id .. " = " .. dcsCommon.event2text(theEvent.id) .. ">, concerns player unit named <" .. uName .. ">.", 30)
end
if not theUnit.getPlayerName then
if scribe.playerUnits[uName] and scribe.verbose then
trigger.action.outText("+++scb no more a player unit (case A: getPlanerName not implemented), event = <" .. theEvent.id .. ">, unit named <" .. uName .. ">", 30)
end
return
end
local playerName = theUnit:getPlayerName()
if not playerName then
if scribe.playerUnits[uName] and scribe.verbose then
trigger.action.outText("+++scb no more a player unit (case B: nilplayer name), event = <" .. theEvent.id .. ">, unit named <" .. uName .. ">", 30)
end
return
end
-- when we get here we have a player event
-- players can only ever activate by birth event
if theEvent.id == 15 then -- birth
scribe.playerBirthedIn(playerName, theUnit)
scribe.playerUnits[uName] = playerName -- for crash helo detection
end
if theEvent.id == 8 or theEvent.id == 9 then -- dead, pilot_dead
scribe.playerDied(playerName)
end
if theEvent.id == 6 then -- ejected
scribe.playerEjected(playerName)
end
if theEvent.id == 5 then -- crash
scribe.playerCrashed(playerName)
end
if theEvent.id == 4 then -- landed
scribe.playerLanded(playerName)
end
if theEvent.id == 3 then -- take-off
scribe.playerDeparted(playerName)
-- trigger.action.outText("departure detected", 30)
end
if theEvent.id == 18 then -- engine start
-- make sure group isn't on hotstart
local theGroup = theUnit:getGroup()
local gName = theGroup:getName()
if cfxMX.groupHotByName[gName] then
if scribe.verbose then
trigger.action.outText("scb: ignored engine start: hot start for <" .. playerName .. ">", 30)
end
else
if scribe.verbose then
trigger.action.outText("scb: engine start for <" .. playerName .. ">", 30)
end
scribe.engineStarted(playerName)
end
end
end
--
-- GUI
--
function scribe.redirectCheckData(args)
timer.scheduleFunction(scribe.doCheckData, args, timer.getTime() + 0.1)
end
function scribe.doCheckData(unitInfo)
local unitName = unitInfo.uName
-- we now try and match player to the unit by rummaning through db
local thePlayerEntry = nil
for pName, theEntry in pairs(scribe.db) do
if unitName == theEntry.lastUnitName and theEntry.isActive then
thePlayerEntry = theEntry
end
end
if (not thePlayerEntry) then
if scribe.verbose then
trigger.action.outText("+++scb: cannot retrieve player for unit <" .. unitName .. ">", 30)
end
return
end
-- tick over so we have updated time
scribe.tickEntry(thePlayerEntry)
local msg = "Player " .. thePlayerEntry.playerName .. ":\n"
msg = msg .. scribe.entry2data(thePlayerEntry)
trigger.action.outTextForGroup(unitInfo.gID, msg, 30)
end
function scribe.entry2data(thePlayerEntry)
local msg = ""
local totals = {}
for aType, uEntry in pairs (thePlayerEntry.units) do
msg = msg .. aType .. " -- " .. scribe.entry2text(uEntry, totals) .. "\n"
end
if dcsCommon.getSizeOfTable(thePlayerEntry.units) > 1 then
local dummy = {}
msg = msg .. "\nTotals -- " .. scribe.entry2text(totals, dummy) .. "\n"
end
return msg
end
--
-- GC -- detect player leaving
--
function scribe.GC()
timer.scheduleFunction(scribe.GC, {}, timer.getTime() + 1)
-- iterate through all players in DB and see if they
-- are still on-line.
for pName, theEntry in pairs(scribe.db) do
if theEntry.isActive then
-- this player is on the books as in the game
local theUnit = Unit.getByName(theEntry.lastUnitName)
if theUnit and Unit.isExist(theUnit) and theUnit:getLife() >= 1 then
-- all is fine, go on
else
-- this unit no longer exists and we finalize player
if scribe.verbose then
trigger.action.outText("+++scb: player <" .. pName .. "> left <" .. theEntry.lastUnitName .. "> unit, finalizing", 30)
end
scribe.finalizeEntry(theEntry)
end
end
end
end
--
-- start
--
function scribe.startPlayerGUI()
-- scan all mx players
-- note: currently assumes single-player groups
-- in preparation of single-player 'commandForUnit'
-- ASSUMES SINGLE-UNIT PLAYER GROUPS!
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 scribe.verbose then
trigger.action.outText("unit <" .. uName .. ">: type <" .. theType .. "> coa <" .. coa .. ">, group <" .. gName .. ">", 30)
end
unitInfo.uName = uName -- needed for reverse-lookup
unitInfo.gName = gName -- also needed for reverse lookup
unitInfo.coa = coa
unitInfo.gID = gData.groupId
unitInfo.uID = uData.unitId
unitInfo.theType = theType
unitInfo.cat = cfxMX.groupTypeByName[gName]
unitInfo.root = missionCommands.addSubMenuForGroup(unitInfo.gID, scribe.uiMenu)
unitInfo.checkData = missionCommands.addCommandForGroup(unitInfo.gID, "Get Pilot's Statistics", unitInfo.root, scribe.redirectCheckData, unitInfo)
end
end
--
-- Config
--
function scribe.readConfigZone()
local theZone = cfxZones.getZoneByName("scribeConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("scribeConfig")
end
scribe.verbose = theZone.verbose
scribe.hasGUI = theZone:getBoolFromZoneProperty("hasGUI", true)
scribe.uiMenu = theZone:getStringFromZoneProperty("uiMenu", "Mission Logbook")
scribe.greetPlayer = theZone:getBoolFromZoneProperty("greetPlayer", true)
scribe.byePlayer = theZone:getBoolFromZoneProperty("byebyePlayer", true)
scribe.landings = theZone:getBoolFromZoneProperty("landings", true)
scribe.lLanding = theZone:getStringFromZoneProperty("lLandings", "landings:")
scribe.departures = theZone:getBoolFromZoneProperty("departures", true)
scribe.lDeparture = theZone:getStringFromZoneProperty("lDepartures", "take-offs:")
scribe.startUps = theZone:getBoolFromZoneProperty("startups", true)
scribe.lStartUp = theZone:getStringFromZoneProperty("lStartups", "starts:")
scribe.crashes = theZone:getBoolFromZoneProperty("crashes", true)
scribe.lCrash = theZone:getStringFromZoneProperty("lCrashes", "crashes:")
scribe.rescues = theZone:getBoolFromZoneProperty("rescues", false)
scribe.lRescue = theZone:getStringFromZoneProperty("lRescues", "rescues:")
scribe.lTime = theZone:getStringFromZoneProperty("lTime", "time:")
scribe.landingCD = theZone:getNumberFromZoneProperty("landingCD", 60) -- seconds between stake-off, landings, or either
end
--
-- load / save (game data)
--
function scribe.saveData()
local theData = {}
-- tick over all player entry recors so we can save
-- most recent data
for planerName, thePlayerEntry in pairs(scribe.db) do
if thePlayerEntry then scribe.tickEntry(thePlayerEntry) end
end
-- save current log. simple clone
local theLog = dcsCommon.clone(scribe.db)
theData.theLog = theLog
return theData
end
function scribe.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("scribe")
if not theData then
if scribe.verbose then
trigger.action.outText("+++scb: no save date received, skipping.", 30)
end
return
end
local theLog = theData.theLog
scribe.db = theLog
-- post-proc: set all to inactive, no player can be in game at start
for pName, theEntry in pairs(scribe.db) do
if theEntry.isActive then
theEntry.isActive = false
theEntry.lastUnitName = "cfxNone"
theEntry.lastUnitType = "xxx"
for uName, uEntry in pairs (theEntry.units) do
uEntry.lastTime = 99999999 -- NOT math.huge
uEntry.lastLanding = 99999999 -- NOT math.huge!
end
end
end
end
--
-- start
--
function scribe.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx scribe requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx scribe", scribe.requiredLibs) then
return false
end
-- install event handler
world.addEventHandler(scribe)
-- get config
scribe.readConfigZone()
-- install menus to all player units
if scribe.hasGUI then
scribe.startPlayerGUI()
end
-- now load all save data and populate map with troops that
-- we deployed last save.
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = scribe.saveData
persistence.registerModule("scribe", callbacks)
-- now load my data
scribe.loadData()
end
-- start GC
timer.scheduleFunction(scribe.GC, {}, timer.getTime() + 1)
-- say hi!
trigger.action.outText("cfx scribe v" .. scribe.version .. " started.", 30)
return true
end
-- let's go
if not scribe.start() then
trigger.action.outText("cfx scribe module failed to launch.", 30)
end

Binary file not shown.