Progress on the new AISET_BALANCER class

This commit is contained in:
Sven Van de Velde 2016-08-24 14:34:04 +02:00
parent 167b0dd598
commit 0edb083d5d
64 changed files with 1133 additions and 506 deletions

View File

@ -1,283 +0,0 @@
--- This module contains the AIBALANCER class.
--
-- ===
--
-- 1) @{AIBalancer#AIBALANCER} class, extends @{Base#BASE}
-- =======================================================
-- The @{AIBalancer#AIBALANCER} class controls the dynamic spawning of AI GROUPS depending on a SET_CLIENT.
-- There will be as many AI GROUPS spawned as there at CLIENTS in SET_CLIENT not spawned.
-- The AIBalancer uses the @{PatrolZone#PATROLZONE} class to make AI patrol an zone until the fuel treshold is reached.
--
-- 1.1) AIBALANCER construction method:
-- ------------------------------------
-- Create a new AIBALANCER object with the @{#AIBALANCER.New} method:
--
-- * @{#AIBALANCER.New}: Creates a new AIBALANCER object.
--
-- 1.2) AIBALANCER returns AI to Airbases:
-- ---------------------------------------
-- You can configure to have the AI to return to:
--
-- * @{#AIBALANCER.ReturnToHomeAirbase}: Returns the AI to the home @{Airbase#AIRBASE}.
-- * @{#AIBALANCER.ReturnToNearestAirbases}: Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
--
-- 1.3) AIBALANCER allows AI to patrol specific zones:
-- ---------------------------------------------------
-- Use @{AIBalancer#AIBALANCER.SetPatrolZone}() to specify a zone where the AI needs to patrol.
--
-- ===
--
-- **API CHANGE HISTORY**
-- ======================
--
-- The underlying change log documents the API changes. Please read this carefully. The following notation is used:
--
-- * **Added** parts are expressed in bold type face.
-- * _Removed_ parts are expressed in italic type face.
--
-- Hereby the change log:
--
-- 2016-08-17: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval )
--
-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called!
-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods.
--
-- ===
--
-- AUTHORS and CONTRIBUTIONS
-- =========================
--
-- ### Contributions:
--
-- * **Dutch_Baron (James)**: Who you can search on the Eagle Dynamics Forums.
-- Working together with James has resulted in the creation of the AIBALANCER class.
-- James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
--
-- * **SNAFU**:
-- Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE.
-- None of the script code has been used however within the new AIBALANCER moose class.
--
-- ### Authors:
--
-- * FlightControl: Framework Design & Programming
--
-- @module AIBalancer
--- AIBALANCER class
-- @type AIBALANCER
-- @field Set#SET_CLIENT SetClient
-- @field Spawn#SPAWN SpawnAI
-- @field #boolean ToNearestAirbase
-- @field Set#SET_AIRBASE ReturnAirbaseSet
-- @field DCSTypes#Distance ReturnTresholdRange
-- @field #boolean ToHomeAirbase
-- @field PatrolZone#PATROLZONE PatrolZone
-- @extends Base#BASE
AIBALANCER = {
ClassName = "AIBALANCER",
PatrolZones = {},
AIGroups = {},
}
--- Creates a new AIBALANCER object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names.
-- @param #AIBALANCER self
-- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).
-- @param SpawnAI A SPAWN object that will spawn the AI units required, balancing the SetClient.
-- @return #AIBALANCER self
function AIBALANCER:New( SetClient, SpawnAI )
-- Inherits from BASE
local self = BASE:Inherit( self, BASE:New() )
self.SetClient = SetClient
if type( SpawnAI ) == "table" then
if SpawnAI.ClassName and SpawnAI.ClassName == "SPAWN" then
self.SpawnAI = { SpawnAI }
else
local SpawnObjects = true
for SpawnObjectID, SpawnObject in pairs( SpawnAI ) do
if SpawnObject.ClassName and SpawnObject.ClassName == "SPAWN" then
self:E( SpawnObject.ClassName )
else
self:E( "other object" )
SpawnObjects = false
end
end
if SpawnObjects == true then
self.SpawnAI = SpawnAI
else
error( "No SPAWN object given in parameter SpawnAI, either as a single object or as a table of objects!" )
end
end
end
self.ToNearestAirbase = false
self.ReturnHomeAirbase = false
self.AIMonitorSchedule = SCHEDULER:New( self, self._ClientAliveMonitorScheduler, {}, 1, 10, 0 )
return self
end
--- Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
-- @param #AIBALANCER self
-- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
-- @param Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to.
function AIBALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet )
self.ToNearestAirbase = true
self.ReturnTresholdRange = ReturnTresholdRange
self.ReturnAirbaseSet = ReturnAirbaseSet
end
--- Returns the AI to the home @{Airbase#AIRBASE}.
-- @param #AIBALANCER self
-- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
function AIBALANCER:ReturnToHomeAirbase( ReturnTresholdRange )
self.ToHomeAirbase = true
self.ReturnTresholdRange = ReturnTresholdRange
end
--- Let the AI patrol a @{Zone} with a given Speed range and Altitude range.
-- @param #AIBALANCER self
-- @param PatrolZone#PATROLZONE PatrolZone The @{PatrolZone} where the AI needs to patrol.
-- @return PatrolZone#PATROLZONE self
function AIBALANCER:SetPatrolZone( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed )
self.PatrolZone = PATROLZONE:New(
self.SpawnAI,
PatrolZone,
PatrolFloorAltitude,
PatrolCeilingAltitude,
PatrolMinSpeed,
PatrolMaxSpeed
)
end
--- Get the @{PatrolZone} object assigned by the @{AIBalancer} object.
-- @param #AIBALANCER self
-- @return PatrolZone#PATROLZONE PatrolZone The @{PatrolZone} where the AI needs to patrol.
function AIBALANCER:GetPatrolZone()
return self.PatrolZone
end
--- @param #AIBALANCER self
function AIBALANCER:_ClientAliveMonitorScheduler()
self.SetClient:ForEachClient(
--- @param Client#CLIENT Client
function( Client )
local ClientAIAliveState = Client:GetState( self, 'AIAlive' )
self:T( ClientAIAliveState )
if Client:IsAlive() then
if ClientAIAliveState == true then
Client:SetState( self, 'AIAlive', false )
local AIGroup = self.AIGroups[Client.UnitName] -- Group#GROUP
-- local PatrolZone = Client:GetState( self, "PatrolZone" )
-- if PatrolZone then
-- PatrolZone = nil
-- Client:ClearState( self, "PatrolZone" )
-- end
if self.ToNearestAirbase == false and self.ToHomeAirbase == false then
AIGroup:Destroy()
else
-- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group.
-- If there is a CLIENT, the AI stays engaged and will not return.
-- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected.
local PlayerInRange = { Value = false }
local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange )
self:E( RangeZone )
_DATABASE:ForEachPlayer(
--- @param Unit#UNIT RangeTestUnit
function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange )
self:E( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } )
if RangeTestUnit:IsInZone( RangeZone ) == true then
self:E( "in zone" )
if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then
self:E( "in range" )
PlayerInRange.Value = true
end
end
end,
--- @param Zone#ZONE_RADIUS RangeZone
-- @param Group#GROUP AIGroup
function( RangeZone, AIGroup, PlayerInRange )
local AIGroupTemplate = AIGroup:GetTemplate()
if PlayerInRange.Value == false then
if self.ToHomeAirbase == true then
local WayPointCount = #AIGroupTemplate.route.points
local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 )
AIGroup:SetCommand( SwitchWayPointCommand )
AIGroup:MessageToRed( "Returning to home base ...", 30 )
else
-- Okay, we need to send this Group back to the nearest base of the Coalition of the AI.
--TODO: i need to rework the POINT_VEC2 thing.
local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y )
local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
self:T( ClosestAirbase.AirbaseName )
AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 )
local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase )
AIGroupTemplate.route = RTBRoute
AIGroup:Respawn( AIGroupTemplate )
end
end
end
, RangeZone, AIGroup, PlayerInRange
)
end
end
else
if not ClientAIAliveState or ClientAIAliveState == false then
Client:SetState( self, 'AIAlive', true )
-- OK, spawn a new group from the SpawnAI objects provided.
local SpawnAICount = #self.SpawnAI
local SpawnAIIndex = math.random( 1, SpawnAICount )
local AIGroup = self.SpawnAI[SpawnAIIndex]:Spawn()
AIGroup:E( "spawning new AIGroup" )
--TODO: need to rework UnitName thing ...
self.AIGroups[Client.UnitName] = AIGroup
--- Now test if the AIGroup needs to patrol a zone, otherwise let it follow its route...
if self.PatrolZone then
self.PatrolZones[#self.PatrolZones+1] = PATROLZONE:New(
self.PatrolZone.PatrolZone,
self.PatrolZone.PatrolFloorAltitude,
self.PatrolZone.PatrolCeilingAltitude,
self.PatrolZone.PatrolMinSpeed,
self.PatrolZone.PatrolMaxSpeed
)
if self.PatrolZone.PatrolManageFuel == true then
self.PatrolZones[#self.PatrolZones]:ManageFuel( self.PatrolZone.PatrolFuelTresholdPercentage, self.PatrolZone.PatrolOutOfFuelOrbitTime )
end
self.PatrolZones[#self.PatrolZones]:SetGroup( AIGroup )
--self.PatrolZones[#self.PatrolZones+1] = PatrolZone
--Client:SetState( self, "PatrolZone", PatrolZone )
end
end
end
end
)
return true
end

View File

@ -0,0 +1,253 @@
--- This module contains the AISET_BALANCER class.
--
-- ===
--
-- 1) @{AISet_Balancer#AISET_BALANCER} class, extends @{StateMachine#STATEMACHINE_SET}
-- ===================================================================================
-- The @{AISet_Balancer#AISET_BALANCER} class monitors and manages as many AI GROUPS as there are
-- CLIENTS in a SET_CLIENT collection not occupied by players.
-- The AI_BALANCER class manages internally a collection of AI_MANAGEMENT objects, which govern the behaviour
-- of the underlying AI GROUPS.
--
-- The parent class @{StateMachine#STATEMACHINE_SET} manages the functionality to control the Finite State Machine (FSM)
-- and calls for each event the state transition functions providing the internal @{StateMachine#STATEMACHINE_SET.Set} object containing the
-- SET_GROUP and additional event parameters provided during the event.
--
-- 1.1) AISET_BALANCER construction method
-- ---------------------------------------
-- Create a new AISET_BALANCER object with the @{#AISET_BALANCER.New} method:
--
-- * @{#AISET_BALANCER.New}: Creates a new AISET_BALANCER object.
--
-- 1.2)
-- ----
-- * Add
-- * Remove
--
-- 1.2) AISET_BALANCER returns AI to Airbases
-- ------------------------------------------
-- You can configure to have the AI to return to:
--
-- * @{#AISET_BALANCER.ReturnToHomeAirbase}: Returns the AI to the home @{Airbase#AIRBASE}.
-- * @{#AISET_BALANCER.ReturnToNearestAirbases}: Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
-- --
-- ===
--
-- **API CHANGE HISTORY**
-- ======================
--
-- The underlying change log documents the API changes. Please read this carefully. The following notation is used:
--
-- * **Added** parts are expressed in bold type face.
-- * _Removed_ parts are expressed in italic type face.
--
-- Hereby the change log:
--
-- 2016-08-17: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval )
--
-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called!
-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods.
--
-- ===
--
-- AUTHORS and CONTRIBUTIONS
-- =========================
--
-- ### Contributions:
--
-- * **Dutch_Baron (James)**: Who you can search on the Eagle Dynamics Forums.
-- Working together with James has resulted in the creation of the AISET_BALANCER class.
-- James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
--
-- * **SNAFU**:
-- Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE.
-- None of the script code has been used however within the new AISET_BALANCER moose class.
--
-- ### Authors:
--
-- * FlightControl: Framework Design & Programming
--
-- @module AISet_Balancer
--- AISET_BALANCER class
-- @type AISET_BALANCER
-- @field Set#SET_CLIENT SetClient
-- @extends StateMachine#STATEMACHINE_SET
AISET_BALANCER = {
ClassName = "AISET_BALANCER",
PatrolZones = {},
AIGroups = {},
}
--- Creates a new AI\_SET\_BALANCER object
-- @param #AISET_BALANCER self
-- @param Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).
-- @param Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
-- @return #AISET_BALANCER
-- @usage
-- -- Define a new AISET_BALANCER Object.
function AISET_BALANCER:New( SetClient, SpawnAI )
local FSMT = {
initial = 'None',
events = {
{ name = 'Start', from = '*', to = 'Monitoring' },
{ name = 'Monitor', from = '*', to = 'Monitoring' },
{ name = 'Spawn', from = '*', to = 'Spawning' },
{ name = 'Destroy', from = '*', to = 'Destroying' },
{ name = 'Return', from = '*', to = 'Returning' },
{ name = 'End', from = '*', to = 'End' },
{ name = 'Dead', from = '*', to = 'End' },
},
}
-- Inherits from BASE
local self = BASE:Inherit( self, STATEMACHINE_SET:New( FSMT, SET_GROUP:New() ) )
self.SetClient = SetClient
self.SpawnAI = SpawnAI
self.ToNearestAirbase = false
self.ToHomeAirbase = false
self:__Start( 1 )
return self
end
--- Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
-- @param #AISET_BALANCER self
-- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
-- @param Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to.
function AISET_BALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet )
self.ToNearestAirbase = true
self.ReturnTresholdRange = ReturnTresholdRange
self.ReturnAirbaseSet = ReturnAirbaseSet
end
--- Returns the AI to the home @{Airbase#AIRBASE}.
-- @param #AISET_BALANCER self
-- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
function AISET_BALANCER:ReturnToHomeAirbase( ReturnTresholdRange )
self.ToHomeAirbase = true
self.ReturnTresholdRange = ReturnTresholdRange
end
--- @param #AISET_BALANCER self
-- @param Set#SET_GROUP SetGroup
-- @param #string ClientName
-- @param Group#GROUP AIGroup
function AISET_BALANCER:onenterSpawning( SetGroup, ClientName )
-- OK, Spawn a new group from the default SpawnAI object provided.
local AIGroup = self.SpawnAI:Spawn()
AIGroup:E( "Spawning new AIGroup" )
--TODO: need to rework UnitName thing ...
SetGroup:Add( ClientName, AIGroup )
end
--- @param #AISET_BALANCER self
-- @param Set#SET_GROUP SetGroup
-- @param Group#GROUP AIGroup
function AISET_BALANCER:onenterDestroying( SetGroup, AIGroup )
AIGroup:Destroy()
end
--- @param #AISET_BALANCER self
-- @param Set#SET_GROUP SetGroup
-- @param Group#GROUP AIGroup
function AISET_BALANCER:onenterReturning( SetGroup, AIGroup )
local AIGroupTemplate = AIGroup:GetTemplate()
if self.ToHomeAirbase == true then
local WayPointCount = #AIGroupTemplate.route.points
local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 )
AIGroup:SetCommand( SwitchWayPointCommand )
AIGroup:MessageToRed( "Returning to home base ...", 30 )
else
-- Okay, we need to send this Group back to the nearest base of the Coalition of the AI.
--TODO: i need to rework the POINT_VEC2 thing.
local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y )
local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
self:T( ClosestAirbase.AirbaseName )
AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 )
local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase )
AIGroupTemplate.route = RTBRoute
AIGroup:Respawn( AIGroupTemplate )
end
end
--- @param #AISET_BALANCER self
function AISET_BALANCER:onenterMonitoring( SetGroup )
self.SetClient:ForEachClient(
--- @param Client#CLIENT Client
function( Client )
self:E(Client.ClientName)
local AIGroup = self.Set:Get( Client.UnitName ) -- Group#GROUP
if Client:IsAlive() then
if AIGroup and AIGroup:IsAlive() == true then
if self.ToNearestAirbase == false and self.ToHomeAirbase == false then
self:Destroy( AIGroup )
else
-- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group.
-- If there is a CLIENT, the AI stays engaged and will not return.
-- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected.
local PlayerInRange = { Value = false }
local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange )
self:E( RangeZone )
_DATABASE:ForEachPlayer(
--- @param Unit#UNIT RangeTestUnit
function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange )
self:E( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } )
if RangeTestUnit:IsInZone( RangeZone ) == true then
self:E( "in zone" )
if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then
self:E( "in range" )
PlayerInRange.Value = true
end
end
end,
--- @param Zone#ZONE_RADIUS RangeZone
-- @param Group#GROUP AIGroup
function( RangeZone, AIGroup, PlayerInRange )
if PlayerInRange.Value == false then
self:Return( AIGroup )
end
end
, RangeZone, AIGroup, PlayerInRange
)
end
self.Set:Remove( Client.UnitName )
end
else
if not AIGroup or not AIGroup:IsAlive() == true then
self:E("client not alive")
self:Spawn( Client.UnitName )
self:E("text after spawn")
end
end
return true
end
)
self:__Monitor( 10 )
end

