custom drawings and animations for video

This commit is contained in:
2025-12-28 14:35:31 +01:00
parent c027921527
commit ca82c3357d
16 changed files with 5500 additions and 72 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+721
View File
@@ -0,0 +1,721 @@
{
"type": "excalidraw",
"version": 2,
"source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor",
"elements": [
{
"id": "HiVK1huIbL16ZCy3rz5t8",
"type": "rectangle",
"x": 1622.7441591132324,
"y": 984.6777917946865,
"width": 1578.7433546522943,
"height": 836.7851776620863,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2L",
"roundness": {
"type": 3
},
"seed": 645107296,
"version": 227,
"versionNonce": 1642809248,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false
},
{
"id": "lTQAmT-q3EhB8l4OFoV4r",
"type": "rectangle",
"x": 2308.0518724509566,
"y": 1544.7916707023237,
"width": 211.02211156486123,
"height": 96.5527520526872,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2M",
"roundness": {
"type": 3
},
"seed": 28492384,
"version": 449,
"versionNonce": 702648928,
"isDeleted": false,
"boundElements": [
{
"id": "23RNiECmee0GZfyG42WJU",
"type": "arrow"
},
{
"id": "AS-wLfqmrJICLegtTjwKA",
"type": "arrow"
}
],
"updated": 1766927996013,
"link": null,
"locked": false
},
{
"id": "9aRWFt8Ol6hDDEvOstOxf",
"type": "text",
"x": 2340.7060558234443,
"y": 1563.9495282342684,
"width": 154.26666259765625,
"height": 55.62747030529269,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2N",
"roundness": null,
"seed": 384530016,
"version": 204,
"versionNonce": 2083905440,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"text": "Mission",
"fontSize": 44.50197624423415,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Mission",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "23RNiECmee0GZfyG42WJU",
"type": "arrow",
"x": 2519.9815623115946,
"y": 1608.9632387014556,
"width": 185.6851361459012,
"height": 84.58953522783993,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2O",
"roundness": {
"type": 2
},
"seed": 1793960544,
"version": 1436,
"versionNonce": 1331590752,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
132.75987383519941,
-15.5442452755197
],
[
185.6851361459012,
-84.58953522783993
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "lTQAmT-q3EhB8l4OFoV4r",
"focus": 0.46767568934546344,
"gap": 1
},
"endBinding": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
},
{
"id": "K6jAODe27_os5DPh8967u",
"type": "text",
"x": 2689.444812908036,
"y": 1465.552661128815,
"width": 222.53535431824844,
"height": 54.779302679674046,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2P",
"roundness": null,
"seed": 1753833056,
"version": 808,
"versionNonce": 1186625440,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"text": "The Story",
"fontSize": 43.823442143739236,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "The Story",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "AS-wLfqmrJICLegtTjwKA",
"type": "arrow",
"x": 2384.330334167984,
"y": 1544.3197414150743,
"width": 49.364397989861686,
"height": 80.37199821269722,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2Q",
"roundness": {
"type": 2
},
"seed": 546214496,
"version": 1667,
"versionNonce": 1614569056,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
-38.3710655807796,
-28.81586470940509
],
[
-49.364397989861686,
-80.37199821269722
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "lTQAmT-q3EhB8l4OFoV4r",
"focus": 0.21013738563944281,
"gap": 1
},
"endBinding": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
},
{
"id": "m8Er51Ess3j92_Q_brntE",
"type": "rectangle",
"x": 2159.6497125177334,
"y": 1335.721614644765,
"width": 363.99766551946664,
"height": 127.50624664551411,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [
"cNSAgbnsi3PlOKVDl2tCl"
],
"frameId": null,
"index": "b2R",
"roundness": {
"type": 3
},
"seed": 164447840,
"version": 969,
"versionNonce": 2063142816,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false
},
{
"id": "XCGW5ooPSVuaaam9AVDz-",
"type": "text",
"x": 2173.9396997857207,
"y": 1357.9865705806305,
"width": 333.6248911062344,
"height": 83.92658771184712,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [
"cNSAgbnsi3PlOKVDl2tCl"
],
"frameId": null,
"index": "b2S",
"roundness": null,
"seed": 1091926624,
"version": 1089,
"versionNonce": 1148591008,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"text": "Spearhead",
"fontSize": 67.14127016947768,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Spearhead",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "moW1_uGfHzYIjzGogGQGK",
"type": "text",
"x": 2104.0596241560615,
"y": 1034.5614167492809,
"width": 646.7199096679688,
"height": 64.22500341608406,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2T",
"roundness": null,
"seed": 1228353120,
"version": 349,
"versionNonce": 1920051104,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"text": "Mission making, made easy",
"fontSize": 51.380002732867254,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Mission making, made easy",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "o2jZSQLCW3jrv6pCqie6_",
"type": "arrow",
"x": 2159.733022744733,
"y": 1387.9176228888964,
"width": 160.53319080851742,
"height": 70.53888252157658,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2U",
"roundness": {
"type": 2
},
"seed": 1258447456,
"version": 260,
"versionNonce": 1897640864,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
-92.03884598362902,
-5.663893806853778
],
[
-160.53319080851742,
-70.53888252157658
]
],
"lastCommittedPoint": null,
"startBinding": null,
"endBinding": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
},
{
"id": "W9vcoug4Anqo3kphgtT9I",
"type": "text",
"x": 1878.031940531582,
"y": 1271.37874036732,
"width": 241.1999053955078,
"height": 45,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2V",
"roundness": null,
"seed": 1430722144,
"version": 123,
"versionNonce": 648298400,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"text": "quick creation",
"fontSize": 36,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "quick creation",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "vKiHSOYVz3WA8hsPa4Gz_",
"type": "text",
"x": 2048.097445465383,
"y": 1156.4671681391064,
"width": 291.2398681640625,
"height": 45,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2W",
"roundness": null,
"seed": 483740256,
"version": 118,
"versionNonce": 859524000,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"text": "multiplayer ready",
"fontSize": 36,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "multiplayer ready",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "slm2yLO4dbFEB4unTSEBM",
"type": "arrow",
"x": 2272.711713817616,
"y": 1335.0347744783633,
"width": 66.5822620861594,
"height": 126.32077628211141,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2X",
"roundness": {
"type": 2
},
"seed": 1014824544,
"version": 263,
"versionNonce": 823187360,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
-54.89666258783586,
-52.65193572538732
],
[
-66.5822620861594,
-126.32077628211141
]
],
"lastCommittedPoint": null,
"startBinding": null,
"endBinding": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
},
{
"id": "AzL0eyJGalMEJhcGJm2_F",
"type": "arrow",
"x": 2416.5200398307725,
"y": 1335.3880374127152,
"width": 114.58165799006088,
"height": 125.00113779000418,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2Y",
"roundness": {
"type": 2
},
"seed": 1963864672,
"version": 298,
"versionNonce": 1951720352,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
81.1107899245286,
-56.913107137971394
],
[
114.58165799006088,
-125.00113779000418
]
],
"lastCommittedPoint": null,
"startBinding": null,
"endBinding": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
},
{
"id": "9XYksy7fqu89wA1b8KHk4",
"type": "text",
"x": 2479.4611352261527,
"y": 1164.386899622711,
"width": 122.79595947265625,
"height": 45,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2Z",
"roundness": null,
"seed": 1834542688,
"version": 150,
"versionNonce": 1261924256,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"text": "flexible",
"fontSize": 36,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "flexible",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "O8kJBNxfzL8ncxdD2kZRM",
"type": "arrow",
"x": 2527.9914421715134,
"y": 1376.2823093418706,
"width": 161.921133337549,
"height": 61.23797674760587,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2a",
"roundness": {
"type": 2
},
"seed": 1915558496,
"version": 341,
"versionNonce": 1038646176,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
110.30502983196084,
-8.630813890686284
],
[
161.921133337549,
-61.23797674760587
]
],
"lastCommittedPoint": null,
"startBinding": null,
"endBinding": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
},
{
"id": "1SS8AR95vxynNN_GgSOm-",
"type": "text",
"x": 2670.559620709462,
"y": 1266.635895233043,
"width": 207.61192321777344,
"height": 45,
"angle": 0,
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b2b",
"roundness": null,
"seed": 911920736,
"version": 322,
"versionNonce": 1318586272,
"isDeleted": false,
"boundElements": [],
"updated": 1766927996013,
"link": null,
"locked": false,
"text": "easy to use",
"fontSize": 36,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "easy to use",
"autoResize": true,
"lineHeight": 1.25
}
],
"appState": {
"gridSize": 20,
"gridStep": 5,
"gridModeEnabled": false,
"viewBackgroundColor": "#1b1d27"
},
"files": {}
}
+78 -1
View File
@@ -53,6 +53,83 @@ do -- env
env = env env = env
end end
do -- Mission table
---@class Mission
---@field drawings Array<DrawingObject>
---@alias PrimitiveType
---| "Polygon"
---| "Line"
---| "TextBox"
---@alias PolygonMode
---| "circle"
---| "free"
---| "oval"
---| "rect"
---| "arrow"
---@alias LineMode
---| "segment"
---| "segments"
---| "free"
---@class DrawingObject
---@field primitiveType PrimitiveType
---@field name string
---@field visible boolean
---@field layerName string?
---@field mapX number
---@field mapY number
---@field colorString string Hex ARGB color
---@field style string
---@field thickness number
---@class Polygon : DrawingObject
---@field polygonMode PolygonMode
---@field fillColorString string?
---@class Circle : Polygon
---@field radius number
---@class Free : Polygon
---@field points Array<Vec2>
---@class Oval : Polygon
---@field r1 number
---@field r2 number
---@field angle number?
---@class Rect : Polygon
---@field width number
---@field height number
---@field angle number?
---@class Arrow : Polygon
---@field length number
---@field angle number?
---@class Line : DrawingObject
---@field primitiveType PrimitiveType # PrimitiveType.Line
---@field lineMode LineMode
---@field closed boolean
---@field points Array<Vec2>
---@class Segment : Line
---@class Segments : Line
---@class FreeLine : Line
---@class TextBox : DrawingObject
---@field fontSize number
---@field text string
---@field fillColorString string
---@field borderThickness number
end
do -- timer do -- timer
---@class timer ---@class timer
---@field getTime fun() : number returns the time in the mission (in seconds. 3 decimals) ---@field getTime fun() : number returns the time in the mission (in seconds. 3 decimals)
@@ -358,7 +435,7 @@ do -- trigger
---@field circleToAll fun(coalition: DrawCoalition, id: number, center: Vec3 , radius: number, color: table , fillColor: table , lineType: LineType , readOnly: boolean?, message: string?) Creates a circle on the map with a given radius, color, fill color, and outline. ---@field circleToAll fun(coalition: DrawCoalition, id: number, center: Vec3 , radius: number, color: table , fillColor: table , lineType: LineType , readOnly: boolean?, message: string?) Creates a circle on the map with a given radius, color, fill color, and outline.
---@field rectToAll fun(coalition:DrawCoalition, id:number, startPoint: Vec3, endPoint:Vec3, color: table, fillColor: table, lineType: LineType , readOnly: boolean?, message: string?) Creates a rectangle on the map from the startpoint in one corner to the endPoint in the opposite corner. ---@field rectToAll fun(coalition:DrawCoalition, id:number, startPoint: Vec3, endPoint:Vec3, color: table, fillColor: table, lineType: LineType , readOnly: boolean?, message: string?) Creates a rectangle on the map from the startpoint in one corner to the endPoint in the opposite corner.
---@field quadToAll fun(coalition:DrawCoalition, id:number, point1: Vec3, point2:Vec3, point3:Vec3, point4:Vec3,color: table, fillColor: table, lineType: LineType , readOnly: boolean?, message: string?) Creates a shape defined by the 4 points on the F10 map. ---@field quadToAll fun(coalition:DrawCoalition, id:number, point1: Vec3, point2:Vec3, point3:Vec3, point4:Vec3,color: table, fillColor: table, lineType: LineType , readOnly: boolean?, message: string?) Creates a shape defined by the 4 points on the F10 map.
---@field textToAll fun(coalition:DrawCoalition, id: number, point:Vec3, color: table, fillColor: table, fontSize:table, readOnly:boolean, text:string) Creates a text imposed on the map at a given point. Text scales with the map. ---@field textToAll fun(coalition:DrawCoalition, id: number, point:Vec3, color: table, fillColor: table, fontSize:number, readOnly:boolean, text:string) Creates a text imposed on the map at a given point. Text scales with the map.
---@field arrowToAll fun(coalition:DrawCoalition, id: number, startPoint:Vec3, endPoint:Vec3, color:table, fillColor:table, lineType:LineType, readonly:boolean?, message: string?) Creates an arrow from the startPoint to the endPoint on the F10 map. The arrow will be "pointing at" the startPoint ---@field arrowToAll fun(coalition:DrawCoalition, id: number, startPoint:Vec3, endPoint:Vec3, color:table, fillColor:table, lineType:LineType, readonly:boolean?, message: string?) Creates an arrow from the startPoint to the endPoint on the F10 map. The arrow will be "pointing at" the startPoint
---@field setMarkupRadius fun(id:number, radius:number) Updates the radius of the specified mark to be the new value. ---@field setMarkupRadius fun(id:number, radius:number) Updates the radius of the specified mark to be the new value.
---@field setMarkupText fun(id:number, text: string) Updates the text value of the passed mark to the passed text value. ---@field setMarkupText fun(id:number, text: string) Updates the text value of the passed mark to the passed text value.
+3 -2
View File
@@ -18,8 +18,9 @@ class Header extends HTMLElement {
<a href="/pages/tutorials.html">Tutorials</a> <a href="/pages/tutorials.html">Tutorials</a>
<div class="dropdown-content"> <div class="dropdown-content">
<a href="/pages/tutorials.html">Quick Starts</a> <a href="/pages/tutorials.html">Quick Starts</a>
<a href="/pages/advanced/CAP.html">Advanced: CAP</a> <a href="/pages/advanced/CAP.html">Advanced: CAP</a>
<a href="/pages/advanced/missions.html">Advanced: Missions</a> <a href="/pages/advanced/missions.html">Advanced: Missions</a>
<a href="/pages/advanced/custom-drawings.html">Custom Drawings</a>
</div> </div>
</div> </div>
+1 -2
View File
@@ -63,5 +63,4 @@
</footer> </footer>
</body> </body>
</html> </html>
```
+1 -2
View File
@@ -220,5 +220,4 @@
</footer> </footer>
</body> </body>
</html> </html>
```
+65
View File
@@ -0,0 +1,65 @@
<!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: Custom Drawings</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 provide an easy way to manage and update these drawings dynamically as the mission progresses. <br>
</p>
<h2 id="custom_drawings_how_to">How to</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><strong>[a]</strong> is the stage number at which the drawing should appear.</li>
<li><strong>[b]</strong> is the stage number at which the drawing should be removed again.</li>
<li><strong>[FreeForm]</strong> 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>
+1 -2
View File
@@ -68,5 +68,4 @@
</footer> </footer>
</body> </body>
</html> </html>
```
-1
View File
@@ -123,4 +123,3 @@
</body> </body>
</html> </html>
```
+2 -2
View File
@@ -270,11 +270,11 @@ header nav a:hover::after {
border-radius: 8px; border-radius: 8px;
max-height: 0px; max-height: 0px;
transition: all 1.5s ease; transition: all 1s ease;
} }
.dropdown:hover .dropdown-content { .dropdown:hover .dropdown-content {
max-height: 200px; max-height: 100vh;
} }
header.collapsible { header.collapsible {
+28 -2
View File
@@ -20,7 +20,7 @@
---@field MissionZoneData table<string, MissionZoneData> ---@field MissionZoneData table<string, MissionZoneData>
---@field FarpZoneData table<string,FarpZoneData> ---@field FarpZoneData table<string,FarpZoneData>
---@field missionCodes table<string, boolean> ---@field missionCodes table<string, boolean>
---@field CustomDrawings Array<CustomDrawing>
---@class CapRoute ---@class CapRoute
---@field zones Array<SpearheadTriggerZone> ---@field zones Array<SpearheadTriggerZone>
@@ -99,7 +99,8 @@ function Database.New(Logger)
MissionZoneData = {}, MissionZoneData = {},
FarpZoneData = {}, FarpZoneData = {},
missionCodes = {}, missionCodes = {},
SupplyHubZones = {} SupplyHubZones = {},
CustomDrawings = {}
} }
Database.__index = Database Database.__index = Database
@@ -215,6 +216,23 @@ function Database.New(Logger)
end end
end end
---@type table<integer, boolean>
do -- custom drawings
if env.mission.drawings and env.mission.drawings.layers then
for i, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
for key, layer_object in pairs(layer.objects) do
if Spearhead.Util.startswith(layer_object.name, "drawing_", true) then
local object = layer_object --[[@as DrawingObject]]
local customDrawing = Spearhead.classes.stageClasses.drawings.CustomDrawing.New(object)
table.insert(self._tables.CustomDrawings, customDrawing)
end
end
end
end
end
end
---@type table<string, boolean> ---@type table<string, boolean>
local availableSupplyHubs = {} local availableSupplyHubs = {}
for _, supplyHubZoneName in pairs(self._tables.SupplyHubZones) do for _, supplyHubZoneName in pairs(self._tables.SupplyHubZones) do
@@ -593,6 +611,14 @@ function Database:loadBlueSamUnits()
end end
end end
---@return Array<CustomDrawing>
function Database:getCustomDrawings()
if self._tables.CustomDrawings == nil then
return {}
end
return self._tables.CustomDrawings
end
---Loads all units, data and briefings ---Loads all units, data and briefings
---@private ---@private
function Database:LoadZoneData(missionZoneName) function Database:LoadZoneData(missionZoneName)
+71 -57
View File
@@ -13,8 +13,12 @@ local WaitingStagesByIndex = {}
local currentStage = -99 local currentStage = -99
---@class GlobalStageManager : StageCompleteListener
GlobalStageManager = {} ---@field private database Database
---@field private logger Logger
---@field private stageConfig StageConfig
local GlobalStageManager = {}
GlobalStageManager.__index = GlobalStageManager
---comment ---comment
---@param database Database ---@param database Database
@@ -22,13 +26,14 @@ GlobalStageManager = {}
---@param logLevel LogLevel ---@param logLevel LogLevel
---@param spawnManager SpawnManager ---@param spawnManager SpawnManager
---@return nil ---@return nil
function GlobalStageManager:NewAndStart(database, stageConfig, logLevel, spawnManager) function GlobalStageManager.NewAndStart(database, stageConfig, logLevel, spawnManager)
local logger = Spearhead.LoggerTemplate.new("StageManager", logLevel) local logger = Spearhead.LoggerTemplate.new("StageManager", logLevel)
logger:info("Using Stage Log Level: " .. logLevel) logger:info("Using Stage Log Level: " .. logLevel)
local o = {} local self = setmetatable({}, GlobalStageManager)
setmetatable(o, { __index = self }) self.database = database
self.stageConfig = stageConfig
o.logger = logger self.logger = logger
if stageConfig.isAutoStages ~= true then if stageConfig.isAutoStages ~= true then
logger:warn("Spearhead will not automatically progress stages due to the given settings. If you manually have implemented this, please ignore this message") logger:warn("Spearhead will not automatically progress stages due to the given settings. If you manually have implemented this, please ignore this message")
end end
@@ -42,53 +47,6 @@ function GlobalStageManager:NewAndStart(database, stageConfig, logLevel, spawnMa
Spearhead.Events.AddStageNumberChangedListener(OnStageNumberChangedListener) Spearhead.Events.AddStageNumberChangedListener(OnStageNumberChangedListener)
---@type StageCompleteListener
local OnStageCompleteListener = {
OnStageComplete = function(self, stage)
logger:debug("Receiving stage complete event from: " .. stage.zoneName)
local anyIncomplete = false
logger:debug("Checking stages for index: " .. tostring(currentStage))
for index, stage in pairs(StagesByIndex[tostring(currentStage)]) do
if stage:IsComplete() == false then
anyIncomplete = true
logger:debug("Need to wait for Stage " .. stage.zoneName .. " to be completed")
else
logger:debug("Stage verified to be completed: " .. stage.zoneName)
end
end
if anyIncomplete == false and stageConfig.isAutoStages == true then
-- CHECK WAITING STAGES
local nextStage = currentStage + 1
if WaitingStagesByIndex[tostring(nextStage)] then
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
if waitingStage:IsActive() == false then
waitingStage:ActivateStage()
end
end
end
local anyWaiting = false
if WaitingStagesByIndex[tostring(nextStage)] then
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
if waitingStage:IsComplete() == false then
anyWaiting = true
end
end
end
if anyWaiting == false then
logger:debug("Setting next stage to: " .. tostring(currentStage + 1))
Spearhead.Events.PublishStageNumberChanged(currentStage + 1)
end
end
end
}
for _, stageName in pairs(database:getStagezoneNames()) do for _, stageName in pairs(database:getStagezoneNames()) do
logger:debug("Found stage zone with name: " .. stageName) logger:debug("Found stage zone with name: " .. stageName)
@@ -136,13 +94,13 @@ function GlobalStageManager:NewAndStart(database, stageConfig, logLevel, spawnMa
if isSideStage == true then if isSideStage == true then
local stage = Spearhead.classes.stageClasses.Stages.ExtraStage.New(database, stageConfig, stagelogger, initData, spawnManager) local stage = Spearhead.classes.stageClasses.Stages.ExtraStage.New(database, stageConfig, stagelogger, initData, spawnManager)
stage:AddStageCompleteListener(OnStageCompleteListener) stage:AddStageCompleteListener(self)
if SideStageByIndex[tostring(orderNumber)] == nil then SideStageByIndex[tostring(orderNumber)] = {} end if SideStageByIndex[tostring(orderNumber)] == nil then SideStageByIndex[tostring(orderNumber)] = {} end
table.insert(SideStageByIndex[tostring(orderNumber)], stage) table.insert(SideStageByIndex[tostring(orderNumber)], stage)
else else
local stage = Spearhead.classes.stageClasses.Stages.PrimaryStage.New(database, stageConfig, stagelogger, initData, spawnManager) local stage = Spearhead.classes.stageClasses.Stages.PrimaryStage.New(database, stageConfig, stagelogger, initData, spawnManager)
stage:AddStageCompleteListener(OnStageCompleteListener) stage:AddStageCompleteListener(self)
if StagesByIndex[tostring(orderNumber)] == nil then StagesByIndex[tostring(orderNumber)] = {} end if StagesByIndex[tostring(orderNumber)] == nil then StagesByIndex[tostring(orderNumber)] = {} end
table.insert(StagesByIndex[tostring(orderNumber)], stage) table.insert(StagesByIndex[tostring(orderNumber)], stage)
@@ -193,15 +151,71 @@ function GlobalStageManager:NewAndStart(database, stageConfig, logLevel, spawnMa
end end
table.insert(WaitingStagesByIndex[tostring(stageIndex)], waitingStage) table.insert(WaitingStagesByIndex[tostring(stageIndex)], waitingStage)
waitingStage:AddStageCompleteListener(OnStageCompleteListener) waitingStage:AddStageCompleteListener(self)
end end
end end
end end
end end
return self
end
function GlobalStageManager:OnStageComplete(stage)
self.logger:debug("Receiving stage complete event from: " .. stage.zoneName)
return o local anyIncomplete = false
self.logger:debug("Checking stages for index: " .. tostring(currentStage))
for index, stage in pairs(StagesByIndex[tostring(currentStage)]) do
if stage:IsComplete() == false then
anyIncomplete = true
self.logger:debug("Need to wait for Stage " .. stage.zoneName .. " to be completed")
else
self.logger:debug("Stage verified to be completed: " .. stage.zoneName)
end
end
if anyIncomplete == false and self.stageConfig.isAutoStages == true then
-- CHECK WAITING STAGES
local nextStage = currentStage + 1
if WaitingStagesByIndex[tostring(nextStage)] then
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
if waitingStage:IsActive() == false then
waitingStage:ActivateStage()
end
end
end
local anyWaiting = false
if WaitingStagesByIndex[tostring(nextStage)] then
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
if waitingStage:IsComplete() == false then
anyWaiting = true
end
end
end
if anyWaiting == false then
local newStageNumber = currentStage + 1
self:UpdateDrawings(newStageNumber)
self.logger:debug("Setting next stage to: " .. tostring(newStageNumber))
Spearhead.Events.PublishStageNumberChanged(newStageNumber)
end
end
end
---@private
function GlobalStageManager:UpdateDrawings(stageNumber)
local drawings = self.database:getCustomDrawings()
for _, drawing in pairs(drawings) do
local startStage, stopStage = drawing:GetStartAndStop()
if stageNumber >= startStage and stageNumber < stopStage then
drawing:Draw()
else
drawing:Remove()
end
end
end end
@@ -0,0 +1,52 @@
---@class CustomDrawing
---@field private _id integer?
---@field private _drawingObject DrawingObject
---@field private _helper DrawingHelper
---@field private _startingStage number
---@field private _removeAtStage number
local CustomDrawing = {}
CustomDrawing.__index = CustomDrawing
---@param drawingObject DrawingObject
---@param id integer?
---@return CustomDrawing
function CustomDrawing.New(drawingObject, id)
local self = setmetatable({}, CustomDrawing)
self._drawingObject = drawingObject
self._id = id
self._helper = Spearhead.classes.stageClasses.drawings.helper.DrawingHelper
local name = drawingObject.name
local split = Spearhead.Util.split_string(name or "", "_")
local secondPart = split[2] or "1"
local splitPart = Spearhead.Util.split_string(secondPart, ":")
self._startingStage = tonumber(splitPart[1]) or 1
self._removeAtStage = tonumber(splitPart[2]) or math.huge
return self
end
---@return number start
---@return number stop
function CustomDrawing:GetStartAndStop()
return self._startingStage, self._removeAtStage
end
function CustomDrawing:Draw()
self._id = self._helper.Draw(self._drawingObject)
end
function CustomDrawing:Remove()
if self._id ~= nil then
self._helper.Remove(self._id)
self._id = nil
end
end
if not Spearhead then Spearhead = {} end
if not Spearhead.classes then Spearhead.classes = {} end
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
if not Spearhead.classes.stageClasses.drawings then Spearhead.classes.stageClasses.drawings = {} end
Spearhead.classes.stageClasses.drawings.CustomDrawing = CustomDrawing
@@ -0,0 +1,228 @@
---@class DrawingHelper
local DrawingHelper = {}
DrawingHelper.__index = DrawingHelper
local customDrawingIdIncrementer = 4210
---@param object DrawingObject
---@return integer? id
function DrawingHelper.Draw(object)
if object == nil then
return nil
end
local id = DrawingHelper.GetAndAddId()
if(object.primitiveType == "Polygon") then
DrawingHelper.DrawPolygon(object--[[@as Polygon]], id)
elseif(object.primitiveType == "Line") then
DrawingHelper.DrawLine(object--[[@as Line]], id)
elseif(object.primitiveType == "TextBox") then
DrawingHelper.DrawTextBox(object--[[@as TextBox]], id)
end
return id
end
function DrawingHelper.GetAndAddId()
customDrawingIdIncrementer = customDrawingIdIncrementer + 1
return customDrawingIdIncrementer
end
---@private
---@param shapeID ShapeId
---@param drawID integer
---@param points Array<Vec3>
---@param fillColor table
---@param lineColor table
local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineStyle)
local functionString = "trigger.action.markupToAll(" .. shapeID .. ", -1, " .. drawID .. ","
for _, point in ipairs(points) do
functionString = functionString .. " { x=" .. point.x .. ", y=0,z=" .. point.z .. "},"
end
functionString = functionString ..
"{ " .. lineColor[1] .. "," .. lineColor[2] .. "," .. lineColor[3] .. "," .. lineColor[4] .. "}, " ..
"{ " .. fillColor[1] .. "," .. fillColor[2] .. "," .. fillColor[3] .. "," .. fillColor[4] .. "}, " ..
lineStyle .. ")"
---@diagnostic disable-next-line: deprecated
local f, err = loadstring(functionString)
if f then
f()
else
env.error("Something failed when drawing complex drawing" .. err)
end
end
---@private
---@param object Polygon
---@param id integer
function DrawingHelper.DrawPolygon(object, id)
if object == nil then
return
end
---@param circle Circle
local function DrawCircle(circle)
local vec3 = { x = circle.mapX, y = 0, z = circle.mapY }
local fillColor = DrawingHelper.ColorToColorTable(circle.fillColorString)
local colorString = DrawingHelper.ColorToColorTable(circle.colorString)
local style = DrawingHelper.ToLineStyleInteger(circle.style)
trigger.action.circleToAll(-1, id, vec3, circle.radius, colorString, fillColor, style, true)
end
---@param oval Oval
local function DrawOval(oval)
---@type Array<Vec3>
local points = {}
local pointsNo = 30
local angleStep = (2 * math.pi) / points
local fillColor = DrawingHelper.ColorToColorTable(oval.fillColorString)
local color = DrawingHelper.ColorToColorTable(oval.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(oval.style)
for i = 1, pointsNo do
local angle = i * angleStep
local x = oval.mapX + (oval.r1 * math.cos(angle))
local y = oval.mapY + (oval.r2 * math.sin(angle))
table.insert(points, { x = x, y = 0, z = y } )
end
MarkupToAll(7, id, points, fillColor, color, lineStyle)
end
---@param free Free
local function DrawFree(free)
local fillColor = DrawingHelper.ColorToColorTable(free.fillColorString)
local color = DrawingHelper.ColorToColorTable(free.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(free.style)
local points = {}
for _, point in ipairs(free.points) do
table.insert(points, { x = point.x, y = 0, z = point.y } )
end
MarkupToAll(7, id, points, fillColor, color, lineStyle)
end
---@param rect Rect
local function DrawRect(rect)
local fillColor = DrawingHelper.ColorToColorTable(rect.fillColorString)
local color = DrawingHelper.ColorToColorTable(rect.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(rect.style)
local pointA = { x = rect.mapX, y = 0, z = rect.mapY }
local pointB = { x = rect.mapX + rect.width, y = 0, z = rect.mapY + rect.height }
trigger.action.rectToAll(-1, id, pointA, pointB, color, fillColor, lineStyle, true)
end
---@param arrow Arrow
local function DrawArrow(arrow)
local fillColor = DrawingHelper.ColorToColorTable(arrow.fillColorString)
local color = DrawingHelper.ColorToColorTable(arrow.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(arrow.style)
local startPoint = { x = arrow.mapX, y = 0, z = arrow.mapY }
local rad = math.rad(arrow.angle or 0)
local length = arrow.length or 100
local endPoint = { x = arrow.mapX + length * math.cos(rad), y = 0, z = arrow.mapY + length * math.sin(rad) }
trigger.action.arrowToAll(-1, id, startPoint, endPoint, color, fillColor, lineStyle, true)
end
if object.polygonMode == "circle" then
DrawCircle(object--[[@as Circle]])
elseif object.polygonMode == "oval" then
DrawOval(object--[[@as Oval]])
elseif object.polygonMode == "free" then
DrawFree(object--[[@as Free]])
elseif object.polygonMode == "rect" then
DrawRect(object--[[@as Rect]])
elseif object.polygonMode == "arrow" then
DrawArrow(object--[[@as Arrow]])
end
end
---@private
---@param object Line
---@param id integer
function DrawingHelper.DrawLine(object, id)
---@type Array<Vec3>
local points = {}
for _, point in ipairs(object.points) do
table.insert(points, { x = point.x, y = 0, z = point.y } )
end
local color = DrawingHelper.ColorToColorTable(object.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(object.style)
MarkupToAll(1, id, points, color, color, lineStyle)
end
---@private
---@param object TextBox
---@param id integer
function DrawingHelper.DrawTextBox(object, id)
trigger.action.textToAll(-1, id, { x= object.mapX, y = 0, z = object.mapY },
DrawingHelper.ColorToColorTable(object.colorString),
DrawingHelper.ColorToColorTable(object.fillColorString),
object.fontSize or 12,
true,
object.text or "")
end
function DrawingHelper.Remove(id)
trigger.action.removeMark(id)
end
---@param hexStr string
---@return table
function DrawingHelper.ColorToColorTable(hexStr)
hexStr = hexStr:gsub("0x", "")
local a = tonumber(hexStr:sub(1, 2), 16) / 255
local r = tonumber(hexStr:sub(3, 4), 16) / 255
local g = tonumber(hexStr:sub(5, 6), 16) / 255
local b = tonumber(hexStr:sub(7, 8), 16) / 255
return { r, g , b , a }
end
---@param lineStyle string
function DrawingHelper.ToLineStyleInteger(lineStyle)
lineStyle = lineStyle:lower()
if lineStyle == "no line" then
return 0
elseif lineStyle == "solid" then
return 1
elseif lineStyle == "dashed" then
return 2
elseif lineStyle == "dotted" then
return 3
elseif lineStyle == "dot dash" then
return 4
elseif lineStyle == "long dash" then
return 5
elseif lineStyle == "two dash" then
return 6
else
return 0
end
end
---@class ARGB
---@field public a number
---@field public r number
---@field public g number
---@field public b number
if not Spearhead then Spearhead = {} end
if not Spearhead.classes then Spearhead.classes = {} end
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
if not Spearhead.classes.stageClasses.drawings then Spearhead.classes.stageClasses.drawings = {} end
if not Spearhead.classes.stageClasses.drawings.helper then Spearhead.classes.stageClasses.drawings.helper = {} end
Spearhead.classes.stageClasses.drawings.helper.DrawingHelper = DrawingHelper
+1 -1
View File
@@ -39,7 +39,7 @@ local detectionLogger = Spearhead.LoggerTemplate.new("DetectionManager", default
local detectionManager = Spearhead.classes.capClasses.detection.DetectionManager.New(detectionLogger) local detectionManager = Spearhead.classes.capClasses.detection.DetectionManager.New(detectionLogger)
Spearhead.classes.capClasses.GlobalCapManager.start(databaseManager, capConfig, detectionManager, stageConfig, defaultLogLevel, spawnManager) Spearhead.classes.capClasses.GlobalCapManager.start(databaseManager, capConfig, detectionManager, stageConfig, defaultLogLevel, spawnManager)
Spearhead.internal.GlobalStageManager:NewAndStart(databaseManager, stageConfig, defaultLogLevel, spawnManager) Spearhead.internal.GlobalStageManager.NewAndStart(databaseManager, stageConfig, defaultLogLevel, spawnManager)
Spearhead.internal.GlobalFleetManager.start(databaseManager) Spearhead.internal.GlobalFleetManager.start(databaseManager)