Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77f4c41a6b | ||
|
|
07a234489f | ||
|
|
e363b3fe94 | ||
|
|
b68f95b748 | ||
|
|
8341a5fb6d | ||
|
|
e32518f48d | ||
|
|
2e7a930473 | ||
|
|
cbccedf710 | ||
|
|
e986887dd8 | ||
|
|
9549e8c48e | ||
|
|
9b2540d461 | ||
|
|
a1355ff05d | ||
|
|
2e636b2ded | ||
|
|
488a492e39 | ||
|
|
772397250f | ||
|
|
0a7e8ea977 | ||
|
|
96507f1d34 | ||
|
|
96ada242a4 | ||
|
|
2bce1985e2 | ||
|
|
5601091b8f | ||
|
|
36ff621759 | ||
|
|
18eff4b85a | ||
|
|
ee5a591c8e | ||
|
|
dd0d16cd1a | ||
|
|
e67200bddd | ||
|
|
f7ff367f5b | ||
|
|
952daa2897 | ||
|
|
7c0c5e4a85 | ||
|
|
970f049440 | ||
|
|
63a1ea44db | ||
|
|
c8329108dd | ||
|
|
a88520a413 | ||
|
|
d8764c6824 | ||
|
|
d0e7f2aae4 | ||
|
|
8c75ba5027 | ||
|
|
52f97f7c92 | ||
|
|
e5abfe19d6 | ||
|
|
b34918ff28 | ||
|
|
ab910b8faf | ||
|
|
f677a83c8e | ||
|
|
a1e36aa5d7 | ||
|
|
bb9144e3a0 | ||
|
|
7475c03db2 | ||
|
|
6390aeb527 | ||
|
|
109db7cc6e | ||
|
|
5277c5e566 | ||
|
|
e3c53c253d | ||
|
|
f6012031e2 | ||
|
|
09bc2629bb | ||
|
|
87578b0cb2 | ||
|
|
d11860d696 | ||
|
|
a354022035 | ||
|
|
3ce1b60d09 | ||
|
|
4fb7d799d5 | ||
|
|
9a04e2f07a | ||
|
|
55138d7e2a | ||
|
|
5a906cdea0 | ||
|
|
44942e5b7a | ||
|
|
5859c889e7 | ||
|
|
2cec4de32e | ||
|
|
c052b1a14a | ||
|
|
4b52d322de |
@@ -0,0 +1,3 @@
|
|||||||
|
FROM nginx:latest
|
||||||
|
|
||||||
|
COPY ./web /usr/share/nginx/html
|
||||||
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 296 KiB After Width: | Height: | Size: 296 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 330 KiB |
|
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 332 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
@@ -21,6 +21,9 @@
|
|||||||
<main>
|
<main>
|
||||||
<div class="reference-container">
|
<div class="reference-container">
|
||||||
<div class="side-nav">
|
<div class="side-nav">
|
||||||
|
|
||||||
|
<div id="beta-box-div"></div>
|
||||||
|
|
||||||
<h4>Quick Links</h4>
|
<h4>Quick Links</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/pages/tutorials.html" class="side-nav-h2">Tutorials</a></li>
|
<li><a href="/pages/tutorials.html" class="side-nav-h2">Tutorials</a></li>
|
||||||
@@ -59,6 +62,13 @@
|
|||||||
<footer>
|
<footer>
|
||||||
<p>© 2025 Spearhead Project</p>
|
<p>© 2025 Spearhead Project</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
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 || '';
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -19,9 +19,10 @@ class Header extends HTMLElement {
|
|||||||
<div class="dropdown-content">
|
<div class="dropdown-content">
|
||||||
<a href="/pages/include-script.html">Include the Script</a>
|
<a href="/pages/include-script.html">Include the Script</a>
|
||||||
<a href="/pages/first-start.html">First Start</a>
|
<a href="/pages/first-start.html">First Start</a>
|
||||||
|
<a href="/pages/advanced/map-markings.html">Map Markings</a>
|
||||||
<a href="/pages/advanced/CAP.html">Advanced: CAP</a>
|
<a href="/pages/advanced/CAP.html">Advanced: CAP</a>
|
||||||
<a href="/pages/advanced/missions.html">Advanced: Missions</a>
|
<a href="/pages/advanced/missions.html">Advanced: Missions</a>
|
||||||
<a href="/pages/advanced/map-markings.html">Map Markings</a>
|
<a href="/pages/advanced/lanes.html">Advanced: Lanes</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class DownloadScript extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://api.github.com/repos/dutchie031/Spearhead/releases/latest');
|
const response = await fetch('https://git.dutchie031.com/api/v1/repos/Spearhead/spearhead/releases/latest');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Cache the data
|
// Cache the data
|
||||||
@@ -50,32 +50,20 @@ class DownloadScript extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
async connectedCallback() {
|
||||||
|
|
||||||
var version = "?";
|
var version = "?";
|
||||||
var timestamp = "?";
|
var timestamp = "?";
|
||||||
var size = "?";
|
var size = "?";
|
||||||
var href = "https://github.com/dutchie031/Spearhead/releases"
|
var href = "https://git.dutchie031.com/api/v1/repos/Spearhead/spearhead/releases"
|
||||||
|
|
||||||
const latestRelease = await this.fetchLatestRelease();
|
const latestRelease = await this.fetchLatestRelease();
|
||||||
if (latestRelease) {
|
if (latestRelease) {
|
||||||
version = latestRelease.tag_name;
|
version = latestRelease.tag_name;
|
||||||
timestamp = new Date(latestRelease.published_at).toLocaleDateString('en-CA');
|
timestamp = new Date(latestRelease.published_at).toLocaleDateString('en-CA');
|
||||||
|
const assets = latestRelease.assets;
|
||||||
const assets = await this.fetchReleaseAssets(latestRelease.assets_url);
|
|
||||||
if (assets && assets.length > 0) {
|
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) {
|
if (spearheadAsset) {
|
||||||
size = (spearheadAsset.size / 1024).toFixed(2) + 'kb';
|
size = (spearheadAsset.size / 1024).toFixed(2) + 'kb';
|
||||||
href = spearheadAsset.browser_download_url;
|
href = spearheadAsset.browser_download_url;
|
||||||
@@ -105,7 +93,7 @@ class DownloadScript extends HTMLElement {
|
|||||||
<p>
|
<p>
|
||||||
<span class="version-text">${version}</span> (${timestamp}) <br>
|
<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="${href}">Download</a> (${size}) <br>
|
||||||
<a style="text-decoration: underline;" target="_blank" href="https://github.com/dutchie031/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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -168,8 +168,17 @@ class Sidebar extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
var betaHtml = '';
|
||||||
|
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>';
|
||||||
|
}
|
||||||
|
|
||||||
this.innerHTML = `
|
this.innerHTML = `
|
||||||
<div class="side-nav">
|
<div class="side-nav">
|
||||||
|
<div style="width:100%">
|
||||||
|
${betaHtml}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4 class="side-nav-title"></h4>
|
<h4 class="side-nav-title"></h4>
|
||||||
<ul>
|
<ul>
|
||||||
<!-- Navigation items will be populated automatically -->
|
<!-- Navigation items will be populated automatically -->
|
||||||
@@ -80,17 +80,12 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
<note-box type="info" title="Combining Tasks">
|
<note-box type="info" title="Combining Tasks">
|
||||||
Currently a group can do 1 thing. Either be Primary, Seconday, Sweep or Intercept. <br>
|
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
|
If you want to have different types during different stages, it's best to copy paste groups and
|
||||||
rename them accordingly. <br>
|
rename them accordingly. <br>
|
||||||
</note-box>
|
</note-box>
|
||||||
|
|
||||||
<note-box type="info" title="Airfield Defenses">
|
|
||||||
|
|
||||||
</note-box>
|
|
||||||
|
|
||||||
<h3 id="cap-zones-ref">Zones</h3>
|
<h3 id="cap-zones-ref">Zones</h3>
|
||||||
<table class="table-no-style">
|
<table class="table-no-style">
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -99,6 +94,7 @@
|
|||||||
<td>=></td>
|
<td>=></td>
|
||||||
<td>A Cap ROUTE. The two most outer points are used to define the fly-over points.</td>
|
<td>A Cap ROUTE. The two most outer points are used to define the fly-over points.</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr><td>______________________</td><td></td><td></td> </tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code-inline>INTERCEPTZONE_[ZoneID]_[NAME]</code-inline></td>
|
<td><code-inline>INTERCEPTZONE_[ZoneID]_[NAME]</code-inline></td>
|
||||||
<td>=></td>
|
<td>=></td>
|
||||||
@@ -112,6 +108,19 @@
|
|||||||
</p>
|
</p>
|
||||||
<h2 id="setup">Setting up groups</h2>
|
<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>
|
<h3 id="types-explained">Types explained</h3>
|
||||||
<p>
|
<p>
|
||||||
Before we go into depth on each type it's best to first look at the types briefly and what we
|
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>
|
The ground attack units mentioned are not in yet and thus escort groups are not either. <br>
|
||||||
|
|
||||||
<note-box type="warning" title="Overriding Taskings">
|
<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 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.
|
Overriding these waypoints and taskings will break the management by Spearhead.
|
||||||
</note-box>
|
</note-box>
|
||||||
@@ -154,7 +163,7 @@
|
|||||||
The way the trigger zone is mapped to a route is as follows: <br>
|
The way the trigger zone is mapped to a route is as follows: <br>
|
||||||
<br>
|
<br>
|
||||||
The 2 points that are furthest apart from each other are chosen as the "leg" of the route. <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>
|
<br>
|
||||||
The Orbit route is then created with an "Anchored" task. With the closest point being the starting
|
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>
|
point with an outbound leg to the furthest point. <br>
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="dark">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Spearhead Lanes</title>
|
||||||
|
<link rel="stylesheet" href="/style/style.css">
|
||||||
|
<script src="/js/site.js"></script>
|
||||||
|
<style>
|
||||||
|
.side-nav a.active {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #4fc3f7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="module" src="/js/components.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<app-header></app-header>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div class="reference-container">
|
||||||
|
<app-sidebar></app-sidebar>
|
||||||
|
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<h1>Lanes</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If up to now you've used mission stages like <code-inline>MISSIONSTAGE_1_STAGE</code-inline> you've been using the main lane. <br>
|
||||||
|
This is the default or "main" lane, however, if you wanted to mix things up and create a more dynamic mission, you can use additional lanes. <br>
|
||||||
|
Each lane can have its own set of mission stages, allowing for parallel or branching mission objectives.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="how-it-works">How To</h2>
|
||||||
|
<h3 id="define-lanes">Define Lanes</h3>
|
||||||
|
<p>
|
||||||
|
Lanes are defined implicitly in the naming convention of the <code-inline>MISSIONSTAGE_</code-inline>. <br>
|
||||||
|
A lane is identified by a single character (alpha [A-Z]), which gives you 26 unique lanes. <br>
|
||||||
|
For example, <code-inline>MISSIONSTAGE_A1_[name]</code-inline> would belong to lane A. <br>
|
||||||
|
<br>
|
||||||
|
If there is no lane character specified, the stage belongs to the main lane. <br>
|
||||||
|
<br>
|
||||||
|
The 26 lane limit is only relevant if you want 26 lanes at the same time. <br>
|
||||||
|
But splitting up lanes (eg. (1-4 and 10-15) ) you can easily have more than 26 stages throughout your entire mission. <br>
|
||||||
|
<br>
|
||||||
|
Lanes don't have to start at index 1. as in the example lanes can easily start at any index. <br>
|
||||||
|
How lanes are then activated and activate other lanes is described below in the Dependencies Explained part.
|
||||||
|
</p>
|
||||||
|
<h3 id="dependencies">Dependencies Explained</h3>
|
||||||
|
<p>
|
||||||
|
When we are talking about lanes it's important to make the distinction between the main lane and additional lanes. <br>
|
||||||
|
The main lane is not only the default, but it's also the central lane that other lanes depend on. <br>
|
||||||
|
|
||||||
|
<h4>Chapters</h4>
|
||||||
|
Another term that we have used in the code is `Chapter` which refers to a grouping of mission stages within a lane. <br>
|
||||||
|
These are implied and cannot be directly controlled or altered. <br>
|
||||||
|
A chapter is a set of `MissionStage`s that are directly connected. <br>
|
||||||
|
For <code-inline>MissionStages</code-inline> <code-inline>[ A1, A2, A3, A5, A6 ]</code-inline> there are 2 chapters: <code-inline>[ A1, A2, A3 ]</code-inline> and <code-inline>[ A5, A6 ]</code-inline>. <br>
|
||||||
|
Within a chapter the stages are only directly dependent on each other. Meaning A2 will start when A1 is completed. <br>
|
||||||
|
<br>
|
||||||
|
However, the chapter start is where the inter-lane dependencies come into play. <br>
|
||||||
|
These operate a little differently. <br>
|
||||||
|
<br>
|
||||||
|
In the Visual example you can see the Stages grouped into chapters by the red boxes. <br>
|
||||||
|
|
||||||
|
<h4>Side Lane Chapter start</h4>
|
||||||
|
<p>
|
||||||
|
Side lane chapters are started when all are true:
|
||||||
|
<ul>
|
||||||
|
<li>The main lane is at or above the current stage lane number.</li>
|
||||||
|
<li>All previous chapters in the same side lane have been completed.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>Main Lane Chapter start</h4>
|
||||||
|
<p>
|
||||||
|
Main lane chapters are started when all are true:
|
||||||
|
<ul>
|
||||||
|
<li>The previous chapter in the main lane has been completed.</li>
|
||||||
|
<li>All side lanes are at or above the current stage lane number or the side lane has no additional stages.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 id="visual">Visual</h2>
|
||||||
|
<p>
|
||||||
|
The image below illustrates how different lanes can be used within a mission. <br>
|
||||||
|
In the example you can see how the dependency between the main lane and side lanes works. <br>
|
||||||
|
An arrow means that the completion of one stage is required before the next stage can begin.
|
||||||
|
</p>
|
||||||
|
<img src="/img/lanes.png" alt="Example of mission lanes">
|
||||||
|
|
||||||
|
<h2 id="mermaid">Mermaid</h2>
|
||||||
|
<p>
|
||||||
|
Spearhead automatically exports a spearhead diagram to the logs on starting a mission. <br>
|
||||||
|
This can be handy to visualize the logic and lanes you've created and verify your mission flow will be the way you want it. <br>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="mermaid-log-example">Log Example</h3>
|
||||||
|
<code-block>
|
||||||
|
2026-09-01 17:53:13.213 INFO SCRIPTING (Main): [Spearhead][StageManager] ========== STAGE FLOW DIAGRAM ==========
|
||||||
|
graph TD
|
||||||
|
default_8["MAIN-2 (S8)"]
|
||||||
|
b_5["West Valley (S5)"]
|
||||||
|
default_1["MAIN-1 (S1)"]
|
||||||
|
a_5["WEST-2 (S5)"]
|
||||||
|
default_4["MAIN-2 (S4)"]
|
||||||
|
default_10["MAIN-1 (S10)"]
|
||||||
|
default_6["MAIN-2 (S6)"]
|
||||||
|
a_4["WEST-1 (S4)"]
|
||||||
|
default_3["MAIN-1 (S3)"]
|
||||||
|
default_9["MAIN-3 (S9)"]
|
||||||
|
a_7["WEST-2 (S7)"]
|
||||||
|
a_3["WEST (S3)"]
|
||||||
|
default_5["MAIN-3 (S5)"]
|
||||||
|
b_6["West Valley-1 (S6)"]
|
||||||
|
default_61["CAP-Maykop (S61)"]
|
||||||
|
default_7["MAIN-1 (S7)"]
|
||||||
|
default_0["START (S0)"]
|
||||||
|
a_6["WEST-3 (S6)"]
|
||||||
|
default_60["CAP-Nalchik (S60)"]
|
||||||
|
default_2["MAIN (S2)"]
|
||||||
|
a_2["WEST-1 (S2)"]
|
||||||
|
a_2 --> a_3
|
||||||
|
a_3 --> a_4
|
||||||
|
a_4 --> a_5
|
||||||
|
a_5 --> a_6
|
||||||
|
a_6 --> a_7
|
||||||
|
default_0 --> default_1
|
||||||
|
default_1 --> default_2
|
||||||
|
default_2 --> default_3
|
||||||
|
default_3 --> default_4
|
||||||
|
default_4 --> default_5
|
||||||
|
default_5 --> default_6
|
||||||
|
default_6 --> default_7
|
||||||
|
default_7 --> default_8
|
||||||
|
default_8 --> default_9
|
||||||
|
default_9 --> default_10
|
||||||
|
default_10 -->|chapter| default_60
|
||||||
|
default_60 --> default_61
|
||||||
|
b_5 --> b_6
|
||||||
|
default_2 -->|unlock| a_2
|
||||||
|
default_5 -->|unlock| b_5
|
||||||
|
a_7 -->|gate| default_60
|
||||||
|
b_6 -->|gate| default_60
|
||||||
|
========== END DIAGRAM ==========
|
||||||
|
|
||||||
|
</code-block>
|
||||||
|
|
||||||
|
<h3 id="mermaid-diagram-example">Diagram Example</h3>
|
||||||
|
<p>
|
||||||
|
If you want to then copy and paste everything between the <br>
|
||||||
|
<code-inline>========== STAGE FLOW DIAGRAM ==========</code-inline> and <br>
|
||||||
|
<code-inline>========== END DIAGRAM ==========</code-inline> <br>
|
||||||
|
you will be able to go to <a href="https://mermaid.live/">https://mermaid.live/</a> and paste the diagram code into the editor. <br>
|
||||||
|
|
||||||
|
It would look like this:
|
||||||
|
</p>
|
||||||
|
<img src="/img/mermaid-preview.png" alt="Example of mermaid diagram">
|
||||||
|
|
||||||
|
<h2 id="lane-limitations">Limitations</h2>
|
||||||
|
<h3 class="lane-limitations-cap">CAP</h3>
|
||||||
|
<p>
|
||||||
|
Due to the CAP naming conventions, we've opted to currently have CAP only react with the main lane. <br>
|
||||||
|
This might mean a bit less flexibility when using lanes for major mission logic, however, it's a limitation we've accepted for now. <br>
|
||||||
|
In the future we might be able to extend CAP's interaction with other lanes as well. <br>
|
||||||
|
Currently <a href="https://git.dutchie031.com/Spearhead/spearhead/issues/47">this issue</a> can be tracked here. <br>
|
||||||
|
|
||||||
|
This does not mean having both CAP and side-lanes is out of the question, since you can still have that side-lanes scouted by CAP aircraft.
|
||||||
|
It just needs a bit more pre-planning on where and how those CAP flights fly. <br>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>© 2025 Spearhead Project</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="dark">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Spearhead Missions</title>
|
||||||
|
<link rel="stylesheet" href="/style/style.css">
|
||||||
|
<script src="/js/site.js"></script>
|
||||||
|
<style>
|
||||||
|
.side-nav a.active {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #4fc3f7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="module" src="/js/components.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<app-header></app-header>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div class="reference-container">
|
||||||
|
<app-sidebar></app-sidebar>
|
||||||
|
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<h1>Advanced Tutorial: Missions</h1>
|
||||||
|
<div id="strike-region">
|
||||||
|
<h2 id="strike">Strike</h2>
|
||||||
|
<p>
|
||||||
|
Naming: <code-inline>MISSION_STRIKE_[Name]</code-inline> <br>
|
||||||
|
</p>
|
||||||
|
<h3 id="specific_targets">Specific Targets</h3>
|
||||||
|
<p>TBD</p>
|
||||||
|
|
||||||
|
<h3 id="scenery_targets">Scenery Targets</h3>
|
||||||
|
<p>
|
||||||
|
Bridges, buildings or other type of targets that are baked into the map are awesome targets.<br>
|
||||||
|
We wanted to make sure they were easily integrated in Spearhead.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<note-box type="info" title="Implementation details">
|
||||||
|
Spearhead detects scenery targets by the trigger zone name. A zone named
|
||||||
|
<strong>scenerytarget_[freeform]</strong> or
|
||||||
|
<strong>scenerytargets</strong> (case-insensitive) will be scanned and any scenery objects that
|
||||||
|
has the attribute "Buildings" inside the zone
|
||||||
|
will be added as mission scenery targets.
|
||||||
|
</note-box>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Scenery targets are treated like mission targets: they are included in the mission completion
|
||||||
|
calculation
|
||||||
|
(their `IsAlive()` state is checked) and their state is persisted/updated when missions spawn or
|
||||||
|
resume.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<note-box type="warning" title="Changing IDs">
|
||||||
|
Due to Object IDs changing when maps are updated and edited the "Assign as..." functionality
|
||||||
|
does not work for scenery targets.
|
||||||
|
Hence the need to use trigger zones to define them.
|
||||||
|
</note-box>
|
||||||
|
|
||||||
|
|
||||||
|
<p>Here's two images showing how a bridge is added to a mission.</p>
|
||||||
|
<img src="/img/bridge.png" style="max-height: 35remrem;" />
|
||||||
|
</div>
|
||||||
|
<div id="cas-region">
|
||||||
|
<h2 id="cas">CAS</h2>
|
||||||
|
<p>
|
||||||
|
Naming: <code-inline>MISSION_CAS_[Name]</code-inline> <br>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="BattleManager">Battle Manager</h3>
|
||||||
|
<p>TBD</p>
|
||||||
|
</div>
|
||||||
|
<div id="dead-region">
|
||||||
|
<h2 id="dead">DEAD</h2>
|
||||||
|
<p>
|
||||||
|
Naming: <code-inline>MISSION_DEAD_[Name]</code-inline> <br>
|
||||||
|
Naming: <code-inline>MISSION_SAM_[Name]</code-inline> <br>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="sam-vs-dead">SAM vs DEAD</h3>
|
||||||
|
<p>TBD</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="bai-region">
|
||||||
|
<h2 id="bai">BAI</h2>
|
||||||
|
<p>
|
||||||
|
Naming: <code-inline>MISSION_BAI_[Name]</code-inline> <br>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="deepstrike-region">
|
||||||
|
<h2 id="deepstrike">DEEPSTRIKE</h2>
|
||||||
|
<p>
|
||||||
|
Naming: <code-inline>MISSION_DEEPSTRIKE_[Name]</code-inline> <br>
|
||||||
|
<br>
|
||||||
|
When you, as a mission editor, want to create a mission that's further away from the current front-line you can use the Deep Strike mission type. <br>
|
||||||
|
This allows you to plan missions that require more strategic positioning and timing. <br>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="activation">Activation</h3>
|
||||||
|
<p>
|
||||||
|
Deep Strike missions are activated when the Stage in which they are located becomes "PreActive" or "Active". <br>
|
||||||
|
"PreActive" is when either the current active StageNumber is a certain number of stages away OR the stage has "CAP" airbases active . <br>
|
||||||
|
<code-inline>SpearheadConfig.StageConfig.preactivateStage</code-inline> changes the configuration. <br>
|
||||||
|
<br>
|
||||||
|
For example: <br>
|
||||||
|
If the current active stage is <code-inline>MissionStage_2_[name]</code-inline> and preActivate config is set to 2. <br>
|
||||||
|
Then both <code-inline>MissionStage_3_[name]</code-inline> and <code-inline>MissionStage_4_[name]</code-inline> would become "PreActive".
|
||||||
|
</p>
|
||||||
|
<h3 id="primary-vs-secondary">Make Primary</h3>
|
||||||
|
<p>
|
||||||
|
By default DEEPSTRIKE missions are "Secondary" missions when spawned as a pre-activated stage.<br>
|
||||||
|
Then when the stage they are in is "Active", the mission becomes a "Primary" mission. <br>
|
||||||
|
<br>
|
||||||
|
If you want to make it a "Primary" mission from the start, you can overwrite it.<br>
|
||||||
|
Click the mission's trigger zone. Add a property named <code-inline>primary</code-inline> and set it to <code-inline>true</code-inline>.
|
||||||
|
This will make the mission "Primary" from the start. <br>
|
||||||
|
<br>
|
||||||
|
If you want it to "Secondary" even when its stage is the active stage, you can set "primary" to false. <br>
|
||||||
|
<br>
|
||||||
|
If you want to have it be "Secondary" when its stage is "Pre-Active" and "primary" when its stage is "Active" you don't have to do anything.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="future-additions">Future</h3>
|
||||||
|
<p>
|
||||||
|
In the future we're thinking it would be cool to make the Deep Strike missions
|
||||||
|
be more integrated with the CAP airbases and/or logistics. <br>
|
||||||
|
Eg. Completing a DEEPSTRIKE mission would disable CAP for X amount of minutes after completion. <br>
|
||||||
|
This still needs to be refined, but we've already have ideas.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>© 2025 Spearhead Project</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -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/>
|
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/>
|
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>
|
<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/>
|
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/>
|
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/>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
--color-bg-hover: rgba(120, 140, 255, 0.12); /* More visible hover */
|
--color-bg-hover: rgba(120, 140, 255, 0.12); /* More visible hover */
|
||||||
--color-bg-note-dark: rgba(40, 35, 30, 0.85); /* Slightly darker note box */
|
--color-bg-note-dark: rgba(40, 35, 30, 0.85); /* Slightly darker note box */
|
||||||
|
|
||||||
--color-text-primary: #a5a5a5; /* Brighter text */
|
--color-text-primary: #b4b3b3; /* Brighter text */
|
||||||
--color-text-secondary: #bfc8d8; /* Lighter secondary text */
|
--color-text-secondary: #bfc8d8; /* Lighter secondary text */
|
||||||
--color-text-accent: #7ecbff; /* Brighter accent */
|
--color-text-accent: #7ecbff; /* Brighter accent */
|
||||||
--color-text-light: #ffffff;
|
--color-text-light: #ffffff;
|
||||||
@@ -262,7 +262,7 @@ header nav a:hover::after {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
top: 100%; /* Position below the dropdown */
|
top: 100%; /* Position below the dropdown */
|
||||||
left: 0; /* Align left edge */
|
left: 0; /* Align left edge */
|
||||||
min-width: 160px;
|
min-width: 240px;
|
||||||
|
|
||||||
background-color: var(--color-bg-secondary);
|
background-color: var(--color-bg-secondary);
|
||||||
box-shadow: var(--shadow-medium);
|
box-shadow: var(--shadow-medium);
|
||||||
@@ -336,8 +336,8 @@ main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.side-nav {
|
.side-nav {
|
||||||
width: 200px;
|
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
|
max-width: 200px;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
name: PR Prerequisites
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
|
||||||
|
env:
|
||||||
|
LUA_VERSION: "5.1"
|
||||||
|
LUA_LANGUAGE_SERVER_VERSION: "3.19.1"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bundles_without_error:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Bundles Without Errors
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Lua
|
||||||
|
uses: leafo/gh-actions-lua@v13
|
||||||
|
with:
|
||||||
|
luaVersion: ${{ env.LUA_VERSION }}
|
||||||
|
|
||||||
|
- name: Compile Spearhead
|
||||||
|
uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-actions/bundle-script@bundle-script/v1
|
||||||
|
with:
|
||||||
|
source-root: ./src
|
||||||
|
output-file: ./output/spearhead.lua
|
||||||
|
|
||||||
|
- name: Verify Compiled Lua Syntax
|
||||||
|
run: lua -e "assert(loadfile('./output/spearhead.lua'))"
|
||||||
|
|
||||||
|
- name: Set up Luarocks
|
||||||
|
uses: leafo/gh-actions-luarocks@v4
|
||||||
|
|
||||||
|
- name: Install luacheck
|
||||||
|
run: luarocks install luacheck
|
||||||
|
|
||||||
|
# "lfs",
|
||||||
|
# "setmetatable", "getmetatable", "table", "string", "math", "pairs", "ipairs",
|
||||||
|
# "require", "type", "tostring", "io", "error", "pcall", "tonumber",
|
||||||
|
# "loadstring",
|
||||||
|
|
||||||
|
- name: Create LuaCheck Config
|
||||||
|
run: |
|
||||||
|
cat << EOF > .luacheckrc
|
||||||
|
|
||||||
|
stds.spearhead_lib = {
|
||||||
|
globals = {
|
||||||
|
"SpearheadConfig", "SpearheadAPI", "GlobalCapCallBacks",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stds.dcs_lib = {
|
||||||
|
globals = {
|
||||||
|
"env", "net", "trigger", "timer", "Object", "land", "coord", "coalition",
|
||||||
|
"Unit", "Group", "Airbase", "StaticObject", "SceneryObject", "world",
|
||||||
|
"country", "missionCommands", "Controller", "Weapon", "atmosphere", "country",
|
||||||
|
"lfs", "AI"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std = "lua51c+spearhead_lib+dcs_lib"
|
||||||
|
self = false
|
||||||
|
|
||||||
|
max_line_length = false
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Show LuaCheck Config
|
||||||
|
run: cat .luacheckrc
|
||||||
|
|
||||||
|
- name: Lint Compiled Output
|
||||||
|
run: luacheck ./output/spearhead.lua
|
||||||
|
|
||||||
|
lua_check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Lua Check
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Lua
|
||||||
|
uses: leafo/gh-actions-lua@v13
|
||||||
|
with:
|
||||||
|
luaVersion: ${{ env.LUA_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Dcs Plugins
|
||||||
|
uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-actions/install-lua-addon@install-lua-addon/v1
|
||||||
|
with:
|
||||||
|
destination-path: "./.vscode/lua-addons" ##same as local so .luarc.json can be the same
|
||||||
|
|
||||||
|
- name: Verify Dcs Plugins Installation
|
||||||
|
run: ls -la ./.vscode/lua-addons
|
||||||
|
|
||||||
|
- name: Install lua-language-server
|
||||||
|
run: |
|
||||||
|
mkdir -p ./luals
|
||||||
|
# For Ubuntu/Debian
|
||||||
|
wget https://github.com/LuaLS/lua-language-server/releases/download/${{ env.LUA_LANGUAGE_SERVER_VERSION }}/lua-language-server-${{ env.LUA_LANGUAGE_SERVER_VERSION }}-linux-x64.tar.gz
|
||||||
|
tar -xzf lua-language-server-*.tar.gz -C ./luals
|
||||||
|
rm lua-language-server-*.tar.gz
|
||||||
|
|
||||||
|
- name: Verify Lua Language Server Installation
|
||||||
|
run: ./luals/bin/lua-language-server --version
|
||||||
|
|
||||||
|
- name: Update Json
|
||||||
|
run: |
|
||||||
|
FULL_PATH=$(realpath ./.vscode/lua-addons)
|
||||||
|
echo "full-path: $FULL_PATH"
|
||||||
|
ls -R $FULL_PATH
|
||||||
|
|
||||||
|
echo "Updating .luarc.json with full path"
|
||||||
|
jq --arg full_path "$FULL_PATH" '.workspace.library[0] = $full_path' ./.luarc.json > ./.luarc.tmp.json
|
||||||
|
mv ./.luarc.tmp.json ./.luarc.json
|
||||||
|
|
||||||
|
- name: Show Config
|
||||||
|
run: cat ./.luarc.json
|
||||||
|
|
||||||
|
- name: Run Lua Language Server
|
||||||
|
run: |
|
||||||
|
CONFIG_FULL_PATH=$(realpath ./.luarc.json)
|
||||||
|
echo "Config full path: $CONFIG_FULL_PATH"
|
||||||
|
|
||||||
|
|
||||||
|
./luals/bin/lua-language-server \
|
||||||
|
--check ./src \
|
||||||
|
--checklevel=Warning \
|
||||||
|
--loglevel=trace \
|
||||||
|
--logpath="./luals/logs" \
|
||||||
|
--configpath="$CONFIG_FULL_PATH" \
|
||||||
|
# --develop
|
||||||
|
|
||||||
|
- name: Show Log
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
ls -R ./luals/logs
|
||||||
|
|
||||||
|
cat ./luals/logs/* || true
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
name: Publish Beta Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
paths:
|
||||||
|
- src/**
|
||||||
|
- .gitea/workflows/release-beta.yml
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Read Version from version.txt
|
||||||
|
id: read_version
|
||||||
|
run: |
|
||||||
|
VERSION=beta
|
||||||
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Version: $VERSION"
|
||||||
|
|
||||||
|
- name: Extract Release Notes
|
||||||
|
id: release_notes
|
||||||
|
run: |
|
||||||
|
VERSION=beta
|
||||||
|
RELEASE_NOTES=$(awk -v ver="Unreleased" '
|
||||||
|
/^## \[/ {
|
||||||
|
if (found) exit
|
||||||
|
if (index($0, "[" ver "]") > 0) found = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
found { print }
|
||||||
|
' RELEASENOTES.md)
|
||||||
|
|
||||||
|
# Write multi-line output properly
|
||||||
|
echo "body<<EOF" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "$RELEASE_NOTES" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Compile Spearhead
|
||||||
|
uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-actions/bundle-script@bundle-script/v1
|
||||||
|
with:
|
||||||
|
source-root: ./src
|
||||||
|
output-file: ./output/spearhead.lua
|
||||||
|
|
||||||
|
- name: Remove existing tag if it exists
|
||||||
|
id: check_tag
|
||||||
|
run: |
|
||||||
|
TAG="${{ steps.read_version.outputs.tag }}"
|
||||||
|
if git tag | grep -q "^$TAG$"; then
|
||||||
|
echo "Tag exists. Removing it..."
|
||||||
|
git push origin :refs/tags/"$TAG" || true
|
||||||
|
git tag -d "$TAG" || true
|
||||||
|
fi
|
||||||
|
echo "Ready to create fresh tag: $TAG"
|
||||||
|
|
||||||
|
- name: Apply Tag to Current Commit
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions"
|
||||||
|
git config user.email "github-actions@github.com"
|
||||||
|
git tag ${{ steps.read_version.outputs.tag }}
|
||||||
|
git push origin ${{ steps.read_version.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Copy Release to Versioned File
|
||||||
|
run: |
|
||||||
|
cp ./output/spearhead.lua ./output/spearhead.${{ steps.read_version.outputs.tag }}.lua
|
||||||
|
echo "Versioned file created: spearhead.${{ steps.read_version.outputs.tag }}.lua"
|
||||||
|
|
||||||
|
- name: Create Release and Upload Assets
|
||||||
|
uses: akkuman/gitea-release-action@v1
|
||||||
|
with:
|
||||||
|
tag_name: ${{ steps.read_version.outputs.tag }}
|
||||||
|
name: Release ${{ steps.read_version.outputs.tag }}
|
||||||
|
body: ${{ steps.release_notes.outputs.body }}
|
||||||
|
prerelease: true
|
||||||
|
files: |-
|
||||||
|
./output/spearhead.lua
|
||||||
|
./output/spearhead.${{ steps.read_version.outputs.tag }}.lua
|
||||||
|
#token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: '--experimental-fetch'
|
||||||
|
|
||||||
|
- name: Announce Release
|
||||||
|
uses: tsickert/discord-webhook@v7.0.0
|
||||||
|
with:
|
||||||
|
webhook-url: ${{ secrets.WEBHOOK_URL }}
|
||||||
|
content: "New Beta Release"
|
||||||
|
username: "Spearhead Release Bot"
|
||||||
|
#TODO: avatar-url spearhead avatar
|
||||||
|
thread-id: 1538520488442331227
|
||||||
|
embed-title: "Spearhead Release Beta"
|
||||||
|
embed-color: 3093247
|
||||||
|
embed-description:
|
||||||
|
${{ steps.release_notes.outputs.body }}
|
||||||
|
embed-url:
|
||||||
|
https://git.dutchie031.com/Spearhead/spearhead/releases/tag/${{ steps.read_version.outputs.tag }}
|
||||||
|
|
||||||
@@ -30,26 +30,22 @@ jobs:
|
|||||||
id: release_notes
|
id: release_notes
|
||||||
run: |
|
run: |
|
||||||
VERSION=$(cat version.txt)
|
VERSION=$(cat version.txt)
|
||||||
# Extract release notes for this version
|
|
||||||
# Find the section for this version and extract until the next version section
|
|
||||||
RELEASE_NOTES=$(awk -v ver="$VERSION" '
|
RELEASE_NOTES=$(awk -v ver="$VERSION" '
|
||||||
/^## \[/ {
|
/^## \[/ {
|
||||||
if (found) exit
|
if (found) exit
|
||||||
if ($0 ~ "\\[" ver "\\]") found=1
|
if (index($0, "[" ver "]") > 0) found = 1
|
||||||
else if (found) exit
|
|
||||||
next
|
next
|
||||||
}
|
}
|
||||||
found && NF { print }
|
found { print }
|
||||||
' RELEASENOTES.md | sed -e :a -e '/^$/N;$!ba' -e 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
' RELEASENOTES.md)
|
||||||
|
|
||||||
# Escape newlines for GitHub output
|
# Write multi-line output properly
|
||||||
RELEASE_NOTES="${RELEASE_NOTES//'%'/'%25'}"
|
echo "body<<EOF" >> "$GITHUB_OUTPUT"
|
||||||
RELEASE_NOTES="${RELEASE_NOTES//$'\n'/'%0A'}"
|
echo "$RELEASE_NOTES" >> "$GITHUB_OUTPUT"
|
||||||
RELEASE_NOTES="${RELEASE_NOTES//$'\r'/'%0D'}"
|
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||||
echo "body=$RELEASE_NOTES" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Compile Spearhead
|
- name: Compile Spearhead
|
||||||
uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-action@action/v0
|
uses: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools/github-actions/bundle-script@bundle-script/v1
|
||||||
with:
|
with:
|
||||||
source-root: ./src
|
source-root: ./src
|
||||||
output-file: ./output/spearhead.lua
|
output-file: ./output/spearhead.lua
|
||||||
@@ -88,3 +84,18 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
NODE_OPTIONS: '--experimental-fetch'
|
NODE_OPTIONS: '--experimental-fetch'
|
||||||
|
|
||||||
|
- name: Announce Release
|
||||||
|
uses: tsickert/discord-webhook@v7.0.0
|
||||||
|
with:
|
||||||
|
webhook-url: ${{ secrets.WEBHOOK_URL }}
|
||||||
|
content: "New Beta Release"
|
||||||
|
username: "Spearhead Release Bot"
|
||||||
|
#TODO: avatar-url spearhead avatar
|
||||||
|
thread-id: 1538520596802175037
|
||||||
|
embed-title: "Spearhead Release"
|
||||||
|
embed-color: 3093247
|
||||||
|
embed-description:
|
||||||
|
${{ steps.release_notes.outputs.body }}
|
||||||
|
embed-url:
|
||||||
|
https://git.dutchie031.com/Spearhead/spearhead/releases/tag/${{ steps.read_version.outputs.tag }}
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,12 @@
|
|||||||
name: Deploy static content to Pages
|
name: Deploy static content to Pages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
release:
|
push:
|
||||||
types: [published]
|
branches:
|
||||||
|
- main
|
||||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
paths:
|
||||||
permissions:
|
- '_docs/**'
|
||||||
contents: read
|
|
||||||
pages: write
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
name: Update Docs
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
paths:
|
||||||
|
- '.docs/**'
|
||||||
|
|
||||||
|
env:
|
||||||
|
DOCKER_REGISTRY: registry.dutchie031.net
|
||||||
|
DOCKER_IMAGE: spearhead-docs
|
||||||
|
DOCKER_TAG: ${{ github.ref_name == 'main' && 'latest' || 'beta' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
## Run replacements and cleanups on the docs
|
||||||
|
prepare-docs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
## --break-system-packages is used to avoid conflicts with system-installed packages on Ubuntu runners.
|
||||||
|
- name: Install Python Pygments
|
||||||
|
run: |
|
||||||
|
python3 -m pip install --upgrade pip --break-system-packages --no-cache-dir --ignore-installed
|
||||||
|
pip install pygments --break-system-packages
|
||||||
|
|
||||||
|
- name: Highlight API code and update HTML
|
||||||
|
run: |
|
||||||
|
pygmentize -f html -l lua -O noclasses,style=monokai src/classes/api/SpearheadApiDoc.lua > .docs/web/pages/temp_api_code.html
|
||||||
|
# Insert the highlighted code into the placeholder in spearheadapi.html
|
||||||
|
sed -i '/@@API_CODE@@/r .docs/web/pages/temp_api_code.html' .docs/web/pages/spearheadapi.html
|
||||||
|
sed -i '/@@API_CODE@@/d' .docs/web/pages/spearheadapi.html
|
||||||
|
rm .docs/web/pages/temp_api_code.html
|
||||||
|
|
||||||
|
- name: Highlight Config code and update HTML
|
||||||
|
run: |
|
||||||
|
pygmentize -f html -l lua -O noclasses,style=monokai ./config.lua > .docs/web/pages/temp_config_code.html
|
||||||
|
# Insert the highlighted code into the placeholder in spearheadapi.html
|
||||||
|
sed -i '/@@CONFIG_CODE@@/r .docs/web/pages/temp_config_code.html' .docs/web/pages/reference.html
|
||||||
|
sed -i '/@@CONFIG_CODE@@/d' .docs/web/pages/reference.html
|
||||||
|
rm .docs/web/pages/temp_config_code.html
|
||||||
|
|
||||||
|
- name: List files in .docs directory
|
||||||
|
run: |
|
||||||
|
echo "Listing files in .docs directory:"
|
||||||
|
ls -R .docs
|
||||||
|
|
||||||
|
- name: Upload spearhead.lua artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: spearhead-docs
|
||||||
|
path: |
|
||||||
|
./.docs/Dockerfile
|
||||||
|
./.docs/web/**/*
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
build-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: prepare-docs
|
||||||
|
steps:
|
||||||
|
|
||||||
|
# Download prepared pages from the previous job
|
||||||
|
- name: Download spearhead-docs artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: spearhead-docs
|
||||||
|
path: ./spearhead-docs
|
||||||
|
|
||||||
|
- name: DEBUG ls
|
||||||
|
run: |
|
||||||
|
ls -R ./spearhead-docs
|
||||||
|
|
||||||
|
- name: Build Docker image for docs
|
||||||
|
run: |
|
||||||
|
docker build -t ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }} ./spearhead-docs
|
||||||
|
|
||||||
|
- name: Push Docker image
|
||||||
|
run: |
|
||||||
|
docker push ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }}
|
||||||
|
|
||||||
|
|
||||||
|
deploy-container:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build-image
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Helm
|
||||||
|
uses: azure/setup-helm@v5
|
||||||
|
with:
|
||||||
|
version: 'latest'
|
||||||
|
id: install
|
||||||
|
|
||||||
|
- name: Set up Kubeconfig
|
||||||
|
run: |
|
||||||
|
mkdir -p $HOME/.kube
|
||||||
|
echo "${{ secrets.KUBECONFIG_CONTENT }}" > $HOME/.kube/config
|
||||||
|
chmod 600 $HOME/.kube/config
|
||||||
|
cat $HOME/.kube/config
|
||||||
|
|
||||||
|
- name: Run Helm upgrade/install spearhead-docs (main)
|
||||||
|
if: github.ref_name == 'main'
|
||||||
|
run: |
|
||||||
|
helm upgrade --install spearhead-docs .helm \
|
||||||
|
--namespace spearhead-docs \
|
||||||
|
--create-namespace \
|
||||||
|
-f .helm/values.yaml \
|
||||||
|
--set image.repository=${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }} \
|
||||||
|
--set image.tag=${{ env.DOCKER_TAG }} \
|
||||||
|
--wait
|
||||||
|
|
||||||
|
- name: Run Helm upgrade/install spearhead-docs (develop)
|
||||||
|
if: github.ref_name == 'develop'
|
||||||
|
run: |
|
||||||
|
helm upgrade --install spearhead-docs-beta .helm \
|
||||||
|
--namespace spearhead-docs \
|
||||||
|
--create-namespace \
|
||||||
|
-f .helm/values.yaml \
|
||||||
|
-f .helm/values.beta.yaml \
|
||||||
|
--set image.repository=${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }} \
|
||||||
|
--set image.tag=${{ env.DOCKER_TAG }} \
|
||||||
|
--wait
|
||||||
|
|
||||||
|
|
||||||
|
# - name: 'Deploy'
|
||||||
|
# uses: deliverybot/helm@v1
|
||||||
|
# with:
|
||||||
|
# release: spearhead-docs
|
||||||
|
# namespace: spearhead-docs
|
||||||
|
# chart: '.helm'
|
||||||
|
# value-files: >-
|
||||||
|
# [
|
||||||
|
# ".helm/values.yaml"
|
||||||
|
# ]
|
||||||
|
# env:
|
||||||
|
# KUBECONFIG_FILE: ${{ secrets.KUBECONFIG_CONTENT }}
|
||||||
@@ -3,4 +3,6 @@
|
|||||||
|
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
|
.vscode/lua-addons
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
|
**\settings.json
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
name: spearhead-docs
|
||||||
|
description: Helm chart for deploying Spearhead documentation
|
||||||
|
type: application
|
||||||
|
version: 1.0.0
|
||||||
|
appVersion: "1.0"
|
||||||
|
keywords:
|
||||||
|
- spearhead
|
||||||
|
- documentation
|
||||||
|
- dcs
|
||||||
|
home: https://git.dutchie031.com/Spearhead/spearhead
|
||||||
|
sources:
|
||||||
|
- https://git.dutchie031.com/Spearhead/Spearhead
|
||||||
|
maintainers:
|
||||||
|
- name: Spearhead Team
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.app }}
|
||||||
|
labels:
|
||||||
|
app: {{ .Values.app }}
|
||||||
|
namespace: spearhead-docs
|
||||||
|
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ .Values.app }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ .Values.app }}
|
||||||
|
annotations:
|
||||||
|
deployment-timestamp: "{{ now.Unix }}"
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 65534
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: {{ .Values.app }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: {{ .Values.ports.containerPort }}
|
||||||
|
volumeMounts:
|
||||||
|
- name: cache
|
||||||
|
mountPath: /var/cache/nginx
|
||||||
|
- name: run
|
||||||
|
mountPath: /var/run
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
volumes:
|
||||||
|
- name: cache
|
||||||
|
emptyDir: {}
|
||||||
|
- name: run
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.app }}
|
||||||
|
namespace: spearhead-docs
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: {{ .Values.app }}
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ .Values.ports.servicePort }}
|
||||||
|
targetPort: {{ .Values.ports.containerPort }}
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Values.app }}-ingress"
|
||||||
|
namespace: spearhead-docs
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/tls-acme: "true"
|
||||||
|
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- host: {{ . }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: {{ $.Values.app }}
|
||||||
|
port:
|
||||||
|
number: {{ $.Values.ports.servicePort }}
|
||||||
|
{{- end }}
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
secretName: "{{ .Values.app }}-docs-tls"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
app: spearhead-docs-beta
|
||||||
|
|
||||||
|
image:
|
||||||
|
tag: "beta"
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
hosts:
|
||||||
|
- beta.spearhead.rocks
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
app: spearhead-docs
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: registry.dutchie031.net/spearhead-docs
|
||||||
|
tag: latest
|
||||||
|
|
||||||
|
ports:
|
||||||
|
servicePort: 80
|
||||||
|
containerPort: 80
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
hosts:
|
||||||
|
- spearhead.dutchie031.com
|
||||||
|
- spearhead.rocks
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
||||||
|
"runtime": {
|
||||||
|
"version": "Lua 5.1"
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"checkThirdParty": false,
|
||||||
|
"maxPreload": 1600,
|
||||||
|
"preloadFileSize": 1000,
|
||||||
|
"ignoreDir": [ "config.lua" ]
|
||||||
|
},
|
||||||
|
"diagnostics": {
|
||||||
|
"enable": true,
|
||||||
|
"globals": [
|
||||||
|
"lfs"
|
||||||
|
],
|
||||||
|
"groupSeverity": {
|
||||||
|
"duplicate": "Warning",
|
||||||
|
"global":"Warning",
|
||||||
|
"luadoc": "Warning",
|
||||||
|
"redefined": "Warning",
|
||||||
|
"strong": "Warning",
|
||||||
|
"type-check": "Warning",
|
||||||
|
"unbalanced" : "Warning",
|
||||||
|
"ambiguity": "Warning",
|
||||||
|
"strict": "Warning",
|
||||||
|
"unused": "Warning"
|
||||||
|
},
|
||||||
|
"groupFileStatus": {
|
||||||
|
"ambiguity": "Any",
|
||||||
|
"await": "Any",
|
||||||
|
"duplicate": "Any",
|
||||||
|
"global": "Any",
|
||||||
|
"luadoc": "Any",
|
||||||
|
"redefined": "Any",
|
||||||
|
"strict": "Any",
|
||||||
|
"type-check": "Any",
|
||||||
|
"unbalanced": "Any",
|
||||||
|
"unused": "Any",
|
||||||
|
"strong": "Any"
|
||||||
|
},
|
||||||
|
"ignoredFiles": "Opened",
|
||||||
|
"libraryFiles": "Opened"
|
||||||
|
},
|
||||||
|
"hint": {
|
||||||
|
"enable": false
|
||||||
|
},
|
||||||
|
"completion": {
|
||||||
|
"enable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"sumneko.lua"
|
"dutchie031.dutchies-dcs-scripting-tools",
|
||||||
|
"sumneko.lua",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"cSpell.words": [
|
|
||||||
"farp",
|
|
||||||
"Sams",
|
|
||||||
"SEAD"
|
|
||||||
],
|
|
||||||
"Lua.diagnostics.globals": [
|
|
||||||
"lfs"
|
|
||||||
],
|
|
||||||
"maptz.regionfolder": {
|
|
||||||
"[lua]" : {
|
|
||||||
"foldEnd": "--endregion",
|
|
||||||
"foldEndRegex": "[\\s]*--endregion",
|
|
||||||
"foldStart": "--region [NAME]",
|
|
||||||
"foldStartRegex": "[\\s]*--region[\\s]*(.*)",
|
|
||||||
"disableFolding": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"livePreview.serverRoot": "/_docs",
|
|
||||||
"Lua.workspace.library": [
|
|
||||||
"/Users/ex61wi/Developer/personal/DcsMissionScriptingTools/dutchies-dcs-scripting-tools/lua-addons",
|
|
||||||
"c:\\Users\\Tim\\.vscode\\extensions\\dutchie031.dutchies-dcs-scripting-tools-0.0.3\\lua-addons"
|
|
||||||
],
|
|
||||||
"dutchies-dcs-scripting-tools.compileAt": "onSave",
|
|
||||||
}
|
|
||||||
@@ -1,53 +1,43 @@
|
|||||||
# Spearhead
|
# Spearhead
|
||||||
|
|
||||||
|
Hello and welcome to Spearhead!
|
||||||
|
|
||||||
## Contributing.
|
If you're a developer that wants to contribute, look at the code or understand what we're doing, feel free to read on.
|
||||||
|
|
||||||
I'm always happy to see contributions! <br/>
|
If you're a mission maker that wants to see the documentation and build you mission with this framework. <br>
|
||||||
Especially if you've found an issue, annoyance or feature that you know how to solve, fix or create! <br/>
|
It's best to go to our fully fledged documentation website:
|
||||||
|
- Stable/Main: https://spearhead.rocks
|
||||||
|
- Beta/Develop: https://beta.spearhead.rocks
|
||||||
|
|
||||||
### Keep it small.
|
## Project Structure
|
||||||
|
|
||||||
Keeping your contributions small and to the point helps both me and yourself. <br/>
|
The Spearhead project is structured in a lot of separate files. <br>
|
||||||
It'll help the PR review and the speed of improvements. <br/>
|
While Lua isn't specifically created for it, most of it has been written with the standards of OOP in mind. <br>
|
||||||
If you have a big feature in mind? Go for it! <br/>
|
Classes, Objects and helper methods.
|
||||||
Does it require an entire refactor of parts or all of the program? Maybe make sure to do step 0 first.
|
|
||||||
|
|
||||||
### Steps (for first timers)
|
You're find in the `/src` folder all the files. <br>
|
||||||
0. Reach out (Optional)
|
They are separated in a somewhat logical order, but some things might feel out of place. <br>
|
||||||
|
|
||||||
If you want to let me know you want to do something to contribute, please do! <br/>
|
### Bundling, Intellisense, Linting
|
||||||
Maybe I'm already working on the thing you wanted to build, or someone else is. <br/>
|
|
||||||
Creating an issue is even better! <br/>
|
|
||||||
|
|
||||||
|
In DCS the easiest way to import a script is with 1 single file, however, that's not as "maintainable".
|
||||||
|
In order to keep it maintainable we're using a self-built tool to bundle, validate and rewrite some of the lua in order to get it into a single file.
|
||||||
|
The tool is open-source here: https://git.dutchie031.com/dutchie031/DcsMissionScriptingTools. <br>
|
||||||
|
It has a `VSCode` extensions and two `Github Action` steps. <br>
|
||||||
|
If your favorite IDE isn't `VSCode` (I respect that), it might be very easy to create an extension for it, if it does allow that.
|
||||||
|
|
||||||
1. Fork the repository
|
The `VSCode` extensions comes with Intellisense as well, in the form of a full file of DCS lua type annotations. <br>
|
||||||
|
This gives the Lua Language Server by Sumneko (which can be used in `VSCode`) all the type definitions it needs.
|
||||||
|
When writing Spearhead code it is also important to keep our own LuaLS Annotations up to date and complete. <br>
|
||||||
|
Read all about those annotations here: https://luals.github.io/wiki/annotations/
|
||||||
|
|
||||||
By forking you can create your own working set of code. <br/>
|
When a pull-request to the `develop` or `main` branches is created, before it can be merged it requires to pass the linter checks.
|
||||||
Whether you keep that fork public or private is up to you! <br/>
|
The first linting check is done by the Lua Language Server. It checks for whitespace and formatting, but it also checks if all types, type-checking and casts are in order. It marks unused variables etc. `.luarc.json` has all the config for those checks. <br>
|
||||||
|
The second linter job is with `luacheck` which first "bundles" the script, and then runs a full linter check over the bundled script. <br>
|
||||||
|
This is to make sure that also the bundled script does not contain any errors.
|
||||||
|
|
||||||
Keep in mind, forking is alright, but it's best with the intent to contribute back. <br/>
|
## Contributing
|
||||||
After all, instead of having 5 slightly different versions and products, it might be nicer to have 1 much better version. <br/>
|
|
||||||
|
|
||||||
2. Create a Draft PR as soon as possible!
|
### Setting up the project
|
||||||
|
|
||||||
As soon as you've found some times to create an initial version, please create a draft PR. <br/>
|
If you've kept on reading I assume you either want to brwose
|
||||||
That way you can let me and everyone know it's being worked on and people can see what conflicts might arise with their own changes. <br/>
|
|
||||||
|
|
||||||
3. Test, test, test
|
|
||||||
|
|
||||||
With DCS and Lua there's a bunch of edge cases that are really hard to get to. <br/>
|
|
||||||
However, you kinda need to make sure most if not all cases are caught and tested. <br/>
|
|
||||||
Since automated testing for now doesn't seem feasible, please make sure to verify and test functionality after touching stuff. <br/>
|
|
||||||
|
|
||||||
4. Finalise and publish the PR.
|
|
||||||
|
|
||||||
Finalise the PR and let the maintainers know! <br/>
|
|
||||||
We can all have a look and discuss the changes. <br/>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
|
||||||
|
|
||||||
- Would it be possible to generate optional markers on mission briefing locations for Tomcat / Phantom navigation? Then Jester can type it in for the pilots automatically.
|
|
||||||
@@ -1,12 +1,64 @@
|
|||||||
|
|
||||||
### [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|
||||||
|
- Addresses #41
|
||||||
|
Adds Stage Complete debug methods for local testing of the Stage flow.
|
||||||
|
PR #40
|
||||||
|
- Closes #35
|
||||||
|
Creating Lane option for stages.
|
||||||
|
Additionally to the primary stage lane, stages can now be created in different lanes.
|
||||||
|
These lanes can be used to create different stage progression flows and create different "side-stories".
|
||||||
|
PR #40
|
||||||
|
- Closes #37
|
||||||
|
Now enabled the mission editor to add a custom briefing to the "Buildable" missions.
|
||||||
|
PR #44
|
||||||
|
- Closes #42
|
||||||
|
DEEPSTRIKE now added as a possible feature.
|
||||||
|
This enables to have missions active in "Pre-Activated" Stages.
|
||||||
|
PR #48
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
## [0.13.0] 2026-08
|
||||||
|
|
||||||
|
A good first release that finally has all major bugs fixed that were caused by the migration from both the underlying script transpiler and the migration to Gitea.
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- Issue #26
|
||||||
|
Possibility to have Stage Overview briefings (which include current missions sorted by distance) to be shown on spawning of a player.
|
||||||
|
PR #31
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Issue #11
|
||||||
|
Supply crate spawning now checks for free space and will not spawn if the area is too crowded.
|
||||||
|
Additionally different units will spawn in different areas depending on loading side.
|
||||||
|
PR #17
|
||||||
|
- Fixed command wiring for supply hubs for better and more accurate detection of units spawning and entering/exiting zone.
|
||||||
|
PR #15
|
||||||
|
- Fixed custom drawings not being drawn correctly.
|
||||||
|
PR #18
|
||||||
|
- Fixed CAP Callbacks not working since the change to a transpiled script. Now a global callback circumvents this issue.
|
||||||
|
PR #18
|
||||||
|
- Fixed #9
|
||||||
|
Changed order of checking mission briefings
|
||||||
|
PR #19
|
||||||
|
- Fixed Configuration defaulting to true for all booleans in StageConfig
|
||||||
|
PR #28
|
||||||
|
- Fixed Pre-Activated stages not always drawing or pre-activating correctly.
|
||||||
|
PR #28
|
||||||
|
- Addressed #24
|
||||||
|
CAP max commit range is now configurable in the config.lua file.
|
||||||
|
Issue remains open in order to apply further fine grained tuning.
|
||||||
|
PR #29
|
||||||
|
|
||||||
## [0.12.0] 2026-06
|
## [0.12.0] 2026-06
|
||||||
|
|
||||||
Migration to Gitea. <br>
|
Migration to Gitea. <br>
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" data-theme="dark">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Spearhead Missions</title>
|
|
||||||
<link rel="stylesheet" href="/style/style.css">
|
|
||||||
<script src="/js/site.js"></script>
|
|
||||||
<style>
|
|
||||||
.side-nav a.active {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #4fc3f7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script type="module" src="/js/components.js"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<app-header></app-header>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<div class="reference-container">
|
|
||||||
<app-sidebar></app-sidebar>
|
|
||||||
|
|
||||||
<div class="content-wrapper">
|
|
||||||
<h1>Advanced Tutorial: Missions</h1>
|
|
||||||
<h2 id="strike">Strike</h2>
|
|
||||||
<p>
|
|
||||||
Naming: <span class="inline-lua"><span class="lua-variable">MISSION_STRIKE_[Name]</span></span> <br>
|
|
||||||
</p>
|
|
||||||
<h3 id="specific_targets">Specific Targets</h3>
|
|
||||||
<p>TBD</p>
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="scenery_targets">Scenery Targets</h3>
|
|
||||||
<p>
|
|
||||||
Bridges, buildings or other type of targets that are baked into the map are awesome targets.<br>
|
|
||||||
We wanted to make sure they were easily integrated in Spearhead.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<note-box type="info" title="Implementation details">
|
|
||||||
Spearhead detects scenery targets by the trigger zone name. A zone named <strong>scenerytarget_[freeform]</strong> or
|
|
||||||
<strong>scenerytargets</strong> (case-insensitive) will be scanned and any scenery objects that has the attribute "Buildings" inside the zone
|
|
||||||
will be added as mission scenery targets.
|
|
||||||
</note-box>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Scenery targets are treated like mission targets: they are included in the mission completion calculation
|
|
||||||
(their `IsAlive()` state is checked) and their state is persisted/updated when missions spawn or resume.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<note-box type="warning" title="Changing IDs">
|
|
||||||
Due to Object IDs changing when maps are updated and edited the "Assign as..." functionality does not work for scenery targets.
|
|
||||||
Hence the need to use trigger zones to define them.
|
|
||||||
</note-box>
|
|
||||||
|
|
||||||
|
|
||||||
<p>Here's two images showing how a bridge is added to a mission.</p>
|
|
||||||
<img src="/img/bridge.png" style="max-height: 35remrem;"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
<footer>
|
|
||||||
<p>© 2025 Spearhead Project</p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -4,9 +4,12 @@ SpearheadConfig = {
|
|||||||
---DEBUG LOGGING
|
---DEBUG LOGGING
|
||||||
debugEnabled = false, -- default false
|
debugEnabled = false, -- default false
|
||||||
|
|
||||||
|
--- DEBUG MENU
|
||||||
|
debugMenuEnabled = false, -- default false
|
||||||
|
|
||||||
--- The time briefings should be displayed by default.
|
--- The time briefings should be displayed by default.
|
||||||
--- Players can always "Clear Messages" through the F10 menu, so setting it to a high value can be
|
--- Players can always "Clear Messages" through the F10 menu, so setting it to a high value can be
|
||||||
briefingMessageDuration = 30, --default 30
|
briefingMessageDuration = 60, --default 60
|
||||||
|
|
||||||
CapConfig = {
|
CapConfig = {
|
||||||
--quickly enable of disable the entire CAP Logic
|
--quickly enable of disable the entire CAP Logic
|
||||||
@@ -29,6 +32,12 @@ SpearheadConfig = {
|
|||||||
-- unit: feet
|
-- unit: feet
|
||||||
maxAlt = 28000, -- default 28000
|
maxAlt = 28000, -- default 28000
|
||||||
|
|
||||||
|
--The "maxDistance" that's set on the CAP tasking for the aircraft to commit to a target.
|
||||||
|
--This is the distance from the aircraft to the target that the aircraft will commit to engaging
|
||||||
|
--This is shared for CAP, Intercept and Sweep missions for now.
|
||||||
|
-- unit: nautical miles
|
||||||
|
maxCommitRange = 35, -- default 35
|
||||||
|
|
||||||
-- DELAYS.
|
-- DELAYS.
|
||||||
-- Delays work as follow.
|
-- Delays work as follow.
|
||||||
-- When an aircraft lands alive and well it will be rearmed and ready to go.
|
-- When an aircraft lands alive and well it will be rearmed and ready to go.
|
||||||
@@ -67,6 +76,9 @@ SpearheadConfig = {
|
|||||||
--The location will continously update for the last killed unit.
|
--The location will continously update for the last killed unit.
|
||||||
markLastContact = false, -- default false
|
markLastContact = false, -- default false
|
||||||
|
|
||||||
|
--If enabled, the stage briefing including the current missions will be shown to players on spawn.
|
||||||
|
briefingOnSpawn = true, -- default true
|
||||||
|
|
||||||
--AutoStages will continue to the next stage automatically on completion of the missions within the stage.
|
--AutoStages will continue to the next stage automatically on completion of the missions within the stage.
|
||||||
-- If you want to make it so the next stage triggers only when you want to disable it here and manually implement the actions needed.
|
-- If you want to make it so the next stage triggers only when you want to disable it here and manually implement the actions needed.
|
||||||
--[[
|
--[[
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
--assert(loadfile("C:\\Repos\\DCS\\Spearhead\\dev\\dev_classes.lua"))()
|
|
||||||
|
|
||||||
local basePath = "C:\\Repos\\DCS\\Spearhead\\"
|
|
||||||
local classPath = basePath .. "classes\\"
|
|
||||||
|
|
||||||
assert(loadfile(classPath .. "_baseClasses\\Queue.lua"))()
|
|
||||||
|
|
||||||
assert(loadfile(classPath .. "spearhead_base.lua"))()
|
|
||||||
assert(loadfile(classPath .. "spearhead_routeutil.lua"))()
|
|
||||||
assert(loadfile(classPath .. "spearhead_events.lua"))()
|
|
||||||
assert(loadfile(classPath .. "spearhead_db.lua"))()
|
|
||||||
|
|
||||||
assert(loadfile(classPath .. "fleetClasses\\FleetGroup.lua"))()
|
|
||||||
assert(loadfile(classPath .. "fleetClasses\\GlobalFleetManager.lua"))()
|
|
||||||
|
|
||||||
assert(loadfile(classPath .. "helpers\\MizGroupsManager.lua"))()
|
|
||||||
assert(loadfile(classPath .. "helpers\\SpawnManager.lua"))()
|
|
||||||
|
|
||||||
assert(loadfile(classPath .. "configuration\\CapConfig.lua"))()
|
|
||||||
assert(loadfile(classPath .. "configuration\\StageConfig.lua"))()
|
|
||||||
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\GlobalStageManager.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\missions\\baseMissions\\Mission.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\missions\\ZoneMission.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\missions\\RunwayStrikeMission.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\missions\\BuildableMission.lua"))()
|
|
||||||
|
|
||||||
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\Stages\\BaseStage\\Stage.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\Stages\\PrimaryStage.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\Stages\\ExtraStage.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\Stages\\WaitingStage.lua"))()
|
|
||||||
|
|
||||||
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\Groups\\SpearheadGroup.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\Groups\\SpearheadSceneryObject.lua"))()
|
|
||||||
|
|
||||||
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\helpers\\MissionCommandsHelper.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\helpers\\SupplyConfig.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\helpers\\SupplyUnitsTracker.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\helpers\\BattleManager.lua"))()
|
|
||||||
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\abstract\\BuildableZone.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\StageBase.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\BlueSam.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\FarpZone.lua"))()
|
|
||||||
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\SupplyHub.lua"))()
|
|
||||||
|
|
||||||
assert(loadfile(classPath .. "capClasses\\taskings\\RTB.lua"))()
|
|
||||||
assert(loadfile(classPath .. "capClasses\\taskings\\CAP.lua"))()
|
|
||||||
assert(loadfile(classPath .. "capClasses\\taskings\\SWEEP.lua"))()
|
|
||||||
assert(loadfile(classPath .. "capClasses\\taskings\\INTERCEPT.lua"))()
|
|
||||||
assert(loadfile(classPath .. "capClasses\\airGroups\\AirGroup.lua"))()
|
|
||||||
assert(loadfile(classPath .. "capClasses\\airGroups\\CapGroup.lua"))()
|
|
||||||
assert(loadfile(classPath .. "capClasses\\airGroups\\SweepGroup.lua"))()
|
|
||||||
assert(loadfile(classPath .. "capClasses\\airGroups\\InterceptGroup.lua"))()
|
|
||||||
assert(loadfile(classPath .. "capClasses\\detection\\DetectionManager.lua"))()
|
|
||||||
assert(loadfile(classPath .. "capClasses\\GlobalCapManager.lua"))()
|
|
||||||
assert(loadfile(classPath .. "capClasses\\CapAirbase.lua"))()
|
|
||||||
assert(loadfile(classPath .. "capClasses\\runwayBombing\\RunwayBombingTracker.lua"))()
|
|
||||||
|
|
||||||
assert(loadfile(classPath .. "persistence\\Persistence.lua"))()
|
|
||||||
|
|
||||||
-- Startup:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
assert(loadfile(basePath .. "main.lua"))()
|
|
||||||
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
|
|
||||||
SpearheadConfig = {
|
|
||||||
|
|
||||||
debugEnabled = true,
|
|
||||||
CapConfig = {
|
|
||||||
--quickly enable of disable the entire CAP Logic
|
|
||||||
--(you can also just rename all units to not be named "CAP_")
|
|
||||||
enabled = true, -- default true
|
|
||||||
|
|
||||||
--min ground speed for CAP aircraft during patrol
|
|
||||||
-- unit: knots
|
|
||||||
minSpeed = 400, -- default 400
|
|
||||||
|
|
||||||
--max speed for CAP aircraft during patrol
|
|
||||||
-- unit: knots
|
|
||||||
maxSpeed = 500, -- default 500
|
|
||||||
|
|
||||||
--minAlt for aircraft on patrol
|
|
||||||
-- unit: feet
|
|
||||||
minAlt = 18000, -- default 18000
|
|
||||||
|
|
||||||
--maxAlt for aircraft on patrol
|
|
||||||
-- unit: feet
|
|
||||||
maxAlt = 28000, -- default 28000
|
|
||||||
|
|
||||||
--Delay for aircraft from touchdown to off the chocks.
|
|
||||||
-- unit: seconds
|
|
||||||
rearmDelay = 180, -- default 600
|
|
||||||
repairDelay = 600, -- default 600
|
|
||||||
--Delay for aircraft from death to takeoff.
|
|
||||||
--When the seconds remaining is the same at the rearmDelay it will be spawned on the ramp and follow the rearm logic.
|
|
||||||
-- !! Can not be lower than rearmDelay
|
|
||||||
-- unit: seconds
|
|
||||||
deathDelay = 1800, -- default 1800
|
|
||||||
},
|
|
||||||
StageConfig = {
|
|
||||||
|
|
||||||
-- management of stages and its missions.
|
|
||||||
-- This is not related to CAP managers which will continue to work even if stage management is disabled
|
|
||||||
enabled = true, -- default true
|
|
||||||
|
|
||||||
--Will draw the active and the next stage
|
|
||||||
drawStages = true, -- default true
|
|
||||||
drawPreActivated = true,
|
|
||||||
markLastContact = true,
|
|
||||||
|
|
||||||
--AutoStages will continue to the next stage automatically on completion of the missions within the stage.
|
|
||||||
-- If you want to make it so the next stage triggers only when you want to disable it here and manually implement the actions needed.
|
|
||||||
--[[
|
|
||||||
TODO: Add manual stage transition documentation
|
|
||||||
]]
|
|
||||||
autoStages = true, --default true
|
|
||||||
|
|
||||||
--Maximum missions per stage (includes all types of missions)
|
|
||||||
maxMissionStage = 100,
|
|
||||||
|
|
||||||
--Stage starting number
|
|
||||||
startingStage = 1,
|
|
||||||
|
|
||||||
---DEBUG logging. Consider keeping this disabled
|
|
||||||
debugEnabled = true
|
|
||||||
},
|
|
||||||
Persistence = {
|
|
||||||
--- io and lfs cannot be sanitized in the MissionScripting.lua
|
|
||||||
|
|
||||||
--- enables or disables the persistence logic in spearhead
|
|
||||||
enabled = true,
|
|
||||||
|
|
||||||
--- sets the directory where the persistence file is stored
|
|
||||||
--- if nil then lfs.writedir() will be used.
|
|
||||||
--- which will
|
|
||||||
directory = nil ,
|
|
||||||
|
|
||||||
--- the filename of the persistence file. Should end with .json for convention, but any text extension should do.
|
|
||||||
fileName = "Spearhead_Persistence_Dev"
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
|
|
||||||
---@class Queue
|
|
||||||
---@field private _items Array
|
|
||||||
---@field private _first number
|
|
||||||
---@field private _last number
|
|
||||||
local Queue = {}
|
|
||||||
Queue.__index = Queue
|
|
||||||
|
|
||||||
---@return Queue
|
|
||||||
function Queue.new()
|
|
||||||
|
|
||||||
local self = setmetatable({}, Queue)
|
|
||||||
self._items = {}
|
|
||||||
self._first = 1
|
|
||||||
self._last = 0
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
---@return nil
|
|
||||||
function Queue:push(item)
|
|
||||||
self._last = self._last + 1
|
|
||||||
self._items[self._last] = item
|
|
||||||
end
|
|
||||||
|
|
||||||
---@return any?
|
|
||||||
function Queue:pop()
|
|
||||||
if self._first > self._last then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local item = self._items[self._first]
|
|
||||||
self._items[self._first] = nil
|
|
||||||
self._first = self._first + 1
|
|
||||||
return item
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---@return Array<any>
|
|
||||||
function Queue:toList()
|
|
||||||
local items = {}
|
|
||||||
for i = self._first, self._last do
|
|
||||||
items[#items + 1] = self._items[i]
|
|
||||||
end
|
|
||||||
return items
|
|
||||||
end
|
|
||||||
|
|
||||||
return Queue
|
|
||||||
@@ -10,7 +10,6 @@ local MissionCompleteListeners = {}
|
|||||||
---@class SpearheadAPIInternal
|
---@class SpearheadAPIInternal
|
||||||
---@field notifyMissionComplete fun(zone_name: string)
|
---@field notifyMissionComplete fun(zone_name: string)
|
||||||
|
|
||||||
|
|
||||||
---@type FullSpearheadAPI
|
---@type FullSpearheadAPI
|
||||||
SpearheadAPI = {
|
SpearheadAPI = {
|
||||||
Stages = {
|
Stages = {
|
||||||
@@ -25,12 +24,12 @@ SpearheadAPI = {
|
|||||||
getCurrentStage = function()
|
getCurrentStage = function()
|
||||||
return GlobalStageManager.getCurrentStage() or nil
|
return GlobalStageManager.getCurrentStage() or nil
|
||||||
end,
|
end,
|
||||||
isStageComplete = function(stageNumber)
|
isStageComplete = function(stageNumber, stageLaneIdentifier)
|
||||||
if type(stageNumber) ~= "number" then
|
if type(stageNumber) ~= "number" then
|
||||||
return false, "stageNumber " .. stageNumber .. " is not a valid number"
|
return false, "stageNumber " .. stageNumber .. " is not a valid number"
|
||||||
end
|
end
|
||||||
|
|
||||||
local isComplete = GlobalStageManager.isStageComplete(stageNumber)
|
local isComplete = GlobalStageManager.isStageComplete(stageNumber, stageLaneIdentifier)
|
||||||
if isComplete == nil then
|
if isComplete == nil then
|
||||||
return nil, "no stage found with number " .. stageNumber
|
return nil, "no stage found with number " .. stageNumber
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ SpearheadAPI = SpearheadAPI
|
|||||||
---@class SpearheadStagesAPI
|
---@class SpearheadStagesAPI
|
||||||
---@field changeStage fun(stageNumber: number): boolean, string @Changes the active stage of spearhead. <br/> All other stages will change based on the normal logic. (CAP, BLUE etc.)
|
---@field changeStage fun(stageNumber: number): boolean, string @Changes the active stage of spearhead. <br/> All other stages will change based on the normal logic. (CAP, BLUE etc.)
|
||||||
---@field getCurrentStage fun(): number | nil @Returns the current stange number <br/> Returns nil when the stagenumber was not set before ever, which means Spearhead was not started.
|
---@field getCurrentStage fun(): number | nil @Returns the current stange number <br/> Returns nil when the stagenumber was not set before ever, which means Spearhead was not started.
|
||||||
---@field isStageComplete fun(stageNumber: number): boolean | nil, string @returns whether a stage (by index) is complete. <br/> @param stageNumber number <br/> @return boolean | nil <br/> @return string
|
---@field isStageComplete fun(stageNumber: number, stageLaneIdentifier: string?): boolean | nil, string @returns whether a stage (by index) is complete. <br/> @param stageNumber number <br/> @param stageLaneIdentifier string? nil for default lane <br/> @return boolean | nil <br/> @return string
|
||||||
|
|
||||||
---@class OnMissionCompleteListener
|
---@class OnMissionCompleteListener
|
||||||
---@field onMissionComplete fun(self: OnMissionCompleteListener, zone_name: string) @Called when a mission is completed. @return void
|
---@field onMissionComplete fun(self: OnMissionCompleteListener, zone_name: string) @Called when a mission is completed. @return void
|
||||||
@@ -14,4 +14,3 @@ SpearheadAPI = SpearheadAPI
|
|||||||
---@class MissionAPI
|
---@class MissionAPI
|
||||||
---@field addOnMissionCompleteListener fun(listener: OnMissionCompleteListener) @Adds a listener to the mission. <br/> @param listener OnMissionCompleteListener <br/> @return void
|
---@field addOnMissionCompleteListener fun(listener: OnMissionCompleteListener) @Adds a listener to the mission. <br/> @param listener OnMissionCompleteListener <br/> @return void
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ local Util = require("classes.util.Util")
|
|||||||
local CapGroup = require("classes.capClasses.airGroups.CapGroup")
|
local CapGroup = require("classes.capClasses.airGroups.CapGroup")
|
||||||
local SweepGroup = require("classes.capClasses.airGroups.SweepGroup")
|
local SweepGroup = require("classes.capClasses.airGroups.SweepGroup")
|
||||||
local InterceptGroup = require("classes.capClasses.airGroups.InterceptGroup")
|
local InterceptGroup = require("classes.capClasses.airGroups.InterceptGroup")
|
||||||
local RunwayBombingTracker = require("classes.capClasses.runwayBombing.RunwayBombingTracker")
|
|
||||||
local SpearheadEvents = require("classes.spearhead_events")
|
local SpearheadEvents = require("classes.spearhead_events")
|
||||||
local RunwayStrikeMission = require("classes.stageClasses.missions.RunwayStrikeMission")
|
local RunwayStrikeMission = require("classes.stageClasses.missions.RunwayStrikeMission")
|
||||||
|
|
||||||
@@ -37,12 +36,11 @@ end
|
|||||||
---@param database Database
|
---@param database Database
|
||||||
---@param logger table
|
---@param logger table
|
||||||
---@param capConfig table
|
---@param capConfig table
|
||||||
---@param stageConfig table
|
|
||||||
---@param runwayBombingTracker RunwayBombingTracker
|
---@param runwayBombingTracker RunwayBombingTracker
|
||||||
---@param detectionManager DetectionManager
|
---@param detectionManager DetectionManager
|
||||||
---@param spawnManager SpawnManager
|
---@param spawnManager SpawnManager
|
||||||
---@return CapBase
|
---@return CapBase
|
||||||
function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runwayBombingTracker, detectionManager, spawnManager)
|
function CapBase.new(airbaseName, database, logger, capConfig, runwayBombingTracker, detectionManager, spawnManager)
|
||||||
CapBase.__index = CapBase
|
CapBase.__index = CapBase
|
||||||
local self = setmetatable({}, { __index = CapBase }) --[[@as CapBase]]
|
local self = setmetatable({}, { __index = CapBase }) --[[@as CapBase]]
|
||||||
|
|
||||||
@@ -61,7 +59,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
|
|||||||
|
|
||||||
local baseData = database:getAirbaseDataForZone(airbaseName)
|
local baseData = database:getAirbaseDataForZone(airbaseName)
|
||||||
if baseData and baseData.CapGroups then
|
if baseData and baseData.CapGroups then
|
||||||
for key, name in pairs(baseData.CapGroups) do
|
for _, name in pairs(baseData.CapGroups) do
|
||||||
local capGroup = CapGroup.New(name, capConfig, logger, spawnManager)
|
local capGroup = CapGroup.New(name, capConfig, logger, spawnManager)
|
||||||
if capGroup then
|
if capGroup then
|
||||||
self.capGroupsByName[name] = capGroup
|
self.capGroupsByName[name] = capGroup
|
||||||
@@ -70,7 +68,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
|
|||||||
end
|
end
|
||||||
|
|
||||||
if baseData and baseData.SweepGroups then
|
if baseData and baseData.SweepGroups then
|
||||||
for key, name in pairs(baseData.SweepGroups) do
|
for _, name in pairs(baseData.SweepGroups) do
|
||||||
local sweepGroup = SweepGroup.New(name, capConfig, logger, spawnManager)
|
local sweepGroup = SweepGroup.New(name, capConfig, logger, spawnManager)
|
||||||
if sweepGroup then
|
if sweepGroup then
|
||||||
self.sweepGroupsByName[name] = sweepGroup
|
self.sweepGroupsByName[name] = sweepGroup
|
||||||
@@ -79,7 +77,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
|
|||||||
end
|
end
|
||||||
|
|
||||||
if baseData and baseData.InterceptGroups then
|
if baseData and baseData.InterceptGroups then
|
||||||
for key, name in pairs(baseData.InterceptGroups) do
|
for _, name in pairs(baseData.InterceptGroups) do
|
||||||
local interceptGroup = InterceptGroup.New(name, capConfig, logger, detectionManager, spawnManager)
|
local interceptGroup = InterceptGroup.New(name, capConfig, logger, detectionManager, spawnManager)
|
||||||
if interceptGroup then
|
if interceptGroup then
|
||||||
self.interceptGroupsByName[name] = interceptGroup
|
self.interceptGroupsByName[name] = interceptGroup
|
||||||
@@ -98,7 +96,6 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
|
|||||||
|
|
||||||
timer.scheduleFunction(CheckStateContinuous, self, timer.getTime() + 15)
|
timer.scheduleFunction(CheckStateContinuous, self, timer.getTime() + 15)
|
||||||
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -127,7 +124,7 @@ end
|
|||||||
|
|
||||||
function CapBase:SpawnIfApplicable()
|
function CapBase:SpawnIfApplicable()
|
||||||
self.logger:debug("Check spawns for airbase " .. self.airbaseName)
|
self.logger:debug("Check spawns for airbase " .. self.airbaseName)
|
||||||
for groupName, capGroup in pairs(self.capGroupsByName) do
|
for _, capGroup in pairs(self.capGroupsByName) do
|
||||||
local targetStage = capGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
|
local targetStage = capGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||||
|
|
||||||
if targetStage ~= nil and capGroup:GetState() == "UnSpawned" then
|
if targetStage ~= nil and capGroup:GetState() == "UnSpawned" then
|
||||||
@@ -135,7 +132,7 @@ function CapBase:SpawnIfApplicable()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for groupName, sweepGroup in pairs(self.sweepGroupsByName) do
|
for _, sweepGroup in pairs(self.sweepGroupsByName) do
|
||||||
local targetStage = sweepGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
|
local targetStage = sweepGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||||
|
|
||||||
if targetStage ~= nil and sweepGroup:GetState() == "UnSpawned" then
|
if targetStage ~= nil and sweepGroup:GetState() == "UnSpawned" then
|
||||||
@@ -143,7 +140,7 @@ function CapBase:SpawnIfApplicable()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for groupName, interceptGroup in pairs(self.interceptGroupsByName) do
|
for _, interceptGroup in pairs(self.interceptGroupsByName) do
|
||||||
local targetStage = interceptGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
|
local targetStage = interceptGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||||
|
|
||||||
if targetStage ~= nil and interceptGroup:GetState() == "UnSpawned" then
|
if targetStage ~= nil and interceptGroup:GetState() == "UnSpawned" then
|
||||||
@@ -154,8 +151,9 @@ end
|
|||||||
|
|
||||||
function CapBase:CheckAndScheduleCAP()
|
function CapBase:CheckAndScheduleCAP()
|
||||||
self.logger:debug("Check taskings for airbase " .. self.airbaseName)
|
self.logger:debug("Check taskings for airbase " .. self.airbaseName)
|
||||||
|
---@type table<string, number>
|
||||||
local countPerStage = {}
|
local countPerStage = {}
|
||||||
|
---@type table<string, number>
|
||||||
local requiredPerStage = {}
|
local requiredPerStage = {}
|
||||||
|
|
||||||
local airbase = Airbase.getByName(self.airbaseName)
|
local airbase = Airbase.getByName(self.airbaseName)
|
||||||
@@ -312,8 +310,7 @@ function CapBase:CheckAndScheduleIntercept()
|
|||||||
|
|
||||||
self.logger:debug("Check intercept taskings for airbase " .. self.airbaseName)
|
self.logger:debug("Check intercept taskings for airbase " .. self.airbaseName)
|
||||||
|
|
||||||
local interceptZoneIDs = {}
|
---@type table<string, boolean>
|
||||||
|
|
||||||
local interceptZoneIDs = {}
|
local interceptZoneIDs = {}
|
||||||
|
|
||||||
local airbase = Airbase.getByName(self.airbaseName)
|
local airbase = Airbase.getByName(self.airbaseName)
|
||||||
@@ -321,7 +318,7 @@ function CapBase:CheckAndScheduleIntercept()
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
for name, group in pairs(self.interceptGroupsByName) do
|
for _, group in pairs(self.interceptGroupsByName) do
|
||||||
local targetZoneID = group:GetZoneIDWhenStageID(tostring(self.activeStage))
|
local targetZoneID = group:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||||
if targetZoneID then
|
if targetZoneID then
|
||||||
interceptZoneIDs[targetZoneID] = true
|
interceptZoneIDs[targetZoneID] = true
|
||||||
@@ -379,7 +376,6 @@ function CapBase:CheckAndScheduleIntercept()
|
|||||||
if total < required then
|
if total < required then
|
||||||
for _, group in pairs(self.interceptGroupsByName) do
|
for _, group in pairs(self.interceptGroupsByName) do
|
||||||
if total < required then
|
if total < required then
|
||||||
local zoneID = group:GetZoneIDWhenStageID(tostring(self.activeStage))
|
|
||||||
if group:GetState() == "ReadyOnTheRamp" then
|
if group:GetState() == "ReadyOnTheRamp" then
|
||||||
group:SendToInterceptUnits(targets, name, airbase)
|
group:SendToInterceptUnits(targets, name, airbase)
|
||||||
total = total + 1
|
total = total + 1
|
||||||
@@ -392,9 +388,12 @@ function CapBase:CheckAndScheduleIntercept()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
function CapBase:OnStageNumberChanged(number)
|
|
||||||
|
function CapBase:OnStageNumberChanged(number, laneIdentifier)
|
||||||
|
-- only react on "main" lane changes, ignore other lanes for now
|
||||||
|
if laneIdentifier ~= nil then return end
|
||||||
|
|
||||||
self.activeStage = number
|
self.activeStage = number
|
||||||
|
|
||||||
if self:IsBaseActiveWhenStageIsActive(number) == true then
|
if self:IsBaseActiveWhenStageIsActive(number) == true then
|
||||||
|
|||||||
@@ -6,21 +6,19 @@ local CapAirbase = require("classes.capClasses.CapAirbase")
|
|||||||
---@class GlobalCapManager
|
---@class GlobalCapManager
|
||||||
local GlobalCapManager = {}
|
local GlobalCapManager = {}
|
||||||
do
|
do
|
||||||
|
---@type table<string, CapBase[]>
|
||||||
local airbasesPerStage = {}
|
local airbasesPerStage = {}
|
||||||
|
---@type table<string, CapBase>
|
||||||
local allAirbasesByName = {}
|
local allAirbasesByName = {}
|
||||||
local activeAirbasesPerActiveStage = {}
|
|
||||||
local unitsPerzonePerStage = {}
|
|
||||||
|
|
||||||
local initiated = false
|
local initiated = false
|
||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@param database Database
|
---@param database Database
|
||||||
---@param capConfig table
|
---@param capConfig table
|
||||||
---@param stageConfig StageConfig
|
|
||||||
---@param detectionManager DetectionManager
|
---@param detectionManager DetectionManager
|
||||||
---@param logLevel LogLevel
|
---@param logLevel LogLevel
|
||||||
---@param spawnManager SpawnManager
|
---@param spawnManager SpawnManager
|
||||||
function GlobalCapManager.start(database, capConfig, detectionManager, stageConfig, logLevel, spawnManager)
|
function GlobalCapManager.start(database, capConfig, detectionManager, logLevel, spawnManager)
|
||||||
if initiated == true then return end
|
if initiated == true then return end
|
||||||
|
|
||||||
local logger = Logger.new("AirbaseManager", logLevel)
|
local logger = Logger.new("AirbaseManager", logLevel)
|
||||||
@@ -29,7 +27,7 @@ do
|
|||||||
|
|
||||||
local zones = database:getStagezoneNames()
|
local zones = database:getStagezoneNames()
|
||||||
if zones then
|
if zones then
|
||||||
for key, stageName in pairs(zones) do
|
for _, stageName in pairs(zones) do
|
||||||
if airbasesPerStage[stageName] == nil then
|
if airbasesPerStage[stageName] == nil then
|
||||||
airbasesPerStage[stageName] = {}
|
airbasesPerStage[stageName] = {}
|
||||||
end
|
end
|
||||||
@@ -40,8 +38,7 @@ do
|
|||||||
if airbaseName then
|
if airbaseName then
|
||||||
local airbaseSpecificLogger = Logger.new("CAP_" .. airbaseName, logLevel)
|
local airbaseSpecificLogger = Logger.new("CAP_" .. airbaseName, logLevel)
|
||||||
|
|
||||||
local airbase = CapAirbase.new(airbaseName, database, airbaseSpecificLogger, capConfig, stageConfig, runwayBombingTracker, detectionManager, spawnManager)
|
local airbase = CapAirbase.new(airbaseName, database, airbaseSpecificLogger, capConfig, runwayBombingTracker, detectionManager, spawnManager)
|
||||||
|
|
||||||
if airbase then
|
if airbase then
|
||||||
table.insert(airbasesPerStage[stageName], airbase)
|
table.insert(airbasesPerStage[stageName], airbase)
|
||||||
allAirbasesByName[airbaseName] = airbase
|
allAirbasesByName[airbaseName] = airbase
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
local SpearheadEvents = require("classes.spearhead_events")
|
local SpearheadEvents = require("classes.spearhead_events")
|
||||||
local RTBMission = require("classes.capClasses.taskings.RTB")
|
local RTBMission = require("classes.capClasses.taskings.RTB")
|
||||||
local Util = require("classes.util.Util")
|
local Util = require("classes.util.Util")
|
||||||
local DcsUtil = require("classes.util.DcsUtil")
|
local GlobalConfig = require("classes.configuration.GlobalConfig")
|
||||||
|
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||||
|
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
|
||||||
|
|
||||||
---@class AirGroup : OnUnitLostListener
|
---@class AirGroup : OnUnitLostListener, OnGroupRTBListener, OnGroupRTBInTenListener, OnLandEventListener, OnGroupOnStationListener
|
||||||
---@field protected _logger Logger
|
---@field protected _logger Logger
|
||||||
---@field protected _groupName string
|
---@field protected _groupName string
|
||||||
---@field protected _groupType AirGroupType
|
---@field protected _groupType AirGroupType
|
||||||
@@ -12,9 +14,12 @@ local DcsUtil = require("classes.util.DcsUtil")
|
|||||||
---@field protected _config CapConfig
|
---@field protected _config CapConfig
|
||||||
---@field protected _checkLivenessNumber number
|
---@field protected _checkLivenessNumber number
|
||||||
---@field protected _spawnManager SpawnManager
|
---@field protected _spawnManager SpawnManager
|
||||||
|
---@field protected _routeDrawing CustomDrawing?
|
||||||
local AirGroup = {}
|
local AirGroup = {}
|
||||||
AirGroup.__index = AirGroup
|
AirGroup.__index = AirGroup
|
||||||
|
|
||||||
|
local globalConfig = GlobalConfig.New()
|
||||||
|
|
||||||
---@param logger Logger
|
---@param logger Logger
|
||||||
---@param groupName string
|
---@param groupName string
|
||||||
---@param groupType AirGroupType
|
---@param groupType AirGroupType
|
||||||
@@ -28,6 +33,7 @@ function AirGroup:New(groupName, groupType, config, logger, spawnManager)
|
|||||||
self._config = config
|
self._config = config
|
||||||
self._logger = logger
|
self._logger = logger
|
||||||
self._spawnManager = spawnManager
|
self._spawnManager = spawnManager
|
||||||
|
self._routeDrawings = {}
|
||||||
|
|
||||||
local group = Group.getByName(self._groupName)
|
local group = Group.getByName(self._groupName)
|
||||||
if group then
|
if group then
|
||||||
@@ -74,7 +80,7 @@ function AirGroup:SetMission(mission)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local setMissionDelayed = function(data, time)
|
local setMissionDelayed = function(data, _)
|
||||||
data.self:SetMissionPrivate(data.mission)
|
data.self:SetMissionPrivate(data.mission)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -92,6 +98,28 @@ function AirGroup:SetMissionPrivate(mission)
|
|||||||
if group and mission then
|
if group and mission then
|
||||||
group:getController():setTask(mission)
|
group:getController():setTask(mission)
|
||||||
self._logger:debug("mission - Task set for group: " .. self._groupName)
|
self._logger:debug("mission - Task set for group: " .. self._groupName)
|
||||||
|
|
||||||
|
if globalConfig:isDebugMenuEnabled() == true then
|
||||||
|
-- draw the mission route for debugging purposes
|
||||||
|
if self._routeDrawing then
|
||||||
|
self._routeDrawing:Remove()
|
||||||
|
end
|
||||||
|
|
||||||
|
local points = {}
|
||||||
|
if mission and mission.params and mission.params.route and mission.params.route.points then
|
||||||
|
local routePoints = mission.params.route.points --[[@as Array<Vec2> ]]
|
||||||
|
for _, wp in pairs(routePoints) do
|
||||||
|
if wp and wp.x and wp.y then
|
||||||
|
table.insert(points, { x = wp.x, y = wp.y })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local colorString = DrawingHelper.ColorTableToColorString({ 1, 0, 0, 1 })
|
||||||
|
self._routeDrawing = CustomDrawing.FromPoints(points, colorString, 5, 2)
|
||||||
|
self._routeDrawing:Draw()
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -171,6 +199,8 @@ function AirGroup:SpawnInternal(force, withoutLoadout)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param selfA AirGroup
|
---@param selfA AirGroup
|
||||||
|
---@param time number
|
||||||
|
---@return number?
|
||||||
local function CheckLivenessTask(selfA, time)
|
local function CheckLivenessTask(selfA, time)
|
||||||
local interval = selfA:CheckLiveness()
|
local interval = selfA:CheckLiveness()
|
||||||
if not interval then return end
|
if not interval then return end
|
||||||
@@ -236,6 +266,7 @@ function AirGroup:OnLastUnitLanded()
|
|||||||
|
|
||||||
---@param data CheckGroupForRestartData
|
---@param data CheckGroupForRestartData
|
||||||
---@param time number
|
---@param time number
|
||||||
|
---@return number?
|
||||||
local checkGroupForRestart = function(data, time)
|
local checkGroupForRestart = function(data, time)
|
||||||
local group = Group.getByName(data.self:GetName())
|
local group = Group.getByName(data.self:GetName())
|
||||||
if not group then
|
if not group then
|
||||||
@@ -329,7 +360,7 @@ do --- RESPAWN FUNCTIONS
|
|||||||
self:SetState("Dead")
|
self:SetState("Dead")
|
||||||
|
|
||||||
---@param selfA AirGroup
|
---@param selfA AirGroup
|
||||||
local respawnTask = function(selfA, time)
|
local respawnTask = function(selfA, _)
|
||||||
selfA:StartRepair()
|
selfA:StartRepair()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -348,8 +379,7 @@ do --- RESPAWN FUNCTIONS
|
|||||||
end
|
end
|
||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@param selfA AirGroup
|
local rearmTask = function(_, _)
|
||||||
local rearmTask = function(selfA, time)
|
|
||||||
self:StartRearm()
|
self:StartRearm()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -374,7 +404,7 @@ do --- RESPAWN FUNCTIONS
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param selfA AirGroup
|
---@param selfA AirGroup
|
||||||
local rearmTask = function(selfA, time)
|
local rearmTask = function(selfA, _)
|
||||||
selfA:MarkRearmComplete()
|
selfA:MarkRearmComplete()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -387,6 +417,7 @@ end
|
|||||||
do --EVENT LISTENERS
|
do --EVENT LISTENERS
|
||||||
---@param unit Unit
|
---@param unit Unit
|
||||||
function AirGroup:OnUnitLost(unit)
|
function AirGroup:OnUnitLost(unit)
|
||||||
|
if unit == nil then return end
|
||||||
self:CheckLiveness()
|
self:CheckLiveness()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -406,7 +437,7 @@ do --EVENT LISTENERS
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function AirGroup:OnUnitLanded(unit, airbase)
|
function AirGroup:OnUnitLanded(_, _)
|
||||||
local anyInAir = false
|
local anyInAir = false
|
||||||
local group = Group.getByName(self._groupName)
|
local group = Group.getByName(self._groupName)
|
||||||
if group then
|
if group then
|
||||||
|
|||||||
@@ -34,9 +34,10 @@ function CapGroup:IsBackup()
|
|||||||
return self._isBackup
|
return self._isBackup
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param stageID string
|
||||||
---@return string?
|
---@return string?
|
||||||
function CapGroup:GetZoneIDWhenStageID(stageID)
|
function CapGroup:GetZoneIDWhenStageID(stageID)
|
||||||
return self._targetZoneIdPerStage[stageID]
|
return self._targetZoneIdPerStage[tostring(stageID)]
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string?
|
---@return string?
|
||||||
@@ -107,13 +108,13 @@ function CapGroup:InitWithName(groupName)
|
|||||||
|
|
||||||
local subsplit = Util.split_string(configPart, "|")
|
local subsplit = Util.split_string(configPart, "|")
|
||||||
if subsplit then
|
if subsplit then
|
||||||
for key, value in pairs(subsplit) do
|
for _, value in pairs(subsplit) do
|
||||||
local keySplit = Util.split_string(value, "]")
|
local keySplit = Util.split_string(value, "]")
|
||||||
local targetZone = keySplit[2]
|
local targetZone = keySplit[2]
|
||||||
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
|
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
|
||||||
local commaSeperated = Util.split_string(allActives, ",")
|
local commaSeperated = Util.split_string(allActives, ",")
|
||||||
for _, value in pairs(commaSeperated) do
|
for _, childValue in pairs(commaSeperated) do
|
||||||
local dashSeperated = Util.split_string(value, "-")
|
local dashSeperated = Util.split_string(childValue, "-")
|
||||||
if Util.tableLength(dashSeperated) > 1 then
|
if Util.tableLength(dashSeperated) > 1 then
|
||||||
local from = tonumber(dashSeperated[1])
|
local from = tonumber(dashSeperated[1])
|
||||||
local till = tonumber(dashSeperated[2])
|
local till = tonumber(dashSeperated[2])
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ end
|
|||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@param units Array<string>
|
---@param units Array<string>
|
||||||
|
---@param zoneName string
|
||||||
---@param homeAirbase Airbase
|
---@param homeAirbase Airbase
|
||||||
function InterceptGroup:SendToInterceptUnits(units, zoneName, homeAirbase)
|
function InterceptGroup:SendToInterceptUnits(units, zoneName, homeAirbase)
|
||||||
|
|
||||||
@@ -68,9 +69,10 @@ function InterceptGroup:SendToInterceptUnits(units, zoneName, homeAirbase)
|
|||||||
self:SetTargetUnits(units)
|
self:SetTargetUnits(units)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param stageID string
|
||||||
---@return string?
|
---@return string?
|
||||||
function InterceptGroup:GetZoneIDWhenStageID(stageID)
|
function InterceptGroup:GetZoneIDWhenStageID(stageID)
|
||||||
return self._targetZoneIdPerStage[stageID]
|
return self._targetZoneIdPerStage[tostring(stageID)]
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string?
|
---@return string?
|
||||||
@@ -91,6 +93,9 @@ function InterceptGroup:SetTargetUnits(unitNames)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param selfA InterceptGroup
|
||||||
|
---@param time number
|
||||||
|
---@return number?
|
||||||
local updateContinous = function(selfA, time)
|
local updateContinous = function(selfA, time)
|
||||||
local next = selfA:UpdateTask()
|
local next = selfA:UpdateTask()
|
||||||
if next then
|
if next then
|
||||||
@@ -102,6 +107,7 @@ function InterceptGroup:SetTargetUnits(unitNames)
|
|||||||
self._updateTaskID = timer.scheduleFunction(updateContinous, self, timer.getTime() + 30)
|
self._updateTaskID = timer.scheduleFunction(updateContinous, self, timer.getTime() + 30)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param unit Unit
|
||||||
function InterceptGroup:RemoveTargetUnit(unit)
|
function InterceptGroup:RemoveTargetUnit(unit)
|
||||||
|
|
||||||
if not unit then return end
|
if not unit then return end
|
||||||
@@ -213,8 +219,6 @@ function InterceptGroup:UpdateTask()
|
|||||||
self._currentTargetName = nil
|
self._currentTargetName = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local speed = self._config:getMaxSpeed()
|
local speed = self._config:getMaxSpeed()
|
||||||
local interceptPoint = self:GetInterceptPoint(groupPoint, speed, closestUnit)
|
local interceptPoint = self:GetInterceptPoint(groupPoint, speed, closestUnit)
|
||||||
|
|
||||||
@@ -222,7 +226,8 @@ function InterceptGroup:UpdateTask()
|
|||||||
return 30 -- Return to base if no intercept point could be calculated
|
return 30 -- Return to base if no intercept point could be calculated
|
||||||
end
|
end
|
||||||
|
|
||||||
local mission = nil
|
---@type table
|
||||||
|
local mission
|
||||||
if self:IsInAir() == true then
|
if self:IsInAir() == true then
|
||||||
-- If the group is in the air, create an intercept mission
|
-- If the group is in the air, create an intercept mission
|
||||||
mission = INTERCEPT.getMissionFromInAir(
|
mission = INTERCEPT.getMissionFromInAir(
|
||||||
@@ -323,13 +328,13 @@ function InterceptGroup:InitWithName(groupName)
|
|||||||
configPart = string.sub(configPart, 2, #configPart)
|
configPart = string.sub(configPart, 2, #configPart)
|
||||||
local subsplit = Util.split_string(configPart, "|")
|
local subsplit = Util.split_string(configPart, "|")
|
||||||
if subsplit then
|
if subsplit then
|
||||||
for key, value in pairs(subsplit) do
|
for _, value in pairs(subsplit) do
|
||||||
local keySplit = Util.split_string(value, "]")
|
local keySplit = Util.split_string(value, "]")
|
||||||
local targetZone = keySplit[2]
|
local targetZone = keySplit[2]
|
||||||
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
|
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
|
||||||
local commaSeperated = Util.split_string(allActives, ",")
|
local commaSeperated = Util.split_string(allActives, ",")
|
||||||
for _, value in pairs(commaSeperated) do
|
for _, childValue in pairs(commaSeperated) do
|
||||||
local dashSeperated = Util.split_string(value, "-")
|
local dashSeperated = Util.split_string(childValue, "-")
|
||||||
if Util.tableLength(dashSeperated) > 1 then
|
if Util.tableLength(dashSeperated) > 1 then
|
||||||
local from = tonumber(dashSeperated[1])
|
local from = tonumber(dashSeperated[1])
|
||||||
local till = tonumber(dashSeperated[2])
|
local till = tonumber(dashSeperated[2])
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ local Util = require("classes.util.Util")
|
|||||||
local MissionEditorWarner = require("classes.util.MissionEditorWarnings")
|
local MissionEditorWarner = require("classes.util.MissionEditorWarnings")
|
||||||
|
|
||||||
---@class SweepGroup : AirGroup
|
---@class SweepGroup : AirGroup
|
||||||
---@field _targetZoneIdPerStage table<number, string>
|
---@field _targetZoneIdPerStage table<string, string>
|
||||||
---@field _currentTargetZoneID string?
|
---@field _currentTargetZoneID string?
|
||||||
local SweepGroup = {}
|
local SweepGroup = {}
|
||||||
SweepGroup.__index = SweepGroup
|
SweepGroup.__index = SweepGroup
|
||||||
@@ -26,9 +26,10 @@ function SweepGroup.New(groupName, config, logger, spawnManager)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param stageID string
|
||||||
---@return string?
|
---@return string?
|
||||||
function SweepGroup:GetZoneIDWhenStageID(stageID)
|
function SweepGroup:GetZoneIDWhenStageID(stageID)
|
||||||
return self._targetZoneIdPerStage[stageID]
|
return self._targetZoneIdPerStage[tostring(stageID)]
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string?
|
---@return string?
|
||||||
@@ -41,7 +42,7 @@ end
|
|||||||
---@field self SweepGroup
|
---@field self SweepGroup
|
||||||
|
|
||||||
---@param params SetTaskParams
|
---@param params SetTaskParams
|
||||||
local setMissionDelayedTask = function(params, time)
|
local setMissionDelayedTask = function(params, _)
|
||||||
params.self:SetMissionPrivate(params.task)
|
params.self:SetMissionPrivate(params.task)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -50,8 +51,6 @@ function SweepGroup:SendToZone(zone, targetZoneID, airbase)
|
|||||||
|
|
||||||
self._currentTargetZoneID = targetZoneID
|
self._currentTargetZoneID = targetZoneID
|
||||||
|
|
||||||
local group = Group.getByName(self._groupName)
|
|
||||||
|
|
||||||
local mission = SWEEP.getAsMissionFromAirbase(self._groupName, airbase, zone, self._config)
|
local mission = SWEEP.getAsMissionFromAirbase(self._groupName, airbase, zone, self._config)
|
||||||
if mission then
|
if mission then
|
||||||
---@type SetTaskParams
|
---@type SetTaskParams
|
||||||
@@ -82,13 +81,13 @@ function SweepGroup:InitWithName(groupName)
|
|||||||
|
|
||||||
local subsplit = Util.split_string(configPart, "|")
|
local subsplit = Util.split_string(configPart, "|")
|
||||||
if subsplit then
|
if subsplit then
|
||||||
for key, value in pairs(subsplit) do
|
for _, value in pairs(subsplit) do
|
||||||
local keySplit = Util.split_string(value, "]")
|
local keySplit = Util.split_string(value, "]")
|
||||||
local targetZone = keySplit[2]
|
local targetZone = keySplit[2]
|
||||||
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
|
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
|
||||||
local commaSeperated = Util.split_string(allActives, ",")
|
local commaSeperated = Util.split_string(allActives, ",")
|
||||||
for _, value in pairs(commaSeperated) do
|
for _, childValue in pairs(commaSeperated) do
|
||||||
local dashSeperated = Util.split_string(value, "-")
|
local dashSeperated = Util.split_string(childValue, "-")
|
||||||
if Util.tableLength(dashSeperated) > 1 then
|
if Util.tableLength(dashSeperated) > 1 then
|
||||||
local from = tonumber(dashSeperated[1])
|
local from = tonumber(dashSeperated[1])
|
||||||
local till = tonumber(dashSeperated[2])
|
local till = tonumber(dashSeperated[2])
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ DetectionManager.__index = DetectionManager
|
|||||||
|
|
||||||
|
|
||||||
---@param logger Logger
|
---@param logger Logger
|
||||||
|
---@return DetectionManager
|
||||||
function DetectionManager.New(logger)
|
function DetectionManager.New(logger)
|
||||||
local self = setmetatable({}, DetectionManager)
|
local self = setmetatable({}, DetectionManager)
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ function DetectionManager.New(logger)
|
|||||||
|
|
||||||
---@param selfA DetectionManager
|
---@param selfA DetectionManager
|
||||||
---@param time number
|
---@param time number
|
||||||
|
---@return number?
|
||||||
local updateDetectingUnitsTask = function(selfA, time)
|
local updateDetectingUnitsTask = function(selfA, time)
|
||||||
selfA:UpdateDetectingUnits()
|
selfA:UpdateDetectingUnits()
|
||||||
return time + 120
|
return time + 120
|
||||||
@@ -32,18 +34,19 @@ function DetectionManager.New(logger)
|
|||||||
|
|
||||||
---@param selfA DetectionManager
|
---@param selfA DetectionManager
|
||||||
---@param time number
|
---@param time number
|
||||||
|
---@return number?
|
||||||
local updateDetected = function(selfA, time)
|
local updateDetected = function(selfA, time)
|
||||||
selfA:UpdateDetectedUnits()
|
selfA:UpdateDetectedUnits()
|
||||||
return time + 10
|
return time + 10
|
||||||
end
|
end
|
||||||
timer.scheduleFunction(updateDetected, self, timer.getTime() + 130)
|
timer.scheduleFunction(updateDetected, self, timer.getTime() + 130)
|
||||||
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param unitName string
|
---@param unitName string
|
||||||
---@param coalitionSide CoalitionSide
|
---@param coalitionSide CoalitionSide
|
||||||
|
---@return boolean
|
||||||
function DetectionManager:IsUnitDetectedBy(unitName, coalitionSide)
|
function DetectionManager:IsUnitDetectedBy(unitName, coalitionSide)
|
||||||
local coalitionString = tostring(coalitionSide)
|
local coalitionString = tostring(coalitionSide)
|
||||||
if not self._detectedUnits[coalitionString] then
|
if not self._detectedUnits[coalitionString] then
|
||||||
@@ -57,7 +60,8 @@ function DetectionManager:IsUnitDetectedBy(unitName, coalitionSide)
|
|||||||
return timer.getTime() - self._detectedUnits[coalitionString][unitName] < 20
|
return timer.getTime() - self._detectedUnits[coalitionString][unitName] < 20
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return Array<string>
|
---@param coalitionSide CoalitionSide
|
||||||
|
---@return Array<string> detectedUnits
|
||||||
function DetectionManager:GetDetectedUnitsBy(coalitionSide)
|
function DetectionManager:GetDetectedUnitsBy(coalitionSide)
|
||||||
local coalitionString = tostring(coalitionSide)
|
local coalitionString = tostring(coalitionSide)
|
||||||
if not self._detectedUnits[coalitionString] then
|
if not self._detectedUnits[coalitionString] then
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ end
|
|||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@param weapon Weapon
|
---@param weapon Weapon
|
||||||
function RunwayBombingTracker:OnWeaponFired(unit, weapon, target)
|
function RunwayBombingTracker:OnWeaponFired(_, weapon, _)
|
||||||
|
|
||||||
if weapon == nil then
|
if weapon == nil then
|
||||||
return
|
return
|
||||||
@@ -56,7 +56,9 @@ end
|
|||||||
---@field self RunwayBombingTracker
|
---@field self RunwayBombingTracker
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
|
---@param time number
|
||||||
---@param weaponTrackingArgs WeaponTrackingArgs
|
---@param weaponTrackingArgs WeaponTrackingArgs
|
||||||
|
---@return number?
|
||||||
function RunwayBombingTracker.trackWeaponTask(weaponTrackingArgs, time)
|
function RunwayBombingTracker.trackWeaponTask(weaponTrackingArgs, time)
|
||||||
|
|
||||||
local weapon = weaponTrackingArgs.weapon
|
local weapon = weaponTrackingArgs.weapon
|
||||||
@@ -83,11 +85,7 @@ function RunwayBombingTracker.trackWeaponTask(weaponTrackingArgs, time)
|
|||||||
x = pos.x + velocity.x * nextInterval,
|
x = pos.x + velocity.x * nextInterval,
|
||||||
y = pos.z + velocity.z * nextInterval
|
y = pos.z + velocity.z * nextInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
self:OnWeaponImpact(weapon:getDesc(), impactPoint)
|
self:OnWeaponImpact(weapon:getDesc(), impactPoint)
|
||||||
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -106,11 +104,10 @@ function RunwayBombingTracker:OnWeaponImpact(weaponDesc, impactPoint)
|
|||||||
|
|
||||||
self._logger:debug("RunwayBombingTracker:OnWeaponImpact")
|
self._logger:debug("RunwayBombingTracker:OnWeaponImpact")
|
||||||
|
|
||||||
local warhead = weaponDesc.warhead
|
local warhead = weaponDesc.warhead --[[@as table]]
|
||||||
local explosiveMass = warhead.explosiveMass or warhead.shapedExplosiveMass
|
local explosiveMass = (warhead.explosiveMass or warhead.shapedExplosiveMass) --[[@as number]]
|
||||||
|
|
||||||
for runway, strikeMission in pairs(self.trackedRunways) do
|
|
||||||
|
|
||||||
|
for _, strikeMission in pairs(self.trackedRunways) do
|
||||||
local zone= strikeMission:GetRunwayZone()
|
local zone= strikeMission:GetRunwayZone()
|
||||||
|
|
||||||
if Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then
|
if Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ local CAP = {}
|
|||||||
---@param attackHelos boolean
|
---@param attackHelos boolean
|
||||||
---@return table
|
---@return table
|
||||||
local function GetCAPTargetTypes(attackHelos)
|
local function GetCAPTargetTypes(attackHelos)
|
||||||
|
---@type Array<string>
|
||||||
local targetTypes = {
|
local targetTypes = {
|
||||||
[1] = "Planes",
|
[1] = "Planes",
|
||||||
}
|
}
|
||||||
@@ -33,9 +34,6 @@ local function GetCAPPointFromTriggerZone(airBase, capZone)
|
|||||||
local furthestA = nil
|
local furthestA = nil
|
||||||
local furthestB = nil
|
local furthestB = nil
|
||||||
|
|
||||||
local furthestFromBase = nil
|
|
||||||
local furthestFromBaseDistance = 0
|
|
||||||
|
|
||||||
local furthestDistance = 0
|
local furthestDistance = 0
|
||||||
|
|
||||||
for indexA, pointA in ipairs(capZone.verts) do
|
for indexA, pointA in ipairs(capZone.verts) do
|
||||||
@@ -56,7 +54,10 @@ local function GetCAPPointFromTriggerZone(airBase, capZone)
|
|||||||
---@type Vec2
|
---@type Vec2
|
||||||
local baseVec2 = { x = baseVec3.x, y = baseVec3.z }
|
local baseVec2 = { x = baseVec3.x, y = baseVec3.z }
|
||||||
|
|
||||||
|
---@type Vec2
|
||||||
local pointA = capZone.verts[furthestA]
|
local pointA = capZone.verts[furthestA]
|
||||||
|
|
||||||
|
---@type Vec2
|
||||||
local pointB = capZone.verts[furthestB]
|
local pointB = capZone.verts[furthestB]
|
||||||
local furthest = pointA
|
local furthest = pointA
|
||||||
local closest = pointB
|
local closest = pointB
|
||||||
@@ -87,6 +88,7 @@ end
|
|||||||
---@param airbase Airbase
|
---@param airbase Airbase
|
||||||
---@param capZone SpearheadTriggerZone
|
---@param capZone SpearheadTriggerZone
|
||||||
---@param capConfig CapConfig
|
---@param capConfig CapConfig
|
||||||
|
---@return table
|
||||||
local GetOutboundTask = function(airbase, capZone, capConfig)
|
local GetOutboundTask = function(airbase, capZone, capConfig)
|
||||||
local airbaseVec3 = airbase:getPoint()
|
local airbaseVec3 = airbase:getPoint()
|
||||||
local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z }
|
local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z }
|
||||||
@@ -128,6 +130,7 @@ end
|
|||||||
---@param airbase Airbase
|
---@param airbase Airbase
|
||||||
---@param capZone SpearheadTriggerZone
|
---@param capZone SpearheadTriggerZone
|
||||||
---@param capConfig CapConfig
|
---@param capConfig CapConfig
|
||||||
|
---@return table
|
||||||
function CAP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig)
|
function CAP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig)
|
||||||
local points = {
|
local points = {
|
||||||
[1] = GetOutboundTask(airbase, capZone, capConfig),
|
[1] = GetOutboundTask(airbase, capZone, capConfig),
|
||||||
@@ -155,6 +158,7 @@ end
|
|||||||
---@param airbase Airbase
|
---@param airbase Airbase
|
||||||
---@param capZone SpearheadTriggerZone
|
---@param capZone SpearheadTriggerZone
|
||||||
---@param capConfig CapConfig
|
---@param capConfig CapConfig
|
||||||
|
---@return table
|
||||||
function CAP.getAsMission(groupName, airbase, capZone, capConfig)
|
function CAP.getAsMission(groupName, airbase, capZone, capConfig)
|
||||||
local points = {
|
local points = {
|
||||||
[1] = CAP.getAsTasking(groupName, airbase, capZone, capConfig),
|
[1] = CAP.getAsTasking(groupName, airbase, capZone, capConfig),
|
||||||
@@ -181,6 +185,7 @@ end
|
|||||||
---@param airbase Airbase
|
---@param airbase Airbase
|
||||||
---@param capZone SpearheadTriggerZone
|
---@param capZone SpearheadTriggerZone
|
||||||
---@param capConfig CapConfig
|
---@param capConfig CapConfig
|
||||||
|
---@return table
|
||||||
function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
|
function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
|
||||||
local duration = math.random(capConfig:getMinDurationOnStation(), capConfig:getMaxDurationOnStation()) or 1500
|
local duration = math.random(capConfig:getMinDurationOnStation(), capConfig:getMaxDurationOnStation()) or 1500
|
||||||
|
|
||||||
@@ -220,7 +225,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
|
|||||||
action = {
|
action = {
|
||||||
id = "Script",
|
id = "Script",
|
||||||
params = {
|
params = {
|
||||||
command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")"
|
command = "pcall(GlobalCapCallBacks.PublishOnStation, \"" .. groupName .. "\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +265,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
|
|||||||
},
|
},
|
||||||
stopCondition = {
|
stopCondition = {
|
||||||
duration = durationBefore10,
|
duration = durationBefore10,
|
||||||
condition = "return Spearhead.DcsUtil.NeedsRTBInTen(\"" .. groupName .. "\", 0.10)",
|
condition = "return GlobalCapCallBacks.NeedsRTBInTen(\"" .. groupName .. "\", 0.10)",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -273,7 +278,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
|
|||||||
action = {
|
action = {
|
||||||
id = "Script",
|
id = "Script",
|
||||||
params = {
|
params = {
|
||||||
command = "pcall(Spearhead.Events.PublishRTBInTen, \"" .. groupName .. "\")"
|
command = "pcall(GlobalCapCallBacks.PublishRTBInTen, \"" .. groupName .. "\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,7 +304,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
|
|||||||
},
|
},
|
||||||
stopCondition = {
|
stopCondition = {
|
||||||
duration = durationAfter10,
|
duration = durationAfter10,
|
||||||
condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\")",
|
condition = "return GlobalCapCallBacks.IsBingoFuel(\"" .. groupName .. "\", 0.10)",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -312,7 +317,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
|
|||||||
action = {
|
action = {
|
||||||
id = "Script",
|
id = "Script",
|
||||||
params = {
|
params = {
|
||||||
command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")"
|
command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
local DcsUtil = require("classes.util.DcsUtil")
|
||||||
|
local Events = require("classes.spearhead_events")
|
||||||
|
|
||||||
|
|
||||||
|
GlobalCapCallBacks = {}
|
||||||
|
|
||||||
|
function GlobalCapCallBacks.IsBingoFuel(groupName, fuelPercent)
|
||||||
|
return DcsUtil.IsBingoFuel(groupName, fuelPercent)
|
||||||
|
end
|
||||||
|
|
||||||
|
function GlobalCapCallBacks.NeedsRTBInTen(groupName, fuelOffset)
|
||||||
|
return DcsUtil.NeedsRTBInTen(groupName, fuelOffset)
|
||||||
|
end
|
||||||
|
|
||||||
|
function GlobalCapCallBacks.PublishRTBInTen(groupName)
|
||||||
|
return Events.PublishRTBInTen(groupName)
|
||||||
|
end
|
||||||
|
|
||||||
|
function GlobalCapCallBacks.PublishRTB(groupName)
|
||||||
|
return Events.PublishRTB(groupName)
|
||||||
|
end
|
||||||
|
|
||||||
|
function GlobalCapCallBacks.PublishOnStation(groupName)
|
||||||
|
return Events.PublishOnStation(groupName)
|
||||||
|
end
|
||||||
@@ -11,6 +11,7 @@ local INTERCEPT = {}
|
|||||||
---@param speed number?
|
---@param speed number?
|
||||||
---@param alt number?
|
---@param alt number?
|
||||||
---@param config CapConfig
|
---@param config CapConfig
|
||||||
|
---@return table
|
||||||
function INTERCEPT.getMissionFromAirbase(groupName, interceptPoint, airbase, config, speed, alt)
|
function INTERCEPT.getMissionFromAirbase(groupName, interceptPoint, airbase, config, speed, alt)
|
||||||
|
|
||||||
local airbaseVec3 = airbase:getPoint()
|
local airbaseVec3 = airbase:getPoint()
|
||||||
@@ -92,7 +93,7 @@ end
|
|||||||
---@param currentPoint Vec2
|
---@param currentPoint Vec2
|
||||||
---@param targetPosition Vec2
|
---@param targetPosition Vec2
|
||||||
---@param targetUnit Unit
|
---@param targetUnit Unit
|
||||||
---@param airbase any
|
---@param airbase Airbase
|
||||||
---@param config any
|
---@param config any
|
||||||
---@param speed number?
|
---@param speed number?
|
||||||
---@param alt number?
|
---@param alt number?
|
||||||
@@ -247,7 +248,7 @@ function INTERCEPT.getInterceptTaskPoint(groupName, currentPoint, targetPoint, a
|
|||||||
action = {
|
action = {
|
||||||
id = "Script",
|
id = "Script",
|
||||||
params = {
|
params = {
|
||||||
command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")"
|
command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,7 +380,7 @@ function INTERCEPT.getUnitInterceptTaskPoint(groupName, currentPoint, targetPosi
|
|||||||
action = {
|
action = {
|
||||||
id = "Script",
|
id = "Script",
|
||||||
params = {
|
params = {
|
||||||
command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")"
|
command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ local RTB = {}
|
|||||||
---@param airbase Airbase
|
---@param airbase Airbase
|
||||||
---@param missionPoint Vec2
|
---@param missionPoint Vec2
|
||||||
---@param capConfig CapConfig
|
---@param capConfig CapConfig
|
||||||
|
---@return table
|
||||||
function RTB.getAsMission(airbase, missionPoint, capConfig)
|
function RTB.getAsMission(airbase, missionPoint, capConfig)
|
||||||
return {
|
return {
|
||||||
id = "Mission",
|
id = "Mission",
|
||||||
@@ -113,6 +114,7 @@ end
|
|||||||
---comment
|
---comment
|
||||||
---@param airbase Airbase
|
---@param airbase Airbase
|
||||||
---@param missionPoint Vec2
|
---@param missionPoint Vec2
|
||||||
|
---@return table
|
||||||
---@param capConfig CapConfig
|
---@param capConfig CapConfig
|
||||||
function RTB.getApproachPoint(airbase, missionPoint, capConfig)
|
function RTB.getApproachPoint(airbase, missionPoint, capConfig)
|
||||||
|
|
||||||
@@ -147,6 +149,7 @@ function RTB.getApproachPoint(airbase, missionPoint, capConfig)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param airbase Airbase
|
---@param airbase Airbase
|
||||||
|
---@return table
|
||||||
function RTB.getInitialPoint(airbase)
|
function RTB.getInitialPoint(airbase)
|
||||||
local point = calcInitialPoint(airbase)
|
local point = calcInitialPoint(airbase)
|
||||||
return {
|
return {
|
||||||
@@ -170,6 +173,8 @@ function RTB.getInitialPoint(airbase)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param airbase Airbase
|
||||||
|
---@return table
|
||||||
function RTB.getLandingPoint(airbase)
|
function RTB.getLandingPoint(airbase)
|
||||||
local basePoint = airbase:getPoint()
|
local basePoint = airbase:getPoint()
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ local SWEEP = {}
|
|||||||
---@param attackHelos boolean
|
---@param attackHelos boolean
|
||||||
---@return table
|
---@return table
|
||||||
local function GetCAPTargetTypes(attackHelos)
|
local function GetCAPTargetTypes(attackHelos)
|
||||||
|
---@type Array<string>
|
||||||
local targetTypes = {
|
local targetTypes = {
|
||||||
[1] = "Planes",
|
[1] = "Planes",
|
||||||
}
|
}
|
||||||
@@ -49,7 +50,9 @@ local function GetCAPPointFromTriggerZone(airBase, capZone)
|
|||||||
---@type Vec2
|
---@type Vec2
|
||||||
local baseVec2 = { x = baseVec3.x, y = baseVec3.z }
|
local baseVec2 = { x = baseVec3.x, y = baseVec3.z }
|
||||||
|
|
||||||
|
---@type Vec2
|
||||||
local pointA = capZone.verts[furthestA]
|
local pointA = capZone.verts[furthestA]
|
||||||
|
---@type Vec2
|
||||||
local pointB = capZone.verts[furthestB]
|
local pointB = capZone.verts[furthestB]
|
||||||
local furthest = pointA
|
local furthest = pointA
|
||||||
local closest = pointB
|
local closest = pointB
|
||||||
@@ -80,6 +83,7 @@ end
|
|||||||
---@param airbase Airbase
|
---@param airbase Airbase
|
||||||
---@param capZone SpearheadTriggerZone
|
---@param capZone SpearheadTriggerZone
|
||||||
---@param capConfig CapConfig
|
---@param capConfig CapConfig
|
||||||
|
---@return table
|
||||||
local GetOutboundTask = function(airbase, capZone, capConfig)
|
local GetOutboundTask = function(airbase, capZone, capConfig)
|
||||||
local airbaseVec3 = airbase:getPoint()
|
local airbaseVec3 = airbase:getPoint()
|
||||||
local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z }
|
local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z }
|
||||||
@@ -121,6 +125,7 @@ end
|
|||||||
---@param airbase Airbase
|
---@param airbase Airbase
|
||||||
---@param capZone SpearheadTriggerZone
|
---@param capZone SpearheadTriggerZone
|
||||||
---@param capConfig CapConfig
|
---@param capConfig CapConfig
|
||||||
|
---@return table
|
||||||
function SWEEP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig)
|
function SWEEP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig)
|
||||||
|
|
||||||
local pointA, pointB, pointC = SWEEP.getAsTasking(groupName, airbase, capZone, capConfig)
|
local pointA, pointB, pointC = SWEEP.getAsTasking(groupName, airbase, capZone, capConfig)
|
||||||
@@ -189,7 +194,7 @@ function SWEEP.getAsTasking(groupName, airbase, capZone, capConfig)
|
|||||||
action = {
|
action = {
|
||||||
id = "Script",
|
id = "Script",
|
||||||
params = {
|
params = {
|
||||||
command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")"
|
command = "pcall(GlobalCapCallBacks.PublishOnStation, \"" .. groupName .. "\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,7 +275,7 @@ function SWEEP.getAsTasking(groupName, airbase, capZone, capConfig)
|
|||||||
action = {
|
action = {
|
||||||
id = "Script",
|
id = "Script",
|
||||||
params = {
|
params = {
|
||||||
command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")"
|
command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ function CapConfig.new()
|
|||||||
if SpearheadConfig == nil then SpearheadConfig = {} end
|
if SpearheadConfig == nil then SpearheadConfig = {} end
|
||||||
if SpearheadConfig.CapConfig == nil then SpearheadConfig.CapConfig = {} end
|
if SpearheadConfig.CapConfig == nil then SpearheadConfig.CapConfig = {} end
|
||||||
|
|
||||||
|
if type(SpearheadConfig.CapConfig.enabled) ~= "boolean" then
|
||||||
|
SpearheadConfig.CapConfig.enabled = true
|
||||||
|
end
|
||||||
|
|
||||||
local enabled = SpearheadConfig.CapConfig.enabled
|
local enabled = SpearheadConfig.CapConfig.enabled
|
||||||
if enabled == nil then enabled = true end
|
if enabled == nil then enabled = true end
|
||||||
self._isEnabled = enabled
|
self._isEnabled = enabled
|
||||||
@@ -34,7 +38,7 @@ function CapConfig.new()
|
|||||||
self._minDurationOnStation = 1200
|
self._minDurationOnStation = 1200
|
||||||
self._maxDurationOnStation = 2700
|
self._maxDurationOnStation = 2700
|
||||||
|
|
||||||
self._maxDeviationRange = 35 * 1852 -- in meters
|
self._maxDeviationRange = (tonumber(SpearheadConfig.CapConfig.maxCommitRange) or 35) * 1852 -- in meters
|
||||||
self._rearmDelay = tonumber(SpearheadConfig.CapConfig.rearmDelay) or 600
|
self._rearmDelay = tonumber(SpearheadConfig.CapConfig.rearmDelay) or 600
|
||||||
self._repairDelay = tonumber(SpearheadConfig.CapConfig.repairDelay) or 600
|
self._repairDelay = tonumber(SpearheadConfig.CapConfig.repairDelay) or 600
|
||||||
self._deathDelay = tonumber(SpearheadConfig.CapConfig.deathDelay) or 1800
|
self._deathDelay = tonumber(SpearheadConfig.CapConfig.deathDelay) or 1800
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
local briefingMessageTime = nil
|
|
||||||
|
|
||||||
---@class GlobalConfig
|
---@class GlobalConfig
|
||||||
---@field private _briefingTime number ;
|
---@field private _briefingTime number
|
||||||
|
---@field private _debugEnabled boolean
|
||||||
|
---@field private _debugMenuEnabled boolean
|
||||||
local GlobalConfig = {}
|
local GlobalConfig = {}
|
||||||
GlobalConfig.__index = GlobalConfig;
|
GlobalConfig.__index = GlobalConfig;
|
||||||
|
|
||||||
@@ -19,15 +17,32 @@ function GlobalConfig.New()
|
|||||||
if SpearheadConfig.briefingMessageDuration then
|
if SpearheadConfig.briefingMessageDuration then
|
||||||
self._briefingTime = SpearheadConfig.briefingMessageDuration
|
self._briefingTime = SpearheadConfig.briefingMessageDuration
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if SpearheadConfig.debugEnabled ~= nil then
|
||||||
|
self._debugEnabled = SpearheadConfig.debugEnabled
|
||||||
|
else
|
||||||
|
self._debugEnabled = false
|
||||||
|
end
|
||||||
|
|
||||||
|
self._debugMenuEnabled = SpearheadConfig.debugMenuEnabled == true
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function GlobalConfig:getBriefingTime()
|
function GlobalConfig:getBriefingTime()
|
||||||
return self._briefingTime or 30
|
return self._briefingTime or 60
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function GlobalConfig:isDebugEnabled()
|
||||||
|
return self._debugEnabled or false
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return boolean
|
||||||
|
function GlobalConfig:isDebugMenuEnabled()
|
||||||
|
return self._debugMenuEnabled or false
|
||||||
|
end
|
||||||
|
|
||||||
return GlobalConfig
|
return GlobalConfig
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
---@class PersistenceConfig
|
||||||
|
---@field private _enabled boolean
|
||||||
|
---@field private directory string?
|
||||||
|
---@field private fileName string?
|
||||||
|
local PersistenceConfig = {}
|
||||||
|
|
||||||
|
function PersistenceConfig.new()
|
||||||
|
local self = setmetatable({}, { __index = PersistenceConfig })
|
||||||
|
|
||||||
|
if not SpearheadConfig then SpearheadConfig = {} end
|
||||||
|
if not SpearheadConfig.Persistence then SpearheadConfig.Persistence = {} end
|
||||||
|
self._enabled = SpearheadConfig.Persistence.enabled == true
|
||||||
|
self.directory = SpearheadConfig.Persistence.directory
|
||||||
|
self.fileName = SpearheadConfig.Persistence.fileName
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function PersistenceConfig:isEnabled()
|
||||||
|
return self._enabled == true
|
||||||
|
end
|
||||||
|
|
||||||
|
function PersistenceConfig:getDirectory()
|
||||||
|
return self.directory
|
||||||
|
end
|
||||||
|
|
||||||
|
function PersistenceConfig:getFileName()
|
||||||
|
return self.fileName
|
||||||
|
end
|
||||||
|
|
||||||
|
return PersistenceConfig
|
||||||
@@ -7,29 +7,47 @@
|
|||||||
--- @field startingStage integer
|
--- @field startingStage integer
|
||||||
--- @field maxMissionsPerStage integer
|
--- @field maxMissionsPerStage integer
|
||||||
--- @field AmountPreactivateStage integer
|
--- @field AmountPreactivateStage integer
|
||||||
|
--- @field briefingOnSpawnEnabled boolean
|
||||||
local StageConfig = {};
|
local StageConfig = {};
|
||||||
|
StageConfig.__index = StageConfig
|
||||||
|
|
||||||
|
local Logger = require("classes.util.Logger")
|
||||||
|
local _logger = Logger.new("StageConfig", Logger.LogLevel)
|
||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@return StageConfig
|
---@return StageConfig
|
||||||
function StageConfig:new()
|
local function new()
|
||||||
|
|
||||||
if SpearheadConfig == nil then SpearheadConfig = {} end
|
if SpearheadConfig == nil then
|
||||||
if SpearheadConfig.StageConfig == nil then SpearheadConfig.StageConfig = {} end
|
_logger:warn("SpearheadConfig is nil, creating default SpearheadConfig")
|
||||||
|
SpearheadConfig = {}
|
||||||
|
end
|
||||||
|
|
||||||
---@type StageConfig
|
if SpearheadConfig.StageConfig == nil then
|
||||||
local o = {
|
_logger:warn("SpearheadConfig.StageConfig is nil, creating default StageConfig")
|
||||||
isEnabled = SpearheadConfig.StageConfig.enabled or true,
|
SpearheadConfig.StageConfig = {}
|
||||||
isDrawStagesEnabled = SpearheadConfig.StageConfig.drawStages or true,
|
end
|
||||||
isAutoStages = SpearheadConfig.StageConfig.autoStages or true,
|
|
||||||
startingStage = SpearheadConfig.StageConfig.startingStage or 1,
|
|
||||||
maxMissionsPerStage = SpearheadConfig.StageConfig.maxMissionStage or 10,
|
|
||||||
isDrawPreActivatedEnabled = SpearheadConfig.StageConfig.drawPreActivated or true,
|
|
||||||
AmountPreactivateStage = SpearheadConfig.StageConfig.preactivateStage or 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
setmetatable(o, { __index = self })
|
local self = setmetatable({}, StageConfig)
|
||||||
|
self.isEnabled = SpearheadConfig.StageConfig.enabled ~= false
|
||||||
|
self.isDrawStagesEnabled = SpearheadConfig.StageConfig.drawStages ~= false
|
||||||
|
self.isAutoStages = SpearheadConfig.StageConfig.autoStages ~= false
|
||||||
|
self.startingStage = SpearheadConfig.StageConfig.startingStage or 1
|
||||||
|
self.maxMissionsPerStage = SpearheadConfig.StageConfig.maxMissionStage or 10
|
||||||
|
self.isDrawPreActivatedEnabled = SpearheadConfig.StageConfig.drawPreActivated ~= false
|
||||||
|
self.AmountPreactivateStage = SpearheadConfig.StageConfig.preactivateStage or 1
|
||||||
|
self.briefingOnSpawnEnabled = SpearheadConfig.StageConfig.briefingOnSpawn ~= false
|
||||||
|
|
||||||
return o;
|
_logger:info("Successfully created StageConfig Object")
|
||||||
|
|
||||||
|
return self;
|
||||||
|
end
|
||||||
|
|
||||||
|
local config = new();
|
||||||
|
|
||||||
|
---@return StageConfig
|
||||||
|
function StageConfig:getInstance()
|
||||||
|
return config
|
||||||
end
|
end
|
||||||
|
|
||||||
return StageConfig
|
return StageConfig
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
local Logger = require("classes.util.Logger")
|
||||||
|
local StageRepository = require("classes.stageClasses.StageRepository")
|
||||||
|
|
||||||
|
---@class DebugMenu
|
||||||
|
---@field private _logger Logger
|
||||||
|
---@field private _stageRepository StageRepository
|
||||||
|
local DebugMenu = {}
|
||||||
|
DebugMenu.__index = DebugMenu
|
||||||
|
|
||||||
|
function DebugMenu.new()
|
||||||
|
local self = setmetatable({}, DebugMenu)
|
||||||
|
self._logger = Logger.new("DebugMenu")
|
||||||
|
self._stageRepository = StageRepository.getInstance()
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
local menuName = "Spearhead Debug"
|
||||||
|
local debugMenuPath = { [1] = menuName }
|
||||||
|
function DebugMenu:RegisterMenus()
|
||||||
|
|
||||||
|
missionCommands.addSubMenu(menuName, {})
|
||||||
|
|
||||||
|
local refresh = function(params)
|
||||||
|
local selfA = params.self --[[@as DebugMenu]]
|
||||||
|
selfA:RefreshMenu()
|
||||||
|
end
|
||||||
|
missionCommands.addCommand("Refresh Menu", debugMenuPath, refresh, { self = self })
|
||||||
|
|
||||||
|
self:AddStageOptions()
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function DebugMenu:RefreshMenu()
|
||||||
|
|
||||||
|
missionCommands.removeItem(debugMenuPath)
|
||||||
|
self:RegisterMenus()
|
||||||
|
end
|
||||||
|
|
||||||
|
function DebugMenu:AddStageOptions()
|
||||||
|
local stageMenuTable = missionCommands.addSubMenu("Stages", debugMenuPath)
|
||||||
|
|
||||||
|
local stageLanes = self._stageRepository:getAllStageLanes()
|
||||||
|
for _, stageLane in pairs(stageLanes) do
|
||||||
|
|
||||||
|
local stages = stageLane:GetStagesAtIndex(stageLane:GetActiveStageIndex())
|
||||||
|
if stages then
|
||||||
|
for _, stage in pairs(stages) do
|
||||||
|
local stageLaneId = stageLane:GetStageLaneIdentifier() or ""
|
||||||
|
local stageIndex = stage:GetStageIndex()
|
||||||
|
local stageName = stage:GetStageName()
|
||||||
|
|
||||||
|
local stageMenuName = stageLaneId .. stageIndex .. "_" .. stageName
|
||||||
|
local currentStageMenuTable = missionCommands.addSubMenu(stageMenuName, stageMenuTable)
|
||||||
|
|
||||||
|
---@class CompleteStageParams
|
||||||
|
---@field self DebugMenu
|
||||||
|
---@field stage Stage
|
||||||
|
|
||||||
|
---@param params CompleteStageParams
|
||||||
|
local completeStage = function(params)
|
||||||
|
local stageA = params.stage
|
||||||
|
local missions = stageA:GetMissions()
|
||||||
|
for _, mission in pairs(missions) do
|
||||||
|
mission:ForceMissionComplete()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type CompleteStageParams
|
||||||
|
local params = { self = self, stage = stage }
|
||||||
|
missionCommands.addCommand("Complete Stage", currentStageMenuTable, completeStage, params)
|
||||||
|
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self._logger:info("No stages found for lane: " .. (stageLane:GetStageLaneIdentifier() or "default") .. " at index: " .. (stageLane:GetActiveStageIndex() or "nil"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return DebugMenu
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
do -- mission aliases
|
|
||||||
|
|
||||||
--- @alias LogLevel
|
|
||||||
--- | "DEBUG"
|
|
||||||
--- | "INFO"
|
|
||||||
--- | "WARN"
|
|
||||||
--- | "ERROR"
|
|
||||||
--- | "NONE"
|
|
||||||
|
|
||||||
---@class Array<T>: { [integer]: T }
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
---@class KeyValuePair
|
|
||||||
---@field key string
|
|
||||||
---@field value any
|
|
||||||
@@ -4,37 +4,43 @@ local MissionEditorWarning = require("classes.util.MissionEditorWarnings")
|
|||||||
local RouteUtil = require("classes.spearhead_routeutil")
|
local RouteUtil = require("classes.spearhead_routeutil")
|
||||||
local SpearheadEvents = require("classes.spearhead_events")
|
local SpearheadEvents = require("classes.spearhead_events")
|
||||||
|
|
||||||
---@class FleetGroup
|
---@class FleetGroup : OnStageChangedListener
|
||||||
|
---@field private fleetGroupName string
|
||||||
|
---@field private logger Logger
|
||||||
|
---@field private fleetNameIdentifier string
|
||||||
|
---@field private targetZonePerStage table<string, string>
|
||||||
|
---@field private currentTargetZone string
|
||||||
|
---@field private pointsPerZone table<string, {pointA: Vec3, pointB: Vec3}>
|
||||||
local FleetGroup = {}
|
local FleetGroup = {}
|
||||||
|
FleetGroup.__index = FleetGroup
|
||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@param fleetGroupName string
|
---@param fleetGroupName string
|
||||||
---@param database Database
|
---@param database Database
|
||||||
---@param logger Logger
|
---@param logger Logger
|
||||||
---@return nil
|
---@return FleetGroup?
|
||||||
function FleetGroup:new(fleetGroupName, database, logger)
|
function FleetGroup.new(fleetGroupName, database, logger)
|
||||||
local o = {}
|
|
||||||
|
|
||||||
setmetatable(o, { __index = self })
|
local self = setmetatable({}, FleetGroup)
|
||||||
|
|
||||||
o.fleetGroupName = fleetGroupName
|
self.fleetGroupName = fleetGroupName
|
||||||
o.logger = logger
|
self.logger = logger
|
||||||
|
|
||||||
local split_name = Util.split_string(fleetGroupName, "_")
|
local split_name = Util.split_string(fleetGroupName, "_")
|
||||||
if Util.tableLength(split_name) < 2 then
|
if Util.tableLength(split_name) < 2 then
|
||||||
MissionEditorWarning.Add("CARRIERGROUP should have at least 2 parts. CARRIERGROUP_<fleetname>")
|
MissionEditorWarning.Add("CARRIERGROUP should have at least 2 parts. CARRIERGROUP_<fleetname>")
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
o.fleetNameIdentifier = split_name[2]
|
self.fleetNameIdentifier = split_name[2]
|
||||||
|
|
||||||
o.targetZonePerStage = {}
|
self.targetZonePerStage = {}
|
||||||
o.currentTargetZone = nil
|
self.currentTargetZone = nil
|
||||||
o.pointsPerZone = {}
|
self.pointsPerZone = {}
|
||||||
|
|
||||||
do --INIT
|
do --INIT
|
||||||
local carrierRouteZones = database:getCarrierRouteZones()
|
local carrierRouteZones = database:getCarrierRouteZones()
|
||||||
for _, zoneName in pairs(carrierRouteZones) do
|
for _, zoneName in pairs(carrierRouteZones) do
|
||||||
if Util.strContains(string.lower(zoneName), "_".. string.lower(o.fleetNameIdentifier) .. "_" ) == true then
|
if Util.strContains(string.lower(zoneName), "_".. string.lower(self.fleetNameIdentifier) .. "_" ) == true then
|
||||||
local zone = DcsUtil.getZoneByName(zoneName)
|
local zone = DcsUtil.getZoneByName(zoneName)
|
||||||
if zone and zone.zone_type == DcsUtil.ZoneType.Polygon then
|
if zone and zone.zone_type == DcsUtil.ZoneType.Polygon then
|
||||||
local split_string = Util.split_string(zoneName, "_")
|
local split_string = Util.split_string(zoneName, "_")
|
||||||
@@ -43,18 +49,18 @@ function FleetGroup:new(fleetGroupName, database, logger)
|
|||||||
"CARRIERROUTE should at least have 3 parts. Check the documentation for: " .. zoneName)
|
"CARRIERROUTE should at least have 3 parts. Check the documentation for: " .. zoneName)
|
||||||
else
|
else
|
||||||
|
|
||||||
---@param zone SpearheadTriggerZone
|
---@param zoneA SpearheadTriggerZone
|
||||||
---@return Vec2, Vec2
|
---@return Vec2, Vec2
|
||||||
local function GetTwoFurthestPoints(zone)
|
local function GetTwoFurthestPoints(zoneA)
|
||||||
|
|
||||||
local biggest = nil
|
local biggest = nil
|
||||||
local biggestA = zone.verts[1]
|
local biggestA = zoneA.verts[1]
|
||||||
local biggestB = zone.verts[2]
|
local biggestB = zoneA.verts[2]
|
||||||
|
|
||||||
for i = 1, 3 do
|
for i = 1, 3 do
|
||||||
for ii = i + 1, 4 do
|
for ii = i + 1, 4 do
|
||||||
local a = zone.verts[i]
|
local a = zoneA.verts[i]
|
||||||
local b = zone.verts[ii]
|
local b = zoneA.verts[ii]
|
||||||
local dist = Util.VectorDistance2d(a, b)
|
local dist = Util.VectorDistance2d(a, b)
|
||||||
|
|
||||||
if biggest == nil or dist > biggest then
|
if biggest == nil or dist > biggest then
|
||||||
@@ -72,7 +78,7 @@ function FleetGroup:new(fleetGroupName, database, logger)
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if Util.startswith(namePart, "%[") == true then
|
if Util.startsWith(namePart, "%[") == true then
|
||||||
namePart = Util.split_string(namePart, "[")[1]
|
namePart = Util.split_string(namePart, "[")[1]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -100,9 +106,15 @@ function FleetGroup:new(fleetGroupName, database, logger)
|
|||||||
local first, second = getMinMaxStage(split_string[3])
|
local first, second = getMinMaxStage(split_string[3])
|
||||||
if first ~= nil and second ~= nil then
|
if first ~= nil and second ~= nil then
|
||||||
for i = first, second do
|
for i = first, second do
|
||||||
o.targetZonePerStage[tostring(i)] = zoneName
|
self.targetZonePerStage[tostring(i)] = zoneName
|
||||||
end
|
end
|
||||||
o.pointsPerZone[zoneName] = { pointA = { x = pointA.x, z = pointA.y, y = 0 }, pointB = { x = pointB.x, z = pointB.y, y = 0} }
|
---@type {pointA: Vec3, pointB: Vec3}
|
||||||
|
local entry = {
|
||||||
|
pointA = {x = pointA.x, z = pointA.y, y = 0},
|
||||||
|
pointB = {x = pointB.x, z = pointB.y, y = 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pointsPerZone[zoneName] = entry
|
||||||
else
|
else
|
||||||
MissionEditorWarning.Add("CARRIERROUTE zone stage numbers not in the format _[<number>-<number>]: " .. zoneName)
|
MissionEditorWarning.Add("CARRIERROUTE zone stage numbers not in the format _[<number>-<number>]: " .. zoneName)
|
||||||
end
|
end
|
||||||
@@ -114,30 +126,32 @@ function FleetGroup:new(fleetGroupName, database, logger)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local SetTaskAsync = function(input, time)
|
SpearheadEvents.AddStageNumberChangedListener(self)
|
||||||
local targetZone = input.targetZone
|
return self
|
||||||
local task = input.task
|
end
|
||||||
local groupName = input.groupName
|
|
||||||
local logger = input.logger
|
local SetTaskAsync = function(input, _)
|
||||||
|
local targetZone = input.targetZone --[[@as string]]
|
||||||
|
local task = input.task --[[@as table]]
|
||||||
|
local groupName = input.groupName --[[@as string]]
|
||||||
|
local l_logger = input.logger --[[@as Logger]]
|
||||||
|
|
||||||
local group = Group.getByName(groupName)
|
local group = Group.getByName(groupName)
|
||||||
if group then
|
if group then
|
||||||
logger:info("Sending " .. groupName .. " to " .. targetZone)
|
l_logger:info("Sending " .. groupName .. " to " .. targetZone)
|
||||||
group:getController():setTask(task)
|
group:getController():setTask(task)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
o.OnStageNumberChanged = function(self, number)
|
function FleetGroup:OnStageNumberChanged(number, laneIdentifier)
|
||||||
|
-- only react on "main" lane changes, ignore other lanes for now
|
||||||
|
if laneIdentifier ~= nil then return end
|
||||||
local targetZone = self.targetZonePerStage[tostring(number)]
|
local targetZone = self.targetZonePerStage[tostring(number)]
|
||||||
if targetZone and targetZone ~= self.currentTargetZone then
|
if targetZone and targetZone ~= self.currentTargetZone then
|
||||||
local points = self.pointsPerZone[targetZone]
|
local points = self.pointsPerZone[targetZone]
|
||||||
local task = RouteUtil.CreateCarrierRacetrack(points.pointA, points.pointB)
|
local task = RouteUtil.CreateCarrierRacetrack(points.pointA, points.pointB)
|
||||||
timer.scheduleFunction(SetTaskAsync, { task = task, targetZone = targetZone, groupName = self.fleetGroupName, logger = self.logger }, timer.getTime() + 5)
|
timer.scheduleFunction(SetTaskAsync, { task = task, targetZone = targetZone, groupName = self.fleetGroupName, logger = self.logger }, timer.getTime() + 5)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
SpearheadEvents.AddStageNumberChangedListener(o)
|
|
||||||
return o
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return FleetGroup
|
return FleetGroup
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ GlobalFleetManager.start = function(database)
|
|||||||
|
|
||||||
local all_groups = MizGroupsManager.getAllGroupNames()
|
local all_groups = MizGroupsManager.getAllGroupNames()
|
||||||
for _, groupName in pairs(all_groups) do
|
for _, groupName in pairs(all_groups) do
|
||||||
if Util.startswith(string.lower(groupName), "carriergroup" ) == true then
|
if Util.startsWith(string.lower(groupName), "carriergroup" ) == true then
|
||||||
logger:info("Registering " .. groupName .. " as a managed fleet")
|
logger:info("Registering " .. groupName .. " as a managed fleet")
|
||||||
local carrierGroup = FleetGroup:new(groupName, database, logger)
|
local carrierGroup = FleetGroup.new(groupName, database, logger)
|
||||||
table.insert(fleetGroups, carrierGroup)
|
table.insert(fleetGroups, carrierGroup)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,24 +15,45 @@ MizGroupsManager._groupNames = {}
|
|||||||
MizGroupsManager._spawnTemplateData = {}
|
MizGroupsManager._spawnTemplateData = {}
|
||||||
|
|
||||||
do --init
|
do --init
|
||||||
for coalition_name, coalition_data in pairs(env.mission.coalition) do
|
|
||||||
local coalition_nr = DcsUtil.stringToCoalition(coalition_name)
|
---@param coalition_name string
|
||||||
|
---@return Coalition
|
||||||
|
local getCoalitionData = function(coalition_name)
|
||||||
|
return env.mission.coalition[coalition_name]
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param country Country
|
||||||
|
---@param key string
|
||||||
|
---@return Groups?
|
||||||
|
local getGroupsData = function(country, key)
|
||||||
|
if key == "planes" then return country.plane end
|
||||||
|
if key == "helicopters" then return country.helicopter end
|
||||||
|
if key == "ground" then return country.vehicle end
|
||||||
|
if key == "ships" then return country.ship end
|
||||||
|
if key == "statics" then return country.static end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, coalition_name in ipairs({"neutral", "blue", "red"}) do
|
||||||
|
local coalition_data = getCoalitionData(coalition_name)
|
||||||
if coalition_data.country then
|
if coalition_data.country then
|
||||||
for country_index, country_data in pairs(coalition_data.country) do
|
for _, country_data in pairs(coalition_data.country) do
|
||||||
for category_name, categorydata in pairs(country_data) do
|
for _, category_name in pairs(DcsUtil.getAllGroupCategoryNames()) do
|
||||||
local category_id = DcsUtil.stringToGroupCategory(category_name)
|
local category_id = DcsUtil.stringToGroupCategory(category_name)
|
||||||
if category_id ~= nil and type(categorydata) == "table" and categorydata.group ~= nil and type(categorydata.group) == "table" then
|
local groups_data = getGroupsData(country_data, category_name)
|
||||||
for group_index, group in pairs(categorydata.group) do
|
|
||||||
local name = group.name
|
if category_id ~= nil and type(groups_data) == "table" and groups_data.group ~= nil and type(groups_data.group) == "table" then
|
||||||
|
for _, group in pairs(groups_data.group) do
|
||||||
|
local name = group.name --[[@as string]]
|
||||||
local skippable = false
|
local skippable = false
|
||||||
local isStatic = false
|
local isStatic = false
|
||||||
if category_id == DcsUtil.GroupCategory.STATIC then
|
if category_id == DcsUtil.GroupCategory.STATIC then
|
||||||
isStatic = true
|
isStatic = true
|
||||||
local unit = group.units[1]
|
local unit = group.units[1] --[[@as table]]
|
||||||
if unit and unit.category == "Heliports" then
|
if unit and unit.category == "Heliports" then
|
||||||
skippable = true
|
skippable = true
|
||||||
elseif unit and unit.name then
|
elseif unit and unit.name then
|
||||||
name = unit.name
|
name = unit.name --[[@as string]]
|
||||||
else
|
else
|
||||||
env.error("Group " .. name .. " has no units, skipping it.")
|
env.error("Group " .. name .. " has no units, skipping it.")
|
||||||
skippable = true
|
skippable = true
|
||||||
@@ -72,6 +93,7 @@ function MizGroupsManager.IsGroupStatic(groupName)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param groupName string
|
||||||
---@return SpawnData?
|
---@return SpawnData?
|
||||||
function MizGroupsManager.getSpawnTemplateData(groupName)
|
function MizGroupsManager.getSpawnTemplateData(groupName)
|
||||||
return MizGroupsManager._spawnTemplateData[groupName]
|
return MizGroupsManager._spawnTemplateData[groupName]
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ SpawnManager.__index = SpawnManager
|
|||||||
---@field uncontrolled boolean? Sets the group to be uncontrolled on spawn
|
---@field uncontrolled boolean? Sets the group to be uncontrolled on spawn
|
||||||
|
|
||||||
---@param logger Logger
|
---@param logger Logger
|
||||||
|
---@return SpawnManager
|
||||||
function SpawnManager.new(logger)
|
function SpawnManager.new(logger)
|
||||||
local self = setmetatable({}, SpawnManager)
|
local self = setmetatable({}, SpawnManager)
|
||||||
|
|
||||||
self._logger = logger
|
self._logger = logger
|
||||||
|
|
||||||
self._persistedUnits = {} -- Stores units that should be persisted by name
|
self._persistedUnits = {} -- Stores units that should be persisted by name
|
||||||
|
|
||||||
return self
|
return self
|
||||||
@@ -44,7 +44,7 @@ function SpawnManager:SpawnGroup(groupName, overrides, isGroupPersistant)
|
|||||||
if spawnData.isStatic == true then
|
if spawnData.isStatic == true then
|
||||||
return self:SpawnStaticInternal(groupName, spawnData, overrides, isGroupPersistant), true
|
return self:SpawnStaticInternal(groupName, spawnData, overrides, isGroupPersistant), true
|
||||||
else
|
else
|
||||||
return self:SpawnGroupInternal(groupName, spawnData, overrides, isGroupPersistant), false
|
return self:SpawnGroupInternal(spawnData, overrides, isGroupPersistant), false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -103,20 +103,20 @@ function SpawnManager:OnUnitLost(object)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param groupName string
|
||||||
function SpawnManager:SpawnCorpsesOnly(groupName)
|
function SpawnManager:SpawnCorpsesOnly(groupName)
|
||||||
|
if groupName == nil then return end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
do --- privates
|
do --- privates
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
---@param groupName string
|
|
||||||
---@param spawnData SpawnData
|
---@param spawnData SpawnData
|
||||||
---@param override SpawnOverrides?
|
---@param override SpawnOverrides?
|
||||||
---@param isPersistent boolean?
|
---@param isPersistent boolean?
|
||||||
---@return Group|nil
|
---@return Group|nil
|
||||||
function SpawnManager:SpawnGroupInternal(groupName, spawnData, override, isPersistent)
|
function SpawnManager:SpawnGroupInternal(spawnData, override, isPersistent)
|
||||||
|
|
||||||
if not spawnData then return end
|
if not spawnData then return end
|
||||||
|
|
||||||
local country = spawnData.country
|
local country = spawnData.country
|
||||||
@@ -125,37 +125,31 @@ do --- privates
|
|||||||
end
|
end
|
||||||
|
|
||||||
local spawnTemplate = Util.deepCopyTable(spawnData.groupTemplate)
|
local spawnTemplate = Util.deepCopyTable(spawnData.groupTemplate)
|
||||||
---@type Array<string>
|
|
||||||
local removeableUnitNames = {}
|
|
||||||
--[[
|
--[[
|
||||||
TODO: Spawn units at "current"/LastKnown position with them going to the next waypoint.
|
TODO: Spawn units at "current"/LastKnown position with them going to the next waypoint.
|
||||||
ONLY when perstable data is found.
|
ONLY when perstable data is found.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
if spawnTemplate and spawnTemplate["units"] then
|
if spawnTemplate and spawnTemplate["units"] then
|
||||||
for _, unit in pairs(spawnTemplate["units"]) do
|
local units = spawnTemplate["units"] --[[@as Array<table>]]
|
||||||
local name = unit["name"]
|
for _, unit in pairs(units) do
|
||||||
|
local name = unit["name"] --[[@as string]]
|
||||||
|
|
||||||
SpearheadEvents.addOnUnitLostEventListener(name, self)
|
SpearheadEvents.addOnUnitLostEventListener(name, self)
|
||||||
local state = Persistence.UnitState(name)
|
|
||||||
if state then
|
|
||||||
if state.isDead == true then
|
|
||||||
removeableUnitNames[#removeableUnitNames+1] = name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if override and override.emptyLoadouts == true then
|
if override and override.emptyLoadouts == true then
|
||||||
if unit["payload"] and unit["payload"]["pylons"] then
|
if unit["payload"] and unit["payload"]["pylons"] then
|
||||||
unit["payload"]["pylons"] = {}
|
local payload = unit["payload"] --[[@as table]]
|
||||||
|
payload["pylons"] = {} --[[@as table]]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if unit["parking"] then
|
if unit["parking"] then
|
||||||
unit["parking_landing"] = unit["parking"]
|
unit["parking_landing"] = unit["parking"] --[[@as number]]
|
||||||
end
|
end
|
||||||
|
|
||||||
if unit["parking_id"] then
|
if unit["parking_id"] then
|
||||||
unit["parking_landing_id"] = unit["parking_id"]
|
unit["parking_landing_id"] = unit["parking_id"] --[[@as number]]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -169,7 +163,6 @@ do --- privates
|
|||||||
|
|
||||||
local group = coalition.addGroup(country, spawnData.category, spawnTemplate)
|
local group = coalition.addGroup(country, spawnData.category, spawnTemplate)
|
||||||
|
|
||||||
|
|
||||||
for _, unit in pairs(group:getUnits()) do
|
for _, unit in pairs(group:getUnits()) do
|
||||||
self:CheckUnitAndReplaceIfPersistentDead(unit)
|
self:CheckUnitAndReplaceIfPersistentDead(unit)
|
||||||
if isPersistent == true then
|
if isPersistent == true then
|
||||||
@@ -181,7 +174,6 @@ do --- privates
|
|||||||
return group
|
return group
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -211,9 +203,11 @@ do --- privates
|
|||||||
spawnTemplate["x"] = persistentState.pos.x
|
spawnTemplate["x"] = persistentState.pos.x
|
||||||
spawnTemplate["y"] = persistentState.pos.z
|
spawnTemplate["y"] = persistentState.pos.z
|
||||||
if spawnTemplate["units"] and spawnTemplate["units"][1] then
|
if spawnTemplate["units"] and spawnTemplate["units"][1] then
|
||||||
spawnTemplate["units"][1]["x"] = persistentState.pos.x
|
local units = spawnTemplate["units"] --[[@as Array<table>]]
|
||||||
spawnTemplate["units"][1]["y"] = persistentState.pos.z
|
local firstUnit = units[1] --[[@as table]]
|
||||||
spawnTemplate["units"][1]["heading"] = persistentState.heading or 0
|
firstUnit["x"] = persistentState.pos.x --[[@as number]]
|
||||||
|
firstUnit["y"] = persistentState.pos.z --[[@as number]]
|
||||||
|
firstUnit["heading"] = persistentState.heading or 0 --[[@as number]]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ do
|
|||||||
---@field unitsStates table<string, UnitState>
|
---@field unitsStates table<string, UnitState>
|
||||||
---@field random_missions table<string, MissionState>
|
---@field random_missions table<string, MissionState>
|
||||||
---@field deliveredKilos table<string, number>
|
---@field deliveredKilos table<string, number>
|
||||||
---@field activeStage integer|nil
|
---@field activeStage number? required for backwards compatibility
|
||||||
|
---@field activeStageInStageLane table<string, number>
|
||||||
|
|
||||||
---@class UnitState
|
---@class UnitState
|
||||||
---@field isDead boolean
|
---@field isDead boolean
|
||||||
@@ -33,7 +34,8 @@ do
|
|||||||
unitsStates = {},
|
unitsStates = {},
|
||||||
random_missions = {},
|
random_missions = {},
|
||||||
deliveredKilos = {},
|
deliveredKilos = {},
|
||||||
activeStage = nil
|
activeStage = nil, -- Backwards compatibility for previous version without lanes
|
||||||
|
activeStageInStageLane = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
local logger = {}
|
local logger = {}
|
||||||
@@ -110,7 +112,7 @@ do
|
|||||||
logger:info("Wrote persistence data to file")
|
logger:info("Wrote persistence data to file")
|
||||||
end
|
end
|
||||||
|
|
||||||
local UpdateContinuous = function(null, time)
|
local UpdateContinuous = function(_, time)
|
||||||
env.info("[Spearhead][Persistence] Checking up on persistence state...")
|
env.info("[Spearhead][Persistence] Checking up on persistence state...")
|
||||||
if Persistence._updateRequired == true then
|
if Persistence._updateRequired == true then
|
||||||
local status, result = pcall(writeToFile)
|
local status, result = pcall(writeToFile)
|
||||||
@@ -133,20 +135,22 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local warnForNonPersistenceContinous = function(null, time)
|
local warnForNonPersistenceContinous = function(_, time)
|
||||||
trigger.action.outText("Persistence was enabeld, however, io and lfs are not available and no persistence will be done. Make sure to either disable persistence or fix the issues before continuing.", 10)
|
trigger.action.outText("Persistence was enabeld, however, io and lfs are not available and no persistence will be done. Make sure to either disable persistence or fix the issues before continuing.", 10)
|
||||||
return time + 9
|
return time + 9
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param dir string @BaseDirectory
|
---@param dir string @BaseDirectory
|
||||||
---@return string
|
---@param startsWith string
|
||||||
|
---@param default string?
|
||||||
|
---@return string?
|
||||||
local getLastFileOrDefault = function(dir, startsWith, default)
|
local getLastFileOrDefault = function(dir, startsWith, default)
|
||||||
|
|
||||||
local latestFile, lastNumber = default, 0
|
local latestFile, lastNumber = default, 0
|
||||||
for file in lfs.dir(dir) do
|
for file in lfs.dir(dir) do
|
||||||
|
|
||||||
local split = Util.split_string(file, ".")
|
local split = Util.split_string(file, ".")
|
||||||
local doesStartWith = Util.startswith(file, startsWith, true)
|
local doesStartWith = Util.startsWith(file, startsWith, true)
|
||||||
if split and #split > 0 and split[#split] == EXTENSION and doesStartWith == true then
|
if split and #split > 0 and split[#split] == EXTENSION and doesStartWith == true then
|
||||||
local numberString = split[#split-1]
|
local numberString = split[#split-1]
|
||||||
local number = tonumber(numberString)
|
local number = tonumber(numberString)
|
||||||
@@ -179,11 +183,14 @@ do
|
|||||||
local fileName = "Spearhead_Persistence.0.spearhead"
|
local fileName = "Spearhead_Persistence.0.spearhead"
|
||||||
if SpearheadConfig and SpearheadConfig.Persistence then
|
if SpearheadConfig and SpearheadConfig.Persistence then
|
||||||
if SpearheadConfig.Persistence.fileName then
|
if SpearheadConfig.Persistence.fileName then
|
||||||
|
if type(SpearheadConfig.Persistence.fileName) ~= "string" then
|
||||||
|
SpearheadConfig.Persistence.fileName = "Spearhead_Persistence.0.spearhead"
|
||||||
|
end
|
||||||
local userFileName = SpearheadConfig.Persistence.fileName
|
local userFileName = SpearheadConfig.Persistence.fileName
|
||||||
local split = Util.split_string(userFileName, ".")
|
local split = Util.split_string(userFileName, ".")
|
||||||
|
|
||||||
if not split and #split < 3 then
|
if not split or #split < 3 then
|
||||||
|
split = split or {}
|
||||||
split[#split+1] = "0"
|
split[#split+1] = "0"
|
||||||
split[#split+1] = "spearhead"
|
split[#split+1] = "spearhead"
|
||||||
end
|
end
|
||||||
@@ -196,7 +203,10 @@ do
|
|||||||
fileName = table.concat(split, ".")
|
fileName = table.concat(split, ".")
|
||||||
end
|
end
|
||||||
|
|
||||||
if SpearheadConfig.Persistence.directory ~= nil then
|
if SpearheadConfig.Persistence.directory then
|
||||||
|
if type(SpearheadConfig.Persistence.directory) ~= "string" then
|
||||||
|
SpearheadConfig.Persistence.directory = lfs.writedir() .. "\\Data"
|
||||||
|
end
|
||||||
dir = SpearheadConfig.Persistence.directory
|
dir = SpearheadConfig.Persistence.directory
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -206,6 +216,10 @@ do
|
|||||||
|
|
||||||
local lastFile = getLastFileOrDefault(dir--[[@as string]], matchingPart, fileName)
|
local lastFile = getLastFileOrDefault(dir--[[@as string]], matchingPart, fileName)
|
||||||
|
|
||||||
|
if lastFile == nil then
|
||||||
|
lastFile = fileName
|
||||||
|
end
|
||||||
|
|
||||||
local fileSplit = Util.split_string(lastFile, ".")
|
local fileSplit = Util.split_string(lastFile, ".")
|
||||||
fileSplit[#fileSplit-1] = tostring(tonumber(fileSplit[#fileSplit-1]) + 1)
|
fileSplit[#fileSplit-1] = tostring(tonumber(fileSplit[#fileSplit-1]) + 1)
|
||||||
fileName = table.concat(fileSplit, ".")
|
fileName = table.concat(fileSplit, ".")
|
||||||
@@ -230,12 +244,29 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
---Sets the stage in the persistence table
|
---Sets the stage in the persistence table
|
||||||
|
---@param stageLane string?
|
||||||
---@param stageNumber number
|
---@param stageNumber number
|
||||||
Persistence.SetActiveStage = function(stageNumber)
|
Persistence.SetActiveStage = function(stageLane, stageNumber)
|
||||||
tables.activeStage = stageNumber
|
stageLane = stageLane or "nil"
|
||||||
|
tables.activeStageInStageLane[stageLane] = stageNumber
|
||||||
Persistence._updateRequired = true
|
Persistence._updateRequired = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Get the active stage as in the persistance file
|
||||||
|
---@param stageLane string?
|
||||||
|
---@return integer|nil
|
||||||
|
Persistence.GetActiveStage = function(stageLane)
|
||||||
|
stageLane = stageLane or "nil"
|
||||||
|
|
||||||
|
-- backwards compatibility for previous versions without stage lanes
|
||||||
|
if stageLane == "nil" and tables.activeStageInStageLane[stageLane] == nil then
|
||||||
|
tables.activeStageInStageLane[stageLane] = tables.activeStage
|
||||||
|
tables.activeStage = nil -- Clear the old activeStage so it's not used or confused later
|
||||||
|
end
|
||||||
|
|
||||||
|
return tables.activeStageInStageLane[stageLane]
|
||||||
|
end
|
||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@param missionName string
|
---@param missionName string
|
||||||
---@param pickedZone string
|
---@param pickedZone string
|
||||||
@@ -255,14 +286,6 @@ do
|
|||||||
return tables.random_missions[string.lower(missionName)]
|
return tables.random_missions[string.lower(missionName)]
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get the active stage as in the persistance file
|
|
||||||
---@return integer|nil
|
|
||||||
Persistence.GetActiveStage = function()
|
|
||||||
if tables.activeStage then
|
|
||||||
return tables.activeStage
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@param zoneName string
|
---@param zoneName string
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ local Util = require("classes.util.Util")
|
|||||||
local DcsUtil = require("classes.util.DcsUtil")
|
local DcsUtil = require("classes.util.DcsUtil")
|
||||||
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
|
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
|
||||||
local MizGroupsManager = require("classes.helpers.MizGroupsManager")
|
local MizGroupsManager = require("classes.helpers.MizGroupsManager")
|
||||||
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
local StageDrawing = require("classes.stageClasses.drawings.StageDrawing")
|
||||||
|
|
||||||
|
|
||||||
---@class DatabaseTables
|
---@class DatabaseTables
|
||||||
@@ -27,7 +27,7 @@ local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
|||||||
---@field MissionZoneData table<string, MissionZoneData>
|
---@field MissionZoneData table<string, MissionZoneData>
|
||||||
---@field FarpZoneData table<string,FarpZoneData>
|
---@field FarpZoneData table<string,FarpZoneData>
|
||||||
---@field missionCodes table<string, boolean>
|
---@field missionCodes table<string, boolean>
|
||||||
---@field CustomDrawings Array<CustomDrawing>
|
---@field StageDrawings Array<StageDrawing>
|
||||||
|
|
||||||
---@class CapRoute
|
---@class CapRoute
|
||||||
---@field zones Array<SpearheadTriggerZone>
|
---@field zones Array<SpearheadTriggerZone>
|
||||||
@@ -58,6 +58,7 @@ local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
|||||||
---@class BlueSamData
|
---@class BlueSamData
|
||||||
---@field groups Array<string>
|
---@field groups Array<string>
|
||||||
---@field buildingKilos number?
|
---@field buildingKilos number?
|
||||||
|
---@field briefing string?
|
||||||
|
|
||||||
---@class MissionZoneData
|
---@class MissionZoneData
|
||||||
---@field ZoneName string
|
---@field ZoneName string
|
||||||
@@ -68,12 +69,14 @@ local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
|||||||
---@field descriptionLocation Vec2?
|
---@field descriptionLocation Vec2?
|
||||||
---@field dependsOn Array<string>
|
---@field dependsOn Array<string>
|
||||||
---@field completeAt number?
|
---@field completeAt number?
|
||||||
|
---@field primaryOverwrite boolean?
|
||||||
|
|
||||||
---@class FarpZoneData
|
---@class FarpZoneData
|
||||||
---@field groups Array<string>
|
---@field groups Array<string>
|
||||||
---@field padNames Array<string>
|
---@field padNames Array<string>
|
||||||
---@field buildingKilos number?
|
---@field buildingKilos number?
|
||||||
---@field supplyHubNames Array<string>
|
---@field supplyHubNames Array<string>
|
||||||
|
---@field briefing string?
|
||||||
|
|
||||||
---@class Database
|
---@class Database
|
||||||
---@field private _tables DatabaseTables
|
---@field private _tables DatabaseTables
|
||||||
@@ -107,7 +110,7 @@ function Database.New(Logger)
|
|||||||
FarpZoneData = {},
|
FarpZoneData = {},
|
||||||
missionCodes = {},
|
missionCodes = {},
|
||||||
SupplyHubZones = {},
|
SupplyHubZones = {},
|
||||||
CustomDrawings = {}
|
StageDrawings = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Database.__index = Database
|
Database.__index = Database
|
||||||
@@ -119,7 +122,7 @@ function Database.New(Logger)
|
|||||||
self._logger:debug("Initiating tables")
|
self._logger:debug("Initiating tables")
|
||||||
|
|
||||||
do -- INIT ZONE TABLES
|
do -- INIT ZONE TABLES
|
||||||
for zone_ind, zone_data in pairs(DcsUtil.__trigger_zones) do
|
for _, zone_data in pairs(DcsUtil.__trigger_zones) do
|
||||||
local zone_name = zone_data.name
|
local zone_name = zone_data.name
|
||||||
|
|
||||||
---@type Vec2
|
---@type Vec2
|
||||||
@@ -206,13 +209,15 @@ function Database.New(Logger)
|
|||||||
self._logger:debug("initiated zone tables, continuing with descriptions")
|
self._logger:debug("initiated zone tables, continuing with descriptions")
|
||||||
do --load markers
|
do --load markers
|
||||||
if env.mission.drawings and env.mission.drawings.layers then
|
if env.mission.drawings and env.mission.drawings.layers then
|
||||||
for i, layer in pairs(env.mission.drawings.layers) do
|
for _, layer in pairs(env.mission.drawings.layers) do
|
||||||
if string.lower(layer.name) == "author" then
|
if string.lower(layer.name) == "author" then
|
||||||
for key, layer_object in pairs(layer.objects) do
|
for _, layer_object in pairs(layer.objects) do
|
||||||
if Util.startswith(string.lower(layer_object.name), "buildable", true) == true then
|
if Util.startsWith(string.lower(layer_object.name), "buildable", true) == true then
|
||||||
local airbaseData = self:getAirbaseDataForDrawLayer(layer_object)
|
local airbaseData = self:getAirbaseDataForDrawLayer(layer_object)
|
||||||
if airbaseData then
|
if airbaseData then
|
||||||
self._logger:debug("found airbase data for " .. layer_object.name)
|
self._logger:debug("found airbase data for " .. layer_object.name)
|
||||||
|
if layer_object.primitiveType == "TextBox" then
|
||||||
|
layer_object = layer_object --[[@as TextBox]]
|
||||||
local number = tonumber(layer_object.text)
|
local number = tonumber(layer_object.text)
|
||||||
airbaseData.buildingKilos = number
|
airbaseData.buildingKilos = number
|
||||||
end
|
end
|
||||||
@@ -222,17 +227,20 @@ function Database.New(Logger)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@type table<integer, boolean>
|
---@type table<integer, boolean>
|
||||||
do -- custom drawings
|
do -- custom drawings
|
||||||
if env.mission.drawings and env.mission.drawings.layers then
|
if env.mission.drawings and env.mission.drawings.layers then
|
||||||
for i, layer in pairs(env.mission.drawings.layers) do
|
for _, layer in pairs(env.mission.drawings.layers) do
|
||||||
if string.lower(layer.name) == "author" then
|
if string.lower(layer.name) == "author" then
|
||||||
for key, layer_object in pairs(layer.objects) do
|
for _, layer_object in pairs(layer.objects) do
|
||||||
if Util.startswith(layer_object.name, "drawing_", true) then
|
if Util.startsWith(layer_object.name, "drawing_", true) then
|
||||||
local object = layer_object --[[@as DrawingObject]]
|
local object = layer_object --[[@as DrawingObject]]
|
||||||
local customDrawing = CustomDrawing.New(object)
|
local stageDrawing = StageDrawing.New(object)
|
||||||
table.insert(self._tables.CustomDrawings, customDrawing)
|
if stageDrawing then
|
||||||
|
table.insert(self._tables.StageDrawings, stageDrawing)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -252,13 +260,15 @@ function Database.New(Logger)
|
|||||||
|
|
||||||
-- Find and add Briefing
|
-- Find and add Briefing
|
||||||
if env.mission.drawings and env.mission.drawings.layers then
|
if env.mission.drawings and env.mission.drawings.layers then
|
||||||
for i, layer in pairs(env.mission.drawings.layers) do
|
for _, layer in pairs(env.mission.drawings.layers) do
|
||||||
if string.lower(layer.name) == "author" then
|
if string.lower(layer.name) == "author" then
|
||||||
for key, layer_object in pairs(layer.objects) do
|
for _, layer_object in pairs(layer.objects) do
|
||||||
if Util.startswith(string.lower(layer_object.name), "stagebriefing_", true) == true then
|
if Util.startsWith(string.lower(layer_object.name), "stagebriefing_", true) == true then
|
||||||
local zone = DcsUtil.getZoneByName(stageZoneName)
|
local zone = DcsUtil.getZoneByName(stageZoneName)
|
||||||
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
|
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
|
||||||
if zone and Util.is2dPointInZone(vec2, zone) == true then
|
if zone and Util.is2dPointInZone(vec2, zone) == true then
|
||||||
|
if layer_object.primitiveType == "TextBox" then
|
||||||
|
layer_object = layer_object --[[@as TextBox]]
|
||||||
local description = layer_object.text
|
local description = layer_object.text
|
||||||
if description and description ~= "" then
|
if description and description ~= "" then
|
||||||
stageData.StageBriefing = description
|
stageData.StageBriefing = description
|
||||||
@@ -269,6 +279,7 @@ function Database.New(Logger)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- fill blue sams
|
-- fill blue sams
|
||||||
for _, blueSamStageName in pairs(self._tables.BlueSams) do
|
for _, blueSamStageName in pairs(self._tables.BlueSams) do
|
||||||
@@ -335,14 +346,14 @@ function Database.New(Logger)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- fill missions
|
-- fill missions
|
||||||
for key, missionZone in pairs(self._tables.MissionZones) do
|
for _, missionZone in pairs(self._tables.MissionZones) do
|
||||||
if DcsUtil.isZoneInZone(missionZone, stageZoneName) == true then
|
if DcsUtil.isZoneInZone(missionZone, stageZoneName) == true then
|
||||||
table.insert(stageData.MissionZones, missionZone)
|
table.insert(stageData.MissionZones, missionZone)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- fill random missions
|
-- fill random missions
|
||||||
for key, missionZone in pairs(self._tables.RandomMissionZones) do
|
for _, missionZone in pairs(self._tables.RandomMissionZones) do
|
||||||
if DcsUtil.isZoneInZone(missionZone, stageZoneName) == true then
|
if DcsUtil.isZoneInZone(missionZone, stageZoneName) == true then
|
||||||
table.insert(stageData.RandomMissionZones, missionZone)
|
table.insert(stageData.RandomMissionZones, missionZone)
|
||||||
end
|
end
|
||||||
@@ -350,18 +361,6 @@ function Database.New(Logger)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, missionZone in pairs(self._tables.MissionZones) do
|
|
||||||
if self._tables.MissionZoneData[missionZone] == nil or self._tables.MissionZoneData[missionZone].description == nil then
|
|
||||||
MissionEditorWarnings.Add("Mission with zonename: " .. missionZone .. " does not have a briefing")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, missionZone in pairs(self._tables.RandomMissionZones) do
|
|
||||||
if self._tables.MissionZoneData[missionZone] == nil or self._tables.MissionZoneData[missionZone].description == nil then
|
|
||||||
MissionEditorWarnings.Add("Mission with zonename: " .. missionZone .. " does not have a briefing")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, farpZoneName in pairs(self._tables.AllFarpZones) do
|
for _, farpZoneName in pairs(self._tables.AllFarpZones) do
|
||||||
for _, airbase in pairs(world.getAirbases()) do
|
for _, airbase in pairs(world.getAirbases()) do
|
||||||
if airbase:getDesc().category == Airbase.Category.HELIPAD then
|
if airbase:getDesc().category == Airbase.Category.HELIPAD then
|
||||||
@@ -447,6 +446,21 @@ function Database.New(Logger)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Checks for missing briefings in mission zones and random mission zones
|
||||||
|
do
|
||||||
|
for _, missionZone in pairs(self._tables.MissionZones) do
|
||||||
|
if self._tables.MissionZoneData[missionZone] == nil or self._tables.MissionZoneData[missionZone].description == nil then
|
||||||
|
MissionEditorWarnings.Add("Mission with zonename: " .. missionZone .. " does not have a briefing")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, missionZone in pairs(self._tables.RandomMissionZones) do
|
||||||
|
if self._tables.MissionZoneData[missionZone] == nil or self._tables.MissionZoneData[missionZone].description == nil then
|
||||||
|
MissionEditorWarnings.Add("Mission with zonename: " .. missionZone .. " does not have a briefing")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if missions == 0 then missions = 1 end
|
if missions == 0 then missions = 1 end
|
||||||
|
|
||||||
self._logger:info("initiated the database with amount of zones: ")
|
self._logger:info("initiated the database with amount of zones: ")
|
||||||
@@ -461,6 +475,7 @@ function Database.New(Logger)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@type table<string, boolean>
|
||||||
local is_group_taken = {}
|
local is_group_taken = {}
|
||||||
|
|
||||||
local getAvailableGroups = function()
|
local getAvailableGroups = function()
|
||||||
@@ -477,7 +492,7 @@ end
|
|||||||
local getAvailableCAPGroups = function()
|
local getAvailableCAPGroups = function()
|
||||||
local result = {}
|
local result = {}
|
||||||
for name, value in pairs(is_group_taken) do
|
for name, value in pairs(is_group_taken) do
|
||||||
if value == false and Util.startswith(name, "CAP") then
|
if value == false and Util.startsWith(name, "CAP") then
|
||||||
table.insert(result, name)
|
table.insert(result, name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -580,11 +595,11 @@ function Database:loadCapUnits()
|
|||||||
for _, groupName in pairs(groups) do
|
for _, groupName in pairs(groups) do
|
||||||
is_group_taken[groupName] = true
|
is_group_taken[groupName] = true
|
||||||
|
|
||||||
if Util.startswith(groupName, "CAP_A", true) or Util.startswith(groupName, "CAP_B", true) then
|
if Util.startsWith(groupName, "CAP_A", true) or Util.startsWith(groupName, "CAP_B", true) then
|
||||||
table.insert(baseData.CapGroups, groupName)
|
table.insert(baseData.CapGroups, groupName)
|
||||||
elseif Util.startswith(groupName, "CAP_I", true) then
|
elseif Util.startsWith(groupName, "CAP_I", true) then
|
||||||
table.insert(baseData.InterceptGroups, groupName)
|
table.insert(baseData.InterceptGroups, groupName)
|
||||||
elseif Util.startswith(groupName, "CAP_S", true) then
|
elseif Util.startsWith(groupName, "CAP_S", true) then
|
||||||
table.insert(baseData.SweepGroups, groupName)
|
table.insert(baseData.SweepGroups, groupName)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -607,10 +622,32 @@ function Database:loadBlueSamUnits()
|
|||||||
local triggerZone = DcsUtil.getZoneByName(blueSamZone)
|
local triggerZone = DcsUtil.getZoneByName(blueSamZone)
|
||||||
if triggerZone then
|
if triggerZone then
|
||||||
for _, kvPair in pairs(triggerZone.properties) do
|
for _, kvPair in pairs(triggerZone.properties) do
|
||||||
if kvPair.key and Util.startswith(kvPair.key, "buildable") then
|
if kvPair.key and Util.startsWith(kvPair.key, "buildable") then
|
||||||
local number = tonumber(kvPair.value)
|
local number = tonumber(kvPair.value)
|
||||||
if number and number > 0 then
|
if number and number > 0 then
|
||||||
samData.buildingKilos = number
|
samData.buildingKilos = number
|
||||||
|
|
||||||
|
if env.mission.drawings and env.mission.drawings.layers then
|
||||||
|
for _, layer in pairs(env.mission.drawings.layers) do
|
||||||
|
if string.lower(layer.name) == "author" then
|
||||||
|
for _, layer_object in pairs(layer.objects) do
|
||||||
|
|
||||||
|
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
|
||||||
|
if layer_object.primitiveType == "TextBox" and triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
|
||||||
|
if layer_object.name and Util.startsWith(layer_object.name, "supplybriefing_", true) then
|
||||||
|
layer_object = layer_object --[[@as TextBox]]
|
||||||
|
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
|
end
|
||||||
end
|
end
|
||||||
@@ -618,12 +655,12 @@ function Database:loadBlueSamUnits()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return Array<CustomDrawing>
|
---@return Array<StageDrawing>
|
||||||
function Database:getCustomDrawings()
|
function Database:getStageDrawings()
|
||||||
if self._tables.CustomDrawings == nil then
|
if self._tables.StageDrawings == nil then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
return self._tables.CustomDrawings
|
return self._tables.StageDrawings
|
||||||
end
|
end
|
||||||
|
|
||||||
---Loads all units, data and briefings
|
---Loads all units, data and briefings
|
||||||
@@ -674,9 +711,9 @@ function Database:LoadZoneData(missionZoneName)
|
|||||||
if triggerZone and triggerZone.properties then
|
if triggerZone and triggerZone.properties then
|
||||||
for _, kvPair in pairs(triggerZone.properties) do
|
for _, kvPair in pairs(triggerZone.properties) do
|
||||||
local key = kvPair.key
|
local key = kvPair.key
|
||||||
if Util.startswith(key, "dependson", true) == true then
|
if Util.startsWith(key, "dependson", true) == true then
|
||||||
table.insert(self._tables.MissionZoneData[missionZoneName].dependsOn, kvPair.value)
|
table.insert(self._tables.MissionZoneData[missionZoneName].dependsOn, kvPair.value)
|
||||||
elseif Util.startswith(key, "completeat") == true then
|
elseif Util.startsWith(key, "completeat") == true then
|
||||||
local value = tonumber(kvPair.value)
|
local value = tonumber(kvPair.value)
|
||||||
if value then
|
if value then
|
||||||
if value > 1 and value <= 100 then
|
if value > 1 and value <= 100 then
|
||||||
@@ -686,19 +723,28 @@ function Database:LoadZoneData(missionZoneName)
|
|||||||
end
|
end
|
||||||
self._tables.MissionZoneData[missionZoneName].completeAt = value
|
self._tables.MissionZoneData[missionZoneName].completeAt = value
|
||||||
end
|
end
|
||||||
|
elseif Util.startsWith(key, "primary", true) == true then
|
||||||
|
if type(kvPair.value) == "string" then
|
||||||
|
if kvPair.value == "true" then
|
||||||
|
self._tables.MissionZoneData[missionZoneName].primaryOverwrite = true
|
||||||
|
elseif kvPair.value == "false" then
|
||||||
|
self._tables.MissionZoneData[missionZoneName].primaryOverwrite = false
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Adds Briefings
|
---Adds Briefings
|
||||||
if env.mission.drawings and env.mission.drawings.layers then
|
if env.mission.drawings and env.mission.drawings.layers then
|
||||||
for i, layer in pairs(env.mission.drawings.layers) do
|
for _, layer in pairs(env.mission.drawings.layers) do
|
||||||
if string.lower(layer.name) == "author" then
|
if string.lower(layer.name) == "author" then
|
||||||
for key, layer_object in pairs(layer.objects) do
|
for _, layer_object in pairs(layer.objects) do
|
||||||
|
|
||||||
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
|
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
|
||||||
if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
|
if layer_object.primitiveType == "TextBox" and triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
|
||||||
if layer_object.name and Util.startswith(layer_object.name, "briefing_", true) then
|
if layer_object.name and Util.startsWith(layer_object.name, "briefing_", true) then
|
||||||
|
layer_object = layer_object --[[@as TextBox]]
|
||||||
local description = layer_object.text
|
local description = layer_object.text
|
||||||
if description and description ~= "" then
|
if description and description ~= "" then
|
||||||
self._tables.MissionZoneData[missionZoneName].description = description
|
self._tables.MissionZoneData[missionZoneName].description = description
|
||||||
@@ -741,10 +787,33 @@ function Database:loadFarpData()
|
|||||||
local triggerZone = DcsUtil.getZoneByName(farpZone)
|
local triggerZone = DcsUtil.getZoneByName(farpZone)
|
||||||
if triggerZone then
|
if triggerZone then
|
||||||
for _, kvPair in pairs(triggerZone.properties) do
|
for _, kvPair in pairs(triggerZone.properties) do
|
||||||
if kvPair.key and Util.startswith(kvPair.key, "buildable", true) == true then
|
if kvPair.key and Util.startsWith(kvPair.key, "buildable", true) == true then
|
||||||
local number = tonumber(kvPair.value)
|
local number = tonumber(kvPair.value)
|
||||||
if number and number > 0 then
|
if number and number > 0 then
|
||||||
farpzoneData.buildingKilos = number
|
farpzoneData.buildingKilos = number
|
||||||
|
|
||||||
|
-- check briefings
|
||||||
|
if env.mission.drawings and env.mission.drawings.layers then
|
||||||
|
for _, layer in pairs(env.mission.drawings.layers) do
|
||||||
|
if string.lower(layer.name) == "author" then
|
||||||
|
for _, layer_object in pairs(layer.objects) do
|
||||||
|
|
||||||
|
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
|
||||||
|
if layer_object.primitiveType == "TextBox" and triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
|
||||||
|
layer_object = layer_object --[[@as TextBox]]
|
||||||
|
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
|
||||||
end
|
end
|
||||||
@@ -900,6 +969,7 @@ function Database:getBriefingForStage(stagename)
|
|||||||
return stageZone.StageBriefing or ""
|
return stageZone.StageBriefing or ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param stagename string
|
||||||
---@return Array<string>
|
---@return Array<string>
|
||||||
function Database:getMissionsForStage(stagename)
|
function Database:getMissionsForStage(stagename)
|
||||||
local stageZone = self._tables.StageZones[stagename]
|
local stageZone = self._tables.StageZones[stagename]
|
||||||
@@ -907,6 +977,7 @@ function Database:getMissionsForStage(stagename)
|
|||||||
return stageZone.MissionZones
|
return stageZone.MissionZones
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param stagename string
|
||||||
---@return Array<string>
|
---@return Array<string>
|
||||||
function Database:getRandomMissionsForStage(stagename)
|
function Database:getRandomMissionsForStage(stagename)
|
||||||
local stageZone = self._tables.StageZones[stagename]
|
local stageZone = self._tables.StageZones[stagename]
|
||||||
@@ -914,13 +985,14 @@ function Database:getRandomMissionsForStage(stagename)
|
|||||||
return stageZone.RandomMissionZones
|
return stageZone.RandomMissionZones
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param missionZoneName string
|
||||||
---@return MissionZoneData?
|
---@return MissionZoneData?
|
||||||
function Database:getMissionDataForZone(missionZoneName)
|
function Database:getMissionDataForZone(missionZoneName)
|
||||||
return self._tables.MissionZoneData[missionZoneName]
|
return self._tables.MissionZoneData[missionZoneName]
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param stageName string
|
---@param stageName string
|
||||||
---@return table result airbase Names
|
---@return Array<string> result airbase Names
|
||||||
function Database:getAirbaseNamesInStage(stageName)
|
function Database:getAirbaseNamesInStage(stageName)
|
||||||
local stageData = self._tables.StageZones[stageName]
|
local stageData = self._tables.StageZones[stageName]
|
||||||
if not stageData then return {} end
|
if not stageData then return {} end
|
||||||
@@ -934,6 +1006,7 @@ function Database:getFarpNamesInStage(stageName)
|
|||||||
return stageData.FarpZones or {}
|
return stageData.FarpZones or {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param farpZoneName string
|
||||||
---@return FarpZoneData?
|
---@return FarpZoneData?
|
||||||
function Database:getFarpDataForZone(farpZoneName)
|
function Database:getFarpDataForZone(farpZoneName)
|
||||||
local farpData = self._tables.FarpZoneData[farpZoneName]
|
local farpData = self._tables.FarpZoneData[farpZoneName]
|
||||||
@@ -949,6 +1022,7 @@ function Database:getAirbaseDataForZone(baseName)
|
|||||||
return baseData
|
return baseData
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param stageName string
|
||||||
---@return string?
|
---@return string?
|
||||||
function Database:getStageBriefingForStage(stageName)
|
function Database:getStageBriefingForStage(stageName)
|
||||||
local stageData = self._tables.StageZones[stageName]
|
local stageData = self._tables.StageZones[stageName]
|
||||||
|
|||||||
@@ -29,36 +29,47 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@class OnStageChangedListener
|
---@class OnStageChangedListener
|
||||||
---@field OnStageNumberChanged fun(self:OnStageChangedListener, number:integer)
|
---@field OnStageNumberChanged fun(self:OnStageChangedListener, number:integer, laneIdentifier:string?)
|
||||||
|
|
||||||
do -- STAGE NUMBER CHANGED
|
do -- STAGE NUMBER CHANGED
|
||||||
|
---@type Array<OnStageChangedListener>
|
||||||
local OnStageNumberChangedListeners = {}
|
local OnStageNumberChangedListeners = {}
|
||||||
|
---@type Array<fun(number:integer, laneIdentifier:string?)>
|
||||||
local OnStageNumberChangedHandlers = {}
|
local OnStageNumberChangedHandlers = {}
|
||||||
---Add a stage zone number changed listener
|
---Add a stage zone number changed listener
|
||||||
---@param listener OnStageChangedListener object with function OnStageNumberChanged(self, number)
|
---@param listener OnStageChangedListener object with function OnStageNumberChanged(self, number, stageLaneIdentifier)
|
||||||
SpearheadEvents.AddStageNumberChangedListener = function(listener)
|
SpearheadEvents.AddStageNumberChangedListener = function(listener)
|
||||||
|
if type(listener) ~= "table" or type(listener.OnStageNumberChanged) ~= "function" then
|
||||||
|
warn("Event handler not of type table/object with function OnStageNumberChanged(self, number, stageLaneIdentifier)")
|
||||||
|
return
|
||||||
|
end
|
||||||
table.insert(OnStageNumberChangedListeners, listener)
|
table.insert(OnStageNumberChangedListeners, listener)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Add a stage zone number changed listener
|
---@class OnStageNumberChangeCompleteListener
|
||||||
---@param handler function function(number)
|
---@field OnStageNumberChangeComplete fun(self:OnStageNumberChangeCompleteListener, number:integer, laneIdentifier:string?)
|
||||||
SpearheadEvents.AddStageNumberChangedHandler = function(handler)
|
|
||||||
if type(handler) ~= "function" then
|
---@type Array<OnStageNumberChangeCompleteListener>
|
||||||
warn("Event handler not of type function, did you mean to use listener?")
|
local OnStageNumberChangeCompleteListeners = {}
|
||||||
|
---@param listener OnStageNumberChangeCompleteListener
|
||||||
|
SpearheadEvents.AddStageNumberChangeCompleteListener = function(listener)
|
||||||
|
if type(listener) ~= "table" or type(listener.OnStageNumberChangeComplete) ~= "function" then
|
||||||
|
warn("Event handler not of type table/object with function OnStageNumberChangeComplete(self, number, laneIdentifier)")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
table.insert(OnStageNumberChangedHandlers, handler)
|
table.insert(OnStageNumberChangeCompleteListeners, listener)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param newStageNumber number
|
---@param newStageNumber number
|
||||||
SpearheadEvents.PublishStageNumberChanged = function(newStageNumber)
|
---@param laneIdentifier string?
|
||||||
|
SpearheadEvents.PublishStageNumberChanged = function(newStageNumber, laneIdentifier)
|
||||||
pcall(function ()
|
pcall(function ()
|
||||||
Persistence.SetActiveStage(newStageNumber)
|
Persistence.SetActiveStage(laneIdentifier, newStageNumber)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
for _, callable in pairs(OnStageNumberChangedListeners) do
|
for _, callable in pairs(OnStageNumberChangedListeners) do
|
||||||
local succ, err = pcall(function()
|
local _, err = pcall(function()
|
||||||
callable:OnStageNumberChanged(newStageNumber)
|
callable:OnStageNumberChanged(newStageNumber, laneIdentifier)
|
||||||
end)
|
end)
|
||||||
if err then
|
if err then
|
||||||
logError(err)
|
logError(err)
|
||||||
@@ -66,11 +77,21 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
for _, callable in pairs(OnStageNumberChangedHandlers) do
|
for _, callable in pairs(OnStageNumberChangedHandlers) do
|
||||||
local succ, err = pcall(callable, newStageNumber)
|
local _, err = pcall(callable, newStageNumber, laneIdentifier)
|
||||||
if err then
|
if err then
|
||||||
logError(err)
|
logError(err)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
for _, callable in pairs(OnStageNumberChangeCompleteListeners) do
|
||||||
|
local _, err = pcall(function()
|
||||||
|
callable:OnStageNumberChangeComplete(newStageNumber, laneIdentifier)
|
||||||
|
end)
|
||||||
|
if err then
|
||||||
|
logError(err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Logger.new("Events", "INFO"):info("Published stage number changed to: " .. tostring(newStageNumber))
|
Logger.new("Events", "INFO"):info("Published stage number changed to: " .. tostring(newStageNumber))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -92,9 +113,12 @@ do
|
|||||||
table.insert(onWeaponFiredListeners, weaponFiredListener)
|
table.insert(onWeaponFiredListeners, weaponFiredListener)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param unit Unit
|
||||||
|
---@param weapon Weapon
|
||||||
|
---@param target Unit?
|
||||||
local triggerWeaponFired = function(unit, weapon, target)
|
local triggerWeaponFired = function(unit, weapon, target)
|
||||||
for _, callable in pairs(onWeaponFiredListeners) do
|
for _, callable in pairs(onWeaponFiredListeners) do
|
||||||
local succ, err = pcall(function()
|
local _, err = pcall(function()
|
||||||
callable:OnWeaponFired(unit, weapon, target)
|
callable:OnWeaponFired(unit, weapon, target)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -104,11 +128,14 @@ do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@class OnLandEventListener
|
||||||
|
---@field OnUnitLanded fun(self:OnLandEventListener, initiatorUnit:Unit, airbase:Airbase)
|
||||||
|
|
||||||
|
---@type table<string,Array<OnLandEventListener>>
|
||||||
local onLandEventListeners = {}
|
local onLandEventListeners = {}
|
||||||
---Add an event listener to a specific unit
|
---Add an event listener to a specific unit
|
||||||
---@param unitName string to call when the unit lands
|
---@param unitName string to call when the unit lands
|
||||||
---@param landListener table table with function OnUnitLanded(self, initiatorUnit, airbase)
|
---@param landListener OnLandEventListener table with function OnUnitLanded(self, initiatorUnit, airbase)
|
||||||
SpearheadEvents.addOnUnitLandEventListener = function(unitName, landListener)
|
SpearheadEvents.addOnUnitLandEventListener = function(unitName, landListener)
|
||||||
if type(landListener) ~= "table" then
|
if type(landListener) ~= "table" then
|
||||||
warn("Event handler not of type table/object")
|
warn("Event handler not of type table/object")
|
||||||
@@ -144,6 +171,11 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
do -- ON RTB
|
do -- ON RTB
|
||||||
|
|
||||||
|
---@class OnGroupRTBListener
|
||||||
|
---@field OnGroupRTB fun(self:OnGroupRTBListener, groupName:string)
|
||||||
|
|
||||||
|
---@type table<string,Array<OnGroupRTBListener>>
|
||||||
local OnGroupRTBListeners = {}
|
local OnGroupRTBListeners = {}
|
||||||
---Adds a function to the events listener that triggers when a group publishes themselves RTB.
|
---Adds a function to the events listener that triggers when a group publishes themselves RTB.
|
||||||
---This is only available when a ROUTE is created via the Spearhead.RouteUtil
|
---This is only available when a ROUTE is created via the Spearhead.RouteUtil
|
||||||
@@ -168,7 +200,7 @@ do
|
|||||||
if groupName ~= nil then
|
if groupName ~= nil then
|
||||||
if OnGroupRTBListeners[groupName] then
|
if OnGroupRTBListeners[groupName] then
|
||||||
for _, callable in pairs(OnGroupRTBListeners[groupName]) do
|
for _, callable in pairs(OnGroupRTBListeners[groupName]) do
|
||||||
local succ, err = pcall(function()
|
local _, err = pcall(function()
|
||||||
callable:OnGroupRTB(groupName)
|
callable:OnGroupRTB(groupName)
|
||||||
end)
|
end)
|
||||||
if err then
|
if err then
|
||||||
@@ -179,11 +211,15 @@ do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@class OnGroupRTBInTenListener
|
||||||
|
---@field OnGroupRTBInTen fun(self:OnGroupRTBInTenListener, groupName:string)
|
||||||
|
|
||||||
|
---@type table<string,Array<OnGroupRTBInTenListener>>
|
||||||
local OnGroupRTBInTenListeners = {}
|
local OnGroupRTBInTenListeners = {}
|
||||||
---Adds a function to the events listener that triggers when a group publishes themselves RTB.
|
---Adds a function to the events listener that triggers when a group publishes themselves RTB.
|
||||||
---This is only available when a ROUTE is created via the Spearhead.RouteUtil
|
---This is only available when a ROUTE is created via the Spearhead.RouteUtil
|
||||||
---@param groupName string the groupname to expect
|
---@param groupName string the groupname to expect
|
||||||
---@param handlingObject table object with OnGroupRTBInTen(self, groupName)
|
---@param handlingObject OnGroupRTBInTenListener object with OnGroupRTBInTen(self, groupName)
|
||||||
SpearheadEvents.addOnGroupRTBInTenListener = function(groupName, handlingObject)
|
SpearheadEvents.addOnGroupRTBInTenListener = function(groupName, handlingObject)
|
||||||
if type(handlingObject) ~= "table" then
|
if type(handlingObject) ~= "table" then
|
||||||
warn("Event handler not of type table/object")
|
warn("Event handler not of type table/object")
|
||||||
@@ -203,7 +239,7 @@ do
|
|||||||
if groupName ~= nil then
|
if groupName ~= nil then
|
||||||
if OnGroupRTBInTenListeners[groupName] then
|
if OnGroupRTBInTenListeners[groupName] then
|
||||||
for _, callable in pairs(OnGroupRTBInTenListeners[groupName]) do
|
for _, callable in pairs(OnGroupRTBInTenListeners[groupName]) do
|
||||||
local succ, err = pcall(function()
|
local _, err = pcall(function()
|
||||||
callable:OnGroupRTBInTen(groupName)
|
callable:OnGroupRTBInTen(groupName)
|
||||||
end)
|
end)
|
||||||
if err then
|
if err then
|
||||||
@@ -216,10 +252,17 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
do -- ON Station
|
do -- ON Station
|
||||||
|
|
||||||
|
---@class OnGroupOnStationListener
|
||||||
|
---@field OnGroupOnStation fun(self:OnGroupOnStationListener, groupName:string)
|
||||||
|
|
||||||
|
---@type table<string,Array<OnGroupOnStationListener>>
|
||||||
local OnGroupOnStationListeners = {}
|
local OnGroupOnStationListeners = {}
|
||||||
|
|
||||||
---Adds a function to the events listener that triggers when a group publishes themselves RTB.
|
---Adds a function to the events listener that triggers when a group publishes themselves RTB.
|
||||||
---This is only available when a ROUTE is created via the Spearhead.RouteUtil
|
---This is only available when a ROUTE is created via the Spearhead.RouteUtil
|
||||||
---@param groupName string the groupname to expect
|
---@param groupName string the groupname to expect
|
||||||
|
---@param handlingObject OnGroupOnStationListener object with OnGroupOnStation(self, groupName)
|
||||||
SpearheadEvents.addOnGroupOnStationListener = function(groupName, handlingObject)
|
SpearheadEvents.addOnGroupOnStationListener = function(groupName, handlingObject)
|
||||||
if type(handlingObject) ~= "table" then
|
if type(handlingObject) ~= "table" then
|
||||||
warn("Event handler not of type table/object")
|
warn("Event handler not of type table/object")
|
||||||
@@ -239,7 +282,7 @@ do
|
|||||||
if groupName ~= nil then
|
if groupName ~= nil then
|
||||||
if OnGroupOnStationListeners[groupName] then
|
if OnGroupOnStationListeners[groupName] then
|
||||||
for _, callable in pairs(OnGroupOnStationListeners[groupName]) do
|
for _, callable in pairs(OnGroupOnStationListeners[groupName]) do
|
||||||
local succ, err = pcall(function()
|
local _, err = pcall(function()
|
||||||
callable:OnGroupOnStation(groupName)
|
callable:OnGroupOnStation(groupName)
|
||||||
end)
|
end)
|
||||||
if err then
|
if err then
|
||||||
@@ -252,15 +295,18 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
do -- PLAYER ENTER UNIT
|
do -- PLAYER ENTER UNIT
|
||||||
|
---@class OnPlayerEnterUnitListener
|
||||||
|
---@field OnPlayerEntersUnit fun(self:OnPlayerEnterUnitListener, unit:Unit)
|
||||||
|
|
||||||
|
---@type Array<OnPlayerEnterUnitListener>
|
||||||
local playerEnterUnitListeners = {}
|
local playerEnterUnitListeners = {}
|
||||||
---comment
|
---comment
|
||||||
---@param listener table object with OnPlayerEntersUnit(self, unit)
|
---@param listener OnPlayerEnterUnitListener object with OnPlayerEntersUnit(self, unit)
|
||||||
SpearheadEvents.AddOnPlayerEnterUnitListener = function(listener)
|
SpearheadEvents.AddOnPlayerEnterUnitListener = function(listener)
|
||||||
if type(listener) ~= "table" then
|
if type(listener) ~= "table" then
|
||||||
warn("Unit lost Event listener not of type table/object")
|
warn("Unit lost Event listener not of type table/object")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(playerEnterUnitListeners, listener)
|
table.insert(playerEnterUnitListeners, listener)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -268,7 +314,7 @@ do
|
|||||||
if unit ~= nil then
|
if unit ~= nil then
|
||||||
if playerEnterUnitListeners then
|
if playerEnterUnitListeners then
|
||||||
for _, callable in pairs(playerEnterUnitListeners) do
|
for _, callable in pairs(playerEnterUnitListeners) do
|
||||||
local succ, err = pcall(function()
|
local _, err = pcall(function()
|
||||||
callable:OnPlayerEntersUnit(unit)
|
callable:OnPlayerEntersUnit(unit)
|
||||||
end)
|
end)
|
||||||
if err then
|
if err then
|
||||||
@@ -297,13 +343,13 @@ do
|
|||||||
local e = {}
|
local e = {}
|
||||||
function e:onEvent(event)
|
function e:onEvent(event)
|
||||||
if event.id == world.event.S_EVENT_LAND or event.id == world.event.S_EVENT_RUNWAY_TOUCH then
|
if event.id == world.event.S_EVENT_LAND or event.id == world.event.S_EVENT_RUNWAY_TOUCH then
|
||||||
local unit = event.initiator
|
local unit = event.initiator --[[@as Unit]]
|
||||||
local airbase = event.place
|
local airbase = event.place --[[@as Airbase]]
|
||||||
if unit ~= nil then
|
if unit ~= nil then
|
||||||
local name = unit:getName()
|
local name = unit:getName()
|
||||||
if onLandEventListeners[name] then
|
if onLandEventListeners[name] then
|
||||||
for _, callable in pairs(onLandEventListeners[name]) do
|
for _, callable in pairs(onLandEventListeners[name]) do
|
||||||
local succ, err = pcall(function()
|
local _, err = pcall(function()
|
||||||
callable:OnUnitLanded(unit, airbase)
|
callable:OnUnitLanded(unit, airbase)
|
||||||
end)
|
end)
|
||||||
if err then
|
if err then
|
||||||
@@ -318,7 +364,7 @@ do
|
|||||||
event.id == world.event.S_EVENT_CRASH or
|
event.id == world.event.S_EVENT_CRASH or
|
||||||
event.id == world.event.S_EVENT_EJECTION or
|
event.id == world.event.S_EVENT_EJECTION or
|
||||||
event.id == world.event.S_EVENT_UNIT_LOST then
|
event.id == world.event.S_EVENT_UNIT_LOST then
|
||||||
local object = event.initiator
|
local object = event.initiator --[[@as Unit]]
|
||||||
|
|
||||||
if object and object.getName then
|
if object and object.getName then
|
||||||
logDebug("Receiving death event from: " .. object:getName())
|
logDebug("Receiving death event from: " .. object:getName())
|
||||||
@@ -326,7 +372,7 @@ do
|
|||||||
|
|
||||||
if object and object.getName and OnUnitLostListeners[object:getName()] then
|
if object and object.getName and OnUnitLostListeners[object:getName()] then
|
||||||
for _, callable in pairs(OnUnitLostListeners[object:getName()]) do
|
for _, callable in pairs(OnUnitLostListeners[object:getName()]) do
|
||||||
local succ, err = pcall(function()
|
local _, err = pcall(function()
|
||||||
callable:OnUnitLost(object)
|
callable:OnUnitLost(object)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -337,15 +383,11 @@ do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if event.id == world.event.S_EVENT_EJECTION then
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
if event.id == world.event.S_EVENT_SHOT then
|
if event.id == world.event.S_EVENT_SHOT then
|
||||||
|
|
||||||
local shooter = event.initiator
|
local shooter = event.initiator --[[@as Unit]]
|
||||||
local weapon = event.weapon
|
local weapon = event.weapon --[[@as Weapon]]
|
||||||
local target = event.target
|
local target = event.target --[[@as Unit?]]
|
||||||
triggerWeaponFired(shooter, weapon, target)
|
triggerWeaponFired(shooter, weapon, target)
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -354,28 +396,32 @@ do
|
|||||||
Persistence.UpdateNow()
|
Persistence.UpdateNow()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@type table<string, boolean>
|
||||||
local AI_GROUPS = {}
|
local AI_GROUPS = {}
|
||||||
|
|
||||||
local function CheckAndTriggerSpawnAsync(unit, time)
|
---@param unit Unit
|
||||||
|
---@param _ any
|
||||||
local function isPlayer(unit)
|
local function CheckAndTriggerSpawnAsync(unit, _)
|
||||||
if unit == nil then return false, "unit is nil" end
|
---@param checkUnit Unit
|
||||||
if unit.getGroup == nil then return false, 'no get group function in unit object, most likely static' end
|
---@return boolean, string?
|
||||||
if Object.getCategory(unit) ~= Object.Category.UNIT then
|
local function isPlayer(checkUnit)
|
||||||
|
if checkUnit == nil then return false, "unit is nil" end
|
||||||
|
if checkUnit.getGroup == nil then return false, 'no get group function in unit object, most likely static' end
|
||||||
|
if Object.getCategory(checkUnit) ~= Object.Category.UNIT then
|
||||||
return false, "object is not a unit"
|
return false, "object is not a unit"
|
||||||
end
|
end
|
||||||
|
|
||||||
if unit:isExist() ~= true then return false, "unit does not exist" end
|
if checkUnit:isExist() ~= true then return false, "unit does not exist" end
|
||||||
local group = unit:getGroup()
|
local group = checkUnit:getGroup()
|
||||||
if group ~= nil then
|
if group ~= nil then
|
||||||
if AI_GROUPS[group:getName()] == true then
|
if AI_GROUPS[group:getName()] == true then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local players = DcsUtil.getAllPlayerUnits()
|
local players = DcsUtil.getAllPlayerUnits()
|
||||||
local unitName = unit:getName()
|
local unitName = checkUnit:getName()
|
||||||
for i, unit in pairs(players) do
|
for _, playerUnit in pairs(players) do
|
||||||
if unit:getName() == unitName then
|
if playerUnit:getName() == unitName then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -385,11 +431,8 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
if isPlayer(unit) == true then
|
if isPlayer(unit) == true then
|
||||||
local groupId = unit:getGroup():getID()
|
|
||||||
SpearheadEvents.TriggerPlayerEntersUnit(unit)
|
SpearheadEvents.TriggerPlayerEntersUnit(unit)
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if event.id == world.event.S_EVENT_BIRTH then
|
if event.id == world.event.S_EVENT_BIRTH then
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ do --setup route util
|
|||||||
---@param attackHelos boolean
|
---@param attackHelos boolean
|
||||||
---@return table
|
---@return table
|
||||||
local function GetCAPTargetTypes(attackHelos)
|
local function GetCAPTargetTypes(attackHelos)
|
||||||
|
---@type table<number, string>
|
||||||
local targetTypes = {
|
local targetTypes = {
|
||||||
[1] = "Planes",
|
[1] = "Planes",
|
||||||
}
|
}
|
||||||
@@ -53,8 +54,9 @@ do --setup route util
|
|||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@param groupName string
|
---@param groupName string
|
||||||
---@param position table { x, y}
|
---@param position Vec3
|
||||||
---@param altitude number
|
---@param altitude number
|
||||||
|
---@param deviationdistance number
|
||||||
---@param speed number
|
---@param speed number
|
||||||
---@param duration number
|
---@param duration number
|
||||||
---@param engageHelos boolean
|
---@param engageHelos boolean
|
||||||
@@ -92,7 +94,7 @@ do --setup route util
|
|||||||
action = {
|
action = {
|
||||||
id = "Script",
|
id = "Script",
|
||||||
params = {
|
params = {
|
||||||
command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")"
|
command = "pcall(GlobalCapCallBacks.PublishOnStation, \"" .. groupName .. "\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +124,7 @@ do --setup route util
|
|||||||
},
|
},
|
||||||
stopCondition = {
|
stopCondition = {
|
||||||
duration = durationBefore10,
|
duration = durationBefore10,
|
||||||
condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\", 0.10)",
|
condition = "return GlobalCapCallBacks.IsBingoFuel(\"" .. groupName .. "\", 0.10)",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -135,7 +137,7 @@ do --setup route util
|
|||||||
action = {
|
action = {
|
||||||
id = "Script",
|
id = "Script",
|
||||||
params = {
|
params = {
|
||||||
command = "pcall(Spearhead.Events.PublishRTBInTen, \"" .. groupName .. "\")"
|
command = "pcall(GlobalCapCallBacks.PublishRTBInTen, \"" .. groupName .. "\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +158,7 @@ do --setup route util
|
|||||||
},
|
},
|
||||||
stopCondition = {
|
stopCondition = {
|
||||||
duration = durationAfter10,
|
duration = durationAfter10,
|
||||||
condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\")",
|
condition = "return GlobalCapCallBacks.IsBingoFuel(\"" .. groupName .. "\", 0.10)",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -169,7 +171,7 @@ do --setup route util
|
|||||||
action = {
|
action = {
|
||||||
id = "Script",
|
id = "Script",
|
||||||
params = {
|
params = {
|
||||||
command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")"
|
command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,7 +183,7 @@ do --setup route util
|
|||||||
end
|
end
|
||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@param position table { x, y}
|
---@param position Vec3
|
||||||
---@param altitude number
|
---@param altitude number
|
||||||
---@param speed number
|
---@param speed number
|
||||||
---@param childTasks table
|
---@param childTasks table
|
||||||
@@ -210,7 +212,8 @@ do --setup route util
|
|||||||
---comment
|
---comment
|
||||||
---@param groupName string groupName you're creating this route for
|
---@param groupName string groupName you're creating this route for
|
||||||
---@param airdromeId number airdromeId
|
---@param airdromeId number airdromeId
|
||||||
---@param capPoint table { x, z }
|
---@param capPoint Vec3
|
||||||
|
---@param racetrackSecondPoint Vec3?
|
||||||
---@param altitude number
|
---@param altitude number
|
||||||
---@param speed number
|
---@param speed number
|
||||||
---@param durationOnStation number
|
---@param durationOnStation number
|
||||||
@@ -258,7 +261,8 @@ do --setup route util
|
|||||||
if racetrackSecondPoint then orbitType = "Race-Track" end
|
if racetrackSecondPoint then orbitType = "Race-Track" end
|
||||||
|
|
||||||
local basePoint = base:getPoint()
|
local basePoint = base:getPoint()
|
||||||
local points = {}
|
---@type Array<table>
|
||||||
|
local points
|
||||||
if racetrackSecondPoint == nil then
|
if racetrackSecondPoint == nil then
|
||||||
points = {
|
points = {
|
||||||
[1] = FlyToPointTask(capPoint, altitude, speed, additionalFlyOverTasks),
|
[1] = FlyToPointTask(capPoint, altitude, speed, additionalFlyOverTasks),
|
||||||
@@ -304,6 +308,7 @@ do --setup route util
|
|||||||
end
|
end
|
||||||
|
|
||||||
local group = Group.getByName(groupName)
|
local group = Group.getByName(groupName)
|
||||||
|
---@type Vec3?
|
||||||
local pos;
|
local pos;
|
||||||
local i = 1
|
local i = 1
|
||||||
if group == nil then
|
if group == nil then
|
||||||
@@ -375,7 +380,7 @@ do --setup route util
|
|||||||
action = {
|
action = {
|
||||||
id = "Script",
|
id = "Script",
|
||||||
params = {
|
params = {
|
||||||
command = "pcall(Spearhead.Events.PublishRTB, \"" ..
|
command = "pcall(GlobalCapCallBacks.PublishRTB, \"" ..
|
||||||
groupName .. "\")"
|
groupName .. "\")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -393,6 +398,9 @@ do --setup route util
|
|||||||
}, ""
|
}, ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param pointA Vec3
|
||||||
|
---@param pointB Vec3
|
||||||
|
---@return table, string?
|
||||||
ROUTE_UTIL.CreateCarrierRacetrack = function(pointA, pointB)
|
ROUTE_UTIL.CreateCarrierRacetrack = function(pointA, pointB)
|
||||||
return {
|
return {
|
||||||
id = "Mission",
|
id = "Mission",
|
||||||
@@ -461,7 +469,7 @@ do --setup route util
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, ""
|
}, nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,295 +1,618 @@
|
|||||||
local Events = require("classes.spearhead_events")
|
local Events = require("classes.spearhead_events")
|
||||||
local Util = require("classes.util.Util")
|
local Util = require("classes.util.Util")
|
||||||
|
local DcsUtil = require("classes.util.DcsUtil")
|
||||||
local Logger = require("classes.util.Logger")
|
local Logger = require("classes.util.Logger")
|
||||||
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
|
local PersistenceConfig = require("classes.configuration.PersistenceConfig")
|
||||||
|
|
||||||
local ExtraStage = require("classes.stageClasses.Stages.ExtraStage")
|
local ExtraStage = require("classes.stageClasses.Stages.ExtraStage")
|
||||||
local PrimaryStage = require("classes.stageClasses.Stages.PrimaryStage")
|
local PrimaryStage = require("classes.stageClasses.Stages.PrimaryStage")
|
||||||
local WaitingStage = require("classes.stageClasses.Stages.WaitingStage")
|
local WaitingStage = require("classes.stageClasses.Stages.WaitingStage")
|
||||||
|
local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper")
|
||||||
local StagesByName = {}
|
local StageRepository = require("classes.stageClasses.StageRepository")
|
||||||
|
local StageLane = require("classes.stageClasses.StageLane")
|
||||||
---@type table<string, Array<Stage>>
|
local Persistence = require("classes.persistence.Persistence")
|
||||||
local StagesByIndex = {}
|
|
||||||
|
|
||||||
---@type table<string, Array<Stage>>
|
|
||||||
local SideStageByIndex = {}
|
|
||||||
|
|
||||||
---@type table<string, Array<WaitingStage>>
|
|
||||||
local WaitingStagesByIndex = {}
|
|
||||||
|
|
||||||
local currentStage = -99
|
local currentStage = -99
|
||||||
|
|
||||||
---@class GlobalStageManager : StageCompleteListener
|
---@class GlobalStageManager : StageCompleteListener, OnStageChangedListener, OnStageNumberChangeCompleteListener
|
||||||
---@field private database Database
|
---@field private database Database
|
||||||
---@field private logger Logger
|
---@field private logger Logger
|
||||||
---@field private stageConfig StageConfig
|
---@field private stageConfig StageConfig
|
||||||
|
---@field private _stageRepository StageRepository
|
||||||
|
---@field private _missionCommandsHelper MissionCommandsHelper
|
||||||
local GlobalStageManager = {}
|
local GlobalStageManager = {}
|
||||||
GlobalStageManager.__index = GlobalStageManager
|
GlobalStageManager.__index = GlobalStageManager
|
||||||
|
|
||||||
GlobalStageManager.getCurrentStage = function() return currentStage end
|
GlobalStageManager.getCurrentStage = function() return currentStage end
|
||||||
|
|
||||||
|
---@type GlobalStageManager
|
||||||
|
local singletonInstance = nil
|
||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@param database Database
|
---@param database Database
|
||||||
---@param stageConfig StageConfig
|
---@param stageConfig StageConfig
|
||||||
---@param logLevel LogLevel
|
---@param logLevel LogLevel
|
||||||
---@param spawnManager SpawnManager
|
---@param spawnManager SpawnManager
|
||||||
---@return nil
|
---@return GlobalStageManager
|
||||||
function GlobalStageManager.NewAndStart(database, stageConfig, logLevel, spawnManager)
|
function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
|
||||||
|
if singletonInstance ~= nil then
|
||||||
|
return singletonInstance
|
||||||
|
end
|
||||||
|
|
||||||
local logger = Logger.new("StageManager", logLevel)
|
local logger = Logger.new("StageManager", logLevel)
|
||||||
logger:info("Using Stage Log Level: " .. logLevel)
|
logger:info("Using Stage Log Level: " .. logLevel)
|
||||||
local self = setmetatable({}, GlobalStageManager)
|
local self = setmetatable({}, GlobalStageManager)
|
||||||
|
singletonInstance = self
|
||||||
|
|
||||||
self.database = database
|
self.database = database
|
||||||
self.stageConfig = stageConfig
|
self.stageConfig = stageConfig
|
||||||
|
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
|
||||||
|
self._stageRepository = StageRepository.getInstance()
|
||||||
|
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
if stageConfig.isAutoStages ~= true then
|
if stageConfig.isAutoStages ~= true then
|
||||||
logger:warn("Spearhead will not automatically progress stages due to the given settings. If you manually have implemented this, please ignore this message")
|
logger:warn(
|
||||||
|
"Spearhead will not automatically progress stages due to the given settings. If you manually have implemented this, please ignore this message")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type OnStageChangedListener
|
Events.AddStageNumberChangedListener(self)
|
||||||
local OnStageNumberChangedListener = {
|
|
||||||
OnStageNumberChanged = function (self, number)
|
|
||||||
currentStage = number
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Events.AddStageNumberChangedListener(OnStageNumberChangedListener)
|
|
||||||
|
|
||||||
for _, stageName in pairs(database:getStagezoneNames()) do
|
for _, stageName in pairs(database:getStagezoneNames()) do
|
||||||
logger:debug("Found stage zone with name: " .. stageName)
|
logger:debug("Found stage zone with name: " .. stageName)
|
||||||
|
|
||||||
if Util.startswith(stageName, "missionstage", true) then
|
local parseResult = self:ParseStageName(stageName)
|
||||||
local valid = true
|
if parseResult.isValid == false then
|
||||||
local split = Util.split_string(stageName, "_")
|
logger:warn("Stage zone with name " .. stageName .. " is not valid: " .. parseResult.invalidReason)
|
||||||
if Util.tableLength(split) < 2 then
|
else
|
||||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a order number or valid format")
|
local initData = {
|
||||||
valid = false
|
stageZoneName = parseResult.stageZoneName,
|
||||||
|
stageNumber = parseResult.orderNumber,
|
||||||
|
stageDisplayName = parseResult.stageDisplayName,
|
||||||
|
stageLaneIdentifier = parseResult.stageLaneIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
if parseResult.stageType == "PrimaryStage" then
|
||||||
|
local stage = PrimaryStage.New(database, stageConfig, logger, initData, spawnManager)
|
||||||
|
|
||||||
|
stage:AddStageCompleteListener(self)
|
||||||
|
self._stageRepository:AddStage(stage)
|
||||||
|
elseif parseResult.stageType == "ExtraStage" then
|
||||||
|
local stage = ExtraStage.New(database, stageConfig, logger, initData, spawnManager)
|
||||||
|
stage:AddStageCompleteListener(self)
|
||||||
|
self._stageRepository:AddStage(stage)
|
||||||
|
elseif parseResult.stageType == "WaitingStage" then
|
||||||
|
local waitingStage = WaitingStage.New(database, stageConfig, logger, initData,
|
||||||
|
parseResult.waitingStageSeconds, spawnManager)
|
||||||
|
waitingStage:AddStageCompleteListener(self)
|
||||||
|
self._stageRepository:AddStage(waitingStage)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
singletonInstance = self
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function GlobalStageManager:Start()
|
||||||
|
self.logger:info("Starting GlobalStageManager")
|
||||||
|
|
||||||
|
local startingStage = self.stageConfig.startingStage or 1
|
||||||
|
local persistenceConfig = PersistenceConfig.new()
|
||||||
|
if persistenceConfig:isEnabled() == true then
|
||||||
|
self.logger:info("Persistence is enabled, loading stage state from persistence")
|
||||||
|
|
||||||
|
local stageLanes = self._stageRepository:getAllStageLanes()
|
||||||
|
for _, stageLane in pairs(stageLanes) do
|
||||||
|
local stageLaneIdentifier = stageLane:GetStageLaneIdentifier()
|
||||||
|
local persistedStage = Persistence.GetActiveStage(stageLaneIdentifier)
|
||||||
|
if persistedStage then
|
||||||
|
self.logger:info("Loaded persisted stage " ..
|
||||||
|
persistedStage .. " for lane " .. (stageLaneIdentifier or "default"))
|
||||||
|
stageLane:SetActiveStageIndex(persistedStage)
|
||||||
|
Events.PublishStageNumberChanged(persistedStage, stageLaneIdentifier)
|
||||||
|
else
|
||||||
|
stageLane:SetActiveStageIndex(startingStage)
|
||||||
|
Events.PublishStageNumberChanged(startingStage, stageLaneIdentifier)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.logger:info("Persistence is disabled, starting at stage " .. startingStage)
|
||||||
|
local stageLanes = self._stageRepository:getAllStageLanes()
|
||||||
|
for _, stageLane in pairs(stageLanes) do
|
||||||
|
stageLane:SetActiveStageIndex(startingStage)
|
||||||
|
Events.PublishStageNumberChanged(startingStage, stageLane:GetStageLaneIdentifier())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class StageNameParseResult
|
||||||
|
---@field isValid boolean
|
||||||
|
---@field orderNumber integer
|
||||||
|
---@field stageLaneIdentifier string?
|
||||||
|
---@field stageDisplayName string
|
||||||
|
---@field stageZoneName string
|
||||||
|
---@field stageType StageType
|
||||||
|
---@field invalidReason string?
|
||||||
|
---@field waitingStageSeconds integer?
|
||||||
|
|
||||||
|
---@param self GlobalStageManager
|
||||||
|
---@param stageName string
|
||||||
|
---@return StageNameParseResult
|
||||||
|
function GlobalStageManager:ParseStageName(stageName)
|
||||||
|
local split = Util.split_string(stageName, "_")
|
||||||
|
|
||||||
if Util.tableLength(split) < 3 then
|
if Util.tableLength(split) < 3 then
|
||||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a stage name")
|
return { isValid = false, invalidReason = "Stage zone with name " ..
|
||||||
|
stageName .. " does not have a order number or valid format" }
|
||||||
end
|
end
|
||||||
|
|
||||||
local orderNumber = nil
|
local typePart = string.lower(split[1])
|
||||||
local isSideStage = false
|
|
||||||
if valid == true then
|
if typePart == "missionstage" then
|
||||||
local orderNumberString = string.lower(split[2])
|
local orderNumberString = string.lower(split[2])
|
||||||
if Util.startswith(orderNumberString, "x") == true then
|
---@type StageType
|
||||||
isSideStage = true
|
local stageType = "PrimaryStage"
|
||||||
|
if Util.endsWith(orderNumberString, "x", true) == true then
|
||||||
|
stageType = "ExtraStage"
|
||||||
|
orderNumberString = orderNumberString:sub(1, -1)
|
||||||
|
end
|
||||||
|
local stageLaneIdentifier = nil
|
||||||
|
local first = orderNumberString:sub(1, 1)
|
||||||
|
if tonumber(first) == nil then -- first character is the lane identifier only if it's not a number
|
||||||
|
stageLaneIdentifier = string.lower(first)
|
||||||
|
|
||||||
orderNumberString = string.gsub(orderNumberString, "x", "")
|
orderNumberString = orderNumberString:sub(2)
|
||||||
orderNumber = tonumber(orderNumberString)
|
|
||||||
else
|
|
||||||
orderNumber = tonumber(split[2])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local orderNumber = tonumber(orderNumberString)
|
||||||
if orderNumber == nil then
|
if orderNumber == nil then
|
||||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a valid order number : " .. split[2])
|
return { isValid = false, invalidReason = "Stage zone with name " ..
|
||||||
valid = false
|
stageName .. " does not have a valid order number : " .. orderNumberString }
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local stageDisplayName = split[3]
|
local stageDisplayName = split[3]
|
||||||
local stagelogger = Logger.new(stageName, logLevel)
|
---@type StageNameParseResult
|
||||||
if valid == true and orderNumber then
|
local result = {
|
||||||
|
isValid = true,
|
||||||
---@type StageInitData
|
orderNumber = orderNumber,
|
||||||
local initData = {
|
stageLaneIdentifier = stageLaneIdentifier,
|
||||||
stageDisplayName = stageDisplayName,
|
stageDisplayName = stageDisplayName,
|
||||||
stageNumber = orderNumber,
|
|
||||||
stageZoneName = stageName,
|
stageZoneName = stageName,
|
||||||
|
stageType = stageType
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
if isSideStage == true then
|
elseif typePart == "waitingstage" then
|
||||||
local stage = ExtraStage.New(database, stageConfig, stagelogger, initData, spawnManager)
|
local stageType = "WaitingStage"
|
||||||
stage:AddStageCompleteListener(self)
|
local orderNumberString = split[2]
|
||||||
|
local orderNumber = tonumber(orderNumberString)
|
||||||
if SideStageByIndex[tostring(orderNumber)] == nil then SideStageByIndex[tostring(orderNumber)] = {} end
|
if orderNumber == nil then
|
||||||
table.insert(SideStageByIndex[tostring(orderNumber)], stage)
|
return { isValid = false, invalidReason = "Waiting Stage zone with name " ..
|
||||||
else
|
stageName .. " does not have a valid order number : " .. orderNumberString }
|
||||||
local stage = PrimaryStage.New(database, stageConfig, stagelogger, initData, spawnManager)
|
|
||||||
stage:AddStageCompleteListener(self)
|
|
||||||
|
|
||||||
if StagesByIndex[tostring(orderNumber)] == nil then StagesByIndex[tostring(orderNumber)] = {} end
|
|
||||||
table.insert(StagesByIndex[tostring(orderNumber)], stage)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if Util.startswith(stageName, "waitingstage", true) then
|
|
||||||
local valid = true
|
|
||||||
|
|
||||||
local split = Util.split_string(stageName, "_")
|
|
||||||
|
|
||||||
if Util.tableLength(split) < 3 then
|
|
||||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a order number or valid format")
|
|
||||||
valid = false
|
|
||||||
end
|
|
||||||
|
|
||||||
if valid == true then
|
|
||||||
local stageIndexString = split[2]
|
|
||||||
local stageIndex = tonumber(stageIndexString)
|
|
||||||
|
|
||||||
if not stageIndex then
|
|
||||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a valid order number")
|
|
||||||
valid = false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local waitingSecondsString = split[3]
|
local waitingSecondsString = split[3]
|
||||||
local waitingSeconds = tonumber(waitingSecondsString)
|
local waitingSeconds = tonumber(waitingSecondsString)
|
||||||
if not waitingSeconds then
|
if waitingSeconds == nil then
|
||||||
MissionEditorWarnings.Add("Waiting Stage zone with name " .. stageName .. " does not have a valid amount of seconds parameter")
|
return { isValid = false, invalidReason = "Waiting Stage zone with name " ..
|
||||||
valid = false
|
stageName .. " does not have a valid amount of seconds parameter : " .. waitingSecondsString }
|
||||||
end
|
end
|
||||||
|
|
||||||
if valid == true then
|
local stageDisplayName = "Waiting Stage " .. orderNumber
|
||||||
local stagelogger = Logger.new(stageName, logLevel)
|
|
||||||
|
|
||||||
---@type WaitingStageInitData
|
---@type StageNameParseResult
|
||||||
local initData = {
|
local result = {
|
||||||
stageDisplayName = "Waiting Stage " .. stageIndex,
|
isValid = true,
|
||||||
stageNumber = stageIndex or -99,
|
orderNumber = orderNumber,
|
||||||
|
stageLaneIdentifier = nil,
|
||||||
|
stageDisplayName = stageDisplayName,
|
||||||
stageZoneName = stageName,
|
stageZoneName = stageName,
|
||||||
waitingSeconds = waitingSeconds --[[@as integer]]
|
stageType = stageType,
|
||||||
|
waitingStageSeconds = waitingSeconds
|
||||||
}
|
}
|
||||||
local waitingStage = WaitingStage.New(database, stageConfig, stagelogger, initData, spawnManager)
|
return result
|
||||||
|
|
||||||
if WaitingStagesByIndex[tostring(stageIndex)] == nil then
|
|
||||||
WaitingStagesByIndex[tostring(stageIndex)] = {}
|
|
||||||
end
|
end
|
||||||
table.insert(WaitingStagesByIndex[tostring(stageIndex)], waitingStage)
|
return { isValid = false, invalidReason = "Stage zone with name " ..
|
||||||
|
stageName .. " has an unrecognized type: " .. typePart }
|
||||||
waitingStage:AddStageCompleteListener(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param stage Stage
|
||||||
function GlobalStageManager:OnStageComplete(stage)
|
function GlobalStageManager:OnStageComplete(stage)
|
||||||
self.logger:debug("Receiving stage complete event from: " .. stage.zoneName)
|
self.logger:debug("Receiving stage complete event from: " .. stage.zoneName)
|
||||||
|
|
||||||
local anyIncomplete = false
|
local stageIndex = stage:GetStageIndex()
|
||||||
self.logger:debug("Checking stages for index: " .. tostring(currentStage))
|
local laneIdentifier = stage:GetStageLaneIdentifier()
|
||||||
for index, stage in pairs(StagesByIndex[tostring(currentStage)]) do
|
|
||||||
if stage:IsComplete() == false then
|
local stageLane = self._stageRepository:getStageLane(laneIdentifier)
|
||||||
anyIncomplete = true
|
if not stageLane or not stageLane:IsCurrentStageIndexComplete() then
|
||||||
self.logger:debug("Need to wait for Stage " .. stage.zoneName .. " to be completed")
|
self.logger:debug("Stage lane " ..
|
||||||
|
(laneIdentifier or "default") .. " is not complete for stage index " .. stageIndex)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local nextStageIndex = stageIndex + 1
|
||||||
|
|
||||||
|
Events.PublishStageNumberChanged(nextStageIndex, laneIdentifier)
|
||||||
|
stageLane:SetActiveStageIndex(nextStageIndex)
|
||||||
|
|
||||||
|
if stageLane:IsDefaultStageLane() then
|
||||||
|
-- COMPLETION IN THE DEFAULT LANE
|
||||||
|
|
||||||
|
-- 1. Check if any lane is at status "BetweenChapters" and the next chapter start is "next stage index"
|
||||||
|
local allLanes = self._stageRepository:getAllStageLanes()
|
||||||
|
for _, lane in ipairs(allLanes) do
|
||||||
|
if lane:GetStageLaneIdentifier() ~= StageLane.DefaultLaneKey and lane:GetStageLaneState() == "BetweenChapters" then
|
||||||
|
local nextChapterStart = lane:GetNextChapterStart()
|
||||||
|
if nextChapterStart == nextStageIndex then
|
||||||
|
self.logger:debug("Lane " ..
|
||||||
|
(lane:GetStageLaneIdentifier() or "default") ..
|
||||||
|
" is at chapter start for next stage index " .. nextStageIndex)
|
||||||
|
Events.PublishStageNumberChanged(nextStageIndex, lane:GetStageLaneIdentifier())
|
||||||
|
lane:SetActiveStageIndex(nextStageIndex)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
self.logger:debug("Stage verified to be completed: " .. stage.zoneName)
|
-- COMPLETION IN A SIDE LANE
|
||||||
|
|
||||||
|
-- 1. Check if the default lane is at status "BetweenChapters"
|
||||||
|
local defaultStageLane = self._stageRepository:getStageLane(StageLane.DefaultLaneKey)
|
||||||
|
if defaultStageLane and defaultStageLane:GetStageLaneState() == "BetweenChapters" then
|
||||||
|
local nextChapterStart = defaultStageLane:GetNextChapterStart()
|
||||||
|
if nextChapterStart == nextStageIndex then
|
||||||
|
-- 2. Check if all side lanes are at or beyond the next chapter starts
|
||||||
|
local allLanes = self._stageRepository:getAllStageLanes()
|
||||||
|
local allSideLanesReady = true
|
||||||
|
for _, lane in ipairs(allLanes) do
|
||||||
|
if lane:GetStageLaneIdentifier() ~= StageLane.DefaultLaneKey then
|
||||||
|
-- Check if the lane is at or beyond the next chapter starts when "InChapter" eq "Active"
|
||||||
|
if lane:GetStageLaneState() == "InChapter" and lane:GetActiveStageIndex() < nextStageIndex then
|
||||||
|
allSideLanesReady = false
|
||||||
|
self.logger:debug("Lane " ..
|
||||||
|
(lane:GetStageLaneIdentifier() or "default") ..
|
||||||
|
" is not ready for next stage index " .. nextStageIndex)
|
||||||
|
break
|
||||||
|
|
||||||
|
-- When in between chapters, check if the next chapter start is less than the next stage index, meaning there is still stages to be completed before the main lane can be activated again
|
||||||
|
elseif lane:GetStageLaneState() == "BetweenChapters" then
|
||||||
|
local nextChapter = lane:GetNextChapterStart()
|
||||||
|
if nextChapter and nextChapter < nextStageIndex then
|
||||||
|
allSideLanesReady = false
|
||||||
|
self.logger:debug("Lane " ..
|
||||||
|
(lane:GetStageLaneIdentifier() or "default") ..
|
||||||
|
" is not ready for next stage index " .. nextStageIndex)
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if anyIncomplete == false and self.stageConfig.isAutoStages == true then
|
|
||||||
|
|
||||||
-- CHECK WAITING STAGES
|
|
||||||
local nextStage = currentStage + 1
|
|
||||||
|
|
||||||
if WaitingStagesByIndex[tostring(nextStage)] then
|
|
||||||
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
|
|
||||||
if waitingStage:IsActive() == false then
|
|
||||||
waitingStage:ActivateStage()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local anyWaiting = false
|
if allSideLanesReady then
|
||||||
if WaitingStagesByIndex[tostring(nextStage)] then
|
self.logger:debug("All side lanes are ready for next stage index " ..
|
||||||
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
|
nextStageIndex .. ", activating default lane")
|
||||||
if waitingStage:IsComplete() == false then
|
Events.PublishStageNumberChanged(nextStageIndex, nil)
|
||||||
anyWaiting = true
|
defaultStageLane:SetActiveStageIndex(nextStageIndex)
|
||||||
|
else
|
||||||
|
self.logger:debug("Not all side lanes are ready for next stage index " ..
|
||||||
|
nextStageIndex .. ", default lane will not be activated")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param stageNumber number
|
||||||
|
---@param stageLaneIdentifier string?
|
||||||
|
---@return boolean?
|
||||||
|
function GlobalStageManager:IsStageComplete(stageNumber, stageLaneIdentifier)
|
||||||
|
local stageLane = self._stageRepository:getStageLane(stageLaneIdentifier)
|
||||||
|
if not stageLane then
|
||||||
|
self.logger:warn("Stage lane " .. (stageLaneIdentifier or "default") .. " does not exist")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return stageLane:IsStageIndexComplete(stageNumber)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@public
|
||||||
|
function GlobalStageManager:OnStageNumberChanged(stageNumber, stageLaneIdentifier)
|
||||||
|
-- only react on "main" lane changes, ignore other lanes for now
|
||||||
|
if stageLaneIdentifier ~= nil then return end
|
||||||
|
|
||||||
|
self.logger:debug("Stage number changed to: " .. tostring(stageNumber))
|
||||||
|
self:UpdateDrawings(stageNumber)
|
||||||
|
end
|
||||||
|
|
||||||
|
function GlobalStageManager:OnStageNumberChangeComplete(stageNumber, stageLaneIdentifier)
|
||||||
|
-- only react on "main" lane changes, ignore other lanes for now
|
||||||
|
if stageLaneIdentifier ~= nil then return end
|
||||||
|
|
||||||
|
self.logger:debug("Stage number change complete to: " .. tostring(stageNumber))
|
||||||
|
---@type Array<Group>
|
||||||
|
local groups = {}
|
||||||
|
|
||||||
|
for _, player in pairs(DcsUtil.getAllPlayerUnits()) do
|
||||||
|
local group = player:getGroup()
|
||||||
|
if group then
|
||||||
|
table.insert(groups, group)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if anyWaiting == false then
|
for _, group in pairs(groups) do
|
||||||
local newStageNumber = currentStage + 1
|
self._missionCommandsHelper:OverviewToGroup(group:getID())
|
||||||
self:UpdateDrawings(newStageNumber)
|
|
||||||
self.logger:debug("Setting next stage to: " .. tostring(newStageNumber))
|
|
||||||
Events.PublishStageNumberChanged(newStageNumber)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
function GlobalStageManager:UpdateDrawings(stageNumber)
|
---@param stageNumber number
|
||||||
local drawings = self.database:getCustomDrawings()
|
---@param stageLaneIdentifier string?
|
||||||
|
function GlobalStageManager:UpdateDrawings(stageNumber, stageLaneIdentifier)
|
||||||
|
self.logger:debug("Updating custom drawings for stage number: " .. tostring(stageNumber))
|
||||||
|
local drawings = self.database:getStageDrawings()
|
||||||
for _, drawing in pairs(drawings) do
|
for _, drawing in pairs(drawings) do
|
||||||
local startStage, stopStage = drawing:GetStartAndStop()
|
local startStage = drawing:GetStartingStage()
|
||||||
|
local stopStage = drawing:GetRemoveAtStage()
|
||||||
|
local laneIdentifier = drawing:GetStageLaneIdentifier()
|
||||||
|
|
||||||
|
if laneIdentifier == stageLaneIdentifier then
|
||||||
if stageNumber >= startStage and stageNumber < stopStage then
|
if stageNumber >= startStage and stageNumber < stopStage then
|
||||||
|
self.logger:debug("Drawing " ..
|
||||||
|
drawing:GetName() .. " is active for stage number: " .. tostring(stageNumber))
|
||||||
drawing:Draw()
|
drawing:Draw()
|
||||||
else
|
else
|
||||||
|
self.logger:debug("Drawing " ..
|
||||||
|
drawing:GetName() .. " is not active for stage number: " .. tostring(stageNumber))
|
||||||
drawing:Remove()
|
drawing:Remove()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function GlobalStageManager:PrintMermaidStage()
|
||||||
|
local lanes = self._stageRepository:getAllStageLanes()
|
||||||
|
|
||||||
GlobalStageManager.printFullOverview = function ()
|
---@type table<string, string>
|
||||||
|
local nodes = {}
|
||||||
|
---@type table<string, string>
|
||||||
|
local edges = {}
|
||||||
|
---@type table<string, Array<number>>
|
||||||
|
local stageIndicesByLane = {} -- Track all stage indices per lane
|
||||||
|
local mainLaneId = "default"
|
||||||
|
|
||||||
local logger = Logger.new("StageOverview", "INFO")
|
-- Color palette for lanes (26 vibrant colors optimized for dark mode)
|
||||||
logger:info("Stage overview:")
|
local laneColors = {
|
||||||
|
"#FF6B6B", -- 1: Red
|
||||||
|
"#4ECDC4", -- 2: Teal
|
||||||
|
"#45B7D1", -- 3: Blue
|
||||||
|
"#FFA502", -- 4: Orange
|
||||||
|
"#95E1D3", -- 5: Mint
|
||||||
|
"#F38181", -- 6: Coral
|
||||||
|
"#AA96DA", -- 7: Purple
|
||||||
|
"#FCBAD3", -- 8: Pink
|
||||||
|
"#A8E6CF", -- 9: Light green
|
||||||
|
"#FFD3B6", -- 10: Peach
|
||||||
|
"#FFAAA5", -- 11: Light red
|
||||||
|
"#FF8B94", -- 12: Rose
|
||||||
|
"#FFEAA7", -- 13: Butter
|
||||||
|
"#DFE6E9", -- 14: Gray
|
||||||
|
"#00B894", -- 15: Emerald
|
||||||
|
"#0984E3", -- 16: Cobalt
|
||||||
|
"#6C5CE7", -- 17: Indigo
|
||||||
|
"#A29BFE", -- 18: Lavender
|
||||||
|
"#FD79A8", -- 19: Magenta
|
||||||
|
"#FDCB6E", -- 20: Gold
|
||||||
|
"#6C757D", -- 21: Slate
|
||||||
|
"#20C997", -- 22: Seafoam
|
||||||
|
"#E74C3C", -- 23: Scarlet
|
||||||
|
"#3498DB", -- 24: Dodger blue
|
||||||
|
"#9B59B6", -- 25: Amethyst
|
||||||
|
"#1ABC9C", -- 26: Turquoise
|
||||||
|
}
|
||||||
|
---@type table<string, string>
|
||||||
|
local laneColorMap = {} -- Map lane ID to color
|
||||||
|
---@type number
|
||||||
|
local colorIndex = 1
|
||||||
|
|
||||||
local max = 0
|
-- Build nodes and collect all stage indices per lane
|
||||||
local lines = {}
|
for _, lane in ipairs(lanes) do
|
||||||
for stageIndex, stages in pairs(StagesByIndex) do
|
local laneId = lane:GetStageLaneIdentifier() or "default"
|
||||||
|
local stageIndices = lane:GetAllStageIndices()
|
||||||
|
stageIndicesByLane[laneId] = stageIndices
|
||||||
|
|
||||||
local totalStrike = 0
|
-- Assign color to this lane
|
||||||
local totalbai = 0
|
laneColorMap[laneId] = laneColors[colorIndex]
|
||||||
local totaldead = 0
|
colorIndex = colorIndex + 1
|
||||||
local totalMissions = 0
|
if colorIndex > #laneColors then
|
||||||
local totalCas = 0
|
colorIndex = 1
|
||||||
|
|
||||||
for _, stage in pairs(stages) do
|
|
||||||
|
|
||||||
local strike, dead, bai, cas = stage:GetStageStats()
|
|
||||||
|
|
||||||
totalStrike = totalStrike + strike
|
|
||||||
totalbai = totalbai + bai
|
|
||||||
totaldead = totaldead + dead
|
|
||||||
totalCas = totalCas + cas
|
|
||||||
totalMissions = totalMissions + strike + dead + bai + cas
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local index = tonumber(stageIndex)
|
-- Create nodes for each stage index in this lane
|
||||||
if index then
|
for _, stageIndex in ipairs(stageIndices) do
|
||||||
if index > max then
|
local stages = lane:GetStagesAtIndex(stageIndex)
|
||||||
max = index
|
|
||||||
end
|
if stages then
|
||||||
lines[index] ="Stage# " .. tostring(stageIndex).. " | " .. totalStrike .. " strikes | " .. totaldead .. " dead | " .. totalbai .. " BAI | " .. totalCas .. " CAS | Total:" .. totalMissions
|
for _, stage in ipairs(stages) do
|
||||||
|
local nodeId = laneId .. "_" .. stageIndex
|
||||||
|
local stageType = stage:GetStageType()
|
||||||
|
local stageName = stage.stageName or stage.zoneName
|
||||||
|
|
||||||
|
-- Generate bracket label: [1], [2] for default, [w1], [e2] for other lanes
|
||||||
|
---@type string
|
||||||
|
local bracketLabel
|
||||||
|
if laneId == "default" then
|
||||||
|
bracketLabel = "[" .. stageIndex .. "]"
|
||||||
else
|
else
|
||||||
logger:warn("Stage index is not a number: " .. stageIndex)
|
bracketLabel = "[" .. laneId .. stageIndex .. "]"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Combined label: [bracket] Name
|
||||||
|
local label = bracketLabel .. " " .. stageName
|
||||||
|
|
||||||
|
-- Different node shapes for different stage types
|
||||||
|
local nodeShape = "["
|
||||||
|
local nodeEnd = "]"
|
||||||
|
if stageType == "ExtraStage" then
|
||||||
|
nodeShape = "(["
|
||||||
|
nodeEnd = "])"
|
||||||
|
elseif stageType == "WaitingStage" then
|
||||||
|
nodeShape = "[["
|
||||||
|
nodeEnd = "]]"
|
||||||
|
end
|
||||||
|
|
||||||
|
nodes[nodeId] = string.format(' %s%s"%s"%s',
|
||||||
|
nodeId, nodeShape, label, nodeEnd)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 1, max do
|
-- Add edges for sequential stages within the same lane
|
||||||
if lines[i] then
|
for laneId, stageIndices in pairs(stageIndicesByLane) do
|
||||||
logger:info(lines[i])
|
for i = 1, #stageIndices - 1 do
|
||||||
|
local fromIdx = stageIndices[i]
|
||||||
|
local toIdx = stageIndices[i + 1]
|
||||||
|
local fromNodeId = laneId .. "_" .. fromIdx
|
||||||
|
local toNodeId = laneId .. "_" .. toIdx
|
||||||
|
|
||||||
|
-- Check if this is a chapter boundary (gap)
|
||||||
|
local isChapter = false
|
||||||
|
for _, lane in ipairs(lanes) do
|
||||||
|
local normalizedLaneId = lane:GetStageLaneIdentifier() or "default"
|
||||||
|
if normalizedLaneId == laneId then
|
||||||
|
isChapter = lane:IsChapterStart(toIdx)
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local label = isChapter and "|chapter|" or ""
|
||||||
|
table.insert(edges, string.format(' %s -->%s %s', fromNodeId, label, toNodeId))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get main lane reference
|
||||||
|
local mainStageLane = nil
|
||||||
|
for _, lane in ipairs(lanes) do
|
||||||
|
if lane:IsDefaultStageLane() then
|
||||||
|
mainStageLane = lane
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add dependencies from DEFAULT lane to SIDE lanes at chapter starts
|
||||||
|
-- Side lanes activate when main completes the stage BEFORE the chapter start
|
||||||
|
if mainStageLane then
|
||||||
|
for _, lane in ipairs(lanes) do
|
||||||
|
if lane:IsDefaultStageLane() == false then
|
||||||
|
local sideId = lane:GetStageLaneIdentifier()
|
||||||
|
local sideIndices = stageIndicesByLane[sideId] or {} --[[@as Array<number>]]
|
||||||
|
|
||||||
|
-- For each stage in the side lane
|
||||||
|
for _, stageIdx in ipairs(sideIndices) do
|
||||||
|
-- Check if this is a chapter start in the side lane
|
||||||
|
if lane:IsChapterStart(stageIdx) then
|
||||||
|
-- Main lane must complete (stageIdx - 1) to activate side lane at stageIdx
|
||||||
|
local fromNodeId = mainLaneId .. "_" .. (stageIdx - 1)
|
||||||
|
local toNodeId = sideId .. "_" .. stageIdx
|
||||||
|
-- Side lane activates when main lane completes the prior stage
|
||||||
|
table.insert(edges, string.format(' %s -->|unlock| %s', fromNodeId, toNodeId))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add dependencies from SIDE lanes gating the MAIN lane at chapter boundaries
|
||||||
|
-- Main cannot advance to the next chapter until ALL side lanes are at or above that chapter index
|
||||||
|
if mainStageLane then
|
||||||
|
local mainIndices = stageIndicesByLane[mainLaneId]
|
||||||
|
|
||||||
|
-- For each chapter start in main lane (skip first one)
|
||||||
|
for i = 2, #mainIndices do
|
||||||
|
local nextIdx = mainIndices[i]
|
||||||
|
|
||||||
|
-- Check if nextIdx is a chapter start (there's a gap before it)
|
||||||
|
if mainStageLane:IsChapterStart(nextIdx) then
|
||||||
|
-- Before main can progress to nextIdx, all side lanes must be at >= nextIdx
|
||||||
|
for _, lane in ipairs(lanes) do
|
||||||
|
if lane:IsDefaultStageLane() == false then
|
||||||
|
local sideId = lane:GetStageLaneIdentifier()
|
||||||
|
local sideIndices = stageIndicesByLane[sideId] or {} --[[@as Array<number>]]
|
||||||
|
|
||||||
|
-- Find the appropriate gate node for this side lane
|
||||||
|
-- Use first stage >= nextIdx if it exists, otherwise use the highest stage
|
||||||
|
local gateStageIdx = nil
|
||||||
|
|
||||||
|
for _, idx in ipairs(sideIndices) do
|
||||||
|
if idx >= nextIdx then
|
||||||
|
gateStageIdx = idx
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If no stage >= nextIdx, use the highest stage in this lane
|
||||||
|
if gateStageIdx == nil and #sideIndices > 0 then
|
||||||
|
gateStageIdx = sideIndices[#sideIndices]
|
||||||
|
end
|
||||||
|
|
||||||
|
if gateStageIdx then
|
||||||
|
local fromNodeId = sideId .. "_" .. gateStageIdx
|
||||||
|
local toNodeId = mainLaneId .. "_" .. nextIdx
|
||||||
|
table.insert(edges, string.format(' %s -->|gate| %s', fromNodeId, toNodeId))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build complete Mermaid diagram
|
||||||
|
local diagramLines = { "graph TD" }
|
||||||
|
|
||||||
|
-- Add class definitions for each lane with colors and contrasting text
|
||||||
|
for laneId, color in pairs(laneColorMap) do
|
||||||
|
table.insert(diagramLines,
|
||||||
|
string.format(' classDef lane_%s fill:%s,stroke:#333,stroke-width:2px,color:#000', laneId, color))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add nodes
|
||||||
|
for _, nodeStr in pairs(nodes) do
|
||||||
|
table.insert(diagramLines, nodeStr)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add edges
|
||||||
|
for _, edgeStr in pairs(edges) do
|
||||||
|
table.insert(diagramLines, edgeStr)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Apply classes to nodes
|
||||||
|
for nodeId in pairs(nodes) do
|
||||||
|
local laneId = nodeId:match("(.+)_[0-9]+$")
|
||||||
|
if laneId then
|
||||||
|
table.insert(diagramLines, string.format(' class %s lane_%s', nodeId, laneId))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Print as single multi-line message
|
||||||
|
local diagram = "========== STAGE FLOW DIAGRAM ==========\n" ..
|
||||||
|
table.concat(diagramLines, "\n") .. "\n========== END DIAGRAM =========="
|
||||||
|
self.logger:info(diagram)
|
||||||
end
|
end
|
||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@param stageNumber number
|
---@param stageNumber number
|
||||||
|
---@param stageLaneIdentifier string? nil for default lan
|
||||||
---@return boolean | nil
|
---@return boolean | nil
|
||||||
GlobalStageManager.isStageComplete = function (stageNumber)
|
GlobalStageManager.isStageComplete = function(stageNumber, stageLaneIdentifier)
|
||||||
|
if singletonInstance == nil then
|
||||||
local stageIndex = tostring(stageNumber)
|
Logger.new("StageManager", "INFO"):warn(
|
||||||
|
"GlobalStageManager.isStageComplete called before GlobalStageManager was initialized. Returning nil")
|
||||||
if StagesByIndex[stageIndex] == nil then return nil end
|
return nil
|
||||||
|
|
||||||
for _, stage in ipairs(StagesByIndex[stageIndex]) do
|
|
||||||
if stage:IsComplete() == false then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return true
|
if stageLaneIdentifier then
|
||||||
|
stageLaneIdentifier = string.lower(stageLaneIdentifier)
|
||||||
|
end
|
||||||
|
|
||||||
|
return singletonInstance:IsStageComplete(stageNumber, stageLaneIdentifier)
|
||||||
end
|
end
|
||||||
|
|
||||||
return GlobalStageManager
|
return GlobalStageManager
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ function SpearheadGroup:Spawn(lateStart)
|
|||||||
uncontrolled = lateStart,
|
uncontrolled = lateStart,
|
||||||
}
|
}
|
||||||
|
|
||||||
local spawnedObject, isStatic = self._spawnManager:SpawnGroup(self._groupName, overrides, self._isPersistent)
|
local _, isStatic = self._spawnManager:SpawnGroup(self._groupName, overrides, self._isPersistent)
|
||||||
self._isStatic = isStatic
|
self._isStatic = isStatic
|
||||||
self._isSpawned = true
|
self._isSpawned = true
|
||||||
end
|
end
|
||||||
@@ -91,9 +91,9 @@ function SpearheadGroup:GetCoalition()
|
|||||||
end
|
end
|
||||||
|
|
||||||
---comment
|
---comment
|
||||||
---@return table result list of objects
|
---@return Array<Unit|StaticObject> result list of objects
|
||||||
function SpearheadGroup:GetObjects()
|
function SpearheadGroup:GetObjects()
|
||||||
|
---@type Array<Unit|StaticObject>
|
||||||
local result = {}
|
local result = {}
|
||||||
if self._isStatic == true then
|
if self._isStatic == true then
|
||||||
local staticObject = StaticObject.getByName(self._groupName)
|
local staticObject = StaticObject.getByName(self._groupName)
|
||||||
|
|||||||
@@ -50,12 +50,6 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
|
|||||||
---@type table<string, Vec3>
|
---@type table<string, Vec3>
|
||||||
local redUnitsPos = {}
|
local redUnitsPos = {}
|
||||||
|
|
||||||
|
|
||||||
local buildable = false
|
|
||||||
if self._buildableCrateKilos and self._buildableCrateKilos > 0 then
|
|
||||||
buildable = true
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, groupName in pairs(blueSamData.groups) do
|
for _, groupName in pairs(blueSamData.groups) do
|
||||||
local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true)
|
local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true)
|
||||||
if spearheadGroup then
|
if spearheadGroup then
|
||||||
@@ -78,7 +72,7 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
|
|||||||
|
|
||||||
--Cleanup units
|
--Cleanup units
|
||||||
local cleanup_distance = 5
|
local cleanup_distance = 5
|
||||||
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
|
for _, blueUnitPos in pairs(blueUnitsPos) do
|
||||||
for redUnitName, redUnitPos in pairs(redUnitsPos) do
|
for redUnitName, redUnitPos in pairs(redUnitsPos) do
|
||||||
local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos)
|
local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos)
|
||||||
if distance <= cleanup_distance then
|
if distance <= cleanup_distance then
|
||||||
@@ -89,7 +83,7 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
|
|||||||
|
|
||||||
local zone = DcsUtil.getZoneByName(zoneName)
|
local zone = DcsUtil.getZoneByName(zoneName)
|
||||||
if zone then
|
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
|
end
|
||||||
|
|
||||||
return self
|
return self
|
||||||
@@ -140,8 +134,10 @@ end
|
|||||||
|
|
||||||
function BlueSam:SpawnGroups()
|
function BlueSam:SpawnGroups()
|
||||||
for unitName, needsCleanup in pairs(self._cleanupUnits) do
|
for unitName, needsCleanup in pairs(self._cleanupUnits) do
|
||||||
|
if needsCleanup then
|
||||||
DcsUtil.DestroyUnit(unitName)
|
DcsUtil.DestroyUnit(unitName)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
for _, group in pairs(self._blueGroups) do
|
for _, group in pairs(self._blueGroups) do
|
||||||
group:Spawn()
|
group:Spawn()
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ function FarpZone.New(database, logger, zoneName, spawnManager)
|
|||||||
local zone = DcsUtil.getZoneByName(zoneName)
|
local zone = DcsUtil.getZoneByName(zoneName)
|
||||||
if zone then
|
if zone then
|
||||||
self._logger:debug("Creating Buildable zone: " .. zoneName .. " with " .. (farpData.buildingKilos or "nil") .. " kilos")
|
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
|
||||||
end
|
end
|
||||||
self:Deactivate()
|
self:Deactivate()
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ function StageBase.New(databaseManager, logger, airbaseName, spawnManager)
|
|||||||
|
|
||||||
local cleanup_distance = 5
|
local cleanup_distance = 5
|
||||||
|
|
||||||
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
|
for _, blueUnitPos in pairs(blueUnitsPos) do
|
||||||
for redUnitName, redUnitPos in pairs(redUnitsPos) do
|
for redUnitName, redUnitPos in pairs(redUnitsPos) do
|
||||||
local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos)
|
local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos)
|
||||||
if distance <= cleanup_distance then
|
if distance <= cleanup_distance then
|
||||||
@@ -113,6 +113,7 @@ end
|
|||||||
function StageBase:SpawnRedUnits()
|
function StageBase:SpawnRedUnits()
|
||||||
---comment
|
---comment
|
||||||
---@param groups Array<SpearheadGroup>
|
---@param groups Array<SpearheadGroup>
|
||||||
|
---@return number|nil
|
||||||
local spawnAsync = function(groups)
|
local spawnAsync = function(groups)
|
||||||
for _, group in pairs(groups) do
|
for _, group in pairs(groups) do
|
||||||
group:Spawn()
|
group:Spawn()
|
||||||
@@ -142,6 +143,7 @@ end
|
|||||||
function StageBase:SpawnBlueUnits()
|
function StageBase:SpawnBlueUnits()
|
||||||
---comment
|
---comment
|
||||||
---@param groups Array<SpearheadGroup>
|
---@param groups Array<SpearheadGroup>
|
||||||
|
---@return number|nil
|
||||||
local spawnAsync = function(groups)
|
local spawnAsync = function(groups)
|
||||||
for _, group in pairs(groups) do
|
for _, group in pairs(groups) do
|
||||||
group:Spawn()
|
group:Spawn()
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ local Util = require("classes.util.Util")
|
|||||||
local DcsUtil = require("classes.util.DcsUtil")
|
local DcsUtil = require("classes.util.DcsUtil")
|
||||||
local SupplyUnitsTracker = require("classes.stageClasses.helpers.SupplyUnitsTracker")
|
local SupplyUnitsTracker = require("classes.stageClasses.helpers.SupplyUnitsTracker")
|
||||||
local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper")
|
local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper")
|
||||||
|
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||||
|
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
|
||||||
|
|
||||||
---@class SupplyHub
|
---@class SupplyHub
|
||||||
---@field private _database Database
|
---@field private _database Database
|
||||||
@@ -12,7 +14,7 @@ local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionComma
|
|||||||
---@field private _isCommmandAdded table<string, boolean>
|
---@field private _isCommmandAdded table<string, boolean>
|
||||||
---@field private _missionCommandsHelper MissionCommandsHelper
|
---@field private _missionCommandsHelper MissionCommandsHelper
|
||||||
---@field private _inZone table<string, boolean>
|
---@field private _inZone table<string, boolean>
|
||||||
---@field private _drawID number
|
---@field private _customDrawing CustomDrawing?
|
||||||
---@field private _cargoInUnits table<table<string, number>>
|
---@field private _cargoInUnits table<table<string, number>>
|
||||||
---@field private _activeAtStart boolean
|
---@field private _activeAtStart boolean
|
||||||
---@field private _active boolean
|
---@field private _active boolean
|
||||||
@@ -30,6 +32,7 @@ function SupplyHub.new(database, logger, zoneName)
|
|||||||
self._database = database
|
self._database = database
|
||||||
self._logger = logger
|
self._logger = logger
|
||||||
self._zoneName = zoneName
|
self._zoneName = zoneName
|
||||||
|
self._customDrawing = nil
|
||||||
|
|
||||||
local split = Util.split_string(zoneName, "_")
|
local split = Util.split_string(zoneName, "_")
|
||||||
if string.lower(split[2]) == "a" then
|
if string.lower(split[2]) == "a" then
|
||||||
@@ -40,9 +43,9 @@ function SupplyHub.new(database, logger, zoneName)
|
|||||||
|
|
||||||
self._zone = DcsUtil.getZoneByName(zoneName)
|
self._zone = DcsUtil.getZoneByName(zoneName)
|
||||||
|
|
||||||
self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate(logger.LogLevel)
|
self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate()
|
||||||
self._inZone = {}
|
self._inZone = {}
|
||||||
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate(logger.LogLevel)
|
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
|
||||||
|
|
||||||
self._logger:debug("Creating Supply Hub zone: " .. self._zoneName)
|
self._logger:debug("Creating Supply Hub zone: " .. self._zoneName)
|
||||||
|
|
||||||
@@ -72,13 +75,13 @@ function SupplyHub:Activate()
|
|||||||
self._logger:debug("Activating Supply Hub zone: " .. self._zoneName)
|
self._logger:debug("Activating Supply Hub zone: " .. self._zoneName)
|
||||||
|
|
||||||
local zone = DcsUtil.getZoneByName(self._zoneName)
|
local zone = DcsUtil.getZoneByName(self._zoneName)
|
||||||
if zone and self._drawID == nil then
|
if zone and self._customDrawing == nil then
|
||||||
---@type DrawColor
|
local fillColor = DrawingHelper.ColorTableToColorString({ 0, 1, 0, 0.2 })
|
||||||
local fillColor = { r=0, g=1, b=0, a=0.2 }
|
local lineColor = DrawingHelper.ColorTableToColorString({ 0, 1, 0, 1})
|
||||||
---@type DrawColor
|
|
||||||
local lineColor = { r=0, g=1, b=0, a=1}
|
|
||||||
local lineStyle = 1
|
local lineStyle = 1
|
||||||
self._drawID = DcsUtil.DrawZone(zone, lineColor, fillColor, lineStyle)
|
|
||||||
|
self._customDrawing = CustomDrawing.FromZone(zone, lineColor, fillColor, lineStyle, 6)
|
||||||
|
self._customDrawing:Draw()
|
||||||
end
|
end
|
||||||
|
|
||||||
self._supplyUnitsTracker:RegisterHub(self)
|
self._supplyUnitsTracker:RegisterHub(self)
|
||||||
|
|||||||