View File

@ -350,7 +350,7 @@ function BASE:GetState( Object, StateName )
local ClassNameAndID = Object:GetClassNameAndID() local ClassNameAndID = Object:GetClassNameAndID()
if self.States[ClassNameAndID] then if self.States[ClassNameAndID] then
local State = self.States[ClassNameAndID][StateName] local State = self.States[ClassNameAndID][StateName] or false
self:T2( { ClassNameAndID, StateName, State } ) self:T2( { ClassNameAndID, StateName, State } )
return State return State
end end

View File

@ -34,7 +34,6 @@ Include.File( "Movement" )
Include.File( "Sead" ) Include.File( "Sead" )
Include.File( "Escort" ) Include.File( "Escort" )
Include.File( "MissileTrainer" ) Include.File( "MissileTrainer" )
--Include.File( "AIBalancer" )
Include.File( "AirbasePolice" ) Include.File( "AirbasePolice" )
Include.File( "Detection" ) Include.File( "Detection" )
@ -54,7 +53,10 @@ Include.File( "Task" )
Include.File( "Task_SEAD" ) Include.File( "Task_SEAD" )
Include.File( "Task_A2G" ) Include.File( "Task_A2G" )
--- AI Handling Classes --- AI Set Handling Classes
Include.File( "AISet_Balancer" )
--- AI Task Handling Classes
Include.File( "AI_PatrolZone" ) Include.File( "AI_PatrolZone" )
-- The order of the declarations is important here. Don't touch it. -- The order of the declarations is important here. Don't touch it.

