mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
698 lines
24 KiB
Lua
698 lines
24 KiB
Lua
cfxSSBClient = {}
|
|
cfxSSBClient.version = "3.0.1"
|
|
cfxSSBClient.verbose = false
|
|
cfxSSBClient.singleUse = false -- set to true to block crashed planes
|
|
-- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick
|
|
cfxSSBClient.reUseAfter = -1 -- seconds for re-use delay
|
|
-- only when singleUse is in effect. -1 means never
|
|
|
|
cfxSSBClient.requiredLibs = {
|
|
"dcsCommon", -- always
|
|
"cfxGroups", -- for slot access
|
|
"cfxZones", -- Zones, of course
|
|
}
|
|
|
|
--[[--
|
|
Version History
|
|
1.0.0 - initial version
|
|
1.1.0 - detect airfield by action and location, not group name
|
|
1.1.1 - performance tuning. only read player groups once
|
|
- and remove in-air-start groups from scan. this requires
|
|
- ssb (server) be not modified
|
|
1.2.0 - API to close airfields: invoke openAirFieldNamed()
|
|
and closeAirfieldNamed() with name as string (exact match required)
|
|
to block an airfield for any player aircraft.
|
|
Works for FARPS as well
|
|
API to associate a player group with any airfied's status (nil for unbind):
|
|
cfxSSBClient.bindGroupToAirfield(group, airfieldName)
|
|
API shortcut to unbind groups: cfxSSBClient.unbindGroup(group)
|
|
verbose messages now identify better: "+++SSB:"
|
|
keepInAirGroups option
|
|
2.0.0 - include single-use ability: crashed airplanes are blocked from further use
|
|
- single-use can be turned off
|
|
- getPlayerGroupForGroupNamed()
|
|
- split setSlotAccess to single accessor
|
|
and interator
|
|
- reUseAfter option for single-use
|
|
- dcsCommon, cfxZones import
|
|
2.0.1 - stricter verbosity: moved more comments to verbose only
|
|
2.0.2 - health check code (initial)
|
|
- added verbosity
|
|
2.0.3 - getPlayerName nil-trap on cloned player planes guard
|
|
in onEvent
|
|
2.1.0 - slotState
|
|
- persistence
|
|
3.0.0 - closing an airfield will not kick players who are active
|
|
- much better verbosity
|
|
- open?
|
|
- close?
|
|
- also persists closed airfield list
|
|
3.0.1 - ability to detect if an airfield doesn't exist (late activate)
|
|
|
|
|
|
|
|
WHAT IT IS
|
|
SSB Client is a small script that forms the client-side counterpart to
|
|
Ciribob's simple slot block. It will block slots for all client airframes
|
|
that are on an airfield that does not belong to the faction that currently
|
|
owns the airfield.
|
|
|
|
REQUIRES CIRIBOB's SIMPLE SLOT BLOCK (SSB) TO RUN ON THE SERVER
|
|
|
|
If run without SSB, your planes will not be blocked.
|
|
|
|
In order to work, a plane that should be blocked when the airfield or
|
|
FARP doesn't belong to the player's faction, the group's first unit
|
|
must be within 3000 meters of the airfield and on the ground.
|
|
Previous versions of this script relied on group names. No longer.
|
|
|
|
|
|
WARNING:
|
|
If you modified ssb's flag values, this script will not work
|
|
|
|
YOU DO NOT NEED TO ACTIVATE SBB, THIS SCRIPT DOES SO AUTOMAGICALLY
|
|
|
|
|
|
--]]--
|
|
|
|
-- below value for enabled MUST BE THE SAME AS THE VALUE OF THE SAME NAME
|
|
-- IN SSB. DEFAULT IS ZERO, AND THIS WILL WORK
|
|
|
|
cfxSSBClient.enabledFlagValue = 0 -- DO NOT CHANGE, MUST MATCH SSB
|
|
cfxSSBClient.disabledFlagValue = cfxSSBClient.enabledFlagValue + 100 -- DO NOT CHANGE
|
|
cfxSSBClient.allowNeutralFields = false -- set to FALSE if players can't spawn on neutral airfields
|
|
cfxSSBClient.maxAirfieldRange = 3000 -- meters to airfield before group is no longer associated with airfield
|
|
-- actions to home in on when a player plane is detected and a slot may
|
|
-- be blocked. Currently, homing in on airfield, but not fly over
|
|
cfxSSBClient.slotActions = {
|
|
"From Runway",
|
|
"From Parking Area",
|
|
"From Parking Area Hot",
|
|
"From Ground Area",
|
|
"From Ground Area Hot",
|
|
}
|
|
cfxSSBClient.keepInAirGroups = false -- if false we only look at planes starting on the ground
|
|
-- setting this to true only makes sense if you plan to bind in-air starts to airfields
|
|
|
|
cfxSSBClient.playerGroups = {}
|
|
cfxSSBClient.closedAirfields = {} -- list that closes airfields for any aircrafts
|
|
cfxSSBClient.playerPlanes = {} -- names of units that a player is flying
|
|
cfxSSBClient.crashedGroups = {} -- names of groups to block after crash of their player-flown plane
|
|
cfxSSBClient.slotState = {} -- keeps a record of which slot has which value. For persistence and debugging
|
|
cfxSSBClient.occupiedUnits = {} -- by unit name if occupied to prevent kicking. clears after crash or leaving plane
|
|
-- will not be persisted because on start all planes are empty
|
|
|
|
-- dml zone interface for open/close interface
|
|
cfxSSBClient.clientZones = {}
|
|
|
|
function cfxSSBClient.addClientZone(theZone)
|
|
table.insert(cfxSSBClient.clientZones, theZone)
|
|
end
|
|
|
|
function cfxSSBClient.getClientZoneByName(aName)
|
|
for idx, aZone in pairs(cfxSSBClient.clientZones) do
|
|
if aName == aZone.name then return aZone end
|
|
end
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++ssbc: no client zone with name <" .. aName ..">", 30)
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--
|
|
-- read client zones
|
|
--
|
|
function cfxSSBClient.createClientZone(theZone)
|
|
local thePoint = cfxZones.getPoint(theZone)
|
|
local theAF = cfxSSBClient.getClosestAirbaseTo(thePoint)
|
|
local afName = theAF:getName()
|
|
if cfxSSBClient.verbose or theZone.verbose then
|
|
trigger.action.outText("+++ssbc: zone <" .. theZone.name .. "> linked to AF/FARP <" .. afName .. ">", 30)
|
|
end
|
|
theZone.afName = afName
|
|
theZone.ssbTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "ssbTriggerMethod", "change")
|
|
|
|
if cfxZones.hasProperty(theZone, "open?") then
|
|
theZone.ssbOpen = cfxZones.getStringFromZoneProperty(theZone, "open?", "none")
|
|
theZone.lastSsbOpen = cfxZones.getFlagValue(theZone.ssbOpen, theZone)
|
|
end
|
|
|
|
if cfxZones.hasProperty(theZone, "close?") then
|
|
theZone.ssbClose = cfxZones.getStringFromZoneProperty(theZone, "close?", "none")
|
|
theZone.lastSsbClose = cfxZones.getFlagValue(theZone.ssbClose, theZone)
|
|
end
|
|
|
|
theZone.ssbOpenOnStart = cfxZones.getBoolFromZoneProperty(theZone, "openOnStart", true)
|
|
if not theZone.ssbOpenOnStart then
|
|
cfxSSBClient.closeAirfieldNamed(theZone.afName)
|
|
end
|
|
end
|
|
|
|
--
|
|
-- Open / Close Airfield API
|
|
--
|
|
function cfxSSBClient.closeAirfieldNamed(name)
|
|
if not name then return end
|
|
cfxSSBClient.closedAirfields[name] = true
|
|
cfxSSBClient.setSlotAccessByAirfieldOwner()
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSB: Airfield " .. name .. " now closed", 30)
|
|
end
|
|
end
|
|
|
|
function cfxSSBClient.openAirFieldNamed(name)
|
|
cfxSSBClient.closedAirfields[name] = nil
|
|
cfxSSBClient.setSlotAccessByAirfieldOwner()
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSB: Airfield " .. name .. " just opened", 30)
|
|
end
|
|
end
|
|
|
|
function cfxSSBClient.unbindGroup(groupName)
|
|
cfxSSBClient.bindGroupToAirfield(groupName, nil)
|
|
end
|
|
|
|
function cfxSSBClient.bindGroupToAirfield(groupName, airfieldName)
|
|
if not groupName then return end
|
|
local airfield = nil
|
|
if airfieldName then airfield = Airbase.getByName(airfieldName) end
|
|
for idx, theGroup in pairs(cfxSSBClient.playerGroups) do
|
|
if theGroup.name == groupName then
|
|
if cfxSSBClient.verbose then
|
|
local newBind = "NIL"
|
|
if airfield then newBind = airfieldName end
|
|
trigger.action.outText("+++SSB: Group " .. theGroup.name .. " changed binding to " .. newBind, 30)
|
|
end
|
|
theGroup.airfield = airfield
|
|
return
|
|
end
|
|
end
|
|
if not airfieldName then airfieldName = "<NIL>" end
|
|
trigger.action.outText("+++SSB: Binding Group " .. groupName .. " to " .. airfieldName .. " failed.", 30)
|
|
end
|
|
|
|
|
|
function cfxSSBClient.getClosestAirbaseTo(thePoint)
|
|
local delta = math.huge
|
|
local allYourBase = world.getAirbases() -- get em all
|
|
local closestBase = nil
|
|
for idx, aBase in pairs(allYourBase) do
|
|
-- iterate them all
|
|
local abPoint = aBase:getPoint()
|
|
newDelta = dcsCommon.dist(thePoint, {x=abPoint.x, y = 0, z=abPoint.z})
|
|
if newDelta < delta then
|
|
delta = newDelta
|
|
closestBase = aBase
|
|
end
|
|
end
|
|
return closestBase, delta
|
|
end
|
|
|
|
function cfxSSBClient.setSlotAccessForGroup(theGroup)
|
|
if not theGroup then return end
|
|
-- WARNING: theGroup is cfxGroup record
|
|
local theName = theGroup.name
|
|
|
|
-- we now check if any plane of that group is still
|
|
-- existing and in the air. if so, we skip this check
|
|
-- to prevent players being kicked for losing their
|
|
-- originating airfield
|
|
|
|
-- we now iterate all playerUnits in theGroup.
|
|
-- theGroup is cfxGroup
|
|
for idx, playerData in pairs (theGroup.playerUnits) do
|
|
local uName = playerData.name
|
|
if cfxSSBClient.occupiedUnits[uName] then
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++ssbc: unit <" .. uName .. "> of group <" .. theName .. "> is occupied, no airfield check", 30)
|
|
end
|
|
return
|
|
end
|
|
end
|
|
|
|
-- when we get here, no unit in the entire group is occupied
|
|
local theMatchingAirfield = theGroup.airfield
|
|
-- airfield was attached at startup to group
|
|
if cfxSSBClient.singleUse and cfxSSBClient.crashedGroups[theName] then
|
|
-- we don't check, as we know it's blocked after crash
|
|
-- and leave it as it is. Nothing to do at all now
|
|
|
|
elseif theMatchingAirfield ~= nil then
|
|
local blockState = cfxSSBClient.enabledFlagValue -- we default to ALLOW the block
|
|
local comment = "available"
|
|
-- we have found a plane that is tied to an airfield
|
|
-- so this group will receive a block/unblock
|
|
-- we always set all block/unblock every time
|
|
|
|
-- see if airfield currently exist (might be dead or late activate)
|
|
if not Object.isExist(theMatchingAirfield) then
|
|
-- airfield does not exits yet/any more
|
|
blockState = cfxSSBClient.disabledFlagValue
|
|
comment = "!inactive airfield!"
|
|
else
|
|
local airFieldSide = theMatchingAirfield:getCoalition()
|
|
local groupCoalition = theGroup.coaNum
|
|
|
|
-- see if airfield is closed
|
|
local afName = theMatchingAirfield:getName()
|
|
if cfxSSBClient.closedAirfields[afName] then
|
|
-- airfield is closed. no take-offs
|
|
blockState = cfxSSBClient.disabledFlagValue
|
|
comment = "!closed airfield!"
|
|
end
|
|
|
|
-- on top of that, check coalitions
|
|
if groupCoalition ~= airFieldSide then
|
|
-- we have a problem. sides don't match
|
|
if airFieldSide == 3
|
|
or (cfxSSBClient.allowNeutralFields and airFieldSide == 0)
|
|
then
|
|
-- all is well, airfield is contested or neutral and
|
|
-- we allow this plane to spawn here
|
|
else
|
|
-- DISALLOWED!!!!
|
|
blockState = cfxSSBClient.disabledFlagValue
|
|
comment = "!!!BLOCKED!!!"
|
|
end
|
|
end
|
|
end
|
|
|
|
-- now set the ssb flag for this group so the server can see it
|
|
if cfxSSBClient.verbose then
|
|
local lastState = trigger.misc.getUserFlag(theName)
|
|
if lastState ~= blockState then
|
|
trigger.action.outText("+++ssbc: <" .. theName .. "> changes from <" .. lastState .. "> to <" .. blockState .. ">", 30)
|
|
trigger.action.outText("+++SSB: group ".. theName .. ": " .. comment, 30)
|
|
end
|
|
end
|
|
trigger.action.setUserFlag(theName, blockState)
|
|
cfxSSBClient.slotState[theName] = blockState
|
|
--if cfxSSBClient.verbose then
|
|
--end
|
|
else
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSB: group ".. theName .. " no bound airfield: available", 30)
|
|
end
|
|
end
|
|
end
|
|
|
|
function cfxSSBClient.setSlotAccessForUnit(theUnit) -- calls setSlotAccessForGroup
|
|
if not theUnit then return end
|
|
local theGroup = theUnit:getGroup()
|
|
if not theGroup then return end
|
|
local gName = theGroup:getName()
|
|
if not gName then return end
|
|
local pGroup = cfxSSBClient.getPlayerGroupForGroupNamed(gName)
|
|
if pGroup then
|
|
cfxSSBClient.setSlotAccessForGroup(pGroup)
|
|
end
|
|
end
|
|
|
|
function cfxSSBClient.getPlayerGroupForGroupNamed(aName)
|
|
local pGroups = cfxSSBClient.playerGroups
|
|
for idx, theGroup in pairs(pGroups) do
|
|
if theGroup.name == aName then return theGroup end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function cfxSSBClient.setSlotAccessByAirfieldOwner()
|
|
-- get all groups that have a player-controlled aircraft
|
|
-- now uses cached, reduced set of player planes
|
|
local pGroups = cfxSSBClient.playerGroups
|
|
for idx, theGroup in pairs(pGroups) do
|
|
cfxSSBClient.setSlotAccessForGroup(theGroup)
|
|
end
|
|
end
|
|
|
|
function cfxSSBClient.reOpenSlotForGroupNamed(args)
|
|
-- this is merely the timer shell for opening the crashed slot
|
|
gName = args[1]
|
|
cfxSSBClient.openSlotForCrashedGroupNamed(gName)
|
|
end
|
|
|
|
function cfxSSBClient.openSlotForCrashedGroupNamed(gName)
|
|
if not gName then return end
|
|
local pGroup = cfxSSBClient.getPlayerGroupForGroupNamed(gName)
|
|
if not pGroup then return end
|
|
cfxSSBClient.crashedGroups[gName] = nil -- set to nil to forget this happened
|
|
cfxSSBClient.setSlotAccessForGroup(pGroup) -- set by current occupation status
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSBC:SU: re-opened slot for group <" .. gName .. ">", 30)
|
|
end
|
|
end
|
|
|
|
function cfxSSBClient:onEvent(event)
|
|
if event.id == 21 then -- S_EVENT_PLAYER_LEAVE_UNIT
|
|
local theUnit = event.initiator
|
|
if not theUnit then
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSB: No unit left, abort", 30)
|
|
end
|
|
return
|
|
end
|
|
local curH = theUnit:getLife()
|
|
local maxH = theUnit:getLife0()
|
|
local uName = theUnit:getName()
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSB: Player leaves unit <" .. uName .. ">", 30)
|
|
trigger.action.outText("+++SSB: unit health check: " .. curH .. " of " .. maxH, 30)
|
|
end
|
|
|
|
cfxSSBClient.occupiedUnits[uName] = nil -- forget I was occupied
|
|
cfxSSBClient.setSlotAccessForUnit(theUnit) -- prevent re-slotting if airfield lost
|
|
return
|
|
end
|
|
|
|
if event.id == 10 then -- S_EVENT_BASE_CAPTURED
|
|
if cfxSSBClient.verbose then
|
|
local place = event.place
|
|
|
|
trigger.action.outText("+++SSB: CAPTURE EVENT: <" .. place:getName() .. "> now owned by <" .. place:getCoalition() .. "> -- RESETTING SLOTS", 30)
|
|
end
|
|
cfxSSBClient.setSlotAccessByAirfieldOwner()
|
|
end
|
|
|
|
-- write down player names and planes
|
|
if event.id == 15 then -- birth
|
|
if not event.initiator then return end
|
|
local theUnit = event.initiator -- we know this exists
|
|
local uName = theUnit:getName()
|
|
if not uName then return end
|
|
-- player entered unit?
|
|
-- check if this is a cloned impostor
|
|
if not theUnit.getPlayerName then
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSBC: non-player 'client' " .. uName .. " detected, ignoring.", 30)
|
|
end
|
|
return
|
|
end
|
|
local playerName = theUnit:getPlayerName()
|
|
if not playerName then
|
|
return -- NPC plane
|
|
end
|
|
-- remember this unit as player controlled plane
|
|
-- because player and plane can easily disconnect
|
|
cfxSSBClient.playerPlanes[uName] = playerName
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSBC:SU: noted " .. playerName .. " piloting player unit " .. uName, 30)
|
|
end
|
|
-- mark it as occupied to player won't get kicked until they
|
|
-- leave the unit
|
|
cfxSSBClient.occupiedUnits[uName] = playerName
|
|
return
|
|
end
|
|
|
|
if event.id == 5 then -- crash PRE-processing
|
|
if not event.initiator then return end
|
|
local theUnit = event.initiator
|
|
local uName = theUnit:getName()
|
|
cfxSSBClient.occupiedUnits[uName] = nil -- no longer occupied
|
|
cfxSSBClient.setSlotAccessForUnit(theUnit) -- prevent re-slotting if airfield lost
|
|
end
|
|
|
|
if cfxSSBClient.singleUse and event.id == 5 then -- crash
|
|
--if not event.initiator then return end
|
|
local theUnit = event.initiator
|
|
local uName = theUnit:getName()
|
|
if not uName then return end
|
|
local theGroup = theUnit:getGroup()
|
|
if not theGroup then return end
|
|
-- see if a player plane
|
|
local thePilot = cfxSSBClient.playerPlanes[uName]
|
|
if not thePilot then
|
|
-- ignore. not a player plane
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSBC:SU: ignored crash for NPC unit <" .. uName .. ">", 30)
|
|
end
|
|
return
|
|
end
|
|
-- if we get here, a player-owned plane has crashed
|
|
local gName = theGroup:getName()
|
|
if not gName then return end
|
|
|
|
-- block this slot.
|
|
trigger.action.setUserFlag(gName, cfxSSBClient.disabledFlagValue)
|
|
cfxSSBClient.slotState[gName] = cfxSSBClient.disabledFlagValue
|
|
-- remember this plane to not re-enable if
|
|
-- airfield changes hands later
|
|
cfxSSBClient.crashedGroups[gName] = thePilot -- set to crash pilot
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSBC:SU: Blocked slot for group <" .. gName .. ">", 30)
|
|
end
|
|
|
|
if cfxSSBClient.reUseAfter > 0 then
|
|
-- schedule re-opening this slot in <x> seconds
|
|
timer.scheduleFunction(
|
|
cfxSSBClient.reOpenSlotForGroupNamed,
|
|
{gName},
|
|
timer.getTime() + cfxSSBClient.reUseAfter
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
function cfxSSBClient.update()
|
|
-- first, re-schedule me in one minute
|
|
timer.scheduleFunction(cfxSSBClient.update, {}, timer.getTime() + 60)
|
|
|
|
-- now establish all slot blocks
|
|
cfxSSBClient.setSlotAccessByAirfieldOwner()
|
|
|
|
-- show occupied planes
|
|
if cfxSSBClient.verbose then
|
|
for uName, pName in pairs (cfxSSBClient.occupiedUnits) do
|
|
trigger.action.outText("+++ssbc: <" .. uName .. "> occupied by <" .. pName .. ">", 30)
|
|
end
|
|
end
|
|
end
|
|
|
|
function cfxSSBClient.dmlUpdate()
|
|
-- first, re-schedule me in one second
|
|
timer.scheduleFunction(cfxSSBClient.dmlUpdate, {}, timer.getTime() + 1)
|
|
|
|
for idx, theZone in pairs (cfxSSBClient.clientZones) do
|
|
-- see if we received any signals on out inputs
|
|
if theZone.ssbOpen and cfxZones.testZoneFlag(theZone, theZone.ssbOpen, theZone.ssbTriggerMethod, "lastSsbOpen") then
|
|
if theZone.verbose then
|
|
trigger.action.outText("+++ssbc: <" .. theZone.name .. "> open input triggered for <" .. theZone.afName .. ">", 30)
|
|
end
|
|
cfxSSBClient.openAirFieldNamed(theZone.afName)
|
|
end
|
|
|
|
if theZone.ssbClose and cfxZones.testZoneFlag(theZone, theZone.ssbClose, theZone.ssbTriggerMethod, "lastSsbClose") then
|
|
if theZone.verbose then
|
|
trigger.action.outText("+++ssbc: <" .. theZone.name .. "> close input triggered for <" .. theZone.afName .. ">", 30)
|
|
end
|
|
cfxSSBClient.closeAirfieldNamed(theZone.afName)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- pre-process static player data to minimize
|
|
-- processor load on checks
|
|
function cfxSSBClient.processPlayerData()
|
|
cfxSSBClient.playerGroups = cfxGroups.getPlayerGroup()
|
|
local pGroups = cfxSSBClient.playerGroups
|
|
local filteredPlayers = {}
|
|
for idx, theGroup in pairs(pGroups) do
|
|
if theGroup.airfield ~= nil or cfxSSBClient.keepInAirGroups or
|
|
cfxSSBClient.singleUse then
|
|
-- only transfer groups that have airfields (or also keepInAirGroups or when single-use)
|
|
-- attached. Ignore the rest as they are
|
|
-- always fine
|
|
table.insert(filteredPlayers, theGroup)
|
|
end
|
|
end
|
|
cfxSSBClient.playerGroups = filteredPlayers
|
|
end
|
|
|
|
-- add airfield information to each player group
|
|
function cfxSSBClient.processGroupData()
|
|
local pGroups = cfxGroups.getPlayerGroup() -- we want the group.name attribute
|
|
for idx, theGroup in pairs(pGroups) do
|
|
-- we always use the first player's plane as referenced
|
|
local playerData = theGroup.playerUnits[1]
|
|
local theAirfield = nil
|
|
local delta = -1
|
|
local action = playerData.action
|
|
if not action then action = "<NIL>" end
|
|
-- see if the data has any of the slot-interesting actions
|
|
if dcsCommon.arrayContainsString(cfxSSBClient.slotActions, action ) then
|
|
-- yes, fetch the closest airfield
|
|
theAirfield, delta = cfxSSBClient.getClosestAirbaseTo(playerData.point)
|
|
local afName = theAirfield:getName()
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSB: group: " .. theGroup.name .. " closest to AF " .. afName .. ": " .. math.floor(delta) .. "m" , 30)
|
|
end
|
|
if delta > cfxSSBClient.maxAirfieldRange then
|
|
-- forget airfield
|
|
theAirfield = nil
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSB: group: " .. theGroup.name .. " unlinked - too far from airfield" , 30)
|
|
end
|
|
end
|
|
theGroup.airfield = theAirfield
|
|
else
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++SSB: group: " .. theGroup.name .. " start option " .. action .. " does not concern SSB", 30)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--
|
|
-- read config zone
|
|
--
|
|
function cfxSSBClient.readConfigZone()
|
|
-- note: must match exactly!!!!
|
|
local theZone = cfxZones.getZoneByName("SSBClientConfig")
|
|
if not theZone then
|
|
trigger.action.outText("+++SSBC: no config zone!", 30)
|
|
return
|
|
end
|
|
|
|
trigger.action.outText("+++SSBC: found config zone!", 30)
|
|
|
|
cfxSSBClient.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
|
|
|
-- single-use
|
|
cfxSSBClient.singleUse = cfxZones.getBoolFromZoneProperty(theZone, "singleUse", false) -- use airframes only once? respawn after kick must be disabled in ssb
|
|
cfxSSBClient.reUseAfter = cfxZones.getNumberFromZoneProperty(theZone, "reUseAfter", -1)
|
|
|
|
-- airfield availability
|
|
cfxSSBClient.allowNeutralFields = cfxZones.getBoolFromZoneProperty(theZone, "allowNeutralFields", false)
|
|
|
|
cfxSSBClient.maxAirfieldRange = cfxZones.getNumberFromZoneProperty(theZone, "maxAirfieldRange", 3000) -- meters, to find attached airfield
|
|
|
|
-- optimization
|
|
|
|
cfxSSBClient.keepInAirGroups = cfxZones.getBoolFromZoneProperty(theZone, "keepInAirGroups", false)
|
|
|
|
-- SSB direct control.
|
|
-- USE ONLY WHEN YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
|
|
|
|
cfxSSBClient.enabledFlagValue = cfxZones.getNumberFromZoneProperty(theZone, "enabledFlagValue", 0)
|
|
|
|
cfxSSBClient.disabledFlagValue = cfxZones.getNumberFromZoneProperty(theZone, "disabledFlagValue", cfxSSBClient.enabledFlagValue + 100)
|
|
end
|
|
|
|
--
|
|
-- load / save
|
|
--
|
|
function cfxSSBClient.saveData()
|
|
local theData = {}
|
|
local states = dcsCommon.clone(cfxSSBClient.slotState)
|
|
local crashed = dcsCommon.clone(cfxSSBClient.crashedGroups)
|
|
local closed = dcsCommon.clone(cfxSSBClient.closedAirfields)
|
|
theData.states = states
|
|
theData.crashed = crashed
|
|
theData.closed = closed
|
|
return theData
|
|
end
|
|
|
|
function cfxSSBClient.loadData()
|
|
if not persistence then return end
|
|
local theData = persistence.getSavedDataForModule("cfxSSBClient")
|
|
if not theData then
|
|
if cfxSSBClient.verbose then
|
|
trigger.action.outText("+++cfxSSB: no save date received, skipping.", 30)
|
|
end
|
|
return
|
|
end
|
|
|
|
cfxSSBClient.slotState = theData.states
|
|
if not cfxSSBClient.slotState then
|
|
trigger.action.outText("SSBClient: nil slot state on load", 30)
|
|
cfxSSBClient.slotState = {}
|
|
end
|
|
for slot, state in pairs (cfxSSBClient.slotState) do
|
|
trigger.action.setUserFlag(slot, state)
|
|
if state > 0 and cfxSSBClient.verbose then
|
|
trigger.action.outText("SSB: blocked <" .. slot .. "> on load", 30)
|
|
end
|
|
end
|
|
if theData.crashed then
|
|
cfxSSBClient.crashedGroups = theData.crashed
|
|
if not cfxSSBClient.crashedGroups then
|
|
cfxSSBClient.crashedGroups = {}
|
|
trigger.action.outText("SSBClient: nil crashers on load", 30)
|
|
end
|
|
end
|
|
|
|
if theData.closed then
|
|
cfxSSBClient.closedAirfields = theData.closed
|
|
end
|
|
|
|
end
|
|
|
|
--
|
|
-- start
|
|
--
|
|
function cfxSSBClient.start()
|
|
-- verify modules loaded
|
|
if not dcsCommon.libCheck("cfx SSB Client",
|
|
cfxSSBClient.requiredLibs) then
|
|
return false
|
|
end
|
|
|
|
-- read config zone if present
|
|
cfxSSBClient.readConfigZone()
|
|
|
|
-- install callback for events in DCS
|
|
world.addEventHandler(cfxSSBClient)
|
|
|
|
-- process group data to attach airfields
|
|
cfxSSBClient.processGroupData()
|
|
|
|
-- process player data to minimize effort and build cache
|
|
-- into cfxSSBClient.playerGroups
|
|
cfxSSBClient.processPlayerData()
|
|
|
|
-- process ssbc zones
|
|
-- for in-mission DML interface
|
|
local attrZones = cfxZones.getZonesWithAttributeNamed("ssbClient")
|
|
for k, theZone in pairs(attrZones) do
|
|
cfxSSBClient.createClientZone(theZone) -- process attributes
|
|
cfxSSBClient.addClientZone(theZone) -- add to list
|
|
end
|
|
|
|
-- install a timed update just to make sure
|
|
-- and start NOW
|
|
timer.scheduleFunction(cfxSSBClient.update, {}, timer.getTime() + 1)
|
|
|
|
-- start dml update (on a different timer
|
|
cfxSSBClient.dmlUpdate()
|
|
|
|
-- now turn on ssb
|
|
trigger.action.setUserFlag("SSB",100)
|
|
|
|
-- persistence: load states
|
|
if persistence then
|
|
-- sign up for persistence
|
|
callbacks = {}
|
|
callbacks.persistData = cfxSSBClient.saveData
|
|
persistence.registerModule("cfxSSBClient", callbacks)
|
|
-- now load my data
|
|
cfxSSBClient.loadData()
|
|
end
|
|
|
|
-- say hi!
|
|
trigger.action.outText("cfxSSBClient v".. cfxSSBClient.version .. " running, SBB enabled", 30)
|
|
|
|
|
|
return true
|
|
end
|
|
|
|
if not cfxSSBClient.start() then
|
|
trigger.action.outText("cfxSSBClient v".. cfxSSBClient.version .. " FAILED loading.", 30)
|
|
cfxSSBClient = nil
|
|
end
|
|
|
|
--[[--
|
|
possible improvements:
|
|
- use explicitBlockList that with API. planes on that list are always blocked. Use this for special effects, such as allowing a slot only to open from scripts, e.g. when a condition is met like money or goals reached
|
|
|
|
-]]-- |