diff --git a/client/src/aic/AICFormation.ts b/client/src/aic/AICFormation.ts index e13d1acc..588f4326 100644 --- a/client/src/aic/AICFormation.ts +++ b/client/src/aic/AICFormation.ts @@ -1,6 +1,6 @@ -import { AICFormationContextDataInterface, AICFormationDescriptor } from "./AICFormationDescriptor"; -import { AICFormationDescriptorPhrase } from "./AICFormationDescriptorPhrase"; -import { AICFormationDescriptorSection } from "./AICFormationDescriptorSection"; +import { AICFormationContextDataInterface, AICFormationDescriptor } from "./aicformationdescriptor"; +import { AICFormationDescriptorPhrase } from "./aicformationdescriptorphrase"; +import { AICFormationDescriptorSection } from "./aicformationdescriptorsection"; export interface AICFormationInterface { "icon" : string, diff --git a/client/src/aic/AICFormation/Azimuth.ts b/client/src/aic/AICFormation/Azimuth.ts index 65a9a003..7db676ae 100644 --- a/client/src/aic/AICFormation/Azimuth.ts +++ b/client/src/aic/AICFormation/Azimuth.ts @@ -1,8 +1,8 @@ -import { AICFormation, AICFormationInterface } from "../AICFormation"; -import { AICFormationContextDataInterface } from "../AICFormationDescriptor"; -import { AICFormationDescriptorSection } from "../AICFormationDescriptorSection"; -import { AICFormationDescriptorComponent } from "../AICFormationDescriptorComponent"; -import { AICFormationDescriptorPhrase } from "../AICFormationDescriptorPhrase"; +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 { diff --git a/client/src/aic/AICFormation/Range.ts b/client/src/aic/AICFormation/Range.ts index 32e48557..08c00cd5 100644 --- a/client/src/aic/AICFormation/Range.ts +++ b/client/src/aic/AICFormation/Range.ts @@ -1,8 +1,8 @@ -import { AICFormation, AICFormationInterface } from "../AICFormation"; -import { AICFormationContextDataInterface } from "../AICFormationDescriptor"; -import { AICFormationDescriptorSection } from "../AICFormationDescriptorSection"; -import { AICFormationDescriptorComponent } from "../AICFormationDescriptorComponent"; -import { AICFormationDescriptorPhrase } from "../AICFormationDescriptorPhrase"; +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 { diff --git a/client/src/aic/AICFormation/Single.ts b/client/src/aic/AICFormation/Single.ts index dd9a8ae4..09830c40 100644 --- a/client/src/aic/AICFormation/Single.ts +++ b/client/src/aic/AICFormation/Single.ts @@ -1,5 +1,5 @@ -import { AICFormation, AICFormationInterface } from "../AICFormation"; -import { AICFormationContextDataInterface, AICFormationDescriptor } from "../AICFormationDescriptor"; +import { AICFormation, AICFormationInterface } from "../aicformation"; +import { AICFormationContextDataInterface, AICFormationDescriptor } from "../aicformationdescriptor"; export class AICFormation_Single extends AICFormation implements AICFormationInterface { diff --git a/client/src/aic/AICFormationDescriptor.ts b/client/src/aic/AICFormationDescriptor.ts index 8bc43c54..c7a5a563 100644 --- a/client/src/aic/AICFormationDescriptor.ts +++ b/client/src/aic/AICFormationDescriptor.ts @@ -1,9 +1,9 @@ -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"; +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 { diff --git a/client/src/aic/AICFormationDescriptorComponent/Distance.ts b/client/src/aic/AICFormationDescriptorComponent/Distance.ts index fe942ac6..2a3766f3 100644 --- a/client/src/aic/AICFormationDescriptorComponent/Distance.ts +++ b/client/src/aic/AICFormationDescriptorComponent/Distance.ts @@ -1,4 +1,4 @@ -import { AICFormationDescriptorComponent } from "../AICFormationDescriptorComponent"; +import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent"; export abstract class AICFormactionDescriptorComponent_Distance extends AICFormationDescriptorComponent { diff --git a/client/src/aic/AICFormationDescriptorComponent/Distance/Range.ts b/client/src/aic/AICFormationDescriptorComponent/Distance/Range.ts index 88a23446..ab8a58d3 100644 --- a/client/src/aic/AICFormationDescriptorComponent/Distance/Range.ts +++ b/client/src/aic/AICFormationDescriptorComponent/Distance/Range.ts @@ -1,4 +1,4 @@ -import { AICFormactionDescriptorComponent_Distance } from "../Distance"; +import { AICFormactionDescriptorComponent_Distance } from "../distance"; export class AICFormationDescriptorComponent_Distance_Range extends AICFormactionDescriptorComponent_Distance { diff --git a/client/src/aic/AICFormationDescriptorPhrase.ts b/client/src/aic/AICFormationDescriptorPhrase.ts index 8ac473ac..b2a519a0 100644 --- a/client/src/aic/AICFormationDescriptorPhrase.ts +++ b/client/src/aic/AICFormationDescriptorPhrase.ts @@ -1,6 +1,6 @@ -import { AICFormation } from "./AICFormation"; -import { AICFormationContextDataInterface } from "./AICFormationDescriptor"; -import { AICFormationDescriptorComponent } from "./AICFormationDescriptorComponent"; +import { AICFormation } from "./aicformation"; +import { AICFormationContextDataInterface } from "./aicformationdescriptor"; +import { AICFormationDescriptorComponent } from "./aicformationdescriptorcomponent"; export interface AICFormationDescriptorPhraseInterface { "generate" : CallableFunction, diff --git a/client/src/aic/AICFormationDescriptorSection.ts b/client/src/aic/AICFormationDescriptorSection.ts index ede57edd..bcd2f3c3 100644 --- a/client/src/aic/AICFormationDescriptorSection.ts +++ b/client/src/aic/AICFormationDescriptorSection.ts @@ -1,6 +1,6 @@ -import { AICFormation } from "./AICFormation"; -import { AICFormationContextDataInterface } from "./AICFormationDescriptor"; -import { AICFormationDescriptorPhrase } from "./AICFormationDescriptorPhrase"; +import { AICFormation } from "./aicformation"; +import { AICFormationContextDataInterface } from "./aicformationdescriptor"; +import { AICFormationDescriptorPhrase } from "./aicformationdescriptorphrase"; export interface AICFormationDescriptorSectionInterface { "generate" : CallableFunction, diff --git a/client/src/aic/AICFormationDescriptorSection/Formation.ts b/client/src/aic/AICFormationDescriptorSection/Formation.ts index 9b742151..c77cb4be 100644 --- a/client/src/aic/AICFormationDescriptorSection/Formation.ts +++ b/client/src/aic/AICFormationDescriptorSection/Formation.ts @@ -1,8 +1,8 @@ -import { AICFormation } from "../AICFormation"; -import { AICFormationContextDataInterface } from "../AICFormationDescriptor"; -import { AICFormationDescriptorSection } from "../AICFormationDescriptorSection"; -import { AICFormationDescriptorComponent } from "../AICFormationDescriptorComponent"; -import { AICFormationDescriptorPhrase } from "../AICFormationDescriptorPhrase"; +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 { diff --git a/client/src/aic/AICFormationDescriptorSection/NumGroups.ts b/client/src/aic/AICFormationDescriptorSection/NumGroups.ts index 2714b8f6..81b92a86 100644 --- a/client/src/aic/AICFormationDescriptorSection/NumGroups.ts +++ b/client/src/aic/AICFormationDescriptorSection/NumGroups.ts @@ -1,8 +1,8 @@ -import { AICFormation } from "../AICFormation"; -import { AICFormationContextDataInterface } from "../AICFormationDescriptor"; -import { AICFormationDescriptorSection } from "../AICFormationDescriptorSection"; -import { AICFormationDescriptorComponent } from "../AICFormationDescriptorComponent"; -import { AICFormationDescriptorPhrase } from "../AICFormationDescriptorPhrase"; +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 { diff --git a/client/src/aic/AICFormationDescriptorSection/Unit.ts b/client/src/aic/AICFormationDescriptorSection/Unit.ts index 70743ee9..749fe625 100644 --- a/client/src/aic/AICFormationDescriptorSection/Unit.ts +++ b/client/src/aic/AICFormationDescriptorSection/Unit.ts @@ -1,8 +1,8 @@ -import { AICFormation } from "../AICFormation"; -import { AICFormationContextDataInterface } from "../AICFormationDescriptor"; -import { AICFormationDescriptorSection } from "../AICFormationDescriptorSection"; -import { AICFormationDescriptorComponent } from "../AICFormationDescriptorComponent"; -import { AICFormationDescriptorPhrase } from "../AICFormationDescriptorPhrase"; +import { AICFormation } from "../aicformation"; +import { AICFormationContextDataInterface } from "../aicformationdescriptor"; +import { AICFormationDescriptorSection } from "../aicformationdescriptorsection"; +import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent"; +import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase"; interface addUnitInformationInterface { omitTrack?: boolean diff --git a/client/src/aic/AICFormationDescriptorSection/Who.ts b/client/src/aic/AICFormationDescriptorSection/Who.ts index 5856c871..244b0558 100644 --- a/client/src/aic/AICFormationDescriptorSection/Who.ts +++ b/client/src/aic/AICFormationDescriptorSection/Who.ts @@ -1,8 +1,8 @@ -import { AICFormation } from "../AICFormation"; -import { AICFormationContextDataInterface } from "../AICFormationDescriptor"; -import { AICFormationDescriptorSection } from "../AICFormationDescriptorSection"; -import { AICFormationDescriptorComponent } from "../AICFormationDescriptorComponent"; -import { AICFormationDescriptorPhrase } from "../AICFormationDescriptorPhrase"; +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 { diff --git a/client/src/aic/aic.ts b/client/src/aic/aic.ts index a28aaa9d..770fd695 100644 --- a/client/src/aic/aic.ts +++ b/client/src/aic/aic.ts @@ -1,8 +1,8 @@ -import { ToggleableFeature } from "../ToggleableFeature"; -import { AICFormation_Azimuth } from "./AICFormation/Azimuth"; -import { AICFormation_Range } from "./AICFormation/Range"; -import { AICFormation_Single } from "./AICFormation/Single"; -import { AICFormationDescriptorSection } from "./AICFormationDescriptorSection"; +import { ToggleableFeature } from "../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 { diff --git a/client/src/aic/aicformation.ts b/client/src/aic/aicformation.ts new file mode 100644 index 00000000..588f4326 --- /dev/null +++ b/client/src/aic/aicformation.ts @@ -0,0 +1,54 @@ +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; + } + + +} \ No newline at end of file diff --git a/client/src/aic/aicformation/azimuth.ts b/client/src/aic/aicformation/azimuth.ts new file mode 100644 index 00000000..7db676ae --- /dev/null +++ b/client/src/aic/aicformation/azimuth.ts @@ -0,0 +1,38 @@ +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" = [ " group", " group" ]; + + constructor() { + + super(); + + } + + addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) { + + switch ( section.name ) { + + case "formation": + + phrase.addComponent( new AICFormationDescriptorComponent( "" ) ); + phrase.addComponent( new AICFormationDescriptorComponent( "track " ) ); + + } + + return phrase; + + + } + +} \ No newline at end of file diff --git a/client/src/aic/aicformation/range.ts b/client/src/aic/aicformation/range.ts new file mode 100644 index 00000000..08c00cd5 --- /dev/null +++ b/client/src/aic/aicformation/range.ts @@ -0,0 +1,38 @@ +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( "" ) ); + phrase.addComponent( new AICFormationDescriptorComponent( "track " ) ); + + + } + + return phrase; + + } + +} \ No newline at end of file diff --git a/client/src/aic/aicformation/single.ts b/client/src/aic/aicformation/single.ts new file mode 100644 index 00000000..09830c40 --- /dev/null +++ b/client/src/aic/aicformation/single.ts @@ -0,0 +1,24 @@ +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" = []; + + constructor() { + + super(); + + } + + + showFormationNameInDescriptor() { + return false; + } + +} \ No newline at end of file diff --git a/client/src/aic/aicformationdescriptor.ts b/client/src/aic/aicformationdescriptor.ts new file mode 100644 index 00000000..c7a5a563 --- /dev/null +++ b/client/src/aic/aicformationdescriptor.ts @@ -0,0 +1,55 @@ +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; + + } + + +} \ No newline at end of file diff --git a/client/src/aic/aicformationdescriptorcomponent.ts b/client/src/aic/aicformationdescriptorcomponent.ts new file mode 100644 index 00000000..da194576 --- /dev/null +++ b/client/src/aic/aicformationdescriptorcomponent.ts @@ -0,0 +1,18 @@ +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; + + } + +} \ No newline at end of file diff --git a/client/src/aic/aicformationdescriptorcomponent/distance.ts b/client/src/aic/aicformationdescriptorcomponent/distance.ts new file mode 100644 index 00000000..2a3766f3 --- /dev/null +++ b/client/src/aic/aicformationdescriptorcomponent/distance.ts @@ -0,0 +1,9 @@ +import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent"; + +export abstract class AICFormactionDescriptorComponent_Distance extends AICFormationDescriptorComponent { + + constructor( value:string, label?:string ) { + super( value, label ); + } + +} \ No newline at end of file diff --git a/client/src/aic/aicformationdescriptorcomponent/distance/range.ts b/client/src/aic/aicformationdescriptorcomponent/distance/range.ts new file mode 100644 index 00000000..ab8a58d3 --- /dev/null +++ b/client/src/aic/aicformationdescriptorcomponent/distance/range.ts @@ -0,0 +1,9 @@ +import { AICFormactionDescriptorComponent_Distance } from "../distance"; + +export class AICFormationDescriptorComponent_Distance_Range extends AICFormactionDescriptorComponent_Distance { + + constructor( value:string, label?:string ) { + super( value, label ); + } + +} \ No newline at end of file diff --git a/client/src/aic/aicformationdescriptorphrase.ts b/client/src/aic/aicformationdescriptorphrase.ts new file mode 100644 index 00000000..b2a519a0 --- /dev/null +++ b/client/src/aic/aicformationdescriptorphrase.ts @@ -0,0 +1,40 @@ +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; + + } + + +} \ No newline at end of file diff --git a/client/src/aic/aicformationdescriptorsection.ts b/client/src/aic/aicformationdescriptorsection.ts new file mode 100644 index 00000000..bcd2f3c3 --- /dev/null +++ b/client/src/aic/aicformationdescriptorsection.ts @@ -0,0 +1,40 @@ +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; + } + + +} \ No newline at end of file diff --git a/client/src/aic/aicformationdescriptorsection/formation.ts b/client/src/aic/aicformationdescriptorsection/formation.ts new file mode 100644 index 00000000..c77cb4be --- /dev/null +++ b/client/src/aic/aicformationdescriptorsection/formation.ts @@ -0,0 +1,39 @@ +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; + + } + +} \ No newline at end of file diff --git a/client/src/aic/aicformationdescriptorsection/numgroups.ts b/client/src/aic/aicformationdescriptorsection/numgroups.ts new file mode 100644 index 00000000..81b92a86 --- /dev/null +++ b/client/src/aic/aicformationdescriptorsection/numgroups.ts @@ -0,0 +1,35 @@ +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; + + } + +} \ No newline at end of file diff --git a/client/src/aic/aicformationdescriptorsection/unit.ts b/client/src/aic/aicformationdescriptorsection/unit.ts new file mode 100644 index 00000000..749fe625 --- /dev/null +++ b/client/src/aic/aicformationdescriptorsection/unit.ts @@ -0,0 +1,83 @@ +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" ) ); + phrase.addComponent( new AICFormationDescriptorComponent( "", "Range" ) ); + phrase.addComponent( new AICFormationDescriptorComponent( "", "Altitude" ) ); + + if ( contextData.control === "broadcast" ) { + if ( !options.hasOwnProperty( "omitTrack" ) || options.omitTrack !== true ) { + phrase.addComponent( new AICFormationDescriptorComponent( "track ", "Tracking" ) ); + } + } else { + phrase.addComponent( new AICFormationDescriptorComponent( "[hot|flanking [left|right]|beam |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( "" ) ); + } + + phrase.addComponent( new AICFormationDescriptorComponent( "hostile" ) ); + + this.addPhrase( phrase ); + + + } + + } else { + + this.addPhrase( + this.addUnitInformation( formation, contextData, new AICFormationDescriptorPhrase() ) + ); + + } + + return this; + + } + + +} \ No newline at end of file diff --git a/client/src/aic/aicformationdescriptorsection/who.ts b/client/src/aic/aicformationdescriptorsection/who.ts new file mode 100644 index 00000000..244b0558 --- /dev/null +++ b/client/src/aic/aicformationdescriptorsection/who.ts @@ -0,0 +1,35 @@ +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" ) ); + } + + phrase.addComponent( new AICFormationDescriptorComponent( contextData.aicCallsign, "Your callsign" ) ); + + this.addPhrase( phrase ); + + return this; + + } + +} \ No newline at end of file diff --git a/client/src/atc/ATC.ts b/client/src/atc/ATC.ts index c409b02e..14710071 100644 --- a/client/src/atc/ATC.ts +++ b/client/src/atc/ATC.ts @@ -1,6 +1,6 @@ -import { ToggleableFeature } from "../ToggleableFeature"; +import { ToggleableFeature } from "../toggleablefeature"; import Sortable from 'sortablejs'; -import { ATCFLightList } from "./FlightList"; +import { ATCFLightList } from "./flightlist"; export class ATC extends ToggleableFeature { diff --git a/client/src/atc/ATCMockAPI/Flights.ts b/client/src/atc/ATCMockAPI/Flights.ts index b710b016..6d6b6435 100644 --- a/client/src/atc/ATCMockAPI/Flights.ts +++ b/client/src/atc/ATCMockAPI/Flights.ts @@ -1,4 +1,4 @@ -import { ATCMockAPI } from "../ATCMockAPI"; +import { ATCMockAPI } from "../atcmockapi"; export class ATCMockAPI_Flights extends ATCMockAPI { diff --git a/client/src/atc/FlightList.ts b/client/src/atc/FlightList.ts index a24a55e2..36185eef 100644 --- a/client/src/atc/FlightList.ts +++ b/client/src/atc/FlightList.ts @@ -1,4 +1,4 @@ -import { ATCMockAPI_Flights } from "./ATCMockAPI/Flights"; +import { ATCMockAPI_Flights } from "./atcmockapi/flights"; export class ATCFLightList { diff --git a/client/src/atc/atc.ts b/client/src/atc/atc.ts new file mode 100644 index 00000000..14710071 --- /dev/null +++ b/client/src/atc/atc.ts @@ -0,0 +1,87 @@ +import { ToggleableFeature } from "../toggleablefeature"; +import Sortable from 'sortablejs'; +import { ATCFLightList } from "./flightlist"; + +export class ATC extends ToggleableFeature { + + constructor() { + + super( true ); + + //this.#generateFlightList(); + + let $list = document.getElementById( "atc-strip-board-arrivals" ); + + if ( $list instanceof HTMLElement ) { + Sortable.create( $list, { + "handle": ".handle" + }); + } + + } + + + #generateFlightList() { + + const flightList = new ATCFLightList(); + const flights:any = flightList.getFlights( true ); + + const $tbody = document.getElementById( "atc-flight-list-table-body" ); + + if ( $tbody instanceof HTMLElement ) { + + if ( flights.length > 0 ) { + + let flight:any = {}; + + let $button, i; + + for ( [ i, flight ] of flights.entries() ) { + + const $row = document.createElement( "tr" ); + $row.dataset.status = flight.status + + let $td = document.createElement( "td" ); + $td.innerText = flight.name; + $row.appendChild( $td ); + + $td = document.createElement( "td" ); + $td.innerText = flight.takeOffTime; + $row.appendChild( $td ); + + $td = document.createElement( "td" ); + $td.innerText = "00:0" + ( 5 + i ); + $row.appendChild( $td ); + + $td = document.createElement( "td" ); + $td.innerText = flight.status; + $row.appendChild( $td ); + + + $td = document.createElement( "td" ); + $button = document.createElement( "button" ); + $button.innerText = "..."; + + $td.appendChild( $button ); + + $row.appendChild( $td ); + + + $tbody.appendChild( $row ); + + } + + } + + } + + } + + + protected onStatusUpdate(): void { + + document.body.classList.toggle( "atc-enabled", this.getStatus() ); + + } + +} \ No newline at end of file diff --git a/client/src/atc/atcmockapi.ts b/client/src/atc/atcmockapi.ts new file mode 100644 index 00000000..9720e2d0 --- /dev/null +++ b/client/src/atc/atcmockapi.ts @@ -0,0 +1,7 @@ +export abstract class ATCMockAPI { + + constructor() {} + + generateMockData() {} + +} \ No newline at end of file diff --git a/client/src/atc/atcmockapi/flights.ts b/client/src/atc/atcmockapi/flights.ts new file mode 100644 index 00000000..6d6b6435 --- /dev/null +++ b/client/src/atc/atcmockapi/flights.ts @@ -0,0 +1,40 @@ +import { ATCMockAPI } from "../atcmockapi"; + +export class ATCMockAPI_Flights extends ATCMockAPI { + + + generateMockData() { + + let data = []; + const statuses = [ "unknown", "checkedIn", "readyToTaxi" ] + + for ( const [ i, flightName ] of [ "Shark", "Whale", "Dolphin" ].entries() ) { + + data.push({ + "name": flightName, + "status": statuses[ i ], + "takeOffTime": "18:0" + i + }); + + } + + localStorage.setItem( "flightList", JSON.stringify( data ) ); + + } + + + get( generateMockDataIfEmpty?:boolean ) : object { + + generateMockDataIfEmpty = generateMockDataIfEmpty || false; + + let data = localStorage.getItem( "flightList" ) || "[]"; + + if ( data === "[]" && generateMockDataIfEmpty ) { + this.generateMockData(); + } + + return JSON.parse( data ); + + } + +} \ No newline at end of file diff --git a/client/src/atc/flightlist.ts b/client/src/atc/flightlist.ts new file mode 100644 index 00000000..36185eef --- /dev/null +++ b/client/src/atc/flightlist.ts @@ -0,0 +1,18 @@ +import { ATCMockAPI_Flights } from "./atcmockapi/flights"; + +export class ATCFLightList { + + + constructor() { + + + + } + + + getFlights( generateMockDataIfEmpty?:boolean ) { + let api = new ATCMockAPI_Flights(); + return api.get( generateMockDataIfEmpty ); + } + +} \ No newline at end of file diff --git a/client/src/featureswitches.ts b/client/src/featureswitches.ts new file mode 100644 index 00000000..ed52d8eb --- /dev/null +++ b/client/src/featureswitches.ts @@ -0,0 +1,161 @@ +export interface FeatureSwitchInterface { + "defaultEnabled": boolean, // default on/off state (if allowed by masterSwitch) + "label": string, + "masterSwitch": boolean, // on/off regardless of user preference + "name": string, + "onEnabled"?: CallableFunction, + "options"?: object, + "removeArtifactsIfDisabled"?: boolean +} + + +class FeatureSwitch { + + // From config param + defaultEnabled; + label; + masterSwitch; + name; + onEnabled; + removeArtifactsIfDisabled = true; + + // Self-set + userPreference; + + + constructor( config:FeatureSwitchInterface ) { + + this.defaultEnabled = config.defaultEnabled; + this.label = config.label; + this.masterSwitch = config.masterSwitch; + this.name = config.name; + this.onEnabled = config.onEnabled; + + this.userPreference = this.getUserPreference(); + + } + + + getUserPreference() { + + let preferences = JSON.parse( localStorage.getItem( "featureSwitches" ) || "{}" ); + + return ( preferences.hasOwnProperty( this.name ) ) ? preferences[ this.name ] : this.defaultEnabled; + + } + + + isEnabled() { + + if ( !this.masterSwitch ) { + return false; + } + + return this.userPreference; + } + +} + +export class FeatureSwitches { + + #featureSwitches:FeatureSwitch[] = [ + + new FeatureSwitch({ + "defaultEnabled": false, + "label": "AIC", + "masterSwitch": true, + "name": "aic" + }), + + new FeatureSwitch({ + "defaultEnabled": false, + "label": "AI Formations", + "masterSwitch": true, + "name": "ai-formations", + "removeArtifactsIfDisabled": false + }), + + new FeatureSwitch({ + "defaultEnabled": false, + "label": "ATC", + "masterSwitch": true, + "name": "atc" + }), + + new FeatureSwitch({ + "defaultEnabled": false, + "label": "Force show unit control panel", + "masterSwitch": true, + "name": "forceShowUnitControlPanel" + }), + + new FeatureSwitch({ + "defaultEnabled": false, + "label": "Show splash screen", + "masterSwitch": true, + "name": "splashScreen" + }) + + ]; + + + constructor() { + + this.#testSwitches(); + + this.savePreferences(); + + } + + + getSwitch( switchName:string ) { + + return this.#featureSwitches.find( featureSwitch => featureSwitch.name === switchName ); + + } + + + #testSwitches() { + + for ( const featureSwitch of this.#featureSwitches ) { + + if ( featureSwitch.isEnabled() ) { + + if ( typeof featureSwitch.onEnabled === "function" ) { + featureSwitch.onEnabled(); + } + + } else { + + document.querySelectorAll( "[data-feature-switch='" + featureSwitch.name + "']" ).forEach( el => { + + if ( featureSwitch.removeArtifactsIfDisabled === false ) { + el.remove(); + } else { + el.classList.add( "hide" ); + } + + }); + + } + + document.body.classList.toggle( "feature-" + featureSwitch.name, featureSwitch.isEnabled() ); + + } + + } + + + savePreferences() { + + let preferences:any = {}; + + for ( const featureSwitch of this.#featureSwitches ) { + preferences[ featureSwitch.name ] = featureSwitch.isEnabled(); + } + + localStorage.setItem( "featureSwitches", JSON.stringify( preferences ) ); + + } + +} \ No newline at end of file diff --git a/client/src/index.ts b/client/src/index.ts index f74f7476..130c3717 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -6,8 +6,8 @@ import { MissionHandler } from "./missionhandler/missionhandler"; import { UnitControlPanel } from "./panels/unitcontrolpanel"; import { MouseInfoPanel } from "./panels/mouseinfopanel"; import { AIC } from "./aic/aic"; -import { ATC } from "./atc/ATC"; -import { FeatureSwitches } from "./FeatureSwitches"; +import { ATC } from "./atc/atc"; +import { FeatureSwitches } from "./featureswitches"; import { LogPanel } from "./panels/logpanel"; import { getAirbases, getBulllseye as getBulllseyes, getMission, getUnits, toggleDemoEnabled } from "./server/server"; diff --git a/client/src/toggleablefeature.ts b/client/src/toggleablefeature.ts new file mode 100644 index 00000000..5873fb6e --- /dev/null +++ b/client/src/toggleablefeature.ts @@ -0,0 +1,35 @@ +export abstract class ToggleableFeature { + + #status:boolean = false; + + + constructor( defaultStatus:boolean ) { + + this.#status = defaultStatus; + + this.onStatusUpdate(); + + } + + + getStatus() : boolean { + return this.#status; + } + + + protected onStatusUpdate() {} + + + toggleStatus( force?:boolean ) : void { + + if ( force ) { + this.#status = force; + } else { + this.#status = !this.#status; + } + + this.onStatusUpdate(); + + } + +} \ No newline at end of file diff --git a/installer/DCSOlympus.iss b/installer/olympus.iss similarity index 100% rename from installer/DCSOlympus.iss rename to installer/olympus.iss diff --git a/missions/olympus.miz b/missions/olympus.miz new file mode 100644 index 00000000..eaf4b797 Binary files /dev/null and b/missions/olympus.miz differ diff --git a/src/core/include/commands.h b/src/core/include/commands.h new file mode 100644 index 00000000..c8ffbb21 --- /dev/null +++ b/src/core/include/commands.h @@ -0,0 +1,275 @@ +#pragma once +#include "framework.h" +#include "luatools.h" +#include "utils.h" + +namespace CommandPriority { + enum CommandPriorities { LOW, MEDIUM, HIGH }; +}; + +namespace CommandType { + enum CommandTypes { NO_TYPE, MOVE, SMOKE, SPAWN_AIR, SPAWN_GROUND, CLONE, FOLLOW, RESET_TASK, SET_OPTION, SET_COMMAND, SET_TASK }; +}; + +namespace SetCommandType { + enum SetCommandTypes { + ROE = 0, + REACTION_ON_THREAT = 1, + RADAR_USING = 3, + FLARE_USING = 4, + FORMATION = 5, + RTB_ON_BINGO = 6, + SILENCE = 7, + RTB_ON_OUT_OF_AMMO = 10, + ECM_USING = 13, + PROHIBIT_AA = 14, + PROHIBIT_JETT = 15, + PROHIBIT_AB = 16, + PROHIBIT_AG = 17, + MISSILE_ATTACK = 18, + PROHIBIT_WP_PASS_REPORT = 19, + OPTION_RADIO_USAGE_CONTACT = 21, + OPTION_RADIO_USAGE_ENGAGE = 22, + OPTION_RADIO_USAGE_KILL = 23, + JETT_TANKS_IF_EMPTY = 25, + FORCED_ATTACK = 26 + }; +} + +namespace ROE { + enum ROEs { + WEAPON_FREE = 0, + OPEN_FIRE_WEAPON_FREE = 1, + OPEN_FIRE = 2, + RETURN_FIRE = 3, + WEAPON_HOLD = 4, + }; +} + +namespace ReactionToThreat { + enum ReactionToThreats { + NO_REACTION = 0, + PASSIVE_DEFENCE = 1, + EVADE_FIRE = 2, + BYPASS_AND_ESCAPE = 3, + ALLOW_ABORT_MISSION = 4 + }; +} + +/* Base command class */ +class Command +{ +public: + int getPriority() { return priority; } + int getType() { return type; } + virtual wstring getString(lua_State* L) = 0; + virtual int getLoad() = 0; + +protected: + int priority = CommandPriority::LOW; + int type = CommandType::NO_TYPE; +}; + +/* Simple low priority move command (from user click) */ +class Move : public Command +{ +public: + Move(int ID, Coords destination, double speed, double altitude, wstring unitCategory, wstring taskOptions): + ID(ID), + destination(destination), + speed(speed), + altitude(altitude), + unitCategory(unitCategory), + taskOptions(taskOptions) + { + priority = CommandPriority::HIGH; + type = CommandType::MOVE; + }; + virtual wstring getString(lua_State* L); + virtual int getLoad() { return 5; } + +private: + const int ID; + const Coords destination; + const wstring unitCategory; + const double speed; + const double altitude; + const wstring taskOptions; +}; + +/* Smoke command */ +class Smoke : public Command +{ +public: + Smoke(wstring color, Coords location) : + color(color), + location(location) + { + priority = CommandPriority::LOW; + type = CommandType::SMOKE; + }; + virtual wstring getString(lua_State* L); + virtual int getLoad() { return 5; } + +private: + const wstring color; + const Coords location; +}; + +/* Spawn ground unit command */ +class SpawnGroundUnit : public Command +{ +public: + SpawnGroundUnit(wstring coalition, wstring unitType, Coords location) : + coalition(coalition), + unitType(unitType), + location(location) + { + priority = CommandPriority::LOW; + type = CommandType::SPAWN_GROUND; + }; + virtual wstring getString(lua_State* L); + virtual int getLoad() { return 100; } + +private: + const wstring coalition; + const wstring unitType; + const Coords location; +}; + +/* Spawn air unit command */ +class SpawnAircraft : public Command +{ +public: + SpawnAircraft(wstring coalition, wstring unitType, Coords location, wstring payloadName, wstring airbaseName) : + coalition(coalition), + unitType(unitType), + location(location), + payloadName(payloadName), + airbaseName(airbaseName) + { + priority = CommandPriority::LOW; + type = CommandType::SPAWN_AIR; + }; + virtual wstring getString(lua_State* L); + virtual int getLoad() { return 100; } + +private: + const wstring coalition; + const wstring unitType; + const Coords location; + const wstring payloadName; + const wstring airbaseName; +}; + +/* Clone unit command */ +class Clone : public Command +{ +public: + Clone(int ID, Coords location) : + ID(ID), + location(location) + { + priority = CommandPriority::LOW; + type = CommandType::CLONE; + }; + virtual wstring getString(lua_State* L); + virtual int getLoad() { return 100; } + +private: + const int ID; + const Coords location; +}; + +/* Delete unit command */ +class Delete : public Command +{ +public: + Delete(int ID) : + ID(ID) + { + priority = CommandPriority::HIGH; + type = CommandType::CLONE; + }; + virtual wstring getString(lua_State* L); + virtual int getLoad() { return 20; } + +private: + const int ID; +}; + +/* Follow command */ +class SetTask : public Command +{ +public: + SetTask(int ID, wstring task) : + ID(ID), + task(task) + { + priority = CommandPriority::MEDIUM; + type = CommandType::FOLLOW; + }; + virtual wstring getString(lua_State* L); + virtual int getLoad() { return 10; } + +private: + const int ID; + const wstring task; +}; + +/* Reset task command */ +class ResetTask : public Command +{ +public: + ResetTask(int ID) : + ID(ID) + { + priority = CommandPriority::HIGH; + type = CommandType::RESET_TASK; + }; + virtual wstring getString(lua_State* L); + virtual int getLoad() { return 10; } + +private: + const int ID; +}; + +/* Set command */ +class SetCommand : public Command +{ +public: + SetCommand(int ID, wstring command) : + ID(ID), + command(command) + { + priority = CommandPriority::HIGH; + type = CommandType::RESET_TASK; + }; + virtual wstring getString(lua_State* L); + virtual int getLoad() { return 10; } + +private: + const int ID; + const wstring command; +}; + +/* Set option command */ +class SetOption : public Command +{ +public: + SetOption(int ID, int optionID, int optionValue) : + ID(ID), + optionID(optionID), + optionValue(optionValue) + { + priority = CommandPriority::HIGH; + type = CommandType::RESET_TASK; + }; + virtual wstring getString(lua_State* L); + virtual int getLoad() { return 10; } + +private: + const int ID; + const int optionID; + const int optionValue; +}; \ No newline at end of file diff --git a/src/core/include/scheduler.h b/src/core/include/scheduler.h new file mode 100644 index 00000000..d5ebfcf6 --- /dev/null +++ b/src/core/include/scheduler.h @@ -0,0 +1,20 @@ +#pragma once +#include "framework.h" +#include "luatools.h" +#include "commands.h" + +class Scheduler +{ +public: + Scheduler(lua_State* L); + ~Scheduler(); + + void appendCommand(Command* command); + void execute(lua_State* L); + void handleRequest(wstring key, json::value value); + +private: + list commands; + int load; +}; + diff --git a/src/core/include/unit.h b/src/core/include/unit.h new file mode 100644 index 00000000..b94f7b1a --- /dev/null +++ b/src/core/include/unit.h @@ -0,0 +1,184 @@ +#pragma once +#include "framework.h" +#include "utils.h" +#include "dcstools.h" +#include "luatools.h" +#include "measure.h" + +namespace State +{ + enum States + { + IDLE, + REACH_DESTINATION, + ATTACK, + WINGMAN, + FOLLOW, + LAND, + REFUEL, + AWACS, + EWR, + TANKER, + RUN_AWAY + }; +}; + +class Unit +{ +public: + Unit(json::value json, int ID); + ~Unit(); + + /********** Public methods **********/ + int getID() { return ID; } + void updateExportData(json::value json); + void updateMissionData(json::value json); + json::value getData(long long time); + + /********** Base data **********/ + void setAI(bool newAI) { AI = newAI; addMeasure(L"AI", json::value(newAI)); } + void setName(wstring newName) { name = newName; addMeasure(L"name", json::value(newName));} + void setUnitName(wstring newUnitName) { unitName = newUnitName; addMeasure(L"unitName", json::value(newUnitName));} + void setGroupName(wstring newGroupName) { groupName = newGroupName; addMeasure(L"groupName", json::value(newGroupName));} + void setAlive(bool newAlive) { alive = newAlive; addMeasure(L"alive", json::value(newAlive));} + void setType(json::value newType) { type = newType; addMeasure(L"type", newType);} + void setCountry(int newCountry) { country = newCountry; addMeasure(L"country", json::value(newCountry));} + bool getAI() { return AI; } + wstring getName() { return name; } + wstring getUnitName() { return unitName; } + wstring getGroupName() { return groupName; } + bool getAlive() { return alive; } + json::value getType() { return type; } + int getCountry() { return country; } + + /********** Flight data **********/ + void setLatitude(double newLatitude) {latitude = newLatitude; addMeasure(L"latitude", json::value(newLatitude));} + void setLongitude(double newLongitude) {longitude = newLongitude; addMeasure(L"longitude", json::value(newLongitude));} + void setAltitude(double newAltitude) {altitude = newAltitude; addMeasure(L"altitude", json::value(newAltitude));} + void setHeading(double newHeading) {heading = newHeading; addMeasure(L"heading", json::value(newHeading));} + void setSpeed(double newSpeed) {speed = newSpeed; addMeasure(L"speed", json::value(newSpeed));} + double getLatitude() { return latitude; } + double getLongitude() { return longitude; } + double getAltitude() { return altitude; } + double getHeading() { return heading; } + double getSpeed() { return speed; } + + /********** Mission data **********/ + void setFuel(double newFuel) { fuel = newFuel; addMeasure(L"fuel", json::value(newFuel));} + void setAmmo(json::value newAmmo) { ammo = newAmmo; addMeasure(L"ammo", json::value(newAmmo));} + void setTargets(json::value newTargets) {targets = newTargets; addMeasure(L"targets", json::value(newTargets));} + void setHasTask(bool newHasTask) { hasTask = newHasTask; addMeasure(L"hasTask", json::value(newHasTask));} + void setCoalitionID(int newCoalitionID); + void setFlags(json::value newFlags) { flags = newFlags; addMeasure(L"flags", json::value(newFlags));} + double getFuel() { return fuel; } + json::value getAmmo() { return ammo; } + json::value getTargets() { return targets; } + bool getHasTask() { return hasTask; } + wstring getCoalition() { return coalition; } + int getCoalitionID(); + json::value getFlags() { return flags; } + + /********** Formation data **********/ + void setIsLeader(bool newIsLeader); + void setIsWingman(bool newIsWingman); + void setLeader(Unit* newLeader); + void setWingmen(vector newWingmen); + void setFormation(wstring newFormation) { formation = newFormation; addMeasure(L"formation", json::value(formation));} + void setFormationOffset(Offset formationOffset); + bool getIsLeader() { return isLeader; } + bool getIsWingman() { return isWingman; } + Unit* getLeader() { return leader; } + vector getWingmen() { return wingmen; } + wstring getFormation() { return formation; } + Offset getFormationoffset() { return formationOffset; } + + /********** Task data **********/ + void setCurrentTask(wstring newCurrentTask) { currentTask = newCurrentTask;addMeasure(L"currentTask", json::value(newCurrentTask)); } + virtual void setTargetSpeed(double newTargetSpeed) { targetSpeed = newTargetSpeed; addMeasure(L"targetSpeed", json::value(newTargetSpeed));} + virtual void setTargetAltitude(double newTargetAltitude) { targetAltitude = newTargetAltitude; addMeasure(L"targetAltitude", json::value(newTargetAltitude));} //TODO fix, double definition + void setActiveDestination(Coords newActiveDestination) { activeDestination = newActiveDestination; addMeasure(L"activeDestination", json::value("")); } // TODO fix + void setActivePath(list newActivePath); + void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));} + wstring getCurrentTask() { return currentTask; } + virtual double getTargetSpeed() { return targetSpeed; }; + virtual double getTargetAltitude() { return targetAltitude; }; + Coords getActiveDestination() { return activeDestination; } + list getActivePath() { return activePath; } + int getTargetID() { return targetID; } + + /********** Options data **********/ + void setROE(wstring newROE); + void setReactionToThreat(wstring newReactionToThreat); + wstring getROE() { return ROE; } + wstring getReactionToThreat() {return reactionToThreat;} + + /********** Control functions **********/ + void landAt(Coords loc); + virtual void changeSpeed(wstring change){}; + virtual void changeAltitude(wstring change){}; + void resetActiveDestination(); + virtual void setState(int newState) { state = newState; }; + void resetTask(); + +protected: + int ID; + + map measures; + + /********** Base data **********/ + bool AI = false; + wstring name = L"undefined"; + wstring unitName = L"undefined"; + wstring groupName = L"undefined"; + bool alive = true; + json::value type = json::value::null(); + int country = NULL; + + /********** Flight data **********/ + double latitude = NULL; + double longitude = NULL; + double altitude = NULL; + double speed = NULL; + double heading = NULL; + + /********** Mission data **********/ + double fuel = 0; + json::value ammo = json::value::null(); + json::value targets = json::value::null(); + bool hasTask = false; + wstring coalition = L""; + json::value flags = json::value::null(); + + /********** Formation data **********/ + bool isLeader = false; + bool isWingman = false; + wstring formation = L""; + Unit *leader = nullptr; + vector wingmen; + Offset formationOffset = Offset(NULL); + + /********** Task data **********/ + wstring currentTask = L""; + double targetSpeed = 0; + double targetAltitude = 0; + list activePath; + Coords activeDestination = Coords(0); + int targetID = NULL; + + /********** Options data **********/ + wstring ROE = L""; + wstring reactionToThreat = L""; + + /********** State machine **********/ + int state = State::IDLE; + + /********** Other **********/ + Coords oldPosition = Coords(0); // Used to approximate speed + + /********** Functions **********/ + virtual wstring getCategory() { return L"No category"; }; + wstring getTargetName(); + bool isTargetAlive(); + virtual void AIloop() = 0; + void addMeasure(wstring key, json::value value); +}; diff --git a/src/core/src/commands.cpp b/src/core/src/commands.cpp new file mode 100644 index 00000000..ef5a7535 --- /dev/null +++ b/src/core/src/commands.cpp @@ -0,0 +1,135 @@ +#include "commands.h" +#include "logger.h" +#include "dcstools.h" + +/* Move command */ +wstring Move::getString(lua_State* L) +{ + std::wostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.move, " + << ID << ", " + << destination.lat << ", " + << destination.lng << ", " + << altitude << ", " + << speed << ", " + << "\"" << unitCategory << "\"" << ", " + << taskOptions; + return commandSS.str(); +} + +/* Smoke command */ +wstring Smoke::getString(lua_State* L) +{ + std::wostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.smoke, " + << "\"" << color << "\"" << ", " + << location.lat << ", " + << location.lng; + return commandSS.str(); +} + +/* Spawn ground command */ +wstring SpawnGroundUnit::getString(lua_State* L) +{ + std::wostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.spawnGroundUnit, " + << "\"" << coalition << "\"" << ", " + << "\"" << unitType << "\"" << ", " + << location.lat << ", " + << location.lng; + return commandSS.str(); +} + +/* Spawn air command */ +wstring SpawnAircraft::getString(lua_State* L) +{ + std::wostringstream optionsSS; + optionsSS.precision(10); + optionsSS << "{" + << "payloadName = \"" << payloadName << "\", " + << "airbaseName = \"" << airbaseName << "\"," + << "}"; + + std::wostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.spawnAircraft, " + << "\"" << coalition << "\"" << ", " + << "\"" << unitType << "\"" << ", " + << location.lat << ", " + << location.lng << "," + << optionsSS.str(); + return commandSS.str(); +} + +/* Clone unit command */ +wstring Clone::getString(lua_State* L) +{ + std::wostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.clone, " + << ID << ", " + << location.lat << ", " + << location.lng; + return commandSS.str(); +} + +/* Delete unit command */ +wstring Delete::getString(lua_State* L) +{ + std::wostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.delete, " + << ID; + return commandSS.str(); +} + +/* Set task command */ +wstring SetTask::getString(lua_State* L) +{ + std::wostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.setTask, " + << ID << "," + << task; + + return commandSS.str(); +} + +/* Reset task command */ +wstring ResetTask::getString(lua_State* L) +{ + std::wostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.resetTask, " + << ID; + + return commandSS.str(); +} + +/* Set command command */ +wstring SetCommand::getString(lua_State* L) +{ + std::wostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.setCommand, " + << ID << "," + << command; + + return commandSS.str(); +} + +/* Set option command */ +wstring SetOption::getString(lua_State* L) +{ + std::wostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.setOption, " + << ID << "," + << optionID << "," + << optionValue; + + return commandSS.str(); +} \ No newline at end of file diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp new file mode 100644 index 00000000..8e80f9bf --- /dev/null +++ b/src/core/src/scheduler.cpp @@ -0,0 +1,263 @@ +#include "scheduler.h" +#include "logger.h" +#include "dcstools.h" +#include "unitsManager.h" +#include "utils.h" +#include "unit.h" + +extern UnitsManager* unitsManager; + +Scheduler::Scheduler(lua_State* L): + load(0) +{ + LogInfo(L, "Scheduler constructor called successfully"); +} + +Scheduler::~Scheduler() +{ + +} + +void Scheduler::appendCommand(Command* command) +{ + commands.push_back(command); +} + +void Scheduler::execute(lua_State* L) +{ + /* Decrease the active computation load. New commands can be sent only if the load has reached 0. + This is needed to avoid server lag. */ + if (load > 0) { + load--; + return; + } + + int priority = CommandPriority::HIGH; + while (priority >= CommandPriority::LOW) + { + for (auto command : commands) + { + if (command->getPriority() == priority) + { + wstring commandString = L"Olympus.protectedCall(" + command->getString(L) + L")"; + if (dostring_in(L, "server", to_string(commandString))) + log(L"Error executing command " + commandString); + load = command->getLoad(); + commands.remove(command); + return; + } + } + priority--; + } +} + +void Scheduler::handleRequest(wstring key, json::value value) +{ + Command* command = nullptr; + + log(L"Received request with ID: " + key); + if (key.compare(L"setPath") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + if (unit != nullptr) + { + wstring unitName = unit->getUnitName(); + json::value path = value[L"path"]; + list newPath; + for (int i = 1; i <= path.as_object().size(); i++) + { + wstring WP = to_wstring(i); + double lat = path[WP][L"lat"].as_double(); + double lng = path[WP][L"lng"].as_double(); + log(unitName + L" set path destination " + WP + L" (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); + Coords dest; dest.lat = lat; dest.lng = lng; + newPath.push_back(dest); + } + + Unit* unit = unitsManager->getUnit(ID); + if (unit != nullptr) + { + unit->setActivePath(newPath); + unit->setState(State::REACH_DESTINATION); + log(unitName + L" new path set successfully"); + } + else + log(unitName + L" not found, request will be discarded"); + } + } + else if (key.compare(L"smoke") == 0) + { + wstring color = value[L"color"].as_string(); + double lat = value[L"location"][L"lat"].as_double(); + double lng = value[L"location"][L"lng"].as_double(); + log(L"Adding " + color + L" smoke at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); + Coords loc; loc.lat = lat; loc.lng = lng; + command = dynamic_cast(new Smoke(color, loc)); + } + else if (key.compare(L"spawnGround") == 0) + { + wstring coalition = value[L"coalition"].as_string(); + wstring type = value[L"type"].as_string(); + double lat = value[L"location"][L"lat"].as_double(); + double lng = value[L"location"][L"lng"].as_double(); + log(L"Spawning " + coalition + L" ground unit of type " + type + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); + Coords loc; loc.lat = lat; loc.lng = lng; + command = dynamic_cast(new SpawnGroundUnit(coalition, type, loc)); + } + else if (key.compare(L"spawnAir") == 0) + { + wstring coalition = value[L"coalition"].as_string(); + wstring type = value[L"type"].as_string(); + double lat = value[L"location"][L"lat"].as_double(); + double lng = value[L"location"][L"lng"].as_double(); + Coords loc; loc.lat = lat; loc.lng = lng; + wstring payloadName = value[L"payloadName"].as_string(); + wstring airbaseName = value[L"airbaseName"].as_string(); + log(L"Spawning " + coalition + L" air unit of type " + type + L" with payload " + payloadName + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L" " + airbaseName + L")"); + command = dynamic_cast(new SpawnAircraft(coalition, type, loc, payloadName, airbaseName)); + } + else if (key.compare(L"attackUnit") == 0) + { + int ID = value[L"ID"].as_integer(); + int targetID = value[L"targetID"].as_integer(); + + Unit* unit = unitsManager->getUnit(ID); + Unit* target = unitsManager->getUnit(targetID); + + wstring unitName; + wstring targetName; + + if (unit != nullptr) + unitName = unit->getUnitName(); + else + return; + + if (target != nullptr) + targetName = target->getUnitName(); + else + return; + + log(L"Unit " + unitName + L" attacking unit " + targetName); + unit->setTargetID(targetID); + unit->setState(State::ATTACK); + } + else if (key.compare(L"stopAttack") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + if (unit != nullptr) + unit->setState(State::REACH_DESTINATION); + else + return; + } + else if (key.compare(L"changeSpeed") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + if (unit != nullptr) + unit->changeSpeed(value[L"change"].as_string()); + } + else if (key.compare(L"changeAltitude") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + if (unit != nullptr) + unit->changeAltitude(value[L"change"].as_string()); + } + else if (key.compare(L"setSpeed") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + if (unit != nullptr) + unit->setTargetSpeed(value[L"speed"].as_double()); + } + else if (key.compare(L"setAltitude") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + if (unit != nullptr) + unit->setTargetAltitude(value[L"altitude"].as_double()); + } + else if (key.compare(L"cloneUnit") == 0) + { + int ID = value[L"ID"].as_integer(); + double lat = value[L"location"][L"lat"].as_double(); + double lng = value[L"location"][L"lng"].as_double(); + Coords loc; loc.lat = lat; loc.lng = lng; + command = dynamic_cast(new Clone(ID, loc)); + log(L"Cloning unit " + to_wstring(ID)); + } + else if (key.compare(L"setLeader") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + bool isLeader = value[L"isLeader"].as_bool(); + if (isLeader) + { + json::value wingmenIDs = value[L"wingmenIDs"]; + vector wingmen; + if (unit != nullptr) + { + for (auto itr = wingmenIDs.as_array().begin(); itr != wingmenIDs.as_array().end(); itr++) + { + Unit* wingman = unitsManager->getUnit(itr->as_integer()); + if (wingman != nullptr) + wingmen.push_back(wingman); + } + unit->setFormation(L"Line abreast"); + unit->setIsLeader(true); + unit->setWingmen(wingmen); + log(L"Setting " + unit->getName() + L" as formation leader"); + } + } + else { + unit->setIsLeader(false); + } + } + else if (key.compare(L"setFormation") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + wstring formation = value[L"formation"].as_string(); + unit->setFormation(formation); + } + else if (key.compare(L"setROE") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + wstring ROE = value[L"ROE"].as_string(); + unit->setROE(ROE); + } + else if (key.compare(L"setReactionToThreat") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + wstring reactionToThreat = value[L"reactionToThreat"].as_string(); + unit->setReactionToThreat(reactionToThreat); + } + else if (key.compare(L"landAt") == 0) + { + int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + double lat = value[L"location"][L"lat"].as_double(); + double lng = value[L"location"][L"lng"].as_double(); + Coords loc; loc.lat = lat; loc.lng = lng; + unit->landAt(loc); + } + else if (key.compare(L"deleteUnit") == 0) + { + int ID = value[L"ID"].as_integer(); + unitsManager->deleteUnit(ID); + } + else + { + log(L"Unknown command: " + key); + } + + if (command != nullptr) + { + appendCommand(command); + } +} + diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp new file mode 100644 index 00000000..09bb63bd --- /dev/null +++ b/src/core/src/unit.cpp @@ -0,0 +1,322 @@ +#include "unit.h" +#include "utils.h" +#include "logger.h" +#include "commands.h" +#include "scheduler.h" +#include "defines.h" +#include "unitsManager.h" + +#include +using namespace std::chrono; + +#include +using namespace GeographicLib; + +extern Scheduler* scheduler; +extern UnitsManager* unitsManager; + +Unit::Unit(json::value json, int ID) : + ID(ID) +{ + log("Creating unit with ID: " + to_string(ID)); +} + +Unit::~Unit() +{ + +} + +void Unit::addMeasure(wstring key, json::value value) +{ + milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); + if (measures.find(key) == measures.end()) + measures[key] = new Measure(value, ms.count()); + else + { + if (measures[key]->getValue() != value) + { + measures[key]->setValue(value); + measures[key]->setTime(ms.count()); + } + } +} + +void Unit::updateExportData(json::value json) +{ + /* Compute speed (loGetWorldObjects does not provide speed, we compute it for better performance instead of relying on many lua calls) */ + if (oldPosition != NULL) + { + double dist = 0; + Geodesic::WGS84().Inverse(latitude, longitude, oldPosition.lat, oldPosition.lng, dist); + setSpeed(getSpeed() * 0.95 + (dist / UPDATE_TIME_INTERVAL) * 0.05); + } + oldPosition = Coords(latitude, longitude, altitude); + + if (json.has_string_field(L"Name")) + setName(json[L"Name"].as_string()); + if (json.has_string_field(L"UnitName")) + setUnitName(json[L"UnitName"].as_string()); + if (json.has_string_field(L"GroupName")) + setGroupName(json[L"GroupName"].as_string()); + if (json.has_object_field(L"Type")) + setType(json[L"Type"]); + if (json.has_number_field(L"Country")) + setCountry(json[L"Country"].as_number().to_int32()); + if (json.has_number_field(L"CoalitionID")) + setCoalitionID(json[L"CoalitionID"].as_number().to_int32()); + if (json.has_object_field(L"LatLongAlt")) + { + setLatitude(json[L"LatLongAlt"][L"Lat"].as_number().to_double()); + setLongitude(json[L"LatLongAlt"][L"Long"].as_number().to_double()); + setAltitude(json[L"LatLongAlt"][L"Alt"].as_number().to_double()); + } + if (json.has_number_field(L"Heading")) + setHeading(json[L"Heading"].as_number().to_double()); + if (json.has_object_field(L"Flags")) + setFlags(json[L"Flags"]); + + /* All units which contain the name "Olympus" are automatically under AI control */ + /* TODO: I don't really like using this method */ + setAI(getUnitName().find(L"Olympus") != wstring::npos); + + /* If the unit is alive and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */ + if (getAI() && getAlive() && getFlags()[L"Human"].as_bool() == false) + AIloop(); +} + +void Unit::updateMissionData(json::value json) +{ + if (json.has_number_field(L"fuel")) + setFuel(int(json[L"fuel"].as_number().to_double() * 100)); + if (json.has_object_field(L"ammo")) + setAmmo(json[L"ammo"]); + if (json.has_object_field(L"targets")) + setTargets(json[L"targets"]); + if (json.has_boolean_field(L"hasTask")) + setHasTask(json[L"hasTask"].as_bool()); +} + +json::value Unit::getData(long long time) +{ + auto json = json::value::object(); + + /********** Base data **********/ + json[L"baseData"] = json::value::object(); + for (auto key : { L"AI", L"name", L"unitName", L"groupName", L"alive", L"category"}) + { + if (measures.find(key) != measures.end() && measures[key]->getTime() > time) + json[L"baseData"][key] = measures[key]->getValue(); + } + + /********** Flight data **********/ + json[L"flightData"] = json::value::object(); + for (auto key : { L"latitude", L"longitude", L"altitude", L"speed", L"heading"}) + { + if (measures.find(key) != measures.end() && measures[key]->getTime() > time) + json[L"flightData"][key] = measures[key]->getValue(); + } + + /********** Mission data **********/ + json[L"missionData"] = json::value::object(); + for (auto key : { L"fuel", L"ammo", L"targets", L"hasTask", L"coalition", L"flags"}) + { + if (measures.find(key) != measures.end() && measures[key]->getTime() > time) + json[L"missionData"][key] = measures[key]->getValue(); + } + + /********** Formation data **********/ + json[L"formationData"] = json::value::object(); + for (auto key : { L"isLeader", L"isWingman", L"formation", L"wingmenIDs", L"leaderID" }) + { + if (measures.find(key) != measures.end() && measures[key]->getTime() > time) + json[L"missionData"][key] = measures[key]->getValue(); + } + + /********** Task data **********/ + json[L"taskData"] = json::value::object(); + for (auto key : { L"currentTask", L"targetSpeed", L"targetAltitude", L"activePath" }) + { + if (measures.find(key) != measures.end() && measures[key]->getTime() > time) + json[L"taskData"][key] = measures[key]->getValue(); + } + + /********** Options data **********/ + json[L"optionsData"] = json::value::object(); + for (auto key : { L"ROE", L"reactionToThreat" }) + { + if (measures.find(key) != measures.end() && measures[key]->getTime() > time) + json[L"optionsData"][key] = measures[key]->getValue(); + } + + return json; +} + +void Unit::setActivePath(list newPath) +{ + if (state != State::WINGMAN && state != State::FOLLOW) + { + activePath = newPath; + resetActiveDestination(); + } + + auto path = json::value::object(); + if (activePath.size() > 0) { + int count = 1; + for (auto& destination : activePath) + { + auto json = json::value::object(); + json[L"lat"] = destination.lat; + json[L"lng"] = destination.lng; + json[L"alt"] = destination.alt; + path[to_wstring(count++)] = json; + } + } + addMeasure(L"activePath", path); +} + +void Unit::setCoalitionID(int newCoalitionID) +{ + if (newCoalitionID == 0) + coalition = L"neutral"; + else if (newCoalitionID == 1) + coalition = L"red"; + else + coalition = L"blue"; + addMeasure(L"coalition", json::value(coalition)); +} + +int Unit::getCoalitionID() +{ + if (coalition == L"neutral") + return 0; + else if (coalition == L"red") + return 1; + else + return 2; +} + +void Unit::setLeader(Unit* newLeader) +{ + leader = newLeader; + if (leader != nullptr) + addMeasure(L"leaderID", json::value(leader->getID())); +} + +void Unit::setWingmen(vector newWingmen) { + wingmen = newWingmen; + auto wingmenIDs = json::value::object(); + int i = 0; + for (auto itr = wingmen.begin(); itr != wingmen.end(); itr++) + wingmenIDs[i++] = (*itr)->getID(); + addMeasure(L"wingmen", wingmenIDs); +} + +wstring Unit::getTargetName() +{ + if (isTargetAlive()) + { + Unit* target = unitsManager->getUnit(targetID); + if (target != nullptr) + return target->getUnitName(); + } + return L""; +} + +bool Unit::isTargetAlive() +{ + if (targetID == NULL) + return false; + + Unit* target = unitsManager->getUnit(targetID); + if (target != nullptr) + return target->alive; + else + return false; +} + +void Unit::resetActiveDestination() +{ + activeDestination = Coords(NULL); +} + +void Unit::resetTask() +{ + Command* command = dynamic_cast(new ResetTask(ID)); + scheduler->appendCommand(command); +} + +void Unit::setIsLeader(bool newIsLeader) { + isLeader = newIsLeader; + if (!isLeader) { + for (auto wingman : wingmen) + { + wingman->setFormation(L""); + wingman->setIsWingman(false); + wingman->setLeader(nullptr); + } + } + addMeasure(L"isLeader", json::value(newIsLeader)); +} + +void Unit::setIsWingman(bool newIsWingman) +{ + isWingman = newIsWingman; + if (isWingman) + setState(State::WINGMAN); + else + setState(State::IDLE); + + addMeasure(L"isWingman", json::value(isWingman)); +} + +void Unit::setFormationOffset(Offset newFormationOffset) +{ + formationOffset = newFormationOffset; + resetTask(); +} + +void Unit::setROE(wstring newROE) { + ROE = newROE; + int ROEEnum; + if (newROE.compare(L"Free") == 0) + ROEEnum = ROE::WEAPON_FREE; + else if (newROE.compare(L"Designated free") == 0) + ROEEnum = ROE::OPEN_FIRE_WEAPON_FREE; + else if (newROE.compare(L"Designated") == 0) + ROEEnum = ROE::OPEN_FIRE; + else if (newROE.compare(L"Return") == 0) + ROEEnum = ROE::RETURN_FIRE; + else if (newROE.compare(L"Hold") == 0) + ROEEnum = ROE::WEAPON_HOLD; + else + return; + Command* command = dynamic_cast(new SetOption(ID, SetCommandType::ROE, ROEEnum)); + scheduler->appendCommand(command); + addMeasure(L"ROE", json::value(newROE)); +} + +void Unit::setReactionToThreat(wstring newReactionToThreat) { + reactionToThreat = newReactionToThreat; + int reactionToThreatEnum; + if (newReactionToThreat.compare(L"None") == 0) + reactionToThreatEnum = ReactionToThreat::NO_REACTION; + else if (newReactionToThreat.compare(L"Passive") == 0) + reactionToThreatEnum = ReactionToThreat::PASSIVE_DEFENCE; + else if (newReactionToThreat.compare(L"Evade") == 0) + reactionToThreatEnum = ReactionToThreat::EVADE_FIRE; + else if (newReactionToThreat.compare(L"Escape") == 0) + reactionToThreatEnum = ReactionToThreat::BYPASS_AND_ESCAPE; + else if (newReactionToThreat.compare(L"Abort") == 0) + reactionToThreatEnum = ReactionToThreat::ALLOW_ABORT_MISSION; + else + return; + Command* command = dynamic_cast(new SetOption(ID, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum)); + scheduler->appendCommand(command); + addMeasure(L"reactionToThreat", json::value(newReactionToThreat)); +} + +void Unit::landAt(Coords loc) { + activePath.clear(); + activePath.push_back(loc); + setState(State::LAND); +} \ No newline at end of file diff --git a/src/olympus.sln b/src/olympus.sln new file mode 100644 index 00000000..bbebd79f --- /dev/null +++ b/src/olympus.sln @@ -0,0 +1,42 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core", "core\core.vcxproj", "{8A48D855-0E01-42BA-BD8C-07B0877C68DF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logger", "logger\logger.vcxproj", "{873ECABE-FCFE-4217-AC15-91959C3CF1C6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dcstools", "dcstools\dcstools.vcxproj", "{2B255368-39A0-431A-A6DE-CC739AC70DC1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "luatools", "luatools\luatools.vcxproj", "{DE139EC1-4F88-47D5-BE73-F41915FE14A3}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "utils", "utils\utils.vcxproj", "{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "olympus", "olympus\olympus.vcxproj", "{5F3FC91E-1FBC-4223-8011-9708DE913474}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8A48D855-0E01-42BA-BD8C-07B0877C68DF}.Release|x64.ActiveCfg = Release|x64 + {8A48D855-0E01-42BA-BD8C-07B0877C68DF}.Release|x64.Build.0 = Release|x64 + {873ECABE-FCFE-4217-AC15-91959C3CF1C6}.Release|x64.ActiveCfg = Release|x64 + {873ECABE-FCFE-4217-AC15-91959C3CF1C6}.Release|x64.Build.0 = Release|x64 + {2B255368-39A0-431A-A6DE-CC739AC70DC1}.Release|x64.ActiveCfg = Release|x64 + {2B255368-39A0-431A-A6DE-CC739AC70DC1}.Release|x64.Build.0 = Release|x64 + {DE139EC1-4F88-47D5-BE73-F41915FE14A3}.Release|x64.ActiveCfg = Release|x64 + {DE139EC1-4F88-47D5-BE73-F41915FE14A3}.Release|x64.Build.0 = Release|x64 + {B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.Release|x64.ActiveCfg = Release|x64 + {B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.Release|x64.Build.0 = Release|x64 + {5F3FC91E-1FBC-4223-8011-9708DE913474}.Release|x64.ActiveCfg = Release|x64 + {5F3FC91E-1FBC-4223-8011-9708DE913474}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FAB9F592-7511-4EB9-B365-078842ED9BDD} + EndGlobalSection +EndGlobal diff --git a/src/olympus/olympus.filters b/src/olympus/olympus.filters new file mode 100644 index 00000000..2bcbeefa --- /dev/null +++ b/src/olympus/olympus.filters @@ -0,0 +1,29 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + + + inc + + + inc + + + + + src + + + src + + + \ No newline at end of file diff --git a/src/olympus/olympus.vcxproj b/src/olympus/olympus.vcxproj new file mode 100644 index 00000000..50e68d5c --- /dev/null +++ b/src/olympus/olympus.vcxproj @@ -0,0 +1,120 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + + {2b255368-39a0-431a-a6de-cc739ac70dc1} + + + {873ecabe-fcfe-4217-ac15-91959c3cf1c6} + + + {b85009ce-4a5c-4a5a-b85d-001b3a2651b2} + + + + 16.0 + Win32Proj + {5f3fc91e-1fbc-4223-8011-9708de913474} + Olympus + 10.0 + olympus + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + false + .\..\..\bin\$(Platform)\$(Configuration)\ + + + false + .\..\..\bin\ + + + true + + + + Level3 + true + _CRT_SECURE_NO_WARNINGS; _DEBUG;OLYMPUS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + include; ..\..\third-party\lua\include; ..\utils\include; ..\shared\include + stdcpp17 + AssemblyCode + + + Windows + true + false + lua.lib + ..\..\third-party\lua + + + + + Level3 + true + true + true + _CRT_SECURE_NO_WARNINGS; NDEBUG;OLYMPUS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + + + include;..\..\third-party\lua\include;..\utils\include;..\shared\include;..\dcstools\include;..\logger\include;..\luatools\include + stdcpp20 + NoListing + + + Windows + true + true + true + false + lua.lib + ..\..\third-party\lua + + + + + + \ No newline at end of file diff --git a/src/olympus/olympus.vcxproj.filters b/src/olympus/olympus.vcxproj.filters new file mode 100644 index 00000000..fa32be80 --- /dev/null +++ b/src/olympus/olympus.vcxproj.filters @@ -0,0 +1,16 @@ + + + + + {c021f649-26ec-4040-abd4-13132def4a81} + + + {2809fbbc-77d2-465e-afef-230525a60c66} + + + + + Source Files + + + \ No newline at end of file diff --git a/src/olympus/src/olympus.cpp b/src/olympus/src/olympus.cpp new file mode 100644 index 00000000..67189855 --- /dev/null +++ b/src/olympus/src/olympus.cpp @@ -0,0 +1,152 @@ +#include "framework.h" +#include "dcstools.h" +#include "logger.h" +#include "utils.h" + +/* Run-time linking to core dll allows for "hot swap". This is useful for development but could be removed when stable.*/ +HINSTANCE hGetProcIDDLL = NULL; +typedef int(__stdcall* f_coreInit)(lua_State* L); +typedef int(__stdcall* f_coreDeinit)(lua_State* L); +typedef int(__stdcall* f_coreFrame)(lua_State* L); +typedef int(__stdcall* f_coreMissionData)(lua_State* L); +f_coreInit coreInit = nullptr; +f_coreDeinit coreDeinit = nullptr; +f_coreFrame coreFrame = nullptr; +f_coreMissionData coreMissionData = nullptr; + +static int onSimulationStart(lua_State* L) +{ + log("onSimulationStart callback called successfully"); + + string modLocation; + string dllLocation; + char* buf = nullptr; + size_t sz = 0; + if (_dupenv_s(&buf, &sz, "DCSOLYMPUS_PATH") == 0 && buf != nullptr) + { + modLocation = buf; + free(buf); + } + else + { + log("DCSOLYMPUS_PATH environment variable is missing"); + goto error; + } + dllLocation = modLocation + "\\bin\\core.dll"; + + log("Loading core.dll"); + hGetProcIDDLL = LoadLibrary(to_wstring(dllLocation).c_str()); + + if (!hGetProcIDDLL) { + LogError(L, "Error loading core DLL"); + goto error; + } + + log("Core DLL loaded successfully"); + + coreInit = (f_coreInit)GetProcAddress(hGetProcIDDLL, "coreInit"); + if (!coreInit) + { + LogError(L, "Error getting coreInit ProcAddress from DLL"); + goto error; + } + + coreDeinit = (f_coreDeinit)GetProcAddress(hGetProcIDDLL, "coreDeinit"); + if (!coreInit) + { + LogError(L, "Error getting coreDeinit ProcAddress from DLL"); + goto error; + } + + coreFrame = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreFrame"); + if (!coreFrame) + { + LogError(L, "Error getting coreFrame ProcAddress from DLL"); + goto error; + } + + coreMissionData = (f_coreFrame)GetProcAddress(hGetProcIDDLL, "coreMissionData"); + if (!coreFrame) + { + LogError(L, "Error getting coreMissionData ProcAddress from DLL"); + goto error; + } + + coreInit(L); + + LogInfo(L, "Module loaded and started successfully."); + + return 0; + +error: + LogError(L, "Error while loading module, see Olympus.log in temporary folder for additional details."); + return 0; +} + +static int onSimulationFrame(lua_State* L) +{ + if (coreFrame) + { + coreFrame(L); + } + return 0; +} + +static int onSimulationStop(lua_State* L) +{ + log("onSimulationStop callback called successfully"); + if (hGetProcIDDLL) + { + log("Trying to unload core DLL"); + if (coreDeinit) + { + coreDeinit(L); + } + + if (FreeLibrary(hGetProcIDDLL)) + { + log("Core DLL unloaded successfully"); + } + else + { + LogError(L, "Error unloading DLL"); + goto error; + } + + coreInit = nullptr; + coreDeinit = nullptr; + coreFrame = nullptr; + coreMissionData = nullptr; + } + + hGetProcIDDLL = NULL; + + return 0; + +error: + LogError(L, "Error while unloading module, see Olympus.log in temporary folder for additional details."); + return 0; +} + +static int setMissionData(lua_State* L) +{ + if (coreMissionData) + { + coreMissionData(L); + } + return 0; +} + +static const luaL_Reg Map[] = { + {"onSimulationStart", onSimulationStart}, + {"onSimulationFrame", onSimulationFrame}, + {"onSimulationStop", onSimulationStop}, + {"setMissionData", setMissionData }, + {NULL, NULL} +}; + +extern "C" DllExport int luaopen_olympus(lua_State * L) +{ + luaL_register(L, "olympus", Map); + return 1; +} \ No newline at end of file