mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
Progress on the AI_PATROLZONE!!! Working test mission!!!
This commit is contained in:
@@ -3,9 +3,10 @@
|
||||
-- ===
|
||||
--
|
||||
-- 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:
|
||||
-- ------------------------------------
|
||||
@@ -26,22 +27,44 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **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.
|
||||
-- * **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**
|
||||
-- * **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
|
||||
-- * FlightControl: Framework Design & Programming
|
||||
--
|
||||
-- @module AIBalancer
|
||||
|
||||
|
||||
|
||||
--- AIBALANCER class
|
||||
-- @type AIBALANCER
|
||||
-- @field Set#SET_CLIENT SetClient
|
||||
@@ -122,11 +145,28 @@ end
|
||||
-- @param #AIBALANCER self
|
||||
-- @param PatrolZone#PATROLZONE PatrolZone The @{PatrolZone} where the AI needs to patrol.
|
||||
-- @return PatrolZone#PATROLZONE self
|
||||
function AIBALANCER:SetPatrolZone( PatrolZone )
|
||||
function AIBALANCER:SetPatrolZone( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed )
|
||||
|
||||
self.PatrolZone = PatrolZone
|
||||
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()
|
||||
|
||||
|
||||
388
Moose Development/Moose/AI_PatrolZone.lua
Normal file
388
Moose Development/Moose/AI_PatrolZone.lua
Normal file
@@ -0,0 +1,388 @@
|
||||
--- This module contains the AI\_PATROLZONE class.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 1) @{#AI_PATROLZONE} class, extends @{StateMachine#STATEMACHINE}
|
||||
-- ================================================================
|
||||
-- The @{#AI_PATROLZONE} class implements the core functions to patrol a @{Zone} by an AIR @{Group}.
|
||||
-- The patrol algorithm works that for each airplane patrolling, upon arrival at the patrol zone,
|
||||
-- a random point is selected as the route point within the 3D space, within the given boundary limits.
|
||||
-- The airplane will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the random 3D point, a new 3D random point will be selected within the patrol zone using the given limits.
|
||||
-- This cycle will continue until a fuel treshold has been reached by the airplane.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 1.1) AI\_PATROLZONE constructor:
|
||||
-- ----------------------------
|
||||
--
|
||||
-- * @{#AI_PATROLZONE.New}(): Creates a new AI\_PATROLZONE object.
|
||||
--
|
||||
-- 1.2) AI\_PATROLZONE state machine:
|
||||
-- ----------------------------------
|
||||
-- The AI\_PATROLZONE is a state machine: it manages the different events and states of the AIGroup it is controlling.
|
||||
--
|
||||
-- ### 1.2.1) AI\_PATROLZONE Events:
|
||||
--
|
||||
-- * @{#AI_PATROLZONE.Route}( AIGroup ): A new 3D route point is selected and the AIGroup will fly towards that point with the given speed.
|
||||
-- * @{#AI_PATROLZONE.Patrol}( AIGroup ): The AIGroup reports it is patrolling. This event is called every 30 seconds.
|
||||
-- * @{#AI_PATROLZONE.RTB}( AIGroup ): The AIGroup will report return to base.
|
||||
-- * @{#AI_PATROLZONE.End}( AIGroup ): The end of the AI\_PATROLZONE process.
|
||||
-- * @{#AI_PATROLZONE.Dead}( AIGroup ): The AIGroup is dead. The AI\_PATROLZONE process will be ended.
|
||||
--
|
||||
-- ### 1.2.2) AI\_PATROLZONE States:
|
||||
--
|
||||
-- * **Route**: A new 3D route point is selected and the AIGroup will fly towards that point with the given speed.
|
||||
-- * **Patrol**: The AIGroup is patrolling. This state is set every 30 seconds, so every 30 seconds, a state transition function can be used.
|
||||
-- * **RTB**: The AIGroup reports it wants to return to the base.
|
||||
-- * **Dead**: The AIGroup is dead ...
|
||||
-- * **End**: The process has come to an end.
|
||||
--
|
||||
-- ### 1.2.3) AI\_PATROLZONE state transition functions:
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition functions will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition function needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition function returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIGroup at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIGroup!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition function needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition functions need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
-- An example how to manage a state transition for an AI\_PATROLZONE object **Patrol** for the state **RTB**:
|
||||
--
|
||||
-- local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone" )
|
||||
-- local PatrolZone = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup )
|
||||
--
|
||||
-- local PatrolSpawn = SPAWN:New( "Patrol Group" )
|
||||
-- local PatrolGroup = PatrolSpawn:Spawn()
|
||||
--
|
||||
-- local Patrol = AI_PATROLZONE:New( PatrolZone, 3000, 6000, 300, 600 )
|
||||
-- Patrol:AddGroup( PatrolGroup )
|
||||
-- Patrol:ManageFuel( 0.2, 60 )
|
||||
--
|
||||
-- **OnBefore**RTB( AIGroup ) will be called by the AI\_PATROLZONE object when the AIGroup reports RTB, but **before** the RTB default action is processed by the AI_PATROLZONE object.
|
||||
--
|
||||
-- --- State transition function for the AI\_PATROLZONE **Patrol** object
|
||||
-- -- @param #AI_PATROLZONE self
|
||||
-- -- @param Group#GROUP AIGroup
|
||||
-- -- @return #boolean If false is returned, then the OnAfter state transition function will not be called.
|
||||
-- function Patrol:OnBeforeRTB( AIGroup )
|
||||
-- AIGroup:MessageToRed( "Returning to base", 20 )
|
||||
-- end
|
||||
--
|
||||
-- **OnAfter**RTB( AIGroup ) will be called by the AI\_PATROLZONE object when the AIGroup reports RTB, but **after** the RTB default action was processed by the AI_PATROLZONE object.
|
||||
--
|
||||
-- --- State transition function for the AI\_PATROLZONE **Patrol** object
|
||||
-- -- @param #AI_PATROLZONE self
|
||||
-- -- @param Group#GROUP AIGroup
|
||||
-- -- @return #Group#GROUP The new AIGroup object that is set to be patrolling the zone.
|
||||
-- function Patrol:OnAfterRTB( AIGroup )
|
||||
-- return PatrolSpawn:Spawn()
|
||||
-- end
|
||||
--
|
||||
-- 1.3) Modify the AI\_PATROLZONE parameters:
|
||||
-- --------------------------------------
|
||||
-- The following methods are available to modify the parameters of a AI\_PATROLZONE object:
|
||||
--
|
||||
-- * @{#AI_PATROLZONE.SetGroup}(): Set the AI Patrol Group.
|
||||
-- * @{#AI_PATROLZONE.SetSpeed}(): Set the patrol speed of the AI, for the next patrol.
|
||||
-- * @{#AI_PATROLZONE.SetAltitude}(): Set altitude of the AI, for the next patrol.
|
||||
--
|
||||
-- 1.3) Manage the out of fuel in the AI\_PATROLZONE:
|
||||
-- ----------------------------------------------
|
||||
-- When the PatrolGroup is out of fuel, it is required that a new PatrolGroup is started, before the old PatrolGroup can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the PatrolGroup will continue for a given time its patrol task in orbit, while a new PatrolGroup is targetted to the AI\_PATROLZONE.
|
||||
-- Once the time is finished, the old PatrolGroup will return to the base.
|
||||
-- Use the method @{#AI_PATROLZONE.ManageFuel}() to have this proces in place.
|
||||
--
|
||||
-- ====
|
||||
--
|
||||
-- **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: AI\_PATROLZONE:New( **PatrolSpawn,** PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed ) replaces AI\_PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed )
|
||||
--
|
||||
-- 2016-07-01: Initial class and API.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- AUTHORS and CONTRIBUTIONS
|
||||
-- =========================
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **DutchBaron**: Testing.
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
-- * **FlightControl**: Design & Programming
|
||||
--
|
||||
--
|
||||
-- @module AI_PatrolZone
|
||||
|
||||
|
||||
|
||||
--- AI\_PATROLZONE class
|
||||
-- @type AI_PATROLZONE
|
||||
-- @field Group#GROUP PatrolGroup The @{Group} patrolling.
|
||||
-- @field Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @field DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @field DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @field DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h.
|
||||
-- @field DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h.
|
||||
-- @extends StateMachine#StateMachine
|
||||
AI_PATROLZONE = {
|
||||
ClassName = "AI_PATROLZONE",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI\_PATROLZONE object
|
||||
-- @param #AI_PATROLZONE self
|
||||
-- @param Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h.
|
||||
-- @param DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h.
|
||||
-- @return #AI_PATROLZONE self
|
||||
-- @usage
|
||||
-- -- Define a new AI_PATROLZONE Object. This PatrolArea will patrol a group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
|
||||
-- PatrolZone = ZONE:New( 'PatrolZone' )
|
||||
-- PatrolSpawn = SPAWN:New( 'Patrol Group' )
|
||||
-- PatrolArea = AI_PATROLZONE:New( PatrolZone, 3000, 6000, 600, 900 )
|
||||
function AI_PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed )
|
||||
|
||||
local FSMT = {
|
||||
initial = 'None',
|
||||
events = {
|
||||
{ name = 'Start', from = '*', to = 'Route' },
|
||||
{ name = 'Route', from = '*', to = 'Route' },
|
||||
{ name = 'Patrol', from = { 'Patrol', 'Route' }, to = 'Patrol' },
|
||||
{ name = 'RTB', from = 'Patrol', to = 'RTB' },
|
||||
{ name = 'End', from = '*', to = 'End' },
|
||||
{ name = 'Dead', from = '*', to = 'End' },
|
||||
},
|
||||
callbacks = {
|
||||
onenterRoute = self._EnterRoute,
|
||||
onenterPatrol = self._EnterPatrol,
|
||||
onenterRTB = self._EnterRTB,
|
||||
onenterEnd = self._EnterEnd,
|
||||
},
|
||||
}
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, STATEMACHINE:New( FSMT ) )
|
||||
|
||||
self.PatrolZone = PatrolZone
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Set the @{Group} to act as the Patroller.
|
||||
-- @param #AI_PATROLZONE self
|
||||
-- @param Group#GROUP PatrolGroup The @{Group} patrolling.
|
||||
-- @return #AI_PATROLZONE self
|
||||
function AI_PATROLZONE:SetGroup( PatrolGroup )
|
||||
|
||||
self.PatrolGroup = PatrolGroup
|
||||
self.PatrolGroupTemplateName = PatrolGroup:GetName()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Sets (modifies) the minimum and maximum speed of the patrol.
|
||||
-- @param #AI_PATROLZONE self
|
||||
-- @param DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h.
|
||||
-- @param DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h.
|
||||
-- @return #AI_PATROLZONE self
|
||||
function AI_PATROLZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
|
||||
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
|
||||
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Sets the floor and ceiling altitude of the patrol.
|
||||
-- @param #AI_PATROLZONE self
|
||||
-- @param DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @return #AI_PATROLZONE self
|
||||
function AI_PATROLZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
|
||||
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
|
||||
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param Group#GROUP PatrolGroup
|
||||
function _NewPatrolRoute( PatrolGroup )
|
||||
|
||||
PatrolGroup:T( "NewPatrolRoute" )
|
||||
local PatrolZone = PatrolGroup:GetState( PatrolGroup, "PatrolZone" ) -- PatrolZone#AI_PATROLZONE
|
||||
PatrolZone:__Route( 1, PatrolGroup )
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- When the PatrolGroup is out of fuel, it is required that a new PatrolGroup is started, before the old PatrolGroup can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the PatrolGroup will continue for a given time its patrol task in orbit, while a new PatrolGroup is targetted to the AI\_PATROLZONE.
|
||||
-- Once the time is finished, the old PatrolGroup will return to the base.
|
||||
-- @param #AI_PATROLZONE self
|
||||
-- @param #number PatrolFuelTresholdPercentage The treshold in percentage (between 0 and 1) when the PatrolGroup is considered to get out of fuel.
|
||||
-- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel PatrolGroup will orbit before returning to the base.
|
||||
-- @return #AI_PATROLZONE self
|
||||
function AI_PATROLZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime )
|
||||
|
||||
self.PatrolManageFuel = true
|
||||
self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage
|
||||
self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Defines a new patrol route using the @{PatrolZone} parameters and settings.
|
||||
-- @param #AI_PATROLZONE self
|
||||
-- @return #AI_PATROLZONE self
|
||||
function AI_PATROLZONE:_EnterRoute( AIGroup )
|
||||
|
||||
self:F2()
|
||||
|
||||
local PatrolRoute = {}
|
||||
|
||||
if self.PatrolGroup:IsAlive() then
|
||||
--- Determine if the PatrolGroup is within the PatrolZone.
|
||||
-- If not, make a waypoint within the to that the PatrolGroup will fly at maximum speed to that point.
|
||||
|
||||
-- --- Calculate the current route point.
|
||||
-- local CurrentVec2 = self.PatrolGroup:GetVec2()
|
||||
-- local CurrentAltitude = self.PatrolGroup:GetUnit(1):GetAltitude()
|
||||
-- local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
-- local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
|
||||
-- POINT_VEC3.RoutePointAltType.BARO,
|
||||
-- POINT_VEC3.RoutePointType.TurningPoint,
|
||||
-- POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
-- ToPatrolZoneSpeed,
|
||||
-- true
|
||||
-- )
|
||||
--
|
||||
-- PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint
|
||||
|
||||
self:T2( PatrolRoute )
|
||||
|
||||
if self.PatrolGroup:IsNotInZone( self.PatrolZone ) then
|
||||
--- Find a random 2D point in PatrolZone.
|
||||
local ToPatrolZoneVec2 = self.PatrolZone:GetRandomVec2()
|
||||
self:T2( ToPatrolZoneVec2 )
|
||||
|
||||
--- Define Speed and Altitude.
|
||||
local ToPatrolZoneAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
self:T2( ToPatrolZoneSpeed )
|
||||
|
||||
--- Obtain a 3D @{Point} from the 2D point + altitude.
|
||||
local ToPatrolZonePointVec3 = POINT_VEC3:New( ToPatrolZoneVec2.x, ToPatrolZoneAltitude, ToPatrolZoneVec2.y )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToPatrolZoneRoutePoint = ToPatrolZonePointVec3:RoutePointAir(
|
||||
POINT_VEC3.RoutePointAltType.BARO,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToPatrolZoneSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
PatrolRoute[#PatrolRoute+1] = ToPatrolZoneRoutePoint
|
||||
|
||||
end
|
||||
|
||||
--- Define a random point in the @{Zone}. The AI will fly to that point within the zone.
|
||||
|
||||
--- Find a random 2D point in PatrolZone.
|
||||
local ToTargetVec2 = self.PatrolZone:GetRandomVec2()
|
||||
self:T2( ToTargetVec2 )
|
||||
|
||||
--- Define Speed and Altitude.
|
||||
local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude )
|
||||
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
|
||||
self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } )
|
||||
|
||||
--- Obtain a 3D @{Point} from the 2D point + altitude.
|
||||
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir(
|
||||
POINT_VEC3.RoutePointAltType.BARO,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToTargetSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
--ToTargetPointVec3:SmokeRed()
|
||||
|
||||
PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint
|
||||
|
||||
--- Now we're going to do something special, we're going to call a function from a waypoint action at the PatrolGroup...
|
||||
self.PatrolGroup:WayPointInitialize( PatrolRoute )
|
||||
|
||||
--- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the PatrolGroup in a temporary variable ...
|
||||
self.PatrolGroup:SetState( self.PatrolGroup, "PatrolZone", self )
|
||||
self.PatrolGroup:WayPointFunction( #PatrolRoute, 1, "_NewPatrolRoute" )
|
||||
|
||||
--- NOW ROUTE THE GROUP!
|
||||
self.PatrolGroup:WayPointExecute( 1 )
|
||||
|
||||
self:__Patrol( 30, self.PatrolGroup )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_PATROLZONE self
|
||||
function AI_PATROLZONE:_EnterPatrol( AIGroup )
|
||||
self:F2()
|
||||
|
||||
if self.PatrolGroup and self.PatrolGroup:IsAlive() then
|
||||
|
||||
local Fuel = self.PatrolGroup:GetUnit(1):GetFuel()
|
||||
if Fuel < self.PatrolFuelTresholdPercentage then
|
||||
local OldPatrolGroup = self.PatrolGroup
|
||||
local PatrolGroupTemplate = self.PatrolGroup:GetTemplate()
|
||||
|
||||
local OrbitTask = OldPatrolGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
|
||||
local TimedOrbitTask = OldPatrolGroup:TaskControlled( OrbitTask, OldPatrolGroup:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) )
|
||||
OldPatrolGroup:SetTask( TimedOrbitTask, 10 )
|
||||
|
||||
self:RTB( self.PatrolGroup )
|
||||
else
|
||||
self:__Patrol( 30, self.PatrolGroup ) -- Execute the Patrol event after 30 seconds.
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -230,9 +230,10 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime )
|
||||
-- Controller.setTask( Controller, DCSTask )
|
||||
|
||||
if not WaitTime then
|
||||
WaitTime = 1
|
||||
Controller:setTask( DCSTask )
|
||||
else
|
||||
SCHEDULER:New( Controller, Controller.setTask, { DCSTask }, WaitTime )
|
||||
end
|
||||
SCHEDULER:New( Controller, Controller.setTask, { DCSTask }, WaitTime )
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -2152,7 +2153,7 @@ function CONTROLLABLE:TaskFunction( WayPoint, WayPointIndex, FunctionString, Fun
|
||||
local DCSTask
|
||||
|
||||
local DCSScript = {}
|
||||
DCSScript[#DCSScript+1] = "local MissionControllable = CONTROLLABLE:Find( ... ) "
|
||||
DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) "
|
||||
|
||||
if FunctionArguments and #FunctionArguments > 0 then
|
||||
DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, " .. table.concat( FunctionArguments, "," ) .. ")"
|
||||
|
||||
@@ -673,19 +673,19 @@ function GROUP:GetMaxVelocity()
|
||||
local DCSGroup = self:GetDCSObject()
|
||||
|
||||
if DCSGroup then
|
||||
local MaxVelocity = 0
|
||||
local GroupVelocityMax = 0
|
||||
|
||||
for Index, UnitData in pairs( DCSGroup:getUnits() ) do
|
||||
|
||||
local Velocity = UnitData:getVelocity()
|
||||
local VelocityTotal = math.abs( Velocity.x ) + math.abs( Velocity.y ) + math.abs( Velocity.z )
|
||||
local UnitVelocityVec3 = UnitData:getVelocity()
|
||||
local UnitVelocity = math.abs( UnitVelocityVec3.x ) + math.abs( UnitVelocityVec3.y ) + math.abs( UnitVelocityVec3.z )
|
||||
|
||||
if VelocityTotal < MaxVelocity then
|
||||
MaxVelocity = VelocityTotal
|
||||
if UnitVelocity > GroupVelocityMax then
|
||||
GroupVelocityMax = UnitVelocity
|
||||
end
|
||||
end
|
||||
|
||||
return MaxVelocity
|
||||
return GroupVelocityMax
|
||||
end
|
||||
|
||||
return nil
|
||||
|
||||
@@ -34,8 +34,8 @@ Include.File( "Movement" )
|
||||
Include.File( "Sead" )
|
||||
Include.File( "Escort" )
|
||||
Include.File( "MissileTrainer" )
|
||||
Include.File( "PatrolZone" )
|
||||
Include.File( "AIBalancer" )
|
||||
Include.File( "AI_PatrolZone" )
|
||||
--Include.File( "AIBalancer" )
|
||||
Include.File( "AirbasePolice" )
|
||||
Include.File( "Detection" )
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
--
|
||||
-- Hereby the change log:
|
||||
--
|
||||
-- 2016-08-17: PATROLZONE:New( **PatrolSpawn,** PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed ) replaces PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed )
|
||||
--
|
||||
-- 2016-07-01: Initial class and API.
|
||||
--
|
||||
-- ===
|
||||
@@ -54,16 +56,17 @@
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * DutchBaron: Testing.
|
||||
-- * **DutchBaron**: Testing.
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
-- * FlightControl: Design & Programming
|
||||
-- * **FlightControl**: Design & Programming
|
||||
--
|
||||
--
|
||||
-- @module PatrolZone
|
||||
|
||||
|
||||
|
||||
--- PATROLZONE class
|
||||
-- @type PATROLZONE
|
||||
-- @field Group#GROUP PatrolGroup The @{Group} patrolling.
|
||||
@@ -77,8 +80,11 @@ PATROLZONE = {
|
||||
ClassName = "PATROLZONE",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new PATROLZONE object, taking a @{Group} object as a parameter. The GROUP needs to be alive.
|
||||
-- @param #PATROLZONE self
|
||||
-- @param Spawn#SPAWN PatrolSpawn The @{SPAWN} object to spawn new group objects when required due to the fuel treshold.
|
||||
-- @param Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
@@ -88,13 +94,14 @@ PATROLZONE = {
|
||||
-- @usage
|
||||
-- -- Define a new PATROLZONE Object. This PatrolArea will patrol a group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
|
||||
-- PatrolZone = ZONE:New( 'PatrolZone' )
|
||||
-- PatrolGroup = GROUP:FindByName( "Patrol Group" )
|
||||
-- PatrolArea = PATROLZONE:New( PatrolGroup, PatrolZone, 3000, 6000, 600, 900 )
|
||||
function PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed )
|
||||
-- PatrolSpawn = SPAWN:New( "Patrol Group" )
|
||||
-- PatrolArea = PATROLZONE:New( PatrolSpawn, PatrolZone, 3000, 6000, 600, 900 )
|
||||
function PATROLZONE:New( PatrolSpawn, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
|
||||
self.PatrolSpawn = PatrolSpawn
|
||||
self.PatrolZone = PatrolZone
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
@@ -104,6 +111,8 @@ function PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude,
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Set the @{Group} to act as the Patroller.
|
||||
-- @param #PATROLZONE self
|
||||
-- @param Group#GROUP PatrolGroup The @{Group} patrolling.
|
||||
@@ -116,12 +125,13 @@ function PATROLZONE:SetGroup( PatrolGroup )
|
||||
|
||||
if not self.PatrolOutOfFuelMonitor then
|
||||
self.PatrolOutOfFuelMonitor = SCHEDULER:New( nil, _MonitorOutOfFuelScheduled, { self }, 1, 120, 0 )
|
||||
self.SpawnPatrolGroup = SPAWN:New( self.PatrolGroupTemplateName )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Sets (modifies) the minimum and maximum speed of the patrol.
|
||||
-- @param #PATROLZONE self
|
||||
-- @param DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h.
|
||||
@@ -134,6 +144,8 @@ function PATROLZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Sets the floor and ceiling altitude of the patrol.
|
||||
-- @param #PATROLZONE self
|
||||
-- @param DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
@@ -156,6 +168,8 @@ function _NewPatrolRoute( PatrolGroup )
|
||||
PatrolZone:NewPatrolRoute()
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Defines a new patrol route using the @{PatrolZone} parameters and settings.
|
||||
-- @param #PATROLZONE self
|
||||
-- @return #PATROLZONE self
|
||||
@@ -267,7 +281,6 @@ function PATROLZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrb
|
||||
|
||||
if self.PatrolGroup then
|
||||
self.PatrolOutOfFuelMonitor = SCHEDULER:New( self, self._MonitorOutOfFuelScheduled, {}, 1, 120, 0 )
|
||||
self.SpawnPatrolGroup = SPAWN:New( self.PatrolGroupTemplateName )
|
||||
end
|
||||
return self
|
||||
end
|
||||
@@ -287,7 +300,7 @@ function _MonitorOutOfFuelScheduled( self )
|
||||
local TimedOrbitTask = OldPatrolGroup:TaskControlled( OrbitTask, OldPatrolGroup:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) )
|
||||
OldPatrolGroup:SetTask( TimedOrbitTask, 10 )
|
||||
|
||||
local NewPatrolGroup = self.SpawnPatrolGroup:Spawn()
|
||||
local NewPatrolGroup = self.PatrolSpawn:Spawn()
|
||||
self.PatrolGroup = NewPatrolGroup
|
||||
self:NewPatrolRoute()
|
||||
end
|
||||
|
||||
@@ -236,6 +236,7 @@ function POSITIONABLE:InAir()
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Returns the POSITIONABLE velocity vector.
|
||||
-- @param Positionable#POSITIONABLE self
|
||||
|
||||
@@ -56,7 +56,7 @@ SCHEDULER = {
|
||||
-- @return #SCHEDULER self
|
||||
function SCHEDULER:New( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F2( { TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } )
|
||||
self:F2( { StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } )
|
||||
|
||||
|
||||
self:Schedule( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds )
|
||||
@@ -75,7 +75,8 @@ end
|
||||
-- @param #number StopSeconds Specifies the amount of seconds when the scheduler will be stopped.
|
||||
-- @return #SCHEDULER self
|
||||
function SCHEDULER:Schedule( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds )
|
||||
self:F2( { TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } )
|
||||
self:F2( { StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } )
|
||||
self:T3( { TimeEventFunctionArguments } )
|
||||
|
||||
self.TimeEventObject = TimeEventObject
|
||||
self.TimeEventFunction = TimeEventFunction
|
||||
@@ -97,7 +98,7 @@ end
|
||||
-- @param #SCHEDULER self
|
||||
-- @return #SCHEDULER self
|
||||
function SCHEDULER:Start()
|
||||
self:F2( self.TimeEventObject )
|
||||
self:F2()
|
||||
|
||||
if self.RepeatSecondsInterval ~= 0 then
|
||||
self.Repeat = true
|
||||
@@ -107,7 +108,8 @@ function SCHEDULER:Start()
|
||||
if self.ScheduleID then
|
||||
timer.removeFunction( self.ScheduleID )
|
||||
end
|
||||
self.ScheduleID = timer.scheduleFunction( self._Scheduler, self, timer.getTime() + self.StartSeconds + .01 )
|
||||
self:T( { self.StartSeconds } )
|
||||
self.ScheduleID = timer.scheduleFunction( self._Scheduler, self, timer.getTime() + self.StartSeconds + .001 )
|
||||
end
|
||||
|
||||
return self
|
||||
|
||||
@@ -182,11 +182,11 @@
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * Aaron: Posed the idea for Group position randomization at SpawnInZone and make the Unit randomization separate from the Group randomization.
|
||||
-- * **Aaron**: Posed the idea for Group position randomization at SpawnInZone and make the Unit randomization separate from the Group randomization.
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
-- * FlightControl : Design & Programming
|
||||
-- * **FlightControl**: Design & Programming
|
||||
--
|
||||
--
|
||||
-- @module Spawn
|
||||
@@ -490,6 +490,10 @@ function SPAWN:InitCleanUp( SpawnCleanUpInterval )
|
||||
|
||||
self.SpawnCleanUpInterval = SpawnCleanUpInterval
|
||||
self.SpawnCleanUpTimeStamps = {}
|
||||
|
||||
local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
|
||||
self:T( { "CleanUp Scheduler:", SpawnGroup } )
|
||||
|
||||
--self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval )
|
||||
self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 )
|
||||
return self
|
||||
@@ -614,6 +618,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex )
|
||||
else
|
||||
|
||||
local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
|
||||
self:T( SpawnTemplate.name )
|
||||
|
||||
if SpawnTemplate then
|
||||
|
||||
@@ -642,7 +647,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex )
|
||||
if self.RepeatOnEngineShutDown then
|
||||
_EVENTDISPATCHER:OnEngineShutDownForTemplate( SpawnTemplate, self._OnEngineShutDown, self )
|
||||
end
|
||||
self:T3( SpawnTemplate )
|
||||
self:T3( SpawnTemplate.name )
|
||||
|
||||
self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate )
|
||||
|
||||
@@ -1506,28 +1511,72 @@ function SPAWN:_Scheduler()
|
||||
return true
|
||||
end
|
||||
|
||||
--- Schedules the CleanUp of Groups
|
||||
-- @param #SPAWN self
|
||||
-- @return #boolean True = Continue Scheduler
|
||||
function SPAWN:_SpawnCleanUpScheduler()
|
||||
self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } )
|
||||
|
||||
local SpawnCursor
|
||||
local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup( SpawnCursor )
|
||||
|
||||
local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
|
||||
self:T( { "CleanUp Scheduler:", SpawnGroup } )
|
||||
|
||||
while SpawnGroup do
|
||||
|
||||
if SpawnGroup:AllOnGround() and SpawnGroup:GetMaxVelocity() < 1 then
|
||||
if not self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] then
|
||||
self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] = timer.getTime()
|
||||
else
|
||||
if self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] + self.SpawnCleanUpInterval < timer.getTime() then
|
||||
self:T( { "CleanUp Scheduler:", "Cleaning:", SpawnGroup } )
|
||||
SpawnGroup:Destroy()
|
||||
end
|
||||
end
|
||||
else
|
||||
self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] = nil
|
||||
end
|
||||
|
||||
local SpawnUnits = SpawnGroup:GetUnits()
|
||||
|
||||
for UnitID, UnitData in pairs( SpawnUnits ) do
|
||||
|
||||
local SpawnUnit = UnitData -- Unit#UNIT
|
||||
local SpawnUnitName = SpawnUnit:GetName()
|
||||
|
||||
|
||||
self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {}
|
||||
local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName]
|
||||
self:T( { SpawnUnitName, Stamp } )
|
||||
|
||||
if Stamp.Moved then
|
||||
if SpawnUnit:InAir() == false then
|
||||
if SpawnUnit:GetVelocityKMH() < 1 then
|
||||
-- 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
|
||||
self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
|
||||
self:ReSpawn( SpawnCursor )
|
||||
Stamp.Moved = nil
|
||||
Stamp.Time = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
Stamp.Time = nil
|
||||
end
|
||||
else
|
||||
Stamp.Moved = nil
|
||||
Stamp.Time = nil
|
||||
end
|
||||
else
|
||||
if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() > 1 then
|
||||
Stamp.Moved = true
|
||||
else
|
||||
-- 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()
|
||||
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
|
||||
Stamp.Moved = nil
|
||||
Stamp.Time = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor )
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
--- STATEMACHINE class
|
||||
-- @type STATEMACHINE
|
||||
-- @extends Base#BASE
|
||||
STATEMACHINE = {
|
||||
ClassName = "STATEMACHINE",
|
||||
}
|
||||
@@ -47,7 +48,10 @@ function STATEMACHINE:New( options )
|
||||
|
||||
for _, event in ipairs(options.events or {}) do
|
||||
local name = event.name
|
||||
local __name = "__" .. event.name
|
||||
self[name] = self[name] or self:_create_transition(name)
|
||||
self[__name] = self[__name] or self:_delayed_transition(name)
|
||||
self:T( "Added methods: " .. name .. ", " .. __name )
|
||||
self.events[name] = self.events[name] or { map = {} }
|
||||
self:_add_to_map(self.events[name].map, event)
|
||||
end
|
||||
@@ -95,56 +99,81 @@ function STATEMACHINE:_call_handler(handler, params)
|
||||
end
|
||||
end
|
||||
|
||||
function STATEMACHINE:_create_transition(name)
|
||||
self:E( { name = name } )
|
||||
return function(self, ...)
|
||||
local can, to = self:can(name)
|
||||
self:T( { name, can, to } )
|
||||
function STATEMACHINE._handler( self, EventName, ... )
|
||||
|
||||
if can then
|
||||
local from = self.current
|
||||
local params = { self, name, from, to, ... }
|
||||
self:F( EventName )
|
||||
|
||||
if self:_call_handler(self["onbefore" .. name], params) == false
|
||||
or self:_call_handler(self["onleave" .. from], params) == false then
|
||||
return false
|
||||
end
|
||||
local can, to = self:can(EventName)
|
||||
self:T( { EventName, can, to } )
|
||||
|
||||
local ReturnValues = nil
|
||||
|
||||
self.current = to
|
||||
|
||||
local execute = true
|
||||
|
||||
local subtable = self:_gosub( to, name )
|
||||
for _, sub in pairs( subtable ) do
|
||||
self:F( "calling sub: " .. sub.event )
|
||||
sub.fsm.fsmparent = self
|
||||
sub.fsm.returnevents = sub.returnevents
|
||||
sub.fsm[sub.event]( sub.fsm )
|
||||
execute = true
|
||||
end
|
||||
|
||||
local fsmparent, event = self:_isendstate( to )
|
||||
if fsmparent and event then
|
||||
self:F( { "end state: ", fsmparent, event } )
|
||||
self:_call_handler(self["onenter" .. to] or self["on" .. to], params)
|
||||
self:_call_handler(self["onafter" .. name] or self["on" .. name], params)
|
||||
self:_call_handler(self["onstatechange"], params)
|
||||
fsmparent[event]( fsmparent )
|
||||
execute = false
|
||||
end
|
||||
if can then
|
||||
local from = self.current
|
||||
local params = { self, ..., EventName, from, to }
|
||||
|
||||
if execute then
|
||||
self:F( { "execute: " .. to, name } )
|
||||
self:_call_handler(self["onenter" .. to] or self["on" .. to], params)
|
||||
self:_call_handler(self["onafter" .. name] or self["on" .. name], params)
|
||||
self:_call_handler(self["onstatechange"], params)
|
||||
end
|
||||
|
||||
return true
|
||||
if self:_call_handler(self["onbefore" .. EventName], params) == false
|
||||
or self:_call_handler(self["onleave" .. from], params) == false then
|
||||
return false
|
||||
end
|
||||
|
||||
return false
|
||||
self.current = to
|
||||
|
||||
local execute = true
|
||||
|
||||
local subtable = self:_gosub( to, EventName )
|
||||
for _, sub in pairs( subtable ) do
|
||||
self:F2( "calling sub: " .. sub.event )
|
||||
sub.fsm.fsmparent = self
|
||||
sub.fsm.returnevents = sub.returnevents
|
||||
sub.fsm[sub.event]( sub.fsm )
|
||||
execute = true
|
||||
end
|
||||
|
||||
local fsmparent, event = self:_isendstate( to )
|
||||
if fsmparent and event then
|
||||
self:F2( { "end state: ", fsmparent, event } )
|
||||
self:_call_handler(self["onenter" .. to] or self["on" .. to], params)
|
||||
self:_call_handler(self["onafter" .. EventName] or self["on" .. EventName], params)
|
||||
self:_call_handler(self["onstatechange"], params)
|
||||
fsmparent[event]( fsmparent )
|
||||
execute = false
|
||||
end
|
||||
|
||||
if execute then
|
||||
self:T3( { onenter = "onenter" .. to, callback = self["onenter" .. to] } )
|
||||
self:_call_handler(self["onenter" .. to] or self["on" .. to], params)
|
||||
|
||||
self:T3( { On = "OnBefore" .. to, callback = self["OnBefore" .. to] } )
|
||||
if ( self:_call_handler(self["OnBefore" .. to], params ) ~= false ) then
|
||||
|
||||
self:T3( { onafter = "onafter" .. EventName, callback = self["onafter" .. EventName] } )
|
||||
self:_call_handler(self["onafter" .. EventName] or self["on" .. EventName], params)
|
||||
|
||||
self:T3( { On = "OnAfter" .. to, callback = self["OnAfter" .. to] } )
|
||||
ReturnValues = self:_call_handler(self["OnAfter" .. to], params )
|
||||
end
|
||||
|
||||
self:_call_handler(self["onstatechange"], params)
|
||||
end
|
||||
|
||||
return ReturnValues
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function STATEMACHINE:_delayed_transition( EventName )
|
||||
self:E( { EventName = EventName } )
|
||||
return function( self, DelaySeconds, ... )
|
||||
self:T( "Delayed Event: " .. EventName )
|
||||
SCHEDULER:New( self, self._handler, { EventName, ... }, DelaySeconds )
|
||||
end
|
||||
end
|
||||
|
||||
function STATEMACHINE:_create_transition( EventName )
|
||||
self:E( { Event = EventName } )
|
||||
return function( self, ... ) return self._handler( self, EventName , ... ) end
|
||||
end
|
||||
|
||||
function STATEMACHINE:_gosub( parentstate, parentevent )
|
||||
|
||||
283
Moose Development/Moose/Task_AIBalancer.lua
Normal file
283
Moose Development/Moose/Task_AIBalancer.lua
Normal file
@@ -0,0 +1,283 @@
|
||||
--- 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
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
2016-08-17
|
||||
|
||||
- Making the PATROLZONE class work with a SPAWN object, instead of a GROUP object.
|
||||
-- The SPAWN object is needed to make the FuelManagement logic work correctly. New planes need to be spawned when old planes are out of fuel.
|
||||
-- Changed the PATROLZONE:New() method to work with a SPAWN object instead of a GROUP object.
|
||||
|
||||
- Making the AIBALANCER class work with the modified PATROLZONE class SPAWN object.
|
||||
-- Added AIBALANCER:GetPatrolZone() method.
|
||||
|
||||
2016-08-15
|
||||
|
||||
- Removed the old classes and moved into an "Old" folder in the Moose/Development folder.
|
||||
|
||||
Reference in New Issue
Block a user