work in progress on publishing docs

This commit is contained in:
2026-07-26 23:31:44 +02:00
parent 5a906cdea0
commit 55138d7e2a
42 changed files with 462 additions and 460 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+64
View File
@@ -0,0 +1,64 @@
<!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</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>
<script type="module" src="js/components.js"></script>
</head>
<body>
<header>
<app-header></app-header>
</header>
<main>
<div class="reference-container">
<div class="side-nav">
<h4>Quick Links</h4>
<ul>
<li><a href="/pages/tutorials.html" class="side-nav-h2">Tutorials</a></li>
<li><a href="/pages/persistence.html" class="side-nav-h2">Persistence</a></li>
<li><a href="/pages/reference.html" class="side-nav-h2">Reference</a></li>
<li><a href="/pages/spearheadapi.html" class="side-nav-h2">API</a></li>
</ul>
</div>
<div class="content-wrapper">
<h1 id="welcome">Welcome to Spearhead!</h1>
<p>Welcome to the story teller friendly mission framework in DCS (at least, that's what we hope to create).</p>
<p>Together with mission editors we've tried to build a framework that makes creating multiplayer missions as easy as possible.
With the help of naming conventions, trigger zones, pre-configured logic and custom configuration items, it gives a huge amount of versatility.
we're trying to strike a balance between no-code and low-code with the possibility to hook into it for the hard core scripters (like ourselves).
<note-box type="info">
<b>For the scripters</b>: If you yourself are a scripter, and you're missing interfaces, please reach out to us! We want to make sure that the framework is as easy to use for scripters as it is for non-scripters, and we can't do that without your help!
</note-box>
</p>
<p>
Whether this is your first mission or your hundredth, we hope Spearhead will make your life easier.
</p>
<download-spearhead></download-spearhead>
</div>
</div>
</main>
<footer>
<p>&copy; 2025 Spearhead Project</p>
</footer>
</body>
</html>
+6
View File
@@ -0,0 +1,6 @@
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';
import './components/latest-version-download.js';
+29
View File
@@ -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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
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);
+32
View File
@@ -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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
// 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);
+54
View File
@@ -0,0 +1,54 @@
class Header extends HTMLElement {
constructor(){
super();
}
connectedCallback() {
this.innerHTML = /*html*/`
<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>
<div class="dropdown">
<a href="/pages/tutorials.html">Tutorials</a>
<div class="dropdown-content">
<a href="/pages/include-script.html">Include the Script</a>
<a href="/pages/first-start.html">First Start</a>
<a href="/pages/advanced/CAP.html">Advanced: CAP</a>
<a href="/pages/advanced/missions.html">Advanced: Missions</a>
<a href="/pages/advanced/map-markings.html">Map Markings</a>
</div>
</div>
<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">
<path d="M12 11.807A9.002 9.002 0 0 1 10.049 2a9.942 9.942 0 0 0-5.12 2.735c-3.905 3.905-3.905 10.237 0 14.142 3.906 3.906 10.237 3.905 14.143 0a9.946 9.946 0 0 0 2.735-5.119A9.003 9.003 0 0 1 12 11.807z"/>
</svg>
<svg id="sun-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="display: none;">
<path d="M6.995 12c0 2.761 2.246 5.007 5.007 5.007s5.007-2.246 5.007-5.007-2.246-5.007-5.007-5.007S6.995 9.239 6.995 12zM12 8.993c1.658 0 3.007 1.349 3.007 3.007S13.658 15.007 12 15.007 8.993 13.658 8.993 12 10.342 8.993 12 8.993zM10.998 19H12.998V22H10.998zM10.998 2H12.998V5H10.998zM1.998 11H4.998V13H1.998zM18.998 11H21.998V13H18.998z"/>
<path transform="rotate(-45.017 5.986 18.01)" d="M4.986 17.01H6.986V19.01H4.986z"/>
<path transform="rotate(-45.001 18.008 5.99)" d="M17.008 4.99H19.008V6.99H17.008z"/>
<path transform="rotate(-134.983 5.988 5.99)" d="M4.988 4.99H6.988V6.99H4.988z"/>
<path transform="rotate(134.999 18.008 18.01)" d="M17.008 17.01H19.008V19.01H17.008z"/>
</svg>
</button>
</div>
</div>
`;
}
}
customElements.define('app-header', Header);
@@ -0,0 +1,119 @@
class DownloadScript extends HTMLElement {
constructor(){
super();
this.cacheKey = 'spearhead-latest-release';
this.cacheExpiryMinutes = 15;
}
getCachedData() {
const cached = localStorage.getItem(this.cacheKey);
if (!cached) return null;
const { data, timestamp } = JSON.parse(cached);
const now = Date.now();
const expiry = this.cacheExpiryMinutes * 60 * 1000; // 15 minutes in ms
if (now - timestamp > expiry) {
localStorage.removeItem(this.cacheKey);
return null;
}
return data;
}
setCachedData(data) {
const cacheItem = {
data: data,
timestamp: Date.now()
};
localStorage.setItem(this.cacheKey, JSON.stringify(cacheItem));
}
async fetchLatestRelease() {
// Check cache first
const cachedData = this.getCachedData();
if (cachedData) {
return cachedData;
}
try {
const response = await fetch('https://api.github.com/repos/dutchie031/Spearhead/releases/latest');
const data = await response.json();
// Cache the data
this.setCachedData(data);
return data;
} catch (error) {
console.error('Error fetching latest release:', error);
return null;
}
}
async fetchReleaseAssets(url){
try {
const response = await fetch(url);
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching release assets:', error);
return null;
}
}
async connectedCallback() {
var version = "?";
var timestamp = "?";
var size = "?";
var href = "https://github.com/dutchie031/Spearhead/releases"
const latestRelease = await this.fetchLatestRelease();
if (latestRelease) {
version = latestRelease.tag_name;
timestamp = new Date(latestRelease.published_at).toLocaleDateString('en-CA');
const assets = await this.fetchReleaseAssets(latestRelease.assets_url);
if (assets && assets.length > 0) {
const spearheadAsset = assets.find(asset => asset.name.includes('spearhead'));
if (spearheadAsset) {
size = (spearheadAsset.size / 1024).toFixed(2) + 'kb';
href = spearheadAsset.browser_download_url;
}
}
}
this.innerHTML = /*html*/`
<div class="latest-version-download-box">
<style>
.latest-version-download-box {
display: flex;
}
.latest-version-download-content {
border: 1px solid #ccc;
border-radius: 8px;
padding: 0px 16px;
}
.version-text {
font-weight: bold;
color: #f5efefff
}
</style>
<div class="latest-version-download-content">
<p>
<span class="version-text">${version}</span> (${timestamp}) <br>
<a style="text-decoration: underline;" target="_blank" href="${href}">Download</a> (${size}) <br>
<a style="text-decoration: underline;" target="_blank" href="https://github.com/dutchie031/Spearhead/releases">See All Versions here</a>
</p>
</div>
</div>
`;
}
}
customElements.define('download-spearhead', DownloadScript);
+35
View File
@@ -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);
+198
View File
@@ -0,0 +1,198 @@
class Sidebar extends HTMLElement {
constructor() {
super();
this.navItems = [];
}
connectedCallback() {
this.render();
this.scanHeaders();
this.setupScrollListener();
this.highlightActiveSection();
}
scanHeaders() {
// Clear existing nav items
this.navItems = [];
// 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], h4[id]');
headers.forEach(header => {
const id = header.getAttribute('id');
const text = header.textContent.trim();
const level = header.tagName.toLowerCase();
this.navItems.push({
id,
text,
level,
element: header
});
});
this.renderNavigation();
}
renderNavigation() {
const ul = this.querySelector('ul');
if (!ul) return;
// Clear existing content
ul.innerHTML = '';
let currentH2Li = null;
let currentH3Li = null;
this.navItems.forEach(item => {
if (item.level === 'h2') {
// Create h2 item
const li = document.createElement('li');
const a = document.createElement('a');
a.href = `#${item.id}`;
a.className = 'side-nav-h2';
a.textContent = item.text;
a.addEventListener('click', (e) => this.handleNavClick(e, item.id));
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');
if (!subUl) {
subUl = document.createElement('ul');
currentH2Li.appendChild(subUl);
}
const li = document.createElement('li');
const a = document.createElement('a');
a.href = `#${item.id}`;
a.className = 'side-nav-h3';
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);
}
});
}
handleNavClick(e, targetId) {
e.preventDefault();
// Remove active class from all links
this.querySelectorAll('a').forEach(link => {
link.classList.remove('active');
});
// Add active class to clicked link
e.target.classList.add('active');
// Smooth scroll to target
const targetElement = document.getElementById(targetId);
if (targetElement) {
targetElement.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}
setupScrollListener() {
let ticking = false;
const handleScroll = () => {
if (!ticking) {
requestAnimationFrame(() => {
this.highlightActiveSection();
ticking = false;
});
ticking = true;
}
};
window.addEventListener('scroll', handleScroll);
}
highlightActiveSection() {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const offset = 100; // Offset for highlighting
let activeId = '';
// Find the currently visible section
this.navItems.forEach(item => {
const element = item.element;
const rect = element.getBoundingClientRect();
const elementTop = rect.top + scrollTop;
if (elementTop <= scrollTop + offset) {
activeId = item.id;
}
});
// Update active state
this.querySelectorAll('a').forEach(link => {
link.classList.remove('active');
});
if (activeId !== nil && activeId !== '') {
const activeLink = this.querySelector(`a[href="#${activeId}"]`);
if (activeLink) {
activeLink.classList.add('active');
}
}
}
render() {
this.innerHTML = `
<div class="side-nav">
<h4 class="side-nav-title"></h4>
<ul>
<!-- Navigation items will be populated automatically -->
</ul>
</div>
`;
}
// Method to refresh the sidebar when content changes
refresh() {
this.scanHeaders();
}
// Method to set the sidebar title
setTitle(title) {
const titleElement = this.querySelector('.side-nav-title');
if (titleElement) {
titleElement.textContent = title;
}
}
}
// Register the custom element
customElements.define('app-sidebar', Sidebar);
export default Sidebar;
File diff suppressed because one or more lines are too long
+91
View File
@@ -0,0 +1,91 @@
document.addEventListener('DOMContentLoaded', function() {
const themeToggle = document.getElementById('theme-toggle');
const sunIcon = document.getElementById('sun-icon');
const moonIcon = document.getElementById('moon-icon');
if (!themeToggle || !sunIcon || !moonIcon) return;
function setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
sunIcon.style.display = theme === 'light' ? 'block' : 'none';
moonIcon.style.display = theme === 'light' ? 'none' : 'block';
}
themeToggle.addEventListener('click', function() {
const current = document.documentElement.getAttribute('data-theme') || 'dark';
setTheme(current === 'dark' ? 'light' : 'dark');
});
// Initialize
const saved = localStorage.getItem('theme') || 'dark';
setTheme(saved);
// Set side-nav-title to the first h1's text
var h1 = document.querySelector('.content-wrapper h1');
var sideNavTitle = document.querySelector('.side-nav-title');
if (h1 && sideNavTitle) {
sideNavTitle.textContent = h1.textContent;
}
});
// Highlight sidenav link on scroll (shared for all pages)
function setupSideNavHighlight() {
const navLinks = document.querySelectorAll('.side-nav a');
if (!navLinks.length) return;
const sections = Array.from(navLinks).map(link => {
const id = link.getAttribute('href').replace('#', '');
return document.getElementById(id);
});
function getHeaderOffset() {
const header = document.querySelector('header');
return header ? header.offsetHeight : 0;
}
// Custom scroll on nav click
navLinks.forEach((link, i) => {
link.addEventListener('click', function(e) {
const section = sections[i];
if (section) {
e.preventDefault();
const headerOffset = getHeaderOffset();
const targetY = window.innerHeight * 0.10;
const sectionTop = section.getBoundingClientRect().top + window.scrollY;
const scrollTo = sectionTop - headerOffset - targetY;
window.scrollTo({ top: scrollTo, behavior: 'smooth' });
}
});
});
function onScroll() {
const headerOffset = getHeaderOffset();
const targetY = window.innerHeight * 0.1; // 30% from the top
let closestIdx = 0;
let minDist = Infinity;
for (let i = 0; i < sections.length; i++) {
const section = sections[i];
if (section) {
const dist = Math.abs(section.getBoundingClientRect().top - headerOffset - targetY);
if (dist < minDist) {
minDist = dist;
closestIdx = i;
}
}
}
navLinks.forEach((link, i) => {
if (i === closestIdx) {
link.classList.add('active');
} else {
link.classList.remove('active');
}
});
}
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onScroll);
onScroll(); // Initial call
}
// Run on DOMContentLoaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupSideNavHighlight);
} else {
setupSideNavHighlight();
}
+64
View File
@@ -0,0 +1,64 @@
<!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 control 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>&copy; 2025 Spearhead Project</p>
</footer>
</body>
</html>
+223
View File
@@ -0,0 +1,223 @@
<!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: 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 {
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">
<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>
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>&copy; 2025 Spearhead Project</p>
</footer>
</body>
</html>
@@ -0,0 +1,85 @@
<!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>Map Markings</h1>
<p>
Drawings can be very powerful in DCS missions. <br>
They can guide players towards the right objectives or warn them of dangers ahead. <br>
In Spearhead, we have both automatic markers and drawings as well as the possibility to integrate custom drawings.<br>
</p>
<h2 id="automatic_drawings">Automated</h2>
<h3 id="stage_drawings">Stages</h3>
<p>
Spearhead automatically draws stage markings on the map for players to see. <br>
These are drawn when a stage becomes active and removed when the stage is completed. <br>
This gives players a clear visual indication of where they need to go next. <br>
<br>
These can however be disabled in the configuration if desired. <br>
<code-inline>SpearheadConfig.StageConfig.drawStages</code-inline> <br>
<code-inline>SpearheadConfig.StageConfig.drawPreActived</code-inline> <br>
</p>
<h3 id="lastcontact_markings">Last Contact</h3>
<p>
If enabled, Spearhead will draw a marker on the map where the enemy was last killed. <br>
This might help in big multiplayer missions where players log off without sharing intel. <br>
This can be enabled or disabled in the configuration. <br>
<code-inline>SpearheadConfig.StageConfig.markLastContact</code-inline>
</p>
<h2 id="custom_drawings">Custom</h2>
<p>
Adding a custom drawing is easy. <br>
Simply create the drawing and then name it according to the following convention: <br>
<code-inline>DRAWING_[a]:[b]_[FreeForm]</starting></code-inline> <br>
Where:
<ul>
<li><code-inline>[a]</code-inline> is the stage number at which the drawing should appear.</li>
<li><code-inline>[b]</code-inline> is the stage number at which the drawing should be removed again.</li>
<li><code-inline>[FreeForm]</code-inline> is any name you want to make it unique or findables.</li>
</ul>
</p>
<note-box type="warning" title="Author Layer">
The custom drawings need to be in the "Author" layer of the mission editor to be managed by Spearhead.
This is due to other layers being visible by default and cannot be as easily removed when not needed.
The author layer is not visible by default in multiplayer, making it perfect for this use case.
</note-box>
</div>
</div>
</main>
<footer>
<p>&copy; 2025 Spearhead Project</p>
</footer>
</body>
</html>
+71
View File
@@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spearhead Missions</title>
<link rel="stylesheet" href="/style/style.css">
<script src="/js/site.js"></script>
<style>
.side-nav a.active {
font-weight: bold;
color: #4fc3f7;
}
</style>
<script type="module" src="/js/components.js"></script>
</head>
<body>
<header>
<app-header></app-header>
</header>
<main>
<div class="reference-container">
<app-sidebar></app-sidebar>
<div class="content-wrapper">
<h1>Advanced Tutorial: Missions</h1>
<h2 id="strike">Strike</h2>
<p>
Naming: <span class="inline-lua"><span class="lua-variable">MISSION_STRIKE_[Name]</span></span> <br>
</p>
<h3 id="specific_targets">Specific Targets</h3>
<p>TBD</p>
<h3 id="scenery_targets">Scenery Targets</h3>
<p>
Bridges, buildings or other type of targets that are baked into the map are awesome targets.<br>
We wanted to make sure they were easily integrated in Spearhead.
</p>
<note-box type="info" title="Implementation details">
Spearhead detects scenery targets by the trigger zone name. A zone named <strong>scenerytarget_[freeform]</strong> or
<strong>scenerytargets</strong> (case-insensitive) will be scanned and any scenery objects that has the attribute "Buildings" inside the zone
will be added as mission scenery targets.
</note-box>
<p>
Scenery targets are treated like mission targets: they are included in the mission completion calculation
(their `IsAlive()` state is checked) and their state is persisted/updated when missions spawn or resume.
</p>
<note-box type="warning" title="Changing IDs">
Due to Object IDs changing when maps are updated and edited the "Assign as..." functionality does not work for scenery targets.
Hence the need to use trigger zones to define them.
</note-box>
<p>Here's two images showing how a bridge is added to a mission.</p>
<img src="/img/bridge.png" style="max-height: 35remrem;"/>
</div>
</div>
</main>
<footer>
<p>&copy; 2025 Spearhead Project</p>
</footer>
</body>
</html>
+208
View File
@@ -0,0 +1,208 @@
<!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 Tutorials</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 {
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>Tutorials</h1>
<p>
This guide is to get you started building your first Spearhead mission. <br />
Spearhead was created to enable the mission maker to worry as little about the running, timing and scripting
and most about the setting and looks and feel of the mission. <br />
In the example we'll show how you can create a simple island hopping mission
</p>
<h2>Include the script</h2>
<p>
Read <a href="/pages/include-script.html">here</a> how to include the Spearhead script in your mission first. <br />
</p>
<h2 id="stages">Stages</h2>
<p>
In general, stages are the backbone of a Spearhead mission. <br>
They define the flow of the mission and what is active at what time. <br>
Don't worry, it can be changed and adapted later on, but it's best to translate your mission idea into stages first. <br>
</p>
<p>
Stages are defined by trigger zones. <br>
They'll be picked up by Spearhead when named according to the naming convention: <br>
<code-inline>MISSIONSTAGE_[OrderNumber]_[FreeForm]</code-inline> <br>
<br>
Stages are divided in primary stages: <code-inline>MISSIONSTAGE_[number]</code-inline> <br>
or secondary stages: <code-inline>MISSIONSTAGE_x[number]</code-inline> <br>
Note the x before the number. This marks it as "extra" <br>
Secondary stages will follow the order number, but are not required to be completed before moving on to the next stage numbers. <br>
</p>
<p>
For this Demo mission we'll create 3 stages. <br>
<code-inline>MissionStage_1_start</code-inline><br>
<code-inline>MissionStage_2_continuation</code-inline><br>
<code-inline>MissionStage_3_finale</code-inline> <br>
Each stage will have it's own trigger zone. <br/>
</p>
<img src="../img/first-start/1.png" alt="The first stages displayed" style="width: 100%;"></img>
<p>
There's 3 stages now. <br>
The first <code-inline>MissionStage_1_start</code-inline> is the first stage and will be active at mission start. <br>
The second <code-inline>MissionStage_2_continuation</code-inline> will be pre-activated when stage 1 is active. <br>
But will be fully activated when stage 1 is complete. <br>
Pre-activated means that the SAM missions and Airbase units will be activated. <br>
This is the difference between SAM and DEAD missions, but let's not get into too much detail at this time. <br>
You can ofcourse create as many stages as you want. <br>
You can also change the amount of stages that get pre-activated. <br>
By default Spearhead will pre-activate 1 stage ahead. <br/>
This can be changed in the configuration. <br/>
</p>
<h2 id="create-missions">Create Missions</h2>
<p>
Now that we've created the stages we can start adding missions to them. <br>
Missions are also defined by trigger zones. <br>
They'll be picked up by Spearhead when named according to the naming convention: <br>
<code-inline>MISSION_[Type]_[FreeForm]</code-inline> <br>
Where <code-inline>[Type]</code-inline> is the type of mission. <a href="./reference.html#mission-zones">See all</a><br>
</p>
<h3 id="mission-cas">CAS</h3>
<p>
Personally CAS is one of my favorite mission types. <br>
Mostly because it's easy to set up and creates a truly immersive experience. <br>
</p>
<h3 id="mission-sam">SAM</h3>
<p>
</p>
<h3 id="mission-strike">STRIKE</h3>
<p>
</p>
<h2 id="setting-up-cap">Setting up CAP</h2>
<p>
If you don't want to use the CAP managers withing Spearhead you can skip this and continue to <a href="#setting-up-the-missions">Setting up Missions</a>. <br />
However CAP is one of the painpoints in a lot of missions and setting up a dynamic feeling airspace can be
quite the challenge. <br />
With the CAP managers we've tried to make this a lot easier. <br />
A CAP group needs to follow the following naming convention: <code-inline>CAP_[A|B][CONFIG]_[Free Form]</code-inline>
For details on config read this: <code-inline>CAP Group Config</code-inline>
For now I set up 3 groups with the following names. <code-inline>CAP_A[1]1_Rota1</code-inline>, <code-inline>CAP_A[1]1_Rota1-1</code-inline>,
<code-inline>CAP_B[1]1_Rota1</code-inline> <br />
The first two are marked with <code-inline>A</code-inline> and will therefore be primary CAP units. They will be
scheduled and make up for the total count. <br />
Meaning that for this airbase there is 2 CAP units max at a time flying out. <br />
In this case all groups have <code-inline>[1]1</code-inline> in the name, (This would be the same as <code-inline>[1]A</code-inline>) which means
that when stage 1 is active the groups will activate and fly out to stage 1.
I also set up a few groups further back. One example: <code-inline>CAP_A[1-3]3_Group1</code-inline>. This group will
protect zone 3 when zones 1 through 3 are active.
CAP units fly out, fly their CAP zone for x amount of minutes and will then RTB. <br />
Before they actually RTB an event is triggered 10 minutes before the actual RTB task. This event
will trigger a backup unit to startup and fly out to take over. <br />
</p>
<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>
Creating CAP routes is not needed per se, but with a multi-stage stage (we have 2 stages with <code-inline>_1_</code-inline>) it is recommended. <br/>
Similarly with huge stages. <br/>
If there is multiple zones it will "round-robin" over them. <br/>
</p>
<p>
If no CAP route is present the unit will fly a route generated differently per zone: <br/>
<code-inline>quad zone</code-inline> => race-track between the corner closest to the origin airbase to the center point of the zone <br/>
<code-inline>circle zone</code-inline> => race-track between the closest point on circle to the origin airbase to the center <br/>
</p>
<p>
If you want to create your own CAP Routes you can! <br/>
For this example I created 2 CAP routes inside of the 2 <code-inline>_1_</code-inline> stages. <br/>
</p>
<p>
As you can see below there's a nice feature you can exploit. As long as the <code-inline>X</code-inline> of the zone is inside of the the <code-inline>CAPROUTE</code-inline> will be used for that stage!
</p>
<img src="../img/cap_routes.png" alt="CAP Routes Image" style="width: 100%;"></img>
<p>
Well, nice, we're done setting up the initial CAP effort. <br/>
If you want to change values for the CAP routes please read about how to configure it here: <a href="./Reference.html#cap-config">Cap Config</a>
</p>
<h2 id="mission-briefings">Mission Briefings</h2>
<p>
So now we've created some missions we also want to add briefings to them. This is pretty easy with Spearhead. <br/>
</p>
<p>
To do so click on <code-inline>draw</code-inline> on the left hand pane in the mission editor. This opens up the drawing tools in the editor. <br/>
On the right click <code-inline>TextBox</code-inline> and click somewhere inside the zone to which you want to add the briefing. <br/>
Give the briefing a name (It's not used, but can be nice to use to reference the briefing later) and add the briefing. <br/>
The text box is quite small, but can have a lot of text. Easiest is to edit the text in an editor of choice and paste it into the box afterwards. <br/>
</p>
<p>
Keep the binding layer to "Author" only. That way it doesn't show up for anyone other than in the mission editor.
</p>
<p>
See the two images below. The left shows the <code-inline>Text Box</code-inline> drawing. The right shows the briefing as shown in the mission.
</p>
<div style="display: flex">
<div style="flex: 50%">
<img src="../img/briefing_me.png"/>
</div>
<div style="flex: 50%">
<img src="../img/briefing_mission.png"/>
</div>
</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/>
</p>
</div>
</div>
</main>
<footer>
<p>&copy; 2025 Spearhead Project</p>
</footer>
</body>
</html>
+69
View File
@@ -0,0 +1,69 @@
<!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 Tutorials</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 {
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>Include the Spearhead Script</h1>
<h2 id="include-the-script">Include the Script</h2>
<p>
Download the latest version of Spearhead. You can choose which version you feel comfortable with. <br />
Versions that end in -rc are not stable. Versions that do not have the "release candidate" (rc) tag are. <br>
Stable does not mean bug-free, so if you find any issues let us know!
<br />
Here is the latest stable version:
<download-spearhead></download-spearhead>
</p>
<p>
Then run the script in the mission. <br />
Please do exactly as it's done below. <br />
</p>
<div style="width: 100%;">
<img style="width: 100%;" src="../img/script_install.png"></img>
</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>
<div class="next-steps">
<a href="/pages/first-start.html">Next: Getting started with Spearhead &rarr;</a>
</div>
</div>
</div>
</main>
<footer>
<p>&copy; 2025 Spearhead Project</p>
</footer>
</body>
</html>
+125
View File
@@ -0,0 +1,125 @@
<!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 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 {
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>Persistence</h1>
<p>
Spearhead comes with a custom Persistence option. <br />
It will even save burned out vehicles to give players a consistent battlefield even after the
restart. <br />
</p>
<p>
Most is pretty straightforward, however, some zones are somewhat special. <br />
Underneath you'll see all special zones listed.
</p>
<h2 id="settings">Settings</h2>
<p>
To see the full config check: <a href="./Reference.html#Configuration">Reference</a>
</p>
<h2 id="feedback">Feedback</h2>
<p>
Since this feature is still very much in development, please let any issues be known as soon as
possible and as concise as possible so a fix can be made quickly. <br />
Currently implemented is local file storage. <br />
If enough interest is expressed, cloud-based persistence would be possible. <br />
</p>
<pre>
<span class="lua-comment">... Rest of config</span>
<span class="lua-variable">Persistence</span> <span class="lua-operator">=</span> {
<span class="lua-comment">--- io and lfs cannot be sanitized in the MissionScripting.lua</span>
<span class="lua-comment">--- enables or disables the persistence logic in spearhead</span>
<span class="lua-variable">enabled</span> <span class="lua-operator">=</span> <span class="lua-keyword">false</span>,
<span class="lua-comment">--- sets the directory where the persistence file is stored
--- if nil then lfs.writedir() will be used.</span>
<span class="lua-variable">directory</span> <span class="lua-operator">=</span> <span class="lua-keyword">nil</span>,
<span class="lua-comment">--- the filename of the persistence file. Should end with .json for convention, but any text extension should do.</span>
<span class="lua-variable">fileName</span> <span class="lua-operator">=</span> <span class="lua-string">"Spearhead_Persistence.json"</span>
}
<span class="lua-comment">... Rest of config</span>
</pre>
<h2 id="basic-behavior">Basic Behavior</h2>
<p>
While playing the mission, Spearhead is keeping track of all units killed. <br />
These units are stored in memory internally and written to file. <br />
This happens every 2 minutes AND during the "onMissionStop" event to make sure the mission is as
up-to-date as possible without having to call IO methods on each event.<br />
</p>
<h2 id="misc-units">Misc Units</h2>
<p>
Miscellaneous units will follow basic behavior. <br />
These are units that are part of a stage but are not in a mission or airbase. <br />
These units will be replaced by a static "DEAD" unit after a mission restart at the location it was
killed. <br />
Due to blue units spawning afterwards, it's generally best to not have these units move through or
over areas where BLUESAMS and Airbase units will spawn after a stage completion.
</p>
<h2 id="missions">Missions</h2>
<p>
Missions follow the same logic as Misc Units. <br />
</p>
<h2 id="blue-sams">Blue SAMs</h2>
<p>
For Blue SAMs, due to placements easily overlapping between red and blue units within a BLUESAM
trigger zone, red units that overlap with blue units will be deleted. <br />
This will ensure that the blue units are placed as needed. <br />
</p>
<h2 id="airbases">Airbases</h2>
<p>
Airbase units will also be checked for overlap. As the blue units will be spawned after the RED
unit. <br />
Units that were alive when the stage was completed will be removed. Units that died will have
corpses spawned. <br />
</p>
<h2 id="warehouses">Warehouses</h2>
<p>
Currently, warehouses are not implemented and therefore warehouses are not persisted. <br />
When supply missions and logistics get implemented, warehouses will be persisted as well. <br />
</p>
</div>
</div>
</main>
<footer>
<p>&copy; 2025 Spearhead Project</p>
</footer>
</body>
</html>
+288
View File
@@ -0,0 +1,288 @@
<!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 Reference</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>Reference</h1>
<p>
This page provides a detailed overview of all settings, naming conventions, and logic used in
Spearhead. <br />
For a quick start guide, visit the <a href="./tutorials.html">Tutorials</a> page.
</p>
<h2 id="configuration">Configuration</h2>
<p>
For the configuration you can reference the configuration as it is below:
</p>
<pre>
@@CONFIG_CODE@@
</pre>
<h2 id="general-naming-conventions">Naming Conventions</h2>
<p>
Spearhead uses specific naming conventions for zones, missions, and CAP configurations. These
conventions are critical for the framework to function correctly.
</p>
<h3 id="stage-zones">Stage Zones</h3>
<p>
<strong>Format:</strong> <code-inline>MISSIONSTAGE_[OrderNumber]_[FreeForm]</code-inline>
<br />
<strong>Example:</strong> <code-inline>MISSIONSTAGE_1_EAST</code-inline>
</p>
<p>
Stages are logical parts of a mission. They encapsulate multiple missions, airbases, and other
objects. <br />
Secondary stages can be defined using the format: <code-inline>MISSIONSTAGE_x[OrderNumber]</code-inline>.
</p>
<h3 id="waiting-stages">Waiting Stages</h3>
<p>
<strong>Format:</strong> <code-inline>WAITINGSTAGE_[Order]_[Seconds]</code-inline> <br />
<strong>Example:</strong> <code-inline>WAITINGSTAGE_2_180</code-inline>
</p>
<p>
Waiting stages introduce delays between stages. They activate only after the previous stage is
completed.
</p>
<h3 id="mission-zones">Mission Zones</h3>
<p>
<strong>Format:</strong> <code-inline>MISSION_[Type]_[Name]</code-inline> <br />
<strong>Example:</strong> <code-inline>MISSION_DEAD_BYRON</code-inline>
</p>
<p>
Missions are completable objectives with specific types. <br />
Randomized missions can be defined using the format: <span
class="inline-lua"><span class="lua-variable">RANDOMMISSION_[Type]_[Name]_[Index]</span></span>.
</p>
<p>
Valid Types:
<ul>
<li>SAM</li>
<li>DEAD</li>
<li>BAI</li>
<li>CAS</li>
<li>STRIKE</li>
</ul>
</p>
<h3 id="cap-routes">CAP Routes</h3>
<p>
<strong>Format:</strong> <code-inline>CAPROUTE_[routeID]_[Name]</code-inline> <br />
<strong>Example:</strong> <code-inline>CAPROUTE_103_ALPHA</code-inline>
</p>
<p>
CAP routes define the patrol paths for CAP units. They are tied to specific stages and zones.
</p>
<h3 id="farp-zones">FARP Zones</h3>
<p>
<strong>Format:</strong> <code-inline>FARP_A|B_[Name]</code-inline> <br />
<strong>Example:</strong> <code-inline>FARP_A_HOUSTON</code-inline>
<strong>Example:</strong> <code-inline>FARP_B_HOUSTON</code-inline>
</p>
<p>
FARP zones are spawned and activated based on the "activation parameter". <br/>
A: Activates when the parent stage zone activates. <br/>
B: Activates when the parent stage is "Blue". <br/>
<br/>
Future: FARP zones will be "buildable" once logistics are implemented.
</p>
<h3 id="supply-hub">Supply Hub</h3>
<p>
<strong>Format:</strong> <code-inline>SUPPLYHUB_[Name]</code-inline> <br />
<strong>Format:</strong> <code-inline>SUPPLYHUB_A_[Name]</code-inline> <br />
<strong>Example:</strong> <code-inline>SUPPLYHUB_HOUSTON</code-inline> <br/> <br/>
Supply hubs are used as zones to pick up logistics for logistic missions. <br/>
They are activated when their parent is activated. <br/>
Activated depends on what it's parent is. <br/>
If it's a stage. It can be SUPPLYHUB_A_[name] when you want it to be active when the stage is initiated. (red state) or SUPPLYHUB_B_ it you want it activated when it's BLUE only. <br/>
If you place the Supply Hub trigger zone inside of a FARP zone the supply hub will activate when the FARP zone is activated. <br/>
If the FARP needs to be built first, then that means the supply hub also is activated later. <br/>
NOTE: The supply hub does not spawn anything, but merely give options to pick up logistics.
</p>
<h2 id="mission-types">Mission Types</h2>
<p>
Each mission type has specific logic and completion criteria. Below are the supported types:
</p>
<h3 id="sam">SAM</h3>
<p>
SAM missions involve surface-to-air missile sites. These missions are activated when a stage is
"Pre-Active." <br />
<strong>Completion Logic:</strong> Destroy all air defenses in the zone.
</p>
<h3 id="dead">DEAD</h3>
<p>
DEAD missions target enemy air defenses. They are activated at the start of a stage. <br />
<strong>Completion Logic:</strong> Destroy all designated targets in the zone.
</p>
<h3 id="bai">BAI</h3>
<p>
BAI missions involve battlefield air interdiction. These missions target enemy ground forces.
</p>
<h3 id="cas">CAS</h3>
<p>
CAS missions involve close air support for friendly ground forces. <br />
CAS mission have a special "BattleManager" module added by defualt. This will make blue and red units shoot at eachoter. <br/>
This creates a nice effect. <br/>
To see how it's done see: <a href="./tutorials.html#mission-cas">[Tutorials] Mission: CAS</a>
</p>
<h3 id="strike">STRIKE</h3>
<p>
STRIKE missions target strategic objectives, such as supply depots or command centers. <br />
<strong>Completion Logic:</strong> Destroy all designated targets in the zone.
</p>
<h2 id="mission-briefings">Mission Briefings</h2>
<p>
Mission briefings are text boxes (draw layer) inside of the trigger zone of the mission. <br />
</p>
<h3 id ="special-fields">Special Fields</h3>
<p>
Special fields are used to display information in the mission briefings. <br />
They are replaced with the corresponding values at the time a briefing is requested and can therefore show real time data. <br/>
<strong>Field:</strong> <code-inline>{{coords}}</code-inline><br/>
Coords are taken from the location of the trigger zone. Then converted to the aircrafts preferred format. <br/>
</p>
<h2 id="cap-configuration">CAP Configuration</h2>
<p>
CAP (Combat Air Patrol) units are managed using specific naming conventions and configurations.
</p>
<h3 id="cap-group-naming">CAP Group Naming</h3>
<p>
<strong>Format:</strong> <code-inline>CAP_[A|B][Config]_[FreeForm]</code-inline> <br />
<strong>Example:</strong> <code-inline>CAP_A[1-4]5_SomeName</code-inline>
</p>
<h3 id="cap-group-config">CAP Group Config</h3>
<code-block>
<span class="lua-comment">1 at x:</span> <span class="lua-variable">[&lt;activeStage&gt;]&lt;CapRouteID&gt;</span>
<span class="lua-comment">n and n at x:</span> <span class="lua-variable">[&lt;activeStage&gt;,&lt;activeStage&gt;]&lt;CapRouteID&gt;</span>
<span class="lua-comment">n till n at x:</span> <span class="lua-variable">[&lt;activeStage&gt;-&lt;activeStage&gt;]&lt;CapRouteID&gt;</span>
<span class="lua-comment">n till n and n at x:</span> <span class="lua-variable">[&lt;activeStage&gt;-&lt;activeStage&gt;,&lt;activeStage&gt;]&lt;CapRouteID&gt;</span>
<span class="lua-comment">Divider: |</span>
<span class="lua-comment">Examples:</span>
<span class="lua-variable">CAP_A[1-4,6]7|[5,7]8_SomeName</span> <span class="lua-comment">=&gt; Will fly CAP at stage 7 when stages 1 through 4 and 6 are active and will fly CAP at 8 when 5 and 7 are active.</span>
<span class="lua-variable">CAP_A[2-5]5|[6]6_SomeName</span> <span class="lua-comment">=&gt; Will fly CAP at stage 5 when stages 2 through 5 are active and will fly CAP at CapRoute 6 when 6 is active.</span>
</code-block>
<h3 id="active-and-backup-cap">Active and Backup CAP</h3>
<p>
<strong>Active Units:</strong> Define the maximum number of groups in a zone at a time. <br />
<strong>Backup Units:</strong> Fill in when active units are unavailable due to RTB, death, or
rearming.
</p>
<h2 id="randomization">Randomization</h2>
<p>
Missions can be randomized using the <span class="inline-lua"><span class="lua-variable">RANDOMMISSION</span></span> prefix. <br />
Spearhead will pick one random mission from zones with the same name.
</p>
<h2 id="in-stage-dependencies">In stage depencies</h2>
<p>
If you want to have missions inside of a stage depends on each other you can. <br/>
Add a text drawing box inside of the mission trigger zone with the name:
<code-inline>dependson_[freeform]</code-inline> <br/>
And in the text add the "Name" of the mission zone you want the mission to depend on. <br/>
The mission will be "pre-activated" (spawned) but won't have a f10 mission menu until the mission(s) it depends on is completed. <br/>
</p>
<h2 id="buildable">Buildables</h2>
<p>
Current buildables: <br/>
<code-inline>FARP_</code-inline> <br/>
<code-inline>BLUESAM_</code-inline> <br/>
<code-inline>Airbases</code-inline> <br/>
<br/>
Buildables are zones or objects that first require logistics. <br/>
Eg. A forward SAM site or FARP requires logistic crates before it can be built. <br/>
<br>
To make a zone buildable (farp, or bluesam): Select triggerzone -> below the property box hit "Add" -> Property Name should be <code-inline>buildable</code-inline> -> Value should be the kilos required eg. <code-inline>8000</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/>
<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/>
All helicopters have a max limit set based on quick google searches and cannot be changed but the ME at this point: <br/>
UH-1H : 2000kg <br/>
Mi-8 : 4000kg <br/>
CH-47 : 10000kg <br/>
MI-24 : 2000kg <br/>
</p>
<h2>Runway Bombing</h2>
<p>
Runway bombing can be a very effective OCA tactic. With Spearhead we've tried adding as much logic and detail to it so it will feel as engaging as possible. <br/>
Firstly, the runway is split up in 5 sections and only sections 2, 3 and 4 are then actually tracked. <br/>
<span style="font-family: Consolas, Monaco, 'Lucida Console', monospace;">
+-----------+-----------+-----------+-----------+-----------+ <br/>
| Section 1 | Section 2 | Section 3 | Section 4 | Section 5 | <br/>
+-----------+-----------+-----------+-----------+-----------+ <br/>
</span>
</p>
</div>
</div>
</main>
<footer>
<p>&copy; 2025 Spearhead Project</p>
</footer>
</body>
</html>
+60
View File
@@ -0,0 +1,60 @@
<!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 API</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>
<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>Spearhead API</h1>
<div class="note">
<p><strong>NOTE:</strong> The Spearhead.API space is only released in the Beta branch at the moment.</p>
</div>
<h2 id="introduction">Introduction</h2>
<p>
The <span class="inline-lua"><span class="lua-variable">Spearhead.API</span></span> space is specifically created to make sure mission makers can interact with the framework.
</p>
<p>
Simply alter logic, get the current state in Spearhead, and give the whole Mission Editor more control.
</p>
<p>
For example, late activate the entire framework by calling <span class="inline-lua"><span class="lua-function">Spearhead.API.Stages.changeStage</span>(<span class="lua-variable">1</span>)</span> later or on demand and setting the starting config stage to -1 in the Spearhead configuration file.
</p>
<h2 id="stages">Stages</h2>
<pre>
@@API_CODE@@
</pre>
</div>
</div>
</main>
<footer>
<p>&copy; 2025 Spearhead Project</p>
</footer>
<style>
.side-nav a.active {
font-weight: bold;
color: #4fc3f7;
}
</style>
</body>
</html>
+294
View File
@@ -0,0 +1,294 @@
<!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 Tutorials</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 {
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>Tutorials</h1>
<p>
This guide is to get you started building your first Spearhead mission. <br />
Spearhead was created to enable the mission maker to worry as little about the running, timing and scripting
and most about the setting and looks and feel of the mission. <br />
In the example we'll show how you can create a simple island hopping mission
</p>
<h2 id="include-the-script">Include the Script</h2>
<p>
Download the latest version of Spearhead. You can choose which version you feel comfortable with. <br />
Versions that end in -rc are not stable. Versions that do not have the "release candidate" (rc) tag are.
<br />
<a style="text-decoration: underline;" target="_blank" href="https://github.com/dutchie031/Spearhead/releases">See All releases here</a>
</p>
<p>
Then run the script in the mission. <br />
Please do exactly as it's done below. <br />
</p>
<div style="width: 100%;">
<img style="width: 100%;" src="../img/script_install.png"></img>
</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>
<p>
So first of all think about the stages.<br />
These are logically ordered zones that will activate one by one based on the mission status in them. <br />
There is a little more to it, but you'll find out. <br />
Stages need to be named according to the convention: <code-inline>MISSIONSTAGE_[OrderNumber]_[FreeForm]</code-inline> <br />
The first stage will be called <code-inline>MISSIONSTAGE_1</code-inline> or <code-inline>MISSIONSTAGE_1_EAST</code-inline> for example. <br />
<br /><br />
Stages are divided in primary stages: <code-inline>MISSIONSTAGE_[number]</code-inline> <br />
or secondary stages: <code-inline>MISSIONSTAGE_x[number]</code-inline> <br />
Note the x before the number. This marks it as "extra"
</p>
<p>
For this mission we started with the three stages: <code-inline>MISSIONSTAGE_1_GROUND</code-inline>,
<code-inline>MISSIONSTAGE_2_WATER</code-inline> and <code-inline>MISSIONSTAGE_1_AIRBASE</code-inline> as you see in the image.
</p>
<img src="../img/starting_stages.png" style="width: 100%;"></img>
<p>
<code-inline>MISSIONSTAGE_1_GROUND</code-inline>will be activated when the mission starts <br />
<code-inline>MISSIONSTAGE_1_WATER</code-inline> will be also be activated <br />
<code-inline>MISSIONSTAGE_2_AIRBASE</code-inline> will be activated when the player enters the zone.
<br />
</p>
<h2 id="setting-up-cap">Setting up CAP</h2>
<p>
If you don't want to use the CAP managers withing Spearhead you can skip this and continue to <a href="#setting-up-the-missions">Setting up Missions</a>. <br />
However CAP is one of the painpoints in a lot of missions and setting up a dynamic feeling airspace can be
quite the challenge. <br />
With the CAP managers we've tried to make this a lot easier. <br />
A CAP group needs to follow the following naming convention: <code-inline>CAP_[A|B][CONFIG]_[Free Form]</code-inline>
For details on config read this: <code-inline>CAP Group Config</code-inline>
For now I set up 3 groups with the following names. <code-inline>CAP_A[1]1_Rota1</code-inline>, <code-inline>CAP_A[1]1_Rota1-1</code-inline>,
<code-inline>CAP_B[1]1_Rota1</code-inline> <br />
The first two are marked with <code-inline>A</code-inline> and will therefore be primary CAP units. They will be
scheduled and make up for the total count. <br />
Meaning that for this airbase there is 2 CAP units max at a time flying out. <br />
In this case all groups have <code-inline>[1]1</code-inline> in the name, (This would be the same as <code-inline>[1]A</code-inline>) which means
that when stage 1 is active the groups will activate and fly out to stage 1.
I also set up a few groups further back. One example: <code-inline>CAP_A[1-3]3_Group1</code-inline>. This group will
protect zone 3 when zones 1 through 3 are active.
CAP units fly out, fly their CAP zone for x amount of minutes and will then RTB. <br />
Before they actually RTB an event is triggered 10 minutes before the actual RTB task. This event
will trigger a backup unit to startup and fly out to take over. <br />
</p>
<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>
Creating CAP routes is not needed per se, but with a multi-stage stage (we have 2 stages with <code-inline>_1_</code-inline>) it is recommended. <br/>
Similarly with huge stages. <br/>
If there is multiple zones it will "round-robin" over them. <br/>
</p>
<p>
If no CAP route is present the unit will fly a route generated differently per zone: <br/>
<code-inline>quad zone</code-inline> => race-track between the corner closest to the origin airbase to the center point of the zone <br/>
<code-inline>circle zone</code-inline> => race-track between the closest point on circle to the origin airbase to the center <br/>
</p>
<p>
If you want to create your own CAP Routes you can! <br/>
For this example I created 2 CAP routes inside of the 2 <code-inline>_1_</code-inline> stages. <br/>
</p>
<p>
As you can see below there's a nice feature you can exploit. As long as the <code-inline>X</code-inline> of the zone is inside of the the <code-inline>CAPROUTE</code-inline> will be used for that stage!
</p>
<img src="../img/cap_routes.png" alt="CAP Routes Image" style="width: 100%;"></img>
<p>
Well, nice, we're done setting up the initial CAP effort. <br/>
If you want to change values for the CAP routes please read about how to configure it here: <a href="./Reference.html#cap-config">Cap Config</a>
</p>
<h2 id="setting-up-the-missions">Setting up Missions</h2>
<p>
Now the part where you as a mission maker can really get into the nitty gritty. <br/>
Missions are managed and monitored by Spearhead. <br/>
Statics, groups and single units all alike. <br/>
</p>
<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 <code-inline>DEAD</code-inline> mission and will consist of an SA-2 site with an additional "control center".
</p>
<h3 id="mission-1-dead">Mission: DEAD</h3>
<p>
As you can see on the left image the template of the SA-2 was placed. Then dragged around to only face south. <br/>
An additional track radar and search radar was added and all launchers were surrounded by sandbags. <br/>
On top of this there was a sort of control center added with walls, vehicles and some tents.
</p>
<div style="display: flex; width: 100%;">
<div style="flex: 50%">
<img style="width: 100%;" src="../img/sa2_mission_editor.png"/>
</div>
<div style="flex: 50%">
<img style="width: 100%;" src="../img/sa2_result.png"/>
</div>
</div>
<p>
Important to note. It's all inside the triggerzone <code-inline>MISSION_DEAD_BYRON</code-inline>. Which means it's a <code-inline>MISSION</code-inline> of type <code-inline>DEAD</code-inline> and with name <code-inline>BYRON</code-inline>. <br/>
At the start Spearhead will detect the triggerzone, take all units and despawn them and only spawn when needed for better performance. <br/>
</p>
<p>
The current list of mission types are:
<code-inline>DEAD</code-inline>
<code-inline>STRIKE</code-inline>
<code-inline>BAI</code-inline>
<code-inline>SAM</code-inline> <br/>
For specific differences, please check the reference page.
</p>
<p>
Each type has some additional completion logic to it. <br/>
<code-inline>DEAD</code-inline> and <code-inline>SAM</code-inline> missions will be marked complete when all air defences are destroyed. This includes Tracking Radars, Self tracking launchers and AAA guns if they are inside the zone. <br/>
If you want to add the Search radar or another random unit like the command tent to the target list you can add a <code-inline>TGT_</code-inline> prefix to the unit or group you want destroyed. <br/>
Please be aware that adding <code-inline>TGT_</code-inline> to a group will make the entire group a target and therefore each unit needs to be destroyed. <br/>
</p>
<h3 id="mission-2-strike">Mission: STRIKE</h3>
<p>
To show the power of <code-inline>TGT_</code-inline> targets I'll create a strike mission next.
</p>
<p>
A nice supply strike mission will do. Add a ship, some containers and some additional units. <br/>
Even some SHORADS to spice the whole thing up. <br/>
In the picture below all units that are selected and who show up as white (they are actually red) have the prefix <code-inline>TGT_</code-inline> in front of their name. <br/>
</p>
<p>
This will make it so the mission will be marked as complete when those units are destroyed. The rest of the units will exist until the entire stage is cleaned up. <br/>
</p>
<img src="../img/strike_target.png"/><br/>
<h3 id="mission-cas">Mision: CAS</h3>
<p>
A CAS mission will add an additional layer to the mission. <br/>
Whereas BAI, Strike and DEAD missions are somewhat clean, CAS missions are where the chaos starts. <br/>
<br/>
With CAS missions an additional BattleManager will be activated. <br/>
This BattleManager will scan for units and force them to fire at other units in the zone. <br/>
Great efforts are made to make AI miss. In the end we want players to be they key factor to mission success. <br/>
If AI do seem to be hitting more than 1 or 2 unfortunate targets, it is most likely you either have another script controlling the units, or you have placed the units too staggered. <br/>
Please make sure to also read the "Notes" section below the image. <br/>
<img src="../img/cas_mission.png" style="width: 100%;"></img>
Notes: <br/>
<ul>
<li>
AAA units will sometimes also follow their lead and fire at targets on the ground.<br/>
They will however always prioritize AIR targets as at that point they have a threat themselves and won't follow their lead's targets. <br/>
If you really don't like this, it's best to put the AAA units in a separate group.
</li>
<li>
Units will purposely aim past a target. <br/>
This simulates a fight, but doesn't actually do anything, so the player is the only person that can complete a mission. <br/>
Be aware that if you stagger groups too much, shooting past a target might hit another. <br/>
Best would be to always test it. <br/>
You can also enable debugging. <br/>
This will draw all boxes and lines. <br/>
NOTE: Keep DEBUG disabled on an actual mission.
</li>
<li>
When you want AI to shoot through gaps a distance of approx 150ft is required between the two outer units. <br/>
These gaps will be automatically detected and can even exist within a group itself. <br/>
</li>
<li>
Line of sight is important, dependent on which type of unit. <br/>
Units won't shoot when they can't fire at a certain point. <br/>
As a mission editor it's up to you to set the range, line of sight and cover. <br/>
</li>
<li>
As Performance is something that's on any multiplayer mission maker's mind, this is a very optimised way.
AI does not have to know, scan, priorise targets every second. <br/>
It does not really have to think or move. <br/>
Ofcourse, there is a balance and too many can be too much. <br/>
For now it has been tested with 60+ units in 3 seperate CAS zones active at a time. <br/>
No performance degradation was seen at all. <br/>
</li>
</ul>
</p>
<h2 id="mission-briefings">Mission Briefings</h2>
<p>
So now we've created some missions we also want to add briefings to them. This is pretty easy with Spearhead. <br/>
</p>
<p>
To do so click on <code-inline>draw</code-inline> on the left hand pane in the mission editor. This opens up the drawing tools in the editor. <br/>
On the right click <code-inline>TextBox</code-inline> and click somewhere inside the zone to which you want to add the briefing. <br/>
Give the briefing a name (It's not used, but can be nice to use to reference the briefing later) and add the briefing. <br/>
The text box is quite small, but can have a lot of text. Easiest is to edit the text in an editor of choice and paste it into the box afterwards. <br/>
</p>
<p>
Keep the binding layer to "Author" only. That way it doesn't show up for anyone other than in the mission editor.
</p>
<p>
See the two images below. The left shows the <code-inline>Text Box</code-inline> drawing. The right shows the briefing as shown in the mission.
</p>
<div style="display: flex">
<div style="flex: 50%">
<img src="../img/briefing_me.png"/>
</div>
<div style="flex: 50%">
<img src="../img/briefing_mission.png"/>
</div>
</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/>
</p>
</div>
</div>
</main>
<footer>
<p>&copy; 2025 Spearhead Project</p>
</footer>
</body>
</html>
+3
View File
@@ -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}
+806
View File
@@ -0,0 +1,806 @@
: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-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-note-dark: rgba(40, 35, 30, 0.85); /* Slightly darker note box */
--color-text-primary: #a5a5a5; /* Brighter text */
--color-text-secondary: #bfc8d8; /* Lighter secondary text */
--color-text-accent: #7ecbff; /* Brighter accent */
--color-text-light: #ffffff;
--color-border-primary: #23263a; /* Stronger border for contrast */
--color-border-accent: #3a7bd5; /* Brighter accent border */
--color-link: #7ecbff;
--color-link-hover: #ffffff;
/* 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);
--shadow-large: 0 4px 12px rgba(0, 0, 0, 0.6); /* Common opacity values */
--opacity-sidenav: 0.85; /* Increased opacity for better contrast */ /* Font families with improved Inter setup */
--font-family-primary: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-family-code: 'JetBrains Mono', 'Monaco', 'Cascadia Code', monospace;
--font-family-heading: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
/* Responsive margins - unchanged */
--margin-left: 20%;
--margin-xlarge: 10%;
--margin-large: 8%;
--margin-medium: 8%;
--margin-small: 5%;
/* Scrollbar colors */
--color-scrollbar-track: rgba(20, 22, 28, 0.8);
--color-scrollbar-thumb-start: #4c5260;
--color-scrollbar-thumb-end: #323642;
--color-scrollbar-thumb-hover-start: #5a606e;
--color-scrollbar-thumb-hover-end: #4c5260;
--color-scrollbar-shadow: rgba(90, 96, 110, 0.3);
--color-scrollbar-shadow-hover: rgba(90, 96, 110, 0.5);
/* New variables for gradients, shadows, and navigation accent */
--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;
--color-heading-h3: #c9e6ff;
--color-heading-h1-border: #8fc6ff;
--color-heading-h2-border: #b3e0ff;
}
[data-theme="light"] {
--color-bg-primary: #e0e0e0;
--color-bg-secondary: #b6b6b6;
--color-bg-tertiary: #b6b6b6;
--color-bg-header: #d3d3d3;
--color-bg-note: rgba(215, 228, 245, 0.75);
--color-bg-hover: rgba(100, 120, 200, 0.1);
--color-bg-note-dark: rgba(235, 240, 250, 0.7);
--color-text-primary: #151720;
--color-text-secondary: #2a2e40;
--color-text-accent: #3050b0;
--color-text-light: #2a2e40;
--color-border-primary: #496cb3;
--color-border-accent: #1d46ee;
--color-link: #3050b0;
--color-link-hover: #4060c0;
/* 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);
--shadow-large: 0 4px 12px rgba(0, 0, 0, 0.25);
/* Common opacity values */
--opacity-sidenav: 0.92;
/* Scrollbar colors */
--color-scrollbar-track: rgba(220, 225, 240, 0.8);
--color-scrollbar-thumb-start: #5c75d9;
--color-scrollbar-thumb-end: #a0b0e0;
--color-scrollbar-thumb-hover-start: #3050b0;
--color-scrollbar-thumb-hover-end: #5c75d9;
--color-scrollbar-shadow: rgba(48, 80, 176, 0.2);
--color-scrollbar-shadow-hover: rgba(48, 80, 176, 0.4);
/* New variables for gradients, shadows, and navigation accent */
--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: #f5e9d2;
--color-logo-gradient: linear-gradient(120deg, #225a9e, #3578c7, #70aaff);
--color-nav-underline: #e0cfa0;
--color-nav-underline-glow: rgba(224, 207, 160, 0.4); --color-nav-hover-bg: var(--color-bg-hover);
--color-nav-hover-text: var(--color-text-primary);
--color-nav-hover-shadow: 0 4px 8px rgba(48, 80, 176, 0.15);
/* Heading colors for light theme */
--color-heading-h1: #225a9e;
--color-heading-h2: #3578c7;
--color-heading-h3: #4a90e2;
--color-heading-h1-border: #225a9e;
--color-heading-h2-border: #3578c7;
}
html {
background-color: var(--color-bg-primary);
height: 100%;
scroll-behavior: smooth;
}
body {
color: var(--color-text-primary);
font-family: var(--font-family-primary);
margin: 0;
padding: 0;
height: 100%;
display: flex;
flex-direction: column;
font-size: 16px;
line-height: 1.65; /* Increased for better Inter readability */
font-weight: 500; /* Medium weight for substantial feel */
letter-spacing: 0.015em; /* Slightly more spacing for better readability */
font-feature-settings: "cv02", "cv03", "cv04", "cv11"; /* Better Inter alternates */
transition: background-color 0.3s ease, color 0.3s ease;
}
a {
color: var(--color-link);
text-decoration: none;
}
a:hover {
color: var(--color-link-hover);
text-decoration: underline;
}
header {
background: var(--gradient-header);
box-shadow: var(--shadow-medium);
}
.header-box {
padding: 1em;
margin-left: var(--margin-left);
min-height: 5em;
display: flex;
flex-direction: row;
justify-content: start;
align-items: center;
margin-bottom: 0%;
position: relative;
padding: 0 0;
}
.header-right {
width: auto;
margin-left: 10em;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 2.5em;
font-weight: 600;
background: var(--color-logo-gradient);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
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);
filter: brightness(1.1);
text-decoration: none;
}
header nav {
display: flex;
flex-direction: row;
align-items: center;
gap: 1.2rem;
}
nav {
display: flex;
align-items: center;
}
header nav a {
position: relative;
color: var(--color-text-primary);
font-weight: 500;
padding: 0.6em 1em;
border-radius: 6px;
transition: all 0.25s ease-in-out;
letter-spacing: 0.02em;
}
header nav a:hover {
color: var(--color-nav-hover-text);
background-color: var(--color-nav-hover-bg);
transform: translateY(-2px);
box-shadow: var(--color-nav-hover-shadow);
text-decoration: none;
}
header nav a::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 2px;
background-color: var(--color-nav-underline);
transition: width 0.3s cubic-bezier(0.4,0,0.2,1), box-shadow 0.3s, background-color 0.3s;
transform: translateX(-50%);
box-shadow: 0 0 6px var(--color-nav-underline-glow);
pointer-events: none;
}
header nav a:hover::after {
width: 80%;
}
.dropdown {
position: relative;
display: flex;
flex-wrap: wrap;
flex-direction: column;
}
.dropdown-content {
display: flex;
flex-direction: column;
position: absolute;
overflow: hidden;
top: 100%; /* Position below the dropdown */
left: 0; /* Align left edge */
min-width: 160px;
background-color: var(--color-bg-secondary);
box-shadow: var(--shadow-medium);
z-index: 1;
border-radius: 8px;
max-height: 0px;
transition: all 1s ease;
}
.dropdown:hover .dropdown-content {
max-height: 100vh;
}
header.collapsible {
cursor: pointer;
position: relative;
}
header.collapsible::after {
content: '\25BC'; /* Downward arrow */
position: absolute;
right: 0;
font-size: 0.8em;
color: var(--color-text-light);
}
header.collapsible.collapsed::after {
content: '\25B6'; /* Rightward arrow */
}
h2.collapsible, h3.collapsible {
cursor: pointer;
position: relative;
}
h2.collapsible::before, h3.collapsible::before {
content: '';
margin-right: 0;
width: 0;
display: none;
}
h2.collapsible.collapsed::before, h3.collapsible.collapsed::before {
content: '';
display: none;
}
footer {
padding-left: var(--margin-left);
background-color: var(--color-bg-header);
color: var(--color-text-primary);
padding-top: 1em;
padding-bottom: 1em;
}
main {
flex: 1;
position: relative;
}
/* Side navigation styles */
.reference-container {
display: block; /* Change from flex since sidebar is now fixed */
margin-top: 2em;
margin-left: var(--margin-left);
position: relative;
/* Ensure the container doesn't interfere with sticky positioning */
overflow: visible;
height: auto;
}
.side-nav {
width: 200px;
min-width: 200px;
padding-left: 0;
overflow-y: auto;
border-right: none;
position: fixed;
top: 7em; /* Account for header height (5em) + spacing (2em) */
left: var(--margin-left);
max-height: calc(100vh - 9em); /* Account for header + top spacing */
z-index: 10;
/* Ensure proper stacking context */
transform: translateZ(0);
will-change: transform;
}
.side-nav ul {
list-style-type: none;
padding-left: 0.5em;
margin-top: 0.5em;
}
.side-nav li {
margin-bottom: 0.5em;
}
.side-nav a {
color: var(--color-text-primary);
text-decoration: none;
padding: 0.5em 1em;;
}
.side-nav a:hover, .side-nav a.active {
background: linear-gradient(90deg, var(--color-border-accent) 0 6px, var(--color-bg-hover) 6px 100%);
font-weight: bold;
border-radius: 4px;
color: var(--color-link-hover);
text-decoration: none;
transition: background 0.2s, color 0.2s;
}
.side-nav a.active {
background: linear-gradient(90deg, var(--color-border-accent) 0 6px, var(--color-bg-hover) 6px 100%);
font-weight: bold;
border-radius: 4px;
color: var(--color-link-hover);
text-decoration: none;
transition: background 0.2s, color 0.2s;
}
.side-nav-h2 {
color: var(--color-text-primary);
font-weight: bold;
}
.side-nav-h3 {
color: var(--color-text-primary);
padding-left: 1em;
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%;
margin-left: 220px; /* Account for fixed sidebar width + gap */
border-left: 2px solid var(--color-border-accent);
padding-left: 1.5em;
padding-bottom: 20vh; /* Allow last section to scroll to top */
}
/* Heading styles */
h1, h2, h3, h4, h5, h6 {
margin-top: 1.5em;
margin-bottom: 0.8em;
color: var(--color-text-secondary);
font-family: var(--font-family-heading);
font-weight: 600;
line-height: 1.3;
}
h1 {
font-size: 2.2em;
color: var(--color-heading-h1);
font-weight: 700;
text-shadow: 0 1px 4px rgba(143,198,255,0.08);
letter-spacing: 0.01em;
border-bottom: 1.5px solid var(--color-heading-h1-border);
padding-bottom: 0.3em;
}
h2 {
font-size: 1.8em;
color: var(--color-heading-h2);
font-weight: 600;
text-shadow: 0 1px 2px rgba(179,224,255,0.07);
letter-spacing: 0.005em;
border-bottom: 1px solid var(--color-heading-h2-border);
padding-bottom: 0.2em;
}
h3 {
font-size: 1.4em;
color: var(--color-heading-h3);
font-weight: 600;
text-shadow: 0 1px 1px rgba(201,230,255,0.06);
letter-spacing: 0.005em;
}
h4 {
font-size: 1.2em;
}
p {
margin-bottom: 1.2em;
}
/* List styling for better readability */
ul, ol {
margin: 1em 0 1.5em 0;
padding-left: 2em;
}
li {
margin-bottom: 0.5em;
}
/* Add better spacing between sections */
main > section {
margin-bottom: 2em;
}
/* Improve table styling if tables are used */
table {
border-collapse: collapse;
width: 100%;
margin: 1.5em 0;
overflow-x: auto;
display: block;
}
th, td {
padding: 0.75em 1em;
border: 1px solid var(--color-border-primary);
}
th {
background-color: var(--color-bg-secondary);
font-weight: bold;
}
/* Note boxes */
.note {
background: linear-gradient(to right, rgba(60, 50, 40, 0.7), rgba(50, 45, 40, 0.55));
border-left: 4px solid var(--color-border-accent);
padding: 1em 1.2em;
margin: 1.8em 0;
border-radius: 0 8px 8px 0;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
}
.note p:last-child {
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%;
height: auto;
border-radius: 4px;
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;
height: 12px;
}
::-webkit-scrollbar-track {
background: var(--color-scrollbar-track);
border-radius: 10px;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.4);
}
::-webkit-scrollbar-thumb {
background: linear-gradient(to bottom, var(--color-scrollbar-thumb-start), var(--color-scrollbar-thumb-end));
border-radius: 10px;
border: 2px solid var(--color-scrollbar-track);
box-shadow: 0 0 3px var(--color-scrollbar-shadow);
transition: all 0.3s ease;
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(to bottom, var(--color-scrollbar-thumb-hover-start), var(--color-scrollbar-thumb-hover-end));
border-width: 1px;
box-shadow: 0 0 5px var(--color-scrollbar-shadow-hover);
}
::-webkit-scrollbar-corner {
background: var(--color-bg-header);
}
/* Firefox scrollbar styling */
* {
scrollbar-width: thin;
scrollbar-color: var(--color-border-accent) var(--color-scrollbar-track);
}
/* Responsive layout adjustments */
@media screen and (max-width: 1500px) {
:root {
--margin-left: var(--margin-xlarge);
}
}
/* Responsive layout adjustments */
@media screen and (max-width: 1200px) {
:root {
--margin-left: var(--margin-large);
}
}
@media screen and (max-width: 900px) {
:root {
--margin-left: var(--margin-small);
}
.side-nav {
display: none;
}
.content-wrapper {
margin-left: 0;
max-width: 100%;
}
.reference-container {
margin-left: var(--margin-small);
}
}
/* Mobile responsive styles */
@media screen and (max-width: 768px) {
:root {
--margin-left: 1em;
}
.side-nav {
display: none; /* Hide side navigation on mobile devices */
}
.content-wrapper {
width: 100%; /* Make content take full width */
margin-left: 0;
max-width: 100%;
}
.reference-container {
margin-left: 1em;
}
main {
margin-left: 5%;
margin-right: 5%;
padding-left: 5%;
padding-right: 5%;
}
header {
padding-left: 5%;
padding-right: 5%;
flex-direction: column;
align-items: center;
min-height: auto;
padding-top: 1em;
padding-bottom: 1em;
}
header nav {
margin-left: 0;
margin-top: 1em;
flex-wrap: wrap;
justify-content: center;
}
}
.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;
border: none;
color: var(--color-text-primary);
cursor: pointer;
font-size: 1.2rem;
margin-left: 15px;
padding: 5px;
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
background-color: var(--color-bg-tertiary);
transition: all 0.3s ease;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.theme-toggle:hover {
background-color: var(--color-bg-hover);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(100, 130, 255, 0.3);
}
.theme-toggle svg {
width: 20px;
height: 20px;
fill: var(--color-text-primary);
transition: all 0.3s ease;
}
.theme-toggle:hover svg {
fill: var(--color-text-accent);
}