mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
1.2.8
PlayerScore 2.0 Owned Zones refactor (started)
This commit is contained in:
parent
aeafc854ac
commit
97d3c10540
Binary file not shown.
Binary file not shown.
1316
modules/cfxOwnedZones (legacy 1.3.0).lua
Normal file
1316
modules/cfxOwnedZones (legacy 1.3.0).lua
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,66 +1,11 @@
|
|||||||
cfxOwnedZones = {}
|
cfxOwnedZones = {}
|
||||||
cfxOwnedZones.version = "1.3.0"
|
cfxOwnedZones.version = "2.0.0"
|
||||||
cfxOwnedZones.verbose = false
|
cfxOwnedZones.verbose = false
|
||||||
cfxOwnedZones.announcer = true
|
cfxOwnedZones.announcer = true
|
||||||
cfxOwnedZones.name = "cfxOwnedZones"
|
cfxOwnedZones.name = "cfxOwnedZones"
|
||||||
--[[-- VERSION HISTORY
|
--[[-- VERSION HISTORY
|
||||||
|
|
||||||
1.0.3 - added getNearestFriendlyZone
|
2.0.0 - factored from cfxOwnedZones 1.x, separating out production
|
||||||
- added getNearestOwnedZone
|
|
||||||
- added hasOwnedZones
|
|
||||||
- added getNearestOwnedZoneToPoint
|
|
||||||
1.0.4 - changed addOwnedZone code to use cfxZone.getCoalitionFromZoneProperty
|
|
||||||
- changed to use dcsCommon.coalition2county
|
|
||||||
- changed to using correct coalition for spawing attackers and defenders
|
|
||||||
1.0.5 - repairing defenders switches to country instead coalition when calling createGroundUnitsInZoneForCoalition -- fixed
|
|
||||||
1.0.6 - removed call to conqTemplate
|
|
||||||
- verified that pause will also apply to init
|
|
||||||
- unbeatable zones
|
|
||||||
- untargetable zones
|
|
||||||
- hidden attribute
|
|
||||||
1.0.7 - optional cfxGroundTroops module, error message when attackers
|
|
||||||
- support of 'none' type string to indicate no attackers/defenders
|
|
||||||
- updated property access
|
|
||||||
- module check
|
|
||||||
- cfxOwnedZones.usesDefenders(aZone)
|
|
||||||
- verifyZone
|
|
||||||
1.0.8 - repairDefenders trims types to allow blanks in
|
|
||||||
type separator
|
|
||||||
1.1.0 - config zone
|
|
||||||
- bang! support r, b, n capture
|
|
||||||
- defaulting attackDelta to 10 instead of radius
|
|
||||||
- verbose code for spawning
|
|
||||||
- verbose code for state transition
|
|
||||||
- attackers have (A) in name, defenders (D)
|
|
||||||
- exit createDefenders if no troops
|
|
||||||
- exit createAttackers if no troops
|
|
||||||
- usesAttackers/usesDefenders checks for neutral ownership
|
|
||||||
- verbose state change
|
|
||||||
- nearestZone supports moving zones
|
|
||||||
- remove exiting defenders from zone after cap to avoid
|
|
||||||
shocked state
|
|
||||||
- announcer
|
|
||||||
1.1.1 - conq+1 flag
|
|
||||||
1.1.2 - corrected type bug in zoneConquered
|
|
||||||
1.2.0 - support for persistence
|
|
||||||
- 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!
|
|
||||||
1.2.3 - fix for persistence bug when not using conquered flag
|
|
||||||
1.2.4 - pause? and activate? inputs
|
|
||||||
1.3.0 - new update method
|
|
||||||
- new fastEval option in config
|
|
||||||
- new numCap option in config
|
|
||||||
- new numKeep option in config
|
|
||||||
- new easyContest option in config
|
|
||||||
- new logic to keep and lose zones. controlled with numKeep and numCap.
|
|
||||||
- winSound
|
|
||||||
- loseSound
|
|
||||||
- redLost! zone output
|
|
||||||
- blueLost! zone output
|
|
||||||
- ownedBy direct zone output
|
|
||||||
- neutral! zone output
|
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
cfxOwnedZones.requiredLibs = {
|
cfxOwnedZones.requiredLibs = {
|
||||||
@ -68,60 +13,27 @@ cfxOwnedZones.requiredLibs = {
|
|||||||
-- pretty stupid to check for this since we
|
-- pretty stupid to check for this since we
|
||||||
-- need common to invoke the check, but anyway
|
-- need common to invoke the check, but anyway
|
||||||
"cfxZones", -- Zones, of course
|
"cfxZones", -- Zones, of course
|
||||||
"cfxCommander", -- to make troops do stuff
|
|
||||||
-- "cfxGroundTroops", -- optional, used for attackers only
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfxOwnedZones.zones = {}
|
cfxOwnedZones.zones = {}
|
||||||
cfxOwnedZones.ups = 1
|
cfxOwnedZones.ups = 1
|
||||||
cfxOwnedZones.initialized = false
|
cfxOwnedZones.initialized = false
|
||||||
cfxOwnedZones.defendingTime = 100 -- 100 seconds until new defenders are produced
|
--[[--
|
||||||
cfxOwnedZones.attackingTime = 300 -- 300 seconds until new attackers are produced
|
owned zones is a module that managers conquerable zones and keeps a record
|
||||||
cfxOwnedZones.shockTime = 200 -- 200 -- 'shocked' period of inactivity
|
of who owns the zone based on rules
|
||||||
cfxOwnedZones.repairTime = 200 -- 200 -- time until we raplace one lost unit, also repairs all other units to 100%
|
|
||||||
|
|
||||||
-- persistence: all attackers we ever sent out.
|
|
||||||
-- is regularly verified and cut to size
|
*** EXTENTDS ZONES ***, so compatible with cfxZones, pilotSafe (limited airframes), may conflict with FARPZones
|
||||||
cfxOwnedZones.spawnedAttackers = {}
|
|
||||||
|
|
||||||
-- owned zones is a module that managers 'conquerable' zones and keeps a
|
|
||||||
-- record of who owns the zone
|
|
||||||
-- based on some simple rules that are regularly checked
|
|
||||||
|
|
||||||
--
|
owned zones are identified by the 'owner' property. It can be initially set to nothing (default), NEUTRAL, RED or BLUE
|
||||||
-- *** EXTENTDS ZONES ***, so compatible with cfxZones, pilotSafe (limited airframes), may conflict with FARPZones
|
|
||||||
--
|
|
||||||
|
|
||||||
-- owned zones are identified by the 'owner' property. It can be initially set to nothing (default), NEUTRAL, RED or BLUE
|
when a zone changes hands, a callback can be installed to be told of that fact
|
||||||
|
callback has the format (zone, newOwner, formerOwner) with zone being the Zone, and new owner and former owners
|
||||||
-- when a zone changes hands, a callback can be installed to be told of that fact
|
--]]--
|
||||||
-- callback has the format (zone, newOwner, formerOwner) with zone being the Zone, and new owner and former owners
|
|
||||||
cfxOwnedZones.conqueredCallbacks = {}
|
cfxOwnedZones.conqueredCallbacks = {}
|
||||||
|
|
||||||
--
|
|
||||||
-- zone attributes when owned
|
|
||||||
-- owner: coalition that owns the zone
|
|
||||||
-- status: FSM for spawning
|
|
||||||
-- defendersRED/BLUE - coma separated type string for the group to spawn on defense cycle completion
|
|
||||||
-- attackersRED/BLUE - as above for attack cycle.
|
|
||||||
-- timeStamp - time when zone switched into current state
|
|
||||||
-- spawnRadius - overrides zone's radius when placing defenders. can be use to place defenders inside or outside zone itself
|
|
||||||
-- formation - defender's formation
|
|
||||||
-- attackFormation - attackers formation
|
|
||||||
-- attackRadius - radius of circle in which attackers are spawned. informs formation
|
|
||||||
-- attackDelta - polar coord: r from zone center where attackers are spawned
|
|
||||||
-- attackPhi - polar degrees where attackers are to be spawned
|
|
||||||
-- paused - will not spawn. default is false
|
|
||||||
-- unbeatable - can't be conquered by other side. default is false
|
|
||||||
-- untargetable - will not be targeted by either side. make unbeatable
|
|
||||||
-- owned zones untargetable, or they'll become a troop magnet for
|
|
||||||
-- zoneAttackers
|
|
||||||
-- hidden - if set (default no), it no markings on the map
|
|
||||||
--
|
|
||||||
-- to create an owned zone that can't be conquered and does nothing
|
|
||||||
-- add the following properties to a zone
|
|
||||||
-- owner = <x>, paused = true, unbeatable = true
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- callback handling
|
-- callback handling
|
||||||
--
|
--
|
||||||
@ -173,7 +85,7 @@ function cfxOwnedZones.drawZoneInMap(aZone)
|
|||||||
|
|
||||||
local lineColor = {1.0, 0, 0, 1.0} -- red
|
local lineColor = {1.0, 0, 0, 1.0} -- red
|
||||||
local fillColor = {1.0, 0, 0, 0.2} -- red
|
local fillColor = {1.0, 0, 0, 0.2} -- red
|
||||||
local owner = cfxOwnedZones.getOwnerForZone(aZone)
|
local owner = aZone.owner -- cfxOwnedZones.getOwnerForZone(aZone)
|
||||||
if owner == 2 then
|
if owner == 2 then
|
||||||
lineColor = {0.0, 0, 1.0, 1.0}
|
lineColor = {0.0, 0, 1.0, 1.0}
|
||||||
fillColor = {0.0, 0, 1.0, 0.2}
|
fillColor = {0.0, 0, 1.0, 0.2}
|
||||||
@ -198,337 +110,46 @@ function cfxOwnedZones.getOwnedZoneByName(zName)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function cfxOwnedZones.addOwnedZone(aZone)
|
function cfxOwnedZones.addOwnedZone(aZone)
|
||||||
local owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0) -- is already readm read it again
|
local owner = aZone.owner --cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0) -- is already readm read it again
|
||||||
|
|
||||||
aZone.owner = owner -- add this attribute to zone
|
|
||||||
|
|
||||||
-- now init all other owned zone properties
|
|
||||||
aZone.state = "init"
|
|
||||||
aZone.timeStamp = timer.getTime()
|
|
||||||
--aZone.defendersRED = "Soldier M4,Soldier M4,Soldier M4,Soldier M4,Soldier M4" -- vehicles allocated to defend when red
|
|
||||||
|
|
||||||
aZone.defendersRED = cfxZones.getStringFromZoneProperty(aZone, "defendersRED", "none")
|
|
||||||
aZone.defendersBLUE = cfxZones.getStringFromZoneProperty(aZone, "defendersBLUE", "none")
|
|
||||||
|
|
||||||
aZone.attackersRED = cfxZones.getStringFromZoneProperty(aZone, "attackersRED", "none")
|
|
||||||
aZone.attackersBLUE = cfxZones.getStringFromZoneProperty(aZone, "attackersBLUE", "none")
|
|
||||||
|
|
||||||
local formation = cfxZones.getZoneProperty(aZone, "formation")
|
|
||||||
if not formation then formation = "circle_out" end
|
|
||||||
aZone.formation = formation
|
|
||||||
formation = cfxZones.getZoneProperty(aZone, "attackFormation")
|
|
||||||
if not formation then formation = "circle_out" end
|
|
||||||
aZone.attackFormation = formation
|
|
||||||
local spawnRadius = cfxZones.getNumberFromZoneProperty(aZone, "spawnRadius", aZone.radius-5) -- "-5" so they remaininside radius
|
|
||||||
|
|
||||||
aZone.spawnRadius = spawnRadius
|
|
||||||
|
|
||||||
local attackRadius = cfxZones.getNumberFromZoneProperty(aZone, "attackRadius", aZone.radius)
|
|
||||||
aZone.attackRadius = attackRadius
|
|
||||||
local attackDelta = cfxZones.getNumberFromZoneProperty(aZone, "attackDelta", 10) -- aZone.radius)
|
|
||||||
aZone.attackDelta = attackDelta
|
|
||||||
local attackPhi = cfxZones.getNumberFromZoneProperty(aZone, "attackPhi", 0)
|
|
||||||
aZone.attackPhi = attackPhi
|
|
||||||
|
|
||||||
local paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false)
|
|
||||||
aZone.paused = paused
|
|
||||||
|
|
||||||
if cfxZones.hasProperty(aZone, "conquered!") then
|
if cfxZones.hasProperty(aZone, "conquered!") then
|
||||||
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conquered!", "*<cfxnone>")
|
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conquered!", "*<cfxnone>")
|
||||||
elseif cfxZones.hasProperty(aZone, "conq+1") then
|
|
||||||
aZone.conqueredFlag = cfxZones.getStringFromZoneProperty(aZone, "conq+1", "*<cfxnone>")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if cfxZones.hasProperty(aZone, "redCap!") then
|
if cfxZones.hasProperty(aZone, "redCap!") then
|
||||||
aZone.redCap = cfxZones.getStringFromZoneProperty(aZone, "redCap!", "none")
|
aZone.redCap = cfxZones.getStringFromZoneProperty(aZone, "redCap!", "none")
|
||||||
end
|
end
|
||||||
|
|
||||||
if cfxZones.hasProperty(aZone, "redLost!") then
|
if cfxZones.hasProperty(aZone, "redLost!") then
|
||||||
aZone.redLost = cfxZones.getStringFromZoneProperty(aZone, "redLost!", "none")
|
aZone.redLost = cfxZones.getStringFromZoneProperty(aZone, "redLost!", "none")
|
||||||
end
|
end
|
||||||
|
|
||||||
if cfxZones.hasProperty(aZone, "blueCap!") then
|
if cfxZones.hasProperty(aZone, "blueCap!") then
|
||||||
aZone.blueCap = cfxZones.getStringFromZoneProperty(aZone, "blueCap!", "none")
|
aZone.blueCap = cfxZones.getStringFromZoneProperty(aZone, "blueCap!", "none")
|
||||||
end
|
end
|
||||||
|
|
||||||
if cfxZones.hasProperty(aZone, "blueLost!") then
|
if cfxZones.hasProperty(aZone, "blueLost!") then
|
||||||
aZone.blueLost = cfxZones.getStringFromZoneProperty(aZone, "blueLost!", "none")
|
aZone.blueLost = cfxZones.getStringFromZoneProperty(aZone, "blueLost!", "none")
|
||||||
end
|
end
|
||||||
|
|
||||||
if cfxZones.hasProperty(aZone, "neutral!") then
|
if cfxZones.hasProperty(aZone, "neutral!") then
|
||||||
aZone.neutralCap = cfxZones.getStringFromZoneProperty(aZone, "neutral!", "none")
|
aZone.neutralCap = cfxZones.getStringFromZoneProperty(aZone, "neutral!", "none")
|
||||||
end
|
end
|
||||||
|
|
||||||
if cfxZones.hasProperty(aZone, "ownedBy") then
|
if cfxZones.hasProperty(aZone, "ownedBy") then
|
||||||
aZone.ownedBy = cfxZones.getStringFromZoneProperty(aZone, "ownedBy", "none")
|
aZone.ownedBy = cfxZones.getStringFromZoneProperty(aZone, "ownedBy", "none")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- pause? and activate?
|
|
||||||
if cfxZones.hasProperty(aZone, "pause?") then
|
|
||||||
aZone.pauseFlag = cfxZones.getStringFromZoneProperty(aZone, "pause?", "none")
|
|
||||||
aZone.lastPauseValue = trigger.misc.getUserFlag(aZone.pauseFlag)
|
|
||||||
end
|
|
||||||
|
|
||||||
if cfxZones.hasProperty(aZone, "activate?") then
|
|
||||||
aZone.activateFlag = cfxZones.getStringFromZoneProperty(aZone, "activate?", "none")
|
|
||||||
aZone.lastActivateValue = trigger.misc.getUserFlag(aZone.activateFlag)
|
|
||||||
end
|
|
||||||
|
|
||||||
aZone.ownedTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
|
aZone.ownedTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
|
||||||
if cfxZones.hasProperty(aZone, "ownedTriggerMethod") then
|
if cfxZones.hasProperty(aZone, "ownedTriggerMethod") then
|
||||||
aZone.ownedTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "ownedTriggerMethod", "change")
|
aZone.ownedTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "ownedTriggerMethod", "change")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false)
|
aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false)
|
||||||
aZone.untargetable = cfxZones.getBoolFromZoneProperty(aZone, "untargetable", false)
|
|
||||||
aZone.hidden = cfxZones.getBoolFromZoneProperty(aZone, "hidden", false)
|
aZone.hidden = cfxZones.getBoolFromZoneProperty(aZone, "hidden", false)
|
||||||
cfxOwnedZones.zones[aZone] = aZone
|
cfxOwnedZones.zones[aZone] = aZone
|
||||||
cfxOwnedZones.drawZoneInMap(aZone)
|
cfxOwnedZones.drawZoneInMap(aZone)
|
||||||
cfxOwnedZones.verifyZone(aZone)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function cfxOwnedZones.verifyZone(aZone)
|
|
||||||
-- do some sanity checks
|
|
||||||
if not cfxGroundTroops and (aZone.attackersRED ~= "none" or aZone.attackersBLUE ~= "none") then
|
|
||||||
trigger.action.outText("+++owdZ: " .. aZone.name .. " attackers need cfxGroundTroops to function", 30)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.getOwnerForZone(aZone)
|
|
||||||
local theZone = cfxOwnedZones.zones[aZone]
|
|
||||||
if not theZone then return 0 end -- unknown zone, return neutral as default
|
|
||||||
return theZone.owner
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.getEnemyZonesFor(aCoalition)
|
|
||||||
local enemyZones = {}
|
|
||||||
local ourEnemy = dcsCommon.getEnemyCoalitionFor(aCoalition)
|
|
||||||
for zKey, aZone in pairs(cfxOwnedZones.zones) do
|
|
||||||
if aZone.owner == ourEnemy then -- only check enemy owned zones
|
|
||||||
-- note: will include untargetable zones
|
|
||||||
table.insert(enemyZones, aZone)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return enemyZones
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.getNearestOwnedZoneToPoint(aPoint)
|
|
||||||
local shortestDist = math.huge
|
|
||||||
local closestZone = nil
|
|
||||||
|
|
||||||
for zKey, aZone in pairs(cfxOwnedZones.zones) do
|
|
||||||
local zPoint = cfxZones.getPoint(aZone)
|
|
||||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
|
||||||
if aZone.untargetable ~= true and
|
|
||||||
currDist < shortestDist then
|
|
||||||
shortestDist = currDist
|
|
||||||
closestZone = aZone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return closestZone, shortestDist
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.getNearestOwnedZone(theZone)
|
|
||||||
local shortestDist = math.huge
|
|
||||||
local closestZone = nil
|
|
||||||
local aPoint = cfxZones.getPoint(theZone)
|
|
||||||
for zKey, aZone in pairs(cfxOwnedZones.zones) do
|
|
||||||
local zPoint = cfxZones.getPoint(aZone)
|
|
||||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
|
||||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
|
||||||
shortestDist = currDist
|
|
||||||
closestZone = aZone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return closestZone, shortestDist
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.getNearestEnemyOwnedZone(theZone, targetNeutral)
|
|
||||||
if not targetNeutral then targetNeutral = false else targetNeutral = true end
|
|
||||||
local shortestDist = math.huge
|
|
||||||
local closestZone = nil
|
|
||||||
local ourEnemy = dcsCommon.getEnemyCoalitionFor(theZone.owner)
|
|
||||||
if not ourEnemy then return nil end -- we called for a neutral zone. they have no enemies
|
|
||||||
local zPoint = cfxZones.getPoint(theZone)
|
|
||||||
|
|
||||||
for zKey, aZone in pairs(cfxOwnedZones.zones) do
|
|
||||||
if targetNeutral then
|
|
||||||
-- return all zones that do not belong to us
|
|
||||||
if aZone.owner ~= theZone.owner then
|
|
||||||
local aPoint = cfxZones.getPoint(aZone)
|
|
||||||
currDist = dcsCommon.dist(aPoint, zPoint)
|
|
||||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
|
||||||
shortestDist = currDist
|
|
||||||
closestZone = aZone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- return zones that are taken by the Enenmy
|
|
||||||
if aZone.owner == ourEnemy then -- only check own zones
|
|
||||||
local aPoint = cfxZones.getPoint(aZone)
|
|
||||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
|
||||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
|
||||||
shortestDist = currDist
|
|
||||||
closestZone = aZone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return closestZone, shortestDist
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.getNearestFriendlyZone(theZone, targetNeutral)
|
|
||||||
if not targetNeutral then targetNeutral = false else targetNeutral = true end
|
|
||||||
local shortestDist = math.huge
|
|
||||||
local closestZone = nil
|
|
||||||
local ourEnemy = dcsCommon.getEnemyCoalitionFor(theZone.owner)
|
|
||||||
if not ourEnemy then return nil end -- we called for a neutral zone. they have no enemies nor friends, all zones would be legal.
|
|
||||||
local zPoint = cfxZones.getPoint(theZone)
|
|
||||||
for zKey, aZone in pairs(cfxOwnedZones.zones) do
|
|
||||||
if targetNeutral then
|
|
||||||
-- target all zones that do not belong to the enemy
|
|
||||||
if aZone.owner ~= ourEnemy then
|
|
||||||
local aPoint = cfxZones.getPoint(aZone)
|
|
||||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
|
||||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
|
||||||
shortestDist = currDist
|
|
||||||
closestZone = aZone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- only target zones that are taken by us
|
|
||||||
if aZone.owner == theZone.owner then -- only check own zones
|
|
||||||
local aPoint = cfxZones.getPoint(aZone)
|
|
||||||
currDist = dcsCommon.dist(zPoint, aPoint)
|
|
||||||
if aZone.untargetable ~= true and currDist < shortestDist then
|
|
||||||
shortestDist = currDist
|
|
||||||
closestZone = aZone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return closestZone, shortestDist
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.enemiesRemaining(aZone)
|
|
||||||
if cfxOwnedZones.getNearestEnemyOwnedZone(aZone) then return true end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.spawnAttackTroops(theTypes, aZone, aCoalition, aFormation)
|
|
||||||
local unitTypes = {} -- build type names
|
|
||||||
-- split theTypes into an array of types
|
|
||||||
unitTypes = dcsCommon.splitString(theTypes, ",")
|
|
||||||
if #unitTypes < 1 then
|
|
||||||
table.insert(unitTypes, "Soldier M4") -- make it one m4 trooper as fallback
|
|
||||||
-- simply exit, no troops specified
|
|
||||||
if cfxOwnedZones.verbose then
|
|
||||||
trigger.action.outText("+++owdZ: no attackers for " .. aZone.name .. ". exiting", 30)
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if cfxOwnedZones.verbose then
|
|
||||||
trigger.action.outText("+++owdZ: spawning attackers for " .. aZone.name, 30)
|
|
||||||
end
|
|
||||||
|
|
||||||
--local theCountry = dcsCommon.coalition2county(aCoalition)
|
|
||||||
|
|
||||||
local spawnPoint = {x = aZone.point.x, y = aZone.point.y, z = aZone.point.z} -- copy struct
|
|
||||||
|
|
||||||
local rads = aZone.attackPhi * 0.01745
|
|
||||||
spawnPoint.x = spawnPoint.x + math.cos(aZone.attackPhi) * aZone.attackDelta
|
|
||||||
spawnPoint.y = spawnPoint.y + math.sin(aZone.attackPhi) * aZone.attackDelta
|
|
||||||
|
|
||||||
local spawnZone = cfxZones.createSimpleZone("attkSpawnZone", spawnPoint, aZone.attackRadius)
|
|
||||||
|
|
||||||
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
|
||||||
aCoalition, -- theCountry,
|
|
||||||
aZone.name .. " (A) " .. dcsCommon.numberUUID(), -- must be unique
|
|
||||||
spawnZone,
|
|
||||||
unitTypes,
|
|
||||||
aFormation, -- outward facing
|
|
||||||
0)
|
|
||||||
return theGroup, theData
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.spawnDefensiveTroops(theTypes, aZone, aCoalition, aFormation)
|
|
||||||
local unitTypes = {} -- build type names
|
|
||||||
-- split theTypes into an array of types
|
|
||||||
unitTypes = dcsCommon.splitString(theTypes, ",")
|
|
||||||
if #unitTypes < 1 then
|
|
||||||
table.insert(unitTypes, "Soldier M4") -- make it one m4 trooper as fallback
|
|
||||||
-- simply exit, no troops specified
|
|
||||||
if cfxOwnedZones.verbose then
|
|
||||||
trigger.action.outText("+++owdZ: no defenders for " .. aZone.name .. ". exiting", 30)
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
--local theCountry = dcsCommon.coalition2county(aCoalition)
|
|
||||||
local spawnZone = cfxZones.createSimpleZone("spawnZone", aZone.point, aZone.spawnRadius)
|
|
||||||
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
|
||||||
aCoalition, --theCountry,
|
|
||||||
aZone.name .. " (D) " .. dcsCommon.numberUUID(), -- must be unique
|
|
||||||
spawnZone, unitTypes,
|
|
||||||
aFormation, -- outward facing
|
|
||||||
0)
|
|
||||||
return theGroup, theData
|
|
||||||
end
|
|
||||||
--
|
--
|
||||||
-- U P D A T E
|
-- U P D A T E
|
||||||
--
|
--
|
||||||
|
|
||||||
function cfxOwnedZones.sendOutAttackers(aZone)
|
|
||||||
-- only spawn if there are zones to attack
|
|
||||||
if not cfxOwnedZones.enemiesRemaining(aZone) then
|
|
||||||
if cfxOwnedZones.verbose then
|
|
||||||
trigger.action.outText("+++owdZ - no enemies, resting ".. aZone.name, 30)
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if cfxOwnedZones.verbose then
|
|
||||||
trigger.action.outText("+++owdZ - attack cycle for ".. aZone.name, 30)
|
|
||||||
end
|
|
||||||
-- load the attacker typestring
|
|
||||||
|
|
||||||
-- step one: get the attackers
|
|
||||||
local attackers = aZone.attackersRED;
|
|
||||||
if (aZone.owner == 2) then attackers = aZone.attackersBLUE end
|
|
||||||
|
|
||||||
if attackers == "none" then return end
|
|
||||||
|
|
||||||
local theGroup, theData = cfxOwnedZones.spawnAttackTroops(attackers, aZone, aZone.owner, aZone.attackFormation)
|
|
||||||
|
|
||||||
local troopData = {}
|
|
||||||
troopData.groupData = theData
|
|
||||||
troopData.orders = "attackOwnedZone" -- lazy coding!
|
|
||||||
troopData.side = aZone.owner
|
|
||||||
cfxOwnedZones.spawnedAttackers[theData.name] = troopData
|
|
||||||
|
|
||||||
-- submit them to ground troops handler as zoneseekers
|
|
||||||
-- and our groundTroops module will handle the rest
|
|
||||||
if cfxGroundTroops then
|
|
||||||
local troops = cfxGroundTroops.createGroundTroops(theGroup)
|
|
||||||
troops.orders = "attackOwnedZone"
|
|
||||||
troops.side = aZone.owner
|
|
||||||
cfxGroundTroops.addGroundTroopsToPool(troops) -- hand off to ground troops
|
|
||||||
else
|
|
||||||
if cfxOwnedZones.verbose then
|
|
||||||
trigger.action.outText("+++ Owned Zones: no ground troops module on send out attackers", 30)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- bang support
|
|
||||||
|
|
||||||
function cfxOwnedZones.bangNeutral(value)
|
function cfxOwnedZones.bangNeutral(value)
|
||||||
if not cfxOwnedZones.neutralTriggerFlag then return end
|
if not cfxOwnedZones.neutralTriggerFlag then return end
|
||||||
local newVal = trigger.misc.getUserFlag(cfxOwnedZones.neutralTriggerFlag) + value
|
local newVal = trigger.misc.getUserFlag(cfxOwnedZones.neutralTriggerFlag) + value
|
||||||
@ -616,286 +237,7 @@ function cfxOwnedZones.zoneConquered(aZone, theSide, formerOwner) -- 0 = neutral
|
|||||||
|
|
||||||
-- update map
|
-- update map
|
||||||
cfxOwnedZones.drawZoneInMap(aZone) -- update status in map. will erase previous version
|
cfxOwnedZones.drawZoneInMap(aZone) -- update status in map. will erase previous version
|
||||||
-- remove all defenders to avoid shock state
|
|
||||||
aZone.defenders = nil
|
|
||||||
|
|
||||||
-- change to captured
|
|
||||||
|
|
||||||
aZone.state = "captured"
|
|
||||||
aZone.timeStamp = timer.getTime()
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.repairDefenders(aZone)
|
|
||||||
--trigger.action.outText("+++ enter repair for ".. aZone.name, 30)
|
|
||||||
-- find a unit that is missing from my typestring and replace it
|
|
||||||
-- one by one until we are back to full strength
|
|
||||||
-- step one: get the defenders and create a type array
|
|
||||||
local defenders = aZone.defendersRED;
|
|
||||||
if (aZone.owner == 2) then defenders = aZone.defendersBLUE end
|
|
||||||
local unitTypes = {} -- build type names
|
|
||||||
|
|
||||||
-- if none, we are done
|
|
||||||
if defenders == "none" then return end
|
|
||||||
|
|
||||||
-- split theTypes into an array of types
|
|
||||||
allTypes = dcsCommon.trimArray(
|
|
||||||
dcsCommon.splitString(defenders, ",")
|
|
||||||
)
|
|
||||||
local livingTypes = {} -- init to emtpy, so we can add to it if none are alive
|
|
||||||
if (aZone.defenders) then
|
|
||||||
-- some remain. add one of the killed
|
|
||||||
livingTypes = dcsCommon.getGroupTypes(aZone.defenders)
|
|
||||||
-- we now iterate over the living types, and remove their
|
|
||||||
-- counterparts from the allTypes. We then take the first that
|
|
||||||
-- is left
|
|
||||||
|
|
||||||
if #livingTypes > 0 then
|
|
||||||
for key, aType in pairs (livingTypes) do
|
|
||||||
if not dcsCommon.findAndRemoveFromTable(allTypes, aType) then
|
|
||||||
trigger.action.outText("+++OwdZ WARNING: found unmatched type <" .. aType .. "> while trying to repair defenders for ".. aZone.name, 30)
|
|
||||||
else
|
|
||||||
-- all good
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- when we get here, allTypes is reduced to those that have been killed
|
|
||||||
if #allTypes < 1 then
|
|
||||||
trigger.action.outText("+++owdZ: WARNING: all types exist when repairing defenders for ".. aZone.name, 30)
|
|
||||||
else
|
|
||||||
table.insert(livingTypes, allTypes[1]) -- we simply use the first that we find
|
|
||||||
end
|
|
||||||
-- remove the old defenders
|
|
||||||
if aZone.defenders then
|
|
||||||
aZone.defenders:destroy()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- now livingTypes holds the full array of units we need to spawn
|
|
||||||
local theCountry = dcsCommon.getACountryForCoalition(aZone.owner)
|
|
||||||
local spawnZone = cfxZones.createSimpleZone("spawnZone", aZone.point, aZone.spawnRadius)
|
|
||||||
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
|
||||||
aZone.owner, -- was wrongly: theCountry
|
|
||||||
aZone.name .. dcsCommon.numberUUID(), -- must be unique
|
|
||||||
spawnZone,
|
|
||||||
livingTypes,
|
|
||||||
|
|
||||||
aZone.formation, -- outward facing
|
|
||||||
0)
|
|
||||||
aZone.defenders = theGroup
|
|
||||||
aZone.lastDefenders = theGroup:getSize()
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.inShock(aZone)
|
|
||||||
-- a unit was destroyed, everyone else is in shock, no rerpairs
|
|
||||||
-- group can re-shock when another unit is destroyed
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.spawnDefenders(aZone)
|
|
||||||
local defenders = aZone.defendersRED;
|
|
||||||
|
|
||||||
if (aZone.owner == 2) then defenders = aZone.defendersBLUE end
|
|
||||||
-- before we spawn new defenders, remove the old ones
|
|
||||||
if aZone.defenders then
|
|
||||||
if aZone.defenders:isExist() then
|
|
||||||
aZone.defenders:destroy()
|
|
||||||
end
|
|
||||||
aZone.defenders = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if 'none', simply exit
|
|
||||||
if defenders == "none" then return end
|
|
||||||
|
|
||||||
local theGroup, theData = cfxOwnedZones.spawnDefensiveTroops(defenders, aZone, aZone.owner, aZone.formation)
|
|
||||||
-- the troops reamin, so no orders to move, no handing off to ground troop manager
|
|
||||||
aZone.defenders = theGroup
|
|
||||||
aZone.defenderData = theData -- used for persistence
|
|
||||||
if theGroup then
|
|
||||||
--aZone.defenderMax = theGroup:getInitialSize() -- so we can determine if some units were destroyed
|
|
||||||
aZone.lastDefenders = theGroup:getInitialSize() --- aZone.defenderMax -- if this is larger than current number, someone bit the dust
|
|
||||||
--trigger.action.outText("+++ spawned defenders for ".. aZone.name, 30)
|
|
||||||
else
|
|
||||||
trigger.action.outText("+++owdZ: WARNING: spawned no defenders for ".. aZone.name, 30)
|
|
||||||
aZone.defenderData = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--
|
|
||||||
-- per-zone update, run down the FSM to determine what to do.
|
|
||||||
-- FSM uses timeStamp since when state was set. Possible states are
|
|
||||||
-- - init -- has just been inited for the first time. will usually immediately produce defenders,
|
|
||||||
-- and then transition to defending
|
|
||||||
-- - catured -- has just been captured. transition to defending
|
|
||||||
-- - defending -- wait until timer has reached goal, then produce defending units and transition to attacking.
|
|
||||||
-- - attacking -- wait until timer has reached goal, and then produce attacking units and send them to closest enemy zone.
|
|
||||||
-- state is interrupted as soon as a defensive unit is lost. state then goes to defending with timer starting
|
|
||||||
-- - idle - do nothing, zone's actions are turned off
|
|
||||||
-- - shocked -- a unit was destroyed. group is in shock for a time until it starts repairing. If another unit is
|
|
||||||
-- destroyed during the shocked period, the timer resets to zero and repairs are delayed
|
|
||||||
-- - repairing -- as long as we aren't at full strength, units get replaced one by one until at full strength
|
|
||||||
-- each time the timer counts down, another missing unit is replaced, and all other unit's health
|
|
||||||
-- is reset to 100%
|
|
||||||
--
|
|
||||||
-- a Zone with the paused attribute set to true will cause it to not do anything
|
|
||||||
--
|
|
||||||
-- check if defenders are specified
|
|
||||||
function cfxOwnedZones.usesDefenders(aZone)
|
|
||||||
if aZone.owner == 0 then return false end
|
|
||||||
local defenders = aZone.defendersRED;
|
|
||||||
if (aZone.owner == 2) then defenders = aZone.defendersBLUE end
|
|
||||||
|
|
||||||
return defenders ~= "none"
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.usesAttackers(aZone)
|
|
||||||
if aZone.owner == 0 then return false end
|
|
||||||
local attackers = aZone.attackersRED;
|
|
||||||
if (aZone.owner == 2) then defenders = aZone.attackersBLUE end
|
|
||||||
|
|
||||||
return attackers ~= "none"
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.updateZone(aZone)
|
|
||||||
-- a zone can be paused, causing it to not progress anything
|
|
||||||
-- even if zone status is still init, will NOT produce anything
|
|
||||||
-- if paused is on.
|
|
||||||
if aZone.paused then return end
|
|
||||||
|
|
||||||
nextState = aZone.state;
|
|
||||||
|
|
||||||
-- first, check if my defenders have been attacked and one of them has been killed
|
|
||||||
-- if so, we immediately switch to 'shocked'
|
|
||||||
if cfxOwnedZones.usesDefenders(aZone) and
|
|
||||||
aZone.defenders then
|
|
||||||
-- we have defenders
|
|
||||||
if aZone.defenders:isExist() then
|
|
||||||
-- isee if group was damaged
|
|
||||||
if not aZone.lastDefenders then
|
|
||||||
-- fresh group, probably from persistence, needs init
|
|
||||||
aZone.lastDefenders = -1
|
|
||||||
end
|
|
||||||
if aZone.defenders:getSize() < aZone.lastDefenders then
|
|
||||||
-- yes, at least one unit destroyed
|
|
||||||
aZone.timeStamp = timer.getTime()
|
|
||||||
aZone.lastDefenders = aZone.defenders:getSize()
|
|
||||||
if aZone.lastDefenders == 0 then
|
|
||||||
aZone.defenders = nil
|
|
||||||
end
|
|
||||||
aZone.state = "shocked"
|
|
||||||
|
|
||||||
return
|
|
||||||
else
|
|
||||||
aZone.lastDefenders = aZone.defenders:getSize()
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
-- group was destroyed. erase link, and go into shock for the last time
|
|
||||||
aZone.state = "shocked"
|
|
||||||
aZone.timeStamp = timer.getTime()
|
|
||||||
aZone.lastDefenders = 0
|
|
||||||
aZone.defenders = nil
|
|
||||||
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
if aZone.state == "init" then
|
|
||||||
-- during init we instantly create the defenders since
|
|
||||||
-- we assume the zone existed already
|
|
||||||
if aZone.owner > 0 then
|
|
||||||
cfxOwnedZones.spawnDefenders(aZone)
|
|
||||||
-- now drop into attacking mode to produce attackers
|
|
||||||
nextState = "attacking"
|
|
||||||
else
|
|
||||||
nextState = "idle"
|
|
||||||
end
|
|
||||||
|
|
||||||
aZone.timeStamp = timer.getTime()
|
|
||||||
|
|
||||||
elseif aZone.state == "idle" then
|
|
||||||
-- nothing to do, zone is effectively switched off.
|
|
||||||
-- used for neutal zones or when forced to turn off
|
|
||||||
-- in some special cases
|
|
||||||
|
|
||||||
elseif aZone.state == "captured" then
|
|
||||||
-- start the clock on defenders
|
|
||||||
nextState = "defending"
|
|
||||||
aZone.timeStamp = timer.getTime()
|
|
||||||
if cfxOwnedZones.verbose then
|
|
||||||
trigger.action.outText("+++owdZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30)
|
|
||||||
end
|
|
||||||
elseif aZone.state == "defending" then
|
|
||||||
if timer.getTime() > aZone.timeStamp + cfxOwnedZones.defendingTime then
|
|
||||||
cfxOwnedZones.spawnDefenders(aZone)
|
|
||||||
-- now drop into attacking mode to produce attackers
|
|
||||||
nextState = "attacking"
|
|
||||||
aZone.timeStamp = timer.getTime()
|
|
||||||
if cfxOwnedZones.verbose then
|
|
||||||
trigger.action.outText("+++owdZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif aZone.state == "repairing" then
|
|
||||||
-- we are currently rebuilding defenders unit by unit
|
|
||||||
if timer.getTime() > aZone.timeStamp + cfxOwnedZones.repairTime then
|
|
||||||
aZone.timeStamp = timer.getTime()
|
|
||||||
-- wait's up, repair one defender, then check if full strength
|
|
||||||
cfxOwnedZones.repairDefenders(aZone)
|
|
||||||
-- see if we are full strenght and if so go to attack, else set timer to reair the next unit
|
|
||||||
if aZone.defenders and aZone.defenders:isExist() and aZone.defenders:getSize() >= aZone.defenders:getInitialSize() then
|
|
||||||
-- we are at max size, time to produce some attackers
|
|
||||||
-- progress to next state
|
|
||||||
nextState = "attacking"
|
|
||||||
aZone.timeStamp = timer.getTime()
|
|
||||||
if cfxOwnedZones.verbose then
|
|
||||||
trigger.action.outText("+++owdZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif aZone.state == "shocked" then
|
|
||||||
-- we are currently rebuilding defenders unit by unit
|
|
||||||
if timer.getTime() > aZone.timeStamp + cfxOwnedZones.shockTime then
|
|
||||||
nextState = "repairing"
|
|
||||||
aZone.timeStamp = timer.getTime()
|
|
||||||
if cfxOwnedZones.verbose then
|
|
||||||
trigger.action.outText("+++owdZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif aZone.state == "attacking" then
|
|
||||||
if timer.getTime() > aZone.timeStamp + cfxOwnedZones.attackingTime then
|
|
||||||
cfxOwnedZones.sendOutAttackers(aZone)
|
|
||||||
-- reset timer
|
|
||||||
aZone.timeStamp = timer.getTime()
|
|
||||||
if cfxOwnedZones.verbose then
|
|
||||||
trigger.action.outText("+++owdZ: State " .. aZone.state .. " reset for " .. aZone.name, 30)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- unknown zone state
|
|
||||||
end
|
|
||||||
aZone.state = nextState
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.GC()
|
|
||||||
-- GC run. remove all my dead remembered troops
|
|
||||||
local before = #cfxOwnedZones.spawnedAttackers
|
|
||||||
local filteredAttackers = {}
|
|
||||||
for gName, gData in pairs (cfxOwnedZones.spawnedAttackers) do
|
|
||||||
-- all we need to do is get the group of that name
|
|
||||||
-- and if it still returns units we are fine
|
|
||||||
local gameGroup = Group.getByName(gName)
|
|
||||||
if gameGroup and gameGroup:isExist() and gameGroup:getSize() > 0 then
|
|
||||||
filteredAttackers[gName] = gData
|
|
||||||
end
|
|
||||||
end
|
|
||||||
cfxOwnedZones.spawnedAttackers = filteredAttackers
|
|
||||||
if cfxOwnedZones.verbose then
|
|
||||||
trigger.action.outText("owned zones GC ran: before <" .. before .. ">, after <" .. #cfxOwnedZones.spawnedAttackers .. ">", 30)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function cfxOwnedZones.update()
|
function cfxOwnedZones.update()
|
||||||
@ -951,9 +293,15 @@ function cfxOwnedZones.update()
|
|||||||
-- trigger.action.outText(theZone.name .. " blue: " .. theZone.numBlue .. " red " .. theZone.numRed, 30)
|
-- trigger.action.outText(theZone.name .. " blue: " .. theZone.numBlue .. " red " .. theZone.numRed, 30)
|
||||||
local lastOwner = theZone.owner
|
local lastOwner = theZone.owner
|
||||||
local newOwner = 0 -- neutral is default
|
local newOwner = 0 -- neutral is default
|
||||||
|
if theZone.unbeatable then -- Parker Lewis can't lose. Neither this zone.
|
||||||
|
newOwner = lastOwner
|
||||||
|
end
|
||||||
|
|
||||||
-- determine new owner
|
-- determine new owner
|
||||||
-- step one: no troops here. Become neutral?
|
if theZone.unbeatable then
|
||||||
if theZone.numRed < 1 and theZone.numBlue < 1 then
|
-- we do nothing
|
||||||
|
elseif theZone.numRed < 1 and theZone.numBlue < 1 then
|
||||||
|
-- no troops here. Become neutral?
|
||||||
if cfxOwnedZones.numKeep < 1 then
|
if cfxOwnedZones.numKeep < 1 then
|
||||||
newOwner = lastOwner -- keep it, else turns neutral
|
newOwner = lastOwner -- keep it, else turns neutral
|
||||||
else
|
else
|
||||||
@ -1018,71 +366,13 @@ function cfxOwnedZones.update()
|
|||||||
end
|
end
|
||||||
theZone.owner = newOwner
|
theZone.owner = newOwner
|
||||||
|
|
||||||
-- production & flags
|
|
||||||
-- see if pause/unpause was issued
|
|
||||||
-- note that capping a zone will not change pause status
|
|
||||||
if theZone.pauseFlag and cfxZones.testZoneFlag(theZone, theZone.pauseFlag, theZone.ownedTriggerMethod, "lastPauseValue") then
|
|
||||||
theZone.paused = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if theZone.activateFlag and cfxZones.testZoneFlag(theZone, theZone.activateFlag, theZone.ownedTriggerMethod, "lastActivateValue") then
|
|
||||||
theZone.paused = false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- update ownership flag if exists
|
-- update ownership flag if exists
|
||||||
if theZone.ownedBy then
|
if theZone.ownedBy then
|
||||||
cfxZones.setFlagValue(theZone.ownedBy, theZone.owner, theZone)
|
cfxZones.setFlagValue(theZone.ownedBy, theZone.owner, theZone)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- now, perhaps with their new owner call updateZone()
|
|
||||||
-- to calcualte production for this zone
|
|
||||||
cfxOwnedZones.updateZone(theZone)
|
|
||||||
end -- iterating all zones
|
end -- iterating all zones
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function cfxOwnedZones.updateOLD()
|
|
||||||
cfxOwnedZones.updateSchedule = timer.scheduleFunction(cfxOwnedZones.updateOLD, {}, timer.getTime() + 1/cfxOwnedZones.ups)
|
|
||||||
|
|
||||||
-- iterate all zones, and determine their current ownership status
|
|
||||||
for key, aZone in pairs(cfxOwnedZones.zones) do
|
|
||||||
-- a hand change can only happen if there are only ground troops from the OTHER side in
|
|
||||||
-- the zone
|
|
||||||
local categ = Group.Category.GROUND
|
|
||||||
local theBlues = cfxZones.groupsOfCoalitionPartiallyInZone(2, aZone, categ)
|
|
||||||
local theReds = cfxZones.groupsOfCoalitionPartiallyInZone(1, aZone, categ)
|
|
||||||
local currentOwner = aZone.owner
|
|
||||||
if #theBlues > 0 and #theReds == 0 and aZone.unbeatable ~= true then
|
|
||||||
-- this now belongs to blue
|
|
||||||
if currentOwner ~= 2 then
|
|
||||||
cfxOwnedZones.zoneConquered(aZone, 2, currentOwner)
|
|
||||||
end
|
|
||||||
elseif #theBlues == 0 and #theReds > 0 and aZone.unbeatable ~= true then
|
|
||||||
-- this now belongs to red
|
|
||||||
if currentOwner ~= 1 then
|
|
||||||
cfxOwnedZones.zoneConquered(aZone, 1, currentOwner)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- see if pause/unpause was issued
|
|
||||||
-- note that capping a zone will not change pause status
|
|
||||||
if aZone.pauseFlag and cfxZones.testZoneFlag(aZone, aZone.pauseFlag, aZone.ownedTriggerMethod, "lastPauseValue") then
|
|
||||||
aZone.paused = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if aZone.activateFlag and cfxZones.testZoneFlag(aZone, aZone.activateFlag, aZone.ownedTriggerMethod, "lastActivateValue") then
|
|
||||||
aZone.paused = false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- now, perhaps with their new owner call updateZone()
|
|
||||||
cfxOwnedZones.updateZone(aZone)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxOwnedZones.houseKeeping()
|
|
||||||
timer.scheduleFunction(cfxOwnedZones.houseKeeping, {}, timer.getTime() + 5 * 60) -- every 5 minutes
|
|
||||||
cfxOwnedZones.GC()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function cfxOwnedZones.sideOwnsAll(theSide)
|
function cfxOwnedZones.sideOwnsAll(theSide)
|
||||||
@ -1116,37 +406,17 @@ function cfxOwnedZones.saveData()
|
|||||||
-- iterate all my zones and create data
|
-- iterate all my zones and create data
|
||||||
for idx, theZone in pairs(cfxOwnedZones.zones) do
|
for idx, theZone in pairs(cfxOwnedZones.zones) do
|
||||||
local zoneData = {}
|
local zoneData = {}
|
||||||
if theZone.defenderData then
|
|
||||||
zoneData.defenderData = dcsCommon.clone(theZone.defenderData)
|
|
||||||
dcsCommon.synchGroupData(zoneData.defenderData)
|
|
||||||
end
|
|
||||||
if theZone.conqueredFlag then
|
if theZone.conqueredFlag then
|
||||||
zoneData.conquered = cfxZones.getFlagValue(theZone.conqueredFlag, theZone)
|
zoneData.conquered = cfxZones.getFlagValue(theZone.conqueredFlag, theZone)
|
||||||
end
|
end
|
||||||
|
|
||||||
zoneData.owner = theZone.owner
|
zoneData.owner = theZone.owner
|
||||||
zoneData.state = theZone.state -- will prevent immediate spawn
|
|
||||||
-- since new zones are spawned with 'init'
|
|
||||||
allZoneData[theZone.name] = zoneData
|
allZoneData[theZone.name] = zoneData
|
||||||
end
|
end
|
||||||
|
|
||||||
-- now iterate all attack groups that we have spawned and that
|
-- now iterate all attack groups that we have spawned and that
|
||||||
-- (maybe) are still alive
|
-- (maybe) are still alive
|
||||||
cfxOwnedZones.GC() -- start with a GC run to remove all dead
|
|
||||||
local livingAttackers = {}
|
|
||||||
for gName, gData in pairs (cfxOwnedZones.spawnedAttackers) do
|
|
||||||
-- all we need to do is get the group of that name
|
|
||||||
-- and if it still returns units we are fine
|
|
||||||
-- spawnedAttackers is a [groupName] table with {.groupData, .orders, .side}
|
|
||||||
local gameGroup = Group.getByName(gName)
|
|
||||||
if gameGroup and gameGroup:isExist() then
|
|
||||||
if gameGroup:getSize() > 0 then
|
|
||||||
local sData = dcsCommon.clone(gData)
|
|
||||||
dcsCommon.synchGroupData(sData.groupData)
|
|
||||||
livingAttackers[gName] = sData
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- now write the info for the flags that we output for #red, etc
|
-- now write the info for the flags that we output for #red, etc
|
||||||
local flagInfo = {}
|
local flagInfo = {}
|
||||||
flagInfo.neutral = cfxZones.getFlagValue(cfxOwnedZones.neutralTriggerFlag, cfxOwnedZones)
|
flagInfo.neutral = cfxZones.getFlagValue(cfxOwnedZones.neutralTriggerFlag, cfxOwnedZones)
|
||||||
@ -1154,7 +424,6 @@ function cfxOwnedZones.saveData()
|
|||||||
flagInfo.blue = cfxZones.getFlagValue(cfxOwnedZones.blueTriggerFlag, cfxOwnedZones)
|
flagInfo.blue = cfxZones.getFlagValue(cfxOwnedZones.blueTriggerFlag, cfxOwnedZones)
|
||||||
-- assemble the data
|
-- assemble the data
|
||||||
theData.zoneData = allZoneData
|
theData.zoneData = allZoneData
|
||||||
theData.attackers = livingAttackers
|
|
||||||
theData.flagInfo = flagInfo
|
theData.flagInfo = flagInfo
|
||||||
|
|
||||||
-- return it
|
-- return it
|
||||||
@ -1180,19 +449,7 @@ function cfxOwnedZones.loadData()
|
|||||||
-- access zone
|
-- access zone
|
||||||
local theZone = cfxOwnedZones.getOwnedZoneByName(zName)
|
local theZone = cfxOwnedZones.getOwnedZoneByName(zName)
|
||||||
if theZone then
|
if theZone then
|
||||||
if zData.defenderData then
|
|
||||||
if theZone.defenders and theZone.defenders:isExist() then
|
|
||||||
-- should not happen, but so be it
|
|
||||||
theZone.defenders:destroy()
|
|
||||||
end
|
|
||||||
local gData = zData.defenderData
|
|
||||||
local cty = gData.cty
|
|
||||||
local cat = gData.cat
|
|
||||||
theZone.defenders = coalition.addGroup(cty, cat, gData)
|
|
||||||
theZone.defenderData = zData.defenderData
|
|
||||||
end
|
|
||||||
theZone.owner = zData.owner
|
theZone.owner = zData.owner
|
||||||
theZone.state = zData.state
|
|
||||||
if zData.conquered then
|
if zData.conquered then
|
||||||
cfxZones.setFlagValue(theZone.conqueredFlag, zData.conquered, theZone)
|
cfxZones.setFlagValue(theZone.conqueredFlag, zData.conquered, theZone)
|
||||||
end
|
end
|
||||||
@ -1203,27 +460,6 @@ function cfxOwnedZones.loadData()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- now process all attackers
|
|
||||||
local allAttackers = theData.attackers
|
|
||||||
for gName, gdTroop in pairs(allAttackers) do
|
|
||||||
-- table is {.groupData, .orders, .side}
|
|
||||||
local gData = gdTroop.groupData
|
|
||||||
local orders = gdTroop.orders
|
|
||||||
local side = gdTroop.side
|
|
||||||
local cty = gData.cty
|
|
||||||
local cat = gData.cat
|
|
||||||
-- add to my own attacker queue so we can save later
|
|
||||||
local dClone = dcsCommon.clone(gdTroop)
|
|
||||||
cfxOwnedZones.spawnedAttackers[gName] = dClone
|
|
||||||
local theGroup = coalition.addGroup(cty, cat, gData)
|
|
||||||
if cfxGroundTroops then
|
|
||||||
local troops = cfxGroundTroops.createGroundTroops(theGroup)
|
|
||||||
troops.orders = orders
|
|
||||||
troops.side = side
|
|
||||||
cfxGroundTroops.addGroundTroopsToPool(troops) -- hand off to ground troops
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- now process module global flags
|
-- now process module global flags
|
||||||
local flagInfo = theData.flagInfo
|
local flagInfo = theData.flagInfo
|
||||||
if flagInfo then
|
if flagInfo then
|
||||||
@ -1242,19 +478,10 @@ function cfxOwnedZones.readConfigZone(theZone)
|
|||||||
cfxOwnedZones.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
cfxOwnedZones.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||||
cfxOwnedZones.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
|
cfxOwnedZones.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
|
||||||
|
|
||||||
-- if cfxZones.hasProperty(theZone, "r!") then
|
cfxOwnedZones.redTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "r!", "*<cfxnone>")
|
||||||
cfxOwnedZones.redTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "r!", "*<cfxnone>")
|
cfxOwnedZones.blueTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "b!", "*<cfxnone>")
|
||||||
-- end
|
cfxOwnedZones.neutralTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "n!", "*<cfxnone>")
|
||||||
-- if cfxZones.hasProperty(theZone, "b!") then
|
|
||||||
cfxOwnedZones.blueTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "b!", "*<cfxnone>")
|
|
||||||
-- end
|
|
||||||
-- if cfxZones.hasProperty(theZone, "n!") then
|
|
||||||
cfxOwnedZones.neutralTriggerFlag = cfxZones.getStringFromZoneProperty(theZone, "n!", "*<cfxnone>")
|
|
||||||
-- end
|
|
||||||
cfxOwnedZones.defendingTime = cfxZones.getNumberFromZoneProperty(theZone, "defendingTime", 100)
|
|
||||||
cfxOwnedZones.attackingTime = cfxZones.getNumberFromZoneProperty(theZone, "attackingTime", 300)
|
|
||||||
cfxOwnedZones.shockTime = cfxZones.getNumberFromZoneProperty(theZone, "shockTime", 200)
|
|
||||||
cfxOwnedZones.repairTime = cfxZones.getNumberFromZoneProperty(theZone, "repairTime", 200)
|
|
||||||
-- numKeep, numCap, fastEval, easyContest
|
-- numKeep, numCap, fastEval, easyContest
|
||||||
cfxOwnedZones.numCap = cfxZones.getNumberFromZoneProperty(theZone, "numCap", 1) -- minimal number of units required to cap zone
|
cfxOwnedZones.numCap = cfxZones.getNumberFromZoneProperty(theZone, "numCap", 1) -- minimal number of units required to cap zone
|
||||||
cfxOwnedZones.numKeep = cfxZones.getNumberFromZoneProperty(theZone, "numKeep", 0) -- number required to keep zone
|
cfxOwnedZones.numKeep = cfxZones.getNumberFromZoneProperty(theZone, "numKeep", 0) -- number required to keep zone
|
||||||
@ -1299,11 +526,7 @@ function cfxOwnedZones.init()
|
|||||||
initialized = true
|
initialized = true
|
||||||
cfxOwnedZones.updateSchedule = timer.scheduleFunction(cfxOwnedZones.update, {}, timer.getTime() + 1/cfxOwnedZones.ups)
|
cfxOwnedZones.updateSchedule = timer.scheduleFunction(cfxOwnedZones.update, {}, timer.getTime() + 1/cfxOwnedZones.ups)
|
||||||
|
|
||||||
-- start housekeeping
|
|
||||||
cfxOwnedZones.houseKeeping()
|
|
||||||
|
|
||||||
trigger.action.outText("cx/x owned zones v".. cfxOwnedZones.version .. " started", 30)
|
trigger.action.outText("cx/x owned zones v".. cfxOwnedZones.version .. " started", 30)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,294 +1,87 @@
|
|||||||
cfxPlayerScoreUI = {}
|
cfxPlayerScoreUI = {}
|
||||||
cfxPlayerScoreUI.version = "1.0.3"
|
cfxPlayerScoreUI.version = "2.0.0"
|
||||||
|
cfxPlayerScoreUI.verbose = false
|
||||||
|
|
||||||
--[[-- VERSION HISTORY
|
--[[-- VERSION HISTORY
|
||||||
- 1.0.2 - initial version
|
- 1.0.2 - initial version
|
||||||
- 1.0.3 - module check
|
- 1.0.3 - module check
|
||||||
|
- 2.0.0 - removed cfxPlayer dependency, handles own commands
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
-- WARNING: REQUIRES cfxPlayerScore to work.
|
cfxPlayerScoreUI.rootCommands = {} -- by unit's group name, for player aircraft
|
||||||
-- WARNING: ASSUMES SINGLE_PLAYER GROUPS!
|
|
||||||
cfxPlayerScoreUI.requiredLibs = {
|
|
||||||
"cfxPlayerScore", -- this is doing score keeping
|
|
||||||
"cfxPlayer", -- player events, comms
|
|
||||||
}
|
|
||||||
-- find & command cfxGroundTroops-based jtacs
|
|
||||||
-- UI installed via OTHER for all groups with players
|
|
||||||
-- module based on xxxGrpUI and jtacUI
|
|
||||||
|
|
||||||
cfxPlayerScoreUI.groupConfig = {} -- all inited group private config data
|
|
||||||
cfxPlayerScoreUI.simpleCommands = true -- if true, f10 other invokes directly
|
|
||||||
|
|
||||||
--
|
|
||||||
-- C O N F I G H A N D L I N G
|
|
||||||
-- =============================
|
|
||||||
--
|
|
||||||
-- Each group has their own config block that can be used to
|
|
||||||
-- store group-private data and configuration items.
|
|
||||||
--
|
|
||||||
|
|
||||||
function cfxPlayerScoreUI.resetConfig(conf)
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxPlayerScoreUI.createDefaultConfig(theGroup)
|
|
||||||
local conf = {}
|
|
||||||
conf.theGroup = theGroup
|
|
||||||
conf.name = theGroup:getName()
|
|
||||||
conf.id = theGroup:getID()
|
|
||||||
conf.coalition = theGroup:getCoalition()
|
|
||||||
|
|
||||||
cfxPlayerScoreUI.resetConfig(conf)
|
|
||||||
|
|
||||||
conf.mainMenu = nil; -- this is where we store the main menu if we branch
|
|
||||||
conf.myCommands = nil; -- this is where we store the commands if we branch
|
|
||||||
|
|
||||||
return conf
|
|
||||||
end
|
|
||||||
|
|
||||||
-- getConfigFor group will allocate if doesn't exist in DB
|
|
||||||
-- and add to it
|
|
||||||
function cfxPlayerScoreUI.getConfigForGroup(theGroup)
|
|
||||||
if not theGroup then
|
|
||||||
trigger.action.outText("+++WARNING: cfxPlayerScoreUI nil group in getConfigForGroup!", 30)
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local theName = theGroup:getName()
|
|
||||||
local c = cfxPlayerScoreUI.getConfigByGroupName(theName) -- we use central accessor
|
|
||||||
if not c then
|
|
||||||
c = cfxPlayerScoreUI.createDefaultConfig(theGroup)
|
|
||||||
cfxPlayerScoreUI.groupConfig[theName] = c -- should use central accessor...
|
|
||||||
end
|
|
||||||
return c
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxPlayerScoreUI.getConfigByGroupName(theName) -- DOES NOT allocate when not exist
|
|
||||||
if not theName then return nil end
|
|
||||||
return cfxPlayerScoreUI.groupConfig[theName]
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function cfxPlayerScoreUI.getConfigForUnit(theUnit)
|
|
||||||
-- simple one-off step by accessing the group
|
|
||||||
if not theUnit then
|
|
||||||
trigger.action.outText("+++WARNING: cfxPlayerScoreUI nil unit in getConfigForUnit!", 30)
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local theGroup = theUnit:getGroup()
|
|
||||||
return getConfigForGroup(theGroup)
|
|
||||||
end
|
|
||||||
|
|
||||||
--
|
|
||||||
--
|
|
||||||
-- M E N U H A N D L I N G
|
|
||||||
-- =========================
|
|
||||||
--
|
|
||||||
--
|
|
||||||
function cfxPlayerScoreUI.clearCommsSubmenus(conf)
|
|
||||||
if conf.myCommands then
|
|
||||||
for i=1, #conf.myCommands do
|
|
||||||
missionCommands.removeItemForGroup(conf.id, conf.myCommands[i])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
conf.myCommands = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxPlayerScoreUI.removeCommsFromConfig(conf)
|
|
||||||
cfxPlayerScoreUI.clearCommsSubmenus(conf)
|
|
||||||
|
|
||||||
if conf.myMainMenu then
|
|
||||||
missionCommands.removeItemForGroup(conf.id, conf.myMainMenu)
|
|
||||||
conf.myMainMenu = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- this only works in single-unit groups. may want to check if group
|
|
||||||
-- has disappeared
|
|
||||||
function cfxPlayerScoreUI.removeCommsForUnit(theUnit)
|
|
||||||
if not theUnit then return end
|
|
||||||
if not theUnit:isExist() then return end
|
|
||||||
-- perhaps add code: check if group is empty
|
|
||||||
local conf = cfxPlayerScoreUI.getConfigForUnit(theUnit)
|
|
||||||
cfxPlayerScoreUI.removeCommsFromConfig(conf)
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxPlayerScoreUI.removeCommsForGroup(theGroup)
|
|
||||||
if not theGroup then return end
|
|
||||||
if not theGroup:isExist() then return end
|
|
||||||
local conf = cfxPlayerScoreUI.getConfigForGroup(theGroup)
|
|
||||||
cfxPlayerScoreUI.removeCommsFromConfig(conf)
|
|
||||||
end
|
|
||||||
|
|
||||||
--
|
|
||||||
-- set main root in F10 Other. All sub menus click into this
|
|
||||||
--
|
|
||||||
--function cfxPlayerScoreUI.isEligibleForMenu(theGroup)
|
|
||||||
-- return true
|
|
||||||
--end
|
|
||||||
|
|
||||||
function cfxPlayerScoreUI.setCommsMenuForUnit(theUnit)
|
|
||||||
if not theUnit then
|
|
||||||
trigger.action.outText("+++WARNING: cfxPlayerScoreUI nil UNIT in setCommsMenuForUnit!", 30)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if not theUnit:isExist() then return end
|
|
||||||
|
|
||||||
local theGroup = theUnit:getGroup()
|
|
||||||
cfxPlayerScoreUI.setCommsMenu(theGroup)
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxPlayerScoreUI.setCommsMenu(theGroup)
|
|
||||||
-- depending on own load state, we set the command structure
|
|
||||||
-- it begins at 10-other, and has 'grpUI' as main menu with submenus
|
|
||||||
-- as required
|
|
||||||
if not theGroup then return end
|
|
||||||
if not theGroup:isExist() then return end
|
|
||||||
|
|
||||||
-- we test here if this group qualifies for
|
|
||||||
-- the menu. if not, exit
|
|
||||||
--if not cfxPlayerScoreUI.isEligibleForMenu(theGroup) then return end
|
|
||||||
|
|
||||||
local conf = cfxPlayerScoreUI.getConfigForGroup(theGroup)
|
|
||||||
conf.id = theGroup:getID(); -- we do this ALWAYS so it is current even after a crash
|
|
||||||
|
|
||||||
|
|
||||||
if cfxPlayerScoreUI.simpleCommands then
|
|
||||||
-- we install directly in F-10 other
|
|
||||||
if not conf.myMainMenu then
|
|
||||||
local commandTxt = "Score / Kills"
|
|
||||||
local theCommand = missionCommands.addCommandForGroup(
|
|
||||||
conf.id,
|
|
||||||
commandTxt,
|
|
||||||
nil,
|
|
||||||
cfxPlayerScoreUI.redirectCommandX,
|
|
||||||
{conf, "score"}
|
|
||||||
)
|
|
||||||
conf.myMainMenu = theCommand
|
|
||||||
end
|
|
||||||
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- ok, first, if we don't have an F-10 menu, create one
|
|
||||||
if not (conf.myMainMenu) then
|
|
||||||
conf.myMainMenu = missionCommands.addSubMenuForGroup(conf.id, 'Score / Kills')
|
|
||||||
end
|
|
||||||
|
|
||||||
-- clear out existing commands
|
|
||||||
cfxPlayerScoreUI.clearCommsSubmenus(conf)
|
|
||||||
|
|
||||||
-- now we have a menu without submenus.
|
|
||||||
-- add our own submenus
|
|
||||||
cfxPlayerScoreUI.addSubMenus(conf)
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function cfxPlayerScoreUI.addSubMenus(conf)
|
|
||||||
-- add menu items to choose from after
|
|
||||||
-- user clickedf on MAIN MENU. In this implementation
|
|
||||||
-- they all result invoked methods
|
|
||||||
|
|
||||||
local commandTxt = "Show Score / Kills"
|
|
||||||
local theCommand = missionCommands.addCommandForGroup(
|
|
||||||
conf.id,
|
|
||||||
commandTxt,
|
|
||||||
conf.myMainMenu,
|
|
||||||
cfxPlayerScoreUI.redirectCommandX,
|
|
||||||
{conf, "score"}
|
|
||||||
)
|
|
||||||
table.insert(conf.myCommands, theCommand)
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
--
|
|
||||||
-- each menu item has a redirect and timed invoke to divorce from the
|
|
||||||
-- no-debug zone in the menu invocation. Delay is .1 seconds
|
|
||||||
--
|
|
||||||
|
|
||||||
|
-- redirect: avoid the debug environ of missionCommand
|
||||||
function cfxPlayerScoreUI.redirectCommandX(args)
|
function cfxPlayerScoreUI.redirectCommandX(args)
|
||||||
timer.scheduleFunction(cfxPlayerScoreUI.doCommandX, args, timer.getTime() + 0.1)
|
timer.scheduleFunction(cfxPlayerScoreUI.doCommandX, args, timer.getTime() + 0.1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function cfxPlayerScoreUI.doCommandX(args)
|
function cfxPlayerScoreUI.doCommandX(args)
|
||||||
local conf = args[1] -- < conf in here
|
local groupName = args[1]
|
||||||
local what = args[2] -- < second argument in here
|
local playerName = args[2]
|
||||||
local theGroup = conf.theGroup
|
local what = args[3] -- "score" or other commands
|
||||||
-- now fetch the first player that drives a unit in this group
|
local theGroup = Group.getByName(groupName)
|
||||||
-- a simpler method would be to access conf.primeUnit
|
local gid = theGroup:getID()
|
||||||
|
|
||||||
local playerName, playerUnit = cfxPlayer.getFirstGroupPlayerName(theGroup)
|
if not cfxPlayerScore.scoreTextForPlayerNamed then
|
||||||
if playerName == nil or playerUnit == nil then
|
trigger.action.outText("***pSGui: CANNOT FIND PlayerScore MODULE", 30)
|
||||||
trigger.action.outText("scoreUI: nil player name or unit for group " .. theGroup:getName(), 30)
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local desc = cfxPlayerScore.scoreTextForPlayerNamed(playerName)
|
local desc = cfxPlayerScore.scoreTextForPlayerNamed(playerName)
|
||||||
|
trigger.action.outTextForGroup(gid, desc, 30)
|
||||||
trigger.action.outTextForGroup(conf.id, desc, 30)
|
trigger.action.outSoundForGroup(gid, "Quest Snare 3.wav")
|
||||||
trigger.action.outSoundForGroup(conf.id, "Quest Snare 3.wav")
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- G R O U P M A N A G E M E N T
|
-- event handling: we are only interested in birth events
|
||||||
|
-- for player aircraft
|
||||||
--
|
--
|
||||||
-- Group Management is required to make sure all groups
|
function cfxPlayerScoreUI:onEvent(event)
|
||||||
-- receive a comms menu and that they receive a clean-up
|
if event.id ~= 15 then return end -- only birth
|
||||||
-- when required
|
if not event.initiator then return end -- no initiator, no joy
|
||||||
--
|
local theUnit = event.initiator
|
||||||
-- Callbacks are provided by cfxPlayer module to which we
|
if not theUnit.getPlayerName then return end -- no player name, bye!
|
||||||
-- subscribe during init
|
local playerName = theUnit:getPlayerName()
|
||||||
--
|
if not playerName then return end
|
||||||
function cfxPlayerScoreUI.playerChangeEvent(evType, description, player, data)
|
|
||||||
|
|
||||||
if evType == "newGroup" then
|
|
||||||
|
|
||||||
cfxPlayerScoreUI.setCommsMenu(data.group)
|
|
||||||
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if evType == "removeGroup" then
|
-- so now we know it's a player plane. get group name
|
||||||
|
local theGroup = theUnit:getGroup()
|
||||||
-- we must remove the comms menu for this group else we try to add another one to this group later
|
local groupName = theGroup:getName()
|
||||||
local conf = cfxPlayerScoreUI.getConfigByGroupName(data.name)
|
local gid = theGroup:getID()
|
||||||
|
|
||||||
if conf then
|
-- see if this group already has a score command
|
||||||
cfxPlayerScoreUI.removeCommsFromConfig(conf) -- remove menus
|
if cfxPlayerScoreUI.rootCommands[groupName] then
|
||||||
cfxPlayerScoreUI.resetConfig(conf) -- re-init this group for when it re-appears
|
-- need re-init to store new pilot name
|
||||||
else
|
if cfxPlayerScoreUI.verbose then
|
||||||
trigger.action.outText("+++ scoreUI: can't retrieve group <" .. data.name .. "> config: not found!", 30)
|
trigger.action.outText("++pSGui: group <" .. groupName .. "> already has score menu, removing.", 30)
|
||||||
end
|
end
|
||||||
|
missionCommands.removeItemForGroup(gid, cfxPlayerScoreUI.rootCommands[groupName])
|
||||||
return
|
cfxPlayerScoreUI.rootCommands[groupName] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- we need to install a group menu item for scores.
|
||||||
|
-- will persist through death
|
||||||
|
local commandTxt = "Show Score / Kills"
|
||||||
|
local theCommand = missionCommands.addCommandForGroup(
|
||||||
|
gid,
|
||||||
|
commandTxt,
|
||||||
|
nil, -- root level
|
||||||
|
cfxPlayerScoreUI.redirectCommandX,
|
||||||
|
{groupName, playerName, "score"}
|
||||||
|
)
|
||||||
|
cfxPlayerScoreUI.rootCommands[groupName] = theCommand
|
||||||
|
|
||||||
|
if cfxPlayerScoreUI.verbose then
|
||||||
|
trigger.action.outText("++pSGui: installed player score menu for group <" .. groupName .. ">", 30)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Start
|
-- Start
|
||||||
--
|
--
|
||||||
|
function cfxPlayerScoreUI.start()
|
||||||
function cfxPlayerScoreUI.start()
|
-- install the event handler for new player planes
|
||||||
if not dcsCommon.libCheck("cfx PlayerScoreUI",
|
world.addEventHandler(cfxPlayerScoreUI)
|
||||||
cfxPlayerScoreUI.requiredLibs)
|
|
||||||
then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
-- iterate existing groups so we have a start situation
|
|
||||||
-- now iterate through all player groups and install the Assault Troop Menu
|
|
||||||
allPlayerGroups = cfxPlayerGroups -- cfxPlayerGroups is a global, don't fuck with it!
|
|
||||||
-- contains per group player record. Does not resolve on unit level!
|
|
||||||
for gname, pgroup in pairs(allPlayerGroups) do
|
|
||||||
local theUnit = pgroup.primeUnit -- get any unit of that group
|
|
||||||
cfxPlayerScoreUI.setCommsMenuForUnit(theUnit) -- set up
|
|
||||||
end
|
|
||||||
-- now install the new group notifier to install Assault Troops menu
|
|
||||||
|
|
||||||
cfxPlayer.addMonitor(cfxPlayerScoreUI.playerChangeEvent)
|
|
||||||
trigger.action.outText("cf/x cfxPlayerScoreUI v" .. cfxPlayerScoreUI.version .. " started", 30)
|
trigger.action.outText("cf/x cfxPlayerScoreUI v" .. cfxPlayerScoreUI.version .. " started", 30)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@ -296,7 +89,6 @@ end
|
|||||||
--
|
--
|
||||||
-- GO GO GO
|
-- GO GO GO
|
||||||
--
|
--
|
||||||
|
|
||||||
if not cfxPlayerScoreUI.start() then
|
if not cfxPlayerScoreUI.start() then
|
||||||
cfxPlayerScoreUI = nil
|
cfxPlayerScoreUI = nil
|
||||||
trigger.action.outText("cf/x PlayerScore UI aborted: missing libraries", 30)
|
trigger.action.outText("cf/x PlayerScore UI aborted: missing libraries", 30)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
cfxZones = {}
|
cfxZones = {}
|
||||||
cfxZones.version = "3.0.6"
|
cfxZones.version = "3.0.8"
|
||||||
|
|
||||||
-- cf/x zone management module
|
-- cf/x zone management module
|
||||||
-- reads dcs zones and makes them accessible and mutable
|
-- reads dcs zones and makes them accessible and mutable
|
||||||
@ -125,6 +125,8 @@ cfxZones.version = "3.0.6"
|
|||||||
- 3.0.6 - new createSimplePolyZone()
|
- 3.0.6 - new createSimplePolyZone()
|
||||||
- new createSimpleQuadZone()
|
- new createSimpleQuadZone()
|
||||||
- 3.0.7 - getPoint() can also get land y when passing true as second param
|
- 3.0.7 - getPoint() can also get land y when passing true as second param
|
||||||
|
- 3.0.8 - new cfxZones.pointInOneOfZones(thePoint, zoneArray, useOrig)
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
cfxZones.verbose = false
|
cfxZones.verbose = false
|
||||||
cfxZones.caseSensitiveProperties = false -- set to true to make property names case sensitive
|
cfxZones.caseSensitiveProperties = false -- set to true to make property names case sensitive
|
||||||
@ -1141,6 +1143,15 @@ function cfxZones.markZoneWithSmokePolarRandom(theZone, radius, smokeColor)
|
|||||||
cfxZones.markZoneWithSmokePolar(theZone, radius, degrees, smokeColor)
|
cfxZones.markZoneWithSmokePolar(theZone, radius, degrees, smokeColor)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function cfxZones.pointInOneOfZones(thePoint, zoneArray, useOrig)
|
||||||
|
if not zoneArray then zoneArray = cfxZones.zones end
|
||||||
|
for idx, theZone in pairs(zoneArray) do
|
||||||
|
local isIn, percent, dist = cfxZones.pointInZone(thePoint, theZone, useOrig)
|
||||||
|
if isIn then return isIn, percent, dist, theZone end
|
||||||
|
end
|
||||||
|
return false, 0, 0, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- unitInZone returns true if theUnit is inside the zone
|
-- unitInZone returns true if theUnit is inside the zone
|
||||||
-- the second value returned is the percentage of distance
|
-- the second value returned is the percentage of distance
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
countDown = {}
|
countDown = {}
|
||||||
countDown.version = "1.3.1"
|
countDown.version = "1.3.2"
|
||||||
countDown.verbose = false
|
countDown.verbose = false
|
||||||
countDown.ups = 1
|
countDown.ups = 1
|
||||||
countDown.requiredLibs = {
|
countDown.requiredLibs = {
|
||||||
@ -27,7 +27,8 @@ countDown.requiredLibs = {
|
|||||||
- new reset? input
|
- new reset? input
|
||||||
- improved verbosity
|
- improved verbosity
|
||||||
- zone-local verbosity
|
- zone-local verbosity
|
||||||
|
1.3.2 - enableCounter? to balande disableCounter?
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
countDown.counters = {}
|
countDown.counters = {}
|
||||||
@ -141,12 +142,18 @@ function countDown.createCountDownWithZone(theZone)
|
|||||||
theZone.counterOut = cfxZones.getStringFromZoneProperty(theZone, "counterOut!", "<none>")
|
theZone.counterOut = cfxZones.getStringFromZoneProperty(theZone, "counterOut!", "<none>")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- disableFlag
|
-- disableFlag/enableFlag
|
||||||
theZone.counterDisabled = false
|
theZone.counterDisabled = false
|
||||||
if cfxZones.hasProperty(theZone, "disableCounter?") then
|
if cfxZones.hasProperty(theZone, "disableCounter?") then
|
||||||
theZone.disableCounterFlag = cfxZones.getStringFromZoneProperty(theZone, "disableCounter?", "<none>")
|
theZone.disableCounterFlag = cfxZones.getStringFromZoneProperty(theZone, "disableCounter?", "<none>")
|
||||||
theZone.disableCounterFlagVal = cfxZones.getFlagValue(theZone.disableCounterFlag, theZone)
|
theZone.disableCounterFlagVal = cfxZones.getFlagValue(theZone.disableCounterFlag, theZone)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if cfxZones.hasProperty(theZone, "enableCounter?") then
|
||||||
|
theZone.enableCounterFlag = cfxZones.getStringFromZoneProperty(theZone, "enableCounter?", "<none>")
|
||||||
|
theZone.enableCounterFlagVal = cfxZones.getFlagValue(theZone.enableCounterFlag, theZone)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--
|
--
|
||||||
@ -260,6 +267,12 @@ function countDown.update()
|
|||||||
aZone.counterDisabled = true
|
aZone.counterDisabled = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if cfxZones.testZoneFlag(aZone, aZone.enableCounterFlag, aZone.ctdwnTriggerMethod, "enableCounterFlagVal") then
|
||||||
|
if countDown.verbose then
|
||||||
|
trigger.action.outText("+++cntD: ENabling counter " .. aZone.name, 30)
|
||||||
|
end
|
||||||
|
aZone.counterDisabled = false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -145,7 +145,9 @@ dcsCommon.version = "2.8.5"
|
|||||||
- new rotatePointAroundPointRad()
|
- new rotatePointAroundPointRad()
|
||||||
- getClosestAirbaseTo() now supports passing list of air bases
|
- getClosestAirbaseTo() now supports passing list of air bases
|
||||||
2.8.5 - better guard in getGroupUnit()
|
2.8.5 - better guard in getGroupUnit()
|
||||||
|
2.8.6 - phonetic helpers
|
||||||
|
new spellString()
|
||||||
|
|
||||||
|
|
||||||
--]]--
|
--]]--
|
||||||
|
|
||||||
@ -3221,7 +3223,7 @@ function dcsCommon.LSR(a, num)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--
|
--
|
||||||
-- string windcards
|
-- string wildcards
|
||||||
--
|
--
|
||||||
function dcsCommon.processStringWildcards(inMsg)
|
function dcsCommon.processStringWildcards(inMsg)
|
||||||
-- Replace STATIC bits of message like CR and zone name
|
-- Replace STATIC bits of message like CR and zone name
|
||||||
@ -3236,6 +3238,82 @@ function dcsCommon.processStringWildcards(inMsg)
|
|||||||
return outMsg
|
return outMsg
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- phonetic alphabet
|
||||||
|
--
|
||||||
|
dcsCommon.alphabet = {
|
||||||
|
a = "alpha",
|
||||||
|
b = "bravo",
|
||||||
|
c = "charlie",
|
||||||
|
d = "delta",
|
||||||
|
e = "echo",
|
||||||
|
f = "foxtrot",
|
||||||
|
g = "golf",
|
||||||
|
h = "hotel",
|
||||||
|
i = "india",
|
||||||
|
j = "juliet",
|
||||||
|
k = "kilo",
|
||||||
|
l = "lima",
|
||||||
|
m = "mike",
|
||||||
|
n = "november",
|
||||||
|
o = "oscar",
|
||||||
|
p = "papa",
|
||||||
|
q = "quebec",
|
||||||
|
r = "romeo",
|
||||||
|
s = "sierra",
|
||||||
|
t = "tango",
|
||||||
|
u = "uniform",
|
||||||
|
v = "victor",
|
||||||
|
w = "whiskey",
|
||||||
|
x = "x-ray",
|
||||||
|
y = "yankee",
|
||||||
|
z = "zulu",
|
||||||
|
["0"] = "zero",
|
||||||
|
["1"] = "wun",
|
||||||
|
["2"] = "too",
|
||||||
|
["3"] = "tree",
|
||||||
|
["4"] = "fower",
|
||||||
|
["5"] = "fife" ,
|
||||||
|
["6"] = "six",
|
||||||
|
["7"] = "seven",
|
||||||
|
["8"] = "att",
|
||||||
|
["9"] = "niner",
|
||||||
|
[" "] = "break",
|
||||||
|
}
|
||||||
|
|
||||||
|
function dcsCommon.letter(inChar)
|
||||||
|
local theChar = ""
|
||||||
|
if type(inChar == "string") then
|
||||||
|
if #inChar < 1 then return "#ERROR0#" end
|
||||||
|
inChar = string.lower(inChar)
|
||||||
|
theChar = string.sub(inChar, 1, 1)
|
||||||
|
elseif type(inChar == "number") then
|
||||||
|
if inChar > 255 then return "#ERROR>#" end
|
||||||
|
if inChar < 0 then return "#ERROR<#" end
|
||||||
|
theChar = char(inChar)
|
||||||
|
else
|
||||||
|
return "#ERRORT#"
|
||||||
|
end
|
||||||
|
-- trigger.action.outText("doing <" .. theChar .. ">", 30)
|
||||||
|
local a = dcsCommon.alphabet[theChar]
|
||||||
|
if a == nil then a = "#ERROR?#" end
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
|
||||||
|
function dcsCommon.spellString(inString)
|
||||||
|
local res = ""
|
||||||
|
local first = true
|
||||||
|
for i = 1, #inString do
|
||||||
|
local c = inString:sub(i,i)
|
||||||
|
if first then
|
||||||
|
res = dcsCommon.letter(c)
|
||||||
|
first = false
|
||||||
|
else
|
||||||
|
res = res .. " " .. dcsCommon.letter(c)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
--
|
--
|
||||||
-- SEMAPHORES
|
-- SEMAPHORES
|
||||||
|
|||||||
886
modules/factoryZone.lua
Normal file
886
modules/factoryZone.lua
Normal file
@ -0,0 +1,886 @@
|
|||||||
|
factoryZone = {}
|
||||||
|
factoryZone.version = "1.0.0"
|
||||||
|
factoryZone.verbose = false
|
||||||
|
factoryZone.name = "factoryZone"
|
||||||
|
|
||||||
|
--[[-- VERSION HISTORY
|
||||||
|
|
||||||
|
1.0.0 - refactored production part from cfxOwnedZones 1.xpcall
|
||||||
|
|
||||||
|
--]]--
|
||||||
|
factoryZone.requiredLibs = {
|
||||||
|
"dcsCommon", -- common is of course needed for everything
|
||||||
|
-- pretty stupid to check for this since we
|
||||||
|
-- need common to invoke the check, but anyway
|
||||||
|
"cfxZones", -- Zones, of course
|
||||||
|
"cfxCommander", -- to make troops do stuff
|
||||||
|
}
|
||||||
|
|
||||||
|
factoryZone.zones = {} -- my factory zones
|
||||||
|
factoryZone.ups = 1
|
||||||
|
factoryZone.initialized = false
|
||||||
|
factoryZone.defendingTime = 100 -- seconds until new defenders are produced
|
||||||
|
factoryZone.attackingTime = 300 -- seconds until new attackers are produced
|
||||||
|
factoryZone.shockTime = 200 -- 'shocked' period of inactivity
|
||||||
|
factoryZone.repairTime = 200 -- time until we raplace one lost unit, also repairs all other units to 100%
|
||||||
|
|
||||||
|
-- persistence: all attackers we ever sent out.
|
||||||
|
-- is regularly verified and cut to size
|
||||||
|
factoryZone.spawnedAttackers = {}
|
||||||
|
|
||||||
|
-- factoryZone is a module that managers production of units
|
||||||
|
-- inside zones and can switch production based on who owns the
|
||||||
|
-- zone. Zone ownership can by dynamic (by using OwnedZones or
|
||||||
|
-- using scripts to change the 'owner' flag
|
||||||
|
--
|
||||||
|
-- *** EXTENTDS ZONES ***
|
||||||
|
--
|
||||||
|
-- zone attributes when owned
|
||||||
|
-- owner: coalition that owns the zone. Managed externally
|
||||||
|
-- status: FSM for spawning
|
||||||
|
-- defendersRED/BLUE - coma separated type string for the group to spawn on defense cycle completion
|
||||||
|
-- attackersRED/BLUE - as above for attack cycle.
|
||||||
|
-- timeStamp - time when zone switched into current state
|
||||||
|
-- spawnRadius - overrides zone's radius when placing defenders. can be use to place defenders inside or outside zone itself
|
||||||
|
-- formation - defender's formation
|
||||||
|
-- attackFormation - attackers formation
|
||||||
|
-- attackRadius - radius of circle in which attackers are spawned. informs formation
|
||||||
|
-- attackDelta - polar coord: r from zone center where attackers are spawned
|
||||||
|
-- attackPhi - polar degrees where attackers are to be spawned
|
||||||
|
-- paused - will not spawn. default is false
|
||||||
|
-- unbeatable - can't be conquered by other side. default is false
|
||||||
|
-- untargetable - will not be targeted by either side. make unbeatable
|
||||||
|
-- owned zones untargetable, or they'll become a troop magnet for
|
||||||
|
-- zoneAttackers
|
||||||
|
|
||||||
|
--
|
||||||
|
-- M I S C
|
||||||
|
--
|
||||||
|
|
||||||
|
|
||||||
|
function factoryZone.getFactoryZoneByName(zName)
|
||||||
|
for zKey, theZone in pairs (factoryZone.zones) do
|
||||||
|
if theZone.name == zName then return theZone end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.addFactoryZone(aZone)
|
||||||
|
aZone.worksFor = cfxZones.getCoalitionFromZoneProperty(aZone, "factory", 0) -- currently unused, have RED/BLUE separate types
|
||||||
|
aZone.state = "init"
|
||||||
|
aZone.timeStamp = timer.getTime()
|
||||||
|
aZone.defendersRED = cfxZones.getStringFromZoneProperty(aZone, "defendersRED", "none")
|
||||||
|
aZone.defendersBLUE = cfxZones.getStringFromZoneProperty(aZone, "defendersBLUE", "none")
|
||||||
|
if cfxZones.hasProperty(aZone, "attackersRED") then
|
||||||
|
-- legacy support
|
||||||
|
aZone.attackersRED = cfxZones.getStringFromZoneProperty(aZone, "attackersRED", "none")
|
||||||
|
else
|
||||||
|
aZone.attackersRED = cfxZones.getStringFromZoneProperty(aZone, "productionRED", "none")
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfxZones.hasProperty(aZone, "attackersBLUE") then
|
||||||
|
-- legacy support
|
||||||
|
aZone.attackersBLUE = cfxZones.getStringFromZoneProperty(aZone, "attackersBLUE", "none")
|
||||||
|
else
|
||||||
|
aZone.attackersBLUE = cfxZones.getStringFromZoneProperty(aZone, "productionBLUE", "none")
|
||||||
|
end
|
||||||
|
|
||||||
|
aZone.formation = cfxZones.getStringFromZoneProperty(aZone, "formation", "circle_out")
|
||||||
|
aZone.attackFormation = cfxZones.getStringFromZoneProperty(aZone, "attackFormation", "circle_out") -- cfxZones.getZoneProperty(aZone, "attackFormation")
|
||||||
|
aZone.spawnRadius = cfxZones.getNumberFromZoneProperty(aZone, "spawnRadius", aZone.radius-5) -- "-5" so they remaininside radius
|
||||||
|
aZone.attackRadius = cfxZones.getNumberFromZoneProperty(aZone, "attackRadius", aZone.radius)
|
||||||
|
aZone.attackDelta = cfxZones.getNumberFromZoneProperty(aZone, "attackDelta", 10) -- aZone.radius)
|
||||||
|
aZone.attackPhi = cfxZones.getNumberFromZoneProperty(aZone, "attackPhi", 0)
|
||||||
|
|
||||||
|
aZone.paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false)
|
||||||
|
aZone.factoryOwner = aZone.owner -- copy so we can compare next round
|
||||||
|
|
||||||
|
-- pause? and activate?
|
||||||
|
if cfxZones.hasProperty(aZone, "pause?") then
|
||||||
|
aZone.pauseFlag = cfxZones.getStringFromZoneProperty(aZone, "pause?", "none")
|
||||||
|
aZone.lastPauseValue = trigger.misc.getUserFlag(aZone.pauseFlag)
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfxZones.hasProperty(aZone, "activate?") then
|
||||||
|
aZone.activateFlag = cfxZones.getStringFromZoneProperty(aZone, "activate?", "none")
|
||||||
|
aZone.lastActivateValue = trigger.misc.getUserFlag(aZone.activateFlag)
|
||||||
|
end
|
||||||
|
|
||||||
|
aZone.factoryTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
|
||||||
|
if cfxZones.hasProperty(aZone, "factoryTriggerMethod") then
|
||||||
|
aZone.factoryTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "factoryTriggerMethod", "change")
|
||||||
|
end
|
||||||
|
|
||||||
|
aZone.untargetable = cfxZones.getBoolFromZoneProperty(aZone, "untargetable", false)
|
||||||
|
|
||||||
|
factoryZone.zones[aZone.name] = aZone
|
||||||
|
factoryZone.verifyZone(aZone)
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.verifyZone(aZone)
|
||||||
|
-- do some sanity checks
|
||||||
|
if not cfxGroundTroops and (aZone.attackersRED ~= "none" or aZone.attackersBLUE ~= "none") then
|
||||||
|
trigger.action.outText("+++factZ: " .. aZone.name .. " attackers need cfxGroundTroops to function", 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.getEnemyZonesFor(aCoalition)
|
||||||
|
-- when cfxOwnedZones is present, or it will return only those
|
||||||
|
-- else it scans all zones from cfxZones
|
||||||
|
local enemyZones = {}
|
||||||
|
local allZones = cfxZones.zones
|
||||||
|
if cfxOwnedZones then
|
||||||
|
allZones = cfxOwnedZones.zones
|
||||||
|
end
|
||||||
|
local ourEnemy = dcsCommon.getEnemyCoalitionFor(aCoalition)
|
||||||
|
for zKey, aZone in pairs(allZones) do
|
||||||
|
if aZone.owner == ourEnemy then -- only check enemy owned zones
|
||||||
|
-- note: will include untargetable zones
|
||||||
|
table.insert(enemyZones, aZone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return enemyZones
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.getNearestOwnedZoneToPoint(aPoint)
|
||||||
|
local shortestDist = math.huge
|
||||||
|
-- when cfxOwnedZones is present, or it will return only those
|
||||||
|
-- else it scans all zones from cfxZones
|
||||||
|
local closestZone = nil
|
||||||
|
local allZones = cfxZones.zones
|
||||||
|
if cfxOwnedZones then
|
||||||
|
allZones = cfxOwnedZones.zones
|
||||||
|
end
|
||||||
|
for zKey, aZone in pairs(allZones) do
|
||||||
|
local zPoint = cfxZones.getPoint(aZone)
|
||||||
|
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||||
|
if aZone.untargetable ~= true and
|
||||||
|
currDist < shortestDist then
|
||||||
|
shortestDist = currDist
|
||||||
|
closestZone = aZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return closestZone, shortestDist
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.getNearestOwnedZone(theZone)
|
||||||
|
local shortestDist = math.huge
|
||||||
|
-- when cfxOwnedZones is present, or it will return only those
|
||||||
|
-- else it scans all zones from cfxZones
|
||||||
|
local closestZone = nil
|
||||||
|
local aPoint = cfxZones.getPoint(theZone)
|
||||||
|
local allZones = cfxZones.zones
|
||||||
|
if cfxOwnedZones then
|
||||||
|
allZones = cfxOwnedZones.zones
|
||||||
|
end
|
||||||
|
for zKey, aZone in pairs(allZones) do
|
||||||
|
local zPoint = cfxZones.getPoint(aZone)
|
||||||
|
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||||
|
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||||
|
shortestDist = currDist
|
||||||
|
closestZone = aZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return closestZone, shortestDist
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.getNearestEnemyOwnedZone(theZone, targetNeutral)
|
||||||
|
if not targetNeutral then targetNeutral = false else targetNeutral = true end
|
||||||
|
local shortestDist = math.huge
|
||||||
|
local closestZone = nil
|
||||||
|
-- when cfxOwnedZones is present, or it will return only those
|
||||||
|
-- else it scans all zones from cfxZones
|
||||||
|
local allZones = cfxZones.zones
|
||||||
|
if cfxOwnedZones then
|
||||||
|
allZones = cfxOwnedZones.zones
|
||||||
|
end
|
||||||
|
local ourEnemy = dcsCommon.getEnemyCoalitionFor(theZone.owner)
|
||||||
|
if not ourEnemy then return nil end -- we called for a neutral zone. they have no enemies
|
||||||
|
local zPoint = cfxZones.getPoint(theZone)
|
||||||
|
|
||||||
|
for zKey, aZone in pairs(allZones) do
|
||||||
|
if targetNeutral then
|
||||||
|
-- return all zones that do not belong to us
|
||||||
|
if aZone.owner ~= theZone.owner then
|
||||||
|
local aPoint = cfxZones.getPoint(aZone)
|
||||||
|
currDist = dcsCommon.dist(aPoint, zPoint)
|
||||||
|
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||||
|
shortestDist = currDist
|
||||||
|
closestZone = aZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- return zones that are taken by the Enenmy
|
||||||
|
if aZone.owner == ourEnemy then -- only check own zones
|
||||||
|
local aPoint = cfxZones.getPoint(aZone)
|
||||||
|
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||||
|
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||||
|
shortestDist = currDist
|
||||||
|
closestZone = aZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return closestZone, shortestDist
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.getNearestFriendlyZone(theZone, targetNeutral)
|
||||||
|
if not targetNeutral then targetNeutral = false else targetNeutral = true end
|
||||||
|
local shortestDist = math.huge
|
||||||
|
local closestZone = nil
|
||||||
|
local ourEnemy = dcsCommon.getEnemyCoalitionFor(theZone.owner)
|
||||||
|
if not ourEnemy then return nil end -- we called for a neutral zone. they have no enemies nor friends, all zones would be legal.
|
||||||
|
local zPoint = cfxZones.getPoint(theZone)
|
||||||
|
-- when cfxOwnedZones is present, or it will return only those
|
||||||
|
-- else it scans all zones from cfxZones
|
||||||
|
local allZones = cfxZones.zones
|
||||||
|
if cfxOwnedZones then
|
||||||
|
allZones = cfxOwnedZones.zones
|
||||||
|
end
|
||||||
|
for zKey, aZone in pairs(allZones) do
|
||||||
|
if targetNeutral then
|
||||||
|
-- target all zones that do not belong to the enemy
|
||||||
|
if aZone.owner ~= ourEnemy then
|
||||||
|
local aPoint = cfxZones.getPoint(aZone)
|
||||||
|
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||||
|
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||||
|
shortestDist = currDist
|
||||||
|
closestZone = aZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- only target zones that are taken by us
|
||||||
|
if aZone.owner == theZone.owner then -- only check own zones
|
||||||
|
local aPoint = cfxZones.getPoint(aZone)
|
||||||
|
currDist = dcsCommon.dist(zPoint, aPoint)
|
||||||
|
if aZone.untargetable ~= true and currDist < shortestDist then
|
||||||
|
shortestDist = currDist
|
||||||
|
closestZone = aZone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return closestZone, shortestDist
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.enemiesRemaining(aZone)
|
||||||
|
if factoryZone.getNearestEnemyOwnedZone(aZone) then return true end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.spawnAttackTroops(theTypes, aZone, aCoalition, aFormation)
|
||||||
|
local unitTypes = {} -- build type names
|
||||||
|
-- split theTypes into an array of types
|
||||||
|
unitTypes = dcsCommon.splitString(theTypes, ",")
|
||||||
|
if #unitTypes < 1 then
|
||||||
|
table.insert(unitTypes, "Soldier M4") -- make it one m4 trooper as fallback
|
||||||
|
-- simply exit, no troops specified
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ: no attackers for " .. aZone.name .. ". exiting", 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ: spawning attackers for " .. aZone.name, 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
local spawnPoint = {x = aZone.point.x, y = aZone.point.y, z = aZone.point.z} -- copy struct
|
||||||
|
|
||||||
|
local rads = aZone.attackPhi * 0.01745
|
||||||
|
spawnPoint.x = spawnPoint.x + math.cos(aZone.attackPhi) * aZone.attackDelta
|
||||||
|
spawnPoint.y = spawnPoint.y + math.sin(aZone.attackPhi) * aZone.attackDelta
|
||||||
|
|
||||||
|
local spawnZone = cfxZones.createSimpleZone("attkSpawnZone", spawnPoint, aZone.attackRadius)
|
||||||
|
|
||||||
|
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
||||||
|
aCoalition, -- theCountry,
|
||||||
|
aZone.name .. " (A) " .. dcsCommon.numberUUID(), -- must be unique
|
||||||
|
spawnZone,
|
||||||
|
unitTypes,
|
||||||
|
aFormation, -- outward facing
|
||||||
|
0)
|
||||||
|
return theGroup, theData
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.spawnDefensiveTroops(theTypes, aZone, aCoalition, aFormation)
|
||||||
|
local unitTypes = {} -- build type names
|
||||||
|
-- split theTypes into an array of types
|
||||||
|
unitTypes = dcsCommon.splitString(theTypes, ",")
|
||||||
|
if #unitTypes < 1 then
|
||||||
|
table.insert(unitTypes, "Soldier M4") -- make it one m4 trooper as fallback
|
||||||
|
-- simply exit, no troops specified
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ: no defenders for " .. aZone.name .. ". exiting", 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
--local theCountry = dcsCommon.coalition2county(aCoalition)
|
||||||
|
local spawnZone = cfxZones.createSimpleZone("spawnZone", aZone.point, aZone.spawnRadius)
|
||||||
|
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
||||||
|
aCoalition, --theCountry,
|
||||||
|
aZone.name .. " (D) " .. dcsCommon.numberUUID(), -- must be unique
|
||||||
|
spawnZone, unitTypes,
|
||||||
|
aFormation, -- outward facing
|
||||||
|
0)
|
||||||
|
return theGroup, theData
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- U P D A T E
|
||||||
|
--
|
||||||
|
|
||||||
|
function factoryZone.sendOutAttackers(aZone)
|
||||||
|
-- sanity check: never done for non-neutral zones
|
||||||
|
if aZone.owner == 0 then
|
||||||
|
if aZone.verbose or factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ: SendAttackers invoked for NEUTRAL zone <" .. aZone.name .. ">", 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- only spawn if there are zones to attack
|
||||||
|
if not factoryZone.enemiesRemaining(aZone) then
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ - no enemies, resting ".. aZone.name, 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ - attack cycle for ".. aZone.name, 30)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- step one: get the attackers
|
||||||
|
local attackers = aZone.attackersRED;
|
||||||
|
if (aZone.owner == 2) then attackers = aZone.attackersBLUE end
|
||||||
|
|
||||||
|
if attackers == "none" then return end
|
||||||
|
|
||||||
|
local theGroup, theData = factoryZone.spawnAttackTroops(attackers, aZone, aZone.owner, aZone.attackFormation)
|
||||||
|
|
||||||
|
local troopData = {}
|
||||||
|
troopData.groupData = theData
|
||||||
|
troopData.orders = "attackOwnedZone" -- lazy coding!
|
||||||
|
troopData.side = aZone.owner
|
||||||
|
factoryZone.spawnedAttackers[theData.name] = troopData
|
||||||
|
|
||||||
|
-- submit them to ground troops handler as zoneseekers
|
||||||
|
-- and our groundTroops module will handle the rest
|
||||||
|
if cfxGroundTroops then
|
||||||
|
local troops = cfxGroundTroops.createGroundTroops(theGroup)
|
||||||
|
troops.orders = "attackOwnedZone"
|
||||||
|
troops.side = aZone.owner
|
||||||
|
cfxGroundTroops.addGroundTroopsToPool(troops) -- hand off to ground troops
|
||||||
|
else
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++ Owned Zones: no ground troops module on send out attackers", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function factoryZone.repairDefenders(aZone)
|
||||||
|
-- sanity check: never done for non-neutral zones
|
||||||
|
if aZone.owner == 0 then
|
||||||
|
if aZone.verbose or factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ: repairDefenders invoked for NEUTRAL zone <" .. aZone.name .. ">", 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- find a unit that is missing from my typestring and replace it
|
||||||
|
-- one by one until we are back to full strength
|
||||||
|
-- step one: get the defenders and create a type array
|
||||||
|
local defenders = aZone.defendersRED;
|
||||||
|
if (aZone.owner == 2) then defenders = aZone.defendersBLUE end
|
||||||
|
local unitTypes = {} -- build type names
|
||||||
|
|
||||||
|
-- if none, we are done
|
||||||
|
if defenders == "none" then return end
|
||||||
|
|
||||||
|
-- split theTypes into an array of types
|
||||||
|
allTypes = dcsCommon.trimArray(
|
||||||
|
dcsCommon.splitString(defenders, ",")
|
||||||
|
)
|
||||||
|
local livingTypes = {} -- init to emtpy, so we can add to it if none are alive
|
||||||
|
if (aZone.defenders) then
|
||||||
|
-- some remain. add one of the killed
|
||||||
|
livingTypes = dcsCommon.getGroupTypes(aZone.defenders)
|
||||||
|
-- we now iterate over the living types, and remove their
|
||||||
|
-- counterparts from the allTypes. We then take the first that
|
||||||
|
-- is left
|
||||||
|
|
||||||
|
if #livingTypes > 0 then
|
||||||
|
for key, aType in pairs (livingTypes) do
|
||||||
|
if not dcsCommon.findAndRemoveFromTable(allTypes, aType) then
|
||||||
|
trigger.action.outText("+++factZ WARNING: found unmatched type <" .. aType .. "> while trying to repair defenders for ".. aZone.name, 30)
|
||||||
|
else
|
||||||
|
-- all good
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- when we get here, allTypes is reduced to those that have been killed
|
||||||
|
if #allTypes < 1 then
|
||||||
|
trigger.action.outText("+++factZ: WARNING: all types exist when repairing defenders for ".. aZone.name, 30)
|
||||||
|
else
|
||||||
|
table.insert(livingTypes, allTypes[1]) -- we simply use the first that we find
|
||||||
|
end
|
||||||
|
-- remove the old defenders
|
||||||
|
if aZone.defenders then
|
||||||
|
aZone.defenders:destroy()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now livingTypes holds the full array of units we need to spawn
|
||||||
|
local theCountry = dcsCommon.getACountryForCoalition(aZone.owner)
|
||||||
|
local spawnZone = cfxZones.createSimpleZone("spawnZone", aZone.point, aZone.spawnRadius)
|
||||||
|
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
|
||||||
|
aZone.owner, -- was wrongly: theCountry
|
||||||
|
aZone.name .. dcsCommon.numberUUID(), -- must be unique
|
||||||
|
spawnZone,
|
||||||
|
livingTypes,
|
||||||
|
|
||||||
|
aZone.formation, -- outward facing
|
||||||
|
0)
|
||||||
|
aZone.defenders = theGroup
|
||||||
|
aZone.lastDefenders = theGroup:getSize()
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.inShock(aZone)
|
||||||
|
-- a unit was destroyed, everyone else is in shock, no rerpairs
|
||||||
|
-- group can re-shock when another unit is destroyed
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.spawnDefenders(aZone)
|
||||||
|
-- sanity check: never done for non-neutral zones
|
||||||
|
if aZone.owner == 0 then
|
||||||
|
if aZone.verbose or factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ: spawnDefenders invoked for NEUTRAL zone <" .. aZone.name .. ">", 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local defenders = aZone.defendersRED;
|
||||||
|
|
||||||
|
if (aZone.owner == 2) then defenders = aZone.defendersBLUE end
|
||||||
|
-- before we spawn new defenders, remove the old ones
|
||||||
|
if aZone.defenders then
|
||||||
|
if aZone.defenders:isExist() then
|
||||||
|
aZone.defenders:destroy()
|
||||||
|
end
|
||||||
|
aZone.defenders = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if 'none', simply exit
|
||||||
|
if defenders == "none" then return end
|
||||||
|
|
||||||
|
local theGroup, theData = factoryZone.spawnDefensiveTroops(defenders, aZone, aZone.owner, aZone.formation)
|
||||||
|
-- the troops reamin, so no orders to move, no handing off to ground troop manager
|
||||||
|
aZone.defenders = theGroup
|
||||||
|
aZone.defenderData = theData -- used for persistence
|
||||||
|
if theGroup then
|
||||||
|
aZone.lastDefenders = theGroup:getInitialSize()
|
||||||
|
else
|
||||||
|
trigger.action.outText("+++factZ: WARNING: spawned no defenders for ".. aZone.name, 30)
|
||||||
|
aZone.defenderData = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- per-zone update, run down the FSM to determine what to do.
|
||||||
|
-- FSM uses timeStamp since when state was set. Possible states are
|
||||||
|
-- - init -- has just been inited for the first time. will usually immediately produce defenders,
|
||||||
|
-- and then transition to defending
|
||||||
|
-- - catured -- has just been captured. transition to defending
|
||||||
|
-- - defending -- wait until timer has reached goal, then produce defending units and transition to attacking.
|
||||||
|
-- - attacking -- wait until timer has reached goal, and then produce attacking units and send them to closest enemy zone.
|
||||||
|
-- state is interrupted as soon as a defensive unit is lost. state then goes to defending with timer starting
|
||||||
|
-- - idle - do nothing, zone's actions are turned off
|
||||||
|
-- - shocked -- a unit was destroyed. group is in shock for a time until it starts repairing. If another unit is
|
||||||
|
-- destroyed during the shocked period, the timer resets to zero and repairs are delayed
|
||||||
|
-- - repairing -- as long as we aren't at full strength, units get replaced one by one until at full strength
|
||||||
|
-- each time the timer counts down, another missing unit is replaced, and all other unit's health
|
||||||
|
-- is reset to 100%
|
||||||
|
--
|
||||||
|
-- a Zone with the paused attribute set to true will cause it to not do anything
|
||||||
|
--
|
||||||
|
-- check if defenders are specified
|
||||||
|
function factoryZone.usesDefenders(aZone)
|
||||||
|
if aZone.owner == 0 then return false end
|
||||||
|
local defenders = aZone.defendersRED;
|
||||||
|
if (aZone.owner == 2) then defenders = aZone.defendersBLUE end
|
||||||
|
return defenders ~= "none"
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.usesAttackers(aZone)
|
||||||
|
if aZone.owner == 0 then return false end
|
||||||
|
local attackers = aZone.attackersRED;
|
||||||
|
if (aZone.owner == 2) then defenders = aZone.attackersBLUE end
|
||||||
|
return attackers ~= "none"
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.updateZoneProduction(aZone)
|
||||||
|
-- a zone can be paused, causing it to not progress anything
|
||||||
|
-- even if zone status is still init, will NOT produce anything
|
||||||
|
-- if paused is on.
|
||||||
|
if aZone.paused then return end
|
||||||
|
|
||||||
|
nextState = aZone.state;
|
||||||
|
|
||||||
|
-- first, check if my defenders have been attacked and one of them has been killed
|
||||||
|
-- if so, we immediately switch to 'shocked'
|
||||||
|
if factoryZone.usesDefenders(aZone) and
|
||||||
|
aZone.defenders then
|
||||||
|
-- we have defenders
|
||||||
|
if aZone.defenders:isExist() then
|
||||||
|
-- isee if group was damaged
|
||||||
|
if not aZone.lastDefenders then
|
||||||
|
-- fresh group, probably from persistence, needs init
|
||||||
|
aZone.lastDefenders = -1
|
||||||
|
end
|
||||||
|
if aZone.defenders:getSize() < aZone.lastDefenders then
|
||||||
|
-- yes, at least one unit destroyed
|
||||||
|
aZone.timeStamp = timer.getTime()
|
||||||
|
aZone.lastDefenders = aZone.defenders:getSize()
|
||||||
|
if aZone.lastDefenders == 0 then
|
||||||
|
aZone.defenders = nil
|
||||||
|
end
|
||||||
|
aZone.state = "shocked"
|
||||||
|
|
||||||
|
return
|
||||||
|
else
|
||||||
|
aZone.lastDefenders = aZone.defenders:getSize()
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
-- group was destroyed. erase link, and go into shock for the last time
|
||||||
|
aZone.state = "shocked"
|
||||||
|
aZone.timeStamp = timer.getTime()
|
||||||
|
aZone.lastDefenders = 0
|
||||||
|
aZone.defenders = nil
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if aZone.state == "init" then
|
||||||
|
-- during init we instantly create the defenders since
|
||||||
|
-- we assume the zone existed already
|
||||||
|
if aZone.owner > 0 then
|
||||||
|
factoryZone.spawnDefenders(aZone)
|
||||||
|
-- now drop into attacking mode to produce attackers
|
||||||
|
nextState = "attacking"
|
||||||
|
else
|
||||||
|
nextState = "idle"
|
||||||
|
end
|
||||||
|
aZone.timeStamp = timer.getTime()
|
||||||
|
|
||||||
|
elseif aZone.state == "idle" then
|
||||||
|
-- nothing to do, zone is effectively switched off.
|
||||||
|
-- used for neutal zones or when forced to turn off
|
||||||
|
-- in some special cases
|
||||||
|
|
||||||
|
elseif aZone.state == "captured" then
|
||||||
|
-- start the clock on defenders
|
||||||
|
nextState = "defending"
|
||||||
|
aZone.timeStamp = timer.getTime()
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30)
|
||||||
|
end
|
||||||
|
elseif aZone.state == "defending" then
|
||||||
|
if timer.getTime() > aZone.timeStamp + factoryZone.defendingTime then
|
||||||
|
factoryZone.spawnDefenders(aZone)
|
||||||
|
-- now drop into attacking mode to produce attackers
|
||||||
|
nextState = "attacking"
|
||||||
|
aZone.timeStamp = timer.getTime()
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif aZone.state == "repairing" then
|
||||||
|
-- we are currently rebuilding defenders unit by unit
|
||||||
|
if timer.getTime() > aZone.timeStamp + factoryZone.repairTime then
|
||||||
|
aZone.timeStamp = timer.getTime()
|
||||||
|
-- wait's up, repair one defender, then check if full strength
|
||||||
|
factoryZone.repairDefenders(aZone)
|
||||||
|
-- see if we are full strenght and if so go to attack, else set timer to reair the next unit
|
||||||
|
if aZone.defenders and aZone.defenders:isExist() and aZone.defenders:getSize() >= aZone.defenders:getInitialSize() then
|
||||||
|
-- we are at max size, time to produce some attackers
|
||||||
|
-- progress to next state
|
||||||
|
nextState = "attacking"
|
||||||
|
aZone.timeStamp = timer.getTime()
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif aZone.state == "shocked" then
|
||||||
|
-- we are currently rebuilding defenders unit by unit
|
||||||
|
if timer.getTime() > aZone.timeStamp + factoryZone.shockTime then
|
||||||
|
nextState = "repairing"
|
||||||
|
aZone.timeStamp = timer.getTime()
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ: State " .. aZone.state .. " to " .. nextState .. " for " .. aZone.name, 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif aZone.state == "attacking" then
|
||||||
|
if timer.getTime() > aZone.timeStamp + factoryZone.attackingTime then
|
||||||
|
factoryZone.sendOutAttackers(aZone)
|
||||||
|
-- reset timer
|
||||||
|
aZone.timeStamp = timer.getTime()
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ: State " .. aZone.state .. " reset for " .. aZone.name, 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- unknown zone state
|
||||||
|
end
|
||||||
|
aZone.state = nextState
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.GC()
|
||||||
|
-- GC run. remove all my dead remembered troops
|
||||||
|
local before = #factoryZone.spawnedAttackers
|
||||||
|
local filteredAttackers = {}
|
||||||
|
for gName, gData in pairs (factoryZone.spawnedAttackers) do
|
||||||
|
-- all we need to do is get the group of that name
|
||||||
|
-- and if it still returns units we are fine
|
||||||
|
local gameGroup = Group.getByName(gName)
|
||||||
|
if gameGroup and gameGroup:isExist() and gameGroup:getSize() > 0 then
|
||||||
|
filteredAttackers[gName] = gData
|
||||||
|
end
|
||||||
|
end
|
||||||
|
factoryZone.spawnedAttackers = filteredAttackers
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("owned zones GC ran: before <" .. before .. ">, after <" .. #factoryZone.spawnedAttackers .. ">", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.update()
|
||||||
|
factoryZone.updateSchedule = timer.scheduleFunction(factoryZone.update, {}, timer.getTime() + 1/factoryZone.ups)
|
||||||
|
-- iterate all zones to see if ownership has
|
||||||
|
-- changed
|
||||||
|
|
||||||
|
for idz, theZone in pairs(factoryZone.zones) do
|
||||||
|
local lastOwner = theZone.factoryOwner
|
||||||
|
local newOwner = theZone.owner
|
||||||
|
if (newOwner ~= lastOwner) then
|
||||||
|
theZone.state = "captured"
|
||||||
|
theZone.timeStamp = timer.getTime()
|
||||||
|
theZone.factoryOwner = theZone.owner
|
||||||
|
if theZone.verbose or factoryZone.verbose then
|
||||||
|
trigger.action.outText("+++factZ: detected factory <" .. theZone.name .. "> changed ownership from <" .. lastOwner .. "> to <" .. theZone.owner .. ">", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- see if pause/unpause was issued
|
||||||
|
if theZone.pauseFlag and cfxZones.testZoneFlag(theZone, theZone.pauseFlag, theZone.factoryTriggerMethod, "lastPauseValue") then
|
||||||
|
theZone.paused = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if theZone.activateFlag and cfxZones.testZoneFlag(theZone, theZone.activateFlag, theZone.factoryTriggerMethod, "lastActivateValue") then
|
||||||
|
theZone.paused = false
|
||||||
|
end
|
||||||
|
-- do production for this zone
|
||||||
|
factoryZone.updateZoneProduction(theZone)
|
||||||
|
end -- iterating all zones
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.houseKeeping()
|
||||||
|
timer.scheduleFunction(factoryZone.houseKeeping, {}, timer.getTime() + 5 * 60) -- every 5 minutes
|
||||||
|
factoryZone.GC()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- load / save data
|
||||||
|
--
|
||||||
|
|
||||||
|
function factoryZone.saveData()
|
||||||
|
-- this is called from persistence when it's time to
|
||||||
|
-- save data. returns a table with all my data
|
||||||
|
local theData = {}
|
||||||
|
local allZoneData = {}
|
||||||
|
-- iterate all my zones and create data
|
||||||
|
for idx, theZone in pairs(factoryZone.zones) do
|
||||||
|
local zoneData = {}
|
||||||
|
if theZone.defenderData then
|
||||||
|
zoneData.defenderData = dcsCommon.clone(theZone.defenderData)
|
||||||
|
dcsCommon.synchGroupData(zoneData.defenderData)
|
||||||
|
end
|
||||||
|
zoneData.owner = theZone.owner
|
||||||
|
zoneData.state = theZone.state -- will prevent immediate spawn
|
||||||
|
-- since new zones are spawned with 'init'
|
||||||
|
allZoneData[theZone.name] = zoneData
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now iterate all attack groups that we have spawned and that
|
||||||
|
-- (maybe) are still alive
|
||||||
|
factoryZone.GC() -- start with a GC run to remove all dead
|
||||||
|
local livingAttackers = {}
|
||||||
|
for gName, gData in pairs (factoryZone.spawnedAttackers) do
|
||||||
|
-- all we need to do is get the group of that name
|
||||||
|
-- and if it still returns units we are fine
|
||||||
|
-- spawnedAttackers is a [groupName] table with {.groupData, .orders, .side}
|
||||||
|
local gameGroup = Group.getByName(gName)
|
||||||
|
if gameGroup and gameGroup:isExist() then
|
||||||
|
if gameGroup:getSize() > 0 then
|
||||||
|
local sData = dcsCommon.clone(gData)
|
||||||
|
dcsCommon.synchGroupData(sData.groupData)
|
||||||
|
livingAttackers[gName] = sData
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now write the info for the flags that we output for #red, etc
|
||||||
|
local flagInfo = {} -- no longer used
|
||||||
|
|
||||||
|
-- assemble the data
|
||||||
|
theData.zoneData = allZoneData
|
||||||
|
theData.attackers = livingAttackers
|
||||||
|
theData.flagInfo = flagInfo
|
||||||
|
|
||||||
|
-- return it
|
||||||
|
return theData
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.loadData()
|
||||||
|
-- remember to draw in map with new owner
|
||||||
|
if not persistence then return end
|
||||||
|
local theData = persistence.getSavedDataForModule("factoryZone")
|
||||||
|
if not theData then
|
||||||
|
if factoryZone.verbose then
|
||||||
|
trigger.action.outText("factZ: no save date received, skipping.", 30)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- theData contains the following tables:
|
||||||
|
-- zoneData: per-zone data
|
||||||
|
-- flagInfo: module-global flags
|
||||||
|
-- attackers: all spawned attackers that we feed to groundTroops
|
||||||
|
local allZoneData = theData.zoneData
|
||||||
|
for zName, zData in pairs(allZoneData) do
|
||||||
|
-- access zone
|
||||||
|
local theZone = factoryZone.getOwnedZoneByName(zName)
|
||||||
|
if theZone then
|
||||||
|
if zData.defenderData then
|
||||||
|
if theZone.defenders and theZone.defenders:isExist() then
|
||||||
|
-- should not happen, but so be it
|
||||||
|
theZone.defenders:destroy()
|
||||||
|
end
|
||||||
|
local gData = zData.defenderData
|
||||||
|
local cty = gData.cty
|
||||||
|
local cat = gData.cat
|
||||||
|
theZone.defenders = coalition.addGroup(cty, cat, gData)
|
||||||
|
theZone.defenderData = zData.defenderData
|
||||||
|
end
|
||||||
|
theZone.owner = zData.owner
|
||||||
|
theZone.factoryOwner = theZone.owner
|
||||||
|
theZone.state = zData.state
|
||||||
|
|
||||||
|
else
|
||||||
|
trigger.action.outText("factZ: load - data mismatch: cannot find zone <" .. zName .. ">, skipping zone.", 30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now process all attackers
|
||||||
|
local allAttackers = theData.attackers
|
||||||
|
for gName, gdTroop in pairs(allAttackers) do
|
||||||
|
-- table is {.groupData, .orders, .side}
|
||||||
|
local gData = gdTroop.groupData
|
||||||
|
local orders = gdTroop.orders
|
||||||
|
local side = gdTroop.side
|
||||||
|
local cty = gData.cty
|
||||||
|
local cat = gData.cat
|
||||||
|
-- add to my own attacker queue so we can save later
|
||||||
|
local dClone = dcsCommon.clone(gdTroop)
|
||||||
|
factoryZone.spawnedAttackers[gName] = dClone
|
||||||
|
local theGroup = coalition.addGroup(cty, cat, gData)
|
||||||
|
if cfxGroundTroops then
|
||||||
|
local troops = cfxGroundTroops.createGroundTroops(theGroup)
|
||||||
|
troops.orders = orders
|
||||||
|
troops.side = side
|
||||||
|
cfxGroundTroops.addGroundTroopsToPool(troops) -- hand off to ground troops
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now process module global flags
|
||||||
|
local flagInfo = theData.flagInfo
|
||||||
|
if flagInfo then
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
function factoryZone.readConfigZone(theZone)
|
||||||
|
if not theZone then theZone = cfxZones.createSimpleZone("factoryZoneConfig") end
|
||||||
|
factoryZone.name = "factoryZone" -- just in case, so we can access with cfxZones
|
||||||
|
factoryZone.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||||
|
factoryZone.defendingTime = cfxZones.getNumberFromZoneProperty(theZone, "defendingTime", 100)
|
||||||
|
factoryZone.attackingTime = cfxZones.getNumberFromZoneProperty(theZone, "attackingTime", 300)
|
||||||
|
factoryZone.shockTime = cfxZones.getNumberFromZoneProperty(theZone, "shockTime", 200)
|
||||||
|
factoryZone.repairTime = cfxZones.getNumberFromZoneProperty(theZone, "repairTime", 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
function factoryZone.init()
|
||||||
|
-- check libs
|
||||||
|
if not dcsCommon.libCheck("cfx Owned Zones",
|
||||||
|
factoryZone.requiredLibs) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- read my config zone
|
||||||
|
local theZone = cfxZones.getZoneByName("factoryZoneConfig")
|
||||||
|
factoryZone.readConfigZone(theZone)
|
||||||
|
|
||||||
|
-- collect all owned zones by their 'factory' property
|
||||||
|
-- start the process
|
||||||
|
local pZones = cfxZones.zonesWithProperty("factory")
|
||||||
|
|
||||||
|
-- now add all zones to my zones table, and convert the owner property into
|
||||||
|
-- a proper attribute
|
||||||
|
for k, aZone in pairs(pZones) do
|
||||||
|
factoryZone.addFactoryZone(aZone)
|
||||||
|
end
|
||||||
|
|
||||||
|
if persistence then
|
||||||
|
-- sign up for persistence
|
||||||
|
callbacks = {}
|
||||||
|
callbacks.persistData = factoryZone.saveData
|
||||||
|
persistence.registerModule("factoryZone", callbacks)
|
||||||
|
-- now load my data
|
||||||
|
factoryZone.loadData()
|
||||||
|
end
|
||||||
|
|
||||||
|
initialized = true
|
||||||
|
factoryZone.updateSchedule = timer.scheduleFunction(factoryZone.update, {}, timer.getTime() + 1/factoryZone.ups)
|
||||||
|
|
||||||
|
-- start housekeeping
|
||||||
|
factoryZone.houseKeeping()
|
||||||
|
|
||||||
|
trigger.action.outText("cx/x factory zones v".. factoryZone.version .. " started", 30)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if not factoryZone.init() then
|
||||||
|
trigger.action.outText("cf/x Factory Zones aborted: missing libraries", 30)
|
||||||
|
factoryZone = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- add property to factory attribute to restrict production to that side,
|
||||||
|
-- eg factory blue will only work for blue, else will work for any side
|
||||||
|
-- currently not needed since we have defendersRED/BLUE and productionRED/BLUE
|
||||||
@ -1,5 +1,5 @@
|
|||||||
persistence = {}
|
persistence = {}
|
||||||
persistence.version = "1.0.6"
|
persistence.version = "1.0.7"
|
||||||
persistence.ups = 1 -- once every 1 seconds
|
persistence.ups = 1 -- once every 1 seconds
|
||||||
persistence.verbose = false
|
persistence.verbose = false
|
||||||
persistence.active = false
|
persistence.active = false
|
||||||
@ -26,7 +26,8 @@ persistence.requiredLibs = {
|
|||||||
new 'saveNotification" can be off
|
new 'saveNotification" can be off
|
||||||
1.0.4 - new optional 'root' property
|
1.0.4 - new optional 'root' property
|
||||||
1.0.5 - desanitize check on readConfig to early-abort
|
1.0.5 - desanitize check on readConfig to early-abort
|
||||||
1.0.6 - removed potential verbosity bug
|
1.0.6 - removed potential verbosity bug
|
||||||
|
1.0.7 - correct abort for sanitized DCS, when non-verbose
|
||||||
|
|
||||||
|
|
||||||
PROVIDES LOAD/SAVE ABILITY TO MODULES
|
PROVIDES LOAD/SAVE ABILITY TO MODULES
|
||||||
@ -513,24 +514,24 @@ function persistence.start()
|
|||||||
if not dcsCommon.libCheck("persistence", persistence.requiredLibs) then
|
if not dcsCommon.libCheck("persistence", persistence.requiredLibs) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- read config
|
-- read config
|
||||||
persistence.saveFileName = dcsCommon.getMissionName() .. " Data.txt"
|
persistence.saveFileName = dcsCommon.getMissionName() .. " Data.txt"
|
||||||
persistence.readConfigZone()
|
persistence.readConfigZone()
|
||||||
|
|
||||||
-- let's see it lfs and io are online
|
-- let's see it lfs and io are online
|
||||||
persistence.active = false
|
persistence.active = false
|
||||||
if not _G["lfs"] then
|
if (not _G["lfs"]) or (not lfs) then
|
||||||
if persistence.verbose then
|
if persistence.verbose then
|
||||||
trigger.action.outText("+++persistence requires 'lfs'", 30)
|
trigger.action.outText("+++persistence requires 'lfs'", 30)
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
if not _G["io"] then
|
if not _G["io"] then
|
||||||
if persistence.verbose then
|
if persistence.verbose then
|
||||||
trigger.action.outText("+++persistence requires 'io'", 30)
|
trigger.action.outText("+++persistence requires 'io'", 30)
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- local mainDir = lfs.writedir() .. persistence.serverDir
|
-- local mainDir = lfs.writedir() .. persistence.serverDir
|
||||||
|
|||||||
BIN
tutorial & demo missions/demo - Later Score.miz
Normal file
BIN
tutorial & demo missions/demo - Later Score.miz
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user