Author SHA1 Message Date
dutchie031 a1355ff05d fixed minor bug
Publish Release / build (push) Failing after 12s
2026-09-08 07:44:24 +02:00
dutchie031 2e636b2ded minor udpdate to docs
Update Docs / prepare-docs (push) Successful in 30s
Update Docs / build-image (push) Successful in 9s
Update Docs / deploy-container (push) Successful in 13s
2026-09-08 07:25:29 +02:00
dutchie031 488a492e39 Custom briefings for BlueSam and FARPS (#44)
Update Docs / prepare-docs (push) Successful in 32s
Publish Release / build (push) Successful in 34s
Update Docs / build-image (push) Successful in 15s
Update Docs / deploy-container (push) Successful in 12s
Fixes #37

<img width="761" alt="image.png" src="attachments/bdc9d61a-8bbc-41f3-8de4-b97edfae28da">
<img width="1095" alt="image.png" src="attachments/9c77798d-f371-4035-84dd-7c60cb79d91f">

Reviewed-on: #44
Co-authored-by: dutchie031 <timrorije@gmail.com>
2026-09-07 17:23:30 +00:00
15 changed files with 120 additions and 39 deletions
+1 -1
View File
@@ -64,7 +64,7 @@
</footer>
<script>
if (true || window.location.hostname.toLowerCase() == 'beta.spearhead.rocks') {
if (window.location.hostname.toLowerCase() == 'beta.spearhead.rocks') {
betaHtml = '<note-box type="warning" title="Beta Version">You are looking at docs that are released with the beta version.</note-box>';
}
document.getElementById('beta-box-div').innerHTML = betaHtml || '';
@@ -49,17 +49,6 @@ class DownloadScript extends HTMLElement {
return null;
}
}
async fetchReleaseAssets(url){
try {
const response = await fetch(url);
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching release assets:', error);
return null;
}
}
async connectedCallback() {
@@ -72,10 +61,9 @@ class DownloadScript extends HTMLElement {
if (latestRelease) {
version = latestRelease.tag_name;
timestamp = new Date(latestRelease.published_at).toLocaleDateString('en-CA');
const assets = await this.fetchReleaseAssets(latestRelease.assets_url);
const assets = latestRelease.assets;
if (assets && assets.length > 0) {
const spearheadAsset = assets.find(asset => asset.name.includes('spearhead'));
const spearheadAsset = assets.find(asset => asset.name.includes('spearhead.v'));
if (spearheadAsset) {
size = (spearheadAsset.size / 1024).toFixed(2) + 'kb';
href = spearheadAsset.browser_download_url;
@@ -105,7 +93,7 @@ class DownloadScript extends HTMLElement {
<p>
<span class="version-text">${version}</span> (${timestamp}) <br>
<a style="text-decoration: underline;" target="_blank" href="${href}">Download</a> (${size}) <br>
<a style="text-decoration: underline;" target="_blank" href="https://git.dutchie031.com/api/v1/repos/Spearhead/spearhead/releases">See All Versions here</a>
<a style="text-decoration: underline;" target="_blank" href="https://git.dutchie031.com/Spearhead/spearhead/releases">See All Versions here</a>
</p>
</div>
+5 -2
View File
@@ -169,13 +169,16 @@ class Sidebar extends HTMLElement {
render() {
var betaHtml = '';
if (window.location.hostname.toLowerCase() == 'beta.spearhead.rocks') {
if (true || window.location.hostname.toLowerCase() == 'beta.spearhead.rocks') {
betaHtml = '<note-box type="warning" title="Beta Version">You are looking at docs that are released with the beta version.</note-box>';
}
this.innerHTML = `
<div class="side-nav">
${betaHtml}
<div style="width:100%">
${betaHtml}
</div>
<h4 class="side-nav-title"></h4>
<ul>
<!-- Navigation items will be populated automatically -->
+16 -7
View File
@@ -80,17 +80,12 @@
</tbody>
</table>
<note-box type="info" title="Combining Tasks">
Currently a group can do 1 thing. Either be Primary, Seconday, Sweep or Intercept. <br>
If you want to have different types during different stages, it's best to copy paste groups and
rename them accordingly. <br>
</note-box>
<note-box type="info" title="Airfield Defenses">
</note-box>
<h3 id="cap-zones-ref">Zones</h3>
<table class="table-no-style">
<tbody>
@@ -99,6 +94,7 @@
<td>=></td>
<td>A Cap ROUTE. The two most outer points are used to define the fly-over points.</td>
</tr>
<tr><td>______________________</td><td></td><td></td> </tr>
<tr>
<td><code-inline>INTERCEPTZONE_[ZoneID]_[NAME]</code-inline></td>
<td>=></td>
@@ -112,6 +108,19 @@
</p>
<h2 id="setup">Setting up groups</h2>
<h3 id="airbases-grouping">Airbases</h3>
<p>
Before talking about the groups, it's good to mention that groups are currently logically grouped and managed by an Airbase. <br>
Meaning, Airbase A does not know how many flights Airbase B is sending out. <br>
This might change in the future where multiple Airbases can work together.
</p>
<note-box type="info" title="Airbase Defenses">
When an Airbase is "active" and thus has aircraft being scheduled for taskings, all RED groups at that airfield will also be spawned. <br>
Additionally, all missions of type <code-inline>SAM</code-inline> will be spawned (without being available in Primary Missions). <br>
This should hopefully give you all the tools to make distance CAP airbases still feel very well defended.
</note-box>
<h3 id="types-explained">Types explained</h3>
<p>
Before we go into depth on each type it's best to first look at the types briefly and what we
@@ -140,7 +149,7 @@
The ground attack units mentioned are not in yet and thus escort groups are not either. <br>
<note-box type="warning" title="Overriding Taskings">
Each tasking has very specific waypoints, both for departure, arrical and ofcourse all point in between. <br>
Each tasking has very specific waypoints, both for departure, arrival and ofcourse all point in between. <br>
Overriding the "tasking" will break part of the cap flow as "RTBInTen" and RTB events are triggered by waypoints. <br>
Overriding these waypoints and taskings will break the management by Spearhead.
</note-box>
@@ -154,7 +163,7 @@
The way the trigger zone is mapped to a route is as follows: <br>
<br>
The 2 points that are furthest apart from each other are chosen as the "leg" of the route. <br>
The other 2 points are discarded. <br>
The other 2 points are discarded at this time. <br>
<br>
The Orbit route is then created with an "Anchored" task. With the closest point being the starting
point with an outbound leg to the furthest point. <br>
+7
View File
@@ -253,6 +253,13 @@
To make an airbase buildable add a text box inside of the trigger zone and name it: <code-inline>buildable_[freeform]</code-inline> <br/>
Then in the text box type amount of kilo's you want to be transfered before the logistisc mission is complete. <br/>
<br>
To add a custom briefing for a buildable zone, include a text box inside the trigger zone and name it: <code-inline>supplybriefing_[freeform]</code-inline>.
<code-inline>{{coords}}</code-inline> is highly recommended so the players can easily find their landing zone.<br>
Additionally, the amount of kilos required and delivered are always shown on the bottom, as well as a note to not land in the construction zone.
<br/>
<br>
The base or zone will slowly build up with each crate giving both a nice view of it happening AND it's good for performance as there's not a big addition of objects at once. <br/>
Right now a crate takes 15 seconds to unpack per 500kg. Meaning 2 crates of 1000kg will be faster than 1 crate of 2000kg. <br/>
+1
View File
@@ -337,6 +337,7 @@ main {
.side-nav {
min-width: 200px;
max-width: 200px;
padding-left: 0;
overflow-y: auto;
border-right: none;
+4
View File
@@ -15,6 +15,10 @@
These lanes can be used to create different stage progression flows and create different "side-stories".
#40
- Issue: #37
Now enabled the mission editor to add a custom briefing to the "Buildable" missions.
#44
### Bug Fixes
## [0.13.0] 2026-08
+47
View File
@@ -58,6 +58,7 @@ local StageDrawing = require("classes.stageClasses.drawings.StageDrawing")
---@class BlueSamData
---@field groups Array<string>
---@field buildingKilos number?
---@field briefing string?
---@class MissionZoneData
---@field ZoneName string
@@ -74,6 +75,7 @@ local StageDrawing = require("classes.stageClasses.drawings.StageDrawing")
---@field padNames Array<string>
---@field buildingKilos number?
---@field supplyHubNames Array<string>
---@field briefing string?
---@class Database
---@field private _tables DatabaseTables
@@ -616,6 +618,27 @@ function Database:loadBlueSamUnits()
local number = tonumber(kvPair.value)
if number and number > 0 then
samData.buildingKilos = number
if env.mission.drawings and env.mission.drawings.layers then
for i, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
for key, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
if layer_object.name and Util.startswith(layer_object.name, "supplybriefing_", true) then
local description = layer_object.text
if description and description ~= "" then
samData.briefing = description
end
end
end
end
end
end
end
else
MissionEditorWarnings.Add("Buildable number for " .. blueSamZone .. " is invalid")
end
end
end
@@ -750,7 +773,31 @@ function Database:loadFarpData()
local number = tonumber(kvPair.value)
if number and number > 0 then
farpzoneData.buildingKilos = number
-- check briefings
if env.mission.drawings and env.mission.drawings.layers then
for i, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
for key, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
if layer_object.name and Util.startswith(layer_object.name, "supplybriefing_", true) then
local description = layer_object.text
if description and description ~= "" then
farpzoneData.briefing = description
end
end
end
end
end
end
end
else
MissionEditorWarnings.Add("Buildable number for " .. farpZone .. " is invalid.")
end
end
end
end
@@ -89,7 +89,7 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
local zone = DcsUtil.getZoneByName(zoneName)
if zone then
BuildableZone.New(self, zone, self._buildableCrateKilos or 0, "SAM_CRATE", self._blueGroups, logger, database)
BuildableZone.New(self, zone, self._buildableCrateKilos or 0, "SAM_CRATE", self._blueGroups, logger, database, blueSamData.briefing)
end
return self
@@ -64,7 +64,7 @@ function FarpZone.New(database, logger, zoneName, spawnManager)
local zone = DcsUtil.getZoneByName(zoneName)
if zone then
self._logger:debug("Creating Buildable zone: " .. zoneName .. " with " .. (farpData.buildingKilos or "nil") .. " kilos")
BuildableZone.New(self, zone, farpData.buildingKilos or 0, "FARP_CRATE", self._groups, logger, database)
BuildableZone.New(self, zone, farpData.buildingKilos or 0, "FARP_CRATE", self._groups, logger, database, farpData.briefing)
end
end
self:Deactivate()
@@ -20,7 +20,8 @@ BuildableZone.__index = BuildableZone
---@param database Database
---@param crateType SupplyType
---@param logger Logger
function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroups, logger, database)
---@param briefing string?
function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroups, logger, database, briefing)
self._targetZone = targetZone
self._requiredKilos = kilosRequired or 0
self._buildableGroups = buildableGroups or {}
@@ -69,7 +70,7 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
local noLandingZone = self:GetNoLandingZone()
if kilosRequired and kilosRequired > 0 then
self._buildableMission = BuildableMission.new(database, logger, targetZone, noLandingZone, kilosRequired, crateType)
self._buildableMission = BuildableMission.new(database, logger, targetZone, noLandingZone, kilosRequired, crateType, briefing)
self._buildableMission:AddOnCrateDroppedOfListener(self)
else
self._buildableMission = nil
@@ -1,6 +1,7 @@
local Stage = require("classes.stageClasses.Stages.BaseStage.Stage")
local GlobalCapManager = require("classes.capClasses.GlobalCapManager")
local StageState = require("classes.stageClasses.Stages.StageState")
---@class ExtraStage : Stage
local ExtraStage = {}
@@ -61,7 +62,7 @@ function ExtraStage:OnStageNumberChanged(number, stageLaneIdentifier)
self:ActivateStage()
end
if self._currentStageState == Stage.CurrentStageState.BLUE then
if self._currentStageState == StageState.BLUE then
self:ActivateBlueStage()
end
@@ -15,7 +15,6 @@ local logger = Logger.new("DrawingHelper", level)
local DrawingHelper = {}
DrawingHelper.__index = DrawingHelper
---@param object DrawingObject
---@return Array<integer> id
function DrawingHelper.Draw(object)
@@ -21,9 +21,18 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
---@field private _dropOffZone SpearheadTriggerZone?
---@field private _noLandingZoneDrawing CustomDrawing?
---@field private _dropOffZoneDrawing CustomDrawing?
---@field private _briefing string?
local BuildableMission = {}
BuildableMission.__index = BuildableMission
local function getDefaultBriefing(siteType, coords)
return "We've dispatched forward units to find a proper spot for a new " .. siteType .. "." ..
"\nYou will need to drop off supplies so they can start building." ..
"\nThe coords are: " .. coords ..
"\n\n"
end
---@class OnCrateDroppedListener
---@field OnCrateDroppedOff fun(self:OnCrateDroppedListener, mission:BuildableMission, kilos:number)
@@ -33,7 +42,8 @@ BuildableMission.__index = BuildableMission
---@param requiredCrateType SupplyType
---@param noLandingZone SpearheadTriggerZone?
---@param logger Logger
function BuildableMission.new(database, logger, targetZone, noLandingZone, requiredKilos, requiredCrateType)
---@param briefing string?
function BuildableMission.new(database, logger, targetZone, noLandingZone, requiredKilos, requiredCrateType, briefing)
setmetatable(BuildableMission, Mission)
@@ -43,6 +53,7 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
self._database = database
self._requiredKilos = requiredKilos
self._droppedKilos = 0
self._briefing = briefing
self._noLandingZone = noLandingZone
@@ -64,7 +75,13 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
end
self.code = tostring(database:GetNewMissionCode())
self.name = "Resupply"
local splitTargetZoneName = Util.split_string(targetZone.name, "_")
if splitTargetZoneName and splitTargetZoneName[3] and splitTargetZoneName[3] ~= "" then
self.name = splitTargetZoneName[3]
else
self.name = "Resupply"
end
local type = "site"
if requiredCrateType == "SAM_CRATE" then
@@ -81,7 +98,6 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate()
self._state = "NEW"
self.location = targetZone.location
self.missionType = "LOGISTICS"
@@ -116,11 +132,16 @@ function BuildableMission:ShowBriefing(groupID)
siteType = "airbase"
end
local briefingPart = self._briefing
if briefingPart then
briefingPart = Util.replaceString(briefingPart, "{{coords}}", coords)
else
briefingPart = getDefaultBriefing(siteType, coords)
end
local briefing = "Mission [" .. self.code .. "] " .. self.name ..
"\n \n" ..
"We've dispatched forward units to find a proper spot for a new " .. siteType .. "." ..
"\nYou will need to drop off supplies so they can start building." ..
"\nThe coords are: " .. coords ..
briefingPart ..
"\n\n" ..
"\nKilos still required: " .. self._requiredKilos - self._droppedKilos ..
"\n\n" ..
+2 -2
View File
@@ -644,7 +644,7 @@ do -- INIT DCS_UTIL
---@param location Vec3
---@return number markID
function DCS_UTIL.AddMarkToGroup(groupID, text, location)
drawID = drawID + 1
local drawID = DCS_UTIL.GetNextDrawID()
trigger.action.markToGroup(drawID, text, location, groupID, true, nil)
return drawID
end
@@ -654,7 +654,7 @@ do -- INIT DCS_UTIL
---@param location Vec3
---@return integer
function DCS_UTIL.AddMarkToAll(text, location)
drawID = drawID + 1
local drawID = DCS_UTIL.GetNextDrawID()
trigger.action.markToAll(drawID, text, location, true, nil)
return drawID
end