Fix/cas mission2 (#44)

* changed from only units to also statics in targets for CAS

* added statics and update docs

* optimised hull creation and did not group by group

* added cas_mission.png

---------

Co-authored-by: dutchie032 <dutchie032>
Co-authored-by: ex61wi <tim.rorije@ing.com>
This commit is contained in:
2025-05-27 19:57:46 +02:00
committed by GitHub
co-authored by dutchie032 <dutchie032> ex61wi
parent 74e7d1ca7a
commit 90c8d113fd
7 changed files with 155 additions and 63 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

+1
View File
@@ -212,6 +212,7 @@
CAS missions involve close air support for friendly ground forces. <br />
CAS mission have a special "BattleManager" module added by defualt. This will make blue and red units shoot at eachoter. <br/>
This creates a nice effect. <br/>
To see how it's done see: <a href="./tutorials.html#mission-cas">[Tutorials] Mission: CAS</a>
</p>
<h3 id="strike">STRIKE</h3>
+52
View File
@@ -58,6 +58,7 @@
<ul>
<li><a href="#mission-1-dead" class="side-nav-h3">Mission: DEAD</a></li>
<li><a href="#mission-2-strike" class="side-nav-h3">Mission: STRIKE</a></li>
<li><a href="#mission-cas" class="side-nav-h3">Mission: CAS</a></li>
</ul>
</li>
<li><a href="#mission-briefings" class="side-nav-h2">Mission Briefings</a></li>
@@ -244,11 +245,62 @@
Even some SHORADS to spice the whole thing up. <br/>
In the picture below all units that are selected and who show up as white (they are actually red) have the prefix <span class="inline-lua"><span class="lua-variable">TGT_</span></span> in front of their name. <br/>
</p>
<p>
This will make it so the mission will be marked as complete when those units are destroyed. The rest of the units will exist until the entire stage is cleaned up. <br/>
</p>
<img src="../img/strike_target.png"/><br/>
<h3 id="mission-cas">Mision: CAS</h3>
<p>
A CAS mission will add an additional layer to the mission. <br/>
Whereas BAI, Strike and DEAD missions are somewhat clean, CAS missions are where the chaos starts. <br/>
<br/>
With CAS missions an additional BattleManager will be activated. <br/>
This BattleManager will scan for units and force them to fire at other units in the zone. <br/>
Great efforts are made to make AI miss. In the end we want players to be they key factor to mission success. <br/>
If AI do seem to be hitting more than 1 or 2 unfortunate targets, it is most likely you either have another script controlling the units, or you have placed the units too staggered. <br/>
Please make sure to also read the "Notes" section below the image. <br/>
<img src="../img/cas_mission.png" style="width: 100%;"></img>
Notes: <br/>
<ul>
<li>
AAA units will sometimes also follow their lead and fire at targets on the ground.<br/>
They will however always prioritize AIR targets as at that point they have a threat themselves and won't follow their lead's targets. <br/>
If you really don't like this, it's best to put the AAA units in a separate group.
</li>
<li>
Units will purposely aim past a target. <br/>
This simulates a fight, but doesn't actually do anything, so the player is the only person that can complete a mission. <br/>
Be aware that if you stagger groups too much, shooting past a target might hit another. <br/>
Best would be to always test it. <br/>
You can also enable debugging. <br/>
This will draw all boxes and lines. <br/>
NOTE: Keep DEBUG disabled on an actual mission.
</li>
<li>
When you want AI to shoot through gaps a distance of approx 150ft is required between the two outer units. <br/>
These gaps will be automatically detected and can even exist within a group itself. <br/>
</li>
<li>
Line of sight is important, dependent on which type of unit. <br/>
Units won't shoot when they can't fire at a certain point. <br/>
As a mission editor it's up to you to set the range, line of sight and cover. <br/>
</li>
<li>
As Performance is something that's on any multiplayer mission maker's mind, this is a very optimised way.
AI does not have to know, scan, priorise targets every second. <br/>
It does not really have to think or move. <br/>
Ofcourse, there is a balance and too many can be too much. <br/>
For now it has been tested with 60+ units in 3 seperate CAS zones active at a time. <br/>
No performance degradation was seen at all. <br/>
</li>
</ul>
</p>
<h2 id="mission-briefings">Mission Briefings</h2>
<p>
So now we've created some missions we also want to add briefings to them. This is pretty easy with Spearhead. <br/>
Binary file not shown.
+15 -3
View File
@@ -1203,9 +1203,10 @@ do -- INIT DCS_UTIL
---@param location table? vector 3 data. { x , z, alt }
---@param route table? route of the group. If nil wil be the default route.
---@param uncontrolled boolean? Sets the group to be uncontrolled on spawn
---@param countryId CountryID? Overwrites the country
---@return table? new_group the Group class that was spawned
---@return boolean? isStatic whether the group is a static or not
function DCS_UTIL.SpawnGroupTemplate(groupName, location, route, uncontrolled)
function DCS_UTIL.SpawnGroupTemplate(groupName, location, route, uncontrolled, countryId)
if groupName == nil then
return nil, nil
end
@@ -1217,7 +1218,8 @@ do -- INIT DCS_UTIL
if template.category == DCS_UTIL.GroupCategory.STATIC then
--TODO: Implement location and route stuff
local spawn_template = template.group_template
return coalition.addStaticObject(template.country_id, spawn_template), true
local country = countryId or template.country_id
return coalition.addStaticObject(country, spawn_template), true
else
local spawn_template = template.group_template
if location ~= nil then
@@ -1244,11 +1246,21 @@ do -- INIT DCS_UTIL
if uncontrolled ~= nil then
spawn_template.uncontrolled = uncontrolled
end
local new_group = coalition.addGroup(template.country_id, template.category, spawn_template)
local country = countryId or template.country_id
local new_group = coalition.addGroup(country, template.category, spawn_template)
return new_group, false
end
end
---@return number? id
function DCS_UTIL.GetNeutralCountry()
for name, id in pairs(country.id) do
if coalition.getCountryCoalition(id) == DCS_UTIL.Coalition.NEUTRAL then
return id
end
end
end
function DCS_UTIL.IsBingoFuel(groupName, offset)
if offset == nil then offset = 0 end
local bingoSetting = 0.20
+14 -9
View File
@@ -156,7 +156,6 @@ function SpearheadGroup:GetObjects()
return result
end
---comment
---@return Array<Unit> result list of objects
function SpearheadGroup:GetAsUnits()
@@ -195,15 +194,21 @@ end
function SpearheadGroup:SetInvisible()
local group = Group.getByName(self.groupName)
if group then
local setInvisible = {
id = 'SetInvisible',
params = {
value = true
if self._isStatic == true then
local country = Spearhead.DcsUtil.GetNeutralCountry()
Spearhead.DcsUtil.SpawnGroupTemplate(self.groupName, nil, nil, nil, country)
else
local group = Group.getByName(self.groupName)
if group then
local setInvisible = {
id = 'SetInvisible',
params = {
value = true
}
}
}
group:getController():setCommand(setInvisible)
group:getController():setCommand(setInvisible)
end
end
end
+73 -51
View File
@@ -93,7 +93,6 @@ function BattleManager:Update()
end
local drawID = 8633
---@private
---@param groups Array<SpearheadGroup>
---@param targetGroups Array<SpearheadGroup>
@@ -101,6 +100,8 @@ function BattleManager:LetUnitsShoot(groups, targetGroups)
local shootChance = math.random(3, 7) / 10
local targetHulls = self:ToShootingHulls(targetGroups)
for _, group in pairs(groups) do
local units = group:GetAsUnits()
@@ -115,7 +116,7 @@ function BattleManager:LetUnitsShoot(groups, targetGroups)
if math.random() <= shootChance then
local unitPos = unit:getPoint()
local point = self:GetRandomPoint({x = unitPos.x, y = unitPos.z }, targetGroups)
local point = self:GetRandomPoint({x = unitPos.x, y = unitPos.z }, targetHulls)
if point then
local ammo, qty = self:getBestAmmo(unit)
@@ -129,18 +130,10 @@ function BattleManager:LetUnitsShoot(groups, targetGroups)
expendQtyEnabled = true
}
}
---- DEBUG ONLY!!
--- DRAWS THE SHOOTING LINE
-- do
-- local color = {r = 1, g = 0, b = 0, a = 1}
-- if unit:getCoalition() == 2 then
-- color = {r = 0, g = 0, b = 1, a = 1}
-- end
-- Spearhead.DcsUtil.DrawLine(unitPos, {x = point.x, y = 0, z = point.y}, color, 1)
-- end
if SpearheadConfig and SpearheadConfig.debugEnabled == true then
self:DrawDebugLine(point, unit)
end
self._logger:debug("Red unit " .. unit:getName() .. " will shoot " .. qty .. " rounds at point: " .. tostring(point))
@@ -207,56 +200,85 @@ function BattleManager:IsUnitApplicable(unit)
end
---@private
---@param origin Vec2
---@param groups Array<SpearheadGroup>
---@return Vec2?
function BattleManager:GetRandomPoint(origin, groups)
local group = Spearhead.Util.randomFromList(groups) --[[@as SpearheadGroup]]
if not group then return nil end
---@return Array<Array<Vec2>>
function BattleManager:ToShootingHulls(groups)
local result = {}
local points = {}
for _, unit in pairs(group:GetAsUnits()) do
local pos = unit:getPoint()
table.insert(points, {x = pos.x, y = pos.z})
for _, group in pairs(groups) do
for _, unit in pairs(group:GetObjects()) do
local pos = unit:getPoint()
table.insert(points, {x = pos.x, y = pos.z})
end
end
local hulls = Spearhead.Util.getSeparatedConvexHulls(points, 50)
local hull = Spearhead.Util.randomFromList(hulls) --[[@as Array<Vec2>]]
local enlargedHulls = {}
for _, hull in pairs(hulls) do
local enlarged = Spearhead.Util.enlargeConvexHull(hull, 25)
if enlarged then
table.insert(enlargedHulls, enlarged)
end
end
for _, hull in pairs(enlargedHulls) do
if #hull > 2 then
table.insert(result, hull)
end
end
return result
end
---@private
---@param origin Vec2
---@param groupHulls Array<Array<Vec2>>
---@return Vec2?
function BattleManager:GetRandomPoint(origin, groupHulls)
local hull = Spearhead.Util.randomFromList(groupHulls) --[[@as Array<Vec2>]]
if not hull then return nil end
local enlargedHull = Spearhead.Util.enlargeConvexHull(hull, 25)
local shootPoints = Spearhead.Util.GetTangentHullPointsFromOrigin(enlargedHull, origin)
---- DEBUG ONLY!!
---- DRAWS the hulls of the units
-- for _, drawHull in pairs(hulls) do
-- ---@type SpearheadTriggerZone
-- local zone = {
-- name = "temp",
-- zone_type = "Polygon",
-- radius = 0,
-- verts = drawHull,
-- location = { x=drawHull[1].x, y=drawHull[1].y },
-- }
-- Spearhead.DcsUtil.DrawZone(zone, {r =0, g= 1, b =0, a = 0.5} ,{r =0, g= 1, b =0, a = 0}, 1)
-- local enlarged = Spearhead.Util.enlargeConvexHull(drawHull, 25)
-- local enlargedZone = {
-- name = "temp_enlarged",
-- zone_type = "Polygon",
-- radius = 0,
-- verts = enlarged,
-- location = { x=enlarged[1].x, y=enlarged[1].y },
-- }
-- Spearhead.DcsUtil.DrawZone(enlargedZone, {r =0, g= 0, b =1, a = 0.5} ,{r =0, g= 1, b =0, a = 0}, 1)
-- end
local shootPoints = Spearhead.Util.GetTangentHullPointsFromOrigin(hull, origin)
if SpearheadConfig and SpearheadConfig.debugEnabled == true then
self:DrawDebugZone({ hull })
end
return Spearhead.Util.randomFromList(shootPoints) --[[@as Vec2]]
end
do --DEBUG
---@param unit Unit
---@param target Vec2
function BattleManager:DrawDebugLine(target, unit)
local color = {r = 1, g = 0, b = 0, a = 1}
if unit:getCoalition() == 2 then
color = {r = 0, g = 0, b = 1, a = 1}
end
Spearhead.DcsUtil.DrawLine(unit:getPoint(), {x = target.x, y = 0, z = target.y}, color, 1)
end
---@param hulls Array<Array<Vec2>>
function BattleManager:DrawDebugZone(hulls)
for _, drawHull in pairs(hulls) do
---@type SpearheadTriggerZone
local zone = {
name = "temp",
zone_type = "Polygon",
radius = 0,
verts = drawHull,
location = { x=drawHull[1].x, y=drawHull[1].y },
}
Spearhead.DcsUtil.DrawZone(zone, {r =0, g=0, b =1, a = 0.5} ,{r =0, g= 0, b =1, a = 0}, 1)
end
end
end --DEBUG
if Spearhead == nil then Spearhead = {} end
if Spearhead.classes == nil then Spearhead.classes = {} end
if Spearhead.classes.stageClasses == nil then Spearhead.classes.stageClasses = {} end