View File

@ -349,6 +349,21 @@ function SET_BASE:Remove( ObjectName )
end end
--- Gets a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name.
-- @param #SET_BASE self
-- @param #string ObjectName
-- @return Base#BASE
function SET_BASE:Get( ObjectName )
self:F( ObjectName )
local t = self.Set[ObjectName]
self:T3( { ObjectName, t } )
return t
end
--- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. --- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes.
-- @param #SET_BASE self -- @param #SET_BASE self
-- @return #number Count -- @return #number Count

View File

@ -1534,46 +1534,34 @@ function SPAWN:_SpawnCleanUpScheduler()
local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName]
self:T( { SpawnUnitName, Stamp } ) self:T( { SpawnUnitName, Stamp } )
if Stamp.Moved then if Stamp.Vec2 then
if SpawnUnit:InAir() == false then if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
if SpawnUnit:GetVelocityKMH() < 1 then local NewVec2 = SpawnUnit:GetVec2()
if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
-- If the plane is not moving, and is on the ground, assign it with a timestamp... -- If the plane is not moving, and is on the ground, assign it with a timestamp...
if not Stamp.Time then
Stamp.Time = timer.getTime()
else
if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
self:ReSpawn( SpawnCursor ) self:ReSpawn( SpawnCursor )
Stamp.Moved = nil Stamp.Vec2 = nil
Stamp.Time = nil
end
end
else
Stamp.Time = nil Stamp.Time = nil
end end
else else
Stamp.Moved = nil Stamp.Time = timer.getTime()
Stamp.Vec2 = SpawnUnit:GetVec2()
end
else
Stamp.Vec2 = nil
Stamp.Time = nil Stamp.Time = nil
end end
else else
if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() > 1 then if SpawnUnit:InAir() == false then
Stamp.Moved = true Stamp.Vec2 = SpawnUnit:GetVec2()
else if SpawnUnit:GetVelocityKMH() < 1 then
-- If the plane did not move and on the runway for about 3 minutes, clean it.
if SpawnUnit:IsAboveRunway() and SpawnUnit:GetVelocityKMH() < 1 then
if not Stamp.Time then
Stamp.Time = timer.getTime() Stamp.Time = timer.getTime()
end end
if Stamp.Time + 180 < timer.getTime() then
self:T( { "CleanUp Scheduler:", "ReSpawning inactive group:", SpawnGroup:GetName() } )
self:ReSpawn( SpawnCursor )
Stamp.Moved = nil
Stamp.Time = nil
end
else else
Stamp.Moved = nil
Stamp.Time = nil Stamp.Time = nil
end Stamp.Vec2 = nil
end end
end end
end end

View File

