Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c31cc6a552 | ||
|
|
77f4c41a6b | ||
|
|
07a234489f | ||
|
|
e363b3fe94 | ||
|
|
b68f95b748 | ||
|
|
8341a5fb6d | ||
|
|
e32518f48d | ||
|
|
2e7a930473 | ||
|
|
cbccedf710 | ||
|
|
e986887dd8 | ||
|
|
9549e8c48e | ||
|
|
9b2540d461 | ||
|
|
a1355ff05d | ||
|
|
2e636b2ded | ||
|
|
488a492e39 | ||
|
|
772397250f | ||
|
|
0a7e8ea977 | ||
|
|
96507f1d34 | ||
|
|
96ada242a4 |
Binary file not shown.
|
After Width: | Height: | Size: 223 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 345 KiB |
@@ -21,6 +21,9 @@
|
||||
<main>
|
||||
<div class="reference-container">
|
||||
<div class="side-nav">
|
||||
|
||||
<div id="beta-box-div"></div>
|
||||
|
||||
<h4>Quick Links</h4>
|
||||
<ul>
|
||||
<li><a href="/pages/tutorials.html" class="side-nav-h2">Tutorials</a></li>
|
||||
@@ -59,6 +62,13 @@
|
||||
<footer>
|
||||
<p>© 2025 Spearhead Project</p>
|
||||
</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>
|
||||
|
||||
</html>
|
||||
@@ -19,9 +19,10 @@ class Header extends HTMLElement {
|
||||
<div class="dropdown-content">
|
||||
<a href="/pages/include-script.html">Include the Script</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/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>
|
||||
|
||||
|
||||
@@ -50,17 +50,6 @@ 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() {
|
||||
|
||||
var version = "?";
|
||||
@@ -72,10 +61,9 @@ class DownloadScript extends HTMLElement {
|
||||
if (latestRelease) {
|
||||
version = latestRelease.tag_name;
|
||||
timestamp = new Date(latestRelease.published_at).toLocaleDateString('en-CA');
|
||||
|
||||
const assets = await this.fetchReleaseAssets(latestRelease.assets_url);
|
||||
const assets = latestRelease.assets;
|
||||
if (assets && assets.length > 0) {
|
||||
const spearheadAsset = assets.find(asset => asset.name.includes('spearhead'));
|
||||
const spearheadAsset = assets.find(asset => asset.name.includes('spearhead.v'));
|
||||
if (spearheadAsset) {
|
||||
size = (spearheadAsset.size / 1024).toFixed(2) + 'kb';
|
||||
href = spearheadAsset.browser_download_url;
|
||||
@@ -105,7 +93,7 @@ class DownloadScript extends HTMLElement {
|
||||
<p>
|
||||
<span class="version-text">${version}</span> (${timestamp}) <br>
|
||||
<a style="text-decoration: underline;" target="_blank" href="${href}">Download</a> (${size}) <br>
|
||||
<a style="text-decoration: underline;" target="_blank" href="https://git.dutchie031.com/api/v1/repos/Spearhead/spearhead/releases">See All Versions here</a>
|
||||
<a style="text-decoration: underline;" target="_blank" href="https://git.dutchie031.com/Spearhead/spearhead/releases">See All Versions here</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -168,8 +168,17 @@ class Sidebar extends HTMLElement {
|
||||
}
|
||||
|
||||
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 = `
|
||||
<div class="side-nav">
|
||||
<div style="width:100%">
|
||||
${betaHtml}
|
||||
</div>
|
||||
|
||||
<h4 class="side-nav-title"></h4>
|
||||
<ul>
|
||||
<!-- Navigation items will be populated automatically -->
|
||||
|
||||
@@ -80,17 +80,12 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<note-box type="info" title="Combining Tasks">
|
||||
Currently a group can do 1 thing. Either be Primary, Seconday, Sweep or Intercept. <br>
|
||||
If you want to have different types during different stages, it's best to copy paste groups and
|
||||
rename them accordingly. <br>
|
||||
</note-box>
|
||||
|
||||
<note-box type="info" title="Airfield Defenses">
|
||||
|
||||
</note-box>
|
||||
|
||||
<h3 id="cap-zones-ref">Zones</h3>
|
||||
<table class="table-no-style">
|
||||
<tbody>
|
||||
@@ -99,6 +94,7 @@
|
||||
<td>=></td>
|
||||
<td>A Cap ROUTE. The two most outer points are used to define the fly-over points.</td>
|
||||
</tr>
|
||||
<tr><td>______________________</td><td></td><td></td> </tr>
|
||||
<tr>
|
||||
<td><code-inline>INTERCEPTZONE_[ZoneID]_[NAME]</code-inline></td>
|
||||
<td>=></td>
|
||||
@@ -112,6 +108,19 @@
|
||||
</p>
|
||||
<h2 id="setup">Setting up groups</h2>
|
||||
|
||||
<h3 id="airbases-grouping">Airbases</h3>
|
||||
<p>
|
||||
Before talking about the groups, it's good to mention that groups are currently logically grouped and managed by an Airbase. <br>
|
||||
Meaning, Airbase A does not know how many flights Airbase B is sending out. <br>
|
||||
This might change in the future where multiple Airbases can work together.
|
||||
</p>
|
||||
|
||||
<note-box type="info" title="Airbase Defenses">
|
||||
When an Airbase is "active" and thus has aircraft being scheduled for taskings, all RED groups at that airfield will also be spawned. <br>
|
||||
Additionally, all missions of type <code-inline>SAM</code-inline> will be spawned (without being available in Primary Missions). <br>
|
||||
This should hopefully give you all the tools to make distance CAP airbases still feel very well defended.
|
||||
</note-box>
|
||||
|
||||
<h3 id="types-explained">Types explained</h3>
|
||||
<p>
|
||||
Before we go into depth on each type it's best to first look at the types briefly and what we
|
||||
@@ -140,7 +149,7 @@
|
||||
The ground attack units mentioned are not in yet and thus escort groups are not either. <br>
|
||||
|
||||
<note-box type="warning" title="Overriding Taskings">
|
||||
Each tasking has very specific waypoints, both for departure, arrical and ofcourse all point in between. <br>
|
||||
Each tasking has very specific waypoints, both for departure, arrival and ofcourse all point in between. <br>
|
||||
Overriding the "tasking" will break part of the cap flow as "RTBInTen" and RTB events are triggered by waypoints. <br>
|
||||
Overriding these waypoints and taskings will break the management by Spearhead.
|
||||
</note-box>
|
||||
@@ -154,7 +163,7 @@
|
||||
The way the trigger zone is mapped to a route is as follows: <br>
|
||||
<br>
|
||||
The 2 points that are furthest apart from each other are chosen as the "leg" of the route. <br>
|
||||
The other 2 points are discarded. <br>
|
||||
The other 2 points are discarded at this time. <br>
|
||||
<br>
|
||||
The Orbit route is then created with an "Anchored" task. With the closest point being the starting
|
||||
point with an outbound leg to the furthest point. <br>
|
||||
|
||||
@@ -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>
|
||||
@@ -26,39 +26,115 @@
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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 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;"/>
|
||||
<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>
|
||||
|
||||
|
||||
@@ -253,6 +253,13 @@
|
||||
To make an airbase buildable add a text box inside of the trigger zone and name it: <code-inline>buildable_[freeform]</code-inline> <br/>
|
||||
Then in the text box type amount of kilo's you want to be transfered before the logistisc mission is complete. <br/>
|
||||
|
||||
<br>
|
||||
To add a custom briefing for a buildable zone, include a text box inside the trigger zone and name it: <code-inline>supplybriefing_[freeform]</code-inline>.
|
||||
<code-inline>{{coords}}</code-inline> is highly recommended so the players can easily find their landing zone.<br>
|
||||
Additionally, the amount of kilos required and delivered are always shown on the bottom, as well as a note to not land in the construction zone.
|
||||
<br/>
|
||||
|
||||
|
||||
<br>
|
||||
The base or zone will slowly build up with each crate giving both a nice view of it happening AND it's good for performance as there's not a big addition of objects at once. <br/>
|
||||
Right now a crate takes 15 seconds to unpack per 500kg. Meaning 2 crates of 1000kg will be faster than 1 crate of 2000kg. <br/>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
--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-text-primary: #a5a5a5; /* Brighter text */
|
||||
--color-text-primary: #b4b3b3; /* Brighter text */
|
||||
--color-text-secondary: #bfc8d8; /* Lighter secondary text */
|
||||
--color-text-accent: #7ecbff; /* Brighter accent */
|
||||
--color-text-light: #ffffff;
|
||||
@@ -262,7 +262,7 @@ header nav a:hover::after {
|
||||
overflow: hidden;
|
||||
top: 100%; /* Position below the dropdown */
|
||||
left: 0; /* Align left edge */
|
||||
min-width: 160px;
|
||||
min-width: 240px;
|
||||
|
||||
background-color: var(--color-bg-secondary);
|
||||
box-shadow: var(--shadow-medium);
|
||||
@@ -336,8 +336,8 @@ main {
|
||||
}
|
||||
|
||||
.side-nav {
|
||||
width: 200px;
|
||||
min-width: 200px;
|
||||
max-width: 200px;
|
||||
padding-left: 0;
|
||||
overflow-y: auto;
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Release
|
||||
name: Publish Beta Release
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- 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:
|
||||
source-root: ./src
|
||||
output-file: ./output/spearhead.lua
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- 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:
|
||||
source-root: ./src
|
||||
output-file: ./output/spearhead.lua
|
||||
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
- name: Run Helm upgrade/install spearhead-docs (develop)
|
||||
if: github.ref_name == 'develop'
|
||||
run: |
|
||||
helm upgrade --install spearhead-docs .helm \
|
||||
helm upgrade --install spearhead-docs-beta .helm \
|
||||
--namespace spearhead-docs \
|
||||
--create-namespace \
|
||||
-f .helm/values.yaml \
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
|
||||
/dist
|
||||
|
||||
.vscode/lua-addons
|
||||
.vscode/settings.json
|
||||
**\settings.json
|
||||
+51
@@ -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
|
||||
}
|
||||
}
|
||||
Vendored
+2
-1
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"sumneko.lua"
|
||||
"dutchie031.dutchies-dcs-scripting-tools",
|
||||
"sumneko.lua",
|
||||
]
|
||||
}
|
||||
@@ -1,53 +1,43 @@
|
||||
# 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/>
|
||||
Especially if you've found an issue, annoyance or feature that you know how to solve, fix or create! <br/>
|
||||
If you're a mission maker that wants to see the documentation and build you mission with this framework. <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/>
|
||||
It'll help the PR review and the speed of improvements. <br/>
|
||||
If you have a big feature in mind? Go for it! <br/>
|
||||
Does it require an entire refactor of parts or all of the program? Maybe make sure to do step 0 first.
|
||||
The Spearhead project is structured in a lot of separate files. <br>
|
||||
While Lua isn't specifically created for it, most of it has been written with the standards of OOP in mind. <br>
|
||||
Classes, Objects and helper methods.
|
||||
|
||||
### Steps (for first timers)
|
||||
0. Reach out (Optional)
|
||||
You're find in the `/src` folder all the files. <br>
|
||||
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/>
|
||||
Maybe I'm already working on the thing you wanted to build, or someone else is. <br/>
|
||||
Creating an issue is even better! <br/>
|
||||
### Bundling, Intellisense, Linting
|
||||
|
||||
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/>
|
||||
Whether you keep that fork public or private is up to you! <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.
|
||||
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/>
|
||||
After all, instead of having 5 slightly different versions and products, it might be nicer to have 1 much better version. <br/>
|
||||
## Contributing
|
||||
|
||||
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/>
|
||||
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.
|
||||
If you've kept on reading I assume you either want to brwose
|
||||
+17
-1
@@ -5,11 +5,27 @@
|
||||
|
||||
### 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
|
||||
|
||||
## [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. <br>
|
||||
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
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ SpearheadConfig = {
|
||||
---DEBUG LOGGING
|
||||
debugEnabled = false, -- default false
|
||||
|
||||
--- DEBUG MENU
|
||||
debugMenuEnabled = false, -- default false
|
||||
|
||||
--- 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
|
||||
briefingMessageDuration = 60, --default 60
|
||||
|
||||
@@ -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"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
---@field notifyMissionComplete fun(zone_name: string)
|
||||
|
||||
|
||||
---@type FullSpearheadAPI
|
||||
SpearheadAPI = {
|
||||
Stages = {
|
||||
@@ -25,12 +24,12 @@ SpearheadAPI = {
|
||||
getCurrentStage = function()
|
||||
return GlobalStageManager.getCurrentStage() or nil
|
||||
end,
|
||||
isStageComplete = function(stageNumber)
|
||||
isStageComplete = function(stageNumber, stageLaneIdentifier)
|
||||
if type(stageNumber) ~= "number" then
|
||||
return false, "stageNumber " .. stageNumber .. " is not a valid number"
|
||||
end
|
||||
|
||||
local isComplete = GlobalStageManager.isStageComplete(stageNumber)
|
||||
local isComplete = GlobalStageManager.isStageComplete(stageNumber, stageLaneIdentifier)
|
||||
if isComplete == nil then
|
||||
return nil, "no stage found with number " .. stageNumber
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ SpearheadAPI = SpearheadAPI
|
||||
---@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 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
|
||||
---@field onMissionComplete fun(self: OnMissionCompleteListener, zone_name: string) @Called when a mission is completed. @return void
|
||||
@@ -14,4 +14,3 @@ SpearheadAPI = SpearheadAPI
|
||||
---@class MissionAPI
|
||||
---@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 SweepGroup = require("classes.capClasses.airGroups.SweepGroup")
|
||||
local InterceptGroup = require("classes.capClasses.airGroups.InterceptGroup")
|
||||
local RunwayBombingTracker = require("classes.capClasses.runwayBombing.RunwayBombingTracker")
|
||||
local SpearheadEvents = require("classes.spearhead_events")
|
||||
local RunwayStrikeMission = require("classes.stageClasses.missions.RunwayStrikeMission")
|
||||
|
||||
@@ -37,12 +36,11 @@ end
|
||||
---@param database Database
|
||||
---@param logger table
|
||||
---@param capConfig table
|
||||
---@param stageConfig table
|
||||
---@param runwayBombingTracker RunwayBombingTracker
|
||||
---@param detectionManager DetectionManager
|
||||
---@param spawnManager SpawnManager
|
||||
---@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
|
||||
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)
|
||||
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)
|
||||
if capGroup then
|
||||
self.capGroupsByName[name] = capGroup
|
||||
@@ -70,7 +68,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
|
||||
end
|
||||
|
||||
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)
|
||||
if sweepGroup then
|
||||
self.sweepGroupsByName[name] = sweepGroup
|
||||
@@ -79,7 +77,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
|
||||
end
|
||||
|
||||
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)
|
||||
if interceptGroup then
|
||||
self.interceptGroupsByName[name] = interceptGroup
|
||||
@@ -98,7 +96,6 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
|
||||
|
||||
timer.scheduleFunction(CheckStateContinuous, self, timer.getTime() + 15)
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -127,7 +124,7 @@ end
|
||||
|
||||
function CapBase:SpawnIfApplicable()
|
||||
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))
|
||||
|
||||
if targetStage ~= nil and capGroup:GetState() == "UnSpawned" then
|
||||
@@ -135,7 +132,7 @@ function CapBase:SpawnIfApplicable()
|
||||
end
|
||||
end
|
||||
|
||||
for groupName, sweepGroup in pairs(self.sweepGroupsByName) do
|
||||
for _, sweepGroup in pairs(self.sweepGroupsByName) do
|
||||
local targetStage = sweepGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||
|
||||
if targetStage ~= nil and sweepGroup:GetState() == "UnSpawned" then
|
||||
@@ -143,7 +140,7 @@ function CapBase:SpawnIfApplicable()
|
||||
end
|
||||
end
|
||||
|
||||
for groupName, interceptGroup in pairs(self.interceptGroupsByName) do
|
||||
for _, interceptGroup in pairs(self.interceptGroupsByName) do
|
||||
local targetStage = interceptGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||
|
||||
if targetStage ~= nil and interceptGroup:GetState() == "UnSpawned" then
|
||||
@@ -154,8 +151,9 @@ end
|
||||
|
||||
function CapBase:CheckAndScheduleCAP()
|
||||
self.logger:debug("Check taskings for airbase " .. self.airbaseName)
|
||||
|
||||
---@type table<string, number>
|
||||
local countPerStage = {}
|
||||
---@type table<string, number>
|
||||
local requiredPerStage = {}
|
||||
|
||||
local airbase = Airbase.getByName(self.airbaseName)
|
||||
@@ -312,8 +310,7 @@ function CapBase:CheckAndScheduleIntercept()
|
||||
|
||||
self.logger:debug("Check intercept taskings for airbase " .. self.airbaseName)
|
||||
|
||||
local interceptZoneIDs = {}
|
||||
|
||||
---@type table<string, boolean>
|
||||
local interceptZoneIDs = {}
|
||||
|
||||
local airbase = Airbase.getByName(self.airbaseName)
|
||||
@@ -321,7 +318,7 @@ function CapBase:CheckAndScheduleIntercept()
|
||||
return nil
|
||||
end
|
||||
|
||||
for name, group in pairs(self.interceptGroupsByName) do
|
||||
for _, group in pairs(self.interceptGroupsByName) do
|
||||
local targetZoneID = group:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||
if targetZoneID then
|
||||
interceptZoneIDs[targetZoneID] = true
|
||||
@@ -379,7 +376,6 @@ function CapBase:CheckAndScheduleIntercept()
|
||||
if total < required then
|
||||
for _, group in pairs(self.interceptGroupsByName) do
|
||||
if total < required then
|
||||
local zoneID = group:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||
if group:GetState() == "ReadyOnTheRamp" then
|
||||
group:SendToInterceptUnits(targets, name, airbase)
|
||||
total = total + 1
|
||||
@@ -392,9 +388,12 @@ function CapBase:CheckAndScheduleIntercept()
|
||||
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
|
||||
|
||||
if self:IsBaseActiveWhenStageIsActive(number) == true then
|
||||
|
||||
@@ -6,21 +6,19 @@ local CapAirbase = require("classes.capClasses.CapAirbase")
|
||||
---@class GlobalCapManager
|
||||
local GlobalCapManager = {}
|
||||
do
|
||||
---@type table<string, CapBase[]>
|
||||
local airbasesPerStage = {}
|
||||
---@type table<string, CapBase>
|
||||
local allAirbasesByName = {}
|
||||
local activeAirbasesPerActiveStage = {}
|
||||
local unitsPerzonePerStage = {}
|
||||
|
||||
local initiated = false
|
||||
|
||||
---comment
|
||||
---@param database Database
|
||||
---@param capConfig table
|
||||
---@param stageConfig StageConfig
|
||||
---@param detectionManager DetectionManager
|
||||
---@param logLevel LogLevel
|
||||
---@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
|
||||
|
||||
local logger = Logger.new("AirbaseManager", logLevel)
|
||||
@@ -29,7 +27,7 @@ do
|
||||
|
||||
local zones = database:getStagezoneNames()
|
||||
if zones then
|
||||
for key, stageName in pairs(zones) do
|
||||
for _, stageName in pairs(zones) do
|
||||
if airbasesPerStage[stageName] == nil then
|
||||
airbasesPerStage[stageName] = {}
|
||||
end
|
||||
@@ -40,8 +38,7 @@ do
|
||||
if airbaseName then
|
||||
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
|
||||
table.insert(airbasesPerStage[stageName], airbase)
|
||||
allAirbasesByName[airbaseName] = airbase
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
local SpearheadEvents = require("classes.spearhead_events")
|
||||
local RTBMission = require("classes.capClasses.taskings.RTB")
|
||||
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 _groupName string
|
||||
---@field protected _groupType AirGroupType
|
||||
@@ -12,9 +14,12 @@ local DcsUtil = require("classes.util.DcsUtil")
|
||||
---@field protected _config CapConfig
|
||||
---@field protected _checkLivenessNumber number
|
||||
---@field protected _spawnManager SpawnManager
|
||||
---@field protected _routeDrawing CustomDrawing?
|
||||
local AirGroup = {}
|
||||
AirGroup.__index = AirGroup
|
||||
|
||||
local globalConfig = GlobalConfig.New()
|
||||
|
||||
---@param logger Logger
|
||||
---@param groupName string
|
||||
---@param groupType AirGroupType
|
||||
@@ -28,6 +33,7 @@ function AirGroup:New(groupName, groupType, config, logger, spawnManager)
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._spawnManager = spawnManager
|
||||
self._routeDrawings = {}
|
||||
|
||||
local group = Group.getByName(self._groupName)
|
||||
if group then
|
||||
@@ -74,7 +80,7 @@ function AirGroup:SetMission(mission)
|
||||
end
|
||||
end
|
||||
|
||||
local setMissionDelayed = function(data, time)
|
||||
local setMissionDelayed = function(data, _)
|
||||
data.self:SetMissionPrivate(data.mission)
|
||||
end
|
||||
|
||||
@@ -92,6 +98,28 @@ function AirGroup:SetMissionPrivate(mission)
|
||||
if group and mission then
|
||||
group:getController():setTask(mission)
|
||||
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
|
||||
|
||||
@@ -171,6 +199,8 @@ function AirGroup:SpawnInternal(force, withoutLoadout)
|
||||
end
|
||||
|
||||
---@param selfA AirGroup
|
||||
---@param time number
|
||||
---@return number?
|
||||
local function CheckLivenessTask(selfA, time)
|
||||
local interval = selfA:CheckLiveness()
|
||||
if not interval then return end
|
||||
@@ -236,6 +266,7 @@ function AirGroup:OnLastUnitLanded()
|
||||
|
||||
---@param data CheckGroupForRestartData
|
||||
---@param time number
|
||||
---@return number?
|
||||
local checkGroupForRestart = function(data, time)
|
||||
local group = Group.getByName(data.self:GetName())
|
||||
if not group then
|
||||
@@ -329,7 +360,7 @@ do --- RESPAWN FUNCTIONS
|
||||
self:SetState("Dead")
|
||||
|
||||
---@param selfA AirGroup
|
||||
local respawnTask = function(selfA, time)
|
||||
local respawnTask = function(selfA, _)
|
||||
selfA:StartRepair()
|
||||
end
|
||||
|
||||
@@ -348,8 +379,7 @@ do --- RESPAWN FUNCTIONS
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param selfA AirGroup
|
||||
local rearmTask = function(selfA, time)
|
||||
local rearmTask = function(_, _)
|
||||
self:StartRearm()
|
||||
end
|
||||
|
||||
@@ -374,7 +404,7 @@ do --- RESPAWN FUNCTIONS
|
||||
end
|
||||
|
||||
---@param selfA AirGroup
|
||||
local rearmTask = function(selfA, time)
|
||||
local rearmTask = function(selfA, _)
|
||||
selfA:MarkRearmComplete()
|
||||
end
|
||||
|
||||
@@ -387,6 +417,7 @@ end
|
||||
do --EVENT LISTENERS
|
||||
---@param unit Unit
|
||||
function AirGroup:OnUnitLost(unit)
|
||||
if unit == nil then return end
|
||||
self:CheckLiveness()
|
||||
end
|
||||
|
||||
@@ -406,7 +437,7 @@ do --EVENT LISTENERS
|
||||
end
|
||||
end
|
||||
|
||||
function AirGroup:OnUnitLanded(unit, airbase)
|
||||
function AirGroup:OnUnitLanded(_, _)
|
||||
local anyInAir = false
|
||||
local group = Group.getByName(self._groupName)
|
||||
if group then
|
||||
|
||||
@@ -34,9 +34,10 @@ function CapGroup:IsBackup()
|
||||
return self._isBackup
|
||||
end
|
||||
|
||||
---@param stageID string
|
||||
---@return string?
|
||||
function CapGroup:GetZoneIDWhenStageID(stageID)
|
||||
return self._targetZoneIdPerStage[stageID]
|
||||
return self._targetZoneIdPerStage[tostring(stageID)]
|
||||
end
|
||||
|
||||
---@return string?
|
||||
@@ -107,13 +108,13 @@ function CapGroup:InitWithName(groupName)
|
||||
|
||||
local subsplit = Util.split_string(configPart, "|")
|
||||
if subsplit then
|
||||
for key, value in pairs(subsplit) do
|
||||
for _, value in pairs(subsplit) do
|
||||
local keySplit = Util.split_string(value, "]")
|
||||
local targetZone = keySplit[2]
|
||||
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
|
||||
local commaSeperated = Util.split_string(allActives, ",")
|
||||
for _, value in pairs(commaSeperated) do
|
||||
local dashSeperated = Util.split_string(value, "-")
|
||||
for _, childValue in pairs(commaSeperated) do
|
||||
local dashSeperated = Util.split_string(childValue, "-")
|
||||
if Util.tableLength(dashSeperated) > 1 then
|
||||
local from = tonumber(dashSeperated[1])
|
||||
local till = tonumber(dashSeperated[2])
|
||||
|
||||
@@ -60,6 +60,7 @@ end
|
||||
|
||||
---comment
|
||||
---@param units Array<string>
|
||||
---@param zoneName string
|
||||
---@param homeAirbase Airbase
|
||||
function InterceptGroup:SendToInterceptUnits(units, zoneName, homeAirbase)
|
||||
|
||||
@@ -68,9 +69,10 @@ function InterceptGroup:SendToInterceptUnits(units, zoneName, homeAirbase)
|
||||
self:SetTargetUnits(units)
|
||||
end
|
||||
|
||||
---@param stageID string
|
||||
---@return string?
|
||||
function InterceptGroup:GetZoneIDWhenStageID(stageID)
|
||||
return self._targetZoneIdPerStage[stageID]
|
||||
return self._targetZoneIdPerStage[tostring(stageID)]
|
||||
end
|
||||
|
||||
---@return string?
|
||||
@@ -91,6 +93,9 @@ function InterceptGroup:SetTargetUnits(unitNames)
|
||||
end)
|
||||
end
|
||||
|
||||
---@param selfA InterceptGroup
|
||||
---@param time number
|
||||
---@return number?
|
||||
local updateContinous = function(selfA, time)
|
||||
local next = selfA:UpdateTask()
|
||||
if next then
|
||||
@@ -102,6 +107,7 @@ function InterceptGroup:SetTargetUnits(unitNames)
|
||||
self._updateTaskID = timer.scheduleFunction(updateContinous, self, timer.getTime() + 30)
|
||||
end
|
||||
|
||||
---@param unit Unit
|
||||
function InterceptGroup:RemoveTargetUnit(unit)
|
||||
|
||||
if not unit then return end
|
||||
@@ -213,8 +219,6 @@ function InterceptGroup:UpdateTask()
|
||||
self._currentTargetName = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
local speed = self._config:getMaxSpeed()
|
||||
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
|
||||
end
|
||||
|
||||
local mission = nil
|
||||
---@type table
|
||||
local mission
|
||||
if self:IsInAir() == true then
|
||||
-- If the group is in the air, create an intercept mission
|
||||
mission = INTERCEPT.getMissionFromInAir(
|
||||
@@ -323,13 +328,13 @@ function InterceptGroup:InitWithName(groupName)
|
||||
configPart = string.sub(configPart, 2, #configPart)
|
||||
local subsplit = Util.split_string(configPart, "|")
|
||||
if subsplit then
|
||||
for key, value in pairs(subsplit) do
|
||||
for _, value in pairs(subsplit) do
|
||||
local keySplit = Util.split_string(value, "]")
|
||||
local targetZone = keySplit[2]
|
||||
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
|
||||
local commaSeperated = Util.split_string(allActives, ",")
|
||||
for _, value in pairs(commaSeperated) do
|
||||
local dashSeperated = Util.split_string(value, "-")
|
||||
for _, childValue in pairs(commaSeperated) do
|
||||
local dashSeperated = Util.split_string(childValue, "-")
|
||||
if Util.tableLength(dashSeperated) > 1 then
|
||||
local from = tonumber(dashSeperated[1])
|
||||
local till = tonumber(dashSeperated[2])
|
||||
|
||||
@@ -4,7 +4,7 @@ local Util = require("classes.util.Util")
|
||||
local MissionEditorWarner = require("classes.util.MissionEditorWarnings")
|
||||
|
||||
---@class SweepGroup : AirGroup
|
||||
---@field _targetZoneIdPerStage table<number, string>
|
||||
---@field _targetZoneIdPerStage table<string, string>
|
||||
---@field _currentTargetZoneID string?
|
||||
local SweepGroup = {}
|
||||
SweepGroup.__index = SweepGroup
|
||||
@@ -26,9 +26,10 @@ function SweepGroup.New(groupName, config, logger, spawnManager)
|
||||
return self
|
||||
end
|
||||
|
||||
---@param stageID string
|
||||
---@return string?
|
||||
function SweepGroup:GetZoneIDWhenStageID(stageID)
|
||||
return self._targetZoneIdPerStage[stageID]
|
||||
return self._targetZoneIdPerStage[tostring(stageID)]
|
||||
end
|
||||
|
||||
---@return string?
|
||||
@@ -41,7 +42,7 @@ end
|
||||
---@field self SweepGroup
|
||||
|
||||
---@param params SetTaskParams
|
||||
local setMissionDelayedTask = function(params, time)
|
||||
local setMissionDelayedTask = function(params, _)
|
||||
params.self:SetMissionPrivate(params.task)
|
||||
end
|
||||
|
||||
@@ -50,8 +51,6 @@ function SweepGroup:SendToZone(zone, targetZoneID, airbase)
|
||||
|
||||
self._currentTargetZoneID = targetZoneID
|
||||
|
||||
local group = Group.getByName(self._groupName)
|
||||
|
||||
local mission = SWEEP.getAsMissionFromAirbase(self._groupName, airbase, zone, self._config)
|
||||
if mission then
|
||||
---@type SetTaskParams
|
||||
@@ -82,13 +81,13 @@ function SweepGroup:InitWithName(groupName)
|
||||
|
||||
local subsplit = Util.split_string(configPart, "|")
|
||||
if subsplit then
|
||||
for key, value in pairs(subsplit) do
|
||||
for _, value in pairs(subsplit) do
|
||||
local keySplit = Util.split_string(value, "]")
|
||||
local targetZone = keySplit[2]
|
||||
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
|
||||
local commaSeperated = Util.split_string(allActives, ",")
|
||||
for _, value in pairs(commaSeperated) do
|
||||
local dashSeperated = Util.split_string(value, "-")
|
||||
for _, childValue in pairs(commaSeperated) do
|
||||
local dashSeperated = Util.split_string(childValue, "-")
|
||||
if Util.tableLength(dashSeperated) > 1 then
|
||||
local from = tonumber(dashSeperated[1])
|
||||
local till = tonumber(dashSeperated[2])
|
||||
|
||||
@@ -8,6 +8,7 @@ DetectionManager.__index = DetectionManager
|
||||
|
||||
|
||||
---@param logger Logger
|
||||
---@return DetectionManager
|
||||
function DetectionManager.New(logger)
|
||||
local self = setmetatable({}, DetectionManager)
|
||||
|
||||
@@ -24,6 +25,7 @@ function DetectionManager.New(logger)
|
||||
|
||||
---@param selfA DetectionManager
|
||||
---@param time number
|
||||
---@return number?
|
||||
local updateDetectingUnitsTask = function(selfA, time)
|
||||
selfA:UpdateDetectingUnits()
|
||||
return time + 120
|
||||
@@ -32,18 +34,19 @@ function DetectionManager.New(logger)
|
||||
|
||||
---@param selfA DetectionManager
|
||||
---@param time number
|
||||
---@return number?
|
||||
local updateDetected = function(selfA, time)
|
||||
selfA:UpdateDetectedUnits()
|
||||
return time + 10
|
||||
end
|
||||
timer.scheduleFunction(updateDetected, self, timer.getTime() + 130)
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@param unitName string
|
||||
---@param coalitionSide CoalitionSide
|
||||
---@return boolean
|
||||
function DetectionManager:IsUnitDetectedBy(unitName, coalitionSide)
|
||||
local coalitionString = tostring(coalitionSide)
|
||||
if not self._detectedUnits[coalitionString] then
|
||||
@@ -57,7 +60,8 @@ function DetectionManager:IsUnitDetectedBy(unitName, coalitionSide)
|
||||
return timer.getTime() - self._detectedUnits[coalitionString][unitName] < 20
|
||||
end
|
||||
|
||||
---@return Array<string>
|
||||
---@param coalitionSide CoalitionSide
|
||||
---@return Array<string> detectedUnits
|
||||
function DetectionManager:GetDetectedUnitsBy(coalitionSide)
|
||||
local coalitionString = tostring(coalitionSide)
|
||||
if not self._detectedUnits[coalitionString] then
|
||||
|
||||
@@ -21,7 +21,7 @@ end
|
||||
|
||||
---comment
|
||||
---@param weapon Weapon
|
||||
function RunwayBombingTracker:OnWeaponFired(unit, weapon, target)
|
||||
function RunwayBombingTracker:OnWeaponFired(_, weapon, _)
|
||||
|
||||
if weapon == nil then
|
||||
return
|
||||
@@ -56,7 +56,9 @@ end
|
||||
---@field self RunwayBombingTracker
|
||||
|
||||
---@private
|
||||
---@param time number
|
||||
---@param weaponTrackingArgs WeaponTrackingArgs
|
||||
---@return number?
|
||||
function RunwayBombingTracker.trackWeaponTask(weaponTrackingArgs, time)
|
||||
|
||||
local weapon = weaponTrackingArgs.weapon
|
||||
@@ -83,11 +85,7 @@ function RunwayBombingTracker.trackWeaponTask(weaponTrackingArgs, time)
|
||||
x = pos.x + velocity.x * nextInterval,
|
||||
y = pos.z + velocity.z * nextInterval
|
||||
}
|
||||
|
||||
|
||||
self:OnWeaponImpact(weapon:getDesc(), impactPoint)
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -106,11 +104,10 @@ function RunwayBombingTracker:OnWeaponImpact(weaponDesc, impactPoint)
|
||||
|
||||
self._logger:debug("RunwayBombingTracker:OnWeaponImpact")
|
||||
|
||||
local warhead = weaponDesc.warhead
|
||||
local explosiveMass = warhead.explosiveMass or warhead.shapedExplosiveMass
|
||||
|
||||
for runway, strikeMission in pairs(self.trackedRunways) do
|
||||
local warhead = weaponDesc.warhead --[[@as table]]
|
||||
local explosiveMass = (warhead.explosiveMass or warhead.shapedExplosiveMass) --[[@as number]]
|
||||
|
||||
for _, strikeMission in pairs(self.trackedRunways) do
|
||||
local zone= strikeMission:GetRunwayZone()
|
||||
|
||||
if Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then
|
||||
|
||||
@@ -7,6 +7,7 @@ local CAP = {}
|
||||
---@param attackHelos boolean
|
||||
---@return table
|
||||
local function GetCAPTargetTypes(attackHelos)
|
||||
---@type Array<string>
|
||||
local targetTypes = {
|
||||
[1] = "Planes",
|
||||
}
|
||||
@@ -33,9 +34,6 @@ local function GetCAPPointFromTriggerZone(airBase, capZone)
|
||||
local furthestA = nil
|
||||
local furthestB = nil
|
||||
|
||||
local furthestFromBase = nil
|
||||
local furthestFromBaseDistance = 0
|
||||
|
||||
local furthestDistance = 0
|
||||
|
||||
for indexA, pointA in ipairs(capZone.verts) do
|
||||
@@ -56,7 +54,10 @@ local function GetCAPPointFromTriggerZone(airBase, capZone)
|
||||
---@type Vec2
|
||||
local baseVec2 = { x = baseVec3.x, y = baseVec3.z }
|
||||
|
||||
---@type Vec2
|
||||
local pointA = capZone.verts[furthestA]
|
||||
|
||||
---@type Vec2
|
||||
local pointB = capZone.verts[furthestB]
|
||||
local furthest = pointA
|
||||
local closest = pointB
|
||||
@@ -87,6 +88,7 @@ end
|
||||
---@param airbase Airbase
|
||||
---@param capZone SpearheadTriggerZone
|
||||
---@param capConfig CapConfig
|
||||
---@return table
|
||||
local GetOutboundTask = function(airbase, capZone, capConfig)
|
||||
local airbaseVec3 = airbase:getPoint()
|
||||
local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z }
|
||||
@@ -128,6 +130,7 @@ end
|
||||
---@param airbase Airbase
|
||||
---@param capZone SpearheadTriggerZone
|
||||
---@param capConfig CapConfig
|
||||
---@return table
|
||||
function CAP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig)
|
||||
local points = {
|
||||
[1] = GetOutboundTask(airbase, capZone, capConfig),
|
||||
@@ -155,6 +158,7 @@ end
|
||||
---@param airbase Airbase
|
||||
---@param capZone SpearheadTriggerZone
|
||||
---@param capConfig CapConfig
|
||||
---@return table
|
||||
function CAP.getAsMission(groupName, airbase, capZone, capConfig)
|
||||
local points = {
|
||||
[1] = CAP.getAsTasking(groupName, airbase, capZone, capConfig),
|
||||
@@ -181,6 +185,7 @@ end
|
||||
---@param airbase Airbase
|
||||
---@param capZone SpearheadTriggerZone
|
||||
---@param capConfig CapConfig
|
||||
---@return table
|
||||
function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
|
||||
local duration = math.random(capConfig:getMinDurationOnStation(), capConfig:getMaxDurationOnStation()) or 1500
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ local INTERCEPT = {}
|
||||
---@param speed number?
|
||||
---@param alt number?
|
||||
---@param config CapConfig
|
||||
---@return table
|
||||
function INTERCEPT.getMissionFromAirbase(groupName, interceptPoint, airbase, config, speed, alt)
|
||||
|
||||
local airbaseVec3 = airbase:getPoint()
|
||||
@@ -92,7 +93,7 @@ end
|
||||
---@param currentPoint Vec2
|
||||
---@param targetPosition Vec2
|
||||
---@param targetUnit Unit
|
||||
---@param airbase any
|
||||
---@param airbase Airbase
|
||||
---@param config any
|
||||
---@param speed number?
|
||||
---@param alt number?
|
||||
|
||||
@@ -7,6 +7,7 @@ local RTB = {}
|
||||
---@param airbase Airbase
|
||||
---@param missionPoint Vec2
|
||||
---@param capConfig CapConfig
|
||||
---@return table
|
||||
function RTB.getAsMission(airbase, missionPoint, capConfig)
|
||||
return {
|
||||
id = "Mission",
|
||||
@@ -113,6 +114,7 @@ end
|
||||
---comment
|
||||
---@param airbase Airbase
|
||||
---@param missionPoint Vec2
|
||||
---@return table
|
||||
---@param capConfig CapConfig
|
||||
function RTB.getApproachPoint(airbase, missionPoint, capConfig)
|
||||
|
||||
@@ -147,6 +149,7 @@ function RTB.getApproachPoint(airbase, missionPoint, capConfig)
|
||||
end
|
||||
|
||||
---@param airbase Airbase
|
||||
---@return table
|
||||
function RTB.getInitialPoint(airbase)
|
||||
local point = calcInitialPoint(airbase)
|
||||
return {
|
||||
@@ -170,6 +173,8 @@ function RTB.getInitialPoint(airbase)
|
||||
}
|
||||
end
|
||||
|
||||
---@param airbase Airbase
|
||||
---@return table
|
||||
function RTB.getLandingPoint(airbase)
|
||||
local basePoint = airbase:getPoint()
|
||||
return {
|
||||
|
||||
@@ -7,6 +7,7 @@ local SWEEP = {}
|
||||
---@param attackHelos boolean
|
||||
---@return table
|
||||
local function GetCAPTargetTypes(attackHelos)
|
||||
---@type Array<string>
|
||||
local targetTypes = {
|
||||
[1] = "Planes",
|
||||
}
|
||||
@@ -49,7 +50,9 @@ local function GetCAPPointFromTriggerZone(airBase, capZone)
|
||||
---@type Vec2
|
||||
local baseVec2 = { x = baseVec3.x, y = baseVec3.z }
|
||||
|
||||
---@type Vec2
|
||||
local pointA = capZone.verts[furthestA]
|
||||
---@type Vec2
|
||||
local pointB = capZone.verts[furthestB]
|
||||
local furthest = pointA
|
||||
local closest = pointB
|
||||
@@ -80,6 +83,7 @@ end
|
||||
---@param airbase Airbase
|
||||
---@param capZone SpearheadTriggerZone
|
||||
---@param capConfig CapConfig
|
||||
---@return table
|
||||
local GetOutboundTask = function(airbase, capZone, capConfig)
|
||||
local airbaseVec3 = airbase:getPoint()
|
||||
local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z }
|
||||
@@ -121,6 +125,7 @@ end
|
||||
---@param airbase Airbase
|
||||
---@param capZone SpearheadTriggerZone
|
||||
---@param capConfig CapConfig
|
||||
---@return table
|
||||
function SWEEP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig)
|
||||
|
||||
local pointA, pointB, pointC = SWEEP.getAsTasking(groupName, airbase, capZone, capConfig)
|
||||
|
||||
@@ -21,6 +21,10 @@ function CapConfig.new()
|
||||
if SpearheadConfig == nil then SpearheadConfig = {} 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
|
||||
if enabled == nil then enabled = true end
|
||||
self._isEnabled = enabled
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
|
||||
|
||||
|
||||
local briefingMessageTime = nil
|
||||
|
||||
---@class GlobalConfig
|
||||
---@field private _briefingTime number
|
||||
---@field private _debugEnabled boolean
|
||||
---@field private _debugMenuEnabled boolean
|
||||
local GlobalConfig = {}
|
||||
GlobalConfig.__index = GlobalConfig;
|
||||
|
||||
@@ -27,6 +24,8 @@ function GlobalConfig.New()
|
||||
self._debugEnabled = false
|
||||
end
|
||||
|
||||
self._debugMenuEnabled = SpearheadConfig.debugMenuEnabled == true
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -40,6 +39,10 @@ function GlobalConfig:isDebugEnabled()
|
||||
return self._debugEnabled or false
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function GlobalConfig:isDebugMenuEnabled()
|
||||
return self._debugMenuEnabled or false
|
||||
end
|
||||
|
||||
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
|
||||
@@ -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 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 = {}
|
||||
FleetGroup.__index = FleetGroup
|
||||
|
||||
---comment
|
||||
---@param fleetGroupName string
|
||||
---@param database Database
|
||||
---@param logger Logger
|
||||
---@return nil
|
||||
function FleetGroup:new(fleetGroupName, database, logger)
|
||||
local o = {}
|
||||
---@return FleetGroup?
|
||||
function FleetGroup.new(fleetGroupName, database, logger)
|
||||
|
||||
setmetatable(o, { __index = self })
|
||||
local self = setmetatable({}, FleetGroup)
|
||||
|
||||
o.fleetGroupName = fleetGroupName
|
||||
o.logger = logger
|
||||
self.fleetGroupName = fleetGroupName
|
||||
self.logger = logger
|
||||
|
||||
local split_name = Util.split_string(fleetGroupName, "_")
|
||||
if Util.tableLength(split_name) < 2 then
|
||||
MissionEditorWarning.Add("CARRIERGROUP should have at least 2 parts. CARRIERGROUP_<fleetname>")
|
||||
return nil
|
||||
end
|
||||
o.fleetNameIdentifier = split_name[2]
|
||||
self.fleetNameIdentifier = split_name[2]
|
||||
|
||||
o.targetZonePerStage = {}
|
||||
o.currentTargetZone = nil
|
||||
o.pointsPerZone = {}
|
||||
self.targetZonePerStage = {}
|
||||
self.currentTargetZone = nil
|
||||
self.pointsPerZone = {}
|
||||
|
||||
do --INIT
|
||||
local carrierRouteZones = database:getCarrierRouteZones()
|
||||
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)
|
||||
if zone and zone.zone_type == DcsUtil.ZoneType.Polygon then
|
||||
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)
|
||||
else
|
||||
|
||||
---@param zone SpearheadTriggerZone
|
||||
---@param zoneA SpearheadTriggerZone
|
||||
---@return Vec2, Vec2
|
||||
local function GetTwoFurthestPoints(zone)
|
||||
local function GetTwoFurthestPoints(zoneA)
|
||||
|
||||
local biggest = nil
|
||||
local biggestA = zone.verts[1]
|
||||
local biggestB = zone.verts[2]
|
||||
local biggestA = zoneA.verts[1]
|
||||
local biggestB = zoneA.verts[2]
|
||||
|
||||
for i = 1, 3 do
|
||||
for ii = i + 1, 4 do
|
||||
local a = zone.verts[i]
|
||||
local b = zone.verts[ii]
|
||||
local a = zoneA.verts[i]
|
||||
local b = zoneA.verts[ii]
|
||||
local dist = Util.VectorDistance2d(a, b)
|
||||
|
||||
if biggest == nil or dist > biggest then
|
||||
@@ -72,7 +78,7 @@ function FleetGroup:new(fleetGroupName, database, logger)
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
if Util.startswith(namePart, "%[") == true then
|
||||
if Util.startsWith(namePart, "%[") == true then
|
||||
namePart = Util.split_string(namePart, "[")[1]
|
||||
end
|
||||
|
||||
@@ -100,9 +106,15 @@ function FleetGroup:new(fleetGroupName, database, logger)
|
||||
local first, second = getMinMaxStage(split_string[3])
|
||||
if first ~= nil and second ~= nil then
|
||||
for i = first, second do
|
||||
o.targetZonePerStage[tostring(i)] = zoneName
|
||||
self.targetZonePerStage[tostring(i)] = zoneName
|
||||
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
|
||||
MissionEditorWarning.Add("CARRIERROUTE zone stage numbers not in the format _[<number>-<number>]: " .. zoneName)
|
||||
end
|
||||
@@ -114,30 +126,32 @@ function FleetGroup:new(fleetGroupName, database, logger)
|
||||
end
|
||||
end
|
||||
|
||||
local SetTaskAsync = function(input, time)
|
||||
local targetZone = input.targetZone
|
||||
local task = input.task
|
||||
local groupName = input.groupName
|
||||
local logger = input.logger
|
||||
SpearheadEvents.AddStageNumberChangedListener(self)
|
||||
return self
|
||||
end
|
||||
|
||||
local group = Group.getByName(groupName)
|
||||
if group then
|
||||
logger:info("Sending " .. groupName .. " to " .. targetZone)
|
||||
group:getController():setTask(task)
|
||||
end
|
||||
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)
|
||||
if group then
|
||||
l_logger:info("Sending " .. groupName .. " to " .. targetZone)
|
||||
group:getController():setTask(task)
|
||||
end
|
||||
end
|
||||
|
||||
o.OnStageNumberChanged = function(self, number)
|
||||
local targetZone = self.targetZonePerStage[tostring(number)]
|
||||
if targetZone and targetZone ~= self.currentTargetZone then
|
||||
local points = self.pointsPerZone[targetZone]
|
||||
local task = RouteUtil.CreateCarrierRacetrack(points.pointA, points.pointB)
|
||||
timer.scheduleFunction(SetTaskAsync, { task = task, targetZone = targetZone, groupName = self.fleetGroupName, logger = self.logger }, timer.getTime() + 5)
|
||||
end
|
||||
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)]
|
||||
if targetZone and targetZone ~= self.currentTargetZone then
|
||||
local points = self.pointsPerZone[targetZone]
|
||||
local task = RouteUtil.CreateCarrierRacetrack(points.pointA, points.pointB)
|
||||
timer.scheduleFunction(SetTaskAsync, { task = task, targetZone = targetZone, groupName = self.fleetGroupName, logger = self.logger }, timer.getTime() + 5)
|
||||
end
|
||||
|
||||
SpearheadEvents.AddStageNumberChangedListener(o)
|
||||
return o
|
||||
end
|
||||
|
||||
return FleetGroup
|
||||
|
||||
@@ -15,9 +15,9 @@ GlobalFleetManager.start = function(database)
|
||||
|
||||
local all_groups = MizGroupsManager.getAllGroupNames()
|
||||
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")
|
||||
local carrierGroup = FleetGroup:new(groupName, database, logger)
|
||||
local carrierGroup = FleetGroup.new(groupName, database, logger)
|
||||
table.insert(fleetGroups, carrierGroup)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,24 +15,45 @@ MizGroupsManager._groupNames = {}
|
||||
MizGroupsManager._spawnTemplateData = {}
|
||||
|
||||
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
|
||||
for country_index, country_data in pairs(coalition_data.country) do
|
||||
for category_name, categorydata in pairs(country_data) do
|
||||
for _, country_data in pairs(coalition_data.country) do
|
||||
for _, category_name in pairs(DcsUtil.getAllGroupCategoryNames()) do
|
||||
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
|
||||
for group_index, group in pairs(categorydata.group) do
|
||||
local name = group.name
|
||||
local groups_data = getGroupsData(country_data, category_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 isStatic = false
|
||||
if category_id == DcsUtil.GroupCategory.STATIC then
|
||||
isStatic = true
|
||||
local unit = group.units[1]
|
||||
local unit = group.units[1] --[[@as table]]
|
||||
if unit and unit.category == "Heliports" then
|
||||
skippable = true
|
||||
elseif unit and unit.name then
|
||||
name = unit.name
|
||||
name = unit.name --[[@as string]]
|
||||
else
|
||||
env.error("Group " .. name .. " has no units, skipping it.")
|
||||
skippable = true
|
||||
@@ -72,6 +93,7 @@ function MizGroupsManager.IsGroupStatic(groupName)
|
||||
return nil
|
||||
end
|
||||
|
||||
---@param groupName string
|
||||
---@return SpawnData?
|
||||
function MizGroupsManager.getSpawnTemplateData(groupName)
|
||||
return MizGroupsManager._spawnTemplateData[groupName]
|
||||
|
||||
@@ -16,11 +16,11 @@ SpawnManager.__index = SpawnManager
|
||||
---@field uncontrolled boolean? Sets the group to be uncontrolled on spawn
|
||||
|
||||
---@param logger Logger
|
||||
---@return SpawnManager
|
||||
function SpawnManager.new(logger)
|
||||
local self = setmetatable({}, SpawnManager)
|
||||
|
||||
self._logger = logger
|
||||
|
||||
self._persistedUnits = {} -- Stores units that should be persisted by name
|
||||
|
||||
return self
|
||||
@@ -44,7 +44,7 @@ function SpawnManager:SpawnGroup(groupName, overrides, isGroupPersistant)
|
||||
if spawnData.isStatic == true then
|
||||
return self:SpawnStaticInternal(groupName, spawnData, overrides, isGroupPersistant), true
|
||||
else
|
||||
return self:SpawnGroupInternal(groupName, spawnData, overrides, isGroupPersistant), false
|
||||
return self:SpawnGroupInternal(spawnData, overrides, isGroupPersistant), false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -103,20 +103,20 @@ function SpawnManager:OnUnitLost(object)
|
||||
end
|
||||
end
|
||||
|
||||
---@param groupName string
|
||||
function SpawnManager:SpawnCorpsesOnly(groupName)
|
||||
if groupName == nil then return end
|
||||
|
||||
end
|
||||
|
||||
do --- privates
|
||||
|
||||
---@private
|
||||
---@param groupName string
|
||||
---@param spawnData SpawnData
|
||||
---@param override SpawnOverrides?
|
||||
---@param isPersistent boolean?
|
||||
---@return Group|nil
|
||||
function SpawnManager:SpawnGroupInternal(groupName, spawnData, override, isPersistent)
|
||||
|
||||
function SpawnManager:SpawnGroupInternal(spawnData, override, isPersistent)
|
||||
if not spawnData then return end
|
||||
|
||||
local country = spawnData.country
|
||||
@@ -125,37 +125,31 @@ do --- privates
|
||||
end
|
||||
|
||||
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.
|
||||
ONLY when perstable data is found.
|
||||
]]
|
||||
|
||||
if spawnTemplate and spawnTemplate["units"] then
|
||||
for _, unit in pairs(spawnTemplate["units"]) do
|
||||
local name = unit["name"]
|
||||
local units = spawnTemplate["units"] --[[@as Array<table>]]
|
||||
for _, unit in pairs(units) do
|
||||
local name = unit["name"] --[[@as string]]
|
||||
|
||||
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 unit["payload"] and unit["payload"]["pylons"] then
|
||||
unit["payload"]["pylons"] = {}
|
||||
local payload = unit["payload"] --[[@as table]]
|
||||
payload["pylons"] = {} --[[@as table]]
|
||||
end
|
||||
end
|
||||
|
||||
if unit["parking"] then
|
||||
unit["parking_landing"] = unit["parking"]
|
||||
unit["parking_landing"] = unit["parking"] --[[@as number]]
|
||||
end
|
||||
|
||||
if unit["parking_id"] then
|
||||
unit["parking_landing_id"] = unit["parking_id"]
|
||||
unit["parking_landing_id"] = unit["parking_id"] --[[@as number]]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -169,7 +163,6 @@ do --- privates
|
||||
|
||||
local group = coalition.addGroup(country, spawnData.category, spawnTemplate)
|
||||
|
||||
|
||||
for _, unit in pairs(group:getUnits()) do
|
||||
self:CheckUnitAndReplaceIfPersistentDead(unit)
|
||||
if isPersistent == true then
|
||||
@@ -181,7 +174,6 @@ do --- privates
|
||||
return group
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -211,9 +203,11 @@ do --- privates
|
||||
spawnTemplate["x"] = persistentState.pos.x
|
||||
spawnTemplate["y"] = persistentState.pos.z
|
||||
if spawnTemplate["units"] and spawnTemplate["units"][1] then
|
||||
spawnTemplate["units"][1]["x"] = persistentState.pos.x
|
||||
spawnTemplate["units"][1]["y"] = persistentState.pos.z
|
||||
spawnTemplate["units"][1]["heading"] = persistentState.heading or 0
|
||||
local units = spawnTemplate["units"] --[[@as Array<table>]]
|
||||
local firstUnit = units[1] --[[@as table]]
|
||||
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
|
||||
|
||||
@@ -13,7 +13,8 @@ do
|
||||
---@field unitsStates table<string, UnitState>
|
||||
---@field random_missions table<string, MissionState>
|
||||
---@field deliveredKilos table<string, number>
|
||||
---@field activeStage integer|nil
|
||||
---@field activeStage number? required for backwards compatibility
|
||||
---@field activeStageInStageLane table<string, number>
|
||||
|
||||
---@class UnitState
|
||||
---@field isDead boolean
|
||||
@@ -33,7 +34,8 @@ do
|
||||
unitsStates = {},
|
||||
random_missions = {},
|
||||
deliveredKilos = {},
|
||||
activeStage = nil
|
||||
activeStage = nil, -- Backwards compatibility for previous version without lanes
|
||||
activeStageInStageLane = {}
|
||||
}
|
||||
|
||||
local logger = {}
|
||||
@@ -110,7 +112,7 @@ do
|
||||
logger:info("Wrote persistence data to file")
|
||||
end
|
||||
|
||||
local UpdateContinuous = function(null, time)
|
||||
local UpdateContinuous = function(_, time)
|
||||
env.info("[Spearhead][Persistence] Checking up on persistence state...")
|
||||
if Persistence._updateRequired == true then
|
||||
local status, result = pcall(writeToFile)
|
||||
@@ -133,20 +135,22 @@ do
|
||||
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)
|
||||
return time + 9
|
||||
end
|
||||
|
||||
---@param dir string @BaseDirectory
|
||||
---@return string
|
||||
---@param startsWith string
|
||||
---@param default string?
|
||||
---@return string?
|
||||
local getLastFileOrDefault = function(dir, startsWith, default)
|
||||
|
||||
local latestFile, lastNumber = default, 0
|
||||
for file in lfs.dir(dir) do
|
||||
|
||||
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
|
||||
local numberString = split[#split-1]
|
||||
local number = tonumber(numberString)
|
||||
@@ -179,11 +183,14 @@ do
|
||||
local fileName = "Spearhead_Persistence.0.spearhead"
|
||||
if SpearheadConfig and SpearheadConfig.Persistence 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 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] = "spearhead"
|
||||
end
|
||||
@@ -196,7 +203,10 @@ do
|
||||
fileName = table.concat(split, ".")
|
||||
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
|
||||
end
|
||||
end
|
||||
@@ -206,6 +216,10 @@ do
|
||||
|
||||
local lastFile = getLastFileOrDefault(dir--[[@as string]], matchingPart, fileName)
|
||||
|
||||
if lastFile == nil then
|
||||
lastFile = fileName
|
||||
end
|
||||
|
||||
local fileSplit = Util.split_string(lastFile, ".")
|
||||
fileSplit[#fileSplit-1] = tostring(tonumber(fileSplit[#fileSplit-1]) + 1)
|
||||
fileName = table.concat(fileSplit, ".")
|
||||
@@ -230,12 +244,29 @@ do
|
||||
end
|
||||
|
||||
---Sets the stage in the persistence table
|
||||
---@param stageLane string?
|
||||
---@param stageNumber number
|
||||
Persistence.SetActiveStage = function(stageNumber)
|
||||
tables.activeStage = stageNumber
|
||||
Persistence.SetActiveStage = function(stageLane, stageNumber)
|
||||
stageLane = stageLane or "nil"
|
||||
tables.activeStageInStageLane[stageLane] = stageNumber
|
||||
Persistence._updateRequired = true
|
||||
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
|
||||
---@param missionName string
|
||||
---@param pickedZone string
|
||||
@@ -255,14 +286,6 @@ do
|
||||
return tables.random_missions[string.lower(missionName)]
|
||||
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
|
||||
---@param zoneName string
|
||||
|
||||
+110
-39
@@ -2,7 +2,7 @@ local Util = require("classes.util.Util")
|
||||
local DcsUtil = require("classes.util.DcsUtil")
|
||||
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
|
||||
local MizGroupsManager = require("classes.helpers.MizGroupsManager")
|
||||
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||
local StageDrawing = require("classes.stageClasses.drawings.StageDrawing")
|
||||
|
||||
|
||||
---@class DatabaseTables
|
||||
@@ -27,7 +27,7 @@ local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||
---@field MissionZoneData table<string, MissionZoneData>
|
||||
---@field FarpZoneData table<string,FarpZoneData>
|
||||
---@field missionCodes table<string, boolean>
|
||||
---@field CustomDrawings Array<CustomDrawing>
|
||||
---@field StageDrawings Array<StageDrawing>
|
||||
|
||||
---@class CapRoute
|
||||
---@field zones Array<SpearheadTriggerZone>
|
||||
@@ -58,6 +58,7 @@ local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||
---@class BlueSamData
|
||||
---@field groups Array<string>
|
||||
---@field buildingKilos number?
|
||||
---@field briefing string?
|
||||
|
||||
---@class MissionZoneData
|
||||
---@field ZoneName string
|
||||
@@ -68,12 +69,14 @@ local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||
---@field descriptionLocation Vec2?
|
||||
---@field dependsOn Array<string>
|
||||
---@field completeAt number?
|
||||
---@field primaryOverwrite boolean?
|
||||
|
||||
---@class FarpZoneData
|
||||
---@field groups Array<string>
|
||||
---@field padNames Array<string>
|
||||
---@field buildingKilos number?
|
||||
---@field supplyHubNames Array<string>
|
||||
---@field briefing string?
|
||||
|
||||
---@class Database
|
||||
---@field private _tables DatabaseTables
|
||||
@@ -107,7 +110,7 @@ function Database.New(Logger)
|
||||
FarpZoneData = {},
|
||||
missionCodes = {},
|
||||
SupplyHubZones = {},
|
||||
CustomDrawings = {}
|
||||
StageDrawings = {}
|
||||
}
|
||||
|
||||
Database.__index = Database
|
||||
@@ -119,7 +122,7 @@ function Database.New(Logger)
|
||||
self._logger:debug("Initiating 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
|
||||
|
||||
---@type Vec2
|
||||
@@ -206,15 +209,18 @@ function Database.New(Logger)
|
||||
self._logger:debug("initiated zone tables, continuing with descriptions")
|
||||
do --load markers
|
||||
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
|
||||
for key, layer_object in pairs(layer.objects) do
|
||||
if Util.startswith(string.lower(layer_object.name), "buildable", true) == true then
|
||||
for _, layer_object in pairs(layer.objects) do
|
||||
if Util.startsWith(string.lower(layer_object.name), "buildable", true) == true then
|
||||
local airbaseData = self:getAirbaseDataForDrawLayer(layer_object)
|
||||
if airbaseData then
|
||||
self._logger:debug("found airbase data for " .. layer_object.name)
|
||||
local number = tonumber(layer_object.text)
|
||||
airbaseData.buildingKilos = number
|
||||
if layer_object.primitiveType == "TextBox" then
|
||||
layer_object = layer_object --[[@as TextBox]]
|
||||
local number = tonumber(layer_object.text)
|
||||
airbaseData.buildingKilos = number
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -226,13 +232,15 @@ function Database.New(Logger)
|
||||
---@type table<integer, boolean>
|
||||
do -- custom drawings
|
||||
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
|
||||
for key, layer_object in pairs(layer.objects) do
|
||||
if Util.startswith(layer_object.name, "drawing_", true) then
|
||||
for _, layer_object in pairs(layer.objects) do
|
||||
if Util.startsWith(layer_object.name, "drawing_", true) then
|
||||
local object = layer_object --[[@as DrawingObject]]
|
||||
local customDrawing = CustomDrawing.New(object)
|
||||
table.insert(self._tables.CustomDrawings, customDrawing)
|
||||
local stageDrawing = StageDrawing.New(object)
|
||||
if stageDrawing then
|
||||
table.insert(self._tables.StageDrawings, stageDrawing)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -252,16 +260,19 @@ function Database.New(Logger)
|
||||
|
||||
-- Find and add Briefing
|
||||
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
|
||||
for key, layer_object in pairs(layer.objects) do
|
||||
if Util.startswith(string.lower(layer_object.name), "stagebriefing_", true) == true then
|
||||
for _, layer_object in pairs(layer.objects) do
|
||||
if Util.startsWith(string.lower(layer_object.name), "stagebriefing_", true) == true then
|
||||
local zone = DcsUtil.getZoneByName(stageZoneName)
|
||||
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
|
||||
if zone and Util.is2dPointInZone(vec2, zone) == true then
|
||||
local description = layer_object.text
|
||||
if description and description ~= "" then
|
||||
stageData.StageBriefing = description
|
||||
if layer_object.primitiveType == "TextBox" then
|
||||
layer_object = layer_object --[[@as TextBox]]
|
||||
local description = layer_object.text
|
||||
if description and description ~= "" then
|
||||
stageData.StageBriefing = description
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -335,14 +346,14 @@ function Database.New(Logger)
|
||||
end
|
||||
|
||||
-- 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
|
||||
table.insert(stageData.MissionZones, missionZone)
|
||||
end
|
||||
end
|
||||
|
||||
-- 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
|
||||
table.insert(stageData.RandomMissionZones, missionZone)
|
||||
end
|
||||
@@ -464,6 +475,7 @@ function Database.New(Logger)
|
||||
return self
|
||||
end
|
||||
|
||||
---@type table<string, boolean>
|
||||
local is_group_taken = {}
|
||||
|
||||
local getAvailableGroups = function()
|
||||
@@ -480,7 +492,7 @@ end
|
||||
local getAvailableCAPGroups = function()
|
||||
local result = {}
|
||||
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)
|
||||
end
|
||||
end
|
||||
@@ -583,11 +595,11 @@ function Database:loadCapUnits()
|
||||
for _, groupName in pairs(groups) do
|
||||
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)
|
||||
elseif Util.startswith(groupName, "CAP_I", true) then
|
||||
elseif Util.startsWith(groupName, "CAP_I", true) then
|
||||
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)
|
||||
end
|
||||
end
|
||||
@@ -610,10 +622,32 @@ function Database:loadBlueSamUnits()
|
||||
local triggerZone = DcsUtil.getZoneByName(blueSamZone)
|
||||
if triggerZone then
|
||||
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)
|
||||
if number and number > 0 then
|
||||
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
|
||||
@@ -621,12 +655,12 @@ function Database:loadBlueSamUnits()
|
||||
end
|
||||
end
|
||||
|
||||
---@return Array<CustomDrawing>
|
||||
function Database:getCustomDrawings()
|
||||
if self._tables.CustomDrawings == nil then
|
||||
---@return Array<StageDrawing>
|
||||
function Database:getStageDrawings()
|
||||
if self._tables.StageDrawings == nil then
|
||||
return {}
|
||||
end
|
||||
return self._tables.CustomDrawings
|
||||
return self._tables.StageDrawings
|
||||
end
|
||||
|
||||
---Loads all units, data and briefings
|
||||
@@ -677,9 +711,9 @@ function Database:LoadZoneData(missionZoneName)
|
||||
if triggerZone and triggerZone.properties then
|
||||
for _, kvPair in pairs(triggerZone.properties) do
|
||||
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)
|
||||
elseif Util.startswith(key, "completeat") == true then
|
||||
elseif Util.startsWith(key, "completeat") == true then
|
||||
local value = tonumber(kvPair.value)
|
||||
if value then
|
||||
if value > 1 and value <= 100 then
|
||||
@@ -689,19 +723,28 @@ function Database:LoadZoneData(missionZoneName)
|
||||
end
|
||||
self._tables.MissionZoneData[missionZoneName].completeAt = value
|
||||
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
|
||||
|
||||
---Adds Briefings
|
||||
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
|
||||
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 }
|
||||
if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
|
||||
if layer_object.name and Util.startswith(layer_object.name, "briefing_", true) 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
|
||||
layer_object = layer_object --[[@as TextBox]]
|
||||
local description = layer_object.text
|
||||
if description and description ~= "" then
|
||||
self._tables.MissionZoneData[missionZoneName].description = description
|
||||
@@ -744,10 +787,33 @@ function Database:loadFarpData()
|
||||
local triggerZone = DcsUtil.getZoneByName(farpZone)
|
||||
if triggerZone then
|
||||
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)
|
||||
if number and number > 0 then
|
||||
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
|
||||
@@ -903,6 +969,7 @@ function Database:getBriefingForStage(stagename)
|
||||
return stageZone.StageBriefing or ""
|
||||
end
|
||||
|
||||
---@param stagename string
|
||||
---@return Array<string>
|
||||
function Database:getMissionsForStage(stagename)
|
||||
local stageZone = self._tables.StageZones[stagename]
|
||||
@@ -910,6 +977,7 @@ function Database:getMissionsForStage(stagename)
|
||||
return stageZone.MissionZones
|
||||
end
|
||||
|
||||
---@param stagename string
|
||||
---@return Array<string>
|
||||
function Database:getRandomMissionsForStage(stagename)
|
||||
local stageZone = self._tables.StageZones[stagename]
|
||||
@@ -917,13 +985,14 @@ function Database:getRandomMissionsForStage(stagename)
|
||||
return stageZone.RandomMissionZones
|
||||
end
|
||||
|
||||
---@param missionZoneName string
|
||||
---@return MissionZoneData?
|
||||
function Database:getMissionDataForZone(missionZoneName)
|
||||
return self._tables.MissionZoneData[missionZoneName]
|
||||
end
|
||||
|
||||
---@param stageName string
|
||||
---@return table result airbase Names
|
||||
---@return Array<string> result airbase Names
|
||||
function Database:getAirbaseNamesInStage(stageName)
|
||||
local stageData = self._tables.StageZones[stageName]
|
||||
if not stageData then return {} end
|
||||
@@ -937,6 +1006,7 @@ function Database:getFarpNamesInStage(stageName)
|
||||
return stageData.FarpZones or {}
|
||||
end
|
||||
|
||||
---@param farpZoneName string
|
||||
---@return FarpZoneData?
|
||||
function Database:getFarpDataForZone(farpZoneName)
|
||||
local farpData = self._tables.FarpZoneData[farpZoneName]
|
||||
@@ -952,6 +1022,7 @@ function Database:getAirbaseDataForZone(baseName)
|
||||
return baseData
|
||||
end
|
||||
|
||||
---@param stageName string
|
||||
---@return string?
|
||||
function Database:getStageBriefingForStage(stageName)
|
||||
local stageData = self._tables.StageZones[stageName]
|
||||
|
||||
@@ -29,49 +29,47 @@ do
|
||||
end
|
||||
|
||||
---@class OnStageChangedListener
|
||||
---@field OnStageNumberChanged fun(self:OnStageChangedListener, number:integer)
|
||||
---@field OnStageNumberChanged fun(self:OnStageChangedListener, number:integer, laneIdentifier:string?)
|
||||
|
||||
do -- STAGE NUMBER CHANGED
|
||||
---@type Array<OnStageChangedListener>
|
||||
local OnStageNumberChangedListeners = {}
|
||||
---@type Array<fun(number:integer, laneIdentifier:string?)>
|
||||
local OnStageNumberChangedHandlers = {}
|
||||
---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)
|
||||
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)
|
||||
end
|
||||
|
||||
---Add a stage zone number changed listener
|
||||
---@param handler function function(number)
|
||||
SpearheadEvents.AddStageNumberChangedHandler = function(handler)
|
||||
if type(handler) ~= "function" then
|
||||
warn("Event handler not of type function, did you mean to use listener?")
|
||||
return
|
||||
end
|
||||
table.insert(OnStageNumberChangedHandlers, handler)
|
||||
end
|
||||
|
||||
---@class OnStageNumberChangeCompleteListener
|
||||
---@field OnStageNumberChangeComplete fun(self:OnStageNumberChangeCompleteListener, number:integer)
|
||||
---@field OnStageNumberChangeComplete fun(self:OnStageNumberChangeCompleteListener, number:integer, laneIdentifier:string?)
|
||||
|
||||
---@type Array<OnStageNumberChangeCompleteListener>
|
||||
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)")
|
||||
warn("Event handler not of type table/object with function OnStageNumberChangeComplete(self, number, laneIdentifier)")
|
||||
return
|
||||
end
|
||||
table.insert(OnStageNumberChangeCompleteListeners, listener)
|
||||
end
|
||||
|
||||
---@param newStageNumber number
|
||||
SpearheadEvents.PublishStageNumberChanged = function(newStageNumber)
|
||||
---@param laneIdentifier string?
|
||||
SpearheadEvents.PublishStageNumberChanged = function(newStageNumber, laneIdentifier)
|
||||
pcall(function ()
|
||||
Persistence.SetActiveStage(newStageNumber)
|
||||
Persistence.SetActiveStage(laneIdentifier, newStageNumber)
|
||||
end)
|
||||
|
||||
for _, callable in pairs(OnStageNumberChangedListeners) do
|
||||
local succ, err = pcall(function()
|
||||
callable:OnStageNumberChanged(newStageNumber)
|
||||
local _, err = pcall(function()
|
||||
callable:OnStageNumberChanged(newStageNumber, laneIdentifier)
|
||||
end)
|
||||
if err then
|
||||
logError(err)
|
||||
@@ -79,15 +77,15 @@ do
|
||||
end
|
||||
|
||||
for _, callable in pairs(OnStageNumberChangedHandlers) do
|
||||
local succ, err = pcall(callable, newStageNumber)
|
||||
local _, err = pcall(callable, newStageNumber, laneIdentifier)
|
||||
if err then
|
||||
logError(err)
|
||||
end
|
||||
end
|
||||
|
||||
for _, callable in pairs(OnStageNumberChangeCompleteListeners) do
|
||||
local succ, err = pcall(function()
|
||||
callable:OnStageNumberChangeComplete(newStageNumber)
|
||||
local _, err = pcall(function()
|
||||
callable:OnStageNumberChangeComplete(newStageNumber, laneIdentifier)
|
||||
end)
|
||||
if err then
|
||||
logError(err)
|
||||
@@ -115,9 +113,12 @@ do
|
||||
table.insert(onWeaponFiredListeners, weaponFiredListener)
|
||||
end
|
||||
|
||||
---@param unit Unit
|
||||
---@param weapon Weapon
|
||||
---@param target Unit?
|
||||
local triggerWeaponFired = function(unit, weapon, target)
|
||||
for _, callable in pairs(onWeaponFiredListeners) do
|
||||
local succ, err = pcall(function()
|
||||
local _, err = pcall(function()
|
||||
callable:OnWeaponFired(unit, weapon, target)
|
||||
end)
|
||||
|
||||
@@ -127,11 +128,14 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
---@class OnLandEventListener
|
||||
---@field OnUnitLanded fun(self:OnLandEventListener, initiatorUnit:Unit, airbase:Airbase)
|
||||
|
||||
---@type table<string,Array<OnLandEventListener>>
|
||||
local onLandEventListeners = {}
|
||||
---Add an event listener to a specific unit
|
||||
---@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)
|
||||
if type(landListener) ~= "table" then
|
||||
warn("Event handler not of type table/object")
|
||||
@@ -167,6 +171,11 @@ do
|
||||
end
|
||||
|
||||
do -- ON RTB
|
||||
|
||||
---@class OnGroupRTBListener
|
||||
---@field OnGroupRTB fun(self:OnGroupRTBListener, groupName:string)
|
||||
|
||||
---@type table<string,Array<OnGroupRTBListener>>
|
||||
local OnGroupRTBListeners = {}
|
||||
---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
|
||||
@@ -191,7 +200,7 @@ do
|
||||
if groupName ~= nil then
|
||||
if OnGroupRTBListeners[groupName] then
|
||||
for _, callable in pairs(OnGroupRTBListeners[groupName]) do
|
||||
local succ, err = pcall(function()
|
||||
local _, err = pcall(function()
|
||||
callable:OnGroupRTB(groupName)
|
||||
end)
|
||||
if err then
|
||||
@@ -202,11 +211,15 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
---@class OnGroupRTBInTenListener
|
||||
---@field OnGroupRTBInTen fun(self:OnGroupRTBInTenListener, groupName:string)
|
||||
|
||||
---@type table<string,Array<OnGroupRTBInTenListener>>
|
||||
local OnGroupRTBInTenListeners = {}
|
||||
---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
|
||||
---@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)
|
||||
if type(handlingObject) ~= "table" then
|
||||
warn("Event handler not of type table/object")
|
||||
@@ -226,7 +239,7 @@ do
|
||||
if groupName ~= nil then
|
||||
if OnGroupRTBInTenListeners[groupName] then
|
||||
for _, callable in pairs(OnGroupRTBInTenListeners[groupName]) do
|
||||
local succ, err = pcall(function()
|
||||
local _, err = pcall(function()
|
||||
callable:OnGroupRTBInTen(groupName)
|
||||
end)
|
||||
if err then
|
||||
@@ -239,10 +252,17 @@ do
|
||||
end
|
||||
|
||||
do -- ON Station
|
||||
|
||||
---@class OnGroupOnStationListener
|
||||
---@field OnGroupOnStation fun(self:OnGroupOnStationListener, groupName:string)
|
||||
|
||||
---@type table<string,Array<OnGroupOnStationListener>>
|
||||
local OnGroupOnStationListeners = {}
|
||||
|
||||
---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
|
||||
---@param groupName string the groupname to expect
|
||||
---@param handlingObject OnGroupOnStationListener object with OnGroupOnStation(self, groupName)
|
||||
SpearheadEvents.addOnGroupOnStationListener = function(groupName, handlingObject)
|
||||
if type(handlingObject) ~= "table" then
|
||||
warn("Event handler not of type table/object")
|
||||
@@ -262,7 +282,7 @@ do
|
||||
if groupName ~= nil then
|
||||
if OnGroupOnStationListeners[groupName] then
|
||||
for _, callable in pairs(OnGroupOnStationListeners[groupName]) do
|
||||
local succ, err = pcall(function()
|
||||
local _, err = pcall(function()
|
||||
callable:OnGroupOnStation(groupName)
|
||||
end)
|
||||
if err then
|
||||
@@ -275,15 +295,18 @@ do
|
||||
end
|
||||
|
||||
do -- PLAYER ENTER UNIT
|
||||
---@class OnPlayerEnterUnitListener
|
||||
---@field OnPlayerEntersUnit fun(self:OnPlayerEnterUnitListener, unit:Unit)
|
||||
|
||||
---@type Array<OnPlayerEnterUnitListener>
|
||||
local playerEnterUnitListeners = {}
|
||||
---comment
|
||||
---@param listener table object with OnPlayerEntersUnit(self, unit)
|
||||
---@param listener OnPlayerEnterUnitListener object with OnPlayerEntersUnit(self, unit)
|
||||
SpearheadEvents.AddOnPlayerEnterUnitListener = function(listener)
|
||||
if type(listener) ~= "table" then
|
||||
warn("Unit lost Event listener not of type table/object")
|
||||
return
|
||||
end
|
||||
|
||||
table.insert(playerEnterUnitListeners, listener)
|
||||
end
|
||||
|
||||
@@ -291,7 +314,7 @@ do
|
||||
if unit ~= nil then
|
||||
if playerEnterUnitListeners then
|
||||
for _, callable in pairs(playerEnterUnitListeners) do
|
||||
local succ, err = pcall(function()
|
||||
local _, err = pcall(function()
|
||||
callable:OnPlayerEntersUnit(unit)
|
||||
end)
|
||||
if err then
|
||||
@@ -320,13 +343,13 @@ do
|
||||
local e = {}
|
||||
function e:onEvent(event)
|
||||
if event.id == world.event.S_EVENT_LAND or event.id == world.event.S_EVENT_RUNWAY_TOUCH then
|
||||
local unit = event.initiator
|
||||
local airbase = event.place
|
||||
local unit = event.initiator --[[@as Unit]]
|
||||
local airbase = event.place --[[@as Airbase]]
|
||||
if unit ~= nil then
|
||||
local name = unit:getName()
|
||||
if onLandEventListeners[name] then
|
||||
for _, callable in pairs(onLandEventListeners[name]) do
|
||||
local succ, err = pcall(function()
|
||||
local _, err = pcall(function()
|
||||
callable:OnUnitLanded(unit, airbase)
|
||||
end)
|
||||
if err then
|
||||
@@ -341,7 +364,7 @@ do
|
||||
event.id == world.event.S_EVENT_CRASH or
|
||||
event.id == world.event.S_EVENT_EJECTION or
|
||||
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
|
||||
logDebug("Receiving death event from: " .. object:getName())
|
||||
@@ -349,7 +372,7 @@ do
|
||||
|
||||
if object and object.getName and OnUnitLostListeners[object:getName()] then
|
||||
for _, callable in pairs(OnUnitLostListeners[object:getName()]) do
|
||||
local succ, err = pcall(function()
|
||||
local _, err = pcall(function()
|
||||
callable:OnUnitLost(object)
|
||||
end)
|
||||
|
||||
@@ -360,15 +383,11 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
if event.id == world.event.S_EVENT_EJECTION then
|
||||
|
||||
end
|
||||
|
||||
if event.id == world.event.S_EVENT_SHOT then
|
||||
|
||||
local shooter = event.initiator
|
||||
local weapon = event.weapon
|
||||
local target = event.target
|
||||
local shooter = event.initiator --[[@as Unit]]
|
||||
local weapon = event.weapon --[[@as Weapon]]
|
||||
local target = event.target --[[@as Unit?]]
|
||||
triggerWeaponFired(shooter, weapon, target)
|
||||
|
||||
end
|
||||
@@ -377,28 +396,32 @@ do
|
||||
Persistence.UpdateNow()
|
||||
end
|
||||
|
||||
---@type table<string, boolean>
|
||||
local AI_GROUPS = {}
|
||||
|
||||
local function CheckAndTriggerSpawnAsync(unit, time)
|
||||
|
||||
local function isPlayer(unit)
|
||||
if unit == nil then return false, "unit is nil" end
|
||||
if unit.getGroup == nil then return false, 'no get group function in unit object, most likely static' end
|
||||
if Object.getCategory(unit) ~= Object.Category.UNIT then
|
||||
---@param unit Unit
|
||||
---@param _ any
|
||||
local function CheckAndTriggerSpawnAsync(unit, _)
|
||||
---@param checkUnit Unit
|
||||
---@return boolean, string?
|
||||
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"
|
||||
end
|
||||
|
||||
if unit:isExist() ~= true then return false, "unit does not exist" end
|
||||
local group = unit:getGroup()
|
||||
if checkUnit:isExist() ~= true then return false, "unit does not exist" end
|
||||
local group = checkUnit:getGroup()
|
||||
if group ~= nil then
|
||||
if AI_GROUPS[group:getName()] == true then
|
||||
return false
|
||||
end
|
||||
|
||||
local players = DcsUtil.getAllPlayerUnits()
|
||||
local unitName = unit:getName()
|
||||
for i, unit in pairs(players) do
|
||||
if unit:getName() == unitName then
|
||||
local unitName = checkUnit:getName()
|
||||
for _, playerUnit in pairs(players) do
|
||||
if playerUnit:getName() == unitName then
|
||||
return true
|
||||
end
|
||||
end
|
||||
@@ -408,11 +431,8 @@ do
|
||||
end
|
||||
|
||||
if isPlayer(unit) == true then
|
||||
local groupId = unit:getGroup():getID()
|
||||
SpearheadEvents.TriggerPlayerEntersUnit(unit)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
if event.id == world.event.S_EVENT_BIRTH then
|
||||
|
||||
@@ -8,6 +8,7 @@ do --setup route util
|
||||
---@param attackHelos boolean
|
||||
---@return table
|
||||
local function GetCAPTargetTypes(attackHelos)
|
||||
---@type table<number, string>
|
||||
local targetTypes = {
|
||||
[1] = "Planes",
|
||||
}
|
||||
@@ -53,8 +54,9 @@ do --setup route util
|
||||
|
||||
---comment
|
||||
---@param groupName string
|
||||
---@param position table { x, y}
|
||||
---@param position Vec3
|
||||
---@param altitude number
|
||||
---@param deviationdistance number
|
||||
---@param speed number
|
||||
---@param duration number
|
||||
---@param engageHelos boolean
|
||||
@@ -181,7 +183,7 @@ do --setup route util
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param position table { x, y}
|
||||
---@param position Vec3
|
||||
---@param altitude number
|
||||
---@param speed number
|
||||
---@param childTasks table
|
||||
@@ -210,7 +212,8 @@ do --setup route util
|
||||
---comment
|
||||
---@param groupName string groupName you're creating this route for
|
||||
---@param airdromeId number airdromeId
|
||||
---@param capPoint table { x, z }
|
||||
---@param capPoint Vec3
|
||||
---@param racetrackSecondPoint Vec3?
|
||||
---@param altitude number
|
||||
---@param speed number
|
||||
---@param durationOnStation number
|
||||
@@ -258,7 +261,8 @@ do --setup route util
|
||||
if racetrackSecondPoint then orbitType = "Race-Track" end
|
||||
|
||||
local basePoint = base:getPoint()
|
||||
local points = {}
|
||||
---@type Array<table>
|
||||
local points
|
||||
if racetrackSecondPoint == nil then
|
||||
points = {
|
||||
[1] = FlyToPointTask(capPoint, altitude, speed, additionalFlyOverTasks),
|
||||
@@ -304,6 +308,7 @@ do --setup route util
|
||||
end
|
||||
|
||||
local group = Group.getByName(groupName)
|
||||
---@type Vec3?
|
||||
local pos;
|
||||
local i = 1
|
||||
if group == nil then
|
||||
@@ -393,6 +398,9 @@ do --setup route util
|
||||
}, ""
|
||||
end
|
||||
|
||||
---@param pointA Vec3
|
||||
---@param pointB Vec3
|
||||
---@return table, string?
|
||||
ROUTE_UTIL.CreateCarrierRacetrack = function(pointA, pointB)
|
||||
return {
|
||||
id = "Mission",
|
||||
@@ -461,7 +469,7 @@ do --setup route util
|
||||
}
|
||||
}
|
||||
}
|
||||
}, ""
|
||||
}, nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2,23 +2,14 @@ local Events = require("classes.spearhead_events")
|
||||
local Util = require("classes.util.Util")
|
||||
local DcsUtil = require("classes.util.DcsUtil")
|
||||
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 PrimaryStage = require("classes.stageClasses.Stages.PrimaryStage")
|
||||
local WaitingStage = require("classes.stageClasses.Stages.WaitingStage")
|
||||
local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper")
|
||||
|
||||
local StagesByName = {}
|
||||
|
||||
---@type table<string, Array<Stage>>
|
||||
local StagesByIndex = {}
|
||||
|
||||
---@type table<string, Array<Stage>>
|
||||
local SideStageByIndex = {}
|
||||
|
||||
---@type table<string, Array<WaitingStage>>
|
||||
local WaitingStagesByIndex = {}
|
||||
local StageRepository = require("classes.stageClasses.StageRepository")
|
||||
local StageLane = require("classes.stageClasses.StageLane")
|
||||
local Persistence = require("classes.persistence.Persistence")
|
||||
|
||||
local currentStage = -99
|
||||
|
||||
@@ -26,29 +17,41 @@ local currentStage = -99
|
||||
---@field private database Database
|
||||
---@field private logger Logger
|
||||
---@field private stageConfig StageConfig
|
||||
---@field private _stageRepository StageRepository
|
||||
---@field private _missionCommandsHelper MissionCommandsHelper
|
||||
local GlobalStageManager = {}
|
||||
GlobalStageManager.__index = GlobalStageManager
|
||||
|
||||
GlobalStageManager.getCurrentStage = function() return currentStage end
|
||||
|
||||
---@type GlobalStageManager
|
||||
local singletonInstance = nil
|
||||
|
||||
---comment
|
||||
---@param database Database
|
||||
---@param stageConfig StageConfig
|
||||
---@param logLevel LogLevel
|
||||
---@param spawnManager SpawnManager
|
||||
---@return nil
|
||||
function GlobalStageManager.NewAndStart(database, stageConfig, logLevel, spawnManager)
|
||||
---@return GlobalStageManager
|
||||
function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
|
||||
if singletonInstance ~= nil then
|
||||
return singletonInstance
|
||||
end
|
||||
|
||||
local logger = Logger.new("StageManager", logLevel)
|
||||
logger:info("Using Stage Log Level: " .. logLevel)
|
||||
local self = setmetatable({}, GlobalStageManager)
|
||||
singletonInstance = self
|
||||
|
||||
self.database = database
|
||||
self.stageConfig = stageConfig
|
||||
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
|
||||
self._stageRepository = StageRepository.getInstance()
|
||||
|
||||
self.logger = logger
|
||||
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
|
||||
|
||||
Events.AddStageNumberChangedListener(self)
|
||||
@@ -56,177 +59,281 @@ function GlobalStageManager.NewAndStart(database, stageConfig, logLevel, spawnMa
|
||||
for _, stageName in pairs(database:getStagezoneNames()) do
|
||||
logger:debug("Found stage zone with name: " .. stageName)
|
||||
|
||||
if Util.startswith(stageName, "missionstage", true) then
|
||||
local valid = true
|
||||
local split = Util.split_string(stageName, "_")
|
||||
if Util.tableLength(split) < 2 then
|
||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a order number or valid format")
|
||||
valid = false
|
||||
end
|
||||
local parseResult = self:ParseStageName(stageName)
|
||||
if parseResult.isValid == false then
|
||||
logger:warn("Stage zone with name " .. stageName .. " is not valid: " .. parseResult.invalidReason)
|
||||
else
|
||||
local initData = {
|
||||
stageZoneName = parseResult.stageZoneName,
|
||||
stageNumber = parseResult.orderNumber,
|
||||
stageDisplayName = parseResult.stageDisplayName,
|
||||
stageLaneIdentifier = parseResult.stageLaneIdentifier
|
||||
}
|
||||
|
||||
if Util.tableLength(split) < 3 then
|
||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a stage name")
|
||||
end
|
||||
if parseResult.stageType == "PrimaryStage" then
|
||||
local stage = PrimaryStage.New(database, stageConfig, logger, initData, spawnManager)
|
||||
|
||||
local orderNumber = nil
|
||||
local isSideStage = false
|
||||
if valid == true then
|
||||
local orderNumberString = string.lower(split[2])
|
||||
if Util.startswith(orderNumberString, "x") == true then
|
||||
isSideStage = true
|
||||
|
||||
orderNumberString = string.gsub(orderNumberString, "x", "")
|
||||
orderNumber = tonumber(orderNumberString)
|
||||
else
|
||||
orderNumber = tonumber(split[2])
|
||||
end
|
||||
|
||||
if orderNumber == nil then
|
||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a valid order number : " .. split[2])
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
|
||||
local stageDisplayName = split[3]
|
||||
local stagelogger = Logger.new(stageName, logLevel)
|
||||
if valid == true and orderNumber then
|
||||
|
||||
---@type StageInitData
|
||||
local initData = {
|
||||
stageDisplayName = stageDisplayName,
|
||||
stageNumber = orderNumber,
|
||||
stageZoneName = stageName,
|
||||
}
|
||||
|
||||
if isSideStage == true then
|
||||
local stage = ExtraStage.New(database, stageConfig, stagelogger, initData, spawnManager)
|
||||
stage:AddStageCompleteListener(self)
|
||||
|
||||
if SideStageByIndex[tostring(orderNumber)] == nil then SideStageByIndex[tostring(orderNumber)] = {} end
|
||||
table.insert(SideStageByIndex[tostring(orderNumber)], stage)
|
||||
else
|
||||
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
|
||||
|
||||
local waitingSecondsString = split[3]
|
||||
local waitingSeconds = tonumber(waitingSecondsString)
|
||||
if not waitingSeconds then
|
||||
MissionEditorWarnings.Add("Waiting Stage zone with name " .. stageName .. " does not have a valid amount of seconds parameter")
|
||||
valid = false
|
||||
end
|
||||
|
||||
if valid == true then
|
||||
local stagelogger = Logger.new(stageName, logLevel)
|
||||
|
||||
---@type WaitingStageInitData
|
||||
local initData = {
|
||||
stageDisplayName = "Waiting Stage " .. stageIndex,
|
||||
stageNumber = stageIndex or -99,
|
||||
stageZoneName = stageName,
|
||||
waitingSeconds = waitingSeconds --[[@as integer]]
|
||||
}
|
||||
local waitingStage = WaitingStage.New(database, stageConfig, stagelogger, initData, spawnManager)
|
||||
|
||||
if WaitingStagesByIndex[tostring(stageIndex)] == nil then
|
||||
WaitingStagesByIndex[tostring(stageIndex)] = {}
|
||||
end
|
||||
table.insert(WaitingStagesByIndex[tostring(stageIndex)], waitingStage)
|
||||
|
||||
waitingStage:AddStageCompleteListener(self)
|
||||
end
|
||||
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
|
||||
|
||||
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
|
||||
return { isValid = false, invalidReason = "Stage zone with name " ..
|
||||
stageName .. " does not have a order number or valid format" }
|
||||
end
|
||||
|
||||
local typePart = string.lower(split[1])
|
||||
|
||||
if typePart == "missionstage" then
|
||||
local orderNumberString = string.lower(split[2])
|
||||
---@type StageType
|
||||
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 = orderNumberString:sub(2)
|
||||
end
|
||||
|
||||
local orderNumber = tonumber(orderNumberString)
|
||||
if orderNumber == nil then
|
||||
return { isValid = false, invalidReason = "Stage zone with name " ..
|
||||
stageName .. " does not have a valid order number : " .. orderNumberString }
|
||||
end
|
||||
|
||||
local stageDisplayName = split[3]
|
||||
---@type StageNameParseResult
|
||||
local result = {
|
||||
isValid = true,
|
||||
orderNumber = orderNumber,
|
||||
stageLaneIdentifier = stageLaneIdentifier,
|
||||
stageDisplayName = stageDisplayName,
|
||||
stageZoneName = stageName,
|
||||
stageType = stageType
|
||||
}
|
||||
return result
|
||||
elseif typePart == "waitingstage" then
|
||||
local stageType = "WaitingStage"
|
||||
local orderNumberString = split[2]
|
||||
local orderNumber = tonumber(orderNumberString)
|
||||
if orderNumber == nil then
|
||||
return { isValid = false, invalidReason = "Waiting Stage zone with name " ..
|
||||
stageName .. " does not have a valid order number : " .. orderNumberString }
|
||||
end
|
||||
|
||||
local waitingSecondsString = split[3]
|
||||
local waitingSeconds = tonumber(waitingSecondsString)
|
||||
if waitingSeconds == nil then
|
||||
return { isValid = false, invalidReason = "Waiting Stage zone with name " ..
|
||||
stageName .. " does not have a valid amount of seconds parameter : " .. waitingSecondsString }
|
||||
end
|
||||
|
||||
local stageDisplayName = "Waiting Stage " .. orderNumber
|
||||
|
||||
---@type StageNameParseResult
|
||||
local result = {
|
||||
isValid = true,
|
||||
orderNumber = orderNumber,
|
||||
stageLaneIdentifier = nil,
|
||||
stageDisplayName = stageDisplayName,
|
||||
stageZoneName = stageName,
|
||||
stageType = stageType,
|
||||
waitingStageSeconds = waitingSeconds
|
||||
}
|
||||
return result
|
||||
end
|
||||
return { isValid = false, invalidReason = "Stage zone with name " ..
|
||||
stageName .. " has an unrecognized type: " .. typePart }
|
||||
end
|
||||
|
||||
---@param stage Stage
|
||||
function GlobalStageManager:OnStageComplete(stage)
|
||||
self.logger:debug("Receiving stage complete event from: " .. stage.zoneName)
|
||||
|
||||
local anyIncomplete = false
|
||||
self.logger:debug("Checking stages for index: " .. tostring(currentStage))
|
||||
for index, stage in pairs(StagesByIndex[tostring(currentStage)]) do
|
||||
if stage:IsComplete() == false then
|
||||
anyIncomplete = true
|
||||
self.logger:debug("Need to wait for Stage " .. stage.zoneName .. " to be completed")
|
||||
else
|
||||
self.logger:debug("Stage verified to be completed: " .. stage.zoneName)
|
||||
end
|
||||
local stageIndex = stage:GetStageIndex()
|
||||
local laneIdentifier = stage:GetStageLaneIdentifier()
|
||||
|
||||
local stageLane = self._stageRepository:getStageLane(laneIdentifier)
|
||||
if not stageLane or not stageLane:IsCurrentStageIndexComplete() then
|
||||
self.logger:debug("Stage lane " ..
|
||||
(laneIdentifier or "default") .. " is not complete for stage index " .. stageIndex)
|
||||
return
|
||||
end
|
||||
|
||||
if anyIncomplete == false and self.stageConfig.isAutoStages == true then
|
||||
|
||||
-- CHECK WAITING STAGES
|
||||
local nextStage = currentStage + 1
|
||||
local nextStageIndex = stageIndex + 1
|
||||
|
||||
if WaitingStagesByIndex[tostring(nextStage)] then
|
||||
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
|
||||
if waitingStage:IsActive() == false then
|
||||
waitingStage:ActivateStage()
|
||||
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
|
||||
-- COMPLETION IN A SIDE LANE
|
||||
|
||||
local anyWaiting = false
|
||||
if WaitingStagesByIndex[tostring(nextStage)] then
|
||||
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
|
||||
if waitingStage:IsComplete() == false then
|
||||
anyWaiting = true
|
||||
-- 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 allSideLanesReady then
|
||||
self.logger:debug("All side lanes are ready for next stage index " ..
|
||||
nextStageIndex .. ", activating default lane")
|
||||
Events.PublishStageNumberChanged(nextStageIndex, nil)
|
||||
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
|
||||
|
||||
if anyWaiting == false then
|
||||
local newStageNumber = currentStage + 1
|
||||
self.logger:debug("Setting next stage to: " .. tostring(newStageNumber))
|
||||
Events.PublishStageNumberChanged(newStageNumber)
|
||||
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)
|
||||
self.logger:debug("Stage number changed to: " .. tostring(stageNumber))
|
||||
currentStage = stageNumber
|
||||
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)
|
||||
self.logger:debug("Stage number change complete to: " .. tostring(stageNumber))
|
||||
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
|
||||
groups[group:getID()] = group
|
||||
table.insert(groups, group)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -236,83 +343,276 @@ function GlobalStageManager:OnStageNumberChangeComplete(stageNumber)
|
||||
end
|
||||
|
||||
---@private
|
||||
function GlobalStageManager:UpdateDrawings(stageNumber)
|
||||
---@param stageNumber number
|
||||
---@param stageLaneIdentifier string?
|
||||
function GlobalStageManager:UpdateDrawings(stageNumber, stageLaneIdentifier)
|
||||
self.logger:debug("Updating custom drawings for stage number: " .. tostring(stageNumber))
|
||||
local drawings = self.database:getCustomDrawings()
|
||||
local drawings = self.database:getStageDrawings()
|
||||
for _, drawing in pairs(drawings) do
|
||||
local startStage, stopStage = drawing:GetStartAndStop()
|
||||
if stageNumber >= startStage and stageNumber < stopStage then
|
||||
self.logger:debug("Drawing " .. drawing:GetName() .. " is active for stage number: " .. tostring(stageNumber))
|
||||
drawing:Draw()
|
||||
else
|
||||
self.logger:debug("Drawing " .. drawing:GetName() .. " is not active for stage number: " .. tostring(stageNumber))
|
||||
drawing:Remove()
|
||||
local startStage = drawing:GetStartingStage()
|
||||
local stopStage = drawing:GetRemoveAtStage()
|
||||
local laneIdentifier = drawing:GetStageLaneIdentifier()
|
||||
|
||||
if laneIdentifier == stageLaneIdentifier then
|
||||
if stageNumber >= startStage and stageNumber < stopStage then
|
||||
self.logger:debug("Drawing " ..
|
||||
drawing:GetName() .. " is active for stage number: " .. tostring(stageNumber))
|
||||
drawing:Draw()
|
||||
else
|
||||
self.logger:debug("Drawing " ..
|
||||
drawing:GetName() .. " is not active for stage number: " .. tostring(stageNumber))
|
||||
drawing:Remove()
|
||||
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")
|
||||
logger:info("Stage overview:")
|
||||
-- Color palette for lanes (26 vibrant colors optimized for dark mode)
|
||||
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
|
||||
local lines = {}
|
||||
for stageIndex, stages in pairs(StagesByIndex) do
|
||||
-- Build nodes and collect all stage indices per lane
|
||||
for _, lane in ipairs(lanes) do
|
||||
local laneId = lane:GetStageLaneIdentifier() or "default"
|
||||
local stageIndices = lane:GetAllStageIndices()
|
||||
stageIndicesByLane[laneId] = stageIndices
|
||||
|
||||
local totalStrike = 0
|
||||
local totalbai = 0
|
||||
local totaldead = 0
|
||||
local totalMissions = 0
|
||||
local totalCas = 0
|
||||
|
||||
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
|
||||
-- Assign color to this lane
|
||||
laneColorMap[laneId] = laneColors[colorIndex]
|
||||
colorIndex = colorIndex + 1
|
||||
if colorIndex > #laneColors then
|
||||
colorIndex = 1
|
||||
end
|
||||
|
||||
local index = tonumber(stageIndex)
|
||||
if index then
|
||||
if index > max then
|
||||
max = index
|
||||
-- Create nodes for each stage index in this lane
|
||||
for _, stageIndex in ipairs(stageIndices) do
|
||||
local stages = lane:GetStagesAtIndex(stageIndex)
|
||||
|
||||
if stages then
|
||||
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
|
||||
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
|
||||
lines[index] ="Stage# " .. tostring(stageIndex).. " | " .. totalStrike .. " strikes | " .. totaldead .. " dead | " .. totalbai .. " BAI | " .. totalCas .. " CAS | Total:" .. totalMissions
|
||||
else
|
||||
logger:warn("Stage index is not a number: " .. stageIndex)
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, max do
|
||||
if lines[i] then
|
||||
logger:info(lines[i])
|
||||
-- Add edges for sequential stages within the same lane
|
||||
for laneId, stageIndices in pairs(stageIndicesByLane) do
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
---comment
|
||||
---@param stageNumber number
|
||||
---@param stageLaneIdentifier string? nil for default lan
|
||||
---@return boolean | nil
|
||||
GlobalStageManager.isStageComplete = function (stageNumber)
|
||||
|
||||
local stageIndex = tostring(stageNumber)
|
||||
|
||||
if StagesByIndex[stageIndex] == nil then return nil end
|
||||
|
||||
for _, stage in ipairs(StagesByIndex[stageIndex]) do
|
||||
if stage:IsComplete() == false then
|
||||
return false
|
||||
end
|
||||
GlobalStageManager.isStageComplete = function(stageNumber, stageLaneIdentifier)
|
||||
if singletonInstance == nil then
|
||||
Logger.new("StageManager", "INFO"):warn(
|
||||
"GlobalStageManager.isStageComplete called before GlobalStageManager was initialized. Returning nil")
|
||||
return nil
|
||||
end
|
||||
|
||||
return true
|
||||
if stageLaneIdentifier then
|
||||
stageLaneIdentifier = string.lower(stageLaneIdentifier)
|
||||
end
|
||||
|
||||
return singletonInstance:IsStageComplete(stageNumber, stageLaneIdentifier)
|
||||
end
|
||||
|
||||
return GlobalStageManager
|
||||
|
||||
@@ -57,7 +57,7 @@ function SpearheadGroup:Spawn(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._isSpawned = true
|
||||
end
|
||||
@@ -91,9 +91,9 @@ function SpearheadGroup:GetCoalition()
|
||||
end
|
||||
|
||||
---comment
|
||||
---@return table result list of objects
|
||||
---@return Array<Unit|StaticObject> result list of objects
|
||||
function SpearheadGroup:GetObjects()
|
||||
|
||||
---@type Array<Unit|StaticObject>
|
||||
local result = {}
|
||||
if self._isStatic == true then
|
||||
local staticObject = StaticObject.getByName(self._groupName)
|
||||
|
||||
@@ -50,12 +50,6 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
|
||||
---@type table<string, Vec3>
|
||||
local redUnitsPos = {}
|
||||
|
||||
|
||||
local buildable = false
|
||||
if self._buildableCrateKilos and self._buildableCrateKilos > 0 then
|
||||
buildable = true
|
||||
end
|
||||
|
||||
for _, groupName in pairs(blueSamData.groups) do
|
||||
local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true)
|
||||
if spearheadGroup then
|
||||
@@ -78,7 +72,7 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
|
||||
|
||||
--Cleanup units
|
||||
local cleanup_distance = 5
|
||||
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
|
||||
for _, blueUnitPos in pairs(blueUnitsPos) do
|
||||
for redUnitName, redUnitPos in pairs(redUnitsPos) do
|
||||
local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos)
|
||||
if distance <= cleanup_distance then
|
||||
@@ -89,7 +83,7 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
|
||||
|
||||
local zone = DcsUtil.getZoneByName(zoneName)
|
||||
if zone then
|
||||
BuildableZone.New(self, zone, self._buildableCrateKilos or 0, "SAM_CRATE", self._blueGroups, logger, database)
|
||||
BuildableZone.New(self, zone, self._buildableCrateKilos or 0, "SAM_CRATE", self._blueGroups, logger, database, blueSamData.briefing)
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -140,7 +134,9 @@ end
|
||||
|
||||
function BlueSam:SpawnGroups()
|
||||
for unitName, needsCleanup in pairs(self._cleanupUnits) do
|
||||
DcsUtil.DestroyUnit(unitName)
|
||||
if needsCleanup then
|
||||
DcsUtil.DestroyUnit(unitName)
|
||||
end
|
||||
end
|
||||
|
||||
for _, group in pairs(self._blueGroups) do
|
||||
|
||||
@@ -64,7 +64,7 @@ function FarpZone.New(database, logger, zoneName, spawnManager)
|
||||
local zone = DcsUtil.getZoneByName(zoneName)
|
||||
if zone then
|
||||
self._logger:debug("Creating Buildable zone: " .. zoneName .. " with " .. (farpData.buildingKilos or "nil") .. " kilos")
|
||||
BuildableZone.New(self, zone, farpData.buildingKilos or 0, "FARP_CRATE", self._groups, logger, database)
|
||||
BuildableZone.New(self, zone, farpData.buildingKilos or 0, "FARP_CRATE", self._groups, logger, database, farpData.briefing)
|
||||
end
|
||||
end
|
||||
self:Deactivate()
|
||||
|
||||
@@ -90,7 +90,7 @@ function StageBase.New(databaseManager, logger, airbaseName, spawnManager)
|
||||
|
||||
local cleanup_distance = 5
|
||||
|
||||
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
|
||||
for _, blueUnitPos in pairs(blueUnitsPos) do
|
||||
for redUnitName, redUnitPos in pairs(redUnitsPos) do
|
||||
local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos)
|
||||
if distance <= cleanup_distance then
|
||||
@@ -113,6 +113,7 @@ end
|
||||
function StageBase:SpawnRedUnits()
|
||||
---comment
|
||||
---@param groups Array<SpearheadGroup>
|
||||
---@return number|nil
|
||||
local spawnAsync = function(groups)
|
||||
for _, group in pairs(groups) do
|
||||
group:Spawn()
|
||||
@@ -142,6 +143,7 @@ end
|
||||
function StageBase:SpawnBlueUnits()
|
||||
---comment
|
||||
---@param groups Array<SpearheadGroup>
|
||||
---@return number|nil
|
||||
local spawnAsync = function(groups)
|
||||
for _, group in pairs(groups) do
|
||||
group:Spawn()
|
||||
|
||||
@@ -20,7 +20,8 @@ BuildableZone.__index = BuildableZone
|
||||
---@param database Database
|
||||
---@param crateType SupplyType
|
||||
---@param logger Logger
|
||||
function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroups, logger, database)
|
||||
---@param briefing string?
|
||||
function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroups, logger, database, briefing)
|
||||
self._targetZone = targetZone
|
||||
self._requiredKilos = kilosRequired or 0
|
||||
self._buildableGroups = buildableGroups or {}
|
||||
@@ -36,6 +37,7 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
|
||||
|
||||
---@param params UnpackCrateParam
|
||||
---@param time number
|
||||
---@return number|nil
|
||||
local startUnpackingCrate = function(params, time)
|
||||
local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2)
|
||||
local alreadySpawned = params.unpackedItems / params.groupsPerKilo
|
||||
@@ -69,14 +71,12 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
|
||||
|
||||
local noLandingZone = self:GetNoLandingZone()
|
||||
if kilosRequired and kilosRequired > 0 then
|
||||
self._buildableMission = BuildableMission.new(database, logger, targetZone, noLandingZone, kilosRequired, crateType)
|
||||
self._buildableMission = BuildableMission.new(database, logger, targetZone, noLandingZone, kilosRequired, crateType, briefing)
|
||||
self._buildableMission:AddOnCrateDroppedOfListener(self)
|
||||
else
|
||||
self._buildableMission = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
if self._buildableMission == nil then
|
||||
self._buildableLogger:debug("No buildable mission for zone: " .. targetZone.name)
|
||||
end
|
||||
@@ -98,25 +98,14 @@ function BuildableZone:OnBuildingComplete() end
|
||||
---@field unpackedItems number
|
||||
---@field unpackedKilos number
|
||||
|
||||
---@param mission BuildableMission?
|
||||
---@param _ BuildableMission?
|
||||
---@param kilos number
|
||||
function BuildableZone:OnCrateDroppedOff(mission, kilos)
|
||||
function BuildableZone:OnCrateDroppedOff(_, kilos)
|
||||
self._buildableLogger:debug("Crate dropped off in zone: " .. self._targetZone.name)
|
||||
|
||||
local timeToUnpack = (kilos / 500) * 15
|
||||
|
||||
---@type UnpackCrateParam
|
||||
local params = {
|
||||
self = self,
|
||||
groupsPerKilo = self._groupsPerKilo,
|
||||
unpackedItems = 0,
|
||||
kilosPerSecond = kilos/timeToUnpack,
|
||||
unpackedKilos = 0,
|
||||
kilos = kilos
|
||||
}
|
||||
|
||||
---@param params UnpackCrateParam
|
||||
---@param time number
|
||||
---@return number|nil
|
||||
local startUnpackingCrate = function(params, time)
|
||||
local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2)
|
||||
local alreadySpawned = params.unpackedItems / params.groupsPerKilo
|
||||
@@ -135,6 +124,17 @@ function BuildableZone:OnCrateDroppedOff(mission, kilos)
|
||||
return time + 2
|
||||
end
|
||||
|
||||
local timeToUnpack = (kilos / 500) * 15
|
||||
---@type UnpackCrateParam
|
||||
local params = {
|
||||
self = self,
|
||||
groupsPerKilo = self._groupsPerKilo,
|
||||
unpackedItems = 0,
|
||||
kilosPerSecond = kilos/timeToUnpack,
|
||||
unpackedKilos = 0,
|
||||
kilos = kilos
|
||||
}
|
||||
|
||||
timer.scheduleFunction(startUnpackingCrate, params, timer.getTime() + 2)
|
||||
end
|
||||
|
||||
@@ -189,7 +189,7 @@ function BuildableZone:SpawnAmount(amount)
|
||||
return nil
|
||||
end
|
||||
|
||||
for i = 1, amount do
|
||||
for _ = 1, amount do
|
||||
local spawned = spawnOne()
|
||||
if spawned ~= true then
|
||||
self._buildableLogger:debug("No more groups to spawn in zone: " .. self._targetZone.name)
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
|
||||
|
||||
---@class StageLane
|
||||
---@field private _isDefaultStageLane boolean
|
||||
---@field private _stageLaneIdentifier string
|
||||
---@field private _activeStageIndex number
|
||||
---@field private _stagesInLaneByIndex table<string, Array<Stage>>
|
||||
---@field private _chapterStarts table<string, boolean>?
|
||||
---@field private _stageLaneState StageLaneState
|
||||
---@field private _maxStageIndex number
|
||||
local StageLane = {}
|
||||
StageLane.__index = StageLane
|
||||
|
||||
---@alias StageLaneState
|
||||
---| "InChapter"
|
||||
---| "BetweenChapters"
|
||||
---| "Completed"
|
||||
|
||||
---@type string?
|
||||
StageLane.DefaultLaneKey = nil
|
||||
|
||||
function StageLane.New(laneIdentifier)
|
||||
|
||||
if not laneIdentifier then
|
||||
laneIdentifier = StageLane.DefaultLaneKey
|
||||
end
|
||||
|
||||
local self = setmetatable({}, { __index = StageLane }) --[[@as StageLane]]
|
||||
self._isDefaultStageLane = laneIdentifier == StageLane.DefaultLaneKey
|
||||
self._stageLaneIdentifier = laneIdentifier
|
||||
self._activeStageIndex = nil
|
||||
self._stagesInLaneByIndex = {}
|
||||
self._chapterStarts = nil
|
||||
self._stageLaneState = "BetweenChapters"
|
||||
return self
|
||||
end
|
||||
|
||||
---@param stage Stage
|
||||
function StageLane:AddStage(stage)
|
||||
local stageIndex = stage:GetStageIndex()
|
||||
if not self._stagesInLaneByIndex[tostring(stageIndex)] then
|
||||
self._stagesInLaneByIndex[tostring(stageIndex)] = {}
|
||||
end
|
||||
|
||||
if not self._maxStageIndex or stageIndex > self._maxStageIndex then
|
||||
self._maxStageIndex = stageIndex
|
||||
end
|
||||
|
||||
table.insert(self._stagesInLaneByIndex[tostring(stageIndex)], stage)
|
||||
end
|
||||
|
||||
---@param stageNumber number
|
||||
---@return boolean?
|
||||
function StageLane:IsStageIndexComplete(stageNumber)
|
||||
local stages = self._stagesInLaneByIndex[tostring(stageNumber)]
|
||||
if not stages then
|
||||
return nil
|
||||
end
|
||||
|
||||
for _, stage in ipairs(stages) do
|
||||
if not stage:IsComplete() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function StageLane:IsCurrentStageIndexComplete()
|
||||
return self:IsStageIndexComplete(self._activeStageIndex) == true
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function StageLane:GetStageLaneIdentifier()
|
||||
return self._stageLaneIdentifier
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function StageLane:IsDefaultStageLane()
|
||||
return self._isDefaultStageLane
|
||||
end
|
||||
|
||||
---@return number
|
||||
function StageLane:GetActiveStageIndex()
|
||||
return self._activeStageIndex
|
||||
end
|
||||
|
||||
---@param stageNumber number
|
||||
function StageLane:SetActiveStageIndex(stageNumber)
|
||||
|
||||
if self._stagesInLaneByIndex[tostring(stageNumber)] == nil then
|
||||
self._stageLaneState = "BetweenChapters" -- stage number is not in this lane, so we are between chapters
|
||||
elseif stageNumber > self._maxStageIndex then
|
||||
self._stageLaneState = "Completed" -- stage number is beyond the max stage index
|
||||
else
|
||||
self._stageLaneState = "InChapter"
|
||||
end
|
||||
|
||||
self._activeStageIndex = stageNumber
|
||||
end
|
||||
|
||||
---@return StageLaneState
|
||||
function StageLane:GetStageLaneState()
|
||||
return self._stageLaneState
|
||||
end
|
||||
|
||||
--- Checks if the given stage number is a chapter start. <br>
|
||||
--- Chapter starts are the first StageIndex after a gap in stage numbers. <br>
|
||||
--- For example, if the stage numbers are 1, 2, 3, 5, 6, 7, then stage number 5 is a chapter start because there is a gap between 3 and 5. <br>
|
||||
---@param stageNumber number
|
||||
---@return boolean
|
||||
function StageLane:IsChapterStart(stageNumber)
|
||||
|
||||
if self._chapterStarts == nil then
|
||||
self:FillChapterStarts()
|
||||
end
|
||||
|
||||
return self._chapterStarts[tostring(stageNumber)] == true
|
||||
end
|
||||
|
||||
--- fills chapter starts, chapter starts are where there's a gap in between stage numbers.
|
||||
--- The first stage in a lane is always a chapter start.
|
||||
---@private
|
||||
function StageLane:FillChapterStarts()
|
||||
|
||||
local previousIndex = nil
|
||||
---@type table<string, number>
|
||||
local stageIndices = {}
|
||||
for stageIndex, _ in pairs(self._stagesInLaneByIndex) do
|
||||
table.insert(stageIndices, tonumber(stageIndex))
|
||||
end
|
||||
table.sort(stageIndices)
|
||||
|
||||
if self._chapterStarts == nil then self._chapterStarts = {} end
|
||||
for _, stageIndex in ipairs(stageIndices) do
|
||||
if previousIndex == nil then
|
||||
-- First stage is always a chapter start
|
||||
self._chapterStarts[tostring(stageIndex)] = true
|
||||
elseif stageIndex > previousIndex + 1 then
|
||||
-- Gap before this stage means it's a chapter start
|
||||
self._chapterStarts[tostring(stageIndex)] = true
|
||||
else
|
||||
-- Consecutive stage, not a chapter start
|
||||
self._chapterStarts[tostring(stageIndex)] = false
|
||||
end
|
||||
previousIndex = stageIndex
|
||||
end
|
||||
end
|
||||
|
||||
---@return Array<number>
|
||||
function StageLane:GetAllStageIndices()
|
||||
local indices = {}
|
||||
for stageIndex, _ in pairs(self._stagesInLaneByIndex) do
|
||||
table.insert(indices, tonumber(stageIndex))
|
||||
end
|
||||
table.sort(indices)
|
||||
return indices
|
||||
end
|
||||
|
||||
---@param stageIndex number
|
||||
---@return Array<Stage>?
|
||||
function StageLane:GetStagesAtIndex(stageIndex)
|
||||
return self._stagesInLaneByIndex[tostring(stageIndex)]
|
||||
end
|
||||
|
||||
---@return number?
|
||||
function StageLane:GetNextChapterStart()
|
||||
if self._chapterStarts == nil then
|
||||
self:FillChapterStarts()
|
||||
end
|
||||
|
||||
for stageIndex, _ in pairs(self._stagesInLaneByIndex) do
|
||||
if tonumber(stageIndex) > self._activeStageIndex and self._chapterStarts[tostring(stageIndex)] == true then
|
||||
return tonumber(stageIndex)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return StageLane
|
||||
@@ -0,0 +1,59 @@
|
||||
local StageLane = require("classes.stageClasses.StageLane")
|
||||
|
||||
---@class StageRepository
|
||||
---@field private StageLanes table<string, StageLane>
|
||||
local StageRepository = {}
|
||||
StageRepository.__index = StageRepository
|
||||
|
||||
local instance = nil
|
||||
|
||||
---@private
|
||||
---@return StageRepository
|
||||
function StageRepository.new()
|
||||
local self = setmetatable({}, StageRepository)
|
||||
self.StageLanes = {}
|
||||
self.StageLanes[tostring(StageLane.DefaultLaneKey)] = StageLane.New(StageLane.DefaultLaneKey)
|
||||
instance = self
|
||||
return self
|
||||
end
|
||||
|
||||
---@return StageRepository
|
||||
StageRepository.getInstance = function()
|
||||
if instance == nil then
|
||||
instance = StageRepository.new()
|
||||
end
|
||||
|
||||
return instance
|
||||
end
|
||||
|
||||
|
||||
---@param laneName string? `nil` for default lane
|
||||
---@return StageLane?
|
||||
function StageRepository:getStageLane(laneName)
|
||||
if not laneName then
|
||||
laneName = StageLane.DefaultLaneKey
|
||||
end
|
||||
return self.StageLanes[tostring(laneName)]
|
||||
end
|
||||
|
||||
---@return Array<StageLane>
|
||||
function StageRepository:getAllStageLanes()
|
||||
local lanes = {}
|
||||
for _, lane in pairs(self.StageLanes) do
|
||||
table.insert(lanes, lane)
|
||||
end
|
||||
return lanes
|
||||
end
|
||||
|
||||
---@param stage Stage
|
||||
function StageRepository:AddStage(stage)
|
||||
|
||||
local stageLaneIdentifier = stage:GetStageLaneIdentifier() or StageLane.DefaultLaneKey
|
||||
if self.StageLanes[tostring(stageLaneIdentifier)] == nil then
|
||||
self.StageLanes[tostring(stageLaneIdentifier)] = StageLane.New(stageLaneIdentifier)
|
||||
end
|
||||
|
||||
self.StageLanes[tostring(stageLaneIdentifier)]:AddStage(stage)
|
||||
end
|
||||
|
||||
return StageRepository
|
||||
@@ -13,12 +13,19 @@ local Events = require("classes.spearhead_events")
|
||||
local GlobalCapManager = require("classes.capClasses.GlobalCapManager")
|
||||
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
|
||||
local StageRepository = require("classes.stageClasses.StageRepository")
|
||||
local StageState = require("classes.stageClasses.Stages.BaseStage.StageState")
|
||||
|
||||
---@alias StageColor
|
||||
---| "RED"
|
||||
---| "BLUE"
|
||||
---| "GRAY"
|
||||
|
||||
---@alias StageType
|
||||
---| "PrimaryStage"
|
||||
---| "ExtraStage"
|
||||
---| "WaitingStage"
|
||||
|
||||
--- @class StageData
|
||||
--- @field stageBriefing string?
|
||||
--- @field missionsByCode table<string, Mission>
|
||||
@@ -33,9 +40,11 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
|
||||
--- @field supplyHubs Array<SupplyHub>
|
||||
|
||||
--- @class StageInitData
|
||||
--- @field type StageType
|
||||
--- @field stageZoneName string
|
||||
--- @field stageNumber integer
|
||||
--- @field stageDisplayName string
|
||||
--- @field stageLaneIdentifier string?
|
||||
|
||||
|
||||
--- @class StageCompleteListener
|
||||
@@ -45,13 +54,15 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
|
||||
--- @field zoneName string
|
||||
--- @field stageName string?
|
||||
--- @field stageNumber number
|
||||
--- @field protected _stageLaneIdentifier string?
|
||||
--- @field protected _stageType StageType
|
||||
--- @field protected _currentStageState CurrentStageState
|
||||
--- @field protected _missionCommandsHelper MissionCommandsHelper
|
||||
--- @field protected _missionPriority MissionPriority
|
||||
--- @field protected _database Database
|
||||
--- @field protected _stageRepository StageRepository
|
||||
--- @field protected _db StageData
|
||||
--- @field protected _logger Logger
|
||||
--- @field protected _activeStage integer
|
||||
--- @field protected _stageConfig StageConfig
|
||||
--- @field protected _customDrawing CustomDrawing
|
||||
--- @field protected _spawnedGroups Array<string>
|
||||
@@ -63,14 +74,6 @@ local Stage = {}
|
||||
|
||||
Stage.__index = Stage
|
||||
|
||||
---@enum CurrentStageState
|
||||
Stage.CurrentStageState = {
|
||||
INACTIVE = 0,
|
||||
PREACTIVATED = 1,
|
||||
ACTIVE = 2,
|
||||
BLUE = 3
|
||||
}
|
||||
|
||||
Stage.StageColors = {
|
||||
INVISIBLE = { 0, 0, 0, 0 },
|
||||
RED_ACTIVE = { 1, 0, 0, 0.20 },
|
||||
@@ -86,15 +89,22 @@ Stage.StageColors = {
|
||||
---@param initData StageInitData
|
||||
---@param missionPriority MissionPriority
|
||||
---@param spawnManager SpawnManager
|
||||
---@param stageType StageType
|
||||
---@return Stage
|
||||
function Stage:superNew(database, stageConfig, logger, initData, missionPriority, spawnManager)
|
||||
function Stage:superNew(database, stageConfig, logger, initData, stageType, missionPriority, spawnManager)
|
||||
|
||||
logger:debug("[BaseStage] Initiating stage with name: " .. initData.stageZoneName)
|
||||
self._currentStageState = Stage.CurrentStageState.INACTIVE
|
||||
self._currentStageState = StageState.Inactive
|
||||
self.zoneName = initData.stageZoneName
|
||||
self.stageNumber = initData.stageNumber
|
||||
self._stageRepository = StageRepository.getInstance()
|
||||
if initData.stageLaneIdentifier then
|
||||
self._stageLaneIdentifier = string.lower(initData.stageLaneIdentifier)
|
||||
else
|
||||
self._stageLaneIdentifier = nil
|
||||
end
|
||||
self.stageName = initData.stageDisplayName
|
||||
self._currentStageState = Stage.CurrentStageState.INACTIVE
|
||||
self._stageType = stageType
|
||||
|
||||
self.OnPostStageComplete = nil
|
||||
self.OnPostBlueActivated = nil
|
||||
@@ -115,7 +125,6 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority
|
||||
supplyHubs = {}
|
||||
}
|
||||
|
||||
self._activeStage = -99
|
||||
self._stageConfig = stageConfig or {}
|
||||
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
|
||||
|
||||
@@ -151,13 +160,14 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority
|
||||
self._logger:info("Initiating new Stage with name: " .. self.zoneName)
|
||||
|
||||
---comment
|
||||
---@param self Stage
|
||||
---@param selfA Stage
|
||||
---@param time number?
|
||||
self.CheckContinuousAsync = function (self, time)
|
||||
---@return number|nil
|
||||
self.CheckContinuousAsync = function (selfA, time)
|
||||
|
||||
self:CheckAndUpdateSelf()
|
||||
if self:IsComplete() == true then
|
||||
self:NotifyComplete()
|
||||
selfA:CheckAndUpdateSelf()
|
||||
if selfA:IsComplete() == true then
|
||||
selfA:NotifyComplete()
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -273,29 +283,61 @@ end
|
||||
|
||||
---@return boolean
|
||||
function Stage:IsComplete()
|
||||
if self._currentStageState == Stage.CurrentStageState.BLUE then return true end
|
||||
if self._currentStageState >= StageState.Blue then return true end
|
||||
|
||||
for i, mission in pairs(self._db.sams) do
|
||||
for _, mission in pairs(self._db.sams) do
|
||||
local state = mission:getState()
|
||||
if state == "ACTIVE" or state == "NEW" or state =="WAITING" then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for i, mission in pairs(self._db.missions) do
|
||||
for _, mission in pairs(self._db.missions) do
|
||||
local state = mission:getState()
|
||||
if state == "ACTIVE" or state == "NEW" then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
self._currentStageState = Stage.CurrentStageState.BLUE
|
||||
return true
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function Stage:IsActive()
|
||||
return self._currentStageState == Stage.CurrentStageState.ACTIVE
|
||||
return self._currentStageState == StageState.Activated
|
||||
end
|
||||
|
||||
---@return StageType
|
||||
function Stage:GetStageType()
|
||||
return self._stageType
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function Stage:GetStageLaneIdentifier()
|
||||
return self._stageLaneIdentifier
|
||||
end
|
||||
|
||||
---@return number
|
||||
function Stage:GetStageIndex()
|
||||
return self.stageNumber
|
||||
end
|
||||
|
||||
---@return string
|
||||
function Stage:GetStageName()
|
||||
return self.stageName
|
||||
end
|
||||
|
||||
---@return Array<Mission>
|
||||
function Stage:GetMissions()
|
||||
---@type Array<Mission>
|
||||
local missions = {}
|
||||
for _, mission in pairs(self._db.missions) do
|
||||
table.insert(missions, mission)
|
||||
end
|
||||
|
||||
for _, sam in pairs(self._db.sams) do
|
||||
table.insert(missions, sam)
|
||||
end
|
||||
return missions
|
||||
end
|
||||
|
||||
---comment
|
||||
@@ -331,9 +373,9 @@ function Stage:CheckAndUpdateSelf()
|
||||
local availableMissionsCount = Util.tableLength(getAvailableMissions())
|
||||
local activeCount = getActiveMissionsCount()
|
||||
if activeCount < max and availableMissionsCount > 0 then
|
||||
for i = activeCount+1, max do
|
||||
for _ = activeCount+1, max do
|
||||
if availableMissionsCount == 0 then
|
||||
i = max+1 --exits this loop
|
||||
break
|
||||
else
|
||||
local mission = Util.randomFromList(getAvailableMissions()) --[[@as Mission]]
|
||||
if mission then
|
||||
@@ -358,7 +400,7 @@ function Stage:IsMissionComplete(missionName)
|
||||
return mission:getState() == "COMPLETED"
|
||||
end
|
||||
|
||||
---private use only
|
||||
-- private usage only
|
||||
function Stage:NotifyComplete()
|
||||
|
||||
self._logger:info("Stage complete: " .. (self.stageName or self.stageNumber or "unknown"))
|
||||
@@ -381,19 +423,28 @@ end
|
||||
|
||||
---Activates all SAMS, Airbase units etc all at once.
|
||||
function Stage:PreActivate()
|
||||
if self._currentStageState == Stage.CurrentStageState.INACTIVE then
|
||||
self._currentStageState = Stage.CurrentStageState.PREACTIVATED
|
||||
for key, mission in pairs(self._db.sams) do
|
||||
if mission then
|
||||
mission:SpawnInactive()
|
||||
end
|
||||
end
|
||||
|
||||
for _, airbase in pairs(self._db.airbases) do
|
||||
airbase:ActivateRedStage()
|
||||
if self._currentStageState >= StageState.PreActivated then
|
||||
return
|
||||
end
|
||||
|
||||
self._currentStageState = StageState.PreActivated
|
||||
for _, mission in pairs(self._db.sams) do
|
||||
if mission then
|
||||
mission:SpawnInactive()
|
||||
end
|
||||
end
|
||||
|
||||
for _, mission in pairs(self._db.missions) do
|
||||
if mission and mission.missionType == "DEEPSTRIKE" then
|
||||
mission:SpawnInactive() --Not Really "inactive"
|
||||
end
|
||||
end
|
||||
|
||||
for _, airbase in pairs(self._db.airbases) do
|
||||
airbase:ActivateRedStage()
|
||||
end
|
||||
|
||||
self:MarkStage()
|
||||
|
||||
end
|
||||
@@ -407,22 +458,22 @@ function Stage:MarkStage()
|
||||
end
|
||||
|
||||
if self._stageConfig.isDrawStagesEnabled == false then return end
|
||||
if self._stageConfig.isDrawPreActivatedEnabled == false and self._currentStageState == Stage.CurrentStageState.PREACTIVATED then
|
||||
if self._stageConfig.isDrawPreActivatedEnabled == false and self._currentStageState == StageState.PreActivated then
|
||||
return
|
||||
end
|
||||
|
||||
if self._customDrawing then
|
||||
self._customDrawing:UpdateDrawingObject(function(drawingObject)
|
||||
local drawing = drawingObject --[[@as Polygon]]
|
||||
if self._currentStageState == Stage.CurrentStageState.ACTIVE then
|
||||
if self._currentStageState == StageState.Activated then
|
||||
drawing.fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.RED_ACTIVE)
|
||||
drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.RED_ACTIVE)
|
||||
drawing.style = "dot dash"
|
||||
elseif self._currentStageState == Stage.CurrentStageState.PREACTIVATED then
|
||||
elseif self._currentStageState == StageState.PreActivated then
|
||||
drawing.fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.RED_PREACTIVE)
|
||||
drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.RED_PREACTIVE)
|
||||
drawing.style = "no line"
|
||||
elseif self._currentStageState == Stage.CurrentStageState.BLUE then
|
||||
elseif self._currentStageState == StageState.Blue then
|
||||
drawing.fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.BLUE)
|
||||
drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.BLUE)
|
||||
drawing.style = "two dash"
|
||||
@@ -440,10 +491,13 @@ function Stage:MarkStage()
|
||||
end
|
||||
|
||||
function Stage:ActivateStage()
|
||||
self._currentStageState = Stage.CurrentStageState.ACTIVE
|
||||
|
||||
if self._currentStageState >= StageState.Activated then
|
||||
return
|
||||
end
|
||||
|
||||
self:PreActivate()
|
||||
|
||||
self._currentStageState = StageState.Activated
|
||||
pcall(function()
|
||||
self:MarkStage()
|
||||
end)
|
||||
@@ -471,6 +525,10 @@ function Stage:ActivateStage()
|
||||
end
|
||||
end
|
||||
|
||||
if self._db and self._db.stageBriefing then
|
||||
self._missionCommandsHelper:AddStageBriefing(self.zoneName, self._db.stageBriefing)
|
||||
end
|
||||
|
||||
timer.scheduleFunction(self.CheckContinuousAsync, self, timer.getTime() + 3)
|
||||
end
|
||||
|
||||
@@ -483,52 +541,68 @@ end
|
||||
---comment
|
||||
---@param self Stage
|
||||
---@param number integer
|
||||
function Stage:OnStageNumberChanged(number)
|
||||
---@param stageLaneIdentifier string?
|
||||
function Stage:OnStageNumberChanged(number, stageLaneIdentifier)
|
||||
|
||||
if self._activeStage == number then --only activate once for a stage
|
||||
return
|
||||
---@return boolean
|
||||
local needsPreActivation = function()
|
||||
if self._stageLaneIdentifier == stageLaneIdentifier then
|
||||
if self.stageNumber <= number + self._stageConfig.AmountPreactivateStage then
|
||||
return true
|
||||
end
|
||||
elseif self._stageLaneIdentifier == nil then
|
||||
--main lane
|
||||
--Check if this stage is a chapter stage
|
||||
if self._stageRepository:getStageLane(self._stageLaneIdentifier):IsChapterStart(self.stageNumber)
|
||||
and self.stageNumber < number + self._stageConfig.AmountPreactivateStage
|
||||
then
|
||||
return true
|
||||
end
|
||||
|
||||
if GlobalCapManager.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local previousActive = self._activeStage
|
||||
self._activeStage = number
|
||||
---@return boolean
|
||||
local needsActivation = function()
|
||||
if self._stageLaneIdentifier == stageLaneIdentifier and self.stageNumber == number then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then
|
||||
self._logger:debug("Pre-activating stage: " .. self.zoneName .. " with number: " .. number)
|
||||
self:PreActivate()
|
||||
elseif GlobalCapManager.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
|
||||
---@return boolean
|
||||
local needsBlueActivation = function()
|
||||
if self._stageLaneIdentifier == stageLaneIdentifier and self.stageNumber < number then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
if needsPreActivation() == true then
|
||||
self:PreActivate()
|
||||
end
|
||||
|
||||
if number == self.stageNumber then
|
||||
if needsActivation() == true then
|
||||
self:ActivateStage()
|
||||
|
||||
if self._db and self._db.stageBriefing then
|
||||
self._missionCommandsHelper:AddStageBriefing(self.zoneName, self._db.stageBriefing)
|
||||
end
|
||||
end
|
||||
|
||||
if previousActive <= self.stageNumber then
|
||||
if number > self.stageNumber then
|
||||
self:ActivateBlueStage()
|
||||
end
|
||||
if needsBlueActivation() == true then
|
||||
self:ActivateBlueStage()
|
||||
end
|
||||
|
||||
if number > self.stageNumber then
|
||||
self._missionCommandsHelper:RemoveStageBriefing(self.zoneName)
|
||||
end
|
||||
end
|
||||
|
||||
function Stage:GetBriefing()
|
||||
return "Briefing For "
|
||||
end
|
||||
|
||||
---@param self Stage
|
||||
---@param mission Mission
|
||||
Stage.OnMissionComplete = function(self, mission)
|
||||
---@param _ Mission
|
||||
Stage.OnMissionComplete = function(self, _)
|
||||
self:CheckAndUpdateSelf()
|
||||
end
|
||||
|
||||
|
||||
---private use only
|
||||
function Stage:ActivateBlueGroups()
|
||||
|
||||
@@ -580,7 +654,7 @@ function Stage:GetStageStats()
|
||||
end
|
||||
end
|
||||
|
||||
for _, mission in pairs(self._db.sams) do
|
||||
for _, _ in pairs(self._db.sams) do
|
||||
dead = dead + 1
|
||||
end
|
||||
|
||||
@@ -590,8 +664,12 @@ end
|
||||
|
||||
function Stage:ActivateBlueStage()
|
||||
|
||||
if self._currentStageState >= StageState.Blue then
|
||||
return
|
||||
end
|
||||
|
||||
self._logger:debug("Setting stage '" .. Util.toString(self.zoneName) .. "' to blue")
|
||||
self._currentStageState = Stage.CurrentStageState.BLUE
|
||||
self._currentStageState = StageState.Blue
|
||||
for _, mission in pairs(self._db.missions) do
|
||||
mission:SpawnPersistedState()
|
||||
end
|
||||
@@ -604,17 +682,20 @@ function Stage:ActivateBlueStage()
|
||||
miscGroup:Spawn()
|
||||
end
|
||||
|
||||
---@param self Stage
|
||||
local ActivateBlueAsync = function(self)
|
||||
---@param selfA Stage
|
||||
---@return number|nil
|
||||
local ActivateBlueAsync = function(selfA)
|
||||
pcall(function()
|
||||
self:MarkStage()
|
||||
selfA:MarkStage()
|
||||
end)
|
||||
|
||||
self:ActivateBlueGroups()
|
||||
selfA:ActivateBlueGroups()
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
self._missionCommandsHelper:RemoveStageBriefing(self.zoneName)
|
||||
|
||||
timer.scheduleFunction(ActivateBlueAsync, self, timer.getTime() + 3)
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
--- StageState enumeration for the current stage state.
|
||||
--- Chronological order, meaning that higher values should represent later stages in the sequence.
|
||||
---@enum CurrentStageState
|
||||
local StageState = {
|
||||
Inactive = 0,
|
||||
PreActivated = 1,
|
||||
Activated = 2,
|
||||
Blue = 3
|
||||
}
|
||||
|
||||
return StageState
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
local Stage = require("classes.stageClasses.Stages.BaseStage.Stage")
|
||||
local GlobalCapManager = require("classes.capClasses.GlobalCapManager")
|
||||
local StageState = require("classes.stageClasses.Stages.BaseStage.StageState")
|
||||
|
||||
---@class ExtraStage : Stage
|
||||
local ExtraStage = {}
|
||||
@@ -19,10 +20,9 @@ function ExtraStage.New(database, stageConfig, logger, initData, spawnManager)
|
||||
setmetatable(ExtraStage, Stage)
|
||||
|
||||
local self = setmetatable({}, { __index = ExtraStage }) --[[@as ExtraStage]]
|
||||
self:superNew(database, stageConfig, logger, initData, "secondary", spawnManager)
|
||||
self:superNew(database, stageConfig, logger, initData, "ExtraStage", "secondary", spawnManager)
|
||||
|
||||
self.OnPostBlueActivated = function (selfStage)
|
||||
|
||||
selfStage:MarkStage()
|
||||
end
|
||||
|
||||
@@ -36,13 +36,17 @@ end
|
||||
---comment
|
||||
---@param self ExtraStage
|
||||
---@param number integer
|
||||
function ExtraStage:OnStageNumberChanged(number)
|
||||
---@param stageLaneIdentifier string?
|
||||
function ExtraStage:OnStageNumberChanged(number, stageLaneIdentifier)
|
||||
|
||||
if stageLaneIdentifier ~= self._stageLaneIdentifier then
|
||||
return
|
||||
end
|
||||
|
||||
if self._activeStage == number then --only activate once for a stage
|
||||
return
|
||||
end
|
||||
|
||||
local previousActive = self._activeStage
|
||||
self._activeStage = number
|
||||
|
||||
if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then
|
||||
@@ -56,7 +60,7 @@ function ExtraStage:OnStageNumberChanged(number)
|
||||
self:ActivateStage()
|
||||
end
|
||||
|
||||
if self._currentStageState == Stage.CurrentStageState.BLUE then
|
||||
if self._currentStageState == StageState.Blue then
|
||||
self:ActivateBlueStage()
|
||||
end
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ function PrimaryStage.New(database, stageConfig, logger, initData, spawnManager)
|
||||
setmetatable(PrimaryStage, Stage)
|
||||
|
||||
local self = setmetatable({}, { __index = PrimaryStage }) --[[@as PrimaryStage]]
|
||||
self:superNew(database, stageConfig, logger, initData, "primary", spawnManager)
|
||||
self:superNew(database, stageConfig, logger, initData, "PrimaryStage", "primary", spawnManager)
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
@@ -4,28 +4,24 @@ local Stage = require("classes.stageClasses.Stages.BaseStage.Stage")
|
||||
---@field private _waitTimeSeconds integer
|
||||
---@field private _startTime number
|
||||
local WaitingStage = {}
|
||||
|
||||
WaitingStage.__index = WaitingStage
|
||||
|
||||
---@class WaitingStageInitData : StageInitData
|
||||
---@field waitingSeconds integer
|
||||
local WaitingStageInitData = {}
|
||||
|
||||
---comment
|
||||
---@param database Database
|
||||
---@param stageConfig StageConfig
|
||||
---@param logger any
|
||||
---@param initData WaitingStageInitData
|
||||
---@param initData StageInitData
|
||||
---@param spawnManager SpawnManager
|
||||
---@param waitingSeconds integer
|
||||
---@return WaitingStage
|
||||
function WaitingStage.New(database, stageConfig, logger, initData, spawnManager)
|
||||
function WaitingStage.New(database, stageConfig, logger, initData, waitingSeconds, spawnManager)
|
||||
setmetatable(WaitingStage, Stage)
|
||||
|
||||
local self = setmetatable({}, { __index = WaitingStage }) --[[@as WaitingStage]]
|
||||
self:superNew(database, stageConfig, logger, initData, "none", spawnManager)
|
||||
self:superNew(database, stageConfig, logger, initData, "WaitingStage", "none", spawnManager)
|
||||
|
||||
self._waitTimeSeconds = 5
|
||||
if initData.waitingSeconds and initData.waitingSeconds > 5 then self._waitTimeSeconds = initData.waitingSeconds end
|
||||
if waitingSeconds and waitingSeconds > 5 then self._waitTimeSeconds = waitingSeconds end
|
||||
self._startTime = nil
|
||||
|
||||
self.CheckContinuousAsync = function (selfA, time)
|
||||
@@ -60,7 +56,7 @@ function WaitingStage:OnStageNumberChanged()
|
||||
self._logger:debug("Waiting Stage OnStageNumberChanged override")
|
||||
end
|
||||
|
||||
function WaitingStage:MarkStage(stageColor)
|
||||
function WaitingStage:MarkStage(_)
|
||||
self._logger:debug("Waiting Stage MarkStage override")
|
||||
end
|
||||
|
||||
|
||||
@@ -1,38 +1,56 @@
|
||||
|
||||
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
|
||||
local Util = require("classes.util.Util")
|
||||
local Logger = require("classes.util.Logger")
|
||||
|
||||
|
||||
local drawingLogger = Logger.new("CustomDrawing")
|
||||
|
||||
|
||||
---@class CustomDrawing
|
||||
---@field private _id integer?
|
||||
---@field private _drawingObject DrawingObject
|
||||
---@field private _startingStage number
|
||||
---@field private _removeAtStage number
|
||||
---@field private _logger Logger
|
||||
---@field protected _IDs Array<integer>?
|
||||
---@field protected _name string?
|
||||
---@field protected _drawingObject DrawingObject
|
||||
local CustomDrawing = {}
|
||||
CustomDrawing.__index = CustomDrawing
|
||||
|
||||
|
||||
---@protected
|
||||
---@param drawingObject DrawingObject
|
||||
---@return CustomDrawing
|
||||
---@return CustomDrawing?
|
||||
function CustomDrawing.New(drawingObject)
|
||||
local self = setmetatable({}, CustomDrawing)
|
||||
self._drawingObject = drawingObject
|
||||
self._id = nil -- initiate ID as nil, when drawn it will be set to the ID returned by DrawingHelper.Draw
|
||||
self._logger = drawingLogger
|
||||
|
||||
local name = drawingObject.name
|
||||
local split = Util.split_string(name or "", "_")
|
||||
local secondPart = split[2] or "1"
|
||||
local splitPart = Util.split_string(secondPart, ":")
|
||||
self._startingStage = tonumber(splitPart[1]) or 1
|
||||
self._removeAtStage = tonumber(splitPart[2]) or math.huge
|
||||
self._IDs = {} -- initiate IDs as an empty array, when drawn it will be set to the IDs returned by DrawingHelper.Draw
|
||||
self._name = drawingObject.name
|
||||
return self
|
||||
end
|
||||
|
||||
---@param points Array<Vec2>
|
||||
---@param colorString string
|
||||
---@param lineStyle LineType
|
||||
---@param lineThickness number
|
||||
---@return CustomDrawing?
|
||||
function CustomDrawing.FromPoints(points, colorString, lineStyle, lineThickness)
|
||||
if points == nil or #points < 3 then
|
||||
drawingLogger:warn("CustomDrawing.FromPoints called with nil or less than 3 points")
|
||||
return nil
|
||||
end
|
||||
|
||||
---@type FreeLine
|
||||
local drawingObject = {
|
||||
mapX = 0,
|
||||
mapY = 0,
|
||||
points = points,
|
||||
lineMode = "free",
|
||||
name = "custom_drawing_" .. tostring(math.random(1000000)),
|
||||
primitiveType = "Polygon",
|
||||
polygonMode = "free",
|
||||
visible = true,
|
||||
style = DrawingHelper.ToLineStyleString(lineStyle),
|
||||
colorString = colorString,
|
||||
thickness = lineThickness,
|
||||
closed = false
|
||||
}
|
||||
|
||||
return CustomDrawing.New(drawingObject)
|
||||
end
|
||||
|
||||
---@param zone SpearheadTriggerZone
|
||||
---@param colorString string
|
||||
---@param fillColorString string
|
||||
@@ -65,7 +83,6 @@ function CustomDrawing.FromZone(zone, colorString, fillColorString, lineStyle, l
|
||||
end
|
||||
|
||||
if zone.zone_type == "Polygon" then
|
||||
|
||||
---@type Free
|
||||
local drawingObject = {
|
||||
mapX = 0,
|
||||
@@ -90,27 +107,25 @@ function CustomDrawing:GetName()
|
||||
return self._drawingObject.name
|
||||
end
|
||||
|
||||
---@return number start
|
||||
---@return number stop
|
||||
function CustomDrawing:GetStartAndStop()
|
||||
return self._startingStage, self._removeAtStage
|
||||
end
|
||||
|
||||
function CustomDrawing:Draw()
|
||||
self._logger:debug("Drawing custom drawing with ID " .. tostring(self._id))
|
||||
drawingLogger:debug("Drawing custom drawing with IDs " .. table.concat(self._IDs, ", "))
|
||||
|
||||
if self._id ~= nil then
|
||||
DrawingHelper.Remove(self._id)
|
||||
if self._IDs ~= nil then
|
||||
for _, id in ipairs(self._IDs) do
|
||||
DrawingHelper.Remove(id)
|
||||
end
|
||||
end
|
||||
|
||||
self._id = DrawingHelper.Draw(self._drawingObject, self._logger)
|
||||
self._IDs = DrawingHelper.Draw(self._drawingObject)
|
||||
end
|
||||
|
||||
function CustomDrawing:Remove()
|
||||
if self._id ~= nil then
|
||||
self._logger:debug("Removing custom drawing with ID " .. tostring(self._id))
|
||||
DrawingHelper.Remove(self._id)
|
||||
self._id = nil
|
||||
if self._IDs ~= nil then
|
||||
for _, id in ipairs(self._IDs) do
|
||||
drawingLogger:debug("Removing custom drawing with ID " .. tostring(id))
|
||||
DrawingHelper.Remove(id)
|
||||
end
|
||||
self._IDs = {}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||
local Util = require("classes.util.Util")
|
||||
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
|
||||
---@class StageDrawing : CustomDrawing
|
||||
---@field private _startingStage number
|
||||
---@field private _removeAtStage number
|
||||
---@field private _stageLaneIdentifier string?
|
||||
local StageDrawing = {}
|
||||
StageDrawing.__index = StageDrawing
|
||||
setmetatable(StageDrawing, CustomDrawing)
|
||||
|
||||
|
||||
---@param drawingObject DrawingObject
|
||||
---@return StageDrawing?
|
||||
function StageDrawing.New(drawingObject)
|
||||
|
||||
local super = CustomDrawing.New(drawingObject)
|
||||
if not super then
|
||||
return nil
|
||||
end
|
||||
local self = setmetatable(super, StageDrawing) --[[@as StageDrawing]]
|
||||
|
||||
local split = Util.split_string(self._name or "", "_")
|
||||
local secondPart = split[2] or "1"
|
||||
local splitPart = Util.split_string(secondPart, ":")
|
||||
if tonumber(splitPart[1]) == nil then
|
||||
local laneIdentifier = string.sub(splitPart[1], 1, 1)
|
||||
if tonumber(laneIdentifier) == nil then
|
||||
self._stageLaneIdentifier = laneIdentifier
|
||||
end
|
||||
local startNumber = string.sub(splitPart[1], 2)
|
||||
local startNumberVal = tonumber(startNumber)
|
||||
if startNumberVal == nil then
|
||||
MissionEditorWarnings.Add("Start number for " ..
|
||||
self._name .. " is not a valid number: " .. tostring(startNumber))
|
||||
return nil
|
||||
end
|
||||
self._startingStage = startNumberVal
|
||||
else
|
||||
local startNumber = tonumber(splitPart[1])
|
||||
if startNumber == nil then
|
||||
MissionEditorWarnings.Add("Start number for " ..
|
||||
self._name .. " is not a valid number: " .. tostring(splitPart[1]))
|
||||
return nil
|
||||
end
|
||||
self._startingStage = startNumber
|
||||
end
|
||||
|
||||
if tonumber(splitPart[2]) == nil then
|
||||
local laneIdentifier = string.sub(splitPart[2], 1, 1)
|
||||
if tonumber(laneIdentifier) == nil then
|
||||
if self._stageLaneIdentifier and self._stageLaneIdentifier ~= laneIdentifier then
|
||||
MissionEditorWarnings.Add("Lane identifiers do not match for " ..
|
||||
self._name ..
|
||||
": " ..
|
||||
self._stageLaneIdentifier ..
|
||||
" vs " .. laneIdentifier .. ". Will only use stage " .. self._stageLaneIdentifier)
|
||||
end
|
||||
end
|
||||
local stopNumber = string.sub(splitPart[2], 2)
|
||||
local stopNumberVal = tonumber(stopNumber)
|
||||
if stopNumberVal == nil then
|
||||
MissionEditorWarnings.Add("Stop number for " ..
|
||||
self._name .. " is not a valid number: " .. tostring(stopNumber))
|
||||
return nil
|
||||
end
|
||||
self._removeAtStage = stopNumberVal
|
||||
else
|
||||
local stopNumber = tonumber(splitPart[2])
|
||||
if stopNumber == nil then
|
||||
MissionEditorWarnings.Add("Stop number for " ..
|
||||
self._name .. " is not a valid number: " .. tostring(splitPart[2]))
|
||||
return nil
|
||||
end
|
||||
self._removeAtStage = stopNumber
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function StageDrawing:GetStageLaneIdentifier()
|
||||
return self._stageLaneIdentifier
|
||||
end
|
||||
|
||||
function StageDrawing:GetStartingStage()
|
||||
return self._startingStage
|
||||
end
|
||||
|
||||
function StageDrawing:GetRemoveAtStage()
|
||||
return self._removeAtStage
|
||||
end
|
||||
|
||||
return StageDrawing
|
||||
@@ -1,57 +1,44 @@
|
||||
local Util = require("classes.util.Util")
|
||||
local DcsUtil = require("classes.util.DcsUtil")
|
||||
local Logger = require("classes.util.Logger")
|
||||
local GlobalConfig = require("classes.configuration.GlobalConfig")
|
||||
|
||||
---@type LogLevel
|
||||
local level = "INFO"
|
||||
if GlobalConfig:isDebugEnabled() then
|
||||
if GlobalConfig.New():isDebugEnabled() then
|
||||
level = "DEBUG"
|
||||
end
|
||||
|
||||
local drawingLogger = Logger.new("DrawingHelper", level)
|
||||
local logger = Logger.new("DrawingHelper", level)
|
||||
|
||||
---@class DrawingHelper
|
||||
local DrawingHelper = {}
|
||||
DrawingHelper.__index = DrawingHelper
|
||||
|
||||
|
||||
local customDrawingIdIncrementer = 4210
|
||||
|
||||
---@param object DrawingObject
|
||||
---@param logger Logger?
|
||||
---@return integer? id
|
||||
function DrawingHelper.Draw(object, logger)
|
||||
---@return Array<integer> id
|
||||
function DrawingHelper.Draw(object)
|
||||
if object == nil then
|
||||
if logger then
|
||||
logger:warn("DrawingHelper.Draw called with nil object")
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local id = DrawingHelper.GetAndAddId()
|
||||
|
||||
if logger then
|
||||
logger:debug("Drawing object with ID " .. tostring(id) .. " and primitive type: " .. tostring(object.primitiveType))
|
||||
return {}
|
||||
end
|
||||
|
||||
if(object.primitiveType == "Polygon") then
|
||||
DrawingHelper.DrawPolygon(object--[[@as Polygon]], id, logger)
|
||||
return DrawingHelper.DrawPolygon(object--[[@as Polygon]])
|
||||
elseif(object.primitiveType == "Line") then
|
||||
DrawingHelper.DrawLine(object--[[@as Line]], id, logger)
|
||||
return DrawingHelper.DrawLine(object--[[@as Line]])
|
||||
elseif(object.primitiveType == "TextBox") then
|
||||
DrawingHelper.DrawTextBox(object--[[@as TextBox]], id)
|
||||
return DrawingHelper.DrawTextBox(object--[[@as TextBox]])
|
||||
else
|
||||
if logger then
|
||||
logger:warn("Unknown primitive type: " .. tostring(object.primitiveType))
|
||||
end
|
||||
logger:warn("Unknown primitive type: " .. tostring(object.primitiveType))
|
||||
end
|
||||
|
||||
return id
|
||||
return {}
|
||||
end
|
||||
|
||||
function DrawingHelper.GetAndAddId()
|
||||
customDrawingIdIncrementer = customDrawingIdIncrementer + 1
|
||||
return customDrawingIdIncrementer
|
||||
return DcsUtil.GetNextDrawID()
|
||||
end
|
||||
|
||||
---@private
|
||||
@@ -62,13 +49,12 @@ end
|
||||
---@param lineColor table
|
||||
---@param lineStyle LineType
|
||||
---@param lineThickness number
|
||||
---@param logger Logger?
|
||||
local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineStyle, lineThickness, logger)
|
||||
local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineStyle, lineThickness)
|
||||
|
||||
if lineThickness == nil or lineThickness <= 0 then
|
||||
lineStyle = 0
|
||||
end
|
||||
|
||||
---@type string
|
||||
local functionString = "trigger.action.markupToAll(" .. shapeID .. ", -1, " .. drawID .. ","
|
||||
|
||||
for _, point in pairs(points) do
|
||||
@@ -76,23 +62,19 @@ local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineS
|
||||
end
|
||||
functionString = functionString .. "{0,1,0,1}, {0,1,0,1}, " .. lineStyle .. ")"
|
||||
|
||||
if logger then
|
||||
logger:debug("Drawing complex drawing with ID " .. tostring(drawID) .. " and function string: " .. functionString)
|
||||
end
|
||||
logger:debug("Drawing complex drawing with ID " .. tostring(drawID) .. " and function string: " .. functionString)
|
||||
|
||||
---@diagnostic disable-next-line: deprecated
|
||||
local f, err = loadstring(functionString)
|
||||
if f then
|
||||
f()
|
||||
else
|
||||
env.error("Something failed when drawing complex drawing" .. err)
|
||||
logger:error("Something failed when drawing complex drawing" .. err)
|
||||
end
|
||||
|
||||
if logger then
|
||||
logger:debug("Drawing with fill color: " .. table.concat(fillColor, ",") .. " and line color: " .. table.concat(lineColor, ",") .. " and line style: " .. tostring(lineStyle) .. " and line thickness: " .. tostring(lineThickness))
|
||||
if fillColor then
|
||||
trigger.action.setMarkupColorFill(drawID, fillColor)
|
||||
end
|
||||
|
||||
trigger.action.setMarkupColorFill(drawID, fillColor)
|
||||
trigger.action.setMarkupColor(drawID, lineColor)
|
||||
trigger.action.setMarkupTypeLine(drawID, lineStyle)
|
||||
|
||||
@@ -100,13 +82,14 @@ end
|
||||
|
||||
---@private
|
||||
---@param object Polygon
|
||||
---@param id integer
|
||||
---@param logger Logger?
|
||||
function DrawingHelper.DrawPolygon(object, id, logger)
|
||||
---@return Array<integer>
|
||||
function DrawingHelper.DrawPolygon(object)
|
||||
if object == nil then
|
||||
return
|
||||
return {}
|
||||
end
|
||||
|
||||
local id = DrawingHelper.GetAndAddId()
|
||||
|
||||
---@param circle Circle
|
||||
local function DrawCircle(circle)
|
||||
local vec3 = { x = circle.mapX, y = 0, z = circle.mapY }
|
||||
@@ -117,8 +100,8 @@ function DrawingHelper.DrawPolygon(object, id, logger)
|
||||
end
|
||||
|
||||
---@param oval Oval
|
||||
---@param logger Logger?
|
||||
local function DrawOval(oval, logger)
|
||||
---@return integer?, Array<CustomDrawing>?
|
||||
local function DrawOval(oval)
|
||||
---@type Array<Vec3>
|
||||
local points = {}
|
||||
local pointsNo = 30
|
||||
@@ -134,23 +117,22 @@ function DrawingHelper.DrawPolygon(object, id, logger)
|
||||
local y = oval.mapY + (oval.r2 * math.sin(angle))
|
||||
table.insert(points, { x = x, y = 0, z = y } )
|
||||
end
|
||||
MarkupToAll(7, id, points, fillColor, color, lineStyle, oval.thickness, logger)
|
||||
MarkupToAll(7, id, points, fillColor, color, lineStyle, oval.thickness)
|
||||
end
|
||||
|
||||
---@param free Free
|
||||
---@param logger Logger?
|
||||
local function DrawFree(free, logger)
|
||||
local function DrawFree(free)
|
||||
local fillColor = DrawingHelper.ColorToColorTable(free.fillColorString)
|
||||
local color = DrawingHelper.ColorToColorTable(free.colorString)
|
||||
local lineStyle = DrawingHelper.ToLineStyleInteger(free.style)
|
||||
|
||||
---@type Array<number>
|
||||
local keys = {}
|
||||
for k, _ in pairs(free.points) do
|
||||
table.insert(keys, k)
|
||||
end
|
||||
|
||||
table.sort(keys, function(a, b) return a < b end)
|
||||
|
||||
---@type Array<Vec3>
|
||||
local points = {}
|
||||
for _, k in ipairs(keys) do
|
||||
local point = free.points[k]
|
||||
@@ -163,7 +145,7 @@ function DrawingHelper.DrawPolygon(object, id, logger)
|
||||
end
|
||||
end
|
||||
|
||||
MarkupToAll(7, id, points, fillColor, color, lineStyle, free.thickness, logger)
|
||||
MarkupToAll(7, id, points, fillColor, color, lineStyle, free.thickness)
|
||||
end
|
||||
|
||||
---@param rect Rect
|
||||
@@ -175,19 +157,16 @@ function DrawingHelper.DrawPolygon(object, id, logger)
|
||||
local pointA = { x = rect.mapX, y = 0, z = rect.mapY }
|
||||
local pointB = { x = rect.mapX + rect.width, y = 0, z = rect.mapY + rect.height }
|
||||
trigger.action.rectToAll(-1, id, pointA, pointB, color, fillColor, lineStyle, true)
|
||||
|
||||
end
|
||||
|
||||
---@param arrow Arrow
|
||||
---@param logger Logger?
|
||||
local function DrawArrow(arrow, logger)
|
||||
local function DrawArrow(arrow)
|
||||
local fillColor = DrawingHelper.ColorToColorTable(arrow.fillColorString)
|
||||
local color = DrawingHelper.ColorToColorTable(arrow.colorString)
|
||||
local lineStyle = DrawingHelper.ToLineStyleInteger(arrow.style)
|
||||
|
||||
if logger then
|
||||
logger:debug("Drawing arrow with start point: " .. tostring(arrow.mapX) .. ", " .. tostring(arrow.mapY) .. " and angle: " .. tostring(arrow.angle) .. " and length: " .. tostring(arrow.length))
|
||||
end
|
||||
logger:debug("Drawing arrow with start point: " .. tostring(arrow.mapX) .. ", " .. tostring(arrow.mapY) .. " and angle: " .. tostring(arrow.angle) .. " and length: " .. tostring(arrow.length))
|
||||
|
||||
|
||||
local endPoint = { x = arrow.mapX, y = 0, z = arrow.mapY }
|
||||
local rad = math.rad(arrow.angle or 0)
|
||||
@@ -203,44 +182,56 @@ function DrawingHelper.DrawPolygon(object, id, logger)
|
||||
if object.polygonMode == "circle" then
|
||||
DrawCircle(object--[[@as Circle]])
|
||||
elseif object.polygonMode == "oval" then
|
||||
DrawOval(object--[[@as Oval]], logger)
|
||||
DrawOval(object--[[@as Oval]])
|
||||
elseif object.polygonMode == "free" then
|
||||
DrawFree(object--[[@as Free]], logger)
|
||||
DrawFree(object--[[@as Free]])
|
||||
elseif object.polygonMode == "rect" then
|
||||
DrawRect(object--[[@as Rect]])
|
||||
elseif object.polygonMode == "arrow" then
|
||||
DrawArrow(object--[[@as Arrow]], logger)
|
||||
DrawArrow(object--[[@as Arrow]])
|
||||
end
|
||||
return {id}
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param object Line
|
||||
---@param id integer
|
||||
---@param logger Logger?
|
||||
function DrawingHelper.DrawLine(object, id, logger)
|
||||
---@return Array<integer>
|
||||
function DrawingHelper.DrawLine(object)
|
||||
|
||||
---@type Array<Vec3>
|
||||
local points = {}
|
||||
local ids = {}
|
||||
|
||||
for _, point in ipairs(object.points) do
|
||||
table.insert(points, { x = point.x, y = 0, z = point.y } )
|
||||
table.insert(points, { x = object.mapX + point.x, y = 0, z = object.mapY + point.y } )
|
||||
end
|
||||
|
||||
local color = DrawingHelper.ColorToColorTable(object.colorString)
|
||||
local lineStyle = DrawingHelper.ToLineStyleInteger(object.style)
|
||||
MarkupToAll(1, id, points, color, color, lineStyle, object.thickness, logger)
|
||||
|
||||
for i = 1, #points - 1 do
|
||||
local id = DrawingHelper.GetAndAddId()
|
||||
trigger.action.lineToAll(-1, id, points[i], points[i + 1], color, lineStyle, true)
|
||||
table.insert(ids, id)
|
||||
end
|
||||
return ids
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param object TextBox
|
||||
---@param id integer
|
||||
function DrawingHelper.DrawTextBox(object, id)
|
||||
---@return Array<integer>
|
||||
function DrawingHelper.DrawTextBox(object)
|
||||
|
||||
local id = DrawingHelper.GetAndAddId()
|
||||
|
||||
trigger.action.textToAll(-1, id, { x= object.mapX, y = 0, z = object.mapY },
|
||||
DrawingHelper.ColorToColorTable(object.colorString),
|
||||
DrawingHelper.ColorToColorTable(object.fillColorString),
|
||||
object.fontSize or 12,
|
||||
true,
|
||||
object.text or "")
|
||||
|
||||
return {id}
|
||||
end
|
||||
|
||||
function DrawingHelper.Remove(id)
|
||||
@@ -252,7 +243,7 @@ end
|
||||
function DrawingHelper.ColorToColorTable(hexStr)
|
||||
|
||||
if hexStr == nil then
|
||||
drawingLogger:warn("ColorToColorTable called with nil hexStr, returning default color {0, 0, 0, 0}")
|
||||
logger:warn("ColorToColorTable called with nil hexStr, returning default color {0, 0, 0, 0}")
|
||||
return { 0, 0, 0, 0 }
|
||||
end
|
||||
|
||||
@@ -266,11 +257,11 @@ function DrawingHelper.ColorToColorTable(hexStr)
|
||||
return { r, g , b , a }
|
||||
end
|
||||
|
||||
---@param rgba table
|
||||
---@param rgba table { r, g, b, a }
|
||||
---@return string
|
||||
function DrawingHelper.ColorTableToColorString(rgba)
|
||||
if rgba == nil or #rgba < 4 or rgba[1] == nil or rgba[2] == nil or rgba[3] == nil or rgba[4] == nil then
|
||||
drawingLogger:warn("ColorTableToColorString called with invalid rgba table, returning default color string '0x00000000'")
|
||||
logger:warn("ColorTableToColorString called with invalid rgba table, returning default color string '0x00000000'")
|
||||
return "0x00000000"
|
||||
end
|
||||
|
||||
@@ -283,6 +274,7 @@ function DrawingHelper.ColorTableToColorString(rgba)
|
||||
end
|
||||
|
||||
---@param lineStyle string
|
||||
---@return integer
|
||||
function DrawingHelper.ToLineStyleInteger(lineStyle)
|
||||
lineStyle = lineStyle:lower()
|
||||
if lineStyle == "no line" then
|
||||
@@ -293,11 +285,11 @@ function DrawingHelper.ToLineStyleInteger(lineStyle)
|
||||
return 2
|
||||
elseif lineStyle == "dotted" then
|
||||
return 3
|
||||
elseif lineStyle == "dot dash" then
|
||||
elseif lineStyle == "dotdash" then
|
||||
return 4
|
||||
elseif lineStyle == "long dash" then
|
||||
elseif lineStyle == "longdash" then
|
||||
return 5
|
||||
elseif lineStyle == "two dash" then
|
||||
elseif lineStyle == "twodash" then
|
||||
return 6
|
||||
else
|
||||
return 0
|
||||
@@ -314,11 +306,11 @@ function DrawingHelper.ToLineStyleString(lineStyle)
|
||||
elseif lineStyle == 3 then
|
||||
return "dotted"
|
||||
elseif lineStyle == 4 then
|
||||
return "dot dash"
|
||||
return "dotdash"
|
||||
elseif lineStyle == 5 then
|
||||
return "long dash"
|
||||
return "longdash"
|
||||
elseif lineStyle == 6 then
|
||||
return "two dash"
|
||||
return "twodash"
|
||||
else
|
||||
return "no line"
|
||||
end
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
local Logger = require("classes.util.Logger")
|
||||
local Util = require("classes.util.Util")
|
||||
local DcsUtil = require("classes.util.DcsUtil")
|
||||
|
||||
---@class BattleManager
|
||||
---@field private _name string
|
||||
@@ -13,8 +12,6 @@ local DcsUtil = require("classes.util.DcsUtil")
|
||||
local BattleManager = {}
|
||||
BattleManager.__index = BattleManager
|
||||
|
||||
local debugDrawing = false
|
||||
|
||||
---@param redGroups Array<SpearheadGroup>
|
||||
---@param blueGroups Array<SpearheadGroup>
|
||||
---@param name string
|
||||
@@ -39,6 +36,7 @@ end
|
||||
|
||||
---@param self BattleManager
|
||||
---@param time number
|
||||
---@return number?
|
||||
local function CheckTask(self, time)
|
||||
local interval = self:Update()
|
||||
if not interval then return end
|
||||
@@ -90,8 +88,6 @@ function BattleManager:Update()
|
||||
|
||||
self._logger:debug("BattleManager Update called for " .. self._name)
|
||||
|
||||
local shootChance = 1 -- Adjust this value to control the shooting probability (0.0 to 1.0)
|
||||
|
||||
self:LetUnitsShoot(self._redGroups, self._blueGroups)
|
||||
self:LetUnitsShoot(self._blueGroups, self._redGroups)
|
||||
|
||||
@@ -153,7 +149,7 @@ end
|
||||
---@return number
|
||||
function BattleManager:getBestAmmo(unit)
|
||||
|
||||
local ammo = unit:getAmmo()
|
||||
local ammo = unit:getAmmo() --[[@as table<number, table>]]
|
||||
|
||||
if not ammo then return 3221225470, 1 end -- Default ammo if no ammo is found
|
||||
|
||||
@@ -167,9 +163,9 @@ function BattleManager:getBestAmmo(unit)
|
||||
end
|
||||
end
|
||||
|
||||
local entry = Util.randomFromList(shells)
|
||||
local entry = Util.randomFromList(shells) --[[@as table]]
|
||||
if entry and entry.desc and entry.desc.warhead then
|
||||
local caliber = entry.desc.warhead.caliber
|
||||
local caliber = entry.desc.warhead.caliber --[[@as number]]
|
||||
if caliber > 50 then
|
||||
return 258503344128, 1
|
||||
else
|
||||
@@ -214,6 +210,7 @@ function BattleManager:ToShootingHulls(groups)
|
||||
end
|
||||
|
||||
local hulls = Util.getSeparatedConvexHulls(points, 50)
|
||||
---@type Array<Array<Vec2>>
|
||||
local enlargedHulls = {}
|
||||
for _, hull in pairs(hulls) do
|
||||
local enlarged = Util.enlargeConvexHull(hull, 25)
|
||||
|
||||
@@ -7,7 +7,7 @@ local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHel
|
||||
local StageConfig = require("classes.configuration.StageConfig")
|
||||
|
||||
|
||||
---@class MissionCommandsHelper
|
||||
---@class MissionCommandsHelper : OnPlayerEnterUnitListener
|
||||
---@field missionsByCode table<string, Mission> @table of missions by their code
|
||||
---@field enabledByCode table<string, boolean> @table of enabled missions by their code
|
||||
---@field updateNeeded boolean @flag to indicate if an update is needed
|
||||
@@ -31,8 +31,6 @@ local function sortMissions(list, groupPos)
|
||||
end)
|
||||
end
|
||||
|
||||
local id = 0
|
||||
|
||||
local instance = nil
|
||||
|
||||
---@return MissionCommandsHelper
|
||||
@@ -57,16 +55,16 @@ function MissionCommandsHelper.getOrCreate()
|
||||
|
||||
instance._supplyUnitsTracker:AddOnSupplyUnitEventListener(
|
||||
{
|
||||
enteredSupplyHub = function(self, unit)
|
||||
enteredSupplyHub = function(_, unit)
|
||||
if unit == nil then return end
|
||||
instance.updateNeeded = true
|
||||
instance:updateCommandsForGroup(unit:getGroup():getID())
|
||||
end,
|
||||
exitedSupplyHub = function(self, unit)
|
||||
exitedSupplyHub = function(_, unit)
|
||||
instance.updateNeeded = true
|
||||
instance:updateCommandsForGroup(unit:getGroup():getID())
|
||||
end,
|
||||
supplyUnitSpawned = function(self, unit)
|
||||
supplyUnitSpawned = function(_, unit)
|
||||
instance.updateNeeded = true
|
||||
instance:updateCommandsForGroup(unit:getGroup():getID())
|
||||
end
|
||||
@@ -212,7 +210,7 @@ function MissionCommandsHelper:OverviewToGroup(groupID)
|
||||
for code, enabled in pairs(self.enabledByCode) do
|
||||
if enabled == true then
|
||||
local mission = self.missionsByCode[code]
|
||||
if mission and mission:getState() == "ACTIVE" and mission.priority == "primary" then
|
||||
if mission and mission:getState() == "ACTIVE" and mission:GetPriority() == "primary" then
|
||||
table.insert(primaryMissions, mission)
|
||||
end
|
||||
end
|
||||
@@ -232,7 +230,7 @@ function MissionCommandsHelper:OverviewToGroup(groupID)
|
||||
for code, enabled in pairs(self.enabledByCode) do
|
||||
if enabled == true then
|
||||
local mission = self.missionsByCode[code]
|
||||
if mission and mission:getState() == "ACTIVE" and mission.priority == "secondary" then
|
||||
if mission and mission:getState() == "ACTIVE" and mission:GetPriority() == "secondary" then
|
||||
table.insert(secondaryMissions, mission)
|
||||
end
|
||||
end
|
||||
@@ -347,7 +345,7 @@ function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID)
|
||||
for code, enabled in pairs(self.enabledByCode) do
|
||||
if enabled == true then
|
||||
local mission = self.missionsByCode[code]
|
||||
if mission and mission.priority == "primary" then
|
||||
if mission and mission:GetPriority() == "primary" then
|
||||
table.insert(primaryMissions, mission)
|
||||
end
|
||||
end
|
||||
@@ -371,12 +369,12 @@ function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID)
|
||||
do --- secondary missions
|
||||
local count = 0
|
||||
local path = { [1] = folderNames.secondary }
|
||||
|
||||
---@type Array<Mission>
|
||||
local secondaryMissions = {}
|
||||
for code, enabled in pairs(self.enabledByCode) do
|
||||
if enabled == true then
|
||||
local mission = self.missionsByCode[code]
|
||||
if mission and mission.priority == "secondary" then
|
||||
if mission and mission:GetPriority() == "secondary" then
|
||||
table.insert(secondaryMissions, mission)
|
||||
end
|
||||
end
|
||||
@@ -418,7 +416,7 @@ function MissionCommandsHelper:addMissionCommands(groupId, path, mission)
|
||||
end
|
||||
|
||||
local missionFolderName = "[" ..
|
||||
mission.code .. "]" .. distance .. mission.name .. "( " .. mission.missionTypeDisplay .. " )"
|
||||
mission.code .. "]" .. distance .. mission.name .. "(" .. mission.missionTypeDisplay .. ")"
|
||||
missionCommands.addSubMenuForGroup(groupId, missionFolderName, path)
|
||||
table.insert(path, missionFolderName)
|
||||
|
||||
@@ -509,7 +507,6 @@ function MissionCommandsHelper:AddCargoCommands(groupID)
|
||||
local unloadCargoCommand = function(params)
|
||||
local unitID = params.unitID
|
||||
local crateType = params.crateType
|
||||
local supplyUnitsTracker = params.supplyUnitsTracker
|
||||
params.supplyUnitsTracker:UnloadRequested(unitID, crateType, params.commandHelper)
|
||||
end
|
||||
|
||||
@@ -518,7 +515,7 @@ function MissionCommandsHelper:AddCargoCommands(groupID)
|
||||
for cargoType, amount in pairs(cargo) do
|
||||
local cargoConfig = SupplyConfigHelper.getSupplyConfig(cargoType)
|
||||
if cargoConfig then
|
||||
for i = 1, amount do
|
||||
for _ = 1, amount do
|
||||
local path = { [1] = folderNames.cargo }
|
||||
---@type UnloadCargoCommandParams
|
||||
local params = { unitID = unit:getID(), crateType = cargoType, supplyUnitsTracker = self
|
||||
|
||||
@@ -76,7 +76,7 @@ local SupplyConfigHelper = {}
|
||||
---@return SupplyConfig?
|
||||
function SupplyConfigHelper.fromObjectName(name)
|
||||
for configName, config in pairs(SupplyConfig) do
|
||||
if Util.startswith(name, configName, true) == true then
|
||||
if Util.startsWith(name, configName, true) == true then
|
||||
return config
|
||||
end
|
||||
end
|
||||
@@ -84,6 +84,7 @@ function SupplyConfigHelper.fromObjectName(name)
|
||||
end
|
||||
|
||||
---@param type CrateType
|
||||
---@return SupplyConfig?
|
||||
function SupplyConfigHelper.getSupplyConfig(type)
|
||||
return SupplyConfig[type]
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ local SupplyLoadConfig = require("classes.stageClasses.helpers.SupplyLoadConfig"
|
||||
---@field enteredSupplyHub fun(self:SupplyUnitEventListener, unit:Unit, hub:SupplyHub) | nil
|
||||
---@field exitedSupplyHub fun(self:SupplyUnitEventListener, unit:Unit, hub:SupplyHub) | nil
|
||||
|
||||
---@class SupplyUnitsTracker
|
||||
---@class SupplyUnitsTracker : OnPlayerEnterUnitListener
|
||||
---@field private _supplyUnitsByName table<string, Unit>
|
||||
---@field private _cargoInUnits table<string, table<CrateType, number>>
|
||||
---@field private _logger Logger
|
||||
@@ -43,6 +43,8 @@ function SupplyUnitsTracker.getOrCreate()
|
||||
SpearheadEvents.AddOnPlayerEnterUnitListener(singleton)
|
||||
|
||||
---@param selfA SupplyUnitsTracker
|
||||
---@param time number
|
||||
---@return number?
|
||||
local function updateTask(selfA, time)
|
||||
|
||||
selfA:Update()
|
||||
@@ -134,6 +136,7 @@ end
|
||||
|
||||
---@private
|
||||
---@param unit Unit
|
||||
---@return boolean
|
||||
function SupplyUnitsTracker:IsSupplyUnit(unit)
|
||||
if unit == nil then return false end
|
||||
|
||||
@@ -158,15 +161,17 @@ function SupplyUnitsTracker:AddCargoToUnit(unitID, crateType)
|
||||
local unit = DcsUtil.GetPlayerUnitByID(unitID)
|
||||
if unit == nil then return end
|
||||
|
||||
if self._cargoInUnits[unitID] == nil then
|
||||
self._cargoInUnits[unitID] = {}
|
||||
local unitIdStr = tostring(unitID)
|
||||
|
||||
if self._cargoInUnits[unitIdStr] == nil then
|
||||
self._cargoInUnits[unitIdStr] = {}
|
||||
end
|
||||
|
||||
if self._cargoInUnits[unitID][crateType] == nil then
|
||||
self._cargoInUnits[unitID][crateType] = 0
|
||||
if self._cargoInUnits[unitIdStr][crateType] == nil then
|
||||
self._cargoInUnits[unitIdStr][crateType] = 0
|
||||
end
|
||||
|
||||
self._cargoInUnits[unitID][crateType] = self._cargoInUnits[unitID][crateType] + 1
|
||||
self._cargoInUnits[unitIdStr][crateType] = self._cargoInUnits[unitIdStr][crateType] + 1
|
||||
|
||||
end
|
||||
|
||||
@@ -185,7 +190,7 @@ function SupplyUnitsTracker:RemoveCargoFromUnit(unitID, crateType)
|
||||
self._cargoInUnits[unitIDStr][crateType] = self._cargoInUnits[unitIDStr][crateType] - 1
|
||||
|
||||
local hasCargo = false
|
||||
for type, count in pairs(self._cargoInUnits[unitIDStr]) do
|
||||
for _, count in pairs(self._cargoInUnits[unitIDStr]) do
|
||||
if count > 0 then
|
||||
hasCargo = true
|
||||
break
|
||||
@@ -216,13 +221,11 @@ end
|
||||
|
||||
function SupplyUnitsTracker:CheckUnitsInZones()
|
||||
|
||||
for name, unit in pairs(self._supplyUnitsByName) do
|
||||
for _, unit in pairs(self._supplyUnitsByName) do
|
||||
if unit ~= nil and unit:isExist() == true then
|
||||
self._logger:debug("Checking unit: " .. unit:getName())
|
||||
local pos = unit:getPoint()
|
||||
|
||||
local group = unit:getGroup()
|
||||
|
||||
for hub, enabled in pairs(self._registeredHubs) do
|
||||
if enabled == true then
|
||||
local zone = hub:GetZone()
|
||||
@@ -255,7 +258,7 @@ function SupplyUnitsTracker:CheckUnitsInZones()
|
||||
end
|
||||
end
|
||||
|
||||
self._unitPositions[unit:getID()] = pos
|
||||
self._unitPositions[tostring(unit:getID())] = pos
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -325,6 +328,7 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe
|
||||
self:UpdateWeightForUnit(unit)
|
||||
|
||||
cargoCount = cargoCount + 1
|
||||
---@type {name: string, type: string, x: number, y: number}
|
||||
local cargoSpawnObject = {
|
||||
name = crateType .. "_" .. cargoCount,
|
||||
type = cargoConfig.staticType,
|
||||
@@ -486,7 +490,7 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
|
||||
end
|
||||
|
||||
local objPos = foundObject:getPoint()
|
||||
local box = desc.box
|
||||
local box = desc.box --[[@type table]]
|
||||
local heading = 0
|
||||
|
||||
-- Try to get object heading from position vector's forward direction
|
||||
@@ -499,10 +503,10 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
|
||||
end)
|
||||
|
||||
-- For rotated objects, we need to rotate the bounding box
|
||||
local minX = box.min.x
|
||||
local maxX = box.max.x
|
||||
local minZ = box.min.z
|
||||
local maxZ = box.max.z
|
||||
local minX = box.min.x --[[@as number]]
|
||||
local maxX = box.max.x --[[@as number]]
|
||||
local minZ = box.min.z --[[@as number]]
|
||||
local maxZ = box.max.z --[[@as number]]
|
||||
|
||||
-- If object has significant rotation, apply rotation to bbox corners
|
||||
if math.abs(heading) > 0.1 then
|
||||
@@ -519,8 +523,8 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
|
||||
minZ, maxZ = math.huge, -math.huge
|
||||
|
||||
for _, corner in ipairs(corners) do
|
||||
local rotX = corner[1] * math.cos(heading) - corner[2] * math.sin(heading)
|
||||
local rotZ = corner[1] * math.sin(heading) + corner[2] * math.cos(heading)
|
||||
local rotX = corner[1] * math.cos(heading) - corner[2] * math.sin(heading) --[[@as number]]
|
||||
local rotZ = corner[1] * math.sin(heading) + corner[2] * math.cos(heading) --[[@as number]]
|
||||
minX = math.min(minX, rotX)
|
||||
maxX = math.max(maxX, rotX)
|
||||
minZ = math.min(minZ, rotZ)
|
||||
@@ -589,7 +593,7 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
|
||||
return nil
|
||||
end
|
||||
|
||||
local crateRelativeBBox = crateDesc.box
|
||||
local crateRelativeBBox = crateDesc.box --[[@as table]]
|
||||
|
||||
-- Get drop zone config for this unit
|
||||
local dropZones = {
|
||||
@@ -612,9 +616,12 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
|
||||
radius = 100 -- Search a large area
|
||||
}
|
||||
}
|
||||
|
||||
---@type Array<{ pos: { x: number, y: number, z: number }, bbox: { min: { x: number, y: number, z: number }, max: { x: number, y: number, z: number } } }>
|
||||
local occupiedObjects = {}
|
||||
local found = function(foundItem, val)
|
||||
|
||||
---@param foundItem Object
|
||||
---@param _ any
|
||||
local found = function(foundItem, _)
|
||||
local bbox = self:GetBoundingBoxes(foundItem)
|
||||
if bbox then
|
||||
self._logger:debug("Found object: " .. foundItem:getTypeName() .. " at (" .. foundItem:getPoint().x .. ", " .. foundItem:getPoint().z .. ")")
|
||||
@@ -662,6 +669,7 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
|
||||
local candidateY = land.getHeight({ x = candidateX, y = candidateZ })
|
||||
|
||||
-- Convert crate's relative bbox to world space at this position
|
||||
---@type { min: { x: number, y: number, z: number }, max: { x: number, y: number, z: number } }
|
||||
local crateBBoxWorldSpace = {
|
||||
min = {
|
||||
x = candidateX + crateRelativeBBox.min.x,
|
||||
|
||||
@@ -21,9 +21,20 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
|
||||
---@field private _dropOffZone SpearheadTriggerZone?
|
||||
---@field private _noLandingZoneDrawing CustomDrawing?
|
||||
---@field private _dropOffZoneDrawing CustomDrawing?
|
||||
---@field private _briefing string?
|
||||
local BuildableMission = {}
|
||||
BuildableMission.__index = BuildableMission
|
||||
|
||||
---@param siteType string
|
||||
---@param coords string
|
||||
---@return string
|
||||
local function getDefaultBriefing(siteType, coords)
|
||||
return "We've dispatched forward units to find a proper spot for a new " .. siteType .. "." ..
|
||||
"\nYou will need to drop off supplies so they can start building." ..
|
||||
"\nThe coords are: " .. coords ..
|
||||
"\n\n"
|
||||
end
|
||||
|
||||
---@class OnCrateDroppedListener
|
||||
---@field OnCrateDroppedOff fun(self:OnCrateDroppedListener, mission:BuildableMission, kilos:number)
|
||||
|
||||
@@ -33,7 +44,9 @@ BuildableMission.__index = BuildableMission
|
||||
---@param requiredCrateType SupplyType
|
||||
---@param noLandingZone SpearheadTriggerZone?
|
||||
---@param logger Logger
|
||||
function BuildableMission.new(database, logger, targetZone, noLandingZone, requiredKilos, requiredCrateType)
|
||||
---@param briefing string?
|
||||
---@return BuildableMission
|
||||
function BuildableMission.new(database, logger, targetZone, noLandingZone, requiredKilos, requiredCrateType, briefing)
|
||||
|
||||
setmetatable(BuildableMission, Mission)
|
||||
|
||||
@@ -43,6 +56,7 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
|
||||
self._database = database
|
||||
self._requiredKilos = requiredKilos
|
||||
self._droppedKilos = 0
|
||||
self._briefing = briefing
|
||||
|
||||
self._noLandingZone = noLandingZone
|
||||
|
||||
@@ -64,13 +78,12 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
|
||||
end
|
||||
|
||||
self.code = tostring(database:GetNewMissionCode())
|
||||
self.name = "Resupply"
|
||||
|
||||
local type = "site"
|
||||
if requiredCrateType == "SAM_CRATE" then
|
||||
type = "SAM site"
|
||||
elseif requiredCrateType == "FARP_CRATE" then
|
||||
type = "FARP"
|
||||
local splitTargetZoneName = Util.split_string(targetZone.name, "_")
|
||||
if splitTargetZoneName and splitTargetZoneName[3] and splitTargetZoneName[3] ~= "" then
|
||||
self.name = splitTargetZoneName[3]
|
||||
else
|
||||
self.name = "Resupply"
|
||||
end
|
||||
|
||||
self.zoneName = targetZone.name .. "_supply"
|
||||
@@ -81,7 +94,6 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
|
||||
self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate()
|
||||
self._state = "NEW"
|
||||
|
||||
|
||||
self.location = targetZone.location
|
||||
|
||||
self.missionType = "LOGISTICS"
|
||||
@@ -108,6 +120,7 @@ function BuildableMission:ShowBriefing(groupID)
|
||||
|
||||
local unitType = DcsUtil.getUnitTypeFromGroup(group)
|
||||
local coords = DcsUtil.convertVec2ToUnitUsableType(self.location, unitType)
|
||||
if coords == nil then coords = "Could not make conversion" end
|
||||
|
||||
local siteType = "FARP"
|
||||
if self._crateType == "SAM_CRATE" then
|
||||
@@ -116,11 +129,16 @@ function BuildableMission:ShowBriefing(groupID)
|
||||
siteType = "airbase"
|
||||
end
|
||||
|
||||
local briefingPart = self._briefing
|
||||
if briefingPart then
|
||||
briefingPart = Util.replaceString(briefingPart, "{{coords}}", coords)
|
||||
else
|
||||
briefingPart = getDefaultBriefing(siteType, coords)
|
||||
end
|
||||
|
||||
local briefing = "Mission [" .. self.code .. "] " .. self.name ..
|
||||
"\n \n" ..
|
||||
"We've dispatched forward units to find a proper spot for a new " .. siteType .. "." ..
|
||||
"\nYou will need to drop off supplies so they can start building." ..
|
||||
"\nThe coords are: " .. coords ..
|
||||
briefingPart ..
|
||||
"\n\n" ..
|
||||
"\nKilos still required: " .. self._requiredKilos - self._droppedKilos ..
|
||||
"\n\n" ..
|
||||
@@ -129,17 +147,20 @@ function BuildableMission:ShowBriefing(groupID)
|
||||
trigger.action.outTextForGroup(groupID, briefing, GlobalConfig:getBriefingTime())
|
||||
end
|
||||
|
||||
---@param groupID number
|
||||
function BuildableMission:MarkMissionAreaToGroup(groupID)
|
||||
|
||||
if self._markIDsPerGroup[groupID] then
|
||||
DcsUtil.RemoveMark(self._markIDsPerGroup[groupID])
|
||||
end
|
||||
local groupIdStr = tostring(groupID)
|
||||
|
||||
if self._markIDsPerGroup[groupIdStr] then
|
||||
DcsUtil.RemoveMark(self._markIDsPerGroup[groupIdStr])
|
||||
end
|
||||
---@type string
|
||||
local text = "[" .. self.code .. "] " .. self.name .. " | " .. self._crateType
|
||||
local location = { x= self.location.x, y=land.getHeight(self.location), z=self.location.y }
|
||||
local markID = DcsUtil.AddMarkToGroup(groupID, text, location)
|
||||
|
||||
self._markIDsPerGroup[groupID] = markID
|
||||
self._markIDsPerGroup[groupIdStr] = markID
|
||||
end
|
||||
|
||||
---@private
|
||||
@@ -183,6 +204,7 @@ function BuildableMission:SpawnActive()
|
||||
|
||||
---@param selfA BuildableMission
|
||||
---@param time number
|
||||
---@return number?
|
||||
local checkForCrateTasks = function (selfA, time)
|
||||
selfA:CheckCratesInZone()
|
||||
|
||||
@@ -237,7 +259,7 @@ function BuildableMission:CheckCratesInZone()
|
||||
|
||||
local crates = self._supplyUnitsTracker:GetCargoCratesDropped()
|
||||
for _, staticObject in pairs(crates) do
|
||||
if staticObject and staticObject:isExist() and Util.startswith(staticObject:getName(), self._crateType, true) then
|
||||
if staticObject and staticObject:isExist() and Util.startsWith(staticObject:getName(), self._crateType, true) then
|
||||
local pos = staticObject:getPoint()
|
||||
|
||||
if Util.is3dPointInZone(pos, self._dropOffZone) then
|
||||
|
||||
@@ -18,12 +18,13 @@ local RunwayStrikeMission = {}
|
||||
---@field corners Array<Vec2>
|
||||
---@field kilosHit number
|
||||
---@field repairGroups Array<string>
|
||||
---@field drawID number?
|
||||
---@field drawID Array<number>
|
||||
|
||||
|
||||
---@param runway Runway
|
||||
---@param database Database
|
||||
---@param logger Logger
|
||||
---@param airbaseName string
|
||||
---@param runwayBombingTracker RunwayBombingTracker
|
||||
---@return RunwayStrikeMission?
|
||||
function RunwayStrikeMission.new(runway, airbaseName, database, logger, runwayBombingTracker)
|
||||
@@ -84,7 +85,6 @@ function RunwayStrikeMission:RunwayHit(impactPoint, explosiveMass)
|
||||
|
||||
self._logger:debug("Runway hit: " .. self._airportName .. ":" .. self._runway.Name)
|
||||
for _, section in pairs(self._runwaySections) do
|
||||
|
||||
local zone = self:SectionToSpearheadZone(section)
|
||||
if Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then
|
||||
if section.kilosHit == nil then
|
||||
@@ -96,7 +96,7 @@ function RunwayStrikeMission:RunwayHit(impactPoint, explosiveMass)
|
||||
end
|
||||
|
||||
---@param selfA RunwayStrikeMission
|
||||
local updateState = function(selfA, time)
|
||||
local updateState = function(selfA, _)
|
||||
selfA:UpdateState()
|
||||
end
|
||||
|
||||
@@ -172,8 +172,10 @@ function RunwayStrikeMission:Draw()
|
||||
|
||||
runwaySection.drawID = DrawingHelper.Draw(drawObject)
|
||||
else
|
||||
DcsUtil.SetFillColor(runwaySection.drawID, fillColor)
|
||||
DcsUtil.SetLineColor(runwaySection.drawID, lineColor)
|
||||
for _, drawID in pairs(runwaySection.drawID) do
|
||||
DcsUtil.SetFillColor(drawID, fillColor)
|
||||
DcsUtil.SetLineColor(drawID, lineColor)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -204,17 +206,18 @@ function RunwayStrikeMission:StartRepair()
|
||||
self._repairInProgress = true
|
||||
self._logger:debug("Starting repair of runway strike mission " .. self._airportName .. ":" .. self._runway.Name)
|
||||
---comment
|
||||
---@param selfA any
|
||||
---@return unknown
|
||||
---@param selfA RunwayStrikeMission
|
||||
---@param time number
|
||||
---@return number?
|
||||
local repairTask = function (selfA, time)
|
||||
local interval = selfA:DoRepairCycle()
|
||||
if interval == nil then return nil end
|
||||
return time + interval
|
||||
end
|
||||
|
||||
timer.scheduleFunction(repairTask, self, timer.getTime() + 5)
|
||||
end
|
||||
|
||||
---@return number?
|
||||
function RunwayStrikeMission:DoRepairCycle()
|
||||
|
||||
local interval = 5
|
||||
@@ -247,12 +250,14 @@ function RunwayStrikeMission:DoRepairCycle()
|
||||
|
||||
end
|
||||
|
||||
---@class StaticSpawn
|
||||
---@class StaticSpawn : table
|
||||
---@field category string
|
||||
---@field type string
|
||||
---@field y number
|
||||
---@field x number
|
||||
---@field heading number
|
||||
---@field name string?
|
||||
---@field hidden boolean?
|
||||
|
||||
|
||||
local counter = 1
|
||||
@@ -441,6 +446,7 @@ end
|
||||
|
||||
---@private
|
||||
---@param runway Runway
|
||||
---@param numSections number
|
||||
---@return Array<RunwaySection>
|
||||
function RunwayStrikeMission:ToSections(runway, numSections)
|
||||
|
||||
@@ -497,6 +503,7 @@ function RunwayStrikeMission:ToSections(runway, numSections)
|
||||
corners = corners,
|
||||
kilosHit = 0,
|
||||
repairGroups = {},
|
||||
drawID = {},
|
||||
}
|
||||
|
||||
table.insert(sections, section)
|
||||
|
||||
@@ -14,7 +14,7 @@ local DcsUtil = require("classes.util.DcsUtil")
|
||||
---@field private _completeAtIndex number
|
||||
---@field private _parentStage Stage
|
||||
---@field private _battleManager? BattleManager
|
||||
---@field private _lastContactMarkerID number;
|
||||
---@field private _lastContactMarkerID number
|
||||
local ZoneMission = {}
|
||||
|
||||
--- @class MissionGroups
|
||||
@@ -24,7 +24,7 @@ local ZoneMission = {}
|
||||
--- @field unitsAlive table<string, table<string, boolean>>
|
||||
--- @field targetsAlive table<string, table<string, boolean>>
|
||||
--- @field sceneryTargets Array<SpearheadSceneryObject>
|
||||
--- @field groupNamesPerunit table<string,string>
|
||||
--- @field groupNamesPerUnit table<string,string>
|
||||
|
||||
---@class ParsedMissionName
|
||||
---@field missionName string
|
||||
@@ -36,7 +36,7 @@ local ZoneMission = {}
|
||||
local function ParseZoneName(input)
|
||||
local split_name = Util.split_string(input, "_")
|
||||
local split_length = Util.tableLength(split_name)
|
||||
if Util.startswith(input, "RANDOMMISSION") == true and split_length < 4 then
|
||||
if Util.startsWith(input, "RANDOMMISSION") == true and split_length < 4 then
|
||||
MissionEditorWarnings.Add("Random Mission with zonename " .. input .. " not in right format")
|
||||
return nil
|
||||
elseif split_length < 3 then
|
||||
@@ -53,9 +53,10 @@ local function ParseZoneName(input)
|
||||
if inputType == "cas" then parsedType = "CAS" end
|
||||
if inputType == "bai" then parsedType = "BAI" end
|
||||
if inputType == "sam" then parsedType = "SAM" end
|
||||
if inputType == "deepstrike" then parsedType = "DEEPSTRIKE" end
|
||||
|
||||
if parsedType == "nil" then
|
||||
MissionEditorWarnings.Add("Mission with zonename '" ..
|
||||
MissionEditorWarnings.Add("Mission with zoneName '" ..
|
||||
input .. "' has an unsupported type '" .. (type or "nil"))
|
||||
return nil
|
||||
end
|
||||
@@ -66,8 +67,6 @@ local function ParseZoneName(input)
|
||||
}
|
||||
end
|
||||
|
||||
MINIMAL_UNITS_ALIVE_RATIO = 0.21
|
||||
|
||||
---comment
|
||||
---@param zoneName string
|
||||
---@param priority MissionPriority
|
||||
@@ -112,7 +111,7 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage, spaw
|
||||
unitsAlive = {},
|
||||
targetsAlive = {},
|
||||
hasTargets = false,
|
||||
groupNamesPerunit = {},
|
||||
groupNamesPerUnit = {},
|
||||
sceneryTargets = {}
|
||||
}
|
||||
|
||||
@@ -150,17 +149,17 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage, spaw
|
||||
local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true)
|
||||
table.insert(self._missionGroups.redGroups, spearheadGroup)
|
||||
|
||||
local isGroupTarget = Util.startswith(string.lower(groupName), "tgt_")
|
||||
local isGroupTarget = Util.startsWith(string.lower(groupName), "tgt_")
|
||||
for _, unit in pairs(spearheadGroup:GetObjects()) do
|
||||
local unitName = unit:getName()
|
||||
local isUnitTarget = Util.startswith(string.lower(unitName), "tgt_")
|
||||
local isUnitTarget = Util.startsWith(string.lower(unitName), "tgt_")
|
||||
|
||||
if self._missionGroups.unitsAlive[groupName] == nil then
|
||||
self._missionGroups.unitsAlive[groupName] = {}
|
||||
end
|
||||
|
||||
self._missionGroups.unitsAlive[groupName][unitName] = true
|
||||
self._missionGroups.groupNamesPerunit[unitName] = groupName
|
||||
self._missionGroups.groupNamesPerUnit[unitName] = groupName
|
||||
|
||||
if isGroupTarget == true or isUnitTarget == true then
|
||||
self._missionGroups.hasTargets = true
|
||||
@@ -180,7 +179,6 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage, spaw
|
||||
|
||||
if self.missionType == "CAS" then
|
||||
self._battleManager = BattleManager.New(self._missionGroups.redGroups, self._missionGroups.blueGroups, self.zoneName, self._logger.LogLevel)
|
||||
|
||||
end
|
||||
|
||||
self._logger:debug("Mission " .. self.name .. " group count: " .. Util.tableLength(missionData.RedGroups))
|
||||
@@ -211,7 +209,7 @@ end
|
||||
---@return boolean
|
||||
function ZoneMission:AllDependenciesMet()
|
||||
local allDependenciesMet = true
|
||||
for missionName, value in pairs(self._dependencies) do
|
||||
for missionName, _ in pairs(self._dependencies) do
|
||||
if self._parentStage:IsMissionComplete(missionName) == false then
|
||||
allDependenciesMet = false
|
||||
self._dependencies[missionName] = false
|
||||
@@ -229,10 +227,9 @@ end
|
||||
|
||||
---@internal
|
||||
---@param checkHealth boolean
|
||||
---@param messageIfDone boolean
|
||||
function ZoneMission:UpdateState(checkHealth, messageIfDone)
|
||||
---@param _ boolean
|
||||
function ZoneMission:UpdateState(checkHealth, _)
|
||||
if checkHealth == nil then checkHealth = false end
|
||||
if messageIfDone == false then messageIfDone = true end
|
||||
|
||||
|
||||
if checkHealth == true then
|
||||
@@ -240,7 +237,7 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
|
||||
local staticObject = StaticObject.getByName(unitName)
|
||||
if staticObject then
|
||||
if staticObject:isExist() == true then
|
||||
local life0 = staticObject:getDesc().life
|
||||
local life0 = staticObject:getDesc().life --[[@as number]]
|
||||
if staticObject:getLife() / life0 < 0.3 then
|
||||
self._logger:debug("exploding unit")
|
||||
trigger.action.explosion(staticObject:getPoint(), 100)
|
||||
@@ -286,7 +283,9 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
|
||||
end
|
||||
|
||||
if self._missionGroups.hasTargets == true then
|
||||
---@type number
|
||||
local total = 0
|
||||
---@type number
|
||||
local alive = 0
|
||||
|
||||
for _, units in pairs(self._missionGroups.targetsAlive) do
|
||||
@@ -299,13 +298,13 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
|
||||
end
|
||||
|
||||
for _, sceneryObject in pairs(self._missionGroups.sceneryTargets) do
|
||||
total = total + 1
|
||||
total = total + 1 --[[@as number]]
|
||||
if sceneryObject:IsAlive() == true then
|
||||
alive = alive + 1
|
||||
end
|
||||
end
|
||||
|
||||
local deadRatio = (total - alive) / total
|
||||
local deadRatio = (total - alive) / total --[[@as number]]
|
||||
if deadRatio >= self._completeAtIndex then
|
||||
self._logger:debug("Dead ratio " .. self.zoneName .. deadRatio .. " >= " .. self._completeAtIndex)
|
||||
self._state = "COMPLETED"
|
||||
@@ -354,6 +353,19 @@ end
|
||||
function ZoneMission:SpawnInactive()
|
||||
self._logger:info("PreActivating " .. self.name)
|
||||
|
||||
if self.missionType == "DEEPSTRIKE" then
|
||||
local missionData = self._database:getMissionDataForZone(self.zoneName)
|
||||
---@type MissionPriority
|
||||
local priority = "secondary"
|
||||
if missionData and missionData.primaryOverwrite ~= nil then
|
||||
if missionData.primaryOverwrite == true then
|
||||
priority = "primary"
|
||||
end
|
||||
end
|
||||
self._priority = priority
|
||||
self._missionCommandsHelper:AddMissionToCommands(self)
|
||||
end
|
||||
|
||||
for _, group in pairs(self._missionGroups.redGroups) do
|
||||
group:Spawn()
|
||||
end
|
||||
@@ -390,6 +402,17 @@ function ZoneMission:SpawnActive()
|
||||
self._battleManager:Start()
|
||||
end
|
||||
|
||||
self._missionCommandsHelper:RemoveMissionToCommands(self) -- Remove first
|
||||
if self.missionType == "DEEPSTRIKE" then
|
||||
local missionData = self._database:getMissionDataForZone(self.zoneName)
|
||||
local priority = "primary"
|
||||
if missionData and missionData.primaryOverwrite ~= nil then
|
||||
if missionData.primaryOverwrite == false then
|
||||
priority = "secondary"
|
||||
end
|
||||
end
|
||||
self._priority = priority
|
||||
end
|
||||
self._missionCommandsHelper:AddMissionToCommands(self)
|
||||
|
||||
self:StartCheckingContinuous()
|
||||
@@ -477,16 +500,18 @@ function ZoneMission:OnUnitLost(object)
|
||||
|
||||
local category = Object.getCategory(object)
|
||||
if category == Object.Category.UNIT then
|
||||
object = object --[[@as Unit]]
|
||||
local unitName = object:getName()
|
||||
self._logger:debug("UnitName:" .. unitName)
|
||||
|
||||
local groupName = self._missionGroups.groupNamesPerunit[unitName]
|
||||
local groupName = self._missionGroups.groupNamesPerUnit[unitName]
|
||||
self._missionGroups.unitsAlive[groupName][unitName] = false
|
||||
|
||||
if self._missionGroups.targetsAlive[groupName] and self._missionGroups.targetsAlive[groupName][unitName] then
|
||||
self._missionGroups.targetsAlive[groupName][unitName] = false
|
||||
end
|
||||
elseif category == Object.Category.STATIC then
|
||||
object = object --[[@as StaticObject]]
|
||||
local name = object:getName()
|
||||
self._missionGroups.unitsAlive[name][name] = false
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ local GlobalConfig = require("classes.configuration.GlobalConfig")
|
||||
---@field zoneName string
|
||||
---@field missionType MissionType
|
||||
---@field missionTypeDisplay string
|
||||
---@field priority MissionPriority
|
||||
---@field protected _priority MissionPriority
|
||||
---@field location Vec2?
|
||||
---@field code string
|
||||
---@field protected _state MissionState
|
||||
@@ -40,7 +40,7 @@ function Mission.newSuper(self, zoneName, missionName, missionType, missionBrief
|
||||
self.zoneName = zoneName
|
||||
self.name = missionName
|
||||
self.missionType = missionType
|
||||
self.priority = priority
|
||||
self._priority = priority
|
||||
self._state = "NEW"
|
||||
self._logger = logger
|
||||
self._database = database
|
||||
@@ -64,13 +64,19 @@ end
|
||||
|
||||
--region PUBLIC
|
||||
|
||||
---@return MissionPriority
|
||||
function Mission:GetPriority()
|
||||
return self._priority
|
||||
end
|
||||
|
||||
function Mission:SpawnPersistedState() end
|
||||
function Mission:SpawnActive() end
|
||||
|
||||
---comment
|
||||
---@param checkHealth boolean
|
||||
---@param messageIfDone boolean
|
||||
function Mission:UpdateState(checkHealth, messageIfDone) end
|
||||
---@param _checkHealth boolean
|
||||
---@param _messageIfDone boolean
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
function Mission:UpdateState(_checkHealth, _messageIfDone) end
|
||||
function Mission:StartCheckingContinuous() end
|
||||
|
||||
function Mission:PercentageComplete()
|
||||
@@ -85,6 +91,7 @@ function Mission:ShowBriefing(groupId)
|
||||
|
||||
local unitType = DcsUtil.getUnitTypeFromGroup(group)
|
||||
local coords = DcsUtil.convertVec2ToUnitUsableType(self.location, unitType)
|
||||
if coords == nil then coords = "Could not make conversion" end
|
||||
self._logger:debug("Coords converted: " .. coords)
|
||||
|
||||
local stateString = self:ToStateString()
|
||||
@@ -112,7 +119,7 @@ end
|
||||
function Mission:NotifyMissionComplete()
|
||||
self._missionCommandsHelper:RemoveMissionToCommands(self)
|
||||
self._logger:info("Mission Completed: " .. self.zoneName)
|
||||
trigger.action.outText("Mission " .. self.name .. " [" .. self.code .. "] was completed succesfully", 20)
|
||||
trigger.action.outText("Mission " .. self.name .. " [" .. self.code .. "] was completed successfully", 20)
|
||||
|
||||
for _, listener in pairs(self._completeListeners) do
|
||||
pcall(function()
|
||||
@@ -120,13 +127,20 @@ function Mission:NotifyMissionComplete()
|
||||
end)
|
||||
end
|
||||
|
||||
local succ, err = pcall(function()
|
||||
local _, _ = pcall(function()
|
||||
SpearheadAPI.Internal.notifyMissionComplete(self.zoneName)
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
function Mission:MarkMissionAreaToGroup(groupId) end
|
||||
function Mission:ForceMissionComplete()
|
||||
self._state = "COMPLETED"
|
||||
self:NotifyMissionComplete()
|
||||
end
|
||||
|
||||
---@param _groupId number
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
function Mission:MarkMissionAreaToGroup(_groupId) end
|
||||
|
||||
---endregion
|
||||
|
||||
@@ -138,29 +152,27 @@ function Mission:ToStateString() return "status: in progress" end
|
||||
|
||||
--endregion
|
||||
|
||||
do --aliases
|
||||
--- @alias MissionPriority
|
||||
--- | "none"
|
||||
--- | "primary"
|
||||
--- | "secondary"
|
||||
|
||||
--- @alias MissionPriority
|
||||
--- | "none"
|
||||
--- | "primary"
|
||||
--- | "secondary"
|
||||
--- @alias MissionType
|
||||
--- | "nil"
|
||||
--- | "STRIKE"
|
||||
--- | "DEEPSTRIKE"
|
||||
--- | "CAS"
|
||||
--- | "BAI"
|
||||
--- | "DEAD"
|
||||
--- | "SAM"
|
||||
--- | "OCA"
|
||||
--- | "LOGISTICS"
|
||||
---
|
||||
--- @alias MissionState
|
||||
--- | "NEW"
|
||||
--- | "WAITING"
|
||||
--- | "ACTIVE"
|
||||
--- | "COMPLETED"
|
||||
|
||||
--- @alias MissionType
|
||||
--- | "nil"
|
||||
--- | "STRIKE"
|
||||
--- | "CAS"
|
||||
--- | "BAI"
|
||||
--- | "DEAD"
|
||||
--- | "SAM"
|
||||
--- | "OCA"
|
||||
--- | "LOGISTICS"
|
||||
|
||||
--- @alias MissionState
|
||||
--- | "NEW"
|
||||
--- | "WAITING"
|
||||
--- | "ACTIVE"
|
||||
--- | "COMPLETED"
|
||||
|
||||
end
|
||||
|
||||
return Mission
|
||||
@@ -0,0 +1,112 @@
|
||||
local Logger = require("classes.util.Logger")
|
||||
local logger = Logger.new("ImageDisplay")
|
||||
|
||||
---@class ImageDisplay
|
||||
local ImageDisplay = {}
|
||||
|
||||
---@param unit Unit
|
||||
---@param settings ImageDisplaySettings
|
||||
function ImageDisplay.outImageToUnit(unit, settings)
|
||||
if unit == nil then return end
|
||||
settings:ValidateOrSetDefaults() --Overwrites wrong settings
|
||||
if settings.picPath == nil then
|
||||
logger:warn("No picture path provided for unit display.")
|
||||
return -- Nothing to display
|
||||
end
|
||||
ImageDisplay.outImage("a_out_picture_u",
|
||||
unit,
|
||||
settings.picPath,
|
||||
settings.duration,
|
||||
settings.clearview,
|
||||
settings.delay,
|
||||
settings.horizontalAlignment,
|
||||
settings.verticalAlignment,
|
||||
settings.size,
|
||||
settings.sizeUnits
|
||||
)
|
||||
end
|
||||
|
||||
---@param group Group
|
||||
---@param settings ImageDisplaySettings
|
||||
function ImageDisplay.outImageToGroup(group, settings)
|
||||
if group == nil then return end
|
||||
settings:ValidateOrSetDefaults() --Overwrites wrong settings
|
||||
if settings.picPath == nil then
|
||||
logger:warn("No picture path provided for group display.")
|
||||
return -- Nothing to display
|
||||
end
|
||||
ImageDisplay.outImage("a_out_picture_g",
|
||||
group,
|
||||
settings.picPath,
|
||||
settings.duration,
|
||||
settings.clearview,
|
||||
settings.delay,
|
||||
settings.horizontalAlignment,
|
||||
settings.verticalAlignment,
|
||||
settings.size,
|
||||
settings.sizeUnits
|
||||
)
|
||||
end
|
||||
|
||||
---@alias OutPictureTriggerName
|
||||
---| "a_out_picture_u"
|
||||
---| "a_out_picture_g"
|
||||
|
||||
---@private
|
||||
---@param object Unit|Group
|
||||
---@param triggerName OutPictureTriggerName
|
||||
---@param picPath string
|
||||
---@param duration number
|
||||
---@param clearview boolean
|
||||
---@param delay number
|
||||
---@param horizontalAlignment ImageDisplaySettings.HorizontalAlignment
|
||||
---@param verticalAlignment ImageDisplaySettings.VerticalAlignment
|
||||
---@param size number
|
||||
---@param sizeUnits ImageDisplaySettings.SizeUnits
|
||||
function ImageDisplay.outImage(triggerName, object, picPath, duration, clearview, delay, horizontalAlignment, verticalAlignment, size, sizeUnits)
|
||||
|
||||
---@type number
|
||||
local id
|
||||
if triggerName == "a_out_picture_u" then
|
||||
object = object --[[@as Unit]]
|
||||
id = object:getID()
|
||||
elseif triggerName == "a_out_picture_g" then
|
||||
object = object --[[@as Group]]
|
||||
id = object:getID()
|
||||
end
|
||||
|
||||
--[[
|
||||
a_out_picture_u(
|
||||
unitID:number,
|
||||
filePath:string,
|
||||
showTime:number,
|
||||
clearview:boolean,
|
||||
startDelayTime:number,
|
||||
horzAlignment:string,
|
||||
vertAlignment:string,
|
||||
size:number,
|
||||
size_units:string
|
||||
) -- args may not correct.
|
||||
]]
|
||||
|
||||
local cmdString = string.format(triggerName .. '(%d, "%s", %d, %s, %d, "%d", "%d", %d, "%d")',
|
||||
id,
|
||||
picPath,
|
||||
duration,
|
||||
clearview,
|
||||
delay,
|
||||
horizontalAlignment,
|
||||
verticalAlignment,
|
||||
size,
|
||||
sizeUnits)
|
||||
|
||||
local f, err = loadstring(cmdString)
|
||||
if f then
|
||||
f()
|
||||
else
|
||||
logger:error("Something failed when drawing complex drawing" .. err)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return ImageDisplay
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
|
||||
---@class ImageDisplaySettings
|
||||
---@field picPath string?
|
||||
---@field duration number
|
||||
---@field clearview boolean
|
||||
---@field delay number
|
||||
---@field horizontalAlignment ImageDisplaySettings.HorizontalAlignment
|
||||
---@field verticalAlignment ImageDisplaySettings.VerticalAlignment
|
||||
---@field size number
|
||||
---@field sizeUnits ImageDisplaySettings.SizeUnits
|
||||
local ImageDisplaySettings = {}
|
||||
ImageDisplaySettings.__index = ImageDisplaySettings
|
||||
|
||||
---@enum ImageDisplaySettings.VerticalAlignment
|
||||
ImageDisplaySettings.VerticalAlignment = {
|
||||
Top = "0",
|
||||
Center = "1",
|
||||
Bottom = "2"
|
||||
}
|
||||
|
||||
---@enum ImageDisplaySettings.HorizontalAlignment
|
||||
ImageDisplaySettings.HorizontalAlignment = {
|
||||
Left = "0",
|
||||
Center = "1",
|
||||
Right = "2"
|
||||
}
|
||||
|
||||
---@enum ImageDisplaySettings.SizeUnits
|
||||
ImageDisplaySettings.SizeUnits = {
|
||||
OriginalSize = "0",
|
||||
WindowSize = "1"
|
||||
}
|
||||
|
||||
function ImageDisplaySettings.new()
|
||||
local self = setmetatable({}, ImageDisplaySettings)
|
||||
self.picPath = nil
|
||||
self.duration = 30
|
||||
self.clearview = false
|
||||
self.delay = 0
|
||||
self.horizontalAlignment = ImageDisplaySettings.HorizontalAlignment.Center
|
||||
self.verticalAlignment = ImageDisplaySettings.VerticalAlignment.Center
|
||||
self.size = 100
|
||||
self.sizeUnits = ImageDisplaySettings.SizeUnits.OriginalSize
|
||||
return self
|
||||
end
|
||||
|
||||
function ImageDisplaySettings:ValidateOrSetDefaults()
|
||||
if self.picPath == "" then self.picPath = nil end
|
||||
if self.duration == nil or self.duration <= 0 then self.duration = 30 end
|
||||
if self.clearview == nil then self.clearview = false end
|
||||
if self.delay == nil then self.delay = 0 end
|
||||
if self.horizontalAlignment == nil then self.horizontalAlignment = ImageDisplaySettings.HorizontalAlignment.Center end
|
||||
if self.verticalAlignment == nil then self.verticalAlignment = ImageDisplaySettings.VerticalAlignment.Center end
|
||||
if self.size == nil or self.size <= 0 then self.size = 100 end
|
||||
if self.sizeUnits == nil then self.sizeUnits = ImageDisplaySettings.SizeUnits.OriginalSize end
|
||||
end
|
||||
|
||||
return ImageDisplaySettings
|
||||
@@ -32,6 +32,10 @@ do -- INIT DCS_UTIL
|
||||
---| "Cilinder"
|
||||
---| "Polygon"
|
||||
|
||||
---@class KeyValuePair
|
||||
---@field key string
|
||||
---@field value any
|
||||
|
||||
---@class SpearheadTriggerZone
|
||||
---@field name string
|
||||
---@field location Vec2
|
||||
@@ -40,7 +44,7 @@ do -- INIT DCS_UTIL
|
||||
---@field zone_type SpearheadTriggerZoneType
|
||||
---@field properties Array<KeyValuePair>?
|
||||
|
||||
---@type Array<SpearheadTriggerZone>
|
||||
---@type table<string, SpearheadTriggerZone>
|
||||
DCS_UTIL.__trigger_zones = {}
|
||||
end
|
||||
|
||||
@@ -66,18 +70,21 @@ do -- INIT DCS_UTIL
|
||||
STATIC = 5 --CUSTOM CATEGORY
|
||||
}
|
||||
|
||||
---@type table<string, string>
|
||||
DCS_UTIL.__airbaseNamesById = {}
|
||||
|
||||
---@type table<string, SpearheadTriggerZone>
|
||||
DCS_UTIL.__airbaseZonesByName = {}
|
||||
|
||||
---@type table<string, CoalitionSide>
|
||||
DCS_UTIL.__airportsStartingCoalition = {}
|
||||
---@type table<string, CoalitionSide>
|
||||
DCS_UTIL.__warehouseStartingCoalition = {}
|
||||
function DCS_UTIL.__INIT()
|
||||
do -- INITS ALL TABLES WITH DATA THAT's from the MIZ environment
|
||||
|
||||
do --init trigger zones
|
||||
for i, trigger_zone in pairs(env.mission.triggers.zones) do
|
||||
for _, trigger_zone in pairs(env.mission.triggers.zones) do
|
||||
-- reorder verts as they are not ordered correctly in the ME
|
||||
local verts = {}
|
||||
if Util.tableLength(trigger_zone.verticies) >= 4 then
|
||||
@@ -104,8 +111,8 @@ do -- INIT DCS_UTIL
|
||||
|
||||
if trigger_zone.properties then
|
||||
for _, kvPair in pairs(trigger_zone.properties) do
|
||||
local key = kvPair["key"]
|
||||
local value = kvPair["value"]
|
||||
local key = kvPair.key
|
||||
local value = kvPair.value
|
||||
zone.properties[#zone.properties + 1] = { key = key, value = value }
|
||||
end
|
||||
end
|
||||
@@ -116,6 +123,7 @@ do -- INIT DCS_UTIL
|
||||
|
||||
do -- init airports and warehouses
|
||||
if env.warehouses.airports then
|
||||
env.warehouses.airports = env.warehouses.airports --[[@as table<string, any>]]
|
||||
for warehouse_id, value in pairs(env.warehouses.airports) do
|
||||
if warehouse_id ~= nil then
|
||||
warehouse_id = tostring(warehouse_id) or "nil"
|
||||
@@ -126,12 +134,12 @@ do -- INIT DCS_UTIL
|
||||
end
|
||||
|
||||
if env.warehouses.warehouses then
|
||||
DCS_UTIL.__warehouseStartingCoalition[-1] = "placeholder"
|
||||
env.warehouses.warehouses = env.warehouses.warehouses --[[@as table<number, any>]]
|
||||
for warehouse_id, value in pairs(env.warehouses.warehouses) do
|
||||
if warehouse_id ~= nil then
|
||||
warehouse_id = tostring(warehouse_id) or "nil"
|
||||
local warehouse_id_str = tostring(warehouse_id) or "nil"
|
||||
local coalitionNumber = DCS_UTIL.stringToCoalition(value.coalition)
|
||||
DCS_UTIL.__warehouseStartingCoalition[warehouse_id] = coalitionNumber
|
||||
DCS_UTIL.__warehouseStartingCoalition[warehouse_id_str] = coalitionNumber
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -192,7 +200,7 @@ do -- INIT DCS_UTIL
|
||||
BLUE = 2
|
||||
}
|
||||
]] --
|
||||
local input = string.lower(input)
|
||||
input = string.lower(input)
|
||||
if input == 'neutrals' or input == "neutral" or input == "0" then
|
||||
return DCS_UTIL.Coalition.NEUTRAL
|
||||
end
|
||||
@@ -218,11 +226,13 @@ do -- INIT DCS_UTIL
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- takes a list of units and returns all the units that are in any of the zones
|
||||
---@param unit_names table unit names
|
||||
---@param zone_names table zone names
|
||||
---@return table unit list of objects { unit = UNIT, zone_name = zoneName}
|
||||
---@param unit_names Array<string> unit names
|
||||
---@param zone_names Array<string> zone names
|
||||
---@return table<number, { unit : Unit|StaticObject, zone_name: string }>
|
||||
function DCS_UTIL.getUnitsInZones(unit_names, zone_names)
|
||||
---@type Array<Unit|StaticObject>
|
||||
local units = {}
|
||||
|
||||
---@type Array<SpearheadTriggerZone>
|
||||
@@ -235,20 +245,25 @@ do -- INIT DCS_UTIL
|
||||
end
|
||||
end
|
||||
|
||||
for index, zone_name in pairs(zone_names) do
|
||||
for _, zone_name in pairs(zone_names) do
|
||||
local zone = DCS_UTIL.__trigger_zones[zone_name]
|
||||
if zone then
|
||||
zones[#zones + 1] = zone
|
||||
end
|
||||
end
|
||||
|
||||
---@type table<number, { unit : Unit|StaticObject, zone_name: string }>
|
||||
local in_zone_units = {}
|
||||
for units_ind = 1, #units do
|
||||
local lUnit = units[units_ind]
|
||||
local unit_pos = lUnit:getPosition().p
|
||||
local isActive = true
|
||||
local lCat = Object.getCategory(lUnit)
|
||||
for zone_name, zone in pairs(zones) do
|
||||
if unit_pos and ((lCat == 1 and lUnit:isActive() == true) or lCat ~= 1) then -- it is a unit and is active or it is not a unit
|
||||
if lCat == Object.Category.UNIT then
|
||||
local unit = lUnit --[[@as Unit]]
|
||||
isActive = unit:isActive() == true
|
||||
end
|
||||
local unit_pos = lUnit:getPosition().p
|
||||
for _, zone in pairs(zones) do
|
||||
if unit_pos and isActive == true then -- it is a unit and is active or it is not a unit
|
||||
local isInZone = Util.is3dPointInZone(unit_pos, zone)
|
||||
if isInZone == true then
|
||||
in_zone_units[#in_zone_units + 1] = { unit = lUnit, zone_name = zone.name }
|
||||
@@ -262,9 +277,9 @@ do -- INIT DCS_UTIL
|
||||
--- takes a list of groups and returns all the group leaders that are in any of the zones
|
||||
---@param group_names table unit names
|
||||
---@param zone_name string zone names
|
||||
---@return table groupnames list of group names
|
||||
---@return Array<string> groupnames list of group names
|
||||
function DCS_UTIL.getGroupsInZone(group_names, zone_name)
|
||||
local zone = DCS_UTIL.__trigger_zones[zone_name]
|
||||
local zone = DCS_UTIL.__trigger_zones[zone_name] --[[@as SpearheadTriggerZone?]]
|
||||
if zone == nil then
|
||||
return {}
|
||||
end
|
||||
@@ -277,11 +292,13 @@ do -- INIT DCS_UTIL
|
||||
---@param zone SpearheadTriggerZone
|
||||
---@return Array<string> groupnames list of groups that are in the zone
|
||||
function DCS_UTIL.areGroupsInCustomZone(group_names, zone)
|
||||
---@type Array<{ unit : Unit|StaticObject, groupname: string }>
|
||||
local units = {}
|
||||
if Util.tableLength(group_names) < 1 then return {} end
|
||||
|
||||
for k = 1, #group_names do
|
||||
local entry = nil
|
||||
---@type { unit : Unit|StaticObject|nil, groupname: string }|nil
|
||||
local entry
|
||||
local group = Group.getByName(group_names[k])
|
||||
if group ~= nil then
|
||||
entry = { unit = group:getUnit(1), groupname = group_names[k] }
|
||||
@@ -290,7 +307,7 @@ do -- INIT DCS_UTIL
|
||||
end
|
||||
|
||||
if entry and entry.unit and entry.unit:isExist() == true then
|
||||
units[#units + 1] = entry
|
||||
units[#units + 1] = { groupname = entry.groupname, unit = entry.unit }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -308,20 +325,20 @@ do -- INIT DCS_UTIL
|
||||
--- takes a x, y poistion and checks if it is inside any of the zones
|
||||
---@param x number North South position
|
||||
---@param z number West East position
|
||||
---@param zone_names table zone names
|
||||
---@return table zones list of objects { zone_name = zoneName}
|
||||
---@param zone_names Array<string> zone names
|
||||
---@return Array<string> zones list of objects { zone_name = zoneName}
|
||||
function DCS_UTIL.isPositionInZones(x, z, zone_names)
|
||||
---@type Array<SpearheadTriggerZone>
|
||||
local zones = {}
|
||||
for index, zone_name in pairs(zone_names) do
|
||||
for _, zone_name in pairs(zone_names) do
|
||||
local zone = DCS_UTIL.__trigger_zones[zone_name]
|
||||
if zone then
|
||||
zones[#zones + 1] = zone
|
||||
end
|
||||
end
|
||||
|
||||
---@type Array<string>
|
||||
local result_zones = {}
|
||||
for zone_name, zone in pairs(zones) do
|
||||
for _, zone in pairs(zones) do
|
||||
if Util.is3dPointInZone({ x = x, z = z, y = 0 }, zone) == true then
|
||||
result_zones[#result_zones + 1] = zone.name
|
||||
end
|
||||
@@ -396,6 +413,18 @@ do -- INIT DCS_UTIL
|
||||
return nil;
|
||||
end
|
||||
|
||||
---@return Array<string>
|
||||
function DCS_UTIL.getAllGroupCategoryNames()
|
||||
return {
|
||||
"airplane",
|
||||
"helicopter",
|
||||
"ground",
|
||||
"ship",
|
||||
"train",
|
||||
"static"
|
||||
}
|
||||
end
|
||||
|
||||
---@type table<string, CoordType>
|
||||
local config =
|
||||
{
|
||||
@@ -408,13 +437,14 @@ do -- INIT DCS_UTIL
|
||||
|
||||
|
||||
---@param location Vec2
|
||||
---@param unitType string
|
||||
---@param unitType string?
|
||||
---@return string?
|
||||
function DCS_UTIL.convertVec2ToUnitUsableType(location, unitType)
|
||||
|
||||
local height = land.getHeight(location)
|
||||
local vec3 = { x = location.x, y = height, z = location.y }
|
||||
|
||||
local unitType = string.lower(unitType or "")
|
||||
unitType = string.lower(unitType or "")
|
||||
local conversionType = config[unitType]
|
||||
|
||||
if not conversionType then conversionType = "DDM" end
|
||||
@@ -429,6 +459,7 @@ do -- INIT DCS_UTIL
|
||||
---@private
|
||||
---@param location Vec3
|
||||
---@param coordType CoordType
|
||||
---@return string?
|
||||
function DCS_UTIL.convertToDisplayCoord(location, coordType)
|
||||
local lattitude, longitude, altitude = coord.LOtoLL(location)
|
||||
|
||||
@@ -440,7 +471,7 @@ do -- INIT DCS_UTIL
|
||||
-- Convert DD to DDM (Degrees Decimal Minutes)
|
||||
local function dd_to_ddm(dd)
|
||||
local degrees = math.floor(math.abs(dd))
|
||||
local minutes = (math.abs(dd) - degrees) * 60
|
||||
local minutes = (math.abs(dd) - degrees) * 60 --[[@as number]]
|
||||
local sign = dd >= 0 and 1 or -1
|
||||
return degrees * sign, minutes
|
||||
end
|
||||
@@ -559,6 +590,7 @@ do -- INIT DCS_UTIL
|
||||
end
|
||||
|
||||
---@param group Group
|
||||
---@return string? unit type name
|
||||
function DCS_UTIL.getUnitTypeFromGroup(group)
|
||||
for _, unit in pairs(group:getUnits()) do
|
||||
if unit and unit:isExist() then
|
||||
@@ -571,10 +603,11 @@ do -- INIT DCS_UTIL
|
||||
---comment Get all units that are players
|
||||
---@return Array<Unit> units
|
||||
function DCS_UTIL.getAllPlayerUnits()
|
||||
---@type Array<Unit>
|
||||
local units = {}
|
||||
for i = 0, 2 do
|
||||
local players = coalition.getPlayers(i)
|
||||
for key, unit in pairs(players) do
|
||||
for _, unit in pairs(players) do
|
||||
units[#units + 1] = unit
|
||||
end
|
||||
end
|
||||
@@ -616,9 +649,9 @@ do -- INIT DCS_UTIL
|
||||
return result
|
||||
end
|
||||
|
||||
---@param unitName string
|
||||
function DCS_UTIL.CleanCorpse(unitName)
|
||||
local unitName = "dead_" .. unitName
|
||||
|
||||
unitName = "dead_" .. unitName
|
||||
local object = StaticObject.getByName(unitName)
|
||||
|
||||
if object then
|
||||
@@ -632,15 +665,21 @@ do -- INIT DCS_UTIL
|
||||
---@field b number
|
||||
---@field a number
|
||||
|
||||
local __drawID = 4210
|
||||
|
||||
function DCS_UTIL.GetNextDrawID()
|
||||
__drawID = __drawID + 1
|
||||
return __drawID
|
||||
end
|
||||
|
||||
---@param groupID number
|
||||
---@param text string
|
||||
---@param location Vec3
|
||||
---@return number markID
|
||||
function DCS_UTIL.AddMarkToGroup(groupID, text, location)
|
||||
drawID = drawID + 1
|
||||
trigger.action.markToGroup(drawID, text, location, groupID, true, nil)
|
||||
return drawID
|
||||
local nextId = DCS_UTIL.GetNextDrawID()
|
||||
trigger.action.markToGroup(nextId, text, location, groupID, true, nil)
|
||||
return nextId
|
||||
end
|
||||
|
||||
---comment
|
||||
@@ -648,9 +687,9 @@ do -- INIT DCS_UTIL
|
||||
---@param location Vec3
|
||||
---@return integer
|
||||
function DCS_UTIL.AddMarkToAll(text, location)
|
||||
drawID = drawID + 1
|
||||
trigger.action.markToAll(drawID, text, location, true, nil)
|
||||
return drawID
|
||||
local nextId = DCS_UTIL.GetNextDrawID()
|
||||
trigger.action.markToAll(nextId, text, location, true, nil)
|
||||
return nextId
|
||||
end
|
||||
|
||||
---@param markId number
|
||||
@@ -677,6 +716,10 @@ do -- INIT DCS_UTIL
|
||||
---@param drawID number
|
||||
---@param fillColor DrawColor
|
||||
function DCS_UTIL.SetFillColor(drawID, fillColor)
|
||||
if fillColor == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local lineColorMapped = {
|
||||
fillColor.r or 0,
|
||||
fillColor.g or 0,
|
||||
@@ -695,7 +738,7 @@ do -- INIT DCS_UTIL
|
||||
|
||||
---@return number? id
|
||||
function DCS_UTIL.GetNeutralCountry()
|
||||
for name, id in pairs(country.id) do
|
||||
for _, id in pairs(country.id) do
|
||||
if coalition.getCountryCoalition(id) == DCS_UTIL.Coalition.NEUTRAL then
|
||||
return id
|
||||
end
|
||||
@@ -725,6 +768,8 @@ do -- INIT DCS_UTIL
|
||||
return false
|
||||
end
|
||||
|
||||
---@param groupName string
|
||||
---@param offset number?
|
||||
---@return boolean
|
||||
function DCS_UTIL.IsBingoFuel(groupName, offset)
|
||||
if offset == nil then offset = 0 end
|
||||
@@ -749,7 +794,7 @@ do -- INIT DCS_UTIL
|
||||
function DCS_UTIL.GetPlayerGroupByGroupID(groupId)
|
||||
for i = 0, 2 do
|
||||
local players = coalition.getPlayers(i)
|
||||
for key, unit in pairs(players) do
|
||||
for _, unit in pairs(players) do
|
||||
if unit and unit:isExist() == true then
|
||||
local group = unit:getGroup()
|
||||
if group and group:getID() == groupId then
|
||||
@@ -765,7 +810,7 @@ do -- INIT DCS_UTIL
|
||||
function DCS_UTIL.GetPlayerUnitByID(unitID)
|
||||
for i = 0, 2 do
|
||||
local players = coalition.getPlayers(i)
|
||||
for key, unit in pairs(players) do
|
||||
for _, unit in pairs(players) do
|
||||
if unit and unit:getID() == unitID then
|
||||
return unit
|
||||
end
|
||||
|
||||
@@ -3,6 +3,13 @@ local Util = require("classes.util.Util")
|
||||
local SpearheadConfig = require("classes.configuration.GlobalConfig")
|
||||
|
||||
|
||||
--- @alias LogLevel
|
||||
--- | "DEBUG"
|
||||
--- | "INFO"
|
||||
--- | "WARN"
|
||||
--- | "ERROR"
|
||||
--- | "NONE"
|
||||
|
||||
---@type LogLevel
|
||||
local defaultLogLevel = "INFO"
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
|
||||
---@class MissionEditingWarnings
|
||||
local MissionEditingWarnings = {}
|
||||
|
||||
---@type table<number, string>
|
||||
MissionEditingWarnings.warnings = {}
|
||||
|
||||
function MissionEditingWarnings.Add(warningMessage)
|
||||
table.insert(MissionEditingWarnings, warningMessage or "skip")
|
||||
table.insert(MissionEditingWarnings.warnings, warningMessage or "skip")
|
||||
end
|
||||
|
||||
---@param logger Logger
|
||||
@@ -12,12 +16,12 @@ function MissionEditingWarnings.WriteAll(logger)
|
||||
return
|
||||
end
|
||||
|
||||
if not MissionEditingWarnings or #MissionEditingWarnings == 0 then
|
||||
if not MissionEditingWarnings.warnings or #MissionEditingWarnings.warnings == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
logger:warn("Mission Editor Warnings:")
|
||||
for _, warning in ipairs(MissionEditingWarnings) do
|
||||
for _, warning in ipairs(MissionEditingWarnings.warnings) do
|
||||
logger:warn("- " .. warning)
|
||||
end
|
||||
|
||||
|
||||
+65
-27
@@ -1,13 +1,13 @@
|
||||
---@class Util
|
||||
local UTIL = {}
|
||||
do -- INIT UTIL
|
||||
---splits a string in sub parts by seperator
|
||||
---splits a string in sub parts by separator
|
||||
---@param input string
|
||||
---@param seperator string
|
||||
---@return table result list of strings
|
||||
function UTIL.split_string(input, seperator)
|
||||
if seperator == nil then
|
||||
seperator = " "
|
||||
---@param separator string
|
||||
---@return Array<string> result list of strings
|
||||
function UTIL.split_string(input, separator)
|
||||
if separator == nil then
|
||||
separator = " "
|
||||
end
|
||||
|
||||
local result = {}
|
||||
@@ -15,14 +15,14 @@ do -- INIT UTIL
|
||||
return result
|
||||
end
|
||||
|
||||
for str in string.gmatch(input, "[^" .. seperator .. "]+") do
|
||||
for str in string.gmatch(input, "[^" .. separator .. "]+") do
|
||||
table.insert(result, str)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param table any
|
||||
---@param table table<any, any>
|
||||
---@return number
|
||||
function UTIL.tableLength(table)
|
||||
if table == nil then return 0 end
|
||||
@@ -35,23 +35,34 @@ do -- INIT UTIL
|
||||
---@param orig table
|
||||
---@return table copy
|
||||
function UTIL.deepCopyTable(orig)
|
||||
local orig_type = type(orig)
|
||||
local copy
|
||||
if orig_type == 'table' then
|
||||
copy = {}
|
||||
for orig_key, orig_value in next, orig, nil do
|
||||
copy[UTIL.deepCopyTable(orig_key)] = UTIL.deepCopyTable(orig_value)
|
||||
|
||||
---@param original any
|
||||
---@return any
|
||||
local function deepCopy(original)
|
||||
local orig_type = type(original)
|
||||
---@type any
|
||||
local copy
|
||||
if orig_type == 'table' then
|
||||
original = original --[[@as table<any, any>]]
|
||||
copy = {} --[[@as table<any, any>]]
|
||||
for orig_key, orig_value in pairs(original) do
|
||||
local copiedKey = deepCopy(orig_key) --[[@as any]]
|
||||
copy[copiedKey] = deepCopy(orig_value)
|
||||
end
|
||||
setmetatable(copy, deepCopy(getmetatable(original)))
|
||||
else -- number, string, boolean, etc
|
||||
copy = original --[[@as any]]
|
||||
end
|
||||
setmetatable(copy, UTIL.deepCopyTable(getmetatable(orig)))
|
||||
else -- number, string, boolean, etc
|
||||
copy = orig
|
||||
return copy
|
||||
end
|
||||
return copy
|
||||
|
||||
return deepCopy(orig)
|
||||
end
|
||||
|
||||
---Gets a random from the list
|
||||
---@param list Array
|
||||
---@return any @random element from the list
|
||||
---@generic T
|
||||
---@param list Array<T>
|
||||
---@return T @random element from the list
|
||||
function UTIL.randomFromList(list)
|
||||
local max = #list
|
||||
|
||||
@@ -65,11 +76,13 @@ do -- INIT UTIL
|
||||
return list[random]
|
||||
end
|
||||
|
||||
---@param list Array
|
||||
---@generic T
|
||||
---@param list Array<T>
|
||||
---@param start number start
|
||||
---@param n number length
|
||||
---@return Array
|
||||
---@return Array<T>
|
||||
function UTIL.sublist(list, start, n)
|
||||
---@type table<number, any>
|
||||
local result = {}
|
||||
for i = start, n do
|
||||
result[#result + 1] = list[i]
|
||||
@@ -90,9 +103,10 @@ do -- INIT UTIL
|
||||
end
|
||||
|
||||
local function table_print(tt, indent, done)
|
||||
done = done or {}
|
||||
done = done or {} --[[@as table<any, boolean>]]
|
||||
indent = indent or 0
|
||||
if type(tt) == "table" then
|
||||
tt = tt --[[@as table<any, any>]]
|
||||
local sb = {}
|
||||
for key, value in pairs(tt) do
|
||||
table.insert(sb, string.rep(" ", indent)) -- indent it
|
||||
@@ -120,7 +134,7 @@ do -- INIT UTIL
|
||||
---@param findable string
|
||||
---@param ignoreCase boolean?
|
||||
---@return boolean
|
||||
UTIL.startswith = function(str, findable, ignoreCase)
|
||||
UTIL.startsWith = function(str, findable, ignoreCase)
|
||||
if ignoreCase == true then
|
||||
return string.lower(str):find('^' .. string.lower(findable)) ~= nil
|
||||
end
|
||||
@@ -128,6 +142,14 @@ do -- INIT UTIL
|
||||
return str:find('^' .. findable) ~= nil
|
||||
end
|
||||
|
||||
UTIL.endsWith = function(str, findable, ignoreCase)
|
||||
if ignoreCase == true then
|
||||
return string.lower(str):find(string.lower(findable) .. '$') ~= nil
|
||||
end
|
||||
|
||||
return str:find(findable .. '$') ~= nil
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param str string
|
||||
---@param findable string
|
||||
@@ -138,11 +160,11 @@ do -- INIT UTIL
|
||||
|
||||
---comment
|
||||
---@param str string
|
||||
---@param findableTable table
|
||||
---@param findableTable table<any,any>
|
||||
---@return boolean
|
||||
UTIL.startswithAny = function(str, findableTable)
|
||||
for key, value in pairs(findableTable) do
|
||||
if type(value) == "string" and UTIL.startswith(str, value) then return true end
|
||||
for _, value in pairs(findableTable) do
|
||||
if type(value) == "string" and UTIL.startsWith(str, value) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
@@ -230,7 +252,13 @@ do -- INIT UTIL
|
||||
return ((vec1Norm.x * vec2Norm.x) + (vec1Norm.y * vec2Norm.y) + (vec1Norm.z * vec2Norm.z))
|
||||
end
|
||||
|
||||
---@param polygon Array<Vec2> of pairs { x, y }
|
||||
---@param x number X location
|
||||
---@param y number Y location
|
||||
---@return boolean
|
||||
local function isInComplexPolygon(polygon, x, y)
|
||||
---@param poly Array<Vec2> of pairs { x, y }
|
||||
---@return Array<{ x1: number, z1: number, x2: number, z2: number }>
|
||||
local function getEdges(poly)
|
||||
local result = {}
|
||||
for i = 1, #poly do
|
||||
@@ -267,6 +295,7 @@ do -- INIT UTIL
|
||||
|
||||
---@param point Vec3
|
||||
---@param zone SpearheadTriggerZone
|
||||
---@return boolean
|
||||
function UTIL.is3dPointInZone(point, zone)
|
||||
if zone.zone_type == "Polygon" and zone.verts then
|
||||
if UTIL.IsPointInPolygon(zone.verts, point.x, point.z) == true then
|
||||
@@ -283,6 +312,7 @@ do -- INIT UTIL
|
||||
|
||||
---@param point Vec2
|
||||
---@param zone SpearheadTriggerZone
|
||||
---@return boolean
|
||||
function UTIL.is2dPointInZone(point, zone)
|
||||
if zone.zone_type == "Polygon" and zone.verts then
|
||||
if UTIL.IsPointInPolygon(zone.verts, point.x, point.y) == true then
|
||||
@@ -391,7 +421,9 @@ do -- INIT UTIL
|
||||
if #points == 0 then return {} end
|
||||
|
||||
-- Simple clustering: group points that are within minSeparation of each other
|
||||
---@type Array<Array<Vec2>>
|
||||
local clusters = {}
|
||||
---@type table<integer, boolean>
|
||||
local assigned = {}
|
||||
|
||||
for i, p in ipairs(points) do
|
||||
@@ -432,6 +464,8 @@ do -- INIT UTIL
|
||||
end
|
||||
|
||||
---@param points Array<Vec2>
|
||||
---@param meters number
|
||||
---@return Array<Vec2>
|
||||
function UTIL.enlargeConvexHull(points, meters)
|
||||
if points == nil or #points == 0 then
|
||||
return {}
|
||||
@@ -509,6 +543,10 @@ function UTIL.GetTangentHullPointsFromOrigin(hull, origin)
|
||||
return {}
|
||||
end
|
||||
|
||||
---@param a Vec2
|
||||
---@param b Vec2
|
||||
---@param c Vec2
|
||||
---@return number
|
||||
local function orientation(a, b, c)
|
||||
-- Returns >0 if c is to the left of ab, <0 if to the right, 0 if colinear
|
||||
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
|
||||
|
||||
+22
-22
@@ -1,13 +1,14 @@
|
||||
--Single player purpose
|
||||
|
||||
local Logger = require("classes.util.Logger")
|
||||
local DcsUtil = require("classes.util.DcsUtil")
|
||||
local Database = require("classes.spearhead_db")
|
||||
local SpearheadEvents = require("classes.spearhead_events")
|
||||
local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper")
|
||||
local GlobalConfig = require("classes.configuration.GlobalConfig")
|
||||
local CapConfig = require("classes.configuration.CapConfig")
|
||||
local StageConfig = require("classes.configuration.StageConfig")
|
||||
local Persistence = require("classes.persistence.Persistence")
|
||||
local PersistenceConfig = require("classes.configuration.PersistenceConfig")
|
||||
local SpawnManager = require("classes.helpers.SpawnManager")
|
||||
local DetectionManager = require("classes.capClasses.detection.DetectionManager")
|
||||
local GlobalCapManager = require("classes.capClasses.GlobalCapManager")
|
||||
@@ -33,19 +34,11 @@ MissionCommandsHelper.getOrCreate() -- initiate
|
||||
local capConfig = CapConfig:new();
|
||||
local stageConfig = StageConfig:getInstance();
|
||||
|
||||
local startingStage = stageConfig.startingStage or 1
|
||||
if SpearheadConfig and SpearheadConfig.Persistence and SpearheadConfig.Persistence.enabled == true then
|
||||
standardLogger:info("Persistence enabled")
|
||||
local persistenceConfig = PersistenceConfig.new()
|
||||
|
||||
if persistenceConfig and persistenceConfig:isEnabled() == true then
|
||||
local persistenceLogger = Logger.new("Persistence", defaultLogLevel)
|
||||
Persistence.Init(persistenceLogger)
|
||||
|
||||
local persistanceStage = Persistence.GetActiveStage()
|
||||
if persistanceStage then
|
||||
standardLogger:info("Persistance activated and using persistant active stage: " .. persistanceStage)
|
||||
startingStage = persistanceStage
|
||||
end
|
||||
else
|
||||
standardLogger:info("Persistence disabled")
|
||||
end
|
||||
|
||||
local spawnLogger = Logger.new("SpawnManager", defaultLogLevel)
|
||||
@@ -53,22 +46,29 @@ local spawnManager = SpawnManager.new(spawnLogger)
|
||||
local detectionLogger = Logger.new("DetectionManager", defaultLogLevel)
|
||||
local detectionManager = DetectionManager.New(detectionLogger)
|
||||
|
||||
GlobalCapManager.start(databaseManager, capConfig, detectionManager, stageConfig, defaultLogLevel, spawnManager)
|
||||
GlobalStageManager.NewAndStart(databaseManager, stageConfig, defaultLogLevel, spawnManager)
|
||||
GlobalCapManager.start(databaseManager, capConfig, detectionManager, defaultLogLevel, spawnManager)
|
||||
local globalStageManager = GlobalStageManager.new(databaseManager, stageConfig, defaultLogLevel, spawnManager)
|
||||
GlobalFleetManager.start(databaseManager)
|
||||
|
||||
local SetStageDelayed = function(number, time)
|
||||
SpearheadEvents.PublishStageNumberChanged(number)
|
||||
return nil
|
||||
end
|
||||
|
||||
timer.scheduleFunction(SetStageDelayed, startingStage, timer.getTime() + 3)
|
||||
|
||||
env.info(startTime .. "ms / " .. timer.getTime() * 1000 .. "ms")
|
||||
local duration = (timer.getTime() * 1000) - startTime
|
||||
standardLogger:info("Spearhead Initialisation duration: " .. tostring(duration) .. "ms")
|
||||
|
||||
local missionEditorWarningsLogger = Logger.new("MissionEditorWarnings", defaultLogLevel)
|
||||
MissionEditorWarnings.WriteAll(missionEditorWarningsLogger)
|
||||
GlobalStageManager:printFullOverview()
|
||||
globalStageManager:PrintMermaidStage()
|
||||
|
||||
local startDelayed = function()
|
||||
globalStageManager:Start()
|
||||
return nil
|
||||
end
|
||||
|
||||
timer.scheduleFunction(startDelayed, nil, timer.getTime() + 5) -- delay start by 5 seconds so every deleted item it first removed entirely
|
||||
|
||||
local globalConfig = GlobalConfig.New()
|
||||
if globalConfig and globalConfig:isDebugMenuEnabled() == true then
|
||||
local DebugMenu = require("classes.debug.DebugMenu")
|
||||
local debugMenu = DebugMenu.new()
|
||||
debugMenu:RegisterMenus()
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user