Feature/scenery targets (#60)
* Added Scenery targets * Documentation updates
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 152 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 193 KiB |
+5
-3
@@ -6,9 +6,11 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Spearhead</title>
|
||||
|
||||
<link rel="stylesheet" href="style/style.css">
|
||||
|
||||
<script src="js/site.js"></script>
|
||||
<link rel="stylesheet" href="/style/prism.css">
|
||||
<link rel="stylesheet" href="/style/style.css">
|
||||
|
||||
<script src="/js/prism.js"></script>
|
||||
<script src="/js/site.js"></script>
|
||||
<script type="module" src="js/components.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
import * as header from "./components/header.js";
|
||||
import * as sidebar from "./components/sidebar.js";
|
||||
import './components/code-block.js';
|
||||
import './components/code-inline.js';
|
||||
import './components/note.js';
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
class CodeBlock extends HTMLElement {
|
||||
connectedCallback() {
|
||||
// Get the code content as text, preserving whitespace
|
||||
let code = this.textContent.replace(/\r\n?/g, "\n");
|
||||
// Remove leading/trailing blank lines
|
||||
code = code.replace(/^\s*\n/, '').replace(/\n\s*$/, '');
|
||||
// Find leading spaces from the first non-empty line
|
||||
const lines = code.split("\n");
|
||||
const firstLine = lines.find(line => line.trim().length > 0) || '';
|
||||
const leadingSpaces = firstLine.match(/^\s*/)[0].length;
|
||||
// Remove that many leading spaces from all lines
|
||||
const stripped = lines.map(line => line.slice(leadingSpaces)).join("\n");
|
||||
// Escape HTML special characters
|
||||
const escaped = stripped
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
const lang = this.getAttribute('lang') || '';
|
||||
const langClass = lang ? `language-${lang}` : '';
|
||||
this.innerHTML = `<pre class=\"custom-code-block\"><code class=\"${langClass}\">${escaped}</code></pre>`;
|
||||
// If Prism or highlight.js is present, trigger highlighting
|
||||
if (window.Prism && Prism.highlightElement) {
|
||||
Prism.highlightElement(this.querySelector('code'));
|
||||
} else if (window.hljs && hljs.highlightElement) {
|
||||
hljs.highlightElement(this.querySelector('code'));
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('code-block', CodeBlock);
|
||||
@@ -0,0 +1,32 @@
|
||||
class CodeInline extends HTMLElement {
|
||||
connectedCallback() {
|
||||
// Get the code content as text
|
||||
let code = this.textContent;
|
||||
|
||||
// Escape HTML special characters
|
||||
code = code
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
|
||||
// Highlight variables: $variable, {variable}, or %variable%
|
||||
code = code.replace(/(\$[a-zA-Z_][\w]*)|(\{[^\}]+\})|(%[^%]+%)/g, match =>
|
||||
`<span class="inline-var">${match}</span>`
|
||||
);
|
||||
|
||||
// Get language attribute for potential syntax highlighting
|
||||
const lang = this.getAttribute('lang') || '';
|
||||
const langClass = lang ? `language-${lang}` : '';
|
||||
|
||||
this.innerHTML = `<code class="custom-inline-code ${langClass}">${code}</code>`;
|
||||
|
||||
// If Prism or highlight.js is present, trigger highlighting
|
||||
if (window.Prism && Prism.highlightElement) {
|
||||
Prism.highlightElement(this.querySelector('code'));
|
||||
} else if (window.hljs && hljs.highlightElement) {
|
||||
hljs.highlightElement(this.querySelector('code'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('code-inline', CodeInline);
|
||||
@@ -9,6 +9,7 @@ class Header extends HTMLElement {
|
||||
this.innerHTML = `
|
||||
<div class="header-box">
|
||||
<a class="logo" href="/index.html">Spearhead</a>
|
||||
<span class="subTitle">mission making, made easy</span>
|
||||
<div class="header-right">
|
||||
<nav>
|
||||
<a href="/index.html">Home</a>
|
||||
@@ -18,6 +19,7 @@ class Header extends HTMLElement {
|
||||
<div class="dropdown-content">
|
||||
<a href="/pages/tutorials.html">Quick Starts</a>
|
||||
<a href="/pages/advanced/CAP.html">Advanced: CAP</a>
|
||||
<a href="/pages/advanced/missions.html">Advanced: Missions</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,6 +27,7 @@ class Header extends HTMLElement {
|
||||
<a href="/pages/persistence.html">Persistence</a>
|
||||
<a href="/pages/reference.html">Reference</a>
|
||||
<a href="/pages/spearheadapi.html">API</a>
|
||||
<a href="/pages/about_us.html">About Us</a>
|
||||
</nav>
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle light/dark theme">
|
||||
<svg id="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
class Note extends HTMLElement {
|
||||
connectedCallback() {
|
||||
// Get the note content
|
||||
const content = this.innerHTML;
|
||||
|
||||
// Get optional type attribute for different note styles
|
||||
const type = this.getAttribute('type') || 'default';
|
||||
|
||||
// Get optional title attribute
|
||||
const title = this.getAttribute('title');
|
||||
|
||||
// Build the note HTML
|
||||
let noteHTML = '<div class="note';
|
||||
|
||||
// Add type-specific class if provided
|
||||
if (type !== 'default') {
|
||||
noteHTML += ` note-${type}`;
|
||||
}
|
||||
|
||||
noteHTML += '">';
|
||||
|
||||
// Add title if provided
|
||||
if (title) {
|
||||
noteHTML += `<h4 class="note-title">${title}</h4>`;
|
||||
}
|
||||
|
||||
// Add the content
|
||||
noteHTML += content;
|
||||
noteHTML += '</div>';
|
||||
|
||||
this.innerHTML = noteHTML;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('note-box', Note);
|
||||
@@ -15,11 +15,11 @@ class Sidebar extends HTMLElement {
|
||||
// Clear existing nav items
|
||||
this.navItems = [];
|
||||
|
||||
// Find all h2 and h3 elements with IDs in the content area
|
||||
// Find all h2, h3, and h4 elements with IDs in the content area
|
||||
const contentWrapper = document.querySelector('.content-wrapper');
|
||||
if (!contentWrapper) return;
|
||||
|
||||
const headers = contentWrapper.querySelectorAll('h2[id], h3[id]');
|
||||
const headers = contentWrapper.querySelectorAll('h2[id], h3[id], h4[id]');
|
||||
|
||||
headers.forEach(header => {
|
||||
const id = header.getAttribute('id');
|
||||
@@ -45,6 +45,7 @@ class Sidebar extends HTMLElement {
|
||||
ul.innerHTML = '';
|
||||
|
||||
let currentH2Li = null;
|
||||
let currentH3Li = null;
|
||||
|
||||
this.navItems.forEach(item => {
|
||||
if (item.level === 'h2') {
|
||||
@@ -59,6 +60,7 @@ class Sidebar extends HTMLElement {
|
||||
li.appendChild(a);
|
||||
ul.appendChild(li);
|
||||
currentH2Li = li;
|
||||
currentH3Li = null;
|
||||
} else if (item.level === 'h3' && currentH2Li) {
|
||||
// Create h3 item under current h2
|
||||
let subUl = currentH2Li.querySelector('ul');
|
||||
@@ -74,6 +76,24 @@ class Sidebar extends HTMLElement {
|
||||
a.textContent = item.text;
|
||||
a.addEventListener('click', (e) => this.handleNavClick(e, item.id));
|
||||
|
||||
li.appendChild(a);
|
||||
subUl.appendChild(li);
|
||||
currentH3Li = li;
|
||||
} else if (item.level === 'h4' && currentH3Li) {
|
||||
// Create h4 item under current h3
|
||||
let subUl = currentH3Li.querySelector('ul');
|
||||
if (!subUl) {
|
||||
subUl = document.createElement('ul');
|
||||
currentH3Li.appendChild(subUl);
|
||||
}
|
||||
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
a.href = `#${item.id}`;
|
||||
a.className = 'side-nav-h4';
|
||||
a.textContent = item.text;
|
||||
a.addEventListener('click', (e) => this.handleNavClick(e, item.id));
|
||||
|
||||
li.appendChild(a);
|
||||
subUl.appendChild(li);
|
||||
}
|
||||
@@ -139,7 +159,7 @@ class Sidebar extends HTMLElement {
|
||||
link.classList.remove('active');
|
||||
});
|
||||
|
||||
if (activeId) {
|
||||
if (activeId !== nil && activeId !== '') {
|
||||
const activeLink = this.querySelector(`a[href="#${activeId}"]`);
|
||||
if (activeLink) {
|
||||
activeLink.classList.add('active');
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,4 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const sunIcon = document.getElementById('sun-icon');
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<!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>About Us</title> <!-- Google Fonts -->
|
||||
|
||||
<link rel="stylesheet" href="/style/prism.css">
|
||||
<link rel="stylesheet" href="/style/style.css">
|
||||
|
||||
<script src="/js/prism.js"></script>
|
||||
<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>About Spearhead</h1>
|
||||
<h2>Created by Mission makers, for mission makers</h2>
|
||||
<p>
|
||||
I Dutchie, started Spearhead as an idea of making mission making easier for myself and others.<br>
|
||||
On the way I've had a lot of help, in the form of feedback, ideas and most importantly testing from a lot of people. <br>
|
||||
Even though, at the time of writing this, I've written every single line of code myself, I could not have done it without the help of these members.<br>
|
||||
Therefore it's something WE created.
|
||||
|
||||
Our goal was very simple, make it possible to make big dynamic missions, without having to either give away all controll or having to get knee deep into scripting. <br>
|
||||
We struck a balance.
|
||||
|
||||
|
||||
|
||||
</p>
|
||||
|
||||
<h2>Hope you enjoy it!</h2>
|
||||
<p>
|
||||
All I can say is that I hope that some of you out there will take the step and make a mission! <br>
|
||||
Or, those that are experienced in mission making, will find more joy in making the mission they always wanted to make!<br>
|
||||
If you do, dont hesitate let me and TDCS know! <br>
|
||||
Don't worry, no obligation to let us use it, but any feedback is always welcome! <br>
|
||||
All I really want is to see the DCS community enjoy the Multiplayer environment as much as I do!<br>
|
||||
|
||||
~ Dutchie
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2025 Spearhead Project</p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
@@ -4,8 +4,12 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Spearhead Persistence</title>
|
||||
<title>Spearhead: Advanced CAP</title>
|
||||
|
||||
<link rel="stylesheet" href="/style/prism.css">
|
||||
<link rel="stylesheet" href="/style/style.css">
|
||||
|
||||
<script src="/js/prism.js"></script>
|
||||
<script src="/js/site.js"></script>
|
||||
<style>
|
||||
.side-nav a.active {
|
||||
@@ -25,14 +29,191 @@
|
||||
<app-sidebar></app-sidebar>
|
||||
|
||||
<div class="content-wrapper">
|
||||
|
||||
<note-box type="warning" title="Work in Progress">
|
||||
This page is not complete and documentation will be changed as updates are made.
|
||||
</note-box>
|
||||
|
||||
<h1>Advanced Tutorial: CAP</h1>
|
||||
<p style="font-size: 24px;">
|
||||
<b>You're a little early! <br>
|
||||
We're still working on this tutorial, but it will be available soon.</b>
|
||||
<p>
|
||||
Setting up CAP can be a challenge. Creating a dynamic environment for your players where enemy
|
||||
aircraft prove a challenge, but is still fun is something that requires some thought. <br>
|
||||
With Spearhead, we tried to make it as easy as possible, but to give you as much control as well.
|
||||
<br>
|
||||
Air combat is so much more than just aircraft flying around. There's intercepts, combat air patrol
|
||||
or maybe fighter sweeps. <br>
|
||||
Sometimes one of these is enough, sometimes creating a network of all three can make it feel like a
|
||||
very dynamic environment. <br>
|
||||
In this tutorial we'll explain how to set up each different type of CAP and we'll finish with some
|
||||
considerations on how to combine them and create an environment we think is fun to play in. <br>
|
||||
</p>
|
||||
|
||||
<h2 id="quick-reference">Quick Reference</h2>
|
||||
<p>
|
||||
Quickly noting down all the names, trigger zone conventions and other things worth mentioning before
|
||||
we get into the details. <br>
|
||||
|
||||
<h3 id="cap-groups-ref">Cap Group Prefix</h3>
|
||||
<table class="table-no-style">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code-inline>CAP_A</code-inline></td>
|
||||
<td>=></td>
|
||||
<td>Primary CAP group. First to go out and also important in defining how many active units
|
||||
can in a zone.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code-inline>CAP_B</code-inline></td>
|
||||
<td>=></td>
|
||||
<td>Secondary CAP group. Will be reinforcing the primary CAP group.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code-inline>CAP_S</code-inline></td>
|
||||
<td>=></td>
|
||||
<td>Sweep groups. These group will be used for fighter sweeps.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code-inline>CAP_I</code-inline></td>
|
||||
<td>=></td>
|
||||
<td>Intercept groups. These groups will be used for intercepts.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<note-box type="info" title="Combining Tasks">
|
||||
Currently a group can do 1 thing. Either be Primary, Seconday, Sweep or Intercept. <br>
|
||||
If you want to have different types during different stages, it's best to copy paste groups and
|
||||
rename them accordingly. <br>
|
||||
</note-box>
|
||||
|
||||
<note-box type="info" title="Airfield Defenses">
|
||||
|
||||
</note-box>
|
||||
|
||||
<h3 id="cap-zones-ref">Zones</h3>
|
||||
<table class="table-no-style">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code-inline>CAPROUTE_[ZoneID]_[NAME]</code-inline></td>
|
||||
<td>=></td>
|
||||
<td>A Cap ROUTE. The two most outer points are used to define the fly-over points.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code-inline>INTERCEPTZONE_[ZoneID]_[NAME]</code-inline></td>
|
||||
<td>=></td>
|
||||
<td>An Intercept zone. This is where, when detected, Intercept flights will try and
|
||||
intercept an enemy units.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
</p>
|
||||
<h2 id="setup">Setting up groups</h2>
|
||||
|
||||
<h3 id="types-explained">Types explained</h3>
|
||||
<p>
|
||||
Before we go into depth on each type it's best to first look at the types briefly and what we
|
||||
mean.<br>
|
||||
All groups that fly CAP, intercept or sweep missions are prefixed with
|
||||
<code-inline>CAP_</code-inline> and then a letter to indicate the type of group. <br>
|
||||
<br>
|
||||
<code-inline>CAP_A</code-inline> and <code-inline>CAP_B</code-inline> are CAP groups as most often
|
||||
seen in missions. <br>
|
||||
They will fly out, patrol a certain route and engage any enemy fighter that gets too close. <br>
|
||||
<br>
|
||||
<code-inline>CAP_S</code-inline> groups are Sweep groups. <br>
|
||||
These will fly out, fly a pre-designated route and engage enemy fighters they come across. <br>
|
||||
They, however, will not stick around and orbit, but will fly back home once their sweep is complete.
|
||||
<br>
|
||||
These are very nice for older scenarios or where Air Superiority is not maintained by either side.
|
||||
<br>
|
||||
<br>
|
||||
<code-inline>CAP_I</code-inline> groups are Intercept groups. <br>
|
||||
These groups will remain on high alert and will be triggered when enemy aircraft are detected in a
|
||||
certain zone. <br>
|
||||
<br>
|
||||
NOT IMPLMENTED YET: <br>
|
||||
<code-inline>CAP_E</code-inline> are escort groups. <br>
|
||||
These units will be dedicated to escorting CAS, SEAD and Strike flights. <br>
|
||||
The ground attack units mentioned are not in yet and thus escort groups are not either. <br>
|
||||
|
||||
<note-box type="warning" title="Overriding Taskings">
|
||||
Each tasking has very specific waypoints, both for departure, arrical and ofcourse all point in between. <br>
|
||||
Overriding the "tasking" will break part of the cap flow as "RTBInTen" and RTB events are triggered by waypoints. <br>
|
||||
Overriding these waypoints and taskings will break the management by Spearhead.
|
||||
</note-box>
|
||||
|
||||
<h3 id="combat-air-patrol">Combat Air Patrol</h3>
|
||||
|
||||
<h4 id="zone-setup-cap">Zone Setup</h4>
|
||||
<p>
|
||||
Combat Air Patrol groups use <code-inline>CAPROUTE_</code-inline> zones to know where to go.<br>
|
||||
These are trigger zones that need to be <code-inline>quad zones</code-inline>.
|
||||
The way the trigger zone is mapped to a route is as follows: <br>
|
||||
<br>
|
||||
The 2 points that are furthest apart from each other are chosen as the "leg" of the route. <br>
|
||||
The other 2 points are discarded. <br>
|
||||
<br>
|
||||
The Orbit route is then created with an "Anchored" task. With the closest point being the starting
|
||||
point with an outbound leg to the furthest point. <br>
|
||||
</p>
|
||||
<h4 id="group-setup-cap">Group Setup</h4>
|
||||
<p>
|
||||
Now you've defined where the CAP group will fly, it's time to set up the group itself. <br>
|
||||
The groups are used directly and will not be renamed, duplicated or anything else. <br>
|
||||
However, please note, again, that if you manually take over controll of the groups by giving them waypoints it will break the management by Spearhead. <br>
|
||||
They will ofcourse be despawned, respawned and tracked by Spearhead as needed. <br>
|
||||
|
||||
Add an aircraft unit and set it to "spawn from parking cold". <br>
|
||||
Name the group (unit names are not regarded) <br>
|
||||
|
||||
|
||||
|
||||
<note-box type="info" title="Cloning">
|
||||
Adding the lead first, then editing the loadout, livery etc. and only then adding flight members will automatically copy the leads loadout and other settings.
|
||||
</note-box>
|
||||
|
||||
</p>
|
||||
|
||||
<h4 id="what-it-does-cap">What it does</h4>
|
||||
<p>
|
||||
|
||||
</p>
|
||||
|
||||
<h3 id="sweep">Sweep</h3>
|
||||
<p>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
<h3 id="intercept">Intercept</h3>
|
||||
<p>
|
||||
|
||||
</p>
|
||||
|
||||
<h2 id="balancing">Balancing & Scaling</h2>
|
||||
<p>
|
||||
We are fully aware that balancing and scaling of the Air Threats is something you need to do. <br>
|
||||
We are still designing how to make this as easy as possible for you, the mission creators. <br>
|
||||
|
||||
The idea is to have thresholds. (If x number of players are in the mission, then add certain groups.) <br/>
|
||||
Most importantly it should be easy to use and easy to estimate before play testing. <br>
|
||||
<br>
|
||||
|
||||
Things we need to think about are (but not exclusively) whether or not we only want to scale based on CAP capable aircraft. <br>
|
||||
eg. if you have 10 apaches, should it bring up twice as much CAP? <br>
|
||||
Ofcourse we also need to think about the downscaling and about how later it can integrate with supplies. <br>
|
||||
|
||||
|
||||
Please bear with us, as we are definitely working on this and making sure that you can scale for to 100 players without much trouble. <br>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2025 Spearhead Project</p>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Spearhead Missions</title>
|
||||
<link rel="stylesheet" href="/style/style.css">
|
||||
<script src="/js/site.js"></script>
|
||||
<style>
|
||||
.side-nav a.active {
|
||||
font-weight: bold;
|
||||
color: #4fc3f7;
|
||||
}
|
||||
</style>
|
||||
<script type="module" src="/js/components.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<app-header></app-header>
|
||||
</header>
|
||||
<main>
|
||||
<div class="reference-container">
|
||||
<app-sidebar></app-sidebar>
|
||||
|
||||
<div class="content-wrapper">
|
||||
<h1>Advanced Tutorial: Missions</h1>
|
||||
<h2 id="strike">Strike</h2>
|
||||
<p>
|
||||
Naming: <span class="inline-lua"><span class="lua-variable">MISSION_STRIKE_[Name]</span></span> <br>
|
||||
</p>
|
||||
<h3 id="specific_targets">Specific Targets</h3>
|
||||
<p>TBD</p>
|
||||
|
||||
|
||||
<h3 id="scenery_targets">Scenery Targets</h3>
|
||||
<p>
|
||||
Bridges, buildings or other type of targets that are baked into the map are awesome targets.<br>
|
||||
We wanted to make sure they were easily integrated in Spearhead.
|
||||
|
||||
All you will need to do is right click on the scenery object you want added. <br>
|
||||
You will then get the option to "Assign as...", click this and you'll see a new trigger zone created. <br>
|
||||
It's best not to edit the triggerzone, but you can rename it however you like. <br>
|
||||
|
||||
Scenery objects will always be a specific "target" in the mission.
|
||||
|
||||
</p>
|
||||
|
||||
<note-box type="warning" title="Changing IDs">
|
||||
Be aware that Object IDs can change when maps are updated and edited and the "Assign as..." action will have to be performed again.
|
||||
</note-box>
|
||||
|
||||
|
||||
<p>Here's two images showing how a bridge is added to a mission.</p>
|
||||
<img src="/img/bridge.png" style="max-height: 35remrem;"/>
|
||||
<img src="/img/bridge_added.png" style="max-height: 35remrem;"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2025 Spearhead Project</p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
@@ -6,7 +6,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Spearhead Persistence</title> <!-- Google Fonts -->
|
||||
|
||||
<link rel="stylesheet" href="/style/prism.css">
|
||||
<link rel="stylesheet" href="/style/style.css">
|
||||
|
||||
<script src="/js/prism.js"></script>
|
||||
<script src="/js/site.js"></script>
|
||||
<style>
|
||||
.side-nav a.active {
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Spearhead Reference</title> <!-- Google Fonts -->
|
||||
|
||||
<link rel="stylesheet" href="../style/style.css">
|
||||
<script src="../js/site.js"></script>
|
||||
<link rel="stylesheet" href="/style/prism.css">
|
||||
<link rel="stylesheet" href="/style/style.css">
|
||||
|
||||
<script src="/js/prism.js"></script>
|
||||
<script src="/js/site.js"></script>
|
||||
<style>
|
||||
.side-nav a.active {
|
||||
font-weight: bold;
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Spearhead API</title>
|
||||
|
||||
<link rel="stylesheet" href="../style/style.css">
|
||||
<script src="../js/site.js"></script>
|
||||
<link rel="stylesheet" href="/style/prism.css">
|
||||
<link rel="stylesheet" href="/style/style.css">
|
||||
|
||||
<script src="/js/prism.js"></script>
|
||||
<script src="/js/site.js"></script>
|
||||
<script type="module" src="../js/components.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
+17
-27
@@ -6,8 +6,11 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Spearhead Tutorials</title>
|
||||
|
||||
<link rel="stylesheet" href="../style/style.css">
|
||||
<script src="../js/site.js"></script>
|
||||
<link rel="stylesheet" href="/style/prism.css">
|
||||
<link rel="stylesheet" href="/style/style.css">
|
||||
|
||||
<script src="/js/prism.js"></script>
|
||||
<script src="/js/site.js"></script>
|
||||
<style>
|
||||
.side-nav a.active {
|
||||
font-weight: bold;
|
||||
@@ -49,13 +52,11 @@
|
||||
<img style="width: 100%;" src="../img/script_install.png"></img>
|
||||
</div>
|
||||
|
||||
<div class="note">
|
||||
<p>
|
||||
Spearhead does not require any dependencies (eg. MIST or MOOSE). Compatibility with other frameworks is
|
||||
not tested at this time, so cannot be guaranteed, but there should be no conflicts if they are not
|
||||
controlling the same units.
|
||||
</p>
|
||||
</div>
|
||||
<note-box type="info" title="Note">
|
||||
Spearhead does not require any dependencies (eg. MIST or MOOSE). Compatibility with other frameworks is
|
||||
not tested at this time, so cannot be guaranteed, but there should be no conflicts if they are not
|
||||
controlling the same units.
|
||||
</note-box>
|
||||
|
||||
<h2 id="stages">Stages</h2>
|
||||
|
||||
@@ -114,13 +115,9 @@
|
||||
will trigger a backup unit to startup and fly out to take over. <br />
|
||||
</p>
|
||||
|
||||
<div class="note">
|
||||
<p>
|
||||
TIP ! <br />
|
||||
You can start a mission, speed up the simulation and make the CAP fly out to see what happens
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<note-box type="info" title="Tip">
|
||||
You can start a mission, speed up the simulation and make the CAP fly out to see what happens
|
||||
</note-box>
|
||||
|
||||
<h3 id="creating-cap-routes">Creating CAP Routes</h3>
|
||||
<p>
|
||||
@@ -153,11 +150,10 @@
|
||||
Missions are managed and monitored by Spearhead. <br/>
|
||||
Statics, groups and single units all alike. <br/>
|
||||
</p>
|
||||
<div class="note">
|
||||
<p>
|
||||
<strong>NOTE:</strong> While static are the same as groups in this context, they are not within DCS, please refrain from using static groups. A <span class="inline-ref">static</span> in DCS has a 1:1 relation for group:unit.
|
||||
</p>
|
||||
</div>
|
||||
<note-box type="info" title="Note">
|
||||
While static are the same as groups in this context, they are not within DCS, please refrain from artificially created static groups. A <code-inline>static</code-inline> in DCS has a 1:1 relation for group:unit. <br>
|
||||
By default DCS will always keep 1 static object in 1 groups.
|
||||
</note-box>
|
||||
<p>
|
||||
For this example I'll set up two missions. The first one is <span class="inline-lua"><span class="lua-variable">DEAD</span></span> mission and will consist of an SA-2 site with an additional "control center".
|
||||
</p>
|
||||
@@ -284,12 +280,6 @@
|
||||
<img src="../img/briefing_mission.png"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note">
|
||||
<p>
|
||||
<strong>TIP:</strong> You can make both the <span class="inline-ref">Color</span> and <span class="inline-ref">Fill</span> have a <span class="inline-ref">0</span> value for <span class="inline-ref">a</span>. This will make the weird box be invisible. Make sure to add the name of the mission to the text box name to easily find it back.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
We are now done creating a first mission. Hit fly and test it. <br/>
|
||||
Check all references for way more features and keep up to date with the latest changes as they come along! <br/>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/* PrismJS 1.30.0
|
||||
https://prismjs.com/download#themes=prism-okaidia&languages=lua */
|
||||
code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
|
||||
+157
-83
@@ -1,14 +1,11 @@
|
||||
|
||||
:root {
|
||||
/* Dark Theme (Default) - Slightly lighter */
|
||||
--color-bg-primary: #1b1d27; /* Much darker primary background */
|
||||
--color-bg-secondary: #2d303f; /* Darker secondary background */
|
||||
--color-bg-tertiary: #2c3041; /* Darker tertiary */
|
||||
--color-bg-header: #10121b; /* Darker header */
|
||||
--color-bg-code: #10121a; /* Almost black code block background */
|
||||
--color-bg-note: rgba(30, 32, 40, 0.85); /* Slightly darker note background */
|
||||
--color-bg-hover: rgba(120, 140, 255, 0.12); /* More visible hover */
|
||||
--color-bg-inline-lua: #181a22; /* Match code block background */
|
||||
--color-bg-note-dark: rgba(40, 35, 30, 0.85); /* Slightly darker note box */
|
||||
|
||||
--color-text-primary: #a5a5a5; /* Brighter text */
|
||||
@@ -22,14 +19,6 @@
|
||||
--color-link: #7ecbff;
|
||||
--color-link-hover: #ffffff;
|
||||
|
||||
/* Lua syntax highlighting colors with high contrast */
|
||||
--color-lua-variable: #4fc3f7;
|
||||
--color-lua-comment: #b0b8d0; /* Lighter, more visible comment */
|
||||
--color-lua-operator: #ffffff;
|
||||
--color-lua-keyword: #ffd166; /* Orange for keywords */
|
||||
--color-lua-string: #7fffde; /* Aqua for strings */
|
||||
--color-lua-function: #ffe066; /* Gold for functions */
|
||||
|
||||
/* Shadow and transparency values - unchanged */
|
||||
--shadow-small: 0 2px 5px rgba(0, 0, 0, 0.3);
|
||||
--shadow-medium: 0 3px 8px rgba(0, 0, 0, 0.5);
|
||||
@@ -59,11 +48,14 @@
|
||||
--gradient-header: linear-gradient(135deg, var(--color-bg-header) 0%, var(--color-bg-secondary) 70%, var(--color-bg-header-accent) 100%);
|
||||
--color-bg-header-accent: #2a2620;
|
||||
--color-logo-gradient: linear-gradient(120deg, #5caaff, #a0e8ff, #ffffff);
|
||||
--color-text-subTitle: #4a576f; /* Lighter subtitle text */
|
||||
--color-nav-underline: #b0a890;
|
||||
--color-nav-underline-glow: rgba(176, 168, 144, 0.6); --color-nav-hover-bg: var(--color-bg-hover);
|
||||
--color-nav-hover-text: #ffffff;
|
||||
--color-nav-hover-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
|
||||
--color-inline-code: #3498db;
|
||||
|
||||
/* Heading colors */
|
||||
--color-heading-h1: #8fc6ff;
|
||||
--color-heading-h2: #b3e0ff;
|
||||
@@ -77,10 +69,8 @@
|
||||
--color-bg-secondary: #b6b6b6;
|
||||
--color-bg-tertiary: #b6b6b6;
|
||||
--color-bg-header: #d3d3d3;
|
||||
--color-bg-code: #e3e6eb; /* Medium-light gray for code block background */
|
||||
--color-bg-note: rgba(215, 228, 245, 0.75);
|
||||
--color-bg-hover: rgba(100, 120, 200, 0.1);
|
||||
--color-bg-inline-lua: #e3e6eb;
|
||||
--color-bg-note-dark: rgba(235, 240, 250, 0.7);
|
||||
|
||||
--color-text-primary: #151720;
|
||||
@@ -94,15 +84,6 @@
|
||||
--color-link: #3050b0;
|
||||
--color-link-hover: #4060c0;
|
||||
|
||||
/* Lua syntax highlighting colors for light theme */
|
||||
--color-lua-variable: #1a237e;
|
||||
--color-lua-comment: #374151;
|
||||
--color-lua-operator: #23272e;
|
||||
--color-lua-keyword: #b45309;
|
||||
--color-lua-string: #065f46;
|
||||
--color-lua-function: #6d28d9;
|
||||
|
||||
|
||||
/* Shadow values adjusted for light theme */
|
||||
--shadow-small: 0 2px 5px rgba(0, 0, 0, 0.15);
|
||||
--shadow-medium: 0 3px 8px rgba(0, 0, 0, 0.2);
|
||||
@@ -188,7 +169,7 @@ header {
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 50%;
|
||||
width: auto;
|
||||
margin-left: 10em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -205,6 +186,13 @@ header {
|
||||
text-shadow: var(--shadow-small);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.subTitle {
|
||||
position: relative;
|
||||
left: -5.5rem;
|
||||
top: 1.7rem;
|
||||
color: var(--color-text-subTitle);
|
||||
text-wrap: nowrap;;
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
transform: scale(1.03);
|
||||
@@ -363,10 +351,6 @@ main {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.side-nav h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.side-nav ul {
|
||||
list-style-type: none;
|
||||
@@ -414,6 +398,12 @@ main {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.side-nav-h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5em;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
flex: 1;
|
||||
max-width: 70%;
|
||||
@@ -503,62 +493,6 @@ th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Code and syntax highlighting with improved contrast */
|
||||
.inline-lua {
|
||||
background-color: var(--color-bg-inline-lua);
|
||||
border-radius: 3px;
|
||||
font-family: var(--font-family-code);
|
||||
white-space: nowrap;
|
||||
padding: 0.1em 0.3em;
|
||||
color: #23272e;
|
||||
}
|
||||
|
||||
/* Code block styling with improved contrast */
|
||||
pre {
|
||||
background-color: var(--color-bg-code);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 8px;
|
||||
padding: 1.2em;
|
||||
margin: 1.5em 0;
|
||||
overflow-x: auto;
|
||||
font-family: var(--font-family-code);
|
||||
line-height: 1.4;
|
||||
box-shadow: inset 0 1px 8px rgba(0, 0, 0, 0.08);
|
||||
color: #23272e;
|
||||
}
|
||||
|
||||
.lua-variable {
|
||||
color: var(--color-lua-variable); /* Brighter variable color */
|
||||
}
|
||||
|
||||
.lua-comment {
|
||||
color: var(--color-lua-comment); /* Brighter comment color */
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.lua-operator {
|
||||
color: var(--color-lua-operator); /* Bright white for better contrast */
|
||||
}
|
||||
|
||||
.lua-keyword {
|
||||
color: var(--color-lua-keyword); /* Brighter keyword color */
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.lua-string {
|
||||
color: var(--color-lua-string); /* Brighter string color */
|
||||
}
|
||||
|
||||
.lua-function {
|
||||
color: var(--color-lua-function); /* Brighter function color */
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.inline-ref {
|
||||
color: var(--color-text-accent);
|
||||
font-weight: bold;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
/* Note boxes */
|
||||
.note {
|
||||
@@ -574,6 +508,58 @@ pre {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Enhanced note component styles */
|
||||
.note-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.8em;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-accent);
|
||||
border-bottom: 1px solid rgba(126, 203, 255, 0.3);
|
||||
padding-bottom: 0.3em;
|
||||
}
|
||||
|
||||
/* Note type variations */
|
||||
.note-warning {
|
||||
background: linear-gradient(to right, rgba(80, 60, 30, 0.7), rgba(70, 55, 30, 0.55));
|
||||
border-left-color: #f39c12;
|
||||
}
|
||||
|
||||
.note-warning .note-title {
|
||||
color: #f39c12;
|
||||
border-bottom-color: rgba(243, 156, 18, 0.3);
|
||||
}
|
||||
|
||||
.note-info {
|
||||
background: linear-gradient(to right, rgba(30, 60, 80, 0.7), rgba(25, 55, 75, 0.55));
|
||||
border-left-color: #3498db;
|
||||
}
|
||||
|
||||
.note-info .note-title {
|
||||
color: #3498db;
|
||||
border-bottom-color: rgba(52, 152, 219, 0.3);
|
||||
}
|
||||
|
||||
.note-success {
|
||||
background: linear-gradient(to right, rgba(30, 80, 40, 0.7), rgba(25, 75, 35, 0.55));
|
||||
border-left-color: #27ae60;
|
||||
}
|
||||
|
||||
.note-success .note-title {
|
||||
color: #27ae60;
|
||||
border-bottom-color: rgba(39, 174, 96, 0.3);
|
||||
}
|
||||
|
||||
.note-danger {
|
||||
background: linear-gradient(to right, rgba(80, 30, 30, 0.7), rgba(75, 25, 25, 0.55));
|
||||
border-left-color: #e74c3c;
|
||||
}
|
||||
|
||||
.note-danger .note-title {
|
||||
color: #e74c3c;
|
||||
border-bottom-color: rgba(231, 76, 60, 0.3);
|
||||
}
|
||||
|
||||
/* Image styling */
|
||||
img {
|
||||
max-width: 100%;
|
||||
@@ -582,6 +568,81 @@ img {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Code styling */
|
||||
code, pre {
|
||||
font-family: var(--font-family-code);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Block code styling */
|
||||
.custom-code-block {
|
||||
background: linear-gradient(135deg, var(--color-bg-secondary) 0%, var(--color-bg-tertiary) 100%);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 8px;
|
||||
padding: 1.2em;
|
||||
margin: 1.5em 0;
|
||||
overflow-x: auto;
|
||||
box-shadow: var(--shadow-small);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.custom-code-block code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.5;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Inline code styling */
|
||||
code.custom-inline-code {
|
||||
background: var(--color-bg-secondary) !important;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 4px;
|
||||
padding: 0.2em 0.4em;
|
||||
font-size: 0.85em;
|
||||
color: var(--color-inline-code) !important;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
display: inline;
|
||||
white-space: nowrap;
|
||||
font-family: var(--font-family-code) !important;
|
||||
}
|
||||
|
||||
/* Variable highlighting within inline code */
|
||||
.inline-var {
|
||||
color: var(--color-text-accent);
|
||||
font-weight: 600;
|
||||
background: rgba(126, 203, 255, 0.1);
|
||||
padding: 0.1em 0.2em;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(126, 203, 255, 0.2);
|
||||
}
|
||||
|
||||
/* Prism.js compatibility - override default styles */
|
||||
.custom-code-block .token.variable {
|
||||
color: var(--color-text-accent) !important;
|
||||
}
|
||||
|
||||
.custom-code-block .token.string {
|
||||
color: #a3d977 !important;
|
||||
}
|
||||
|
||||
.custom-code-block .token.keyword {
|
||||
color: #ff7aa3 !important;
|
||||
}
|
||||
|
||||
.custom-code-block .token.function {
|
||||
color: #78dce8 !important;
|
||||
}
|
||||
|
||||
.custom-code-block .token.comment {
|
||||
color: #727072 !important;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar - Enhanced version */
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
@@ -694,6 +755,19 @@ img {
|
||||
}
|
||||
}
|
||||
|
||||
.table-no-style {
|
||||
border-collapse: collapse;
|
||||
gap: 0px;
|
||||
|
||||
}
|
||||
|
||||
.table-no-style > tr,td {
|
||||
border: none;
|
||||
padding: 0px;
|
||||
padding-left: 1rem;
|
||||
margin: none;
|
||||
}
|
||||
|
||||
/* Theme Toggle Button */
|
||||
.theme-toggle {
|
||||
background: none;
|
||||
|
||||
Binary file not shown.
@@ -14,3 +14,6 @@ do -- mission aliases
|
||||
|
||||
end
|
||||
|
||||
---@class KeyValuePair
|
||||
---@field key string
|
||||
---@field value any
|
||||
|
||||
@@ -539,6 +539,7 @@ do -- INIT DCS_UTIL
|
||||
---@field radius number
|
||||
---@field verts Array<Vec2>
|
||||
---@field zone_type SpearheadTriggerZoneType
|
||||
---@field properties Array<KeyValuePair>?
|
||||
|
||||
---@type Array<SpearheadTriggerZone>
|
||||
DCS_UTIL.__trigger_zones = {}
|
||||
@@ -598,9 +599,18 @@ do -- INIT DCS_UTIL
|
||||
zone_type = zoneType,
|
||||
location = { x = trigger_zone.x, y = trigger_zone.y },
|
||||
radius = trigger_zone.radius,
|
||||
verts = verts
|
||||
verts = verts,
|
||||
properties = {}
|
||||
}
|
||||
|
||||
if trigger_zone.properties then
|
||||
for _, kvPair in pairs(trigger_zone.properties) do
|
||||
local key = kvPair["key"]
|
||||
local value = kvPair["value"]
|
||||
zone.properties[#zone.properties + 1] = { key = key, value = value }
|
||||
end
|
||||
end
|
||||
|
||||
DCS_UTIL.__trigger_zones[zone.name] = zone
|
||||
end
|
||||
end
|
||||
@@ -977,7 +987,8 @@ do -- INIT DCS_UTIL
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
---comment Get all units that are players
|
||||
---@return Array<Unit> units
|
||||
function DCS_UTIL.getAllPlayerUnits()
|
||||
|
||||
+57
-54
@@ -7,6 +7,7 @@
|
||||
---@field MissionZonesLocations table<string, Vec2> All Mission Zone Locations
|
||||
---@field StageZonesByNumber table<string, Array<string>> Stage zones grouped by index number
|
||||
---@field AllFarpZones Array<string>
|
||||
---@field AllSceneryObjects Array<SpearheadSceneryObject> All scenery objects
|
||||
---@field AllCapRoutes Array<string> All Cap route zone names
|
||||
---@field AllInterceptZones Array<string> All intercept zones
|
||||
---@field capZonesByCapZoneID table<string, CapRoute>
|
||||
@@ -58,11 +59,9 @@
|
||||
|
||||
---@class MissionZoneData
|
||||
---@field RedGroups Array<string>
|
||||
---@field SceneryTargets Array<SpearheadSceneryObject>
|
||||
---@field BlueGroups Array<string>
|
||||
|
||||
---@class MissionData
|
||||
---@field Groups Array<string>
|
||||
|
||||
---@class FarpZoneData
|
||||
---@field groups Array<string>
|
||||
---@field padNames Array<string>
|
||||
@@ -85,6 +84,7 @@ function Database.New(Logger)
|
||||
AllCapRoutes = {},
|
||||
capZonesByCapZoneID = {},
|
||||
AllInterceptZones = {},
|
||||
AllSceneryObjects = {},
|
||||
interceptZonesByZoneID = {},
|
||||
CarrierRouteZones = {},
|
||||
MissionZones = {},
|
||||
@@ -186,6 +186,19 @@ function Database.New(Logger)
|
||||
if lowered == "supplyhub" then
|
||||
table.insert(self._tables.SupplyHubZones, zone_name)
|
||||
end
|
||||
|
||||
if zone_data.properties then
|
||||
for _, kvPair in pairs(zone_data.properties) do
|
||||
if kvPair.key and kvPair.key == "OBJECT ID" then
|
||||
local objectID = tonumber(kvPair.value)
|
||||
if objectID then
|
||||
local sceneryObject = Spearhead.classes.stageClasses.Groups.SpearheadSceneryObject.New(objectID)
|
||||
table.insert(self._tables.AllSceneryObjects, sceneryObject)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -649,67 +662,57 @@ function Database:loadBlueSamUnits()
|
||||
end
|
||||
|
||||
---@private
|
||||
function Database:loadMissionzoneUnits()
|
||||
function Database:LoadZoneData(missionZoneName)
|
||||
local all_groups = getAvailableGroups()
|
||||
for _, missionZoneName in pairs(self._tables.MissionZones) do
|
||||
self._tables.MissionZoneData[missionZoneName] = {
|
||||
RedGroups = {},
|
||||
BlueGroups = {},
|
||||
}
|
||||
self._tables.MissionZoneData[missionZoneName] = {
|
||||
RedGroups = {},
|
||||
BlueGroups = {},
|
||||
SceneryTargets = {}
|
||||
}
|
||||
|
||||
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, missionZoneName)
|
||||
for _, groupName in pairs(groups) do
|
||||
if Spearhead.classes.helpers.MizGroupsManager.IsGroupStatic(groupName) == true then
|
||||
local object = StaticObject.getByName(groupName)
|
||||
|
||||
if object and object:getCoalition() == coalition.side.RED then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].RedGroups, groupName)
|
||||
elseif object then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].BlueGroups, groupName)
|
||||
end
|
||||
|
||||
else
|
||||
local group = Group.getByName(groupName)
|
||||
if group and group:getCoalition() == coalition.side.RED then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].RedGroups, groupName)
|
||||
elseif group then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].BlueGroups, groupName)
|
||||
end
|
||||
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, missionZoneName)
|
||||
for _, groupName in pairs(groups) do
|
||||
if Spearhead.classes.helpers.MizGroupsManager.IsGroupStatic(groupName) == true then
|
||||
local object = StaticObject.getByName(groupName)
|
||||
|
||||
if object and object:getCoalition() == coalition.side.RED then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].RedGroups, groupName)
|
||||
elseif object then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].BlueGroups, groupName)
|
||||
end
|
||||
|
||||
else
|
||||
local group = Group.getByName(groupName)
|
||||
if group and group:getCoalition() == coalition.side.RED then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].RedGroups, groupName)
|
||||
elseif group then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].BlueGroups, groupName)
|
||||
end
|
||||
end
|
||||
is_group_taken[groupName] = true
|
||||
end
|
||||
|
||||
for _, sceneryObject in pairs(self._tables.AllSceneryObjects) do
|
||||
local point = sceneryObject:GetPoint()
|
||||
if point then
|
||||
if Spearhead.DcsUtil.isPositionInZone(point.x, point.z, missionZoneName) == true then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].SceneryTargets, sceneryObject)
|
||||
end
|
||||
is_group_taken[groupName] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
function Database:loadMissionzoneUnits()
|
||||
for _, missionZoneName in pairs(self._tables.MissionZones) do
|
||||
self:LoadZoneData(missionZoneName)
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
function Database:loadRandomMissionzoneUnits()
|
||||
local all_groups = getAvailableGroups()
|
||||
for _, missionZoneName in pairs(self._tables.RandomMissionZones) do
|
||||
self._tables.MissionZoneData[missionZoneName] = {
|
||||
RedGroups = {},
|
||||
BlueGroups = {},
|
||||
}
|
||||
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, missionZoneName)
|
||||
for _, groupName in pairs(groups) do
|
||||
if Spearhead.classes.helpers.MizGroupsManager.IsGroupStatic(groupName) == true then
|
||||
local object = StaticObject.getByName(groupName)
|
||||
|
||||
if object and object:getCoalition() == coalition.side.RED then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].RedGroups, groupName)
|
||||
elseif object then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].BlueGroups, groupName)
|
||||
end
|
||||
|
||||
else
|
||||
local group = Group.getByName(groupName)
|
||||
if group and group:getCoalition() == coalition.side.RED then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].RedGroups, groupName)
|
||||
elseif group then
|
||||
table.insert(self._tables.MissionZoneData[missionZoneName].BlueGroups, groupName)
|
||||
end
|
||||
end
|
||||
is_group_taken[groupName] = true
|
||||
end
|
||||
self:LoadZoneData(missionZoneName)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
---@class SpearheadSceneryObject
|
||||
---@field private persistentName string The persistent name of the scenery object
|
||||
---@field private objectID number The ID of the scenery object
|
||||
---@field private internalObj table
|
||||
---@field private isDead boolean Indicates if the scenery object is dead
|
||||
local SpearheadSceneryObject = {}
|
||||
SpearheadSceneryObject.__index = SpearheadSceneryObject
|
||||
|
||||
---comment
|
||||
---@param objectID number
|
||||
---@return SpearheadSceneryObject?
|
||||
function SpearheadSceneryObject.New(objectID)
|
||||
|
||||
local self = setmetatable({}, SpearheadSceneryObject)
|
||||
|
||||
if objectID == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
self.persistentName = "SpearheadSceneryObject_" .. objectID
|
||||
self.objectID = objectID
|
||||
self.isDead = false
|
||||
self.internalObj = {
|
||||
["id_"] = objectID
|
||||
}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function SpearheadSceneryObject:IsAlive()
|
||||
|
||||
if Object.isExist(self.internalObj) == false then
|
||||
self:MarkDead()
|
||||
return false
|
||||
end
|
||||
|
||||
if SceneryObject.getLife(self.internalObj) <= 0.10 then
|
||||
self:MarkDead()
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---@private
|
||||
function SpearheadSceneryObject:MarkDead()
|
||||
if self.isDead == true then return end
|
||||
self.isDead = true
|
||||
Spearhead.classes.persistence.Persistence.UnitKilled(self.persistentName, self:GetPoint(), 0, "Scenery")
|
||||
end
|
||||
|
||||
function SpearheadSceneryObject:UpdateStatePersistently()
|
||||
if self.isDead == true then
|
||||
return
|
||||
end
|
||||
|
||||
local state = Spearhead.classes.persistence.Persistence.UnitState(self.persistentName)
|
||||
if state and state.isDead == true then
|
||||
trigger.action.explosion(self:GetPoint(), 1000)
|
||||
self.isDead = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function SpearheadSceneryObject:GetPersistentName()
|
||||
return self.persistentName
|
||||
end
|
||||
|
||||
function SpearheadSceneryObject:GetPoint()
|
||||
return Object.getPoint(self.internalObj)
|
||||
end
|
||||
|
||||
if not Spearhead.classes then Spearhead.classes = {} end
|
||||
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
|
||||
if not Spearhead.classes.stageClasses.Groups then Spearhead.classes.stageClasses.Groups = {} end
|
||||
Spearhead.classes.stageClasses.Groups.SpearheadSceneryObject = SpearheadSceneryObject
|
||||
@@ -15,6 +15,7 @@ local ZoneMission = {}
|
||||
--- @field blueGroups Array<SpearheadGroup>
|
||||
--- @field unitsAlive table<string, table<string, boolean>>
|
||||
--- @field targetsAlive table<string, table<string, boolean>>
|
||||
--- @field sceneryTargets Array<SpearheadSceneryObject>
|
||||
--- @field groupNamesPerunit table<string,string>
|
||||
|
||||
---@class ParsedMissionName
|
||||
@@ -101,7 +102,8 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage, spaw
|
||||
unitsAlive = {},
|
||||
targetsAlive = {},
|
||||
hasTargets = false,
|
||||
groupNamesPerunit = {}
|
||||
groupNamesPerunit = {},
|
||||
sceneryTargets = {}
|
||||
}
|
||||
|
||||
self._parentStage = parentStage
|
||||
@@ -127,6 +129,10 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage, spaw
|
||||
|
||||
local missionData = database:getMissionDataForZone(zoneName)
|
||||
if not missionData then return end
|
||||
self._missionGroups.sceneryTargets = missionData.SceneryTargets or {}
|
||||
if Spearhead.Util.tableLength(self._missionGroups.sceneryTargets) > 0 then
|
||||
self._missionGroups.hasTargets = true
|
||||
end
|
||||
|
||||
for _, groupName in pairs(missionData.BlueGroups) do
|
||||
local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true)
|
||||
@@ -288,6 +294,13 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
|
||||
end
|
||||
end
|
||||
|
||||
for _, sceneryObject in pairs(self._missionGroups.sceneryTargets) do
|
||||
total = total + 1
|
||||
if sceneryObject:IsAlive() == true then
|
||||
alive = alive + 1
|
||||
end
|
||||
end
|
||||
|
||||
local deadRatio = (total - alive) / total
|
||||
if deadRatio >= self._completeAtIndex then
|
||||
self._logger:debug("Dead ratio " .. self.zoneName .. deadRatio .. " >= " .. self._completeAtIndex)
|
||||
@@ -327,6 +340,10 @@ function ZoneMission:SpawnPersistedState()
|
||||
for _, group in pairs(self._missionGroups.redGroups) do
|
||||
group:Spawn()
|
||||
end
|
||||
|
||||
for _, object in pairs(self._missionGroups.sceneryTargets) do
|
||||
object:UpdateStatePersistently()
|
||||
end
|
||||
end
|
||||
|
||||
---spawns the mission, but doesn't add
|
||||
@@ -361,6 +378,10 @@ function ZoneMission:SpawnActive()
|
||||
group:Spawn()
|
||||
end
|
||||
|
||||
for _, sceneryObject in pairs(self._missionGroups.sceneryTargets) do
|
||||
sceneryObject:UpdateStatePersistently()
|
||||
end
|
||||
|
||||
if self._battleManager then
|
||||
self._battleManager:Start()
|
||||
end
|
||||
@@ -404,6 +425,13 @@ function ZoneMission:PercentageComplete()
|
||||
end
|
||||
end
|
||||
|
||||
for _, sceneryObject in pairs(self._missionGroups.sceneryTargets) do
|
||||
total = total + 1
|
||||
if sceneryObject:IsAlive() == false then
|
||||
dead = dead + 1
|
||||
end
|
||||
end
|
||||
|
||||
if total > 0 then
|
||||
return math.floor((dead / total) * 100)
|
||||
end
|
||||
|
||||
@@ -36,6 +36,8 @@ assert(loadfile(classPath .. "stageClasses\\Stages\\WaitingStage.lua"))()
|
||||
|
||||
|
||||
assert(loadfile(classPath .. "stageClasses\\Groups\\SpearheadGroup.lua"))()
|
||||
assert(loadfile(classPath .. "stageClasses\\Groups\\SpearheadSceneryObject.lua"))()
|
||||
|
||||
|
||||
assert(loadfile(classPath .. "stageClasses\\helpers\\MissionCommandsHelper.lua"))()
|
||||
assert(loadfile(classPath .. "stageClasses\\helpers\\SupplyConfig.lua"))()
|
||||
|
||||
Reference in New Issue
Block a user