@ -364,3 +364,54 @@ function STATEMACHINE_CONTROLLABLE:_call_handler( handler, params )
return handler( self, self.Controllable, unpack( params ) ) return handler( self, self.Controllable, unpack( params ) )
end end
end end
do -- STATEMACHINE_SET
--- STATEMACHINE_SET class
-- @type STATEMACHINE_SET
-- @field Set#SET_BASE Set
-- @extends StateMachine#STATEMACHINE
STATEMACHINE_SET = {
ClassName = "STATEMACHINE_SET",
}
--- Creates a new STATEMACHINE_SET object.
-- @param #STATEMACHINE_SET self
-- @param #table FSMT Finite State Machine Table
-- @param Set_SET_BASE FSMSet (optional) The Set object that the STATEMACHINE_SET governs.
-- @return #STATEMACHINE_SET
function STATEMACHINE_SET:New( FSMT, FSMSet )
-- Inherits from BASE
local self = BASE:Inherit( self, STATEMACHINE:New( FSMT ) ) -- StateMachine#STATEMACHINE_SET
if FSMSet then
self:Set( FSMSet )
end
return self
end
--- Sets the SET_BASE object that the STATEMACHINE_SET governs.
-- @param #STATEMACHINE_SET self
-- @param Set#SET_BASE FSMSet
-- @return #STATEMACHINE_SET
function STATEMACHINE_SET:Set( FSMSet )
self:F( FSMSet )
self.Set = FSMSet
end
--- Gets the SET_BASE object that the STATEMACHINE_SET governs.
-- @param #STATEMACHINE_SET self
-- @return Set#SET_BASE
function STATEMACHINE_SET:Get()
return self.Controllable
end
function STATEMACHINE_SET:_call_handler( handler, params )
if handler then
return handler( self, self.Set, unpack( params ) )
end
end
end

View File

@ -1,5 +1,5 @@
env.info( '*** MOOSE STATIC INCLUDE START *** ' ) env.info( '*** MOOSE STATIC INCLUDE START *** ' )
env.info( 'Moose Generation Timestamp: 20160823_0655' ) env.info( 'Moose Generation Timestamp: 20160824_1433' )
local base = _G local base = _G
Include = {} Include = {}
@ -3142,7 +3142,7 @@ function BASE:GetState( Object, StateName )
local ClassNameAndID = Object:GetClassNameAndID() local ClassNameAndID = Object:GetClassNameAndID()
if self.States[ClassNameAndID] then if self.States[ClassNameAndID] then
local State = self.States[ClassNameAndID][StateName] local State = self.States[ClassNameAndID][StateName] or false
self:T2( { ClassNameAndID, StateName, State } ) self:T2( { ClassNameAndID, StateName, State } )
return State return State
end end
@ -12462,6 +12462,21 @@ function SET_BASE:Remove( ObjectName )
end end
--- Gets a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name.
-- @param #SET_BASE self
-- @param #string ObjectName
-- @return Base#BASE
function SET_BASE:Get( ObjectName )
self:F( ObjectName )
local t = self.Set[ObjectName]
self:T3( { ObjectName, t } )
return t
end
--- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. --- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes.
-- @param #SET_BASE self -- @param #SET_BASE self
-- @return #number Count -- @return #number Count
@ -15082,7 +15097,6 @@ Include.File( "Movement" )
Include.File( "Sead" ) Include.File( "Sead" )
Include.File( "Escort" ) Include.File( "Escort" )
Include.File( "MissileTrainer" ) Include.File( "MissileTrainer" )
--Include.File( "AIBalancer" )
Include.File( "AirbasePolice" ) Include.File( "AirbasePolice" )
Include.File( "Detection" ) Include.File( "Detection" )
@ -15102,7 +15116,10 @@ Include.File( "Task" )
Include.File( "Task_SEAD" ) Include.File( "Task_SEAD" )
Include.File( "Task_A2G" ) Include.File( "Task_A2G" )
--- AI Handling Classes --- AI Set Handling Classes
Include.File( "AISet_Balancer" )
--- AI Task Handling Classes
Include.File( "AI_PatrolZone" ) Include.File( "AI_PatrolZone" )
-- The order of the declarations is important here. Don't touch it. -- The order of the declarations is important here. Don't touch it.
@ -20088,46 +20105,34 @@ function SPAWN:_SpawnCleanUpScheduler()
local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName]
self:T( { SpawnUnitName, Stamp } ) self:T( { SpawnUnitName, Stamp } )
if Stamp.Moved then if Stamp.Vec2 then
if SpawnUnit:InAir() == false then if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
if SpawnUnit:GetVelocityKMH() < 1 then local NewVec2 = SpawnUnit:GetVec2()
if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
-- If the plane is not moving, and is on the ground, assign it with a timestamp... -- If the plane is not moving, and is on the ground, assign it with a timestamp...
if not Stamp.Time then
Stamp.Time = timer.getTime()
else
if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
self:ReSpawn( SpawnCursor ) self:ReSpawn( SpawnCursor )
Stamp.Moved = nil Stamp.Vec2 = nil
Stamp.Time = nil
end
end
else
Stamp.Time = nil Stamp.Time = nil
end end
else else
Stamp.Moved = nil Stamp.Time = timer.getTime()
Stamp.Vec2 = SpawnUnit:GetVec2()
end
else
Stamp.Vec2 = nil
Stamp.Time = nil Stamp.Time = nil
end end
else else
if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() > 1 then if SpawnUnit:InAir() == false then
Stamp.Moved = true Stamp.Vec2 = SpawnUnit:GetVec2()
else if SpawnUnit:GetVelocityKMH() < 1 then
-- If the plane did not move and on the runway for about 3 minutes, clean it.
if SpawnUnit:IsAboveRunway() and SpawnUnit:GetVelocityKMH() < 1 then
if not Stamp.Time then
Stamp.Time = timer.getTime() Stamp.Time = timer.getTime()
end end
if Stamp.Time + 180 < timer.getTime() then
self:T( { "CleanUp Scheduler:", "ReSpawning inactive group:", SpawnGroup:GetName() } )
self:ReSpawn( SpawnCursor )
Stamp.Moved = nil
Stamp.Time = nil
end
else else
Stamp.Moved = nil
Stamp.Time = nil Stamp.Time = nil
end Stamp.Vec2 = nil
end end
end end
end end
@ -25430,7 +25435,59 @@ function STATEMACHINE_CONTROLLABLE:_call_handler( handler, params )
if handler then if handler then
return handler( self, self.Controllable, unpack( params ) ) return handler( self, self.Controllable, unpack( params ) )
end end
end--- @module Process end
do -- STATEMACHINE_SET
--- STATEMACHINE_SET class
-- @type STATEMACHINE_SET
-- @field Set#SET_BASE Set
-- @extends StateMachine#STATEMACHINE
STATEMACHINE_SET = {
ClassName = "STATEMACHINE_SET",
}
--- Creates a new STATEMACHINE_SET object.
-- @param #STATEMACHINE_SET self
-- @param #table FSMT Finite State Machine Table
-- @param Set_SET_BASE FSMSet (optional) The Set object that the STATEMACHINE_SET governs.
-- @return #STATEMACHINE_SET
function STATEMACHINE_SET:New( FSMT, FSMSet )
-- Inherits from BASE
local self = BASE:Inherit( self, STATEMACHINE:New( FSMT ) ) -- StateMachine#STATEMACHINE_SET
if FSMSet then
self:Set( FSMSet )
end
return self
end
--- Sets the SET_BASE object that the STATEMACHINE_SET governs.
-- @param #STATEMACHINE_SET self
-- @param Set#SET_BASE FSMSet
-- @return #STATEMACHINE_SET
function STATEMACHINE_SET:Set( FSMSet )
self:F( FSMSet )
self.Set = FSMSet
end
--- Gets the SET_BASE object that the STATEMACHINE_SET governs.
-- @param #STATEMACHINE_SET self
-- @return Set#SET_BASE
function STATEMACHINE_SET:Get()
return self.Controllable
end
function STATEMACHINE_SET:_call_handler( handler, params )
if handler then
return handler( self, self.Set, unpack( params ) )
end
end
end
--- @module Process
--- The PROCESS class --- The PROCESS class
-- @type PROCESS -- @type PROCESS
@ -27437,6 +27494,259 @@ end
--- This module contains the AISET_BALANCER class.
--
-- ===
--
-- 1) @{AISet_Balancer#AISET_BALANCER} class, extends @{StateMachine#STATEMACHINE_SET}
-- ===================================================================================
-- The @{AISet_Balancer#AISET_BALANCER} class monitors and manages as many AI GROUPS as there are
-- CLIENTS in a SET_CLIENT collection not occupied by players.
-- The AI_BALANCER class manages internally a collection of AI_MANAGEMENT objects, which govern the behaviour
-- of the underlying AI GROUPS.
--
-- The parent class @{StateMachine#STATEMACHINE_SET} manages the functionality to control the Finite State Machine (FSM)
-- and calls for each event the state transition functions providing the internal @{StateMachine#STATEMACHINE_SET.Set} object containing the
-- SET_GROUP and additional event parameters provided during the event.
--
-- 1.1) AISET_BALANCER construction method
-- ---------------------------------------
-- Create a new AISET_BALANCER object with the @{#AISET_BALANCER.New} method:
--
-- * @{#AISET_BALANCER.New}: Creates a new AISET_BALANCER object.
--
-- 1.2)
-- ----
-- * Add
-- * Remove
--
-- 1.2) AISET_BALANCER returns AI to Airbases
-- ------------------------------------------
-- You can configure to have the AI to return to:
--
-- * @{#AISET_BALANCER.ReturnToHomeAirbase}: Returns the AI to the home @{Airbase#AIRBASE}.
-- * @{#AISET_BALANCER.ReturnToNearestAirbases}: Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
-- --
-- ===
--
-- **API CHANGE HISTORY**
-- ======================
--
-- The underlying change log documents the API changes. Please read this carefully. The following notation is used:
--
-- * **Added** parts are expressed in bold type face.
-- * _Removed_ parts are expressed in italic type face.
--
-- Hereby the change log:
--
-- 2016-08-17: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval )
--
-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called!
-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods.
--
-- ===
--
-- AUTHORS and CONTRIBUTIONS
-- =========================
--
-- ### Contributions:
--
-- * **Dutch_Baron (James)**: Who you can search on the Eagle Dynamics Forums.
-- Working together with James has resulted in the creation of the AISET_BALANCER class.
-- James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
--
-- * **SNAFU**:
-- Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE.
-- None of the script code has been used however within the new AISET_BALANCER moose class.
--
-- ### Authors:
--
-- * FlightControl: Framework Design & Programming
--
-- @module AISet_Balancer
--- AISET_BALANCER class
-- @type AISET_BALANCER
-- @field Set#SET_CLIENT SetClient
-- @extends StateMachine#STATEMACHINE_SET
AISET_BALANCER = {
ClassName = "AISET_BALANCER",
PatrolZones = {},
AIGroups = {},
}
--- Creates a new AI\_SET\_BALANCER object
-- @param #AISET_BALANCER self
-- @param Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).
-- @param Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
-- @return #AISET_BALANCER
-- @usage
-- -- Define a new AISET_BALANCER Object.
function AISET_BALANCER:New( SetClient, SpawnAI )
local FSMT = {
initial = 'None',
events = {
{ name = 'Start', from = '*', to = 'Monitoring' },
{ name = 'Monitor', from = '*', to = 'Monitoring' },
{ name = 'Spawn', from = '*', to = 'Spawning' },
{ name = 'Destroy', from = '*', to = 'Destroying' },
{ name = 'Return', from = '*', to = 'Returning' },
{ name = 'End', from = '*', to = 'End' },
{ name = 'Dead', from = '*', to = 'End' },
},
}
-- Inherits from BASE
local self = BASE:Inherit( self, STATEMACHINE_SET:New( FSMT, SET_GROUP:New() ) )
self.SetClient = SetClient
self.SpawnAI = SpawnAI
self.ToNearestAirbase = false
self.ToHomeAirbase = false
self:__Start( 1 )
return self
end
--- Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
-- @param #AISET_BALANCER self
-- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
-- @param Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to.
function AISET_BALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet )
self.ToNearestAirbase = true
self.ReturnTresholdRange = ReturnTresholdRange
self.ReturnAirbaseSet = ReturnAirbaseSet
end
--- Returns the AI to the home @{Airbase#AIRBASE}.
-- @param #AISET_BALANCER self
-- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
function AISET_BALANCER:ReturnToHomeAirbase( ReturnTresholdRange )
self.ToHomeAirbase = true
self.ReturnTresholdRange = ReturnTresholdRange
end
--- @param #AISET_BALANCER self
-- @param Set#SET_GROUP SetGroup
-- @param #string ClientName
-- @param Group#GROUP AIGroup
function AISET_BALANCER:onenterSpawning( SetGroup, ClientName )
-- OK, Spawn a new group from the default SpawnAI object provided.
local AIGroup = self.SpawnAI:Spawn()
AIGroup:E( "Spawning new AIGroup" )
--TODO: need to rework UnitName thing ...
SetGroup:Add( ClientName, AIGroup )
end
--- @param #AISET_BALANCER self
-- @param Set#SET_GROUP SetGroup
-- @param Group#GROUP AIGroup
function AISET_BALANCER:onenterDestroying( SetGroup, AIGroup )
AIGroup:Destroy()
end
--- @param #AISET_BALANCER self
-- @param Set#SET_GROUP SetGroup
-- @param Group#GROUP AIGroup
function AISET_BALANCER:onenterReturning( SetGroup, AIGroup )
local AIGroupTemplate = AIGroup:GetTemplate()
if self.ToHomeAirbase == true then
local WayPointCount = #AIGroupTemplate.route.points
local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 )
AIGroup:SetCommand( SwitchWayPointCommand )
AIGroup:MessageToRed( "Returning to home base ...", 30 )
else
-- Okay, we need to send this Group back to the nearest base of the Coalition of the AI.
--TODO: i need to rework the POINT_VEC2 thing.
local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y )
local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
self:T( ClosestAirbase.AirbaseName )
AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 )
local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase )
AIGroupTemplate.route = RTBRoute
AIGroup:Respawn( AIGroupTemplate )
end
end
--- @param #AISET_BALANCER self
function AISET_BALANCER:onenterMonitoring( SetGroup )
self.SetClient:ForEachClient(
--- @param Client#CLIENT Client
function( Client )
self:E(Client.ClientName)
local AIGroup = self.Set:Get( Client.UnitName ) -- Group#GROUP
if Client:IsAlive() then
if AIGroup and AIGroup:IsAlive() == true then
if self.ToNearestAirbase == false and self.ToHomeAirbase == false then
self:Destroy( AIGroup )
else
-- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group.
-- If there is a CLIENT, the AI stays engaged and will not return.
-- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected.
local PlayerInRange = { Value = false }
local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange )
self:E( RangeZone )
_DATABASE:ForEachPlayer(
--- @param Unit#UNIT RangeTestUnit
function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange )
self:E( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } )
if RangeTestUnit:IsInZone( RangeZone ) == true then
self:E( "in zone" )
if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then
self:E( "in range" )
PlayerInRange.Value = true
end
end
end,
--- @param Zone#ZONE_RADIUS RangeZone
-- @param Group#GROUP AIGroup
function( RangeZone, AIGroup, PlayerInRange )
if PlayerInRange.Value == false then
self:Return( AIGroup )
end
end
, RangeZone, AIGroup, PlayerInRange
)
end
self.Set:Remove( Client.UnitName )
end
else
if not AIGroup or not AIGroup:IsAlive() == true then
self:E("client not alive")
self:Spawn( Client.UnitName )
self:E("text after spawn")
end
end
return true
end
)
self:__Monitor( 10 )
end
--- This module contains the AI\_PATROLZONE class. --- This module contains the AI\_PATROLZONE class.
-- --
-- === -- ===

View File

@ -1,5 +1,5 @@
env.info( '*** MOOSE STATIC INCLUDE START *** ' ) env.info( '*** MOOSE STATIC INCLUDE START *** ' )
env.info( 'Moose Generation Timestamp: 20160823_0655' ) env.info( 'Moose Generation Timestamp: 20160824_1433' )
local base = _G local base = _G
Include = {} Include = {}
@ -3142,7 +3142,7 @@ function BASE:GetState( Object, StateName )
local ClassNameAndID = Object:GetClassNameAndID() local ClassNameAndID = Object:GetClassNameAndID()
if self.States[ClassNameAndID] then if self.States[ClassNameAndID] then
local State = self.States[ClassNameAndID][StateName] local State = self.States[ClassNameAndID][StateName] or false
self:T2( { ClassNameAndID, StateName, State } ) self:T2( { ClassNameAndID, StateName, State } )
return State return State
end end
@ -12462,6 +12462,21 @@ function SET_BASE:Remove( ObjectName )
end end
--- Gets a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name.
-- @param #SET_BASE self
-- @param #string ObjectName
-- @return Base#BASE
function SET_BASE:Get( ObjectName )
self:F( ObjectName )
local t = self.Set[ObjectName]
self:T3( { ObjectName, t } )
return t
end
--- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. --- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes.
-- @param #SET_BASE self -- @param #SET_BASE self
-- @return #number Count -- @return #number Count
@ -15082,7 +15097,6 @@ Include.File( "Movement" )
Include.File( "Sead" ) Include.File( "Sead" )
Include.File( "Escort" ) Include.File( "Escort" )
Include.File( "MissileTrainer" ) Include.File( "MissileTrainer" )
--Include.File( "AIBalancer" )
Include.File( "AirbasePolice" ) Include.File( "AirbasePolice" )
Include.File( "Detection" ) Include.File( "Detection" )
@ -15102,7 +15116,10 @@ Include.File( "Task" )
Include.File( "Task_SEAD" ) Include.File( "Task_SEAD" )
Include.File( "Task_A2G" ) Include.File( "Task_A2G" )
--- AI Handling Classes --- AI Set Handling Classes
Include.File( "AISet_Balancer" )
--- AI Task Handling Classes
Include.File( "AI_PatrolZone" ) Include.File( "AI_PatrolZone" )
-- The order of the declarations is important here. Don't touch it. -- The order of the declarations is important here. Don't touch it.
@ -20088,46 +20105,34 @@ function SPAWN:_SpawnCleanUpScheduler()
local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName]
self:T( { SpawnUnitName, Stamp } ) self:T( { SpawnUnitName, Stamp } )
if Stamp.Moved then if Stamp.Vec2 then
if SpawnUnit:InAir() == false then if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
if SpawnUnit:GetVelocityKMH() < 1 then local NewVec2 = SpawnUnit:GetVec2()
if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
-- If the plane is not moving, and is on the ground, assign it with a timestamp... -- If the plane is not moving, and is on the ground, assign it with a timestamp...
if not Stamp.Time then
Stamp.Time = timer.getTime()
else
if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
self:ReSpawn( SpawnCursor ) self:ReSpawn( SpawnCursor )
Stamp.Moved = nil Stamp.Vec2 = nil
Stamp.Time = nil
end
end
else
Stamp.Time = nil Stamp.Time = nil
end end
else else
Stamp.Moved = nil Stamp.Time = timer.getTime()
Stamp.Vec2 = SpawnUnit:GetVec2()
end
else
Stamp.Vec2 = nil
Stamp.Time = nil Stamp.Time = nil
end end
else else
if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() > 1 then if SpawnUnit:InAir() == false then
Stamp.Moved = true Stamp.Vec2 = SpawnUnit:GetVec2()
else if SpawnUnit:GetVelocityKMH() < 1 then
-- If the plane did not move and on the runway for about 3 minutes, clean it.
if SpawnUnit:IsAboveRunway() and SpawnUnit:GetVelocityKMH() < 1 then
if not Stamp.Time then
Stamp.Time = timer.getTime() Stamp.Time = timer.getTime()
end end
if Stamp.Time + 180 < timer.getTime() then
self:T( { "CleanUp Scheduler:", "ReSpawning inactive group:", SpawnGroup:GetName() } )
self:ReSpawn( SpawnCursor )
Stamp.Moved = nil
Stamp.Time = nil
end
else else
Stamp.Moved = nil
Stamp.Time = nil Stamp.Time = nil
end Stamp.Vec2 = nil
end end
end end
end end
@ -25430,7 +25435,59 @@ function STATEMACHINE_CONTROLLABLE:_call_handler( handler, params )
if handler then if handler then
return handler( self, self.Controllable, unpack( params ) ) return handler( self, self.Controllable, unpack( params ) )
end end
end--- @module Process end
do -- STATEMACHINE_SET
--- STATEMACHINE_SET class
-- @type STATEMACHINE_SET
-- @field Set#SET_BASE Set
-- @extends StateMachine#STATEMACHINE
STATEMACHINE_SET = {
ClassName = "STATEMACHINE_SET",
}
--- Creates a new STATEMACHINE_SET object.
-- @param #STATEMACHINE_SET self
-- @param #table FSMT Finite State Machine Table
-- @param Set_SET_BASE FSMSet (optional) The Set object that the STATEMACHINE_SET governs.
-- @return #STATEMACHINE_SET
function STATEMACHINE_SET:New( FSMT, FSMSet )
-- Inherits from BASE
local self = BASE:Inherit( self, STATEMACHINE:New( FSMT ) ) -- StateMachine#STATEMACHINE_SET
if FSMSet then
self:Set( FSMSet )
end
return self
end
--- Sets the SET_BASE object that the STATEMACHINE_SET governs.
-- @param #STATEMACHINE_SET self
-- @param Set#SET_BASE FSMSet
-- @return #STATEMACHINE_SET
function STATEMACHINE_SET:Set( FSMSet )
self:F( FSMSet )
self.Set = FSMSet
end
--- Gets the SET_BASE object that the STATEMACHINE_SET governs.
-- @param #STATEMACHINE_SET self
-- @return Set#SET_BASE
function STATEMACHINE_SET:Get()
return self.Controllable
end
function STATEMACHINE_SET:_call_handler( handler, params )
if handler then
return handler( self, self.Set, unpack( params ) )
end
end
end
--- @module Process
--- The PROCESS class --- The PROCESS class
-- @type PROCESS -- @type PROCESS
@ -27437,6 +27494,259 @@ end
--- This module contains the AISET_BALANCER class.
--
-- ===
--
-- 1) @{AISet_Balancer#AISET_BALANCER} class, extends @{StateMachine#STATEMACHINE_SET}
-- ===================================================================================
-- The @{AISet_Balancer#AISET_BALANCER} class monitors and manages as many AI GROUPS as there are
-- CLIENTS in a SET_CLIENT collection not occupied by players.
-- The AI_BALANCER class manages internally a collection of AI_MANAGEMENT objects, which govern the behaviour
-- of the underlying AI GROUPS.
--
-- The parent class @{StateMachine#STATEMACHINE_SET} manages the functionality to control the Finite State Machine (FSM)
-- and calls for each event the state transition functions providing the internal @{StateMachine#STATEMACHINE_SET.Set} object containing the
-- SET_GROUP and additional event parameters provided during the event.
--
-- 1.1) AISET_BALANCER construction method
-- ---------------------------------------
-- Create a new AISET_BALANCER object with the @{#AISET_BALANCER.New} method:
--
-- * @{#AISET_BALANCER.New}: Creates a new AISET_BALANCER object.
--
-- 1.2)
-- ----
-- * Add
-- * Remove
--
-- 1.2) AISET_BALANCER returns AI to Airbases
-- ------------------------------------------
-- You can configure to have the AI to return to:
--
-- * @{#AISET_BALANCER.ReturnToHomeAirbase}: Returns the AI to the home @{Airbase#AIRBASE}.
-- * @{#AISET_BALANCER.ReturnToNearestAirbases}: Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
-- --
-- ===
--
-- **API CHANGE HISTORY**
-- ======================
--
-- The underlying change log documents the API changes. Please read this carefully. The following notation is used:
--
-- * **Added** parts are expressed in bold type face.
-- * _Removed_ parts are expressed in italic type face.
--
-- Hereby the change log:
--
-- 2016-08-17: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval )
--
-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called!
-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods.
--
-- ===
--
-- AUTHORS and CONTRIBUTIONS
-- =========================
--
-- ### Contributions:
--
-- * **Dutch_Baron (James)**: Who you can search on the Eagle Dynamics Forums.
-- Working together with James has resulted in the creation of the AISET_BALANCER class.
-- James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
--
-- * **SNAFU**:
-- Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE.
-- None of the script code has been used however within the new AISET_BALANCER moose class.
--
-- ### Authors:
--
-- * FlightControl: Framework Design & Programming
--
-- @module AISet_Balancer
--- AISET_BALANCER class
-- @type AISET_BALANCER
-- @field Set#SET_CLIENT SetClient
-- @extends StateMachine#STATEMACHINE_SET
AISET_BALANCER = {
ClassName = "AISET_BALANCER",
PatrolZones = {},
AIGroups = {},
}
--- Creates a new AI\_SET\_BALANCER object
-- @param #AISET_BALANCER self
-- @param Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).
-- @param Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
-- @return #AISET_BALANCER
-- @usage
-- -- Define a new AISET_BALANCER Object.
function AISET_BALANCER:New( SetClient, SpawnAI )
local FSMT = {
initial = 'None',
events = {
{ name = 'Start', from = '*', to = 'Monitoring' },
{ name = 'Monitor', from = '*', to = 'Monitoring' },
{ name = 'Spawn', from = '*', to = 'Spawning' },
{ name = 'Destroy', from = '*', to = 'Destroying' },
{ name = 'Return', from = '*', to = 'Returning' },
{ name = 'End', from = '*', to = 'End' },
{ name = 'Dead', from = '*', to = 'End' },
},
}
-- Inherits from BASE
local self = BASE:Inherit( self, STATEMACHINE_SET:New( FSMT, SET_GROUP:New() ) )
self.SetClient = SetClient
self.SpawnAI = SpawnAI
self.ToNearestAirbase = false
self.ToHomeAirbase = false
self:__Start( 1 )
return self
end
--- Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
-- @param #AISET_BALANCER self
-- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
-- @param Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to.
function AISET_BALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet )
self.ToNearestAirbase = true
self.ReturnTresholdRange = ReturnTresholdRange
self.ReturnAirbaseSet = ReturnAirbaseSet
end
--- Returns the AI to the home @{Airbase#AIRBASE}.
-- @param #AISET_BALANCER self
-- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
function AISET_BALANCER:ReturnToHomeAirbase( ReturnTresholdRange )
self.ToHomeAirbase = true
self.ReturnTresholdRange = ReturnTresholdRange
end
--- @param #AISET_BALANCER self
-- @param Set#SET_GROUP SetGroup
-- @param #string ClientName
-- @param Group#GROUP AIGroup
function AISET_BALANCER:onenterSpawning( SetGroup, ClientName )
-- OK, Spawn a new group from the default SpawnAI object provided.
local AIGroup = self.SpawnAI:Spawn()
AIGroup:E( "Spawning new AIGroup" )
--TODO: need to rework UnitName thing ...
SetGroup:Add( ClientName, AIGroup )
end
--- @param #AISET_BALANCER self
-- @param Set#SET_GROUP SetGroup
-- @param Group#GROUP AIGroup
function AISET_BALANCER:onenterDestroying( SetGroup, AIGroup )
AIGroup:Destroy()
end
--- @param #AISET_BALANCER self
-- @param Set#SET_GROUP SetGroup
-- @param Group#GROUP AIGroup
function AISET_BALANCER:onenterReturning( SetGroup, AIGroup )
local AIGroupTemplate = AIGroup:GetTemplate()
if self.ToHomeAirbase == true then
local WayPointCount = #AIGroupTemplate.route.points
local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 )
AIGroup:SetCommand( SwitchWayPointCommand )
AIGroup:MessageToRed( "Returning to home base ...", 30 )
else
-- Okay, we need to send this Group back to the nearest base of the Coalition of the AI.
--TODO: i need to rework the POINT_VEC2 thing.
local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y )
local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
self:T( ClosestAirbase.AirbaseName )
AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 )
local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase )
AIGroupTemplate.route = RTBRoute
AIGroup:Respawn( AIGroupTemplate )
end
end
--- @param #AISET_BALANCER self
function AISET_BALANCER:onenterMonitoring( SetGroup )
self.SetClient:ForEachClient(
--- @param Client#CLIENT Client
function( Client )
self:E(Client.ClientName)
local AIGroup = self.Set:Get( Client.UnitName ) -- Group#GROUP
if Client:IsAlive() then
if AIGroup and AIGroup:IsAlive() == true then
if self.ToNearestAirbase == false and self.ToHomeAirbase == false then
self:Destroy( AIGroup )
else
-- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group.
-- If there is a CLIENT, the AI stays engaged and will not return.
-- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected.
local PlayerInRange = { Value = false }
local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange )
self:E( RangeZone )
_DATABASE:ForEachPlayer(
--- @param Unit#UNIT RangeTestUnit
function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange )
self:E( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } )
if RangeTestUnit:IsInZone( RangeZone ) == true then
self:E( "in zone" )
if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then
self:E( "in range" )
PlayerInRange.Value = true
end
end
end,
--- @param Zone#ZONE_RADIUS RangeZone
-- @param Group#GROUP AIGroup
function( RangeZone, AIGroup, PlayerInRange )
if PlayerInRange.Value == false then
self:Return( AIGroup )
end
end
, RangeZone, AIGroup, PlayerInRange
)
end
self.Set:Remove( Client.UnitName )
end
else
if not AIGroup or not AIGroup:IsAlive() == true then
self:E("client not alive")
self:Spawn( Client.UnitName )
self:E("text after spawn")
end
end
return true
end
)
self:__Monitor( 10 )
end
--- This module contains the AI\_PATROLZONE class. --- This module contains the AI\_PATROLZONE class.
-- --
-- === -- ===

View File

@ -73,7 +73,6 @@ COPY /b Moose.lua + %1\Movement.lua Moose.lua
COPY /b Moose.lua + %1\Sead.lua Moose.lua COPY /b Moose.lua + %1\Sead.lua Moose.lua
COPY /b Moose.lua + %1\Escort.lua Moose.lua COPY /b Moose.lua + %1\Escort.lua Moose.lua
COPY /b Moose.lua + %1\MissileTrainer.lua Moose.lua COPY /b Moose.lua + %1\MissileTrainer.lua Moose.lua
rem COPY /b Moose.lua + %1\AIBalancer.lua Moose.lua
COPY /b Moose.lua + %1\AirbasePolice.lua Moose.lua COPY /b Moose.lua + %1\AirbasePolice.lua Moose.lua
COPY /b Moose.lua + %1\Detection.lua Moose.lua COPY /b Moose.lua + %1\Detection.lua Moose.lua
@ -93,7 +92,10 @@ COPY /b Moose.lua + %1\Task.lua Moose.lua
COPY /b Moose.lua + %1\Task_SEAD.lua Moose.lua COPY /b Moose.lua + %1\Task_SEAD.lua Moose.lua
COPY /b Moose.lua + %1\Task_A2G.lua Moose.lua COPY /b Moose.lua + %1\Task_A2G.lua Moose.lua
rem AI Handling Classes rem AI Set Handling Classes
COPY /b Moose.lua + %1\AISet_Balancer.lua Moose.lua
rem AI Task Handling Classes
COPY /b Moose.lua + %1\AI_PatrolZone.lua Moose.lua COPY /b Moose.lua + %1\AI_PatrolZone.lua Moose.lua

View File

@ -1,45 +0,0 @@
local US_PlanesClientSet = SET_CLIENT:New():FilterCountries( "USA" ):FilterCategories( "plane" ):FilterStart()
local US_PlanesSpawn1 = SPAWN:New( "AI US 1" ):InitCleanUp( 90 )
local US_PlanesSpawn2 = SPAWN:New( "AI US 2" ):InitCleanUp( 90 )
local US_AIBalancer = AIBALANCER:New( US_PlanesClientSet )
US_AIBalancer:OnNewAI(
function( AIGroup )
AIGroup = US_PlanesSpawn1:Spawn()
local AIPatrolZone = AI_PATROLZONE:New( 3000, 6000, 900, 1100 )
AIPatrolZone:ManageFuel( 0.2, 180 )
AIGroup:SetTask( AIPatrolZone )
AIPatrolZone:OnRTB(
function( AIGroup )
AIGroup = US_PlanesSpawn1:Spawn()
end
)
end
)
local RU_PlanesClientSet = SET_CLIENT:New():FilterCountries( "RUSSIA" ):FilterCategories( "plane" ):FilterStart()
local RU_PlanesSpawn = SPAWN:New( "AI RU" ):InitCleanUp( 90 )
local RU_AIBalancer = AIBALANCER:New( RU_PlanesClientSet, RU_PlanesSpawn )
local RU_AirbasesSet = SET_AIRBASE:New():FilterCoalitions("red"):FilterStart()
RU_AirbasesSet:Flush()
RU_AIBalancer:ReturnToNearestAirbases( 10000, RU_AirbasesSet )
--RU_AIBalancer:ReturnToHomeAirbase( 10000 )
local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone Blue" )
local PatrolZoneBlue = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup )
local PatrolZoneB = PATROLZONE:New( PatrolZoneBlue, 3000, 6000, 900, 1100 ):ManageFuel( 0.2, 180 )
US_AIBalancer:SetPatrolZone( PatrolZoneB )
local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone Red" )
local PatrolZoneRed = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup )
local PatrolZoneR = PATROLZONE:New( PatrolZoneRed, 3000, 6000, 900, 1100 ):ManageFuel( 0.2, 180 )
RU_AIBalancer:SetPatrolZone( PatrolZoneR )

View File

@ -0,0 +1,24 @@
local US_PlanesClientSet = SET_CLIENT:New():FilterCountries( "USA" ):FilterCategories( "plane" ):FilterStart()
local US_PlanesSpawn = SPAWN:New( "AI US" ):InitCleanUp( 20 )
local US_AIBalancer = AISET_BALANCER:New( US_PlanesClientSet, US_PlanesSpawn )
local RU_PlanesClientSet = SET_CLIENT:New():FilterCountries( "RUSSIA" ):FilterCategories( "plane" ):FilterStart()
local RU_PlanesSpawn = SPAWN:New( "AI RU" ):InitCleanUp( 20 )
local RU_AIBalancer = AISET_BALANCER:New( RU_PlanesClientSet, RU_PlanesSpawn )
local RU_AirbasesSet = SET_AIRBASE:New():FilterCoalitions("red"):FilterStart()
RU_AirbasesSet:Flush()
RU_AIBalancer:ReturnToNearestAirbases( 10000, RU_AirbasesSet )
--RU_AIBalancer:ReturnToHomeAirbase( 10000 )
--local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone Blue" )
--local PatrolZoneBlue = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup )
--local PatrolZoneB = PATROLZONE:New( PatrolZoneBlue, 3000, 6000, 900, 1100 ):ManageFuel( 0.2, 180 )
--US_AIBalancer:SetPatrolZone( PatrolZoneB )
--
--local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone Red" )
--local PatrolZoneRed = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup )
--local PatrolZoneR = PATROLZONE:New( PatrolZoneRed, 3000, 6000, 900, 1100 ):ManageFuel( 0.2, 180 )
--RU_AIBalancer:SetPatrolZone( PatrolZoneR )