Fixed respawn bug in AI_BALANCER + Scheduler bug

AI_BALANCER respawns back the AI. Bug was in the FSM. In a transition,
when states are the same, the events SHOULD execute.
Added spawn delay interval option API SetSpawnInterval().
Bug that prevented AI_BALANCER to start if multiple AI_BALANCERs is also
fixed.
Fixed bug in scheduler dispatcher. Multiple schedules for the same
scheduler work now too!
This commit is contained in:
FlightControl 2017-01-08 03:53:05 +01:00
parent 243f33764a
commit 1f90c0c766
9 changed files with 113 additions and 14 deletions

View File

@ -74,30 +74,35 @@
--- AI_BALANCER class --- AI_BALANCER class
-- @type AI_BALANCER -- @type AI_BALANCER
-- @field Core.Set#SET_CLIENT SetClient -- @field Core.Set#SET_CLIENT SetClient
-- @field Functional.Spawn#SPAWN SpawnAI
-- @field Wrapper.Group#GROUP Test
-- @extends Core.Fsm#FSM_SET -- @extends Core.Fsm#FSM_SET
AI_BALANCER = { AI_BALANCER = {
ClassName = "AI_BALANCER", ClassName = "AI_BALANCER",
PatrolZones = {}, PatrolZones = {},
AIGroups = {}, AIGroups = {},
Earliest = 5, -- Earliest a new AI can be spawned is in 5 seconds.
Latest = 60, -- Latest a new AI can be spawned is in 60 seconds.
} }
--- Creates a new AI_BALANCER object --- Creates a new AI_BALANCER object
-- @param #AI_BALANCER self -- @param #AI_BALANCER self
-- @param Core.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 Core.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 Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed. -- @param Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
-- @return #AI_BALANCER -- @return #AI_BALANCER
-- @usage
-- -- Define a new AI_BALANCER Object.
function AI_BALANCER:New( SetClient, SpawnAI ) function AI_BALANCER:New( SetClient, SpawnAI )
-- Inherits from BASE -- Inherits from BASE
self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- Core.Fsm#FSM_SET local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- AI.AI_Balancer#AI_BALANCER
self:SetStartState( "None" ) self:SetStartState( "None" )
self:AddTransition( "*", "Start", "Monitoring" ) self:AddTransition( "*", "Start", "Monitoring" )
self:AddTransition( "*", "Monitor", "Monitoring" ) self:AddTransition( "*", "Monitor", "Monitoring" )
self:AddTransition( "*", "Spawn", "Spawning" ) self:AddTransition( "*", "Spawn", "Spawning" )
self:AddTransition( "Spawning", "Spawned", "Spawned" ) self:AddTransition( "Spawning", "Spawned", "Spawned" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
self:AddTransition( "*", "Destroy", "Destroying" ) self:AddTransition( "*", "Destroy", "Destroying" )
self:AddTransition( "*", "Return", "Returning" ) self:AddTransition( "*", "Return", "Returning" )
self:AddTransition( "*", "End", "End" ) self:AddTransition( "*", "End", "End" )
@ -105,6 +110,9 @@ function AI_BALANCER:New( SetClient, SpawnAI )
self.SetClient = SetClient self.SetClient = SetClient
self.SpawnAI = SpawnAI self.SpawnAI = SpawnAI
self.SpawnQueue = {}
self.ToNearestAirbase = false self.ToNearestAirbase = false
self.ToHomeAirbase = false self.ToHomeAirbase = false
@ -113,6 +121,20 @@ function AI_BALANCER:New( SetClient, SpawnAI )
return self return self
end end
--- Sets the earliest to the latest interval in seconds how long AI_BALANCER will wait to spawn a new AI.
-- Provide 2 identical seconds if the interval should be a fixed amount of seconds.
-- @param #AI_BALANCER self
-- @param #number Earliest The earliest a new AI can be spawned in seconds.
-- @param #number Latest The latest a new AI can be spawned in seconds.
-- @return self
function AI_BALANCER:InitSpawnInterval( Earliest, Latest )
self.Earliest = Earliest
self.Latest = Latest
return self
end
--- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}. --- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}.
-- @param #AI_BALANCER self -- @param #AI_BALANCER self
-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. -- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}.
@ -140,15 +162,34 @@ end
function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName ) function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName )
-- OK, Spawn a new group from the default SpawnAI object provided. -- OK, Spawn a new group from the default SpawnAI object provided.
local AIGroup = self.SpawnAI:Spawn() local AIGroup = self.SpawnAI:Spawn() -- Wrapper.Group#GROUP
AIGroup:E( "Spawning new AIGroup" ) AIGroup:E( "Spawning new AIGroup" )
--TODO: need to rework UnitName thing ... --TODO: need to rework UnitName thing ...
SetGroup:Add( ClientName, AIGroup ) SetGroup:Add( ClientName, AIGroup )
self.SpawnQueue[ClientName] = nil
-- --- @param Wrapper.Group#GROUP AIGroup
-- -- @param Core.Event#EVENTDATA EventData
-- local function Respawn( AIGroup, EventData )
-- if EventData.IniUnit then
-- local CheckGroup = EventData.IniUnit:GetGroup()
-- if CheckGroup:GetName() == AIGroup:GetName() then
-- if CheckGroup:GetUnits() == nil then
-- AIGroup:Respawn( AIGroup:GetTemplate() )
-- end
-- end
-- end
-- end
--
--
-- AIGroup:EventOnDead( Respawn )
-- AIGroup:EventOnEjection( Respawn )
-- Fire the Spawned event. The first parameter is the AIGroup just Spawned. -- Fire the Spawned event. The first parameter is the AIGroup just Spawned.
-- Mission designers can catch this event to bind further actions to the AIGroup. -- Mission designers can catch this event to bind further actions to the AIGroup.
self:Spawned( AIGroup ) self:Spawned( AIGroup )
end end
--- @param #AI_BALANCER self --- @param #AI_BALANCER self
@ -159,6 +200,14 @@ function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, AIGroup )
AIGroup:Destroy() AIGroup:Destroy()
end end
--- @param #AI_BALANCER self
-- @param Core.Set#SET_GROUP SetGroup
-- @param Wrapper.Group#GROUP AIGroup
function AI_BALANCER:onenterDestroyed( SetGroup, From, Event, To, AIGroup )
AIGroup:Destroy()
end
--- @param #AI_BALANCER self --- @param #AI_BALANCER self
-- @param Core.Set#SET_GROUP SetGroup -- @param Core.Set#SET_GROUP SetGroup
-- @param Wrapper.Group#GROUP AIGroup -- @param Wrapper.Group#GROUP AIGroup
@ -240,9 +289,13 @@ function AI_BALANCER:onenterMonitoring( SetGroup )
end end
else else
if not AIGroup or not AIGroup:IsAlive() == true then if not AIGroup or not AIGroup:IsAlive() == true then
self:E("client not alive") self:E( "Client " .. Client.UnitName .. " not alive." )
self:Spawn( Client.UnitName ) if not self.SpawnQueue[Client.UnitName] then
self:E("text after spawn") -- Spawn a new AI taking into account the spawn interval Earliest, Latest
self:__Spawn( math.random( self.Earliest, self.Latest ), Client.UnitName )
self.SpawnQueue[Client.UnitName] = true
self:E( "New AI Spawned for Client " .. Client.UnitName )
end
end end
end end
return true return true

View File

@ -582,10 +582,10 @@ do -- FSM
if execute then if execute then
-- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute! -- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute!
if from ~= to then --if from ~= to then
self:_call_handler("onenter" .. to, params) self:_call_handler("onenter" .. to, params)
self:_call_handler("OnEnter" .. to, params) self:_call_handler("OnEnter" .. to, params)
end --end
self:_call_handler("onafter" .. EventName, params) self:_call_handler("onafter" .. EventName, params)
self:_call_handler("OnAfter" .. EventName, params) self:_call_handler("OnAfter" .. EventName, params)

View File

@ -75,7 +75,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
end end
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } ) self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } )
self.Schedule[Scheduler] = {} self.Schedule[Scheduler] = self.Schedule[Scheduler] or {}
self.Schedule[Scheduler][self.CallID] = {} self.Schedule[Scheduler][self.CallID] = {}
self.Schedule[Scheduler][self.CallID].Function = ScheduleFunction self.Schedule[Scheduler][self.CallID].Function = ScheduleFunction
self.Schedule[Scheduler][self.CallID].Arguments = ScheduleArguments self.Schedule[Scheduler][self.CallID].Arguments = ScheduleArguments

View File

@ -207,6 +207,7 @@ SPAWN = {
SpawnAliasPrefix = nil, SpawnAliasPrefix = nil,
} }
--- @type SPAWN.SpawnZoneTable --- @type SPAWN.SpawnZoneTable
-- @list <Core.Zone#ZONE_BASE> SpawnZone -- @list <Core.Zone#ZONE_BASE> SpawnZone

View File

@ -883,4 +883,3 @@ function GROUP:CalculateThreatLevelA2G()
end end

View File

@ -34,7 +34,6 @@ OBJECT = {
ObjectName = "", ObjectName = "",
} }
--- A DCSObject --- A DCSObject
-- @type DCSObject -- @type DCSObject
-- @field id_ The ID of the controllable in DCS -- @field id_ The ID of the controllable in DCS
@ -43,10 +42,11 @@ OBJECT = {
-- @param #OBJECT self -- @param #OBJECT self
-- @param Dcs.DCSWrapper.Object#Object ObjectName The Object name -- @param Dcs.DCSWrapper.Object#Object ObjectName The Object name
-- @return #OBJECT self -- @return #OBJECT self
function OBJECT:New( ObjectName ) function OBJECT:New( ObjectName, Test )
local self = BASE:Inherit( self, BASE:New() ) local self = BASE:Inherit( self, BASE:New() )
self:F2( ObjectName ) self:F2( ObjectName )
self.ObjectName = ObjectName self.ObjectName = ObjectName
return self return self
end end

View File

@ -0,0 +1,46 @@
-- Name: Respawn Test when Destroyed
-- Author: FlightControl
-- Date Created: 7 January 2017
--
-- # Situation:
--
-- For the red coalition, 2 client slots are foreseen.
-- For those players that have not joined the mission, red AI is spawned.
-- The red AI should start patrolling an area.
--
-- The blue side has SAMs nearby.
-- Once the red AI takes off, the red AI is attacked by the blue SAMs.
-- Red AI should be killed and once that happens, a Respawn of the group should happen!
-- The Respawn happens through the InitCleanUp() API of SPAWN.
--
-- # Test cases:
--
-- 1. If no player is logging into the red slots, 2 red AI planes should be alive.
-- 2. If a player joins one red slot, one red AI plane should return to the nearest home base.
-- 3. If two players join the red slots, no AI plane should be spawned, and all airborne AI planes should return to the nearest home base.
-- 4. Monitor that once a red AI is destroyed, that it ReSpawns...
--
-- Define the SET of CLIENTs from the red coalition. This SET is filled during startup.
local RU_PlanesClientSet = SET_CLIENT:New():FilterCountries( "RUSSIA" ):FilterCategories( "plane" ):FilterStart()
-- Define the SPAWN object for the red AI plane template.
-- We use InitCleanUp to check every 20 seconds, if there are no planes blocked at the airbase, waithing for take-off.
-- If a blocked plane exists, this red plane will be ReSpawned.
local RU_PlanesSpawn = SPAWN:New( "AI RU" ):InitCleanUp( 20 )
-- Start the AI_BALANCER, using the SET of red CLIENTs, and the SPAWN object as a parameter.
local RU_AI_Balancer = AI_BALANCER:New( RU_PlanesClientSet, RU_PlanesSpawn )
function RU_AI_Balancer:OnAfterSpawned( SetGroup, From, Event, To, AIGroup )
local PatrolZoneGroup = GROUP:FindByName( "PatrolZone" )
local PatrolZone = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup )
local Patrol = AI_PATROLZONE:New( PatrolZone, 3000, 6000, 400, 600 )
Patrol:ManageFuel( 0.2, 60 )
Patrol:SetControllable( AIGroup )
Patrol:__Start( 5 )
end