Minor refactor and bug fixing

This commit is contained in:
Pax1601 2023-09-08 22:41:37 +02:00
parent 89c39c7038
commit 74d5480587
59 changed files with 174 additions and 2392 deletions

View File

@ -1,194 +0,0 @@
/**************************************/
.olympus-dialog {
align-self: center;
background:white;
border-radius: 10px;
display: flex;
flex-direction: column;
justify-self: center;
padding:10px;
position:absolute;
width:fit-content;
z-index: 9999;
}
.olympus-dialog-close {
cursor:pointer;
position:absolute;
right:10px;
top:5px;
}
.olympus-dialog-header {
font-weight:bold;
}
/**************************************/
/***** AIC *****/
.aic-panel {
z-index: 9999;
}
#aic-control-panel {
bottom:30px;
position: absolute;
left:30px;
}
#aic-control-panel .olympus-button img {
max-width: 32px;
}
#aic-toolbox, #aic-callsign-panel {
align-items: flex-start;
align-self: center;
flex-direction: column;
row-gap: 10px;
display:none;
position:absolute;
}
.aic-panel {
background:#eaeaea;
border-bottom-right-radius: 10px;
border-top-right-radius: 10px;
justify-self: left;
padding:5px 10px;
}
.aic-enabled #aic-toolbox, .aic-enabled #aic-callsign-panel {
display:flex;
}
.aic-enabled #aic-callsign-panel {
align-self: auto;
top: 100px;
}
.aic-panel h2 {
font-size:90%;
margin:0;
padding:0;
text-align: center;
}
#aic-callsign-display {
text-align: center;
}
#aic-formation-list {
display:flex;
flex-direction: column;
justify-content: center;
}
#aic-formation-list > div {
align-items: center;
cursor: pointer;
display:flex;
flex-direction: column;
justify-content: center;
margin-top:10px;
position:relative;
}
#aic-formation-list .aic-formation-image img {
border: 1px solid #ccc;
border-radius: 10px;
max-width: 50px;
}
#aic-formation-list .aic-formation-name {
font-size:90%;
}
#aic-formation-list .aic-formation-descriptor {
background:white;
border-radius: 10px;
left:100px;
padding:5px;
position:absolute;
width: max-content;
}
#aic-teleprompt {
background-color: white;
border:2px solid black;
border-radius: 10px;
bottom: 50px;
color: black;
display: none;
justify-content: center;
justify-self: center;
padding: 10px;
position: absolute;
width: fit-content;
z-index: 9999;
}
.aic-enabled #aic-teleprompt {
display:flex;
}
#aic-descriptor {
display:flex;
flex-direction: row;
}
#aic-descriptor .aic-descriptor-section {
display:flex;
flex-direction: column;
margin:0 10px;
}
#aic-descriptor .aic-descriptor-section-label {
background-color:#eaeaea;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
padding:.25em;
text-align: center;
}
#aic-descriptor .aic-descriptor-phrase {
border-bottom: 1px solid #ccc;
display:flex;
flex-direction: row;
margin-bottom:5px;
padding-bottom:2px;
}
#aic-descriptor .aic-descriptor-phrase:last-of-type {
margin-bottom: 0;
}
#aic-descriptor .aic-descriptor-components .aic-descriptor-component {
margin:0 5px;
text-align: center;
}
#aic-descriptor .aic-descriptor-component-label {
display:none;
}
#aic-descriptor .aic-descriptor-component-value:after {
content:",";
margin-right:5px;
}
#aic-descriptor .aic-descriptor-component:last-of-type .aic-descriptor-component-value:after {
content:"; ";
}
#aic-descriptor .aic-descriptor-section:last-of-type .aic-descriptor-component:last-of-type .aic-descriptor-component-value:after {
content:".";
}
/**************************************/

View File

@ -1,205 +0,0 @@
.ol-strip-board .ol-dialog-header {
align-items: center;
display:flex;
justify-content: space-between;
}
.ol-strip-board-strips {
display:flex;
flex-direction: column;
row-gap: 4px;
}
.ol-strip-board-strip {
align-items: center;
border-radius: var( --border-radius-sm );
column-gap: 4px;
display:flex;
flex-flow: row nowrap;
row-gap:4px;
}
.ol-strip-board-strip[data-flight-status="checkedin"] {
background-color: #ffffff2A;
}
.ol-strip-board-strip[data-flight-status="readytotaxi"] {
background-color: #ffff0063;
}
.ol-strip-board-strip[data-flight-status="clearedtotaxi"] {
background-color: #00ff0030;
}
.ol-strip-board-strip[data-flight-status="halted"] {
background-color: #FF000040;
}
.ol-strip-board-strip[data-flight-status="terminated"] {
background-color: black;
}
.ol-strip-board-headers {
column-gap: 4px;
display:flex;
flex-flow:row nowrap;
text-align: center;
}
.ol-strip-board-headers > *, .ol-strip-board-strip > [data-point] {
padding: 4px;
text-overflow: ellipsis;
white-space: nowrap;
width:80px;
}
.ol-strip-board-strip input[type="text"] {
appearance: none;
background-color: transparent;
border:1px solid #ffffff30;
border-radius: var( --border-radius-sm );
color:white;
font-size:12px;
font-weight:normal;
outline:none;
padding: 4px 0;
text-align: center;
width:100%;
}
.ol-strip-board-strip[data-time-warning="level-1"] [data-point="timeToGo"] {
border:1px solid #cc0000;
}
.ol-strip-board-headers :nth-child(1) {
width:12px;
}
.ol-strip-board-headers :nth-child(2),
.ol-strip-board-strip :nth-child(2),
[data-board-type="ground"] .ol-strip-board-headers :nth-child(3),
[data-board-type="ground"] .ol-strip-board-strip :nth-child(3) {
width:130px;
}
[data-board-type="ground"] .ol-strip-board-strip :nth-child(5) {
text-align: center;
}
.ol-strip-board-headers :last-child,
.ol-strip-board-strip :last-child {
width:20px;
}
[data-board-type="tower"] .ol-strip-board-strip > * {
text-align: center;
}
[data-board-type="tower"] .ol-strip-board-strip a {
color:white;
}
[data-board-type="tower"] .ol-strip-board-strip > :nth-child(2) {
text-align: left;
}
[data-board-type="tower"] .ol-strip-board-strip :nth-child(3) input,
[data-board-type="tower"] .ol-strip-board-strip :nth-child(5) input {
width:30px;
}
[data-board-type="tower"] .ol-strip-board-strip :nth-child(3) {
font-size:10px;
}
[data-altitude-assigned] [data-point="assignedAltitude"] input,
[data-speed-assigned] [data-point="assignedSpeed"] input {
background-color:#ffffffbb;
color: black;
font-weight: var( --font-weight-bolder );
}
[data-warning-altitude] [data-point="altitude"],
[data-warning-speed] [data-point="speed"] {
background:#cc0000;
border-radius: var( --border-radius-sm );
}
.ol-strip-board-strip > [data-point="name"] {
text-overflow: ellipsis;
overflow:hidden;
}
.ol-strip-board-strip .ol-select-value {
opacity: .85;
}
.ol-strip-board-add-flight {
display:flex;
flex-flow: row nowrap;
position:relative;
}
.ol-strip-board-add-flight > * {
border:none;
outline: none;
padding:4px 8px;
}
.add-flight-by-click img {
filter:invert();
height: 12px;
}
.ol-strip-board-add-flight input {
border-radius: var( --border-radius-sm );
}
.ol-strip-board-add-flight .ol-auto-suggest {
background:white;
border-radius: var(--border-radius-sm );
color:black;
display:none;
flex-direction: column;
left:0;
margin:0;
position:absolute;
translate:0 -100%;
top:0;
}
.ol-strip-board-add-flight .ol-auto-suggest[data-has-suggestions] {
display:flex;
row-gap: 4px;
}
.ol-strip-board-add-flight .ol-auto-suggest[data-has-suggestions] a {
cursor: pointer;
}
[data-board-type="ground"] {
bottom:20px;
}
[data-board-type="tower"] {
right:10px;
top:10px;
}
[data-board-type="tower"] .ol-auto-suggest {
top:30px;
translate:0;
}

View File

@ -1,27 +0,0 @@
#unit-list {
display:flex;
flex-direction: column;
font-size:13px;
height: 250px;
width:fit-content;
}
#unit-list > div {
display:flex;
flex-direction: row;
flex-wrap: nowrap;
}
#unit-list > div > div {
text-overflow: ellipsis;
white-space: nowrap;
width:100px;
}
#unit-list > div:first-of-type {
text-align: center;
}
#unit-list > div > div:nth-of-type( 4 ) {
text-align: center;
}

View File

@ -1,9 +1,6 @@
@import url("layout/layout.css");
@import url("style/style.css");
@import url("atc/atc.css");
@import url("atc/unitdatatable.css");
@import url("panels/connectionstatus.css");
@import url("panels/serverstatus.css");
@import url("panels/mouseinfo.css");

View File

@ -1,4 +1,3 @@
* {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
@ -7,7 +6,7 @@
html * {
font-family: 'Open Sans', sans-serif !important;
user-select: none;
user-select: none;
}
body {
@ -117,7 +116,8 @@ form>div {
.ol-panel {
background-color: var(--background-steel);
border-radius: var(--border-radius-md);;
border-radius: var(--border-radius-md);
;
box-shadow: 0px 2px 5px #000A;
color: white;
font-size: 12px;
@ -963,16 +963,16 @@ nav.ol-panel> :last-child {
#command-mode-phase.game-commenced::after {
content: "Spawn restrictions are being enforced";
font-size: 10px;
font-size: 10px;
}
#command-mode-phase.no-restrictions::after {
content: "No spawn restrictions";
font-size: 10px;
font-size: 10px;
}
#command-mode-toolbar {
min-width: fit-content ;
min-width: fit-content;
}
#command-mode-toolbar .ol-button {
@ -990,17 +990,17 @@ nav.ol-panel> :last-child {
}
#command-mode-settings-dialog>.ol-dialog-content {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
margin-bottom: 10px;
margin-top: 10px;
row-gap: 10px;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
margin-bottom: 10px;
margin-top: 10px;
row-gap: 10px;
width: 100%;
}
#command-mode-settings-dialog>.ol-dialog-content .ol-group {
justify-content: space-between;
justify-content: space-between;
}
#command-mode-settings-dialog h4 {
@ -1292,11 +1292,11 @@ input[type=number]::-webkit-outer-spin-button {
position: relative;
transition: background-color 0.2s;
height: var(--height);
width: var(--width);
width: var(--width);
}
.ol-switch-fill::after {
aspect-ratio : 1 / 1;
aspect-ratio: 1 / 1;
background-clip: content-box;
background-color: #ffffff;
border-radius: 999px;
@ -1310,15 +1310,15 @@ input[type=number]::-webkit-outer-spin-button {
}
.ol-switch-fill::before {
align-items: center;
box-sizing: border-box;
color: white;
display: flex;
font-size: 11px;
height: 100%;
padding: 0px 7px;
position: absolute;
transition: transform 0.2s;
align-items: center;
box-sizing: border-box;
color: white;
display: flex;
font-size: 11px;
height: 100%;
padding: 0px 7px;
position: absolute;
transition: transform 0.2s;
}
.ol-switch[data-value="false"]>.ol-switch-fill::before {
@ -1326,11 +1326,11 @@ input[type=number]::-webkit-outer-spin-button {
}
.ol-switch[data-value="true"]>.ol-switch-fill::after {
transform: translateX(calc(var(--width) - var(--height)));
transform: translateX(calc(var(--width) - var(--height)));
}
.ol-switch[data-value="undefined"]>.ol-switch-fill::after {
transform: translateX(calc((var(--width) - var(--height)) * 0.5));
transform: translateX(calc((var(--width) - var(--height)) * 0.5));
}
.ol-contexmenu-panel {
@ -1338,15 +1338,15 @@ input[type=number]::-webkit-outer-spin-button {
}
.ol-coalition-switch[data-value="false"]>.ol-switch-fill {
background-color: var(--primary-blue);
background-color: var(--primary-blue);
}
.ol-coalition-switch[data-value="true"]>.ol-switch-fill {
background-color: var(--primary-red);
background-color: var(--primary-red);
}
.ol-coalition-switch[data-value="undefined"]>.ol-switch-fill {
background-color: var(--primary-neutral);
background-color: var(--primary-neutral);
}
.ol-context-menu>ul {
@ -1412,4 +1412,4 @@ input[type=number]::-webkit-outer-spin-button {
.ol-log-entry {
border-bottom: 1px solid #FFFFFF44;
}
}

View File

@ -1,172 +0,0 @@
import { ToggleableFeature } from "../features/toggleablefeature";
import { AICFormation_Azimuth } from "./aicformation/azimuth";
import { AICFormation_Range } from "./aicformation/range";
import { AICFormation_Single } from "./aicformation/single";
import { AICFormationDescriptorSection } from "./aicformationdescriptorsection";
export class AIC extends ToggleableFeature {
#formations = [
new AICFormation_Single(),
new AICFormation_Range(),
new AICFormation_Azimuth()
];
constructor() {
super( false );
this.onStatusUpdate();
// This feels kind of dirty
let $aicFormationList = document.getElementById( "aic-formation-list" );
if ( $aicFormationList ) {
this.getFormations().forEach( formation => {
// Image
let $imageDiv = document.createElement( "div" );
$imageDiv.classList.add( "aic-formation-image" );
let $img = document.createElement( "img" );
$img.src = "images/formations/" + formation.icon;
$imageDiv.appendChild( $img );
// Name
let $nameDiv = document.createElement( "div" );
$nameDiv.classList.add( "aic-formation-name" );
$nameDiv.innerText = formation.label;
// Wrapper
let $wrapperDiv = document.createElement( "div" );
$wrapperDiv.dataset.formationName = formation.name;
$wrapperDiv.appendChild( $imageDiv )
$wrapperDiv.appendChild( $nameDiv );
$wrapperDiv.addEventListener( "click", ( ev ) => {
const controlTypeInput = document.querySelector( "input[type='radio'][name='control-type']:checked" );
let controlTypeValue:any = ( controlTypeInput instanceof HTMLInputElement && [ "broadcast", "tactical" ].indexOf( controlTypeInput.value ) > -1 ) ? controlTypeInput.value : "broadcast";
// TODO: make this not an "any"
const output:any = formation.getDescriptor({
"aicCallsign" : "Magic",
"bullseyeName" : "Bullseye",
"control" : controlTypeValue,
"numGroups" : formation.numGroups
});
this.updateTeleprompt( output );
});
// Add to DOM
$aicFormationList?.appendChild( $wrapperDiv );
});
}
}
getFormations() {
return this.#formations;
}
onStatusUpdate() {
// Update the DOM
document.body.classList.toggle( "aic-enabled", this.getStatus() );
}
toggleHelp() {
document.getElementById( "aic-help" )?.classList.toggle( "hide" );
}
//*
updateTeleprompt<T extends AICFormationDescriptorSection>( descriptor:T[] ) {
let $teleprompt = document.getElementById( "aic-teleprompt" );
if ( $teleprompt instanceof HTMLElement ) {
// Clean slate
while ( $teleprompt.childNodes.length > 0 ) {
$teleprompt.childNodes[0].remove();
}
function newDiv() {
return document.createElement( "div" );
}
// Wrapper
let $descriptor = newDiv();
$descriptor.id = "aic-descriptor";
for ( const section of descriptor ) {
if ( section.omitSection ) {
continue;
}
let $section = newDiv();
$section.classList.add( "aic-descriptor-section" );
let $sectionLabel = newDiv();
$sectionLabel.classList.add( "aic-descriptor-section-label" );
$sectionLabel.innerText = section.label;
$section.appendChild( $sectionLabel );
for ( const phrase of section.getPhrases() ) {
let $phrase = newDiv();
$phrase.classList.add( "aic-descriptor-phrase" );
for ( const component of phrase.getComponents() ) {
let $component = newDiv();
$component.classList.add( "aic-descriptor-component" );
let $componentLabel = newDiv();
$componentLabel.classList.add( "aic-descriptor-component-label" );
$componentLabel.innerText = component.label;
let $componentValue = newDiv();
$componentValue.classList.add( "aic-descriptor-component-value" );
$componentValue.innerText = component.value;
$component.appendChild( $componentLabel );
$component.appendChild( $componentValue );
$phrase.appendChild( $component );
}
$section.appendChild( $phrase );
}
$descriptor.appendChild( $section );
}
$teleprompt.appendChild( $descriptor );
}
}
//*/
}

View File

@ -1,54 +0,0 @@
import { AICFormationContextDataInterface, AICFormationDescriptor } from "./aicformationdescriptor";
import { AICFormationDescriptorPhrase } from "./aicformationdescriptorphrase";
import { AICFormationDescriptorSection } from "./aicformationdescriptorsection";
export interface AICFormationInterface {
"icon" : string,
"label" : string,
"name" : string,
"numGroups" : number,
"summary" : string,
"unitBreakdown" : string[]
}
export abstract class AICFormation {
"icon" = "";
"label" = "";
"name" = "";
"numGroups" = 1;
"summary" = "";
"unitBreakdown":string[] = []
constructor() {
this.unitBreakdown = [];
}
addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) {
return phrase;
}
getDescriptor( contextData: AICFormationContextDataInterface ) {
return new AICFormationDescriptor().generate( this, contextData );
}
hasUnitBreakdown() {
return this.unitBreakdown.length > 0;
}
showFormationNameInDescriptor() {
return true;
}
}

View File

@ -1,38 +0,0 @@
import { AICFormation, AICFormationInterface } from "../aicformation";
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
export class AICFormation_Azimuth extends AICFormation implements AICFormationInterface {
"icon" = "azimuth.png";
"label" = "Azimuth";
"name" = "azimuth";
"numGroups" = 2;
"summary" = "Two contacts, side-by-side in a line perpedicular to the perspective.";
"unitBreakdown" = [ "<compass> group", "<compass> group" ];
constructor() {
super();
}
addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) {
switch ( section.name ) {
case "formation":
phrase.addComponent( new AICFormationDescriptorComponent( "<distance>" ) );
phrase.addComponent( new AICFormationDescriptorComponent( "track <compass>" ) );
}
return phrase;
}
}

View File

@ -1,38 +0,0 @@
import { AICFormation, AICFormationInterface } from "../aicformation";
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
export class AICFormation_Range extends AICFormation implements AICFormationInterface {
"icon" = "range.png";
"label" = "Range";
"name" = "range";
"numGroups" = 2;
"summary" = "Two contacts, one behind the other";
"unitBreakdown" = [ "Lead group", "Trail group" ];
constructor() {
super();
}
addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) {
switch ( section.name ) {
case "formation":
phrase.addComponent( new AICFormationDescriptorComponent( "<distance>" ) );
phrase.addComponent( new AICFormationDescriptorComponent( "track <compass>" ) );
}
return phrase;
}
}

View File

@ -1,24 +0,0 @@
import { AICFormation, AICFormationInterface } from "../aicformation";
import { AICFormationContextDataInterface, AICFormationDescriptor } from "../aicformationdescriptor";
export class AICFormation_Single extends AICFormation implements AICFormationInterface {
"icon" = "single.png";
"label" = "Single";
"name" = "single";
"numGroups" = 1;
"summary" = "One contact on its own";
"unitBreakdown": string[] = [];
constructor() {
super();
}
showFormationNameInDescriptor() {
return false;
}
}

View File

@ -1,55 +0,0 @@
import { AICFormation } from "./aicformation";
import { AICFormationDescriptorSection } from "./aicformationdescriptorsection";
import { AICFormationDescriptorSection_Formation } from "./aicformationdescriptorsection/formation";
import { AICFormationDescriptorSection_Unit } from "./aicformationdescriptorsection/unit";
import { AICFormationDescriptorSection_NumGroups } from "./aicformationdescriptorsection/numgroups";
import { AICFormationDescriptorSection_Who } from "./aicformationdescriptorsection/who";
export interface AICFormationContextDataInterface {
"aicCallsign" : string,
"bullseyeName" : string,
"control" : "broadcast" | "tactical",
"numGroups" : number
}
export class AICFormationDescriptor {
#sections:AICFormationDescriptorSection[] = [
new AICFormationDescriptorSection_Who(),
new AICFormationDescriptorSection_NumGroups(),
new AICFormationDescriptorSection_Formation(),
new AICFormationDescriptorSection_Unit()
]
constructor() {
}
addSection( section:AICFormationDescriptorSection ) {
this.#sections.push( section );
}
getSections() {
return this.#sections;
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
let output:object[] = [];
for ( const section of this.#sections ) {
output.push(
section.generate( formation, contextData )
);
}
return output;
}
}

View File

@ -1,18 +0,0 @@
interface ComponentInterface {
"label" : string;
"value" : string;
}
export class AICFormationDescriptorComponent implements ComponentInterface {
label = "(not set)";
value = "(not set)";
constructor( value:any, label?:string ) {
this.label = label || "(not set)";
this.value = value;
}
}

View File

@ -1,9 +0,0 @@
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
export abstract class AICFormactionDescriptorComponent_Distance extends AICFormationDescriptorComponent {
constructor( value:string, label?:string ) {
super( value, label );
}
}

View File

@ -1,9 +0,0 @@
import { AICFormactionDescriptorComponent_Distance } from "../distance";
export class AICFormationDescriptorComponent_Distance_Range extends AICFormactionDescriptorComponent_Distance {
constructor( value:string, label?:string ) {
super( value, label );
}
}

View File

@ -1,40 +0,0 @@
import { AICFormation } from "./aicformation";
import { AICFormationContextDataInterface } from "./aicformationdescriptor";
import { AICFormationDescriptorComponent } from "./aicformationdescriptorcomponent";
export interface AICFormationDescriptorPhraseInterface {
"generate" : CallableFunction,
"label" : string,
"name" : string
}
export class AICFormationDescriptorPhrase {
#components : AICFormationDescriptorComponent[] = [];
label = "";
name = "";
constructor() {
}
addComponent( component:AICFormationDescriptorComponent ) {
this.#components.push( component );
return this;
}
getComponents() {
return this.#components;
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
return this;
}
}

View File

@ -1,40 +0,0 @@
import { AICFormation } from "./aicformation";
import { AICFormationContextDataInterface } from "./aicformationdescriptor";
import { AICFormationDescriptorPhrase } from "./aicformationdescriptorphrase";
export interface AICFormationDescriptorSectionInterface {
"generate" : CallableFunction,
"label" : string,
"name" : string,
"omitSection" : boolean
}
export abstract class AICFormationDescriptorSection {
#phrases : AICFormationDescriptorPhrase[] = [];
label = "";
name = "";
omitSection = false;
constructor() {
}
addPhrase( phrase:AICFormationDescriptorPhrase ) {
this.#phrases.push( phrase );
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
return this;
}
getPhrases() {
return this.#phrases;
}
}

View File

@ -1,39 +0,0 @@
import { AICFormation } from "../aicformation";
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
export class AICFormationDescriptorSection_Formation extends AICFormationDescriptorSection {
label = "Formation";
name = "formation";
constructor() {
super();
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
if ( !formation.showFormationNameInDescriptor() ) {
this.omitSection = true;
return this;
}
let phrase = new AICFormationDescriptorPhrase();
phrase.addComponent( new AICFormationDescriptorComponent( formation.label, "Formation" ) );
phrase = formation.addToDescriptorPhrase( this, phrase, contextData );
this.addPhrase( phrase );
return this;
}
}

View File

@ -1,35 +0,0 @@
import { AICFormation } from "../aicformation";
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
export class AICFormationDescriptorSection_NumGroups extends AICFormationDescriptorSection {
label = "Groups";
name = "numgroups";
constructor() {
super();
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
let value = "Single group";
if ( contextData.numGroups > 1 ) {
value = contextData.numGroups + " groups";
}
let phrase = new AICFormationDescriptorPhrase();
phrase.addComponent( new AICFormationDescriptorComponent( value, "Number of groups" ) );
this.addPhrase( phrase );
return this;
}
}

View File

@ -1,83 +0,0 @@
import { AICFormation } from "../aicformation";
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
interface addUnitInformationInterface {
omitTrack?: boolean
}
export class AICFormationDescriptorSection_Unit extends AICFormationDescriptorSection {
label = "Unit";
name = "unit";
constructor() {
super();
}
addUnitInformation( formation:AICFormation, contextData: AICFormationContextDataInterface, phrase: AICFormationDescriptorPhrase, options?:addUnitInformationInterface ) {
options = options || {};
const originPoint = ( contextData.control === "broadcast" ) ? contextData.bullseyeName : "BRAA";
phrase.addComponent( new AICFormationDescriptorComponent( originPoint, "Bearing origin point" ) );
phrase.addComponent( new AICFormationDescriptorComponent( "<bearing>", "Bearing" ) );
phrase.addComponent( new AICFormationDescriptorComponent( "<range>", "Range" ) );
phrase.addComponent( new AICFormationDescriptorComponent( "<altitude>", "Altitude" ) );
if ( contextData.control === "broadcast" ) {
if ( !options.hasOwnProperty( "omitTrack" ) || options.omitTrack !== true ) {
phrase.addComponent( new AICFormationDescriptorComponent( "track <compass>", "Tracking" ) );
}
} else {
phrase.addComponent( new AICFormationDescriptorComponent( "[hot|flanking [left|right]|beam <compass>|cold]", "Azimuth" ) );
}
return phrase;
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
if ( formation.hasUnitBreakdown() ) {
for ( const [ i, unitRef ] of formation.unitBreakdown.entries() ) {
let phrase = new AICFormationDescriptorPhrase();
phrase.addComponent( new AICFormationDescriptorComponent( unitRef, "Unit reference" ) );
if ( i === 0 ) {
this.addUnitInformation( formation, contextData, phrase, { "omitTrack": true } );
} else {
phrase.addComponent( new AICFormationDescriptorComponent( "<altitude>" ) );
}
phrase.addComponent( new AICFormationDescriptorComponent( "hostile" ) );
this.addPhrase( phrase );
}
} else {
this.addPhrase(
this.addUnitInformation( formation, contextData, new AICFormationDescriptorPhrase() )
);
}
return this;
}
}

View File

@ -1,35 +0,0 @@
import { AICFormation } from "../aicformation";
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
export class AICFormationDescriptorSection_Who extends AICFormationDescriptorSection {
label = "Who";
name = "who";
constructor() {
super();
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
let phrase = new AICFormationDescriptorPhrase();
if ( contextData.control === "tactical" ) {
phrase.addComponent( new AICFormationDescriptorComponent( "<their callsign>", "Their callsign" ) );
}
phrase.addComponent( new AICFormationDescriptorComponent( contextData.aicCallsign, "Your callsign" ) );
this.addPhrase( phrase );
return this;
}
}

View File

@ -1,188 +0,0 @@
import { getMissionHandler } from "..";
import { convertDateAndTimeToDate } from "../other/utils";
import { getConnected } from "../server/server";
import { ATCBoard } from "./atcboard";
import { ATCBoardGround } from "./board/ground";
import { ATCBoardTower } from "./board/tower";
export interface FlightInterface {
assignedSpeed: any;
assignedAltitude : any;
id : string;
boardId : string;
name : string;
order : number;
status : "unknown";
takeoffTime : number;
unitId : number;
}
class ATCDataHandler {
#atc:ATC;
#flights:{[key:string]: FlightInterface} = {};
#updateInterval:number|undefined = undefined;
#updateIntervalDelay:number = 2500; // Wait between unit update requests
constructor( atc:ATC ) {
this.#atc = atc;
}
getFlights( boardId:string ) {
return Object.values( this.#flights ).reduce( ( acc:{[key:string]: FlightInterface}, flight ) => {
if ( flight.boardId === boardId ) {
acc[ flight.id ] = flight;
}
return acc;
}, {} );
}
startUpdates() {
this.#updateInterval = window.setInterval( () => {
if ( !getConnected() ) {
return;
}
const aBoardIsVisible = this.#atc.getBoards().some( board => board.boardIsVisible() );
if ( aBoardIsVisible ) {
fetch( '/api/atc/flight', {
method: 'GET',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
}
})
.then( response => response.json() )
.then( data => {
this.setFlights( data );
});
}
}, this.#updateIntervalDelay );
}
setFlights( flights:{[key:string]: any} ) {
this.#flights = flights;
}
stopUpdates() {
clearInterval( this.#updateInterval );
}
}
export class ATC {
#boards:ATCBoard[] = [];
#dataHandler:ATCDataHandler;
#initDate:Date = new Date();
constructor() {
this.#dataHandler = new ATCDataHandler( this );
this.lookForBoards();
}
addBoard<T extends ATCBoard>( board:T ) {
board.startUpdates();
this.#boards.push( board );
}
getBoards() {
return this.#boards;
}
getDataHandler() {
return this.#dataHandler;
}
getMissionElapsedSeconds() : number {
return new Date().getTime() - this.#initDate.getTime();
}
getMissionStartDateTime() : Date {
return new Date( 1990, 3, 1, 18, 0, 0 );
}
getMissionDate() : Date {
return convertDateAndTimeToDate(getMissionHandler().getDateAndTime());
}
lookForBoards() {
document.querySelectorAll( ".ol-strip-board" ).forEach( board => {
if ( board instanceof HTMLElement ) {
switch ( board.dataset.boardType ) {
case "ground":
this.addBoard( new ATCBoardGround( this, board ) );
return;
case "tower":
this.addBoard( new ATCBoardTower( this, board ) );
return;
default:
console.warn( "Unknown board type for ATC board, got: " + board.dataset.boardType );
}
}
});
}
startUpdates() {
this.#dataHandler.startUpdates();
}
stopUpdates() {
this.#dataHandler.stopUpdates();
}
}

View File

@ -1,527 +0,0 @@
import { Dropdown } from "../controls/dropdown";
import { zeroAppend } from "../other/utils";
import { ATC } from "./atc";
import { Unit } from "../unit/unit";
import { getMissionHandler, getUnitsManager } from "..";
import Sortable from "sortablejs";
import { FlightInterface } from "./atc";
import { getConnected } from "../server/server";
export interface StripBoardStripInterface {
"id": string,
"element": HTMLElement,
"dropdowns": {[key:string]: Dropdown},
"isDeleted"?: boolean,
"unitId": number
}
export abstract class ATCBoard {
#atc:ATC;
#boardId:string = "";
#templates: {[key:string]: string} = {};
// Elements
#boardElement:HTMLElement;
#clockElement:HTMLElement;
#stripBoardElement:HTMLElement;
// Content
#isAddFlightByClickEnabled:boolean = false;
#strips:{[key:string]: StripBoardStripInterface} = {};
#unitIdsBeingMonitored:number[] = [];
// Update timing
#updateInterval:number|undefined = undefined;
#updateIntervalDelay:number = 1000;
constructor( atc:ATC, boardElement:HTMLElement, options?:{[key:string]: any} ) {
options = options || {};
this.#atc = atc;
this.#boardElement = boardElement;
this.#stripBoardElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-strips" );
this.#clockElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-clock" );
new MutationObserver( () => {
if ( this.boardIsVisible() ) {
this.startUpdates();
} else {
this.stopUpdates();
}
}).observe( this.getBoardElement(), {
"attributes": true,
"childList": false,
"subtree": false
});
new Sortable( this.getStripBoardElement(), {
"handle": ".handle",
"onUpdate": ev => {
const order = [].slice.call( this.getStripBoardElement().children ).map( ( strip:HTMLElement ) => {
return strip.dataset.flightId
});
fetch( '/api/atc/flight/order', {
method: 'POST',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify({
"boardId" : this.getBoardId(),
"order" : order
})
});
}
});
window.setInterval( () => {
if ( !getConnected() ) {
return;
}
this.updateClock();
}, 1000 );
if ( this.#boardElement.classList.contains( "ol-draggable" ) ) {
let options:any = {};
let handle = this.#boardElement.querySelector( ".handle" );
if ( handle instanceof HTMLElement ) {
options.handle = handle;
}
}
this.#setupAddFlight();
// this.#_setupDemoData();
}
addFlight( unit:Unit ) {
const baseData = unit.getData();
const unitCanBeAdded = () => {
if ( unit.getCategory() !== "Aircraft" ) {
return false;
}
if ( baseData.controlled === true ) {
// return false;
}
if ( this.#unitIdsBeingMonitored.includes( unit.ID ) ) {
return false;
}
return true;
}
if ( !unitCanBeAdded() ) {
return;
}
this.#unitIdsBeingMonitored.push( unit.ID );
return fetch( '/api/atc/flight/', {
method: 'POST',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify({
"boardId" : this.getBoardId(),
"name" : baseData.unitName,
"unitId" : unit.ID
})
});
}
addStrip( strip:StripBoardStripInterface ) {
this.#strips[ strip.id ] = strip;
strip.element.querySelectorAll( "button.deleteFlight" ).forEach( btn => {
btn.addEventListener( "click", ev => {
ev.preventDefault();
this.deleteFlight( strip.id );
});
});
}
boardIsVisible() {
return ( !this.getBoardElement().classList.contains( "hide" ) );
}
calculateTimeToGo( fromTimestamp:number, toTimestamp:number ) {
let timestamp = ( toTimestamp - fromTimestamp ) / 1000;
const hasElapsed = ( timestamp < 0 ) ? true : false;
if ( hasElapsed ) {
timestamp = -( timestamp );
}
const hours = ( timestamp < 3600 ) ? "00" : zeroAppend( Math.floor( timestamp / 3600 ), 2 );
const rMinutes = timestamp % 3600;
const minutes = ( timestamp < 60 ) ? "00" : zeroAppend( Math.floor( rMinutes / 60 ), 2 );
const seconds = zeroAppend( Math.floor( rMinutes % 60 ), 2 );
return {
"elapsedMarker": ( hasElapsed ) ? "+" : "-",
"hasElapsed": hasElapsed,
"hours": hours,
"minutes": minutes,
"seconds": seconds,
"time": `${hours}:${minutes}:${seconds}`,
"totalSeconds": timestamp
};
}
deleteStrip( flightId:string ) {
if ( this.#strips.hasOwnProperty( flightId ) ) {
this.#strips[ flightId ].element.remove();
this.#strips[ flightId ].isDeleted = true;
window.setTimeout( () => {
delete this.#strips[ flightId ];
}, 10000 );
}
}
deleteFlight( flightId:string ) {
this.deleteStrip( flightId );
fetch( '/api/atc/flight/' + flightId, {
method: 'DELETE',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify({
"boardId": this.getBoardId()
})
});
}
getATC() {
return this.#atc;
}
getBoardElement() {
return this.#boardElement;
}
getBoardId(): string {
return this.getBoardElement().id;
}
getStripBoardElement() {
return this.#stripBoardElement;
}
getStrips() {
return this.#strips;
}
getStrip( id:string ) {
return this.#strips[ id ] || false;
}
getTemplate( key:string ) {
return this.#templates[ key ] || false;
}
getUnitIdsBeingMonitored() {
return this.#unitIdsBeingMonitored;
}
setTemplates( templates:{[key:string]: string} ) {
this.#templates = templates;
}
#setupAddFlight() {
const toggleIsAddFlightByClickEnabled = () => {
this.#isAddFlightByClickEnabled = ( !this.#isAddFlightByClickEnabled );
this.getBoardElement().classList.toggle( "add-flight-by-click", this.#isAddFlightByClickEnabled );
}
document.addEventListener( "unitSelection", ( ev:CustomEventInit ) => {
if ( this.#isAddFlightByClickEnabled !== true ) {
return;
}
this.addFlight( ev.detail );
toggleIsAddFlightByClickEnabled();
});
const form = <HTMLElement>this.getBoardElement().querySelector( "form.ol-strip-board-add-flight" );
const suggestions = <HTMLElement>form.querySelector( ".ol-auto-suggest" );
const unitName = <HTMLInputElement>form.querySelector( "input[name='unitName']" );
const toggleSuggestions = ( bool:boolean ) => {
suggestions.toggleAttribute( "data-has-suggestions", bool );
}
let searchTimeout:number|null;
unitName.addEventListener( "keyup", ev => {
if ( searchTimeout ) {
clearTimeout( searchTimeout );
}
const resetSuggestions = () => {
suggestions.innerHTML = "";
toggleSuggestions( false );
}
resetSuggestions();
searchTimeout = window.setTimeout( () => {
const searchString = unitName.value.toLowerCase();
if ( searchString === "" ) {
return;
}
const units = getUnitsManager().getSelectableAircraft();
const unitIdsBeingMonitored = this.getUnitIdsBeingMonitored();
const results = Object.keys( units ).reduce( ( acc:Unit[], unitId:any ) => {
const unit = units[ unitId ];
const baseData = unit.getData();
if ( !unitIdsBeingMonitored.includes( parseInt( unitId ) ) && baseData.unitName.toLowerCase().indexOf( searchString ) > -1 ) {
acc.push( unit );
}
return acc;
}, [] );
toggleSuggestions( results.length > 0 );
results.forEach( unit => {
const baseData = unit.getData();
const a = document.createElement( "a" );
a.innerText = baseData.unitName;
a.addEventListener( "click", ev => {
this.addFlight( unit );
resetSuggestions();
unitName.value = "";
});
suggestions.appendChild( a );
});
}, 1000 );
});
form.querySelectorAll( ".add-flight-by-click" ).forEach( el => {
el.addEventListener( "click", ev => {
ev.preventDefault();
toggleIsAddFlightByClickEnabled();
});
});
}
sortFlights( flights:FlightInterface[] ) {
flights.sort( ( a, b ) => {
const aVal = a.order;
const bVal = b.order;
return ( aVal > bVal ) ? 1 : -1;
});
return flights;
}
startUpdates() {
if ( !this.boardIsVisible() ) {
return;
}
this.#updateInterval = window.setInterval( () => {
if ( !getConnected() ) {
return;
}
this.update();
}, this.#updateIntervalDelay );
}
stopUpdates() {
clearInterval( this.#updateInterval );
}
timestampToLocaleTime( timestamp:number ) {
return ( timestamp === -1 ) ? "-" : new Date( timestamp ).toLocaleTimeString();
}
timeToGo( timestamp:number ) {
const timeData = this.calculateTimeToGo( this.getATC().getMissionDate().getTime(), timestamp );
return ( timestamp === -1 ) ? "-" : timeData.elapsedMarker + timeData.time;
}
protected update() {
console.warn( "No custom update method defined." );
}
updateClock() {
const missionTime = this.#atc.getMissionDate().getTime();
const timeDiff = new Date().getTime() - getMissionHandler().getDateAndTime().elapsedTime;
const nowDate = new Date( missionTime + timeDiff );
this.#clockElement.innerText = nowDate.toLocaleTimeString();
}
updateFlight( flightId:string, reqBody:object ) {
return fetch( '/api/atc/flight/' + flightId, {
method: 'PATCH',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify( reqBody )
});
}
#_setupDemoData() {
fetch( '/api/atc/flight/', {
method: 'POST',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify({
"boardId" : this.getBoardId(),
"name" : this.getBoardId() + " 1",
"unitId" : 1
})
});
// fetch( '/api/atc/flight/', {
// method: 'POST',
// headers: {
// 'Accept': '*/*',
// 'Content-Type': 'application/json'
// },
// "body": JSON.stringify({
// "boardId" : this.getBoardId(),
// "name" : this.getBoardId() + " 2",
// "unitId" : 2
// })
// });
// fetch( '/api/atc/flight/', {
// method: 'POST',
// headers: {
// 'Accept': '*/*',
// 'Content-Type': 'application/json'
// },
// "body": JSON.stringify({
// "boardId" : this.getBoardId(),
// "name" : this.getBoardId() + " 3",
// "unitId" : 9
// })
// });
}
}

View File

@ -1,200 +0,0 @@
import { Dropdown } from "../../controls/dropdown";
import { ATC } from "../atc";
import { ATCBoard } from "../atcboard";
export class ATCBoardGround extends ATCBoard {
constructor( atc:ATC, element:HTMLElement ) {
super( atc, element );
}
update() {
const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) );
const stripBoard = this.getStripBoardElement();
const missionTime = this.getATC().getMissionDate().getTime();
for( const strip of stripBoard.children ) {
strip.toggleAttribute( "data-updating", true );
}
flights.forEach( flight => {
let strip = this.getStrip( flight.id );
if ( !strip ) {
const template = `<div class="ol-strip-board-strip" data-flight-id="${flight.id}" data-flight-status="${flight.status}">
<div class="handle"></div>
<div data-point="name">${flight.name}</div>
<div id="flight-status-${flight.id}" class="ol-select narrow" data-point="status">
<div class="ol-select-value">${flight.status}</div>
<div class="ol-select-options"></div>
</div>
<div data-point="takeoffTime"><input type="text" name="takeoffTime" value="${this.timestampToLocaleTime( flight.takeoffTime )}" /></div>
<div data-point="timeToGo">${this.timeToGo( flight.takeoffTime )}</div>
<button class="deleteFlight">&times;</button>
</div>`;
stripBoard.insertAdjacentHTML( "beforeend", template );
strip = {
"id": flight.id,
"element": <HTMLElement>stripBoard.lastElementChild,
"dropdowns": {},
"unitId": -1
};
strip.element.querySelectorAll( ".ol-select" ).forEach( select => {
switch( select.getAttribute( "data-point" ) ) {
case "status":
strip.dropdowns.status = new Dropdown( select.id, ( value:string, ev:MouseEvent ) => {
fetch( '/api/atc/flight/' + flight.id, {
method: 'PATCH',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify({
"status": value
})
});
}, [
"unknown", "checkedin", "readytotaxi", "clearedtotaxi", "halted", "terminated"
]);
break;
}
});
strip.element.querySelectorAll( `input[type="text"]` ).forEach( input => {
if ( input instanceof HTMLInputElement ) {
input.addEventListener( "blur", ( ev ) => {
const target = ev.target;
if ( target instanceof HTMLInputElement ) {
if ( /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/.test( target.value ) ) {
target.value += ":00";
}
const value = target.value;
if ( value === target.dataset.previousValue ) {
return;
} else if ( value === "" ) {
this.#updateTakeoffTime( flight.id, -1 );
} else if ( /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/.test( value ) ) {
let [ hours, minutes, seconds ] = value.split( ":" ).map( str => parseInt( str ) );
const missionStart = this.getATC().getMissionStartDateTime();
this.#updateTakeoffTime( flight.id, new Date(
missionStart.getFullYear(),
missionStart.getMonth(),
missionStart.getDate(),
hours,
minutes,
seconds
).getTime() );
} else {
target.value === target.dataset.previousValue
}
}
});
}
});
this.addStrip( strip );
} else {
if ( flight.status !== strip.element.getAttribute( "data-flight-status" ) ) {
strip.element.setAttribute( "data-flight-status", flight.status );
strip.dropdowns.status.selectText( flight.status );
}
strip.element.querySelectorAll( `input[name="takeoffTime"]:not(:focus)` ).forEach( el => {
if ( el instanceof HTMLInputElement ) {
el.value = this.timestampToLocaleTime( flight.takeoffTime );
el.dataset.previousValue = el.value;
}
});
strip.element.querySelectorAll( `[data-point="timeToGo"]` ).forEach( el => {
if ( flight.takeoffTime > 0 && this.calculateTimeToGo( missionTime, flight.takeoffTime ).totalSeconds <= 120 ) {
strip.element.setAttribute( "data-time-warning", "level-1" );
}
if ( el instanceof HTMLElement ) {
el.innerText = this.timeToGo( flight.takeoffTime );
}
});
}
strip.element.toggleAttribute( "data-updating", false );
});
stripBoard.querySelectorAll( `[data-updating]` ).forEach( strip => {
this.deleteStrip( strip.getAttribute( "data-flight-id" ) || "" );
});
}
#updateTakeoffTime = function( flightId:string, time:number ) {
fetch( '/api/atc/flight/' + flightId, {
method: 'PATCH',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify({
"takeoffTime": time
})
});
}
}

View File

@ -1,194 +0,0 @@
import { getUnitsManager } from "../..";
import { Dropdown } from "../../controls/dropdown";
import { mToFt, msToKnots } from "../../other/utils";
import { ATC } from "../atc";
import { ATCBoard } from "../atcboard";
export class ATCBoardTower extends ATCBoard {
constructor( atc:ATC, element:HTMLElement ) {
super( atc, element );
}
update() {
const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) );
const missionTime = this.getATC().getMissionDate().getTime();
const selectableUnits = getUnitsManager().getSelectableAircraft();
const stripBoard = this.getStripBoardElement();
for( const strip of stripBoard.children ) {
strip.toggleAttribute( "data-updating", true );
}
flights.forEach( flight => {
let strip = this.getStrip( flight.id );
if ( strip.isDeleted === true ) {
return;
}
const flightData = {
latitude: -1,
longitude: -1,
altitude: -1,
heading: -1,
speed: -1,
...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getData() : {} )
};
if ( !strip ) {
const template = `<div class="ol-strip-board-strip" data-flight-id="${flight.id}" data-flight-status="${flight.status}">
<div class="handle"></div>
<div data-point="name"><a href="#" class="select-unit">${flight.name}</a></div>
<div data-point="assignedAltitude"><input type="text" name="assignedAltitude" value="${flight.assignedAltitude}" size="2" /> 000</div>
<div data-point="altitude">-</div>
<div data-point="assignedSpeed"><input type="text" name="assignedSpeed" value="${flight.assignedSpeed}" size="3" /></div>
<div data-point="speed">-</div>
<button class="deleteFlight">&times;</button>
</div>`;
stripBoard.insertAdjacentHTML( "beforeend", template );
strip = {
"id": flight.id,
"element": <HTMLElement>stripBoard.lastElementChild,
"dropdowns": {},
"unitId": flight.unitId
};
strip.element.querySelectorAll( `input[type="text"]` ).forEach( input => {
if ( input instanceof HTMLInputElement ) {
switch ( input.name ) {
case "assignedAltitude":
input.addEventListener( "change", ( ev ) => {
let val = parseInt( input.value.replace( /[^\d]/g, "" ) );
if ( isNaN( val ) || val < 0 || val > 40 ) {
val = 0;
}
this.updateFlight( flight.id, {
"assignedAltitude": val
});
});
break;
case "assignedSpeed":
input.addEventListener( "change", ( ev ) => {
let val = parseInt( input.value.replace( /[^\d]/g, "" ) );
if ( isNaN( val ) || val < 0 || val > 750 ) {
val = 0;
}
this.updateFlight( flight.id, {
"assignedSpeed": val
});
});
break;
}
}
});
strip.element.querySelectorAll( ".select-unit" ).forEach( el => {
el.addEventListener( "click", ev => {
ev.preventDefault();
getUnitsManager().selectUnit( flight.unitId );
});
});
this.addStrip( strip );
} else {
//
// Altitude
//
let assignedAltitude = <HTMLInputElement>strip.element.querySelector( `input[name="assignedAltitude"]`);
if ( !assignedAltitude.matches( ":focus" ) && assignedAltitude.value !== flight.assignedAltitude ) {
assignedAltitude.value = flight.assignedAltitude;
}
flightData.altitude = Math.floor( mToFt(flightData.altitude) );
strip.element.querySelectorAll( `[data-point="altitude"]` ).forEach( el => {
if ( el instanceof HTMLElement ) {
el.innerText = "" + flightData.altitude;
}
});
const altitudeDelta = ( flight.assignedAltitude === 0 ) ? 0 : ( flight.assignedAltitude * 1000 ) - flightData.altitude;
strip.element.toggleAttribute( "data-altitude-assigned", ( flight.assignedAltitude > 0 ) );
strip.element.toggleAttribute( "data-warning-altitude", ( altitudeDelta >= 300 || altitudeDelta <= -300 ) );
//
// Speed
//
let assignedSpeed = <HTMLInputElement>strip.element.querySelector( `input[name="assignedSpeed"]`);
if ( !assignedSpeed.matches( ":focus" ) && assignedSpeed.value !== flight.assignedSpeed ) {
assignedSpeed.value = flight.assignedSpeed;
}
flightData.speed = Math.floor( msToKnots(flightData.speed) );
strip.element.querySelectorAll( `[data-point="speed"]` ).forEach( el => {
if ( el instanceof HTMLElement ) {
el.innerText = "" + flightData.speed;
}
});
const speedDelta = ( flight.assignedSpeed === 0 ) ? 0 : flight.assignedSpeed - flightData.speed;
strip.element.toggleAttribute( "data-speed-assigned", ( flight.assignedSpeed > 0 ) );
strip.element.toggleAttribute( "data-warning-speed", ( speedDelta >= 25 || speedDelta <= -25 ) );
}
strip.element.toggleAttribute( "data-updating", false );
});
stripBoard.querySelectorAll( `[data-updating]` ).forEach( strip => {
this.deleteStrip( strip.getAttribute( "data-flight-id" ) || "" );
});
}
}

View File

@ -1,61 +0,0 @@
import { getUnitsManager } from "..";
import { Panel } from "../panels/panel";
import { Unit } from "../unit/unit";
export class UnitDataTable extends Panel {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
this.hide();
}
update() {
var units = getUnitsManager().getUnits();
const unitsArray = Object.values(units).sort((a: Unit, b: Unit) => {
const aVal = a.getUnitName()?.toLowerCase();
const bVal = b.getUnitName()?.toLowerCase();
if (aVal > bVal) {
return 1;
} else if (bVal > aVal) {
return -1;
} else {
return 0;
}
});
function addRow(parentEl: HTMLElement, columns: string[]) {
const rowDiv = document.createElement("div");
for (const item of columns) {
const div = document.createElement("div");
div.innerText = item;
rowDiv.appendChild(div);
}
parentEl.appendChild(rowDiv);
}
const el = <HTMLElement> this.getElement().querySelector("#unit-list");
if (el) {
el.innerHTML = "";
addRow(el, ["Callsign", "Name", "Category", "AI/Human"])
for (const unit of unitsArray) {
const dataset = [unit.getUnitName(), unit.getName(), unit.getCategory(), (unit.getControlled()) ? "AI" : "Human"];
addRow(el, dataset);
}
}
}
}

View File

@ -35,6 +35,9 @@ export class AirbaseSpawnContextMenu extends ContextMenu {
this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.hide();
}

View File

@ -1,12 +1,12 @@
import { LatLng } from "leaflet";
import { getMap, getMissionHandler, getUnitsManager } from "..";
import { GAME_MASTER, IADSTypes } from "../constants/constants";
import { CoalitionArea } from "../map/coalitionarea";
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
import { ContextMenu } from "./contextmenu";
import { Dropdown } from "../controls/dropdown";
import { Slider } from "../controls/slider";
import { Switch } from "../controls/switch";
import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
/** This context menu allows the user to edit or delete a CoalitionArea. Moreover, it allows the user to create a IADS automatically using the CoalitionArea as bounds. */

View File

@ -4,10 +4,10 @@ import { spawnExplosion, spawnSmoke } from "../server/server";
import { ContextMenu } from "./contextmenu";
import { Switch } from "../controls/switch";
import { GAME_MASTER } from "../constants/constants";
import { CoalitionArea } from "../map/coalitionarea";
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
import { AircraftSpawnMenu, GroundUnitSpawnMenu, HelicopterSpawnMenu, NavyUnitSpawnMenu } from "../controls/unitspawnmenu";
import { Airbase } from "../mission/airbase";
import { SmokeMarker } from "../map/smokemarker";
import { SmokeMarker } from "../map/markers/smokemarker";
/** The MapContextMenu is the main contextmenu shown to the user whenever it rightclicks on the map. It is the primary interaction method for the user.
* It allows to spawn units, create explosions and smoke, and edit CoalitionAreas.
@ -75,6 +75,11 @@ export class MapContextMenu extends ContextMenu {
this.#groundUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#navyUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#groundUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#navyUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.hide();
}

View File

@ -1,16 +1,16 @@
import { LatLng } from "leaflet";
import { Dropdown } from "./dropdown";
import { Slider } from "./slider";
import { UnitDatabase } from "../unit/unitdatabase";
import { UnitDatabase } from "../unit/databases/unitdatabase";
import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager } from "..";
import { GAME_MASTER } from "../constants/constants";
import { UnitSpawnOptions } from "../@types/unitdatabase";
import { Airbase } from "../mission/airbase";
import { ftToM } from "../other/utils";
import { aircraftDatabase } from "../unit/aircraftdatabase";
import { helicopterDatabase } from "../unit/helicopterdatabase";
import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { navyUnitDatabase } from "../unit/navyunitdatabase";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
export class UnitSpawnMenu {
#container: HTMLElement;
@ -418,7 +418,7 @@ export class AircraftSpawnMenu extends UnitSpawnMenu {
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
});
getMap().getMapContextMenu().hide();
this.getContainer().dispatchEvent(new Event("hide"));
}
}
}
@ -456,7 +456,7 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu {
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
});
getMap().getMapContextMenu().hide();
this.getContainer().dispatchEvent(new Event("hide"));
}
}
}
@ -493,7 +493,7 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu {
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
});
getMap().getMapContextMenu().hide();
this.getContainer().dispatchEvent(new Event("hide"));
}
}
}
@ -530,7 +530,7 @@ export class NavyUnitSpawnMenu extends UnitSpawnMenu {
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
});
getMap().getMapContextMenu().hide();
this.getContainer().dispatchEvent(new Event("hide"));
}
}
}

View File

@ -15,6 +15,8 @@ import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constant
import { ServerStatusPanel } from "./panels/serverstatuspanel";
import { WeaponsManager } from "./weapon/weaponsmanager";
import { ConfigParameters } from "./@types/dom";
import { CommandModeToolbar } from "./toolbars/commandmodetoolbar";
import { PrimaryToolbar } from "./toolbars/primaryToolbar";
/* Global data */
var activeCoalition: string = "blue";
@ -36,10 +38,15 @@ var mouseInfoPanel: MouseInfoPanel;
var logPanel: LogPanel;
var hotgroupPanel: HotgroupPanel;
/* UI Toolbars */
var primaryToolbar: PrimaryToolbar;
var commandModeToolbar: CommandModeToolbar;
/* Popups */
var infoPopup: Popup;
function setup() {
/* Initialize base functionalitites */
map = new Map('map-container');
@ -56,6 +63,10 @@ function setup() {
hotgroupPanel = new HotgroupPanel("hotgroup-panel");
logPanel = new LogPanel("log-panel");
/* Toolbars */
primaryToolbar = new PrimaryToolbar("primary-toolbar");
commandModeToolbar = new CommandModeToolbar("command-mode-toolbar");
/* Popups */
infoPopup = new Popup("info-popup");

View File

@ -1,8 +1,8 @@
import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet";
import { getMap, getMissionHandler, getUnitsManager } from "..";
import { getMap, getMissionHandler, getUnitsManager } from "../..";
import { CoalitionAreaHandle } from "./coalitionareahandle";
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
import { BLUE_COMMANDER, RED_COMMANDER } from "../constants/constants";
import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants";
export class CoalitionArea extends Polygon {
#coalition: string = "blue";

View File

@ -1,5 +1,5 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
import { CustomMarker } from "../markers/custommarker";
export class CoalitionAreaHandle extends CustomMarker {
constructor(latlng: LatLng) {

View File

@ -1,5 +1,5 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
import { CustomMarker } from "../markers/custommarker";
export class CoalitionAreaMiddleHandle extends CustomMarker {
constructor(latlng: LatLng) {

View File

@ -1,5 +1,5 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
import { CustomMarker } from "../markers/custommarker";
export class DrawingCursor extends CustomMarker {
constructor() {

View File

@ -8,15 +8,15 @@ import { Dropdown } from "../controls/dropdown";
import { Airbase } from "../mission/airbase";
import { Unit } from "../unit/unit";
import { bearing, createCheckboxOption } from "../other/utils";
import { DestinationPreviewMarker } from "./destinationpreviewmarker";
import { TemporaryUnitMarker } from "./temporaryunitmarker";
import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker";
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector'
import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTooltips, MOVE_UNIT, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes, SHOW_UNIT_LABELS } from "../constants/constants";
import { TargetMarker } from "./targetmarker";
import { CoalitionArea } from "./coalitionarea";
import { TargetMarker } from "./markers/targetmarker";
import { CoalitionArea } from "./coalitionarea/coalitionarea";
import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
import { DrawingCursor } from "./drawingcursor";
import { DrawingCursor } from "./coalitionarea/drawingcursor";
import { AirbaseSpawnContextMenu } from "../contextmenus/airbasespawnmenu";
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);

View File

@ -1,7 +1,7 @@
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
import { SVGInjector } from "@tanem/svg-injector";
import { getMap } from "..";
import { getMap } from "../..";
export class SmokeMarker extends CustomMarker {
#color: string;

View File

@ -1,9 +1,9 @@
import { CustomMarker } from "./custommarker";
import { DivIcon, LatLng } from "leaflet";
import { SVGInjector } from "@tanem/svg-injector";
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../other/utils";
import { isCommandExecuted } from "../server/server";
import { getMap } from "..";
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../../other/utils";
import { isCommandExecuted } from "../../server/server";
import { getMap } from "../..";
export class TemporaryUnitMarker extends CustomMarker {
#name: string;

View File

@ -1,5 +1,5 @@
import { DivIcon } from 'leaflet';
import { CustomMarker } from '../map/custommarker';
import { CustomMarker } from '../map/markers/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
export interface AirbaseOptions {

View File

@ -1,5 +1,5 @@
import { DivIcon } from "leaflet";
import { CustomMarker } from "../map/custommarker";
import { CustomMarker } from "../map/markers/custommarker";
import { SVGInjector } from "@tanem/svg-injector";
export class Bullseye extends CustomMarker {

View File

@ -5,11 +5,11 @@ import { Bullseye } from "./bullseye";
import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
import { refreshAll, setCommandModeOptions } from "../server/server";
import { Dropdown } from "../controls/dropdown";
import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
import { aircraftDatabase } from "../unit/aircraftdatabase";
import { helicopterDatabase } from "../unit/helicopterdatabase";
import { navyUnitDatabase } from "../unit/navyunitdatabase";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
export class MissionManager {
#bullseyes: { [name: string]: Bullseye } = {};

View File

@ -1,14 +1,14 @@
import { LatLng, Point, Polygon } from "leaflet";
import * as turf from "@turf/turf";
import { UnitDatabase } from "../unit/unitdatabase";
import { aircraftDatabase } from "../unit/aircraftdatabase";
import { helicopterDatabase } from "../unit/helicopterdatabase";
import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { UnitDatabase } from "../unit/databases/unitdatabase";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { Buffer } from "buffer";
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
import { Dropdown } from "../controls/dropdown";
import { UnitBlueprint } from "../@types/unitdatabase";
import { navyUnitDatabase } from "../unit/navyunitdatabase";
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
const φ1 = deg2rad(lat1); // φ, λ in radians

View File

@ -19,6 +19,7 @@ export class LogPanel extends Panel {
this.#open = !this.#open;
this.#queuedMessages = 0;
this.#updateHeader();
this.#calculateHeight();
if (this.#scrolledDown)
this.#scrollDown();
@ -89,6 +90,9 @@ export class LogPanel extends Panel {
#calculateHeight() {
const mouseInfoPanel = getMouseInfoPanel();
this.getElement().style.height = `${mouseInfoPanel.getElement().offsetTop - this.getElement().offsetTop - 10}px`;
if (this.#open)
this.getElement().style.height = `${mouseInfoPanel.getElement().offsetTop - this.getElement().offsetTop - 10}px`;
else
this.getElement().style.height = "fit-content";
}
}

View File

@ -2,7 +2,7 @@ import { SVGInjector } from "@tanem/svg-injector";
import { getUnitsManager } from "..";
import { Dropdown } from "../controls/dropdown";
import { Slider } from "../controls/slider";
import { aircraftDatabase } from "../unit/aircraftdatabase";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";
import { Switch } from "../controls/switch";

View File

@ -1,5 +1,5 @@
import { Ammo } from "../@types/unit";
import { aircraftDatabase } from "../unit/aircraftdatabase";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";

View File

@ -0,0 +1,5 @@
import { Toolbar } from "./toolbar";
export class CommandModeToolbar extends Toolbar {
// TODO move here all code about the command mode toolbar
}

View File

@ -0,0 +1,13 @@
import { Dropdown } from "../controls/dropdown";
import { Toolbar } from "./toolbar";
export class PrimaryToolbar extends Toolbar {
constructor(ID: string) {
super(ID);
// TODO move here all code about primary toolbar
/* The content of the dropdown is entirely defined in the .ejs file */
new Dropdown("app-icon", () => { });
}
}

View File

@ -0,0 +1,38 @@
export class Toolbar {
#element: HTMLElement
#visible: boolean = true;
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
this.#element = document.getElementById(ID) as HTMLElement;
}
show() {
this.#element.classList.toggle("hide", false);
this.#visible = true;
}
hide() {
this.#element.classList.toggle("hide", true);
this.#visible = false;
}
toggle() {
// Simple way to track if currently visible
if (this.#visible)
this.hide();
else
this.show();
}
getElement() {
return this.#element;
}
getVisible(){
return this.#visible;
}
}

View File

@ -1,5 +1,5 @@
import { getMissionHandler } from "..";
import { GAME_MASTER } from "../constants/constants";
import { getMissionHandler } from "../..";
import { GAME_MASTER } from "../../constants/constants";
import { UnitDatabase } from "./unitdatabase"
export class AircraftDatabase extends UnitDatabase {

View File

@ -1,5 +1,5 @@
import { getMissionHandler} from "..";
import { GAME_MASTER } from "../constants/constants";
import { getMissionHandler } from "../..";
import { GAME_MASTER } from "../../constants/constants";
import { UnitDatabase } from "./unitdatabase"
export class GroundUnitDatabase extends UnitDatabase {

View File

@ -1,5 +1,5 @@
import { getMissionHandler } from "..";
import { GAME_MASTER } from "../constants/constants";
import { getMissionHandler } from "../..";
import { GAME_MASTER } from "../../constants/constants";
import { UnitDatabase } from "./unitdatabase"
export class HelicopterDatabase extends UnitDatabase {

View File

@ -1,5 +1,5 @@
import { getMissionHandler } from "..";
import { GAME_MASTER } from "../constants/constants";
import { getMissionHandler } from "../..";
import { GAME_MASTER } from "../../constants/constants";
import { UnitDatabase } from "./unitdatabase"
export class NavyUnitDatabase extends UnitDatabase {

View File

@ -1,7 +1,7 @@
import { LatLng } from "leaflet";
import { getMissionHandler, getUnitsManager } from "..";
import { GAME_MASTER } from "../constants/constants";
import { UnitBlueprint } from "../@types/unitdatabase";
import { getMissionHandler, getUnitsManager } from "../..";
import { GAME_MASTER } from "../../constants/constants";
import { UnitBlueprint } from "../../@types/unitdatabase";
export class UnitDatabase {
blueprints: { [key: string]: UnitBlueprint } = {};

View File

@ -2,15 +2,15 @@ import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point } fro
import { getMap, getMissionHandler, getUnitsManager, getWeaponsManager } from '..';
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM } from '../other/utils';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server';
import { CustomMarker } from '../map/custommarker';
import { CustomMarker } from '../map/markers/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { UnitDatabase } from './unitdatabase';
import { TargetMarker } from '../map/targetmarker';
import { UnitDatabase } from './databases/unitdatabase';
import { TargetMarker } from '../map/markers/targetmarker';
import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_CONTACT_LINES, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, ObjectIconOptions, UnitData } from '../@types/unit';
import { DataExtractor } from '../server/dataextractor';
import { groundUnitDatabase } from './groundunitdatabase';
import { navyUnitDatabase } from './navyunitdatabase';
import { groundUnitDatabase } from './databases/groundunitdatabase';
import { navyUnitDatabase } from './databases/navyunitdatabase';
import { Weapon } from '../weapon/weapon';
import { LoadoutBlueprint } from '../@types/unitdatabase';

View File

@ -3,16 +3,16 @@ import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitsMana
import { Unit } from "./unit";
import { cloneUnits, deleteUnit, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server";
import { bearingAndDistanceToLatLng, deg2rad, getUnitDatabaseByCategory, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils";
import { CoalitionArea } from "../map/coalitionarea";
import { groundUnitDatabase } from "./groundunitdatabase";
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
import { groundUnitDatabase } from "./databases/groundunitdatabase";
import { DataIndexes, GAME_MASTER, IADSDensities, IDLE, MOVE_UNIT } from "../constants/constants";
import { DataExtractor } from "../server/dataextractor";
import { Contact, UnitData } from "../@types/unit";
import { citiesDatabase } from "./citiesDatabase";
import { aircraftDatabase } from "./aircraftdatabase";
import { helicopterDatabase } from "./helicopterdatabase";
import { navyUnitDatabase } from "./navyunitdatabase";
import { TemporaryUnitMarker } from "../map/temporaryunitmarker";
import { aircraftDatabase } from "./databases/aircraftdatabase";
import { helicopterDatabase } from "./databases/helicopterdatabase";
import { navyUnitDatabase } from "./databases/navyunitdatabase";
import { TemporaryUnitMarker } from "../map/markers/temporaryunitmarker";
/** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only
* result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows
@ -595,11 +595,7 @@ export class UnitsManager {
var unit = selectedUnits[idx];
units.push({ ID: unit.ID, location: unit.getPosition() });
}
cloneUnits(units, true, 0 /* No spawn points, we delete the original units */, () => {
units.forEach((unit: any) => {
deleteUnit(unit.ID, false, false);
});
});
cloneUnits(units, true, 0 /* No spawn points, we delete the original units */);
}
/***********************************************/

View File

@ -1,7 +1,7 @@
import { LatLng, DivIcon, Map } from 'leaflet';
import { getMap, getMissionHandler, getUnitsManager } from '..';
import { enumToCoalition, mToFt, msToKnots, rad2deg } from '../other/utils';
import { CustomMarker } from '../map/custommarker';
import { CustomMarker } from '../map/markers/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { DLINK, DataIndexes, GAME_MASTER, IRST, OPTIC, RADAR, VISUAL } from '../constants/constants';
import { ObjectIconOptions } from '../@types/unit';

View File

@ -22,11 +22,6 @@
<%- include('toolbars/commandmode.ejs') %>
</div>
<!-- AIC/ATC -->
<%- include('aic/aic.ejs') %>
<%- include('atc/atc.ejs') %>
<%- include('atc/unitdatatable.ejs') %>
<!-- Panels -->
<%- include('panels/unitcontrol.ejs') %>
<%- include('panels/unitinfo.ejs') %>