Author SHA1 Message Date
dutchie031 9db4756732 removed settings.json 2026-08-15 15:04:44 +00:00
dutchie031 9e4a8a1876 Updated wiring of supply hub commands 2026-08-15 15:04:44 +00:00
67 changed files with 868 additions and 5588 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

-10
View File
@@ -21,9 +21,6 @@
<main> <main>
<div class="reference-container"> <div class="reference-container">
<div class="side-nav"> <div class="side-nav">
<div id="beta-box-div"></div>
<h4>Quick Links</h4> <h4>Quick Links</h4>
<ul> <ul>
<li><a href="/pages/tutorials.html" class="side-nav-h2">Tutorials</a></li> <li><a href="/pages/tutorials.html" class="side-nav-h2">Tutorials</a></li>
@@ -62,13 +59,6 @@
<footer> <footer>
<p>&copy; 2025 Spearhead Project</p> <p>&copy; 2025 Spearhead Project</p>
</footer> </footer>
<script>
if (true || window.location.hostname.toLowerCase() == 'beta.spearhead.rocks') {
betaHtml = '<note-box type="warning" title="Beta Version">You are looking at docs that are released with the beta version.</note-box>';
}
document.getElementById('beta-box-div').innerHTML = betaHtml || '';
</script>
</body> </body>
</html> </html>
+1 -2
View File
@@ -19,10 +19,9 @@ class Header extends HTMLElement {
<div class="dropdown-content"> <div class="dropdown-content">
<a href="/pages/include-script.html">Include the Script</a> <a href="/pages/include-script.html">Include the Script</a>
<a href="/pages/first-start.html">First Start</a> <a href="/pages/first-start.html">First Start</a>
<a href="/pages/advanced/map-markings.html">Map Markings</a>
<a href="/pages/advanced/CAP.html">Advanced: CAP</a> <a href="/pages/advanced/CAP.html">Advanced: CAP</a>
<a href="/pages/advanced/missions.html">Advanced: Missions</a> <a href="/pages/advanced/missions.html">Advanced: Missions</a>
<a href="/pages/advanced/lanes.html">Advanced: Lanes</a> <a href="/pages/advanced/map-markings.html">Map Markings</a>
</div> </div>
</div> </div>
-6
View File
@@ -168,14 +168,8 @@ class Sidebar extends HTMLElement {
} }
render() { render() {
var betaHtml = '';
if (window.location.hostname.toLowerCase() == 'beta.spearhead.rocks') {
betaHtml = '<note-box type="warning" title="Beta Version">You are looking at docs that are released with the beta version.</note-box>';
}
this.innerHTML = ` this.innerHTML = `
<div class="side-nav"> <div class="side-nav">
${betaHtml}
<h4 class="side-nav-title"></h4> <h4 class="side-nav-title"></h4>
<ul> <ul>
<!-- Navigation items will be populated automatically --> <!-- Navigation items will be populated automatically -->
-183
View File
@@ -1,183 +0,0 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spearhead 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>&copy; 2025 Spearhead Project</p>
</footer>
</body>
</html>
-7
View File
@@ -253,13 +253,6 @@
To make an airbase buildable add a text box inside of the trigger zone and name it: <code-inline>buildable_[freeform]</code-inline> <br/> To make an airbase buildable add a text box inside of the trigger zone and name it: <code-inline>buildable_[freeform]</code-inline> <br/>
Then in the text box type amount of kilo's you want to be transfered before the logistisc mission is complete. <br/> Then in the text box type amount of kilo's you want to be transfered before the logistisc mission is complete. <br/>
<br>
To add a custom briefing for a buildable zone, include a text box inside the trigger zone and name it: <code-inline>supplybriefing_[freeform]</code-inline>.
<code-inline>{{coords}}</code-inline> is highly recommended so the players can easily find their landing zone.<br>
Additionally, the amount of kilos required and delivered are always shown on the bottom, as well as a note to not land in the construction zone.
<br/>
<br> <br>
The base or zone will slowly build up with each crate giving both a nice view of it happening AND it's good for performance as there's not a big addition of objects at once. <br/> The base or zone will slowly build up with each crate giving both a nice view of it happening AND it's good for performance as there's not a big addition of objects at once. <br/>
Right now a crate takes 15 seconds to unpack per 500kg. Meaning 2 crates of 1000kg will be faster than 1 crate of 2000kg. <br/> Right now a crate takes 15 seconds to unpack per 500kg. Meaning 2 crates of 1000kg will be faster than 1 crate of 2000kg. <br/>
+2 -1
View File
@@ -262,7 +262,7 @@ header nav a:hover::after {
overflow: hidden; overflow: hidden;
top: 100%; /* Position below the dropdown */ top: 100%; /* Position below the dropdown */
left: 0; /* Align left edge */ left: 0; /* Align left edge */
min-width: 240px; min-width: 160px;
background-color: var(--color-bg-secondary); background-color: var(--color-bg-secondary);
box-shadow: var(--shadow-medium); box-shadow: var(--shadow-medium);
@@ -336,6 +336,7 @@ main {
} }
.side-nav { .side-nav {
width: 200px;
min-width: 200px; min-width: 200px;
padding-left: 0; padding-left: 0;
overflow-y: auto; overflow-y: auto;
-16
View File
@@ -6,7 +6,6 @@ on:
- develop - develop
paths: paths:
- src/** - src/**
- .gitea/workflows/release-beta.yml
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@@ -88,18 +87,3 @@ jobs:
env: env:
NODE_OPTIONS: '--experimental-fetch' NODE_OPTIONS: '--experimental-fetch'
- name: Announce Release
uses: tsickert/discord-webhook@v7.0.0
with:
webhook-url: ${{ secrets.WEBHOOK_URL }}
content: "New Beta Release"
username: "Spearhead Release Bot"
#TODO: avatar-url spearhead avatar
thread-id: 1538520488442331227
embed-title: "Spearhead Release Beta"
embed-color: 3093247
embed-description:
${{ steps.release_notes.outputs.body }}
embed-url:
https://git.dutchie031.com/Spearhead/spearhead/releases/tag/${{ steps.read_version.outputs.tag }}
-15
View File
@@ -84,18 +84,3 @@ jobs:
env: env:
NODE_OPTIONS: '--experimental-fetch' NODE_OPTIONS: '--experimental-fetch'
- name: Announce Release
uses: tsickert/discord-webhook@v7.0.0
with:
webhook-url: ${{ secrets.WEBHOOK_URL }}
content: "New Beta Release"
username: "Spearhead Release Bot"
#TODO: avatar-url spearhead avatar
thread-id: 1538520596802175037
embed-title: "Spearhead Release"
embed-color: 3093247
embed-description:
${{ steps.release_notes.outputs.body }}
embed-url:
https://git.dutchie031.com/Spearhead/spearhead/releases/tag/${{ steps.read_version.outputs.tag }}
+3 -19
View File
@@ -4,14 +4,13 @@ on:
push: push:
branches: branches:
- main - main
- develop
paths: paths:
- '.docs/**' - '.docs/**'
env: env:
DOCKER_REGISTRY: registry.dutchie031.net DOCKER_REGISTRY: registry.dutchie031.net
DOCKER_IMAGE: spearhead-docs DOCKER_IMAGE: spearhead-docs
DOCKER_TAG: ${{ github.ref_name == 'main' && 'latest' || 'beta' }} DOCKER_TAG: latest
jobs: jobs:
## Run replacements and cleanups on the docs ## Run replacements and cleanups on the docs
@@ -37,7 +36,7 @@ jobs:
sed -i '/@@API_CODE@@/d' .docs/web/pages/spearheadapi.html sed -i '/@@API_CODE@@/d' .docs/web/pages/spearheadapi.html
rm .docs/web/pages/temp_api_code.html rm .docs/web/pages/temp_api_code.html
- name: Highlight Config code and update HTML - name: Highlight API code and update HTML
run: | run: |
pygmentize -f html -l lua -O noclasses,style=monokai ./config.lua > .docs/web/pages/temp_config_code.html pygmentize -f html -l lua -O noclasses,style=monokai ./config.lua > .docs/web/pages/temp_config_code.html
# Insert the highlighted code into the placeholder in spearheadapi.html # Insert the highlighted code into the placeholder in spearheadapi.html
@@ -106,30 +105,15 @@ jobs:
chmod 600 $HOME/.kube/config chmod 600 $HOME/.kube/config
cat $HOME/.kube/config cat $HOME/.kube/config
- name: Run Helm upgrade/install spearhead-docs (main) - name: Run Helm upgrade/install
if: github.ref_name == 'main'
run: | run: |
helm upgrade --install spearhead-docs .helm \ helm upgrade --install spearhead-docs .helm \
--namespace spearhead-docs \ --namespace spearhead-docs \
--create-namespace \ --create-namespace \
-f .helm/values.yaml \
--set image.repository=${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }} \ --set image.repository=${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }} \
--set image.tag=${{ env.DOCKER_TAG }} \ --set image.tag=${{ env.DOCKER_TAG }} \
--wait --wait
- name: Run Helm upgrade/install spearhead-docs (develop)
if: github.ref_name == 'develop'
run: |
helm upgrade --install spearhead-docs-beta .helm \
--namespace spearhead-docs \
--create-namespace \
-f .helm/values.yaml \
-f .helm/values.beta.yaml \
--set image.repository=${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }} \
--set image.tag=${{ env.DOCKER_TAG }} \
--wait
# - name: 'Deploy' # - name: 'Deploy'
# uses: deliverybot/helm@v1 # uses: deliverybot/helm@v1
# with: # with:
-1
View File
@@ -4,4 +4,3 @@
/dist /dist
.vscode/settings.json .vscode/settings.json
**\settings.json
-1
View File
@@ -14,4 +14,3 @@ sources:
maintainers: maintainers:
- name: Spearhead Team - name: Spearhead Team
+1 -1
View File
@@ -25,7 +25,7 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: {{ .Values.app }} - name: {{ .Values.app }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: {{ .Values.ports.containerPort }} - containerPort: {{ .Values.ports.containerPort }}
+17 -10
View File
@@ -14,7 +14,7 @@ spec:
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: "{{ .Values.app }}-ingress" name: {{ .Values.app }}-ingress
namespace: spearhead-docs namespace: spearhead-docs
annotations: annotations:
kubernetes.io/tls-acme: "true" kubernetes.io/tls-acme: "true"
@@ -22,21 +22,28 @@ metadata:
spec: spec:
ingressClassName: nginx ingressClassName: nginx
rules: rules:
{{- range .Values.ingress.hosts }} - host: spearhead.dutchie031.com
- host: {{ . }}
http: http:
paths: paths:
- path: / - path: /
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: {{ $.Values.app }} name: {{ .Values.app }}
port: port:
number: {{ $.Values.ports.servicePort }} number: {{ .Values.ports.servicePort }}
{{- end }} - host: spearhead.rocks
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Values.app }}
port:
number: {{ .Values.ports.servicePort }}
tls: tls:
- hosts: - hosts:
{{- range .Values.ingress.hosts }} - spearhead.dutchie031.com
- {{ . }} - spearhead.rocks
{{- end }} secretName: spearhead-docs-tls
secretName: "{{ .Values.app }}-docs-tls"
-9
View File
@@ -1,9 +0,0 @@
app: spearhead-docs-beta
image:
tag: "beta"
ingress:
hosts:
- beta.spearhead.rocks
-5
View File
@@ -8,8 +8,3 @@ image:
ports: ports:
servicePort: 80 servicePort: 80
containerPort: 80 containerPort: 80
ingress:
hosts:
- spearhead.dutchie031.com
- spearhead.rocks
+2 -41
View File
@@ -5,57 +5,18 @@
### New Features ### New Features
- Issue: #41
Adds Stage Complete debug methods for local testing of the Stage flow.
#40
- Issue: #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".
#40
- Issue: #37
Now enabled the mission editor to add a custom briefing to the "Buildable" missions.
#44
### Bug Fixes ### Bug Fixes
## [0.13.0] 2026-08
A good first release that finally has all major bugs fixed that were caused by the migration from both the underlying script transpiler and the migration to Gitea. ## [0.12.1] 2026-07
### Breaking Changes ### Breaking Changes
### New Features ### New Features
- Issue #26
Possibility to have Stage Overview briefings (which include current missions sorted by distance) to be shown on spawning of a player.
PR #31
### Bug Fixes ### Bug Fixes
- Issue #11 - Fixed stage drawing to only be checked when a stage completed. Now done on stage number changed.
Supply crate spawning now checks for free space and will not spawn if the area is too crowded.
Additionally different units will spawn in different areas depending on loading side.
PR #17
- Fixed command wiring for supply hubs for better and more accurate detection of units spawning and entering/exiting zone.
PR #15
- Fixed custom drawings not being drawn correctly.
PR #18
- Fixed CAP Callbacks not working since the change to a transpiled script. Now a global callback circumvents this issue.
PR #18
- Fixed #9
Changed order of checking mission briefings
PR #19
- Fixed Configuration defaulting to true for all booleans in StageConfig
PR #28
- Fixed Pre-Activated stages not always drawing or pre-activating correctly.
PR #28
- Addressed #24
CAP max commit range is now configurable in the config.lua file.
Issue remains open in order to apply further fine grained tuning.
PR #29
## [0.12.0] 2026-06 ## [0.12.0] 2026-06
-12
View File
@@ -4,9 +4,6 @@ SpearheadConfig = {
---DEBUG LOGGING ---DEBUG LOGGING
debugEnabled = false, -- default false debugEnabled = false, -- default false
--- DEBUG MENU
debugMenuEnabled = false, -- default false
--- The time briefings should be displayed by default. --- The time briefings should be displayed by default.
--- Players can always "Clear Messages" through the F10 menu, so setting it to a high value can be --- Players can always "Clear Messages" through the F10 menu, so setting it to a high value can be
briefingMessageDuration = 60, --default 60 briefingMessageDuration = 60, --default 60
@@ -32,12 +29,6 @@ SpearheadConfig = {
-- unit: feet -- unit: feet
maxAlt = 28000, -- default 28000 maxAlt = 28000, -- default 28000
--The "maxDistance" that's set on the CAP tasking for the aircraft to commit to a target.
--This is the distance from the aircraft to the target that the aircraft will commit to engaging
--This is shared for CAP, Intercept and Sweep missions for now.
-- unit: nautical miles
maxCommitRange = 35, -- default 35
-- DELAYS. -- DELAYS.
-- Delays work as follow. -- Delays work as follow.
-- When an aircraft lands alive and well it will be rearmed and ready to go. -- When an aircraft lands alive and well it will be rearmed and ready to go.
@@ -76,9 +67,6 @@ SpearheadConfig = {
--The location will continously update for the last killed unit. --The location will continously update for the last killed unit.
markLastContact = false, -- default false markLastContact = false, -- default false
--If enabled, the stage briefing including the current missions will be shown to players on spawn.
briefingOnSpawn = true, -- default true
--AutoStages will continue to the next stage automatically on completion of the missions within the stage. --AutoStages will continue to the next stage automatically on completion of the missions within the stage.
-- If you want to make it so the next stage triggers only when you want to disable it here and manually implement the actions needed. -- If you want to make it so the next stage triggers only when you want to disable it here and manually implement the actions needed.
--[[ --[[
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -10,6 +10,7 @@ local MissionCompleteListeners = {}
---@class SpearheadAPIInternal ---@class SpearheadAPIInternal
---@field notifyMissionComplete fun(zone_name: string) ---@field notifyMissionComplete fun(zone_name: string)
---@type FullSpearheadAPI ---@type FullSpearheadAPI
SpearheadAPI = { SpearheadAPI = {
Stages = { Stages = {
@@ -24,12 +25,12 @@ SpearheadAPI = {
getCurrentStage = function() getCurrentStage = function()
return GlobalStageManager.getCurrentStage() or nil return GlobalStageManager.getCurrentStage() or nil
end, end,
isStageComplete = function(stageNumber, stageLaneIdentifier) isStageComplete = function(stageNumber)
if type(stageNumber) ~= "number" then if type(stageNumber) ~= "number" then
return false, "stageNumber " .. stageNumber .. " is not a valid number" return false, "stageNumber " .. stageNumber .. " is not a valid number"
end end
local isComplete = GlobalStageManager.isStageComplete(stageNumber, stageLaneIdentifier) local isComplete = GlobalStageManager.isStageComplete(stageNumber)
if isComplete == nil then if isComplete == nil then
return nil, "no stage found with number " .. stageNumber return nil, "no stage found with number " .. stageNumber
end end
+2 -1
View File
@@ -6,7 +6,7 @@ SpearheadAPI = SpearheadAPI
---@class SpearheadStagesAPI ---@class SpearheadStagesAPI
---@field changeStage fun(stageNumber: number): boolean, string @Changes the active stage of spearhead. <br/> All other stages will change based on the normal logic. (CAP, BLUE etc.) ---@field changeStage fun(stageNumber: number): boolean, string @Changes the active stage of spearhead. <br/> All other stages will change based on the normal logic. (CAP, BLUE etc.)
---@field getCurrentStage fun(): number | nil @Returns the current stange number <br/> Returns nil when the stagenumber was not set before ever, which means Spearhead was not started. ---@field getCurrentStage fun(): number | nil @Returns the current stange number <br/> Returns nil when the stagenumber was not set before ever, which means Spearhead was not started.
---@field isStageComplete fun(stageNumber: number, 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 ---@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
---@class OnMissionCompleteListener ---@class OnMissionCompleteListener
---@field onMissionComplete fun(self: OnMissionCompleteListener, zone_name: string) @Called when a mission is completed. @return void ---@field onMissionComplete fun(self: OnMissionCompleteListener, zone_name: string) @Called when a mission is completed. @return void
@@ -14,3 +14,4 @@ SpearheadAPI = SpearheadAPI
---@class MissionAPI ---@class MissionAPI
---@field addOnMissionCompleteListener fun(listener: OnMissionCompleteListener) @Adds a listener to the mission. <br/> @param listener OnMissionCompleteListener <br/> @return void ---@field addOnMissionCompleteListener fun(listener: OnMissionCompleteListener) @Adds a listener to the mission. <br/> @param listener OnMissionCompleteListener <br/> @return void
+2 -5
View File
@@ -392,12 +392,9 @@ function CapBase:CheckAndScheduleIntercept()
end end
end end
end end
end end
function CapBase:OnStageNumberChanged(number)
function CapBase:OnStageNumberChanged(number, laneIdentifier)
-- only react on "main" lane changes, ignore other lanes for now
if laneIdentifier ~= nil then return end
self.activeStage = number self.activeStage = number
if self:IsBaseActiveWhenStageIsActive(number) == true then if self:IsBaseActiveWhenStageIsActive(number) == true then
@@ -2,9 +2,6 @@ local SpearheadEvents = require("classes.spearhead_events")
local RTBMission = require("classes.capClasses.taskings.RTB") local RTBMission = require("classes.capClasses.taskings.RTB")
local Util = require("classes.util.Util") local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil") local 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
---@field protected _logger Logger ---@field protected _logger Logger
@@ -15,12 +12,9 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
---@field protected _config CapConfig ---@field protected _config CapConfig
---@field protected _checkLivenessNumber number ---@field protected _checkLivenessNumber number
---@field protected _spawnManager SpawnManager ---@field protected _spawnManager SpawnManager
---@field protected _routeDrawing CustomDrawing?
local AirGroup = {} local AirGroup = {}
AirGroup.__index = AirGroup AirGroup.__index = AirGroup
local globalConfig = GlobalConfig.New()
---@param logger Logger ---@param logger Logger
---@param groupName string ---@param groupName string
---@param groupType AirGroupType ---@param groupType AirGroupType
@@ -34,7 +28,6 @@ function AirGroup:New(groupName, groupType, config, logger, spawnManager)
self._config = config self._config = config
self._logger = logger self._logger = logger
self._spawnManager = spawnManager self._spawnManager = spawnManager
self._routeDrawings = {}
local group = Group.getByName(self._groupName) local group = Group.getByName(self._groupName)
if group then if group then
@@ -99,27 +92,6 @@ function AirGroup:SetMissionPrivate(mission)
if group and mission then if group and mission then
group:getController():setTask(mission) group:getController():setTask(mission)
self._logger:debug("mission - Task set for group: " .. self._groupName) self._logger:debug("mission - Task set for group: " .. self._groupName)
if globalConfig:isDebugMenuEnabled() == true then
-- draw the mission route for debugging purposes
if self._routeDrawing then
self._routeDrawing:Remove()
end
local points = {}
if mission and mission.params and mission.params.route and mission.params.route.points then
for _, wp in pairs(mission.params.route.points) do
if wp.x and wp.y then
table.insert(points, { x = wp.x, y = wp.y })
end
end
end
local colorString = DrawingHelper.ColorTableToColorString({ 1, 0, 0, 1 })
self._routeDrawing = CustomDrawing.FromPoints(points, colorString, 5, 2)
self._routeDrawing:Draw()
end
end end
end end
+5 -5
View File
@@ -220,7 +220,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(GlobalCapCallBacks.PublishOnStation, \"" .. groupName .. "\")" command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")"
} }
} }
} }
@@ -260,7 +260,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
}, },
stopCondition = { stopCondition = {
duration = durationBefore10, duration = durationBefore10,
condition = "return GlobalCapCallBacks.NeedsRTBInTen(\"" .. groupName .. "\", 0.10)", condition = "return Spearhead.DcsUtil.NeedsRTBInTen(\"" .. groupName .. "\", 0.10)",
} }
} }
}, },
@@ -273,7 +273,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(GlobalCapCallBacks.PublishRTBInTen, \"" .. groupName .. "\")" command = "pcall(Spearhead.Events.PublishRTBInTen, \"" .. groupName .. "\")"
} }
} }
} }
@@ -299,7 +299,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
}, },
stopCondition = { stopCondition = {
duration = durationAfter10, duration = durationAfter10,
condition = "return GlobalCapCallBacks.IsBingoFuel(\"" .. groupName .. "\", 0.10)", condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\")",
} }
} }
}, },
@@ -312,7 +312,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")" command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")"
} }
} }
} }
@@ -1,26 +0,0 @@
local DcsUtil = require("classes.util.DcsUtil")
local Events = require("classes.spearhead_events")
GlobalCapCallBacks = {}
function GlobalCapCallBacks.IsBingoFuel(groupName, fuelPercent)
return DcsUtil.IsBingoFuel(groupName, fuelPercent)
end
function GlobalCapCallBacks.NeedsRTBInTen(groupName, fuelOffset)
return DcsUtil.NeedsRTBInTen(groupName, fuelOffset)
end
function GlobalCapCallBacks.PublishRTBInTen(groupName)
return Events.PublishRTBInTen(groupName)
end
function GlobalCapCallBacks.PublishRTB(groupName)
return Events.PublishRTB(groupName)
end
function GlobalCapCallBacks.PublishOnStation(groupName)
return Events.PublishOnStation(groupName)
end
@@ -247,7 +247,7 @@ function INTERCEPT.getInterceptTaskPoint(groupName, currentPoint, targetPoint, a
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")" command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")"
} }
} }
} }
@@ -379,7 +379,7 @@ function INTERCEPT.getUnitInterceptTaskPoint(groupName, currentPoint, targetPosi
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")" command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")"
} }
} }
} }
+2 -2
View File
@@ -189,7 +189,7 @@ function SWEEP.getAsTasking(groupName, airbase, capZone, capConfig)
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(GlobalCapCallBacks.PublishOnStation, \"" .. groupName .. "\")" command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")"
} }
} }
} }
@@ -270,7 +270,7 @@ function SWEEP.getAsTasking(groupName, airbase, capZone, capConfig)
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")" command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")"
} }
} }
} }
+1 -1
View File
@@ -34,7 +34,7 @@ function CapConfig.new()
self._minDurationOnStation = 1200 self._minDurationOnStation = 1200
self._maxDurationOnStation = 2700 self._maxDurationOnStation = 2700
self._maxDeviationRange = (tonumber(SpearheadConfig.CapConfig.maxCommitRange) or 35) * 1852 -- in meters self._maxDeviationRange = 35 * 1852 -- in meters
self._rearmDelay = tonumber(SpearheadConfig.CapConfig.rearmDelay) or 600 self._rearmDelay = tonumber(SpearheadConfig.CapConfig.rearmDelay) or 600
self._repairDelay = tonumber(SpearheadConfig.CapConfig.repairDelay) or 600 self._repairDelay = tonumber(SpearheadConfig.CapConfig.repairDelay) or 600
self._deathDelay = tonumber(SpearheadConfig.CapConfig.deathDelay) or 1800 self._deathDelay = tonumber(SpearheadConfig.CapConfig.deathDelay) or 1800
+1 -20
View File
@@ -4,9 +4,7 @@
local briefingMessageTime = nil local briefingMessageTime = nil
---@class GlobalConfig ---@class GlobalConfig
---@field private _briefingTime number ---@field private _briefingTime number ;
---@field private _debugEnabled boolean
---@field private _debugMenuEnabled boolean
local GlobalConfig = {} local GlobalConfig = {}
GlobalConfig.__index = GlobalConfig; GlobalConfig.__index = GlobalConfig;
@@ -21,15 +19,6 @@ function GlobalConfig.New()
if SpearheadConfig.briefingMessageDuration then if SpearheadConfig.briefingMessageDuration then
self._briefingTime = SpearheadConfig.briefingMessageDuration self._briefingTime = SpearheadConfig.briefingMessageDuration
end end
if SpearheadConfig.debugEnabled ~= nil then
self._debugEnabled = SpearheadConfig.debugEnabled
else
self._debugEnabled = false
end
self._debugMenuEnabled = SpearheadConfig.debugMenuEnabled == true
end end
return self return self
@@ -39,14 +28,6 @@ function GlobalConfig:getBriefingTime()
return self._briefingTime or 60 return self._briefingTime or 60
end end
function GlobalConfig:isDebugEnabled()
return self._debugEnabled or false
end
---@return boolean
function GlobalConfig:isDebugMenuEnabled()
return self._debugMenuEnabled or false
end
return GlobalConfig return GlobalConfig
@@ -1,31 +0,0 @@
---@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
+15 -33
View File
@@ -7,47 +7,29 @@
--- @field startingStage integer --- @field startingStage integer
--- @field maxMissionsPerStage integer --- @field maxMissionsPerStage integer
--- @field AmountPreactivateStage integer --- @field AmountPreactivateStage integer
--- @field briefingOnSpawnEnabled boolean
local StageConfig = {}; local StageConfig = {};
StageConfig.__index = StageConfig
local Logger = require("classes.util.Logger")
local _logger = Logger.new("StageConfig", Logger.LogLevel)
---comment ---comment
---@return StageConfig ---@return StageConfig
local function new() function StageConfig:new()
if SpearheadConfig == nil then if SpearheadConfig == nil then SpearheadConfig = {} end
_logger:warn("SpearheadConfig is nil, creating default SpearheadConfig") if SpearheadConfig.StageConfig == nil then SpearheadConfig.StageConfig = {} end
SpearheadConfig = {}
end
if SpearheadConfig.StageConfig == nil then ---@type StageConfig
_logger:warn("SpearheadConfig.StageConfig is nil, creating default StageConfig") local o = {
SpearheadConfig.StageConfig = {} isEnabled = SpearheadConfig.StageConfig.enabled or true,
end isDrawStagesEnabled = SpearheadConfig.StageConfig.drawStages or true,
isAutoStages = SpearheadConfig.StageConfig.autoStages or true,
startingStage = SpearheadConfig.StageConfig.startingStage or 1,
maxMissionsPerStage = SpearheadConfig.StageConfig.maxMissionStage or 10,
isDrawPreActivatedEnabled = SpearheadConfig.StageConfig.drawPreActivated or true,
AmountPreactivateStage = SpearheadConfig.StageConfig.preactivateStage or 1,
}
local self = setmetatable({}, StageConfig) setmetatable(o, { __index = self })
self.isEnabled = SpearheadConfig.StageConfig.enabled ~= false
self.isDrawStagesEnabled = SpearheadConfig.StageConfig.drawStages ~= false
self.isAutoStages = SpearheadConfig.StageConfig.autoStages ~= false
self.startingStage = SpearheadConfig.StageConfig.startingStage or 1
self.maxMissionsPerStage = SpearheadConfig.StageConfig.maxMissionStage or 10
self.isDrawPreActivatedEnabled = SpearheadConfig.StageConfig.drawPreActivated ~= false
self.AmountPreactivateStage = SpearheadConfig.StageConfig.preactivateStage or 1
self.briefingOnSpawnEnabled = SpearheadConfig.StageConfig.briefingOnSpawn ~= false
_logger:info("Successfully created StageConfig Object") return o;
return self;
end
local config = new();
---@return StageConfig
function StageConfig:getInstance()
return config
end end
return StageConfig return StageConfig
-80
View File
@@ -1,80 +0,0 @@
local Logger = require("classes.util.Logger")
local Util = require("classes.util.Util")
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
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 -3
View File
@@ -127,9 +127,7 @@ function FleetGroup:new(fleetGroupName, database, logger)
end end
end end
o.OnStageNumberChanged = function(self, number, laneIdentifier) o.OnStageNumberChanged = function(self, number)
-- only react on "main" lane changes, ignore other lanes for now
if laneIdentifier ~= nil then return end
local targetZone = self.targetZonePerStage[tostring(number)] local targetZone = self.targetZonePerStage[tostring(number)]
if targetZone and targetZone ~= self.currentTargetZone then if targetZone and targetZone ~= self.currentTargetZone then
local points = self.pointsPerZone[targetZone] local points = self.pointsPerZone[targetZone]
+12 -23
View File
@@ -13,8 +13,7 @@ do
---@field unitsStates table<string, UnitState> ---@field unitsStates table<string, UnitState>
---@field random_missions table<string, MissionState> ---@field random_missions table<string, MissionState>
---@field deliveredKilos table<string, number> ---@field deliveredKilos table<string, number>
---@field activeStage number? required for backwards compatibility ---@field activeStage integer|nil
---@field activeStageInStageLane table<string, number>
---@class UnitState ---@class UnitState
---@field isDead boolean ---@field isDead boolean
@@ -34,8 +33,7 @@ do
unitsStates = {}, unitsStates = {},
random_missions = {}, random_missions = {},
deliveredKilos = {}, deliveredKilos = {},
activeStage = nil, -- Backwards compatibility for previous version without lanes activeStage = nil
activeStageInStageLane = {}
} }
local logger = {} local logger = {}
@@ -232,29 +230,12 @@ do
end end
---Sets the stage in the persistence table ---Sets the stage in the persistence table
---@param stageLane string?
---@param stageNumber number ---@param stageNumber number
Persistence.SetActiveStage = function(stageLane, stageNumber) Persistence.SetActiveStage = function(stageNumber)
stageLane = stageLane or "nil" tables.activeStage = stageNumber
tables.activeStageInStageLane[stageLane] = stageNumber
Persistence._updateRequired = true Persistence._updateRequired = true
end end
---Get the active stage as in the persistance file
---@param stageLane string?
---@return integer|nil
Persistence.GetActiveStage = function(stageLane)
stageLane = stageLane or "nil"
-- backwards compatibility for previous versions without stage lanes
if stageLane == "nil" and tables.activeStageInStageLane[stageLane] == nil then
tables.activeStageInStageLane[stageLane] = tables.activeStage
tables.activeStage = nil -- Clear the old activeStage so it's not used or confused later
end
return tables.activeStageInStageLane[stageLane]
end
---comment ---comment
---@param missionName string ---@param missionName string
---@param pickedZone string ---@param pickedZone string
@@ -274,6 +255,14 @@ do
return tables.random_missions[string.lower(missionName)] return tables.random_missions[string.lower(missionName)]
end end
---Get the active stage as in the persistance file
---@return integer|nil
Persistence.GetActiveStage = function()
if tables.activeStage then
return tables.activeStage
end
return nil
end
---comment ---comment
---@param zoneName string ---@param zoneName string
+21 -73
View File
@@ -2,7 +2,7 @@ local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil") local DcsUtil = require("classes.util.DcsUtil")
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings") local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
local MizGroupsManager = require("classes.helpers.MizGroupsManager") local MizGroupsManager = require("classes.helpers.MizGroupsManager")
local StageDrawing = require("classes.stageClasses.drawings.StageDrawing") local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
---@class DatabaseTables ---@class DatabaseTables
@@ -27,7 +27,7 @@ local StageDrawing = require("classes.stageClasses.drawings.StageDrawing")
---@field MissionZoneData table<string, MissionZoneData> ---@field MissionZoneData table<string, MissionZoneData>
---@field FarpZoneData table<string,FarpZoneData> ---@field FarpZoneData table<string,FarpZoneData>
---@field missionCodes table<string, boolean> ---@field missionCodes table<string, boolean>
---@field StageDrawings Array<StageDrawing> ---@field CustomDrawings Array<CustomDrawing>
---@class CapRoute ---@class CapRoute
---@field zones Array<SpearheadTriggerZone> ---@field zones Array<SpearheadTriggerZone>
@@ -58,7 +58,6 @@ local StageDrawing = require("classes.stageClasses.drawings.StageDrawing")
---@class BlueSamData ---@class BlueSamData
---@field groups Array<string> ---@field groups Array<string>
---@field buildingKilos number? ---@field buildingKilos number?
---@field briefing string?
---@class MissionZoneData ---@class MissionZoneData
---@field ZoneName string ---@field ZoneName string
@@ -75,7 +74,6 @@ local StageDrawing = require("classes.stageClasses.drawings.StageDrawing")
---@field padNames Array<string> ---@field padNames Array<string>
---@field buildingKilos number? ---@field buildingKilos number?
---@field supplyHubNames Array<string> ---@field supplyHubNames Array<string>
---@field briefing string?
---@class Database ---@class Database
---@field private _tables DatabaseTables ---@field private _tables DatabaseTables
@@ -109,7 +107,7 @@ function Database.New(Logger)
FarpZoneData = {}, FarpZoneData = {},
missionCodes = {}, missionCodes = {},
SupplyHubZones = {}, SupplyHubZones = {},
StageDrawings = {} CustomDrawings = {}
} }
Database.__index = Database Database.__index = Database
@@ -233,10 +231,8 @@ function Database.New(Logger)
for key, layer_object in pairs(layer.objects) do for key, layer_object in pairs(layer.objects) do
if Util.startswith(layer_object.name, "drawing_", true) then if Util.startswith(layer_object.name, "drawing_", true) then
local object = layer_object --[[@as DrawingObject]] local object = layer_object --[[@as DrawingObject]]
local stageDrawing = StageDrawing.New(object) local customDrawing = CustomDrawing.New(object)
if stageDrawing then table.insert(self._tables.CustomDrawings, customDrawing)
table.insert(self._tables.StageDrawings, stageDrawing)
end
end end
end end
end end
@@ -354,6 +350,18 @@ function Database.New(Logger)
end end
end end
for _, missionZone in pairs(self._tables.MissionZones) do
if self._tables.MissionZoneData[missionZone] == nil or self._tables.MissionZoneData[missionZone].description == nil then
MissionEditorWarnings.Add("Mission with zonename: " .. missionZone .. " does not have a briefing")
end
end
for _, missionZone in pairs(self._tables.RandomMissionZones) do
if self._tables.MissionZoneData[missionZone] == nil or self._tables.MissionZoneData[missionZone].description == nil then
MissionEditorWarnings.Add("Mission with zonename: " .. missionZone .. " does not have a briefing")
end
end
for _, farpZoneName in pairs(self._tables.AllFarpZones) do for _, farpZoneName in pairs(self._tables.AllFarpZones) do
for _, airbase in pairs(world.getAirbases()) do for _, airbase in pairs(world.getAirbases()) do
if airbase:getDesc().category == Airbase.Category.HELIPAD then if airbase:getDesc().category == Airbase.Category.HELIPAD then
@@ -439,21 +447,6 @@ function Database.New(Logger)
end end
end end
-- Checks for missing briefings in mission zones and random mission zones
do
for _, missionZone in pairs(self._tables.MissionZones) do
if self._tables.MissionZoneData[missionZone] == nil or self._tables.MissionZoneData[missionZone].description == nil then
MissionEditorWarnings.Add("Mission with zonename: " .. missionZone .. " does not have a briefing")
end
end
for _, missionZone in pairs(self._tables.RandomMissionZones) do
if self._tables.MissionZoneData[missionZone] == nil or self._tables.MissionZoneData[missionZone].description == nil then
MissionEditorWarnings.Add("Mission with zonename: " .. missionZone .. " does not have a briefing")
end
end
end
if missions == 0 then missions = 1 end if missions == 0 then missions = 1 end
self._logger:info("initiated the database with amount of zones: ") self._logger:info("initiated the database with amount of zones: ")
@@ -618,27 +611,6 @@ function Database:loadBlueSamUnits()
local number = tonumber(kvPair.value) local number = tonumber(kvPair.value)
if number and number > 0 then if number and number > 0 then
samData.buildingKilos = number samData.buildingKilos = number
if env.mission.drawings and env.mission.drawings.layers then
for i, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
for key, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
if layer_object.name and Util.startswith(layer_object.name, "supplybriefing_", true) then
local description = layer_object.text
if description and description ~= "" then
samData.briefing = description
end
end
end
end
end
end
end
else
MissionEditorWarnings.Add("Buildable number for " .. blueSamZone .. " is invalid")
end end
end end
end end
@@ -646,12 +618,12 @@ function Database:loadBlueSamUnits()
end end
end end
---@return Array<StageDrawing> ---@return Array<CustomDrawing>
function Database:getStageDrawings() function Database:getCustomDrawings()
if self._tables.StageDrawings == nil then if self._tables.CustomDrawings == nil then
return {} return {}
end end
return self._tables.StageDrawings return self._tables.CustomDrawings
end end
---Loads all units, data and briefings ---Loads all units, data and briefings
@@ -773,36 +745,12 @@ function Database:loadFarpData()
local number = tonumber(kvPair.value) local number = tonumber(kvPair.value)
if number and number > 0 then if number and number > 0 then
farpzoneData.buildingKilos = number farpzoneData.buildingKilos = number
-- check briefings
if env.mission.drawings and env.mission.drawings.layers then
for i, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
for key, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
if layer_object.name and Util.startswith(layer_object.name, "supplybriefing_", true) then
local description = layer_object.text
if description and description ~= "" then
farpzoneData.briefing = description
end end
end end
end end
end end
end end
end end
end
else
MissionEditorWarnings.Add("Buildable number for " .. farpZone .. " is invalid.")
end
end
end
end
end
end
function Database:loadAirbaseGroups() function Database:loadAirbaseGroups()
local all_groups = getAvailableGroups() local all_groups = getAvailableGroups()
+12 -30
View File
@@ -29,44 +29,36 @@ do
end end
---@class OnStageChangedListener ---@class OnStageChangedListener
---@field OnStageNumberChanged fun(self:OnStageChangedListener, number:integer, laneIdentifier:string?) ---@field OnStageNumberChanged fun(self:OnStageChangedListener, number:integer)
do -- STAGE NUMBER CHANGED do -- STAGE NUMBER CHANGED
local OnStageNumberChangedListeners = {} local OnStageNumberChangedListeners = {}
local OnStageNumberChangedHandlers = {} local OnStageNumberChangedHandlers = {}
---Add a stage zone number changed listener ---Add a stage zone number changed listener
---@param listener OnStageChangedListener object with function OnStageNumberChanged(self, number, stageLaneIdentifier) ---@param listener OnStageChangedListener object with function OnStageNumberChanged(self, number)
SpearheadEvents.AddStageNumberChangedListener = function(listener) SpearheadEvents.AddStageNumberChangedListener = function(listener)
if type(listener) ~= "table" or type(listener.OnStageNumberChanged) ~= "function" then
warn("Event handler not of type table/object with function OnStageNumberChanged(self, number, stageLaneIdentifier)")
return
end
table.insert(OnStageNumberChangedListeners, listener) table.insert(OnStageNumberChangedListeners, listener)
end end
---@class OnStageNumberChangeCompleteListener ---Add a stage zone number changed listener
---@field OnStageNumberChangeComplete fun(self:OnStageNumberChangeCompleteListener, number:integer, laneIdentifier:string?) ---@param handler function function(number)
SpearheadEvents.AddStageNumberChangedHandler = function(handler)
local OnStageNumberChangeCompleteListeners = {} if type(handler) ~= "function" then
---@param listener OnStageNumberChangeCompleteListener warn("Event handler not of type function, did you mean to use listener?")
SpearheadEvents.AddStageNumberChangeCompleteListener = function(listener)
if type(listener) ~= "table" or type(listener.OnStageNumberChangeComplete) ~= "function" then
warn("Event handler not of type table/object with function OnStageNumberChangeComplete(self, number, laneIdentifier)")
return return
end end
table.insert(OnStageNumberChangeCompleteListeners, listener) table.insert(OnStageNumberChangedHandlers, handler)
end end
---@param newStageNumber number ---@param newStageNumber number
---@param laneIdentifier string? SpearheadEvents.PublishStageNumberChanged = function(newStageNumber)
SpearheadEvents.PublishStageNumberChanged = function(newStageNumber, laneIdentifier)
pcall(function () pcall(function ()
Persistence.SetActiveStage(laneIdentifier, newStageNumber) Persistence.SetActiveStage(newStageNumber)
end) end)
for _, callable in pairs(OnStageNumberChangedListeners) do for _, callable in pairs(OnStageNumberChangedListeners) do
local succ, err = pcall(function() local succ, err = pcall(function()
callable:OnStageNumberChanged(newStageNumber, laneIdentifier) callable:OnStageNumberChanged(newStageNumber)
end) end)
if err then if err then
logError(err) logError(err)
@@ -74,21 +66,11 @@ do
end end
for _, callable in pairs(OnStageNumberChangedHandlers) do for _, callable in pairs(OnStageNumberChangedHandlers) do
local succ, err = pcall(callable, newStageNumber, laneIdentifier) local succ, err = pcall(callable, newStageNumber)
if err then if err then
logError(err) logError(err)
end end
end end
for _, callable in pairs(OnStageNumberChangeCompleteListeners) do
local succ, err = pcall(function()
callable:OnStageNumberChangeComplete(newStageNumber, laneIdentifier)
end)
if err then
logError(err)
end
end
Logger.new("Events", "INFO"):info("Published stage number changed to: " .. tostring(newStageNumber)) Logger.new("Events", "INFO"):info("Published stage number changed to: " .. tostring(newStageNumber))
end end
end end
+6 -6
View File
@@ -92,7 +92,7 @@ do --setup route util
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(GlobalCapCallBacks.PublishOnStation, \"" .. groupName .. "\")" command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")"
} }
} }
} }
@@ -122,7 +122,7 @@ do --setup route util
}, },
stopCondition = { stopCondition = {
duration = durationBefore10, duration = durationBefore10,
condition = "return GlobalCapCallBacks.IsBingoFuel(\"" .. groupName .. "\", 0.10)", condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\", 0.10)",
} }
} }
}, },
@@ -135,7 +135,7 @@ do --setup route util
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(GlobalCapCallBacks.PublishRTBInTen, \"" .. groupName .. "\")" command = "pcall(Spearhead.Events.PublishRTBInTen, \"" .. groupName .. "\")"
} }
} }
} }
@@ -156,7 +156,7 @@ do --setup route util
}, },
stopCondition = { stopCondition = {
duration = durationAfter10, duration = durationAfter10,
condition = "return GlobalCapCallBacks.IsBingoFuel(\"" .. groupName .. "\", 0.10)", condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\")",
} }
} }
}, },
@@ -169,7 +169,7 @@ do --setup route util
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")" command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")"
} }
} }
} }
@@ -375,7 +375,7 @@ do --setup route util
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. command = "pcall(Spearhead.Events.PublishRTB, \"" ..
groupName .. "\")" groupName .. "\")"
} }
} }
+173 -482
View File
@@ -1,54 +1,46 @@
local Events = require("classes.spearhead_events") local Events = require("classes.spearhead_events")
local Util = require("classes.util.Util") local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil")
local Logger = require("classes.util.Logger") local Logger = require("classes.util.Logger")
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings") local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
local PersistenceConfig = require("classes.configuration.PersistenceConfig")
local ExtraStage = require("classes.stageClasses.Stages.ExtraStage") local ExtraStage = require("classes.stageClasses.Stages.ExtraStage")
local PrimaryStage = require("classes.stageClasses.Stages.PrimaryStage") local PrimaryStage = require("classes.stageClasses.Stages.PrimaryStage")
local WaitingStage = require("classes.stageClasses.Stages.WaitingStage") local WaitingStage = require("classes.stageClasses.Stages.WaitingStage")
local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper")
local StageRepository = require("classes.stageClasses.StageRepository") local StagesByName = {}
local StageLane = require("classes.stageClasses.StageLane")
local Persistence = require("classes.persistence.Persistence") ---@type table<string, Array<Stage>>
local StagesByIndex = {}
---@type table<string, Array<Stage>>
local SideStageByIndex = {}
---@type table<string, Array<WaitingStage>>
local WaitingStagesByIndex = {}
local currentStage = -99 local currentStage = -99
---@class GlobalStageManager : StageCompleteListener, OnStageChangedListener, OnStageNumberChangeCompleteListener ---@class GlobalStageManager : StageCompleteListener, OnStageChangedListener
---@field private database Database ---@field private database Database
---@field private logger Logger ---@field private logger Logger
---@field private stageConfig StageConfig ---@field private stageConfig StageConfig
---@field private _stageRepository StageRepository
---@field private _missionCommandsHelper MissionCommandsHelper
local GlobalStageManager = {} local GlobalStageManager = {}
GlobalStageManager.__index = GlobalStageManager GlobalStageManager.__index = GlobalStageManager
GlobalStageManager.getCurrentStage = function() return currentStage end GlobalStageManager.getCurrentStage = function() return currentStage end
---@type GlobalStageManager
local singletonInstance = nil
---comment ---comment
---@param database Database ---@param database Database
---@param stageConfig StageConfig ---@param stageConfig StageConfig
---@param logLevel LogLevel ---@param logLevel LogLevel
---@param spawnManager SpawnManager ---@param spawnManager SpawnManager
---@return GlobalStageManager ---@return nil
function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager) function GlobalStageManager.NewAndStart(database, stageConfig, logLevel, spawnManager)
if singletonInstance ~= nil then
return singletonInstance
end
local logger = Logger.new("StageManager", logLevel) local logger = Logger.new("StageManager", logLevel)
logger:info("Using Stage Log Level: " .. logLevel) logger:info("Using Stage Log Level: " .. logLevel)
local self = setmetatable({}, GlobalStageManager) local self = setmetatable({}, GlobalStageManager)
singletonInstance = self
self.database = database self.database = database
self.stageConfig = stageConfig self.stageConfig = stageConfig
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
self._stageRepository = StageRepository.getInstance()
self.logger = logger self.logger = logger
if stageConfig.isAutoStages ~= true then if stageConfig.isAutoStages ~= true then
@@ -60,544 +52,243 @@ function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
for _, stageName in pairs(database:getStagezoneNames()) do for _, stageName in pairs(database:getStagezoneNames()) do
logger:debug("Found stage zone with name: " .. stageName) logger:debug("Found stage zone with name: " .. stageName)
local parseResult = self:ParseStageName(stageName) if Util.startswith(stageName, "missionstage", true) then
if parseResult.isValid == false then local valid = true
logger:warn("Stage zone with name " .. stageName .. " is not valid: " .. parseResult.invalidReason) 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
if Util.tableLength(split) < 3 then
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a stage name")
end
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 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 = { local initData = {
stageZoneName = parseResult.stageZoneName, stageDisplayName = stageDisplayName,
stageNumber = parseResult.orderNumber, stageNumber = orderNumber,
stageDisplayName = parseResult.stageDisplayName, stageZoneName = stageName,
stageLaneIdentifier = parseResult.stageLaneIdentifier
} }
if parseResult.stageType == "PrimaryStage" then if isSideStage == true then
local stage = PrimaryStage.New(database, stageConfig, logger, initData, spawnManager) local stage = ExtraStage.New(database, stageConfig, stagelogger, initData, spawnManager)
stage:AddStageCompleteListener(self) stage:AddStageCompleteListener(self)
self._stageRepository:AddStage(stage)
elseif parseResult.stageType == "ExtraStage" then if SideStageByIndex[tostring(orderNumber)] == nil then SideStageByIndex[tostring(orderNumber)] = {} end
local stage = ExtraStage.New(database, stageConfig, logger, initData, spawnManager) table.insert(SideStageByIndex[tostring(orderNumber)], stage)
else
local stage = PrimaryStage.New(database, stageConfig, stagelogger, initData, spawnManager)
stage:AddStageCompleteListener(self) 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 if StagesByIndex[tostring(orderNumber)] == nil then StagesByIndex[tostring(orderNumber)] = {} end
table.insert(StagesByIndex[tostring(orderNumber)], stage)
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 end
end end
---@class StageNameParseResult if Util.startswith(stageName, "waitingstage", true) then
---@field isValid boolean local valid = true
---@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, "_") local split = Util.split_string(stageName, "_")
if Util.tableLength(split) < 3 then if Util.tableLength(split) < 3 then
return { isValid = false, invalidReason = "Stage zone with name " .. stageName .. " does not have a order number or valid format" } MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a order number or valid format")
valid = false
end end
local typePart = string.lower(split[1]) if valid == true then
local stageIndexString = split[2]
local stageIndex = tonumber(stageIndexString)
if typePart == "missionstage" then if not stageIndex then
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a valid order number")
local orderNumberString = string.lower(split[2]) valid = false
---@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 end
local waitingSecondsString = split[3] local waitingSecondsString = split[3]
local waitingSeconds = tonumber(waitingSecondsString) local waitingSeconds = tonumber(waitingSecondsString)
if waitingSeconds == nil then if not waitingSeconds then
return { isValid = false, invalidReason = "Waiting Stage zone with name " .. stageName .. " does not have a valid amount of seconds parameter : " .. waitingSecondsString } MissionEditorWarnings.Add("Waiting Stage zone with name " .. stageName .. " does not have a valid amount of seconds parameter")
valid = false
end end
local stageDisplayName = "Waiting Stage " .. orderNumber if valid == true then
local stagelogger = Logger.new(stageName, logLevel)
---@type StageNameParseResult ---@type WaitingStageInitData
local result = { local initData = {
isValid = true, stageDisplayName = "Waiting Stage " .. stageIndex,
orderNumber = orderNumber, stageNumber = stageIndex or -99,
stageLaneIdentifier = nil,
stageDisplayName = stageDisplayName,
stageZoneName = stageName, stageZoneName = stageName,
stageType = stageType, waitingSeconds = waitingSeconds --[[@as integer]]
waitingStageSeconds = waitingSeconds
} }
return result 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
end
end end
return { isValid = false, invalidReason = "Stage zone with name " .. stageName .. " has an unrecognized type: " .. typePart }
end end
---@param stage Stage return self
end
function GlobalStageManager:OnStageComplete(stage) function GlobalStageManager:OnStageComplete(stage)
self.logger:debug("Receiving stage complete event from: " .. stage.zoneName) self.logger:debug("Receiving stage complete event from: " .. stage.zoneName)
local stageIndex = stage:GetStageIndex() local anyIncomplete = false
local laneIdentifier = stage:GetStageLaneIdentifier() self.logger:debug("Checking stages for index: " .. tostring(currentStage))
for index, stage in pairs(StagesByIndex[tostring(currentStage)]) do
local stageLane = self._stageRepository:getStageLane(laneIdentifier) if stage:IsComplete() == false then
if not stageLane or not stageLane:IsCurrentStageIndexComplete() then anyIncomplete = true
self.logger:debug("Stage lane " .. (laneIdentifier or "default") .. " is not complete for stage index " .. stageIndex) self.logger:debug("Need to wait for Stage " .. stage.zoneName .. " to be completed")
return
end
local nextStageIndex = stageIndex + 1
Events.PublishStageNumberChanged(nextStageIndex, laneIdentifier)
stageLane:SetActiveStageIndex(nextStageIndex)
if stageLane:IsDefaultStageLane() then
-- COMPLETION IN THE DEFAULT LANE
-- 1. Check if any lane is at status "BetweenChapters" and the next chapter start is "next stage index"
local allLanes = self._stageRepository:getAllStageLanes()
for _, lane in ipairs(allLanes) do
if lane:GetStageLaneIdentifier() ~= StageLane.DefaultLaneKey and lane:GetStageLaneState() == "BetweenChapters" then
local nextChapterStart = lane:GetNextChapterStart()
if nextChapterStart == nextStageIndex then
self.logger:debug("Lane " .. (lane:GetStageLaneIdentifier() or "default") .. " is at chapter start for next stage index " .. nextStageIndex)
Events.PublishStageNumberChanged(nextStageIndex, lane:GetStageLaneIdentifier())
lane:SetActiveStageIndex(nextStageIndex)
end
end
end
else else
-- COMPLETION IN A SIDE LANE self.logger:debug("Stage verified to be completed: " .. stage.zoneName)
-- 1. Check if the default lane is at status "BetweenChapters"
local defaultStageLane = self._stageRepository:getStageLane(StageLane.DefaultLaneKey)
if defaultStageLane and defaultStageLane:GetStageLaneState() == "BetweenChapters" then
local nextChapterStart = defaultStageLane:GetNextChapterStart()
if nextChapterStart == nextStageIndex then
-- 2. Check if all side lanes are at or beyond the next chapter starts
local allLanes = self._stageRepository:getAllStageLanes()
local allSideLanesReady = true
for _, lane in ipairs(allLanes) do
if lane:GetStageLaneIdentifier() ~= StageLane.DefaultLaneKey then
-- Check if the lane is at or beyond the next chapter starts when "InChapter" eq "Active"
if lane:GetStageLaneState() == "InChapter" and lane:GetActiveStageIndex() < nextStageIndex then
allSideLanesReady = false
self.logger:debug("Lane " .. (lane:GetStageLaneIdentifier() or "default") .. " is not ready for next stage index " .. nextStageIndex)
break
-- When in between chapters, check if the next chapter start is less than the next stage index, meaning there is still stages to be completed before the main lane can be activated again
elseif lane:GetStageLaneState() == "BetweenChapters" then
local nextChapter = lane:GetNextChapterStart()
if nextChapter and nextChapter < nextStageIndex then
allSideLanesReady = false
self.logger:debug("Lane " .. (lane:GetStageLaneIdentifier() or "default") .. " is not ready for next stage index " .. nextStageIndex)
break
end end
end end
if anyIncomplete == false and self.stageConfig.isAutoStages == true then
-- CHECK WAITING STAGES
local nextStage = currentStage + 1
if WaitingStagesByIndex[tostring(nextStage)] then
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
if waitingStage:IsActive() == false then
waitingStage:ActivateStage()
end
end end
end end
if allSideLanesReady then local anyWaiting = false
self.logger:debug("All side lanes are ready for next stage index " .. nextStageIndex .. ", activating default lane") if WaitingStagesByIndex[tostring(nextStage)] then
Events.PublishStageNumberChanged(nextStageIndex, nil) for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
defaultStageLane:SetActiveStageIndex(nextStageIndex) if waitingStage:IsComplete() == false then
else anyWaiting = true
self.logger:debug("Not all side lanes are ready for next stage index " .. nextStageIndex .. ", default lane will not be activated")
end
end
end end
end end
end end
---@param stageNumber number if anyWaiting == false then
---@param stageLaneIdentifier string? local newStageNumber = currentStage + 1
---@return boolean? self.logger:debug("Setting next stage to: " .. tostring(newStageNumber))
function GlobalStageManager:IsStageComplete(stageNumber, stageLaneIdentifier) Events.PublishStageNumberChanged(newStageNumber)
local stageLane = self._stageRepository:getStageLane(stageLaneIdentifier) end
if not stageLane then
self.logger:warn("Stage lane " .. (stageLaneIdentifier or "default") .. " does not exist")
return nil
end end
return stageLane:IsStageIndexComplete(stageNumber)
end end
---@public ---@public
function GlobalStageManager:OnStageNumberChanged(stageNumber, stageLaneIdentifier) function GlobalStageManager:OnStageNumberChanged(stageNumber)
-- 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.logger:debug("Stage number changed to: " .. tostring(stageNumber))
currentStage = stageNumber
self:UpdateDrawings(stageNumber) self:UpdateDrawings(stageNumber)
end end
function GlobalStageManager:OnStageNumberChangeComplete(stageNumber, stageLaneIdentifier)
-- only react on "main" lane changes, ignore other lanes for now
if stageLaneIdentifier ~= nil then return end
self.logger:debug("Stage number change complete to: " .. tostring(stageNumber))
local groups = {}
for _, player in pairs(DcsUtil.getAllPlayerUnits()) do
local group = player:getGroup()
if group then
groups[group:getID()] = group
end
end
for _, group in pairs(groups) do
self._missionCommandsHelper:OverviewToGroup(group:getID())
end
end
---@private ---@private
---@param stageNumber number function GlobalStageManager:UpdateDrawings(stageNumber)
---@param stageLaneIdentifier string? local drawings = self.database:getCustomDrawings()
function GlobalStageManager:UpdateDrawings(stageNumber, stageLaneIdentifier)
self.logger:debug("Updating custom drawings for stage number: " .. tostring(stageNumber))
local drawings = self.database:getStageDrawings()
for _, drawing in pairs(drawings) do for _, drawing in pairs(drawings) do
local startStage = drawing:GetStartingStage() local startStage, stopStage = drawing:GetStartAndStop()
local stopStage = drawing:GetRemoveAtStage()
local laneIdentifier = drawing:GetStageLaneIdentifier()
if laneIdentifier == stageLaneIdentifier then
if stageNumber >= startStage and stageNumber < stopStage then if stageNumber >= startStage and stageNumber < stopStage then
self.logger:debug("Drawing " .. drawing:GetName() .. " is active for stage number: " .. tostring(stageNumber))
drawing:Draw() drawing:Draw()
else else
self.logger:debug("Drawing " .. drawing:GetName() .. " is not active for stage number: " .. tostring(stageNumber))
drawing:Remove() drawing:Remove()
end end
end end
end end
GlobalStageManager.printFullOverview = function ()
local logger = Logger.new("StageOverview", "INFO")
logger:info("Stage overview:")
local max = 0
local lines = {}
for stageIndex, stages in pairs(StagesByIndex) do
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
end end
local index = tonumber(stageIndex)
function GlobalStageManager:PrintMermaidStage() if index then
local lanes = self._stageRepository:getAllStageLanes() if index > max then
max = index
local nodes = {}
local edges = {}
local stageIndicesByLane = {} -- Track all stage indices per lane
local mainLaneId = "default"
-- 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
}
local laneColorMap = {} -- Map lane ID to color
local colorIndex = 1
-- 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
-- Assign color to this lane
laneColorMap[laneId] = laneColors[colorIndex]
colorIndex = colorIndex + 1
if colorIndex > #laneColors then
colorIndex = 1
end end
lines[index] ="Stage# " .. tostring(stageIndex).. " | " .. totalStrike .. " strikes | " .. totaldead .. " dead | " .. totalbai .. " BAI | " .. totalCas .. " CAS | Total:" .. totalMissions
-- 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
local bracketLabel
if laneId == "default" then
bracketLabel = "[" .. stageIndex .. "]"
else else
bracketLabel = "[" .. laneId .. stageIndex .. "]" logger:warn("Stage index is not a number: " .. stageIndex)
end
-- Combined label: [bracket] Name
local label = bracketLabel .. " " .. stageName
-- Different node shapes for different stage types
local nodeShape = "["
local nodeEnd = "]"
if stageType == "ExtraStage" then
nodeShape = "(["
nodeEnd = "])"
elseif stageType == "WaitingStage" then
nodeShape = "[["
nodeEnd = "]]"
end
nodes[nodeId] = string.format(' %s%s"%s"%s',
nodeId, nodeShape, label, nodeEnd)
end
end
end end
end end
-- Add edges for sequential stages within the same lane for i = 1, max do
for laneId, stageIndices in pairs(stageIndicesByLane) do if lines[i] then
for i = 1, #stageIndices - 1 do logger:info(lines[i])
local fromIdx = stageIndices[i]
local toIdx = stageIndices[i + 1]
local fromNodeId = laneId .. "_" .. fromIdx
local toNodeId = laneId .. "_" .. toIdx
-- Check if this is a chapter boundary (gap)
local isChapter = false
for _, lane in ipairs(lanes) do
local normalizedLaneId = lane:GetStageLaneIdentifier() or "default"
if normalizedLaneId == laneId then
isChapter = lane:IsChapterStart(toIdx)
break
end end
end end
local label = isChapter and "|chapter|" or ""
table.insert(edges, string.format(' %s -->%s %s', fromNodeId, label, toNodeId))
end
end
-- Get main lane reference
local mainStageLane = nil
for _, lane in ipairs(lanes) do
if lane:IsDefaultStageLane() then
mainStageLane = lane
break
end
end
-- Add dependencies from DEFAULT lane to SIDE lanes at chapter starts
-- Side lanes activate when main completes the stage BEFORE the chapter start
if mainStageLane then
for _, lane in ipairs(lanes) do
if lane:IsDefaultStageLane() == false then
local sideId = lane:GetStageLaneIdentifier()
local sideIndices = stageIndicesByLane[sideId]
-- 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]
-- Find the appropriate gate node for this side lane
-- Use first stage >= nextIdx if it exists, otherwise use the highest stage
local gateStageIdx = nil
for _, idx in ipairs(sideIndices) do
if idx >= nextIdx then
gateStageIdx = idx
break
end
end
-- If no stage >= nextIdx, use the highest stage in this lane
if gateStageIdx == nil and #sideIndices > 0 then
gateStageIdx = sideIndices[#sideIndices]
end
if gateStageIdx then
local fromNodeId = sideId .. "_" .. gateStageIdx
local toNodeId = mainLaneId .. "_" .. nextIdx
table.insert(edges, string.format(' %s -->|gate| %s', fromNodeId, toNodeId))
end
end
end
end
end
end
-- Build complete Mermaid diagram
local diagramLines = {"graph TD"}
-- Add class definitions for each lane with colors and contrasting text
for laneId, color in pairs(laneColorMap) do
table.insert(diagramLines, string.format(' classDef lane_%s fill:%s,stroke:#333,stroke-width:2px,color:#000', laneId, color))
end
-- Add nodes
for _, nodeStr in pairs(nodes) do
table.insert(diagramLines, nodeStr)
end
-- Add edges
for _, edgeStr in pairs(edges) do
table.insert(diagramLines, edgeStr)
end
-- Apply classes to nodes
for nodeId in pairs(nodes) do
local laneId = nodeId:match("(.+)_[0-9]+$")
if laneId then
table.insert(diagramLines, string.format(' class %s lane_%s', nodeId, laneId))
end
end
-- Print as single multi-line message
local diagram = "========== STAGE FLOW DIAGRAM ==========\n" .. table.concat(diagramLines, "\n") .. "\n========== END DIAGRAM =========="
self.logger:info(diagram)
end end
---comment ---comment
---@param stageNumber number ---@param stageNumber number
---@param stageLaneIdentifier string? nil for default lan
---@return boolean | nil ---@return boolean | nil
GlobalStageManager.isStageComplete = function (stageNumber, stageLaneIdentifier) GlobalStageManager.isStageComplete = function (stageNumber)
if singletonInstance == nil then local stageIndex = tostring(stageNumber)
Logger.new("StageManager", "INFO"):warn("GlobalStageManager.isStageComplete called before GlobalStageManager was initialized. Returning nil")
return nil if StagesByIndex[stageIndex] == nil then return nil end
for _, stage in ipairs(StagesByIndex[stageIndex]) do
if stage:IsComplete() == false then
return false
end
end end
if stageLaneIdentifier then return true
stageLaneIdentifier = string.lower(stageLaneIdentifier)
end
return singletonInstance:IsStageComplete(stageNumber, stageLaneIdentifier)
end end
return GlobalStageManager return GlobalStageManager
@@ -89,7 +89,7 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
local zone = DcsUtil.getZoneByName(zoneName) local zone = DcsUtil.getZoneByName(zoneName)
if zone then if zone then
BuildableZone.New(self, zone, self._buildableCrateKilos or 0, "SAM_CRATE", self._blueGroups, logger, database, blueSamData.briefing) BuildableZone.New(self, zone, self._buildableCrateKilos or 0, "SAM_CRATE", self._blueGroups, logger, database)
end end
return self return self
@@ -64,7 +64,7 @@ function FarpZone.New(database, logger, zoneName, spawnManager)
local zone = DcsUtil.getZoneByName(zoneName) local zone = DcsUtil.getZoneByName(zoneName)
if zone then if zone then
self._logger:debug("Creating Buildable zone: " .. zoneName .. " with " .. (farpData.buildingKilos or "nil") .. " kilos") self._logger:debug("Creating Buildable zone: " .. zoneName .. " with " .. (farpData.buildingKilos or "nil") .. " kilos")
BuildableZone.New(self, zone, farpData.buildingKilos or 0, "FARP_CRATE", self._groups, logger, database, farpData.briefing) BuildableZone.New(self, zone, farpData.buildingKilos or 0, "FARP_CRATE", self._groups, logger, database)
end end
end end
self:Deactivate() self:Deactivate()
@@ -2,8 +2,6 @@ local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil") local DcsUtil = require("classes.util.DcsUtil")
local SupplyUnitsTracker = require("classes.stageClasses.helpers.SupplyUnitsTracker") local SupplyUnitsTracker = require("classes.stageClasses.helpers.SupplyUnitsTracker")
local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper") local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper")
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
---@class SupplyHub ---@class SupplyHub
---@field private _database Database ---@field private _database Database
@@ -14,7 +12,7 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
---@field private _isCommmandAdded table<string, boolean> ---@field private _isCommmandAdded table<string, boolean>
---@field private _missionCommandsHelper MissionCommandsHelper ---@field private _missionCommandsHelper MissionCommandsHelper
---@field private _inZone table<string, boolean> ---@field private _inZone table<string, boolean>
---@field private _customDrawing CustomDrawing? ---@field private _drawID number
---@field private _cargoInUnits table<table<string, number>> ---@field private _cargoInUnits table<table<string, number>>
---@field private _activeAtStart boolean ---@field private _activeAtStart boolean
---@field private _active boolean ---@field private _active boolean
@@ -32,7 +30,6 @@ function SupplyHub.new(database, logger, zoneName)
self._database = database self._database = database
self._logger = logger self._logger = logger
self._zoneName = zoneName self._zoneName = zoneName
self._customDrawing = nil
local split = Util.split_string(zoneName, "_") local split = Util.split_string(zoneName, "_")
if string.lower(split[2]) == "a" then if string.lower(split[2]) == "a" then
@@ -43,9 +40,9 @@ function SupplyHub.new(database, logger, zoneName)
self._zone = DcsUtil.getZoneByName(zoneName) self._zone = DcsUtil.getZoneByName(zoneName)
self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate() self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate(logger.LogLevel)
self._inZone = {} self._inZone = {}
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() self._missionCommandsHelper = MissionCommandsHelper.getOrCreate(logger.LogLevel)
self._logger:debug("Creating Supply Hub zone: " .. self._zoneName) self._logger:debug("Creating Supply Hub zone: " .. self._zoneName)
@@ -75,13 +72,13 @@ function SupplyHub:Activate()
self._logger:debug("Activating Supply Hub zone: " .. self._zoneName) self._logger:debug("Activating Supply Hub zone: " .. self._zoneName)
local zone = DcsUtil.getZoneByName(self._zoneName) local zone = DcsUtil.getZoneByName(self._zoneName)
if zone and self._customDrawing == nil then if zone and self._drawID == nil then
local fillColor = DrawingHelper.ColorTableToColorString({ 0, 1, 0, 0.2 }) ---@type DrawColor
local lineColor = DrawingHelper.ColorTableToColorString({ 0, 1, 0, 1}) local fillColor = { r=0, g=1, b=0, a=0.2 }
---@type DrawColor
local lineColor = { r=0, g=1, b=0, a=1}
local lineStyle = 1 local lineStyle = 1
self._drawID = DcsUtil.DrawZone(zone, lineColor, fillColor, lineStyle)
self._customDrawing = CustomDrawing.FromZone(zone, lineColor, fillColor, lineStyle, 6)
self._customDrawing:Draw()
end end
self._supplyUnitsTracker:RegisterHub(self) self._supplyUnitsTracker:RegisterHub(self)
@@ -20,8 +20,7 @@ BuildableZone.__index = BuildableZone
---@param database Database ---@param database Database
---@param crateType SupplyType ---@param crateType SupplyType
---@param logger Logger ---@param logger Logger
---@param briefing string? function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroups, logger, database)
function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroups, logger, database, briefing)
self._targetZone = targetZone self._targetZone = targetZone
self._requiredKilos = kilosRequired or 0 self._requiredKilos = kilosRequired or 0
self._buildableGroups = buildableGroups or {} self._buildableGroups = buildableGroups or {}
@@ -70,7 +69,7 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
local noLandingZone = self:GetNoLandingZone() local noLandingZone = self:GetNoLandingZone()
if kilosRequired and kilosRequired > 0 then if kilosRequired and kilosRequired > 0 then
self._buildableMission = BuildableMission.new(database, logger, targetZone, noLandingZone, kilosRequired, crateType, briefing) self._buildableMission = BuildableMission.new(database, logger, targetZone, noLandingZone, kilosRequired, crateType)
self._buildableMission:AddOnCrateDroppedOfListener(self) self._buildableMission:AddOnCrateDroppedOfListener(self)
else else
self._buildableMission = nil self._buildableMission = nil
-178
View File
@@ -1,178 +0,0 @@
---@class StageLane
---@field private _isDefaultStageLane boolean
---@field private _stageLaneIdentifier string
---@field private _activeStageIndex number
---@field private _stagesInLaneByIndex table<number, Array<Stage>>
---@field private _chapterStarts table<number, boolean>?
---@field private _stageLaneState StageLaneState
---@field private _maxStageIndex number
local StageLane = {}
StageLane.__index = StageLane
---@alias StageLaneState
---| "InChapter"
---| "BetweenChapters"
---| "Completed"
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[stageIndex] then
self._stagesInLaneByIndex[stageIndex] = {}
end
if not self._maxStageIndex or stageIndex > self._maxStageIndex then
self._maxStageIndex = stageIndex
end
table.insert(self._stagesInLaneByIndex[stageIndex], stage)
end
---@param stageNumber number
---@return boolean?
function StageLane:IsStageIndexComplete(stageNumber)
local stages = self._stagesInLaneByIndex[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[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
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
local stageIndices = {}
for stageIndex, _ in pairs(self._stagesInLaneByIndex) do
table.insert(stageIndices, tonumber(stageIndex))
end
table.sort(stageIndices)
self._chapterStarts = self._chapterStarts or {}
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[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
@@ -1,60 +0,0 @@
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
@@ -11,21 +11,12 @@ local StageBase = require("classes.stageClasses.SpecialZones.StageBase")
local BlueSam = require("classes.stageClasses.SpecialZones.BlueSam") local BlueSam = require("classes.stageClasses.SpecialZones.BlueSam")
local Events = require("classes.spearhead_events") local Events = require("classes.spearhead_events")
local GlobalCapManager = require("classes.capClasses.GlobalCapManager") 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 ---@alias StageColor
---| "RED" ---| "RED"
---| "BLUE" ---| "BLUE"
---| "GRAY" ---| "GRAY"
---@alias StageType
---| "PrimaryStage"
---| "ExtraStage"
---| "WaitingStage"
--- @class StageData --- @class StageData
--- @field stageBriefing string? --- @field stageBriefing string?
--- @field missionsByCode table<string, Mission> --- @field missionsByCode table<string, Mission>
@@ -40,11 +31,9 @@ local StageState = require("classes.stageClasses.Stages.BaseStage.StageState")
--- @field supplyHubs Array<SupplyHub> --- @field supplyHubs Array<SupplyHub>
--- @class StageInitData --- @class StageInitData
--- @field type StageType
--- @field stageZoneName string --- @field stageZoneName string
--- @field stageNumber integer --- @field stageNumber integer
--- @field stageDisplayName string --- @field stageDisplayName string
--- @field stageLaneIdentifier string?
--- @class StageCompleteListener --- @class StageCompleteListener
@@ -54,17 +43,17 @@ local StageState = require("classes.stageClasses.Stages.BaseStage.StageState")
--- @field zoneName string --- @field zoneName string
--- @field stageName string? --- @field stageName string?
--- @field stageNumber number --- @field stageNumber number
--- @field protected _stageLaneIdentifier string?
--- @field protected _stageType StageType
--- @field protected _currentStageState CurrentStageState
--- @field protected _missionCommandsHelper MissionCommandsHelper --- @field protected _missionCommandsHelper MissionCommandsHelper
--- @field protected _isActive boolean
--- @field protected _isComplete boolean
--- @field protected _missionPriority MissionPriority --- @field protected _missionPriority MissionPriority
--- @field protected _database Database --- @field protected _database Database
--- @field protected _stageRepository StageRepository
--- @field protected _db StageData --- @field protected _db StageData
--- @field protected _logger Logger --- @field protected _logger Logger
--- @field protected _preActivated boolean
--- @field protected _activeStage integer
--- @field protected _stageConfig StageConfig --- @field protected _stageConfig StageConfig
--- @field protected _customDrawing CustomDrawing --- @field protected _stageDrawingId integer
--- @field protected _spawnedGroups Array<string> --- @field protected _spawnedGroups Array<string>
--- @field protected _stageCompleteListeners Array<StageCompleteListener> --- @field protected _stageCompleteListeners Array<StageCompleteListener>
--- @field protected CheckContinuousAsync fun(self:Stage, time:number) : number? --- @field protected CheckContinuousAsync fun(self:Stage, time:number) : number?
@@ -74,12 +63,13 @@ local Stage = {}
Stage.__index = Stage Stage.__index = Stage
Stage.StageColors = { Stage.StageColors = {
INVISIBLE = { 0, 0, 0, 0 }, INVISIBLE = { r=0, g=0, b=0, a=0 },
RED_ACTIVE = { 1, 0, 0, 0.20 }, RED_ACTIVE = { r=1, g=0, b=0, a=0.15 },
RED_PREACTIVE = { 1, 0, 0, 0.05}, RED_PREACTIVE = { r=1, g=0, b=0, a=0.10},
BLUE = { 0, 0, 1, 0.10}, BLUE = { r=0, g=0, b=1, a=0.10},
GRAY = { 80/255, 80/255, 80/255, 0.10 } GRAY = { r=80/255, g=80/255, b=80/255, a=0.10 }
} }
---comment ---comment
@@ -89,22 +79,16 @@ Stage.StageColors = {
---@param initData StageInitData ---@param initData StageInitData
---@param missionPriority MissionPriority ---@param missionPriority MissionPriority
---@param spawnManager SpawnManager ---@param spawnManager SpawnManager
---@param stageType StageType
---@return Stage ---@return Stage
function Stage:superNew(database, stageConfig, logger, initData, stageType, missionPriority, spawnManager) function Stage:superNew(database, stageConfig, logger, initData, missionPriority, spawnManager)
logger:debug("[BaseStage] Initiating stage with name: " .. initData.stageZoneName) logger:debug("[BaseStage] Initiating stage with name: " .. initData.stageZoneName)
self._currentStageState = StageState.Inactive
self.zoneName = initData.stageZoneName self.zoneName = initData.stageZoneName
self.stageNumber = initData.stageNumber self.stageNumber = initData.stageNumber
self._stageRepository = StageRepository.getInstance() self._isActive = false
if initData.stageLaneIdentifier then self._isComplete = false
self._stageLaneIdentifier = string.lower(initData.stageLaneIdentifier)
else
self._stageLaneIdentifier = nil
end
self.stageName = initData.stageDisplayName self.stageName = initData.stageDisplayName
self._stageType = stageType
self.OnPostStageComplete = nil self.OnPostStageComplete = nil
self.OnPostBlueActivated = nil self.OnPostBlueActivated = nil
@@ -125,19 +109,14 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
supplyHubs = {} supplyHubs = {}
} }
self._activeStage = -99
self._preActivated = false
self._stageConfig = stageConfig or {} self._stageConfig = stageConfig or {}
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() self._missionCommandsHelper = MissionCommandsHelper.getOrCreate(logger.LogLevel)
local zone = DcsUtil.getZoneByName(self.zoneName) local zone = DcsUtil.getZoneByName(self.zoneName)
if zone then if zone then
self._stageDrawingId = DcsUtil.DrawZone(zone, Stage.StageColors.INVISIBLE, Stage.StageColors.INVISIBLE, 4)
local colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.INVISIBLE)
local fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.INVISIBLE)
local customDrawing = CustomDrawing.FromZone(zone, colorString, fillColorString, 1, 5)
if customDrawing then
self._customDrawing = customDrawing
end
end end
self._spawnedGroups = {} self._spawnedGroups = {}
@@ -274,7 +253,6 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
end end
end end
Events.AddStageNumberChangedListener(self) Events.AddStageNumberChangedListener(self)
return self return self
@@ -282,7 +260,7 @@ end
---@return boolean ---@return boolean
function Stage:IsComplete() function Stage:IsComplete()
if self._currentStageState >= StageState.Blue then return true end if self._isComplete == true then return true end
for i, mission in pairs(self._db.sams) do for i, mission in pairs(self._db.sams) do
local state = mission:getState() local state = mission:getState()
@@ -297,44 +275,14 @@ function Stage:IsComplete()
return false return false
end end
end end
self._isComplete = true
return true return true
end end
---@return boolean ---@return boolean
function Stage:IsActive() function Stage:IsActive()
return self._currentStageState == StageState.Activated return self._isActive == true
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
function Stage:GetMissions()
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 end
---comment ---comment
@@ -397,7 +345,7 @@ function Stage:IsMissionComplete(missionName)
return mission:getState() == "COMPLETED" return mission:getState() == "COMPLETED"
end end
-- private usage only ---private use only
function Stage:NotifyComplete() function Stage:NotifyComplete()
self._logger:info("Stage complete: " .. (self.stageName or self.stageNumber or "unknown")) self._logger:info("Stage complete: " .. (self.stageName or self.stageNumber or "unknown"))
@@ -419,13 +367,10 @@ function Stage:AddStageCompleteListener(listener)
end end
---Activates all SAMS, Airbase units etc all at once. ---Activates all SAMS, Airbase units etc all at once.
function Stage:PreActivate() ---@param draw boolean
function Stage:PreActivate(draw)
if self._currentStageState >= StageState.PreActivated then if self._preActivated == false then
return self._preActivated = true
end
self._currentStageState = StageState.PreActivated
for key, mission in pairs(self._db.sams) do for key, mission in pairs(self._db.sams) do
if mission then if mission then
mission:SpawnInactive() mission:SpawnInactive()
@@ -435,64 +380,42 @@ function Stage:PreActivate()
for _, airbase in pairs(self._db.airbases) do for _, airbase in pairs(self._db.airbases) do
airbase:ActivateRedStage() airbase:ActivateRedStage()
end end
end
self:MarkStage() if draw == true then
self:MarkStage(Stage.StageColors.RED_PREACTIVE)
end
end end
function Stage:MarkStage() ---@param stageColor DrawColor
function Stage:MarkStage(stageColor)
local lineColor = { r=stageColor.r, g=stageColor.g, b=stageColor.b, a=stageColor.a }
local fillColor = { r=stageColor.r, g=stageColor.g, b=stageColor.b, a=stageColor.a }
self._logger:debug("Marking stage '" .. Util.toString(self.zoneName) .. "' with state: " .. self._currentStageState) if stageColor.a > 0 then
lineColor.a = 1
if self._customDrawing then
self._customDrawing:Remove()
end end
if self._stageConfig.isDrawStagesEnabled == false then return end if stageColor == Stage.StageColors.RED_PREACTIVE then
if self._stageConfig.isDrawPreActivatedEnabled == false and self._currentStageState == StageState.PreActivated then lineColor.a = 0
return
end end
if self._customDrawing then if self._stageDrawingId and self._stageConfig.isDrawStagesEnabled == true then
self._customDrawing:UpdateDrawingObject(function(drawingObject) DcsUtil.SetLineColor(self._stageDrawingId, lineColor)
local drawing = drawingObject --[[@as Polygon]] DcsUtil.SetFillColor(self._stageDrawingId, fillColor)
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 == 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 == StageState.Blue then
drawing.fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.BLUE)
drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.BLUE)
drawing.style = "two dash"
else
drawing.fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.INVISIBLE)
drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.INVISIBLE)
drawing.style = "no line"
end end
return drawing
end)
self._customDrawing:Draw()
end
end end
function Stage:ActivateStage() function Stage:ActivateStage()
self._isActive = true;
if self._currentStageState >= StageState.Activated then
return
end
self:PreActivate()
self._currentStageState = StageState.Activated
pcall(function() pcall(function()
self:MarkStage() self:MarkStage(Stage.StageColors.RED_ACTIVE)
end) end)
self:PreActivate(false)
self._logger:debug("Activating Misc groups for zone. Count: " .. Util.tableLength(self._db.miscGroups)) self._logger:debug("Activating Misc groups for zone. Count: " .. Util.tableLength(self._db.miscGroups))
for _, miscGroup in pairs(self._db.miscGroups) do for _, miscGroup in pairs(self._db.miscGroups) do
miscGroup:Spawn() miscGroup:Spawn()
@@ -516,10 +439,6 @@ function Stage:ActivateStage()
end end
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) timer.scheduleFunction(self.CheckContinuousAsync, self, timer.getTime() + 3)
end end
@@ -532,60 +451,43 @@ end
---comment ---comment
---@param self Stage ---@param self Stage
---@param number integer ---@param number integer
---@param stageLaneIdentifier string? function Stage:OnStageNumberChanged(number)
function Stage:OnStageNumberChanged(number, stageLaneIdentifier)
---@return boolean if self._activeStage == number then --only activate once for a stage
local needsPreActivation = function() return
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 end
if GlobalCapManager.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then local previousActive = self._activeStage
return true self._activeStage = number
end
if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then
self._logger:debug("Pre-activating stage: " .. self.zoneName .. " with number: " .. number)
self:PreActivate(true)
elseif GlobalCapManager.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
self:PreActivate(false)
end end
return false if number == self.stageNumber then
end
---@return boolean
local needsActivation = function()
if self._stageLaneIdentifier == stageLaneIdentifier and self.stageNumber == number then
return true
end
return false
end
---@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 needsActivation() == true then
self:ActivateStage() self:ActivateStage()
if self._db and self._db.stageBriefing then
self._missionCommandsHelper:AddStageBriefing(self.zoneName, self._db.stageBriefing)
end
end end
if needsBlueActivation() == true then if previousActive <= self.stageNumber then
if number > self.stageNumber then
self:ActivateBlueStage() self:ActivateBlueStage()
end end
end
if number > self.stageNumber then
self._missionCommandsHelper:RemoveStageBriefing(self.zoneName)
end
end
function Stage:GetBriefing()
return "Briefing For "
end end
---@param self Stage ---@param self Stage
@@ -594,6 +496,7 @@ Stage.OnMissionComplete = function(self, mission)
self:CheckAndUpdateSelf() self:CheckAndUpdateSelf()
end end
---private use only ---private use only
function Stage:ActivateBlueGroups() function Stage:ActivateBlueGroups()
@@ -655,12 +558,8 @@ end
function Stage:ActivateBlueStage() function Stage:ActivateBlueStage()
if self._currentStageState >= StageState.Blue then
return
end
self._logger:debug("Setting stage '" .. Util.toString(self.zoneName) .. "' to blue") self._logger:debug("Setting stage '" .. Util.toString(self.zoneName) .. "' to blue")
self._currentStageState = StageState.Blue
for _, mission in pairs(self._db.missions) do for _, mission in pairs(self._db.missions) do
mission:SpawnPersistedState() mission:SpawnPersistedState()
end end
@@ -676,7 +575,7 @@ function Stage:ActivateBlueStage()
---@param self Stage ---@param self Stage
local ActivateBlueAsync = function(self) local ActivateBlueAsync = function(self)
pcall(function() pcall(function()
self:MarkStage() self:MarkStage(Stage.StageColors.BLUE)
end) end)
self:ActivateBlueGroups() self:ActivateBlueGroups()
@@ -684,8 +583,6 @@ function Stage:ActivateBlueStage()
return nil return nil
end end
self._missionCommandsHelper:RemoveStageBriefing(self.zoneName)
timer.scheduleFunction(ActivateBlueAsync, self, timer.getTime() + 3) timer.scheduleFunction(ActivateBlueAsync, self, timer.getTime() + 3)
end end
@@ -1,12 +0,0 @@
--- 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
+7 -12
View File
@@ -19,11 +19,11 @@ function ExtraStage.New(database, stageConfig, logger, initData, spawnManager)
setmetatable(ExtraStage, Stage) setmetatable(ExtraStage, Stage)
local self = setmetatable({}, { __index = ExtraStage }) --[[@as ExtraStage]] local self = setmetatable({}, { __index = ExtraStage }) --[[@as ExtraStage]]
self:superNew(database, stageConfig, logger, initData, "ExtraStage", "secondary", spawnManager) self:superNew(database, stageConfig, logger, initData, "secondary", spawnManager)
self.OnPostBlueActivated = function (selfStage) self.OnPostBlueActivated = function (selfStage)
selfStage:MarkStage() selfStage:MarkStage(Stage.StageColors.GRAY)
end end
self.OnPostStageComplete = function (selfStage) self.OnPostStageComplete = function (selfStage)
@@ -34,14 +34,9 @@ function ExtraStage.New(database, stageConfig, logger, initData, spawnManager)
end end
---comment ---comment
---@param self ExtraStage ---@param self Stage
---@param number integer ---@param number integer
---@param stageLaneIdentifier string? function ExtraStage:OnStageNumberChanged(number)
function ExtraStage:OnStageNumberChanged(number, stageLaneIdentifier)
if stageLaneIdentifier ~= self._stageLaneIdentifier then
return
end
if self._activeStage == number then --only activate once for a stage if self._activeStage == number then --only activate once for a stage
return return
@@ -52,16 +47,16 @@ function ExtraStage:OnStageNumberChanged(number, stageLaneIdentifier)
if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then
self._logger:debug("Pre-activating stage: " .. self.zoneName .. " with number: " .. number) self._logger:debug("Pre-activating stage: " .. self.zoneName .. " with number: " .. number)
self:PreActivate() self:PreActivate(true)
elseif GlobalCapManager.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then elseif GlobalCapManager.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
self:PreActivate() self:PreActivate(false)
end end
if number == self.stageNumber then if number == self.stageNumber then
self:ActivateStage() self:ActivateStage()
end end
if self._currentStageState == Stage.CurrentStageState.BLUE then if self._isComplete == true then
self:ActivateBlueStage() self:ActivateBlueStage()
end end
@@ -18,7 +18,7 @@ function PrimaryStage.New(database, stageConfig, logger, initData, spawnManager)
setmetatable(PrimaryStage, Stage) setmetatable(PrimaryStage, Stage)
local self = setmetatable({}, { __index = PrimaryStage }) --[[@as PrimaryStage]] local self = setmetatable({}, { __index = PrimaryStage }) --[[@as PrimaryStage]]
self:superNew(database, stageConfig, logger, initData, "PrimaryStage", "primary", spawnManager) self:superNew(database, stageConfig, logger, initData, "primary", spawnManager)
return self return self
end end
@@ -4,24 +4,28 @@ local Stage = require("classes.stageClasses.Stages.BaseStage.Stage")
---@field private _waitTimeSeconds integer ---@field private _waitTimeSeconds integer
---@field private _startTime number ---@field private _startTime number
local WaitingStage = {} local WaitingStage = {}
WaitingStage.__index = WaitingStage WaitingStage.__index = WaitingStage
---@class WaitingStageInitData : StageInitData
---@field waitingSeconds integer
local WaitingStageInitData = {}
---comment ---comment
---@param database Database ---@param database Database
---@param stageConfig StageConfig ---@param stageConfig StageConfig
---@param logger any ---@param logger any
---@param initData StageInitData ---@param initData WaitingStageInitData
---@param spawnManager SpawnManager ---@param spawnManager SpawnManager
---@param waitingSeconds integer
---@return WaitingStage ---@return WaitingStage
function WaitingStage.New(database, stageConfig, logger, initData, waitingSeconds, spawnManager) function WaitingStage.New(database, stageConfig, logger, initData, spawnManager)
setmetatable(WaitingStage, Stage) setmetatable(WaitingStage, Stage)
local self = setmetatable({}, { __index = WaitingStage }) --[[@as WaitingStage]] local self = setmetatable({}, { __index = WaitingStage }) --[[@as WaitingStage]]
self:superNew(database, stageConfig, logger, initData, "WaitingStage", "none", spawnManager) self:superNew(database, stageConfig, logger, initData, "none", spawnManager)
self._waitTimeSeconds = 5 self._waitTimeSeconds = 5
if waitingSeconds and waitingSeconds > 5 then self._waitTimeSeconds = waitingSeconds end if initData.waitingSeconds and initData.waitingSeconds > 5 then self._waitTimeSeconds = initData.waitingSeconds end
self._startTime = nil self._startTime = nil
self.CheckContinuousAsync = function (selfA, time) self.CheckContinuousAsync = function (selfA, time)
@@ -1,139 +1,48 @@
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper") local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
local Util = require("classes.util.Util") local Util = require("classes.util.Util")
local Logger = require("classes.util.Logger")
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
local drawingLogger = Logger.new("CustomDrawing")
---@class CustomDrawing ---@class CustomDrawing
---@field protected _IDs Array<integer>? ---@field private _id integer?
---@field protected _name string? ---@field private _drawingObject DrawingObject
---@field protected _drawingObject DrawingObject ---@field private _startingStage number
---@field private _removeAtStage number
local CustomDrawing = {} local CustomDrawing = {}
CustomDrawing.__index = CustomDrawing CustomDrawing.__index = CustomDrawing
---@protected
---@param drawingObject DrawingObject ---@param drawingObject DrawingObject
---@return CustomDrawing? ---@param id integer?
function CustomDrawing.New(drawingObject) ---@return CustomDrawing
function CustomDrawing.New(drawingObject, id)
local self = setmetatable({}, CustomDrawing) local self = setmetatable({}, CustomDrawing)
self._drawingObject = drawingObject self._drawingObject = drawingObject
self._IDs = {} -- initiate IDs as an empty array, when drawn it will be set to the IDs returned by DrawingHelper.Draw self._id = id
self._name = drawingObject.name
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
return self return self
end end
---@param points Array<Vec2> ---@return number start
---@param colorString string ---@return number stop
---@param lineStyle LineType function CustomDrawing:GetStartAndStop()
---@param lineThickness number return self._startingStage, self._removeAtStage
---@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
---@param lineStyle LineType
---@param lineThickness number
---@return CustomDrawing?
function CustomDrawing.FromZone(zone, colorString, fillColorString, lineStyle, lineThickness)
if zone == nil then
drawingLogger:warn("CustomDrawing.FromZone called with nil zone")
return nil
end
if zone.zone_type == "Cilinder" then
---@type Circle
local drawingObject = {
mapX = zone.location.x,
mapY = zone.location.y,
radius = zone.radius,
name = zone.name .. "_drawing",
primitiveType = "Polygon",
polygonMode = "circle",
visible = true,
style = DrawingHelper.ToLineStyleString(lineStyle),
colorString = colorString,
fillColorString = fillColorString,
thickness = lineThickness,
}
return CustomDrawing.New(drawingObject)
end
if zone.zone_type == "Polygon" then
---@type Free
local drawingObject = {
mapX = 0,
mapY = 0,
points = zone.verts,
name = zone.name .. "_drawing",
primitiveType = "Polygon",
polygonMode = "free",
visible = true,
style = DrawingHelper.ToLineStyleString(lineStyle),
colorString = colorString,
fillColorString = fillColorString,
thickness = lineThickness,
}
return CustomDrawing.New(drawingObject)
end
end
---@return string
function CustomDrawing:GetName()
return self._drawingObject.name
end end
function CustomDrawing:Draw() function CustomDrawing:Draw()
drawingLogger:debug("Drawing custom drawing with IDs " .. table.concat(self._IDs, ", ")) self._id = DrawingHelper.Draw(self._drawingObject)
if self._IDs ~= nil then
for _, id in ipairs(self._IDs) do
DrawingHelper.Remove(id)
end
end
self._IDs = DrawingHelper.Draw(self._drawingObject)
end end
function CustomDrawing:Remove() function CustomDrawing:Remove()
if self._IDs ~= nil then if self._id ~= nil then
for _, id in ipairs(self._IDs) do DrawingHelper.Remove(self._id)
drawingLogger:debug("Removing custom drawing with ID " .. tostring(id)) self._id = nil
DrawingHelper.Remove(id)
end end
self._IDs = {}
end
end
---@param updateFunc fun(drawingObject:DrawingObject):DrawingObject
function CustomDrawing:UpdateDrawingObject(updateFunc)
self._drawingObject = updateFunc(self._drawingObject)
end end
return CustomDrawing return CustomDrawing
@@ -1,93 +0,0 @@
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,45 +1,33 @@
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.New():isDebugEnabled() then
level = "DEBUG"
end
local logger = Logger.new("DrawingHelper", level)
---@class DrawingHelper ---@class DrawingHelper
local DrawingHelper = {} local DrawingHelper = {}
DrawingHelper.__index = DrawingHelper DrawingHelper.__index = DrawingHelper
local customDrawingIdIncrementer = 4210
---@param object DrawingObject ---@param object DrawingObject
---@return Array<integer> id ---@return integer? id
function DrawingHelper.Draw(object) function DrawingHelper.Draw(object)
if object == nil then if object == nil then
if logger then return nil
logger:warn("DrawingHelper.Draw called with nil object")
end
return {}
end end
local id = DrawingHelper.GetAndAddId()
if(object.primitiveType == "Polygon") then if(object.primitiveType == "Polygon") then
return DrawingHelper.DrawPolygon(object--[[@as Polygon]]) DrawingHelper.DrawPolygon(object--[[@as Polygon]], id)
elseif(object.primitiveType == "Line") then elseif(object.primitiveType == "Line") then
return DrawingHelper.DrawLine(object--[[@as Line]]) DrawingHelper.DrawLine(object--[[@as Line]], id)
elseif(object.primitiveType == "TextBox") then elseif(object.primitiveType == "TextBox") then
return DrawingHelper.DrawTextBox(object--[[@as TextBox]]) DrawingHelper.DrawTextBox(object--[[@as TextBox]], id)
else
logger:warn("Unknown primitive type: " .. tostring(object.primitiveType))
end end
return {} return id
end end
function DrawingHelper.GetAndAddId() function DrawingHelper.GetAndAddId()
return DcsUtil.GetNextDrawID() customDrawingIdIncrementer = customDrawingIdIncrementer + 1
return customDrawingIdIncrementer
end end
---@private ---@private
@@ -48,49 +36,35 @@ end
---@param points Array<Vec3> ---@param points Array<Vec3>
---@param fillColor table ---@param fillColor table
---@param lineColor table ---@param lineColor table
---@param lineStyle LineType local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineStyle)
---@param lineThickness number
local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineStyle, lineThickness)
if lineThickness == nil or lineThickness <= 0 then
lineStyle = 0
end
local functionString = "trigger.action.markupToAll(" .. shapeID .. ", -1, " .. drawID .. "," local functionString = "trigger.action.markupToAll(" .. shapeID .. ", -1, " .. drawID .. ","
for _, point in ipairs(points) do
for _, point in pairs(points) do
functionString = functionString .. " { x=" .. point.x .. ", y=0,z=" .. point.z .. "}," functionString = functionString .. " { x=" .. point.x .. ", y=0,z=" .. point.z .. "},"
end end
functionString = functionString .. "{0,1,0,1}, {0,1,0,1}, " .. lineStyle .. ")" functionString = functionString ..
"{ " .. lineColor[1] .. "," .. lineColor[2] .. "," .. lineColor[3] .. "," .. lineColor[4] .. "}, " ..
logger:debug("Drawing complex drawing with ID " .. tostring(drawID) .. " and function string: " .. functionString) "{ " .. fillColor[1] .. "," .. fillColor[2] .. "," .. fillColor[3] .. "," .. fillColor[4] .. "}, " ..
lineStyle .. ")"
---@diagnostic disable-next-line: deprecated ---@diagnostic disable-next-line: deprecated
local f, err = loadstring(functionString) local f, err = loadstring(functionString)
if f then if f then
f() f()
else else
logger:error("Something failed when drawing complex drawing" .. err) env.error("Something failed when drawing complex drawing" .. err)
end end
if fillColor then
trigger.action.setMarkupColorFill(drawID, fillColor)
end
trigger.action.setMarkupColor(drawID, lineColor)
trigger.action.setMarkupTypeLine(drawID, lineStyle)
end end
---@private ---@private
---@param object Polygon ---@param object Polygon
---@return Array<integer> ---@param id integer
function DrawingHelper.DrawPolygon(object) function DrawingHelper.DrawPolygon(object, id)
if object == nil then if object == nil then
return {} return
end end
local id = DrawingHelper.GetAndAddId()
---@param circle Circle ---@param circle Circle
local function DrawCircle(circle) local function DrawCircle(circle)
local vec3 = { x = circle.mapX, y = 0, z = circle.mapY } local vec3 = { x = circle.mapX, y = 0, z = circle.mapY }
@@ -101,12 +75,11 @@ function DrawingHelper.DrawPolygon(object)
end end
---@param oval Oval ---@param oval Oval
---@return integer?, Array<CustomDrawing>?
local function DrawOval(oval) local function DrawOval(oval)
---@type Array<Vec3> ---@type Array<Vec3>
local points = {} local points = {}
local pointsNo = 30 local pointsNo = 30
local angleStep = (2 * math.pi) / pointsNo local angleStep = (2 * math.pi) / points
local fillColor = DrawingHelper.ColorToColorTable(oval.fillColorString) local fillColor = DrawingHelper.ColorToColorTable(oval.fillColorString)
local color = DrawingHelper.ColorToColorTable(oval.colorString) local color = DrawingHelper.ColorToColorTable(oval.colorString)
@@ -118,7 +91,7 @@ function DrawingHelper.DrawPolygon(object)
local y = oval.mapY + (oval.r2 * math.sin(angle)) local y = oval.mapY + (oval.r2 * math.sin(angle))
table.insert(points, { x = x, y = 0, z = y } ) table.insert(points, { x = x, y = 0, z = y } )
end end
MarkupToAll(7, id, points, fillColor, color, lineStyle, oval.thickness) MarkupToAll(7, id, points, fillColor, color, lineStyle)
end end
---@param free Free ---@param free Free
@@ -127,26 +100,11 @@ function DrawingHelper.DrawPolygon(object)
local color = DrawingHelper.ColorToColorTable(free.colorString) local color = DrawingHelper.ColorToColorTable(free.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(free.style) local lineStyle = DrawingHelper.ToLineStyleInteger(free.style)
local keys = {}
for k, _ in pairs(free.points) do
table.insert(keys, k)
end
table.sort(keys, function(a, b) return a < b end)
local points = {} local points = {}
for _, k in ipairs(keys) do for _, point in ipairs(free.points) do
local point = free.points[k] table.insert(points, { x = point.x, y = 0, z = point.y } )
local newPoint = { x = free.mapX + point.x, y = 0, z = free.mapY + point.y }
local firstPoint = points[1]
if firstPoint == nil or newPoint.x ~= firstPoint.x or newPoint.z ~= firstPoint.z then
table.insert(points, newPoint)
end end
end MarkupToAll(7, id, points, fillColor, color, lineStyle)
MarkupToAll(7, id, points, fillColor, color, lineStyle, free.thickness)
end end
---@param rect Rect ---@param rect Rect
@@ -166,20 +124,13 @@ function DrawingHelper.DrawPolygon(object)
local color = DrawingHelper.ColorToColorTable(arrow.colorString) local color = DrawingHelper.ColorToColorTable(arrow.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(arrow.style) local lineStyle = DrawingHelper.ToLineStyleInteger(arrow.style)
logger:debug("Drawing arrow with start point: " .. tostring(arrow.mapX) .. ", " .. tostring(arrow.mapY) .. " and angle: " .. tostring(arrow.angle) .. " and length: " .. tostring(arrow.length)) local startPoint = { x = arrow.mapX, y = 0, z = arrow.mapY }
local endPoint = { x = arrow.mapX, y = 0, z = arrow.mapY }
local rad = math.rad(arrow.angle or 0) local rad = math.rad(arrow.angle or 0)
local length = arrow.length or 100 local length = arrow.length or 100
local startPoint = { x = arrow.mapX - length * math.sin(rad), y = 0, z = arrow.mapY + length * math.cos(rad) } local endPoint = { x = arrow.mapX + length * math.cos(rad), y = 0, z = arrow.mapY + length * math.sin(rad) }
trigger.action.arrowToAll(-1, id, startPoint, endPoint, color, fillColor, lineStyle, true) trigger.action.arrowToAll(-1, id, startPoint, endPoint, color, fillColor, lineStyle, true)
end end
if logger then
logger:debug("Drawing polygon with ID " .. tostring(id) .. " and polygon mode: " .. tostring(object.polygonMode))
end
if object.polygonMode == "circle" then if object.polygonMode == "circle" then
DrawCircle(object--[[@as Circle]]) DrawCircle(object--[[@as Circle]])
elseif object.polygonMode == "oval" then elseif object.polygonMode == "oval" then
@@ -191,89 +142,55 @@ function DrawingHelper.DrawPolygon(object)
elseif object.polygonMode == "arrow" then elseif object.polygonMode == "arrow" then
DrawArrow(object--[[@as Arrow]]) DrawArrow(object--[[@as Arrow]])
end end
return {id}
end end
---@private ---@private
---@param object Line ---@param object Line
---@return Array<integer> ---@param id integer
function DrawingHelper.DrawLine(object) function DrawingHelper.DrawLine(object, id)
---@type Array<Vec3> ---@type Array<Vec3>
local points = {} local points = {}
local ids = {}
for _, point in ipairs(object.points) do for _, point in ipairs(object.points) do
table.insert(points, { x = object.mapX + point.x, y = 0, z = object.mapY + point.y } ) table.insert(points, { x = point.x, y = 0, z = point.y } )
end end
local color = DrawingHelper.ColorToColorTable(object.colorString) local color = DrawingHelper.ColorToColorTable(object.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(object.style) local lineStyle = DrawingHelper.ToLineStyleInteger(object.style)
MarkupToAll(1, id, points, color, color, lineStyle)
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 end
---@private ---@private
---@param object TextBox ---@param object TextBox
---@return Array<integer> ---@param id integer
function DrawingHelper.DrawTextBox(object) function DrawingHelper.DrawTextBox(object, id)
local id = DrawingHelper.GetAndAddId()
trigger.action.textToAll(-1, id, { x= object.mapX, y = 0, z = object.mapY }, trigger.action.textToAll(-1, id, { x= object.mapX, y = 0, z = object.mapY },
DrawingHelper.ColorToColorTable(object.colorString), DrawingHelper.ColorToColorTable(object.colorString),
DrawingHelper.ColorToColorTable(object.fillColorString), DrawingHelper.ColorToColorTable(object.fillColorString),
object.fontSize or 12, object.fontSize or 12,
true, true,
object.text or "") object.text or "")
return {id}
end end
function DrawingHelper.Remove(id) function DrawingHelper.Remove(id)
trigger.action.removeMark(id) trigger.action.removeMark(id)
end end
---@param hexStr string ---@param hexStr string
---@return table ---@return table
function DrawingHelper.ColorToColorTable(hexStr) function DrawingHelper.ColorToColorTable(hexStr)
if hexStr == nil then
logger:warn("ColorToColorTable called with nil hexStr, returning default color {0, 0, 0, 0}")
return { 0, 0, 0, 0 }
end
hexStr = hexStr:gsub("0x", "") hexStr = hexStr:gsub("0x", "")
local r = tonumber(hexStr:sub(1, 2), 16) / 255 local a = tonumber(hexStr:sub(1, 2), 16) / 255
local g = tonumber(hexStr:sub(3, 4), 16) / 255 local r = tonumber(hexStr:sub(3, 4), 16) / 255
local b = tonumber(hexStr:sub(5, 6), 16) / 255 local g = tonumber(hexStr:sub(5, 6), 16) / 255
local a = tonumber(hexStr:sub(7, 8), 16) / 255 local b = tonumber(hexStr:sub(7, 8), 16) / 255
return { r, g , b , a } return { r, g , b , a }
end end
---@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
logger:warn("ColorTableToColorString called with invalid rgba table, returning default color string '0x00000000'")
return "0x00000000"
end
local r = string.format("%02X", math.floor(rgba[1] * 255))
local g = string.format("%02X", math.floor(rgba[2] * 255))
local b = string.format("%02X", math.floor(rgba[3] * 255))
local a = string.format("%02X", math.floor(rgba[4] * 255))
return "0x" .. r .. g .. b .. a
end
---@param lineStyle string ---@param lineStyle string
function DrawingHelper.ToLineStyleInteger(lineStyle) function DrawingHelper.ToLineStyleInteger(lineStyle)
lineStyle = lineStyle:lower() lineStyle = lineStyle:lower()
@@ -296,26 +213,6 @@ function DrawingHelper.ToLineStyleInteger(lineStyle)
end end
end end
function DrawingHelper.ToLineStyleString(lineStyle)
if lineStyle == 0 then
return "no line"
elseif lineStyle == 1 then
return "solid"
elseif lineStyle == 2 then
return "dashed"
elseif lineStyle == 3 then
return "dotted"
elseif lineStyle == 4 then
return "dotdash"
elseif lineStyle == 5 then
return "longdash"
elseif lineStyle == 6 then
return "twodash"
else
return "no line"
end
end
---@class ARGB ---@class ARGB
---@field public a number ---@field public a number
---@field public r number ---@field public r number
@@ -137,6 +137,10 @@ function BattleManager:LetUnitsShoot(groups, targetGroups)
} }
} }
if debugDrawing == true then
self:DrawDebugLine(point, unit)
end
local controller = unit:getController() local controller = unit:getController()
if controller then if controller then
controller:setTask(shootTask) controller:setTask(shootTask)
@@ -241,8 +245,42 @@ function BattleManager:GetRandomPoint(origin, groupHulls)
if not hull then return nil end if not hull then return nil end
local shootPoints = Util.GetTangentHullPointsFromOrigin(hull, origin) local shootPoints = Util.GetTangentHullPointsFromOrigin(hull, origin)
if debugDrawing == true then
self:DrawDebugZone({ hull })
end
return Util.randomFromList(shootPoints) --[[@as Vec2]] return Util.randomFromList(shootPoints) --[[@as Vec2]]
end end
do --DEBUG
---@param unit Unit
---@param target Vec2
function BattleManager:DrawDebugLine(target, unit)
local color = {r = 1, g = 0, b = 0, a = 1}
if unit:getCoalition() == 2 then
color = {r = 0, g = 0, b = 1, a = 1}
end
DcsUtil.DrawLine(unit:getPoint(), {x = target.x, y = 0, z = target.y}, color, 1)
end
---@param hulls Array<Array<Vec2>>
function BattleManager:DrawDebugZone(hulls)
for _, drawHull in pairs(hulls) do
---@type SpearheadTriggerZone
local zone = {
name = "temp",
zone_type = "Polygon",
radius = 0,
verts = drawHull,
location = { x=drawHull[1].x, y=drawHull[1].y },
}
DcsUtil.DrawZone(zone, {r =0, g=0, b =1, a = 0.5} ,{r =0, g= 0, b =1, a = 0}, 1)
end
end
end --DEBUG
return BattleManager return BattleManager
@@ -0,0 +1,20 @@
---@class MaxLoadConfig
---@field maxInternalLoad number
---@type table<string, MaxLoadConfig>
local MaxLoadConfig = {
["Mi-8MT"] = {
maxInternalLoad = 4000,
},
["CH-47Fbl1"] = {
maxInternalLoad = 10000
},
["Mi-24P"] = {
maxInternalLoad = 2000
},
["UH-1H"] = {
maxInternalLoad = 2000
}
}
return MaxLoadConfig
@@ -4,7 +4,6 @@ local Logger = require("classes.util.Logger")
local SpearheadEvents = require("classes.spearhead_events") local SpearheadEvents = require("classes.spearhead_events")
local SupplyUnitsTracker = require("classes.stageClasses.helpers.SupplyUnitsTracker") local SupplyUnitsTracker = require("classes.stageClasses.helpers.SupplyUnitsTracker")
local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHelper") local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHelper")
local StageConfig = require("classes.configuration.StageConfig")
---@class MissionCommandsHelper ---@class MissionCommandsHelper
@@ -17,7 +16,6 @@ local StageConfig = require("classes.configuration.StageConfig")
---@field private _stageBriefings table<string, string> @table of stage briefings by stage name ---@field private _stageBriefings table<string, string> @table of stage briefings by stage name
---@field private _logger Logger @logger instance for logging ---@field private _logger Logger @logger instance for logging
---@field private _supplyUnitsTracker SupplyUnitsTracker @supply units tracker instance ---@field private _supplyUnitsTracker SupplyUnitsTracker @supply units tracker instance
---@field private _stageConfig StageConfig
local MissionCommandsHelper = {} local MissionCommandsHelper = {}
MissionCommandsHelper.__index = MissionCommandsHelper MissionCommandsHelper.__index = MissionCommandsHelper
@@ -36,11 +34,12 @@ local id = 0
local instance = nil local instance = nil
---@return MissionCommandsHelper ---@return MissionCommandsHelper
function MissionCommandsHelper.getOrCreate() ---@param logLevel string @log level for the logger
function MissionCommandsHelper.getOrCreate(logLevel)
if instance == nil then if instance == nil then
instance = setmetatable({}, MissionCommandsHelper) instance = setmetatable({}, MissionCommandsHelper)
instance._logger = Logger.new("MissionCommandsHelper") instance._logger = Logger.new("MissionCommandsHelper", logLevel)
instance._logger:info("Creating MissionCommandsHelper instance") instance._logger:info("Creating MissionCommandsHelper instance")
@@ -51,9 +50,7 @@ function MissionCommandsHelper.getOrCreate()
instance.lastUpdate = 0 instance.lastUpdate = 0
instance._stageBriefings = {} instance._stageBriefings = {}
instance._stageConfig = StageConfig:getInstance() instance._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate(logLevel)
instance._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate()
instance._supplyUnitsTracker:AddOnSupplyUnitEventListener( instance._supplyUnitsTracker:AddOnSupplyUnitEventListener(
{ {
@@ -98,6 +95,7 @@ function MissionCommandsHelper.getOrCreate()
timer.scheduleFunction(instance.updateContinuous, instance, timer.getTime() + 5) timer.scheduleFunction(instance.updateContinuous, instance, timer.getTime() + 5)
SpearheadEvents.AddOnPlayerEnterUnitListener(instance) SpearheadEvents.AddOnPlayerEnterUnitListener(instance)
end end
return instance return instance
@@ -126,16 +124,12 @@ function MissionCommandsHelper:RemoveMissionToCommands(mission)
self.updateNeeded = true self.updateNeeded = true
end end
---@param unit Unit ---@param unit Unit
function MissionCommandsHelper:OnPlayerEntersUnit(unit) function MissionCommandsHelper:OnPlayerEntersUnit(unit)
if unit then if unit then
local group = unit:getGroup() local group = unit:getGroup()
if group then if group then self:updateCommandsForGroup(group:getID()) end
self:updateCommandsForGroup(group:getID())
if self._stageConfig.briefingOnSpawnEnabled == true then
self:OverviewToGroup(group:getID())
end
end
end end
end end
@@ -170,11 +164,14 @@ local pinMissionCommand = function(args)
end end
end end
---@param groupID integer ---@private
function MissionCommandsHelper:OverviewToGroup(groupID) function MissionCommandsHelper:AddOverviewCommand(groupID)
local MissionsOverviewToGroup = function (id)
local text = "Missions Overview\n\n" local text = "Missions Overview\n\n"
local group = DcsUtil.GetPlayerGroupByGroupID(groupID) local group = DcsUtil.GetPlayerGroupByGroupID(id)
---@type Vec2 ---@type Vec2
local groupPos = { x=0, y=0 } local groupPos = { x=0, y=0 }
if group then if group then
@@ -182,9 +179,11 @@ function MissionCommandsHelper:OverviewToGroup(groupID)
groupPos = { x= pos.x, y=pos.z } groupPos = { x= pos.x, y=pos.z }
end end
---comment
---@param mission Mission ---@param mission Mission
---@return string ---@return string
local function formatLine(mission) local function formatLine(mission)
local distanceText = "?" local distanceText = "?"
if group then if group then
local lead = group:getUnit(1) local lead = group:getUnit(1)
@@ -196,14 +195,16 @@ function MissionCommandsHelper:OverviewToGroup(groupID)
end end
end end
return string.format("[%s]\t%s \t%s \t%s %% \t%s nM\n", mission.code, mission.missionTypeDisplay, mission.name, return string.format("[%s]\t%s \t%s \t%s %% \t%s nM\n", mission.code, mission.missionTypeDisplay, mission.name, mission:PercentageComplete(), distanceText)
mission:PercentageComplete(), distanceText)
end end
for _, briefing in pairs(self._stageBriefings) do for _, briefing in pairs(self._stageBriefings) do
text = text .. briefing .. "\n\n" text = text .. briefing .. "\n\n"
end end
---Primary missions ---Primary missions
text = text .. "Primary Missions\n" text = text .. "Primary Missions\n"
@@ -230,6 +231,7 @@ function MissionCommandsHelper:OverviewToGroup(groupID)
---@type Array<Mission> ---@type Array<Mission>
local secondaryMissions = {} local secondaryMissions = {}
for code, enabled in pairs(self.enabledByCode) do for code, enabled in pairs(self.enabledByCode) do
if enabled == true then if enabled == true then
local mission = self.missionsByCode[code] local mission = self.missionsByCode[code]
if mission and mission:getState() == "ACTIVE" and mission.priority == "secondary" then if mission and mission:getState() == "ACTIVE" and mission.priority == "secondary" then
@@ -244,42 +246,29 @@ function MissionCommandsHelper:OverviewToGroup(groupID)
end end
trigger.action.outTextForGroup(groupID, text, 20, true) trigger.action.outTextForGroup(id, text, 20, true)
end end
---@private
function MissionCommandsHelper:AddOverviewCommand(groupID)
---@class OverviewToGroupCommandArgs
---@field self MissionCommandsHelper @the MissionCommandsHelper instance
---@field groupId integer @the group ID of the player requesting the overview
---@param args OverviewToGroupCommandArgs
local MissionOverViewToGroup = function(args)
args.self:OverviewToGroup(args.groupId)
end
---@type OverviewToGroupCommandArgs
local overviewToGroupCommandArgs = { self = self, groupId = groupID }
missionCommands.removeItemForGroup(groupID, { "Overview" } ) missionCommands.removeItemForGroup(groupID, { "Overview" } )
missionCommands.addCommandForGroup(groupID, "Overview", nil, MissionOverViewToGroup, overviewToGroupCommandArgs) missionCommands.addCommandForGroup(groupID, "Overview", nil, MissionsOverviewToGroup, groupID)
end end
---@private ---@private
---@param groupID number ---@param groupID number
function MissionCommandsHelper:AddPinnedMission(groupID) function MissionCommandsHelper:AddPinnedMission(groupID)
local pinndedMission = self.pinnedByGroup[tostring(groupID)] local pinndedMission = self.pinnedByGroup[tostring(groupID)]
missionCommands.removeItemForGroup(groupID, { "Pinned Mission" }) missionCommands.removeItemForGroup(groupID, { "Pinned Mission" })
if pinndedMission and self.enabledByCode[tostring(pinndedMission.code)] == true then if pinndedMission and self.enabledByCode[tostring(pinndedMission.code)] == true then
missionCommands.addCommandForGroup(groupID, "Pinned Mission", nil, missionBriefingRequested, missionCommands.addCommandForGroup(groupID, "Pinned Mission", nil, missionBriefingRequested, { groupId = groupID, mission = pinndedMission })
{ groupId = groupID, mission = pinndedMission })
end end
end end
---@param groupID number ---@param groupID number
function MissionCommandsHelper:updateCommandsForGroup(groupID) function MissionCommandsHelper:updateCommandsForGroup(groupID)
self._logger:debug("Updating commands for group: " .. tostring(groupID)) self._logger:debug("Updating commands for group: " .. tostring(groupID))
self:AddPinnedMission(groupID) self:AddPinnedMission(groupID)
@@ -305,6 +294,7 @@ function MissionCommandsHelper:updateCommandsForGroup(groupID)
self._logger:debug("Manual refresh of missions for group: " .. tostring(refresh_mission_id)) self._logger:debug("Manual refresh of missions for group: " .. tostring(refresh_mission_id))
self:updateCommandsForGroup(refresh_mission_id) self:updateCommandsForGroup(refresh_mission_id)
end, groupID) end, groupID)
end end
local folderNames = { local folderNames = {
@@ -327,6 +317,7 @@ function MissionCommandsHelper:PinMission(mission, groupID)
end end
function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID) function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID)
local perFolder = 9 local perFolder = 9
local group = DcsUtil.GetPlayerGroupByGroupID(groupID) local group = DcsUtil.GetPlayerGroupByGroupID(groupID)
@@ -404,7 +395,9 @@ end
---@param path Array<string> ---@param path Array<string>
---@param mission Mission ---@param mission Mission
function MissionCommandsHelper:addMissionCommands(groupId, path, mission) function MissionCommandsHelper:addMissionCommands(groupId, path, mission)
if path then if path then
local group = DcsUtil.GetPlayerGroupByGroupID(groupId) local group = DcsUtil.GetPlayerGroupByGroupID(groupId)
local distance = "[?]" local distance = "[?]"
if group then if group then
@@ -417,15 +410,13 @@ function MissionCommandsHelper:addMissionCommands(groupId, path, mission)
end end
end end
local missionFolderName = "[" .. local missionFolderName = "[" .. mission.code .. "]" .. distance .. mission.name .. "( " .. mission.missionTypeDisplay .. " )"
mission.code .. "]" .. distance .. mission.name .. "( " .. mission.missionTypeDisplay .. " )"
missionCommands.addSubMenuForGroup(groupId, missionFolderName, path) missionCommands.addSubMenuForGroup(groupId, missionFolderName, path)
table.insert(path, missionFolderName) table.insert(path, missionFolderName)
---@type MissionBriefingRequestedArgs ---@type MissionBriefingRequestedArgs
local missionBriefingRequestedArgs = { groupId = groupId, mission = mission } local missionBriefingRequestedArgs = { groupId = groupId, mission = mission }
missionCommands.addCommandForGroup(groupId, "Briefing", path, missionBriefingRequested, missionCommands.addCommandForGroup(groupId, "Briefing", path, missionBriefingRequested,missionBriefingRequestedArgs)
missionBriefingRequestedArgs)
---@type PinMissionCommandArgs ---@type PinMissionCommandArgs
local pinMissionCommandArgs = { self = self, groupId = groupId, mission = mission } local pinMissionCommandArgs = { self = self, groupId = groupId, mission = mission }
@@ -436,6 +427,7 @@ end
---@private ---@private
---@param groupID integer ---@param groupID integer
function MissionCommandsHelper:AddSupplyHubCommandsIfApplicable(groupID) function MissionCommandsHelper:AddSupplyHubCommandsIfApplicable(groupID)
if self._supplyUnitsTracker:IsGroupLeadInSupplyHub(groupID) ~= true then return end if self._supplyUnitsTracker:IsGroupLeadInSupplyHub(groupID) ~= true then return end
self._logger:debug("Adding supply hub commands for group: " .. tostring(groupID)) self._logger:debug("Adding supply hub commands for group: " .. tostring(groupID))
@@ -466,32 +458,28 @@ function MissionCommandsHelper:AddSupplyHubCommandsIfApplicable(groupID)
local path = { [1] = folderNames.supplyHub } local path = { [1] = folderNames.supplyHub }
---@type LoadCargoCommandParams ---@type LoadCargoCommandParams
local farpParams1000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "FARP_CRATE_1000", supplyUnitsTracker = local farpParams1000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "FARP_CRATE_1000", supplyUnitsTracker = self._supplyUnitsTracker, commandHelper = self }
self._supplyUnitsTracker, commandHelper = self }
missionCommands.addCommandForGroup(groupID, "Load FARP Crate (1000)", path, loadCargoCommand, farpParams1000) missionCommands.addCommandForGroup(groupID, "Load FARP Crate (1000)", path, loadCargoCommand, farpParams1000)
---@type LoadCargoCommandParams ---@type LoadCargoCommandParams
local farpParams2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "FARP_CRATE_2000", supplyUnitsTracker = local farpParams2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "FARP_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker, commandHelper = self }
self._supplyUnitsTracker, commandHelper = self }
missionCommands.addCommandForGroup(groupID, "Load FARP Crate (2000)", path, loadCargoCommand, farpParams2000) missionCommands.addCommandForGroup(groupID, "Load FARP Crate (2000)", path, loadCargoCommand, farpParams2000)
---@type LoadCargoCommandParams ---@type LoadCargoCommandParams
local samParms1000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "SAM_CRATE_2000", supplyUnitsTracker = local samParms1000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "SAM_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker, commandHelper = self }
self._supplyUnitsTracker, commandHelper = self }
missionCommands.addCommandForGroup(groupID, "Load SAM Crate (1000)", path, loadCargoCommand, samParms1000) missionCommands.addCommandForGroup(groupID, "Load SAM Crate (1000)", path, loadCargoCommand, samParms1000)
---@type LoadCargoCommandParams ---@type LoadCargoCommandParams
local samParms2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "SAM_CRATE_2000", supplyUnitsTracker = local samParms2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "SAM_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker, commandHelper = self }
self._supplyUnitsTracker, commandHelper = self }
missionCommands.addCommandForGroup(groupID, "Load SAM Crate (2000)", path, loadCargoCommand, samParms2000) missionCommands.addCommandForGroup(groupID, "Load SAM Crate (2000)", path, loadCargoCommand, samParms2000)
---@type LoadCargoCommandParams ---@type LoadCargoCommandParams
local airbaseParms2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "AIRBASE_CRATE_2000", supplyUnitsTracker = local airbaseParms2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "AIRBASE_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker, commandHelper = self }
self._supplyUnitsTracker, commandHelper = self }
missionCommands.addCommandForGroup(groupID, "Airbase Crate (2000)", path, loadCargoCommand, airbaseParms2000) missionCommands.addCommandForGroup(groupID, "Airbase Crate (2000)", path, loadCargoCommand, airbaseParms2000)
end end
function MissionCommandsHelper:AddCargoCommands(groupID) function MissionCommandsHelper:AddCargoCommands(groupID)
local group = DcsUtil.GetPlayerGroupByGroupID(groupID) local group = DcsUtil.GetPlayerGroupByGroupID(groupID)
if group == nil then return end if group == nil then return end
@@ -521,19 +509,20 @@ function MissionCommandsHelper:AddCargoCommands(groupID)
for i = 1, amount do for i = 1, amount do
local path = { [1] = folderNames.cargo } local path = { [1] = folderNames.cargo }
---@type UnloadCargoCommandParams ---@type UnloadCargoCommandParams
local params = { unitID = unit:getID(), crateType = cargoType, supplyUnitsTracker = self local params = { unitID = unit:getID(), crateType = cargoType, supplyUnitsTracker = self._supplyUnitsTracker, commandHelper = self }
._supplyUnitsTracker, commandHelper = self } missionCommands.addCommandForGroup(groupID, "Unload " .. cargoConfig.displayName, path, unloadCargoCommand, params)
missionCommands.addCommandForGroup(groupID, "Unload " .. cargoConfig.displayName, path,
unloadCargoCommand, params)
end end
end end
end end
end end
end end
---@private ---@private
---@param groupId integer ---@param groupId integer
function MissionCommandsHelper:addMissionFolders(groupId) function MissionCommandsHelper:addMissionFolders(groupId)
missionCommands.addSubMenuForGroup(groupId, folderNames.primary) missionCommands.addSubMenuForGroup(groupId, folderNames.primary)
missionCommands.addSubMenuForGroup(groupId, folderNames.secondary) missionCommands.addSubMenuForGroup(groupId, folderNames.secondary)
@@ -1,42 +0,0 @@
---@class DropZoneSlice
---@field centerAngle number Angle in degrees (0=forward, 90=right, 180=rear, 270=left)
---@field angleWidth number Total width of the slice in degrees (e.g. 60 = ±30°)
---@field minRadius number Minimum search radius in meters (safe distance from helicopter)
---@field maxRadius number Maximum search radius in meters
---@field spacing number Distance increment when searching outward in meters
---@class SupplyLoadConfig
---@field maxInternalLoad number
---@field dropZones Array<DropZoneSlice>
---@type table<string, SupplyLoadConfig>
local SupplyLoadConfig = {
["Mi-8MT"] = {
maxInternalLoad = 4000,
dropZones = {
{ centerAngle = 180, angleWidth = 60, minRadius = 20, maxRadius = 50, spacing = 5 },
}
},
["CH-47Fbl1"] = {
maxInternalLoad = 10000,
dropZones = {
{ centerAngle = 180, angleWidth = 30, minRadius = 20, maxRadius = 75, spacing = 5 },
}
},
["Mi-24P"] = {
maxInternalLoad = 2000,
dropZones = {
{ centerAngle = 270, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 },
{ centerAngle = 90, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 },
}
},
["UH-1H"] = {
maxInternalLoad = 2000,
dropZones = {
{ centerAngle = 270, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 },
{ centerAngle = 90, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 },
}
}
}
return SupplyLoadConfig
@@ -3,7 +3,7 @@ local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil") local DcsUtil = require("classes.util.DcsUtil")
local SpearheadEvents = require("classes.spearhead_events") local SpearheadEvents = require("classes.spearhead_events")
local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHelper") local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHelper")
local SupplyLoadConfig = require("classes.stageClasses.helpers.SupplyLoadConfig") local MaxLoadConfig = require("classes.stageClasses.helpers.MaxLoadConfig")
---@class SupplyUnitEventListener ---@class SupplyUnitEventListener
---@field supplyUnitSpawned fun(self:SupplyUnitEventListener, unit:Unit) | nil ---@field supplyUnitSpawned fun(self:SupplyUnitEventListener, unit:Unit) | nil
@@ -26,12 +26,13 @@ SupplyUnitsTracker.__index = SupplyUnitsTracker
local singleton = nil local singleton = nil
---comment ---comment
---@param logLevel LogLevel
---@return SupplyUnitsTracker ---@return SupplyUnitsTracker
function SupplyUnitsTracker.getOrCreate() function SupplyUnitsTracker.getOrCreate(logLevel)
if singleton == nil then if singleton == nil then
singleton = setmetatable({}, SupplyUnitsTracker) singleton = setmetatable({}, SupplyUnitsTracker)
singleton._logger = Logger.new("SupplyUnitsTracker") singleton._logger = Logger.new("SupplyUnitsTracker", logLevel)
singleton._unitPositions = {} singleton._unitPositions = {}
singleton._cargoInUnits = {} singleton._cargoInUnits = {}
singleton._supplyUnitsByName = {} singleton._supplyUnitsByName = {}
@@ -296,16 +297,15 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe
self._logger:debug("Unload requested for unit: " .. unitID .. " crateType: " .. crateType) self._logger:debug("Unload requested for unit: " .. unitID .. " crateType: " .. crateType)
local unit = DcsUtil.GetPlayerUnitByID(unitID) local unit = DcsUtil.GetPlayerUnitByID(unitID)
if unit == nil or unit:isExist() == false then if unit == nil or unit:isExist() == false then return end
self._logger:warn("Unload requested for non-existent unit: " .. unitID)
return
end
local group = unit:getGroup() local group = unit:getGroup()
if group == nil then if group == nil then
self._logger:warn("Unload requested for unit with no group: " .. unit:getName())
return return
end end
self:RemoveCargoFromUnit(unitID, crateType)
self:UpdateWeightForUnit(unit)
local cargoConfig = SupplyConfigHelper.getSupplyConfig(crateType) local cargoConfig = SupplyConfigHelper.getSupplyConfig(crateType)
if cargoConfig == nil then if cargoConfig == nil then
@@ -313,16 +313,7 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe
return return
end end
local cargoPos = self:GetCargoPlacePosition(unit, cargoConfig.staticType) local cargoPos = self:GetCargoPlacePosition(unit)
if cargoPos == nil then
self._logger:warn("No valid position found to drop cargo for unit: " .. unit:getName())
trigger.action.outTextForUnit(unit:getID(), "No valid position to drop cargo. Unloading area is too crowded.", 10)
return
end
self:RemoveCargoFromUnit(unitID, crateType)
self:UpdateWeightForUnit(unit)
cargoCount = cargoCount + 1 cargoCount = cargoCount + 1
local cargoSpawnObject = { local cargoSpawnObject = {
@@ -332,11 +323,9 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe
y = cargoPos.z, y = cargoPos.z,
} }
self._logger:debug("Spawning crate #" .. cargoCount .. " at (" .. string.format("%.2f", cargoPos.x) .. ", " .. string.format("%.2f", cargoPos.y) .. ", " .. string.format("%.2f", cargoPos.z) .. ")")
local spawned = coalition.addStaticObject(unit:getCoalition(), cargoSpawnObject) local spawned = coalition.addStaticObject(unit:getCoalition(), cargoSpawnObject)
self._droppedCrates[cargoSpawnObject.name] = spawned self._droppedCrates[cargoSpawnObject.name] = spawned
missionCommandsHelper:updateCommandsForGroup(group:getID()) missionCommandsHelper:updateCommandsForGroup(group:getID())
self._logger:debug("Cargo dropped for unit: " .. unit:getName() .. " crateType: " .. crateType)
end end
---@return table<string,StaticObject> ---@return table<string,StaticObject>
@@ -424,7 +413,7 @@ function SupplyUnitsTracker:TryLoadCrateInUnit(unit, crateType, commandHelper)
end end
end end
local unitConfig = SupplyLoadConfig[unit:getTypeName()] local unitConfig = MaxLoadConfig[unit:getTypeName()]
if unitConfig == nil then if unitConfig == nil then
trigger.action.outTextForUnit(unit:getID(), "Your unit type is not configured for logistics: " .. crateType, 5) trigger.action.outTextForUnit(unit:getID(), "Your unit type is not configured for logistics: " .. crateType, 5)
self._logger:error("Invalid unit type: " .. unit:getTypeName()) self._logger:error("Invalid unit type: " .. unit:getTypeName())
@@ -467,234 +456,53 @@ function SupplyUnitsTracker:UnitRequestCrateSpawn(groupID, crateType)
end end
end end
---@class SupplyUnitsBoundingBox
---@field min Vec3 World space minimum
---@field max Vec3 World space maximum
---@field heading number? Object heading in radians
---@param foundObject Object
---@return SupplyUnitsBoundingBox?
function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
local desc = foundObject:getDesc()
if foundObject:getCategory() == Object.Category.SCENERY then
desc = SceneryObject.getDescByName(foundObject:getTypeName())
end
if desc == nil or desc.box == nil then
return nil
end
local objPos = foundObject:getPoint()
local box = desc.box
local heading = 0
-- Try to get object heading from position vector's forward direction
-- This works for units and other objects that support getPosition
pcall(function()
local objPosition = foundObject:getPosition()
if objPosition and objPosition.x then
heading = math.atan2(objPosition.x.z, objPosition.x.x)
end
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
-- If object has significant rotation, apply rotation to bbox corners
if math.abs(heading) > 0.1 then
-- Get all 4 corners of bbox in local space
local corners = {
{minX, minZ},
{minX, maxZ},
{maxX, minZ},
{maxX, maxZ}
}
-- Rotate corners and find new min/max
minX, maxX = math.huge, -math.huge
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)
minX = math.min(minX, rotX)
maxX = math.max(maxX, rotX)
minZ = math.min(minZ, rotZ)
maxZ = math.max(maxZ, rotZ)
end
end
-- Convert relative bbox to world space by adding object position
---@type SupplyUnitsBoundingBox
return {
min = {
x = objPos.x + minX,
y = objPos.y + box.min.y,
z = objPos.z + minZ
},
max = {
x = objPos.x + maxX,
y = objPos.y + box.max.y,
z = objPos.z + maxZ
},
heading = heading
}
end
---Check if two axis-aligned bounding boxes collide with safety margin
---@param crateBBox SupplyUnitsBoundingBox The crate's bbox in world space
---@param objBBox SupplyUnitsBoundingBox The existing object's bbox in world space
---@param safetyMargin number Safety margin around objects
---@return boolean True if collision detected
function SupplyUnitsTracker:CheckBBoxCollision(crateBBox, objBBox, safetyMargin)
-- Apply safety margin to object bbox
local objMin = {
x = objBBox.min.x - safetyMargin,
y = objBBox.min.y - safetyMargin,
z = objBBox.min.z - safetyMargin
}
local objMax = {
x = objBBox.max.x + safetyMargin,
y = objBBox.max.y + safetyMargin,
z = objBBox.max.z + safetyMargin
}
-- AABB collision detection
return crateBBox.min.x <= objMax.x and crateBBox.max.x >= objMin.x and
crateBBox.min.y <= objMax.y and crateBBox.max.y >= objMin.y and
crateBBox.min.z <= objMax.z and crateBBox.max.z >= objMin.z
end
---@private ---@private
---@param unit Unit ---@param unit Unit
---@param crateTypeName string ---@return Vec3
---@return Vec3? function SupplyUnitsTracker:GetCargoPlacePosition(unit)
function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
local unitPos = unit:getPosition() local pos = unit:getPosition()
local preferredPos = {
-- Get unit's heading from the forward vector (x component) x = pos.p.x - 10 * pos.x.x,
-- Heading is calculated as: atan2(forward.z, forward.x) y = pos.p.y - 10 * pos.x.y,
local unitHeading = math.atan2(unitPos.x.z, unitPos.x.x) z = pos.p.z - 10 * pos.x.z
-- Get crate bbox - relative to placement position
local crateDesc = StaticObject.getDescByName(crateTypeName) --[[@as table]]
if crateDesc == nil or crateDesc.box == nil then
self._logger:error("Could not get bbox for crate type: " .. crateTypeName)
return nil
end
local crateRelativeBBox = crateDesc.box
-- Get drop zone config for this unit
local dropZones = {
{ centerAngle = 180, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 }
} }
if SupplyLoadConfig[unit:getTypeName()] ~= nil then return preferredPos
dropZones = SupplyLoadConfig[unit:getTypeName()].dropZones
end
-- Get occupied objects with their bboxes
local searchVolume = {
id = world.VolumeType.SPHERE,
params = {
point = {
x = unitPos.p.x,
y = unitPos.p.y,
z = unitPos.p.z
},
radius = 100 -- Search a large area
}
}
local occupiedObjects = {} -- local volume = {
local found = function(foundItem, val) -- id = world.VolumeType.SPHERE,
local bbox = self:GetBoundingBoxes(foundItem) -- params = {
if bbox then -- point = preferredPos,
self._logger:debug("Found object: " .. foundItem:getTypeName() .. " at (" .. foundItem:getPoint().x .. ", " .. foundItem:getPoint().z .. ")") -- radius = 10
table.insert(occupiedObjects, { -- }
pos = foundItem:getPoint(), -- }
bbox = bbox
})
else
self._logger:debug("Found object without bbox: " .. foundItem:getTypeName())
end
end
local searchCategories = {} -- local occupiedPosX = {}
for key, value in pairs(Object.Category) do -- local occupiedPosZ = {}
self._logger:debug("Adding category to search: " .. tostring(value) .. " (" .. tostring(key) .. ")")
table.insert(searchCategories, value)
end
---@diagnostic disable-next-line: param-type-mismatch -- ---@param foundItem Object
world.searchObjects(searchCategories, searchVolume, found) -- local found = function(foundItem, val)
local safetyMargin = 3 -- Safety margin around objects -- local foundPos = foundItem:getPoint()
-- Search through each slice -- local z = math.floor(foundPos.z)
for _, zone in ipairs(dropZones) do -- for i = z - 3 , z + 3 do
-- Calculate angle range for this slice -- occupiedPosZ[i] = true
local minAngle = zone.centerAngle - (zone.angleWidth / 2) -- end
local maxAngle = zone.centerAngle + (zone.angleWidth / 2)
-- Search outward in rings starting from minRadius -- local x = math.floor(foundPos.x)
for distance = zone.minRadius, zone.maxRadius, zone.spacing do -- for i = x - 3 , x + 3 do
-- Check multiple positions within the angular slice -- occupiedPosX[i] = true
local angleStep = math.min(15, zone.angleWidth / 3) -- Divide slice into sections -- end
-- end
for angle = minAngle, maxAngle, angleStep do -- world.searchObjects(volume.id, volume.params, found)
local radians = math.rad(angle)
-- Calculate position at this angle and distance, relative to unit's heading
-- Angle 0 = forward, 90 = right, 180 = rear, 270 = left
-- Apply unit heading to make angles relative to unit orientation
local worldAngle = radians + unitHeading
local candidateX = unitPos.p.x + distance * math.sin(worldAngle)
local candidateZ = unitPos.p.z + distance * math.cos(worldAngle)
local candidateY = land.getHeight({ x = candidateX, y = candidateZ })
-- Convert crate's relative bbox to world space at this position
local crateBBoxWorldSpace = {
min = {
x = candidateX + crateRelativeBBox.min.x,
y = candidateY + crateRelativeBBox.min.y,
z = candidateZ + crateRelativeBBox.min.z
},
max = {
x = candidateX + crateRelativeBBox.max.x,
y = candidateY + crateRelativeBBox.max.y,
z = candidateZ + crateRelativeBBox.max.z
}
}
-- Check if crate bbox collides with any existing objects
local collides = false
for _, obj in ipairs(occupiedObjects) do
if self:CheckBBoxCollision(crateBBoxWorldSpace, obj.bbox, safetyMargin) then
collides = true
self._logger:debug("Collision at angle=" .. angle .. ", distance=" .. distance)
break
end
end
if not collides then
self._logger:debug("Valid position found at angle=" .. angle .. ", distance=" .. distance .. ", pos=(" .. string.format("%.2f", candidateX) .. ", " .. string.format("%.2f", candidateY) .. ", " .. string.format("%.2f", candidateZ) .. ")")
return { x = candidateX, y = candidateY, z = candidateZ }
end
end
end
end
-- No free spot found
return nil
end end
return SupplyUnitsTracker return SupplyUnitsTracker
@@ -5,8 +5,6 @@ local SupplyUnitsTracker = require("classes.stageClasses.helpers.SupplyUnitsTrac
local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper") local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper")
local GlobalConfig = require("classes.configuration.GlobalConfig") local GlobalConfig = require("classes.configuration.GlobalConfig")
local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHelper") local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHelper")
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
---@class BuildableMission : Mission, SupplyUnitEventListener ---@class BuildableMission : Mission, SupplyUnitEventListener
---@field private _requiredKilos number ---@field private _requiredKilos number
@@ -19,20 +17,11 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
---@field private _supplyUnitsTracker SupplyUnitsTracker ---@field private _supplyUnitsTracker SupplyUnitsTracker
---@field private _noLandingZone SpearheadTriggerZone? ---@field private _noLandingZone SpearheadTriggerZone?
---@field private _dropOffZone SpearheadTriggerZone? ---@field private _dropOffZone SpearheadTriggerZone?
---@field private _noLandingZoneDrawing CustomDrawing? ---@field private _noLandingZoneId number
---@field private _dropOffZoneDrawing CustomDrawing? ---@field private _dropOffZoneId number
---@field private _briefing string?
local BuildableMission = {} local BuildableMission = {}
BuildableMission.__index = BuildableMission BuildableMission.__index = BuildableMission
local function getDefaultBriefing(siteType, coords)
return "We've dispatched forward units to find a proper spot for a new " .. siteType .. "." ..
"\nYou will need to drop off supplies so they can start building." ..
"\nThe coords are: " .. coords ..
"\n\n"
end
---@class OnCrateDroppedListener ---@class OnCrateDroppedListener
---@field OnCrateDroppedOff fun(self:OnCrateDroppedListener, mission:BuildableMission, kilos:number) ---@field OnCrateDroppedOff fun(self:OnCrateDroppedListener, mission:BuildableMission, kilos:number)
@@ -42,8 +31,7 @@ end
---@param requiredCrateType SupplyType ---@param requiredCrateType SupplyType
---@param noLandingZone SpearheadTriggerZone? ---@param noLandingZone SpearheadTriggerZone?
---@param logger Logger ---@param logger Logger
---@param briefing string? function BuildableMission.new(database, logger, targetZone, noLandingZone, requiredKilos, requiredCrateType)
function BuildableMission.new(database, logger, targetZone, noLandingZone, requiredKilos, requiredCrateType, briefing)
setmetatable(BuildableMission, Mission) setmetatable(BuildableMission, Mission)
@@ -53,7 +41,6 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
self._database = database self._database = database
self._requiredKilos = requiredKilos self._requiredKilos = requiredKilos
self._droppedKilos = 0 self._droppedKilos = 0
self._briefing = briefing
self._noLandingZone = noLandingZone self._noLandingZone = noLandingZone
@@ -75,13 +62,7 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
end end
self.code = tostring(database:GetNewMissionCode()) self.code = tostring(database:GetNewMissionCode())
local splitTargetZoneName = Util.split_string(targetZone.name, "_")
if splitTargetZoneName and splitTargetZoneName[3] and splitTargetZoneName[3] ~= "" then
self.name = splitTargetZoneName[3]
else
self.name = "Resupply" self.name = "Resupply"
end
local type = "site" local type = "site"
if requiredCrateType == "SAM_CRATE" then if requiredCrateType == "SAM_CRATE" then
@@ -95,9 +76,10 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
self._onCrateDroppedOfListeners = {} self._onCrateDroppedOfListeners = {}
self._completeListeners = {} self._completeListeners = {}
self._markIDsPerGroup = {} self._markIDsPerGroup = {}
self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate() self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate(logger.LogLevel)
self._state = "NEW" self._state = "NEW"
self.location = targetZone.location self.location = targetZone.location
self.missionType = "LOGISTICS" self.missionType = "LOGISTICS"
@@ -105,7 +87,7 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
self.priority = "secondary" self.priority = "secondary"
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() self._missionCommandsHelper = MissionCommandsHelper.getOrCreate(self._logger.LogLevel)
self._crateType = requiredCrateType self._crateType = requiredCrateType
@@ -132,16 +114,11 @@ function BuildableMission:ShowBriefing(groupID)
siteType = "airbase" siteType = "airbase"
end 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 .. local briefing = "Mission [" .. self.code .. "] " .. self.name ..
"\n \n" .. "\n \n" ..
briefingPart .. "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" .. "\n\n" ..
"\nKilos still required: " .. self._requiredKilos - self._droppedKilos .. "\nKilos still required: " .. self._requiredKilos - self._droppedKilos ..
"\n\n" .. "\n\n" ..
@@ -187,20 +164,20 @@ function BuildableMission:SpawnActive()
return return
end end
local lineColor = DrawingHelper.ColorTableToColorString({ 230/255, 93/255, 49/255, 1}) ---@type DrawColor
local fillColor = DrawingHelper.ColorTableToColorString({ 230/255, 93/255, 49/255, 0.2}) local lineColor = { r=230/255, g=93/255, b=49/255, a=1}
self._noLandingZoneDrawing = CustomDrawing.FromZone(self._noLandingZone, lineColor, fillColor, 2, 6) ---@type DrawColor
self._noLandingZoneDrawing:Draw() local fillColor = { r=230/255, g=93/255, b=49/255, a=0.2}
self._noLandingZoneId = DcsUtil.DrawZone(self._noLandingZone, lineColor, fillColor, 6)
if self._dropOffZone == nil then if self._dropOffZone == nil then
self._logger:error("No drop off zone found for mission: " .. self.code) self._logger:error("No drop off zone found for mission: " .. self.code)
return return
end end
local lineColor2 = DrawingHelper.ColorTableToColorString({ 0, 0, 1, 1}) local lineColor2 = { r=0, g=0, b=1, a=1}
local fillColor2 = DrawingHelper.ColorTableToColorString({ 0, 0, 1, 0}) local fillColor2 = { r=0, g=0, b=1, a=0}
self._dropOffZoneDrawing = CustomDrawing.FromZone(self._dropOffZone, lineColor2, fillColor2, 2, 6) self._dropOffZoneId = DcsUtil.DrawZone(self._dropOffZone, lineColor2, fillColor2, 6)
self._dropOffZoneDrawing:Draw()
---@param selfA BuildableMission ---@param selfA BuildableMission
---@param time number ---@param time number
@@ -277,8 +254,8 @@ function BuildableMission:CheckCratesInZone()
end end
if self._droppedKilos >= self._requiredKilos then if self._droppedKilos >= self._requiredKilos then
self._dropOffZoneDrawing:Remove() DcsUtil.RemoveMark(self._noLandingZoneId)
self._noLandingZoneDrawing:Remove() DcsUtil.RemoveMark(self._dropOffZoneId)
self:NotifyMissionComplete() self:NotifyMissionComplete()
self._state = "COMPLETED" self._state = "COMPLETED"
end end
@@ -1,7 +1,6 @@
local Mission = require("classes.stageClasses.missions.baseMissions.Mission") local Mission = require("classes.stageClasses.missions.baseMissions.Mission")
local Util = require("classes.util.Util") local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil") local DcsUtil = require("classes.util.DcsUtil")
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
---@class RunwayStrikeMission : Mission ---@class RunwayStrikeMission : Mission
---@field runwayBombingTracker RunwayBombingTracker ---@field runwayBombingTracker RunwayBombingTracker
@@ -18,7 +17,7 @@ local RunwayStrikeMission = {}
---@field corners Array<Vec2> ---@field corners Array<Vec2>
---@field kilosHit number ---@field kilosHit number
---@field repairGroups Array<string> ---@field repairGroups Array<string>
---@field drawID Array<number> ---@field drawID number?
---@param runway Runway ---@param runway Runway
@@ -155,27 +154,12 @@ function RunwayStrikeMission:Draw()
if runwaySection.drawID == nil then if runwaySection.drawID == nil then
local zone = self:SectionToSpearheadZone(runwaySection) local zone = self:SectionToSpearheadZone(runwaySection)
---@type Free local color = { r=0, g=1, b=0, a=0.5 }
local drawObject = {
primitiveType = "Polygon",
polygonMode = "free",
mapX = 0,
mapY = 0,
points = zone.verts,
name = zone.name,
fillColorString = DrawingHelper.ColorTableToColorString(fillColor),
colorString = DrawingHelper.ColorTableToColorString(lineColor),
style = "solid",
thickness = 1,
visible = true,
}
runwaySection.drawID = DrawingHelper.Draw(drawObject) runwaySection.drawID = DcsUtil.DrawZone(zone, color, color, 5)
else else
for _, drawID in pairs(runwaySection.drawID) do DcsUtil.SetFillColor(runwaySection.drawID, fillColor)
DcsUtil.SetFillColor(drawID, fillColor) DcsUtil.SetLineColor(runwaySection.drawID, lineColor)
DcsUtil.SetLineColor(drawID, lineColor)
end
end end
end end
@@ -499,7 +483,6 @@ function RunwayStrikeMission:ToSections(runway, numSections)
corners = corners, corners = corners,
kilosHit = 0, kilosHit = 0,
repairGroups = {}, repairGroups = {},
drawID = {},
} }
table.insert(sections, section) table.insert(sections, section)
@@ -350,7 +350,7 @@ function ZoneMission:SpawnPersistedState()
end end
end end
---spawns the mission, but doesn't add it to the mission commands. ---spawns the mission, but doesn't add
function ZoneMission:SpawnInactive() function ZoneMission:SpawnInactive()
self._logger:info("PreActivating " .. self.name) self._logger:info("PreActivating " .. self.name)
@@ -52,7 +52,7 @@ function Mission.newSuper(self, zoneName, missionName, missionType, missionBrief
self.location = database:GetLocationForMissionZone(zoneName) self.location = database:GetLocationForMissionZone(zoneName)
self.missionTypeDisplay = self.missionType self.missionTypeDisplay = self.missionType
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() self._missionCommandsHelper = MissionCommandsHelper.getOrCreate(logger.LogLevel)
return true, "success" return true, "success"
end end
@@ -126,11 +126,6 @@ function Mission:NotifyMissionComplete()
end end
function Mission:ForceMissionComplete()
self._state = "COMPLETED"
self:NotifyMissionComplete()
end
function Mission:MarkMissionAreaToGroup(groupId) end function Mission:MarkMissionAreaToGroup(groupId) end
---endregion ---endregion
+68 -9
View File
@@ -173,6 +173,10 @@ do -- INIT DCS_UTIL
verts = enlargedPoints verts = enlargedPoints
} }
if SpearheadConfig and SpearheadConfig.debugEnabled == true then
DCS_UTIL.DrawZone(triggerZone, { r = 0, g = 1, b = 0, a = 1 }, { a = 0, r = 0, g = 1, b = 0 }, 1)
end
DCS_UTIL.__airbaseZonesByName[name] = triggerZone DCS_UTIL.__airbaseZonesByName[name] = triggerZone
end end
end end
@@ -632,10 +636,69 @@ do -- INIT DCS_UTIL
---@field b number ---@field b number
---@field a number ---@field a number
local drawID = 4210 local drawID = 400
---@param zone SpearheadTriggerZone
function DCS_UTIL.GetNextDrawID() ---@param lineColor DrawColor
---@param fillColor DrawColor
---@param lineStyle LineType
---@return number drawID
function DCS_UTIL.DrawZone(zone, lineColor, fillColor, lineStyle)
if lineStyle == nil then lineStyle = 4 end
drawID = drawID + 1 drawID = drawID + 1
if zone.zone_type == "Cilinder" then
trigger.action.circleToAll(-1, drawID, { x = zone.location.x, y = 0, z = zone.location.y }, zone.radius,
{ 0, 0, 0, 0 }, { 0, 0, 0, 0 }, lineStyle, true)
else
local functionString = "trigger.action.markupToAll(7, -1, " .. drawID .. ","
for _, vecpoint in pairs(zone.verts) do
functionString = functionString .. " { x=" .. vecpoint.x .. ", y=0,z=" .. vecpoint.y .. "},"
end
functionString = functionString .. "{0,1,0,1}, {0,1,0,1}, " .. lineStyle .. ")"
---@diagnostic disable-next-line: deprecated
local f, err = loadstring(functionString)
if f then
f()
else
env.error("Something failed when drawing complex drawing" .. err)
end
end
local fillColorMapped = {
fillColor.r or 0,
fillColor.g or 0,
fillColor.b or 0,
fillColor.a or 0.5
}
local lineColorMapped = {
lineColor.r or 0,
lineColor.g or 0,
lineColor.b or 0,
lineColor.a or 1
}
trigger.action.setMarkupColorFill(drawID, fillColorMapped)
trigger.action.setMarkupColor(drawID, lineColorMapped)
return drawID
end
---@param start Vec3
---@param finish Vec3
---@param lineColor DrawColor
---@param lineStyle LineType
function DCS_UTIL.DrawLine(start, finish, lineColor, lineStyle)
if lineStyle == nil then lineStyle = 4 end
drawID = drawID + 1
local lineColorMapped = {
lineColor.r or 0,
lineColor.g or 0,
lineColor.b or 0,
lineColor.a or 1
}
trigger.action.lineToAll(-1, drawID, start, finish, lineColorMapped, lineStyle)
return drawID return drawID
end end
@@ -644,7 +707,7 @@ do -- INIT DCS_UTIL
---@param location Vec3 ---@param location Vec3
---@return number markID ---@return number markID
function DCS_UTIL.AddMarkToGroup(groupID, text, location) function DCS_UTIL.AddMarkToGroup(groupID, text, location)
local drawID = DCS_UTIL.GetNextDrawID() drawID = drawID + 1
trigger.action.markToGroup(drawID, text, location, groupID, true, nil) trigger.action.markToGroup(drawID, text, location, groupID, true, nil)
return drawID return drawID
end end
@@ -654,7 +717,7 @@ do -- INIT DCS_UTIL
---@param location Vec3 ---@param location Vec3
---@return integer ---@return integer
function DCS_UTIL.AddMarkToAll(text, location) function DCS_UTIL.AddMarkToAll(text, location)
local drawID = DCS_UTIL.GetNextDrawID() drawID = drawID + 1
trigger.action.markToAll(drawID, text, location, true, nil) trigger.action.markToAll(drawID, text, location, true, nil)
return drawID return drawID
end end
@@ -683,10 +746,6 @@ do -- INIT DCS_UTIL
---@param drawID number ---@param drawID number
---@param fillColor DrawColor ---@param fillColor DrawColor
function DCS_UTIL.SetFillColor(drawID, fillColor) function DCS_UTIL.SetFillColor(drawID, fillColor)
if fillColor == nil then
return
end
local lineColorMapped = { local lineColorMapped = {
fillColor.r or 0, fillColor.r or 0,
fillColor.g or 0, fillColor.g or 0,
+2 -14
View File
@@ -1,17 +1,5 @@
local Util = require("classes.util.Util") local Util = require("classes.util.Util")
local SpearheadConfig = require("classes.configuration.GlobalConfig")
---@type LogLevel
local defaultLogLevel = "INFO"
if SpearheadConfig then
if SpearheadConfig:isDebugEnabled() then
defaultLogLevel = "DEBUG"
end
end
--- @class Logger --- @class Logger
--- @field LoggerName string the name of the logger --- @field LoggerName string the name of the logger
@@ -22,13 +10,13 @@ do
---comment ---comment
---@param logger_name any ---@param logger_name any
---@param logLevel LogLevel? override the default log level ---@param logLevel LogLevel
---@return Logger ---@return Logger
function LOGGER.new(logger_name, logLevel) function LOGGER.new(logger_name, logLevel)
LOGGER.__index = LOGGER LOGGER.__index = LOGGER
local self = setmetatable({}, LOGGER) local self = setmetatable({}, LOGGER)
self.LoggerName = logger_name or "(loggername not set)" self.LoggerName = logger_name or "(loggername not set)"
self.LogLevel = logLevel or defaultLogLevel self.LogLevel = logLevel or "INFO"
return self return self
end end
-51
View File
@@ -128,14 +128,6 @@ do -- INIT UTIL
return str:find('^' .. findable) ~= nil return str:find('^' .. findable) ~= nil
end 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 ---comment
---@param str string ---@param str string
---@param findable string ---@param findable string
@@ -305,49 +297,6 @@ do -- INIT UTIL
return false return false
end end
---@param points Array<Vec3>
---@return Array<Vec3>
function UTIL.getConvexHull3d(points)
if #points == 0 then
return {}
end
---comment
---@param a Vec3
---@param b Vec3
---@param c Vec3
---@return boolean
local function ccw(a, b, c)
return (b.z - a.z) * (c.x - a.x) > (b.x - a.x) * (c.z - a.z)
end
table.sort(points, function(left, right)
return left.z < right.z
end)
local hull = {}
-- lower hull
for _, point in pairs(points) do
while #hull >= 2 and not ccw(hull[#hull - 1], hull[#hull], point) do
table.remove(hull, #hull)
end
table.insert(hull, point)
end
-- upper hull
local t = #hull + 1
for i = #points, 1, -1 do
local point = points[i]
while #hull >= t and not ccw(hull[#hull - 1], hull[#hull], point) do
table.remove(hull, #hull)
end
table.insert(hull, point)
end
table.remove(hull, #hull)
return hull
end
---comment ---comment
---@param points Array<Vec2> points ---@param points Array<Vec2> points
---@return Array<Vec2> hullPoints ---@return Array<Vec2> hullPoints
+41 -20
View File
@@ -5,11 +5,9 @@ local DcsUtil = require("classes.util.DcsUtil")
local Database = require("classes.spearhead_db") local Database = require("classes.spearhead_db")
local SpearheadEvents = require("classes.spearhead_events") local SpearheadEvents = require("classes.spearhead_events")
local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper") local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper")
local GlobalConfig = require("classes.configuration.GlobalConfig")
local CapConfig = require("classes.configuration.CapConfig") local CapConfig = require("classes.configuration.CapConfig")
local StageConfig = require("classes.configuration.StageConfig") local StageConfig = require("classes.configuration.StageConfig")
local Persistence = require("classes.persistence.Persistence") local Persistence = require("classes.persistence.Persistence")
local PersistenceConfig = require("classes.configuration.PersistenceConfig")
local SpawnManager = require("classes.helpers.SpawnManager") local SpawnManager = require("classes.helpers.SpawnManager")
local DetectionManager = require("classes.capClasses.detection.DetectionManager") local DetectionManager = require("classes.capClasses.detection.DetectionManager")
local GlobalCapManager = require("classes.capClasses.GlobalCapManager") local GlobalCapManager = require("classes.capClasses.GlobalCapManager")
@@ -30,16 +28,24 @@ SpearheadEvents.Init(defaultLogLevel)
local dbLogger = Logger.new("database", defaultLogLevel) local dbLogger = Logger.new("database", defaultLogLevel)
local standardLogger = Logger.new("", defaultLogLevel) local standardLogger = Logger.new("", defaultLogLevel)
local databaseManager = Database.New(dbLogger) local databaseManager = Database.New(dbLogger)
MissionCommandsHelper.getOrCreate() -- initiate MissionCommandsHelper.getOrCreate(defaultLogLevel) -- initiate
local capConfig = CapConfig:new(); local capConfig = CapConfig:new();
local stageConfig = StageConfig:getInstance(); local stageConfig = StageConfig:new();
local persistenceConfig = PersistenceConfig.new() local startingStage = stageConfig.startingStage or 1
if SpearheadConfig and SpearheadConfig.Persistence and SpearheadConfig.Persistence.enabled == true then
if persistenceConfig and persistenceConfig:isEnabled() == true then standardLogger:info("Persistence enabled")
local persistenceLogger = Logger.new("Persistence", defaultLogLevel) local persistenceLogger = Logger.new("Persistence", defaultLogLevel)
Persistence.Init(persistenceLogger) 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 end
local spawnLogger = Logger.new("SpawnManager", defaultLogLevel) local spawnLogger = Logger.new("SpawnManager", defaultLogLevel)
@@ -48,28 +54,43 @@ local detectionLogger = Logger.new("DetectionManager", defaultLogLevel)
local detectionManager = DetectionManager.New(detectionLogger) local detectionManager = DetectionManager.New(detectionLogger)
GlobalCapManager.start(databaseManager, capConfig, detectionManager, stageConfig, defaultLogLevel, spawnManager) GlobalCapManager.start(databaseManager, capConfig, detectionManager, stageConfig, defaultLogLevel, spawnManager)
local globalStageManager = GlobalStageManager.new(databaseManager, stageConfig, defaultLogLevel, spawnManager) GlobalStageManager.NewAndStart(databaseManager, stageConfig, defaultLogLevel, spawnManager)
GlobalFleetManager.start(databaseManager) 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") env.info(startTime .. "ms / " .. timer.getTime() * 1000 .. "ms")
local duration = (timer.getTime() * 1000) - startTime local duration = (timer.getTime() * 1000) - startTime
standardLogger:info("Spearhead Initialisation duration: " .. tostring(duration) .. "ms") standardLogger:info("Spearhead Initialisation duration: " .. tostring(duration) .. "ms")
local missionEditorWarningsLogger = Logger.new("MissionEditorWarnings", defaultLogLevel) local missionEditorWarningsLogger = Logger.new("MissionEditorWarnings", defaultLogLevel)
MissionEditorWarnings.WriteAll(missionEditorWarningsLogger) MissionEditorWarnings.WriteAll(missionEditorWarningsLogger)
globalStageManager:PrintMermaidStage() GlobalStageManager:printFullOverview()
local startDelayed = function() --Check lines of code in directory per file:
globalStageManager:Start() -- Get-ChildItem . -Include *.lua -Recurse | foreach {""+(Get-Content $_).Count + " => " + $_.name }; GCI . -Include *.lua* -Recurse | foreach{(GC $_).Count} | measure-object -sum | % Sum
return nil -- find . -name '*.lua' | xargs wc -l
end
timer.scheduleFunction(startDelayed, nil, timer.getTime() + 5) -- delay start by 5 seconds so every deleted item it first removed entirely --- ==================== DEBUG ORDER OR ZONE VEC ===========================
-- local zone = Spearhead.DcsUtil.getZoneByName("MISSIONSTAGE_99")
local globalConfig = GlobalConfig.New() -- local count = Spearhead.Util.tableLength(zone.verts)
if globalConfig and globalConfig:isDebugMenuEnabled() == true then
local DebugMenu = require("classes.debug.DebugMenu")
local debugMenu = DebugMenu.new()
debugMenu:RegisterMenus()
end
-- for i = 1, count - 1 do
-- local a = zone.verts[i]
-- local b = zone.verts[i+1]
-- local color = {0,0,0,1}
-- color[i] = 1
-- trigger.action.textToAll(-1, 46+i , { x= a.x, y = 0, z = a.z } , color, {0,0,0}, 24 , true , "" .. i )
-- trigger.action.lineToAll(-1 , 56+i , { x= a.x, y = 0, z = a.z } , { x = b.x, y = 0, z = b.z } , color , 1, true)
-- end
+1 -1
View File
@@ -1 +1 @@
0.13.0 0.12.1