mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Large rework of events and state
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32"
|
||||
preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228">
|
||||
<path fill="#00D8FF"
|
||||
d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
@@ -1,20 +1,23 @@
|
||||
// TODO Convert to typescript
|
||||
// Audio library I shamelessly copied from the web
|
||||
|
||||
// SAFARI Polyfills
|
||||
if (!window.AudioBuffer.prototype.copyToChannel) {
|
||||
window.AudioBuffer.prototype.copyToChannel = function copyToChannel(buffer, channel) {
|
||||
window.AudioBuffer.prototype.copyToChannel = function copyToChannel(buffer: Float32Array, channel: number): void {
|
||||
this.getChannelData(channel).set(buffer);
|
||||
};
|
||||
}
|
||||
if (!window.AudioBuffer.prototype.copyFromChannel) {
|
||||
window.AudioBuffer.prototype.copyFromChannel = function copyFromChannel(buffer, channel) {
|
||||
window.AudioBuffer.prototype.copyFromChannel = function copyFromChannel(buffer: Float32Array, channel: number): void {
|
||||
buffer.set(this.getChannelData(channel));
|
||||
};
|
||||
}
|
||||
|
||||
export class Effect {
|
||||
constructor(context) {
|
||||
name: string;
|
||||
context: AudioContext;
|
||||
input: GainNode;
|
||||
effect: GainNode | BiquadFilterNode | null;
|
||||
bypassed: boolean;
|
||||
output: GainNode;
|
||||
|
||||
constructor(context: AudioContext) {
|
||||
this.name = "effect";
|
||||
this.context = context;
|
||||
this.input = this.context.createGain();
|
||||
@@ -25,22 +28,31 @@ export class Effect {
|
||||
this.wireUp();
|
||||
}
|
||||
|
||||
setup() {
|
||||
setup(): void {
|
||||
this.effect = this.context.createGain();
|
||||
}
|
||||
|
||||
wireUp() {
|
||||
this.input.connect(this.effect);
|
||||
this.effect.connect(this.output);
|
||||
wireUp(): void {
|
||||
if (this.effect) {
|
||||
this.input.connect(this.effect);
|
||||
this.effect.connect(this.output);
|
||||
}
|
||||
}
|
||||
|
||||
connect(destination) {
|
||||
connect(destination: AudioNode): void {
|
||||
this.output.connect(destination);
|
||||
}
|
||||
}
|
||||
|
||||
export class Sample {
|
||||
constructor(context) {
|
||||
context: AudioContext;
|
||||
buffer: AudioBufferSourceNode;
|
||||
sampleBuffer: AudioBuffer | null;
|
||||
rawBuffer: ArrayBuffer | null;
|
||||
loaded: boolean;
|
||||
output: GainNode;
|
||||
|
||||
constructor(context: AudioContext) {
|
||||
this.context = context;
|
||||
this.buffer = this.context.createBufferSource();
|
||||
this.buffer.start();
|
||||
@@ -51,7 +63,7 @@ export class Sample {
|
||||
this.output.gain.value = 0.1;
|
||||
}
|
||||
|
||||
play() {
|
||||
play(): void {
|
||||
if (this.loaded) {
|
||||
this.buffer = this.context.createBufferSource();
|
||||
this.buffer.buffer = this.sampleBuffer;
|
||||
@@ -60,20 +72,20 @@ export class Sample {
|
||||
}
|
||||
}
|
||||
|
||||
connect(input) {
|
||||
connect(input: AudioNode): void {
|
||||
this.output.connect(input);
|
||||
}
|
||||
|
||||
load(path) {
|
||||
load(path: string): Promise<Sample> {
|
||||
this.loaded = false;
|
||||
return fetch(path)
|
||||
.then((response) => response.arrayBuffer())
|
||||
.then((myBlob) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<AudioBuffer>((resolve, reject) => {
|
||||
this.context.decodeAudioData(myBlob, resolve, reject);
|
||||
});
|
||||
})
|
||||
.then((buffer) => {
|
||||
.then((buffer: AudioBuffer) => {
|
||||
this.sampleBuffer = buffer;
|
||||
this.loaded = true;
|
||||
return this;
|
||||
@@ -82,82 +94,100 @@ export class Sample {
|
||||
}
|
||||
|
||||
export class AmpEnvelope {
|
||||
constructor(context, gain = 1) {
|
||||
context: AudioContext;
|
||||
output: GainNode;
|
||||
partials: any[];
|
||||
velocity: number;
|
||||
gain: number;
|
||||
#attack: number;
|
||||
#decay: number;
|
||||
#sustain: number;
|
||||
#release: number;
|
||||
|
||||
constructor(context: AudioContext, gain: number = 1) {
|
||||
this.context = context;
|
||||
this.output = this.context.createGain();
|
||||
this.output.gain.value = gain;
|
||||
this.partials = [];
|
||||
this.velocity = 0;
|
||||
this.gain = gain;
|
||||
this._attack = 0;
|
||||
this._decay = 0.001;
|
||||
this._sustain = this.output.gain.value;
|
||||
this._release = 0.001;
|
||||
this.#attack = 0;
|
||||
this.#decay = 0.001;
|
||||
this.#sustain = this.output.gain.value;
|
||||
this.#release = 0.001;
|
||||
}
|
||||
|
||||
on(velocity) {
|
||||
on(velocity: number): void {
|
||||
this.velocity = velocity / 127;
|
||||
this.start(this.context.currentTime);
|
||||
}
|
||||
|
||||
off(MidiEvent) {
|
||||
off(MidiEvent: any): void {
|
||||
return this.stop(this.context.currentTime);
|
||||
}
|
||||
|
||||
start(time) {
|
||||
start(time: number): void {
|
||||
this.output.gain.value = 0;
|
||||
this.output.gain.setValueAtTime(0, time);
|
||||
this.output.gain.setTargetAtTime(1, time, this.attack + 0.00001);
|
||||
this.output.gain.setTargetAtTime(this.sustain * this.velocity, time + this.attack, this.decay);
|
||||
}
|
||||
|
||||
stop(time) {
|
||||
stop(time: number): void {
|
||||
this.sustain = this.output.gain.value;
|
||||
this.output.gain.cancelScheduledValues(time);
|
||||
this.output.gain.setValueAtTime(this.sustain, time);
|
||||
this.output.gain.setTargetAtTime(0, time, this.release + 0.00001);
|
||||
}
|
||||
|
||||
set attack(value) {
|
||||
this._attack = value;
|
||||
set attack(value: number) {
|
||||
this.#attack = value;
|
||||
}
|
||||
|
||||
get attack() {
|
||||
return this._attack;
|
||||
get attack(): number {
|
||||
return this.#attack;
|
||||
}
|
||||
|
||||
set decay(value) {
|
||||
this._decay = value;
|
||||
set decay(value: number) {
|
||||
this.#decay = value;
|
||||
}
|
||||
|
||||
get decay() {
|
||||
return this._decay;
|
||||
get decay(): number {
|
||||
return this.#decay;
|
||||
}
|
||||
|
||||
set sustain(value) {
|
||||
set sustain(value: number) {
|
||||
this.gain = value;
|
||||
this._sustain;
|
||||
this.#sustain;
|
||||
}
|
||||
|
||||
get sustain() {
|
||||
get sustain(): number {
|
||||
return this.gain;
|
||||
}
|
||||
|
||||
set release(value) {
|
||||
this._release = value;
|
||||
set release(value: number) {
|
||||
this.#release = value;
|
||||
}
|
||||
|
||||
get release() {
|
||||
return this._release;
|
||||
get release(): number {
|
||||
return this.#release;
|
||||
}
|
||||
|
||||
connect(destination) {
|
||||
connect(destination: AudioNode): void {
|
||||
this.output.connect(destination);
|
||||
}
|
||||
}
|
||||
|
||||
export class Voice {
|
||||
constructor(context, type = "sawtooth", gain = 0.1) {
|
||||
context: AudioContext;
|
||||
type: string;
|
||||
value: number;
|
||||
gain: number;
|
||||
output: GainNode;
|
||||
partials: any[];
|
||||
ampEnvelope: AmpEnvelope;
|
||||
|
||||
constructor(context: AudioContext, gain: number = 0.1, type: string = "sawtooth") {
|
||||
this.context = context;
|
||||
this.type = type;
|
||||
this.value = -1;
|
||||
@@ -169,83 +199,87 @@ export class Voice {
|
||||
this.ampEnvelope.connect(this.output);
|
||||
}
|
||||
|
||||
init() {
|
||||
init(): void {
|
||||
let osc = this.context.createOscillator();
|
||||
osc.type = this.type;
|
||||
osc.type = this.type as OscillatorType;
|
||||
osc.connect(this.ampEnvelope.output);
|
||||
osc.start(this.context.currentTime);
|
||||
this.partials.push(osc);
|
||||
}
|
||||
|
||||
on(MidiEvent) {
|
||||
on(MidiEvent: any): void {
|
||||
this.value = MidiEvent.value;
|
||||
this.partials.forEach((osc) => {
|
||||
this.partials.forEach((osc: OscillatorNode) => {
|
||||
osc.frequency.value = MidiEvent.frequency;
|
||||
});
|
||||
this.ampEnvelope.on(MidiEvent.velocity || MidiEvent);
|
||||
}
|
||||
|
||||
off(MidiEvent) {
|
||||
off(MidiEvent: any): void {
|
||||
this.ampEnvelope.off(MidiEvent);
|
||||
this.partials.forEach((osc) => {
|
||||
this.partials.forEach((osc: OscillatorNode) => {
|
||||
osc.stop(this.context.currentTime + this.ampEnvelope.release * 4);
|
||||
});
|
||||
}
|
||||
|
||||
connect(destination) {
|
||||
connect(destination: AudioNode): void {
|
||||
this.output.connect(destination);
|
||||
}
|
||||
|
||||
set detune(value) {
|
||||
this.partials.forEach((p) => (p.detune.value = value));
|
||||
set detune(value: number) {
|
||||
this.partials.forEach((p: OscillatorNode) => (p.detune.value = value));
|
||||
}
|
||||
|
||||
set attack(value) {
|
||||
set attack(value: number) {
|
||||
this.ampEnvelope.attack = value;
|
||||
}
|
||||
|
||||
get attack() {
|
||||
get attack(): number {
|
||||
return this.ampEnvelope.attack;
|
||||
}
|
||||
|
||||
set decay(value) {
|
||||
set decay(value: number) {
|
||||
this.ampEnvelope.decay = value;
|
||||
}
|
||||
|
||||
get decay() {
|
||||
get decay(): number {
|
||||
return this.ampEnvelope.decay;
|
||||
}
|
||||
|
||||
set sustain(value) {
|
||||
set sustain(value: number) {
|
||||
this.ampEnvelope.sustain = value;
|
||||
}
|
||||
|
||||
get sustain() {
|
||||
get sustain(): number {
|
||||
return this.ampEnvelope.sustain;
|
||||
}
|
||||
|
||||
set release(value) {
|
||||
set release(value: number) {
|
||||
this.ampEnvelope.release = value;
|
||||
}
|
||||
|
||||
get release() {
|
||||
get release(): number {
|
||||
return this.ampEnvelope.release;
|
||||
}
|
||||
}
|
||||
|
||||
export class Noise extends Voice {
|
||||
constructor(context, gain) {
|
||||
#length: number;
|
||||
|
||||
constructor(context: AudioContext, gain: number) {
|
||||
super(context, gain);
|
||||
this._length = 2;
|
||||
this.#length = 2;
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this._length || 2;
|
||||
}
|
||||
set length(value) {
|
||||
this._length = value;
|
||||
get length(): number {
|
||||
return this.#length || 2;
|
||||
}
|
||||
|
||||
init() {
|
||||
set length(value: number) {
|
||||
this.#length = value;
|
||||
}
|
||||
|
||||
init(): void {
|
||||
var lBuffer = new Float32Array(this.length * this.context.sampleRate);
|
||||
var rBuffer = new Float32Array(this.length * this.context.sampleRate);
|
||||
for (let i = 0; i < this.length * this.context.sampleRate; i++) {
|
||||
@@ -266,22 +300,24 @@ export class Noise extends Voice {
|
||||
this.partials.push(osc);
|
||||
}
|
||||
|
||||
on(MidiEvent) {
|
||||
on(MidiEvent: any): void {
|
||||
this.value = MidiEvent.value;
|
||||
this.ampEnvelope.on(MidiEvent.velocity || MidiEvent);
|
||||
}
|
||||
}
|
||||
|
||||
export class Filter extends Effect {
|
||||
constructor(context, type = "lowpass", cutoff = 1000, resonance = 0.9) {
|
||||
constructor(context: AudioContext, type: string = "lowpass", cutoff: number = 1000, resonance: number = 0.9) {
|
||||
super(context);
|
||||
this.name = "filter";
|
||||
this.effect.frequency.value = cutoff;
|
||||
this.effect.Q.value = resonance;
|
||||
this.effect.type = type;
|
||||
if (this.effect instanceof BiquadFilterNode) {
|
||||
this.effect.frequency.value = cutoff;
|
||||
this.effect.Q.value = resonance;
|
||||
this.effect.type = type as BiquadFilterType;
|
||||
}
|
||||
}
|
||||
|
||||
setup() {
|
||||
setup(): void {
|
||||
this.effect = this.context.createBiquadFilter();
|
||||
this.effect.connect(this.output);
|
||||
this.wireUp();
|
||||
@@ -12,6 +12,7 @@ import { Unit } from "../unit/unit";
|
||||
import { UnitSink } from "./unitsink";
|
||||
import { AudioPacket, MessageType } from "./audiopacket";
|
||||
import { AudioManagerStateChangedEvent, AudioSinksChangedEvent, AudioSourcesChangedEvent, ConfigLoadedEvent, SRSClientsChangedEvent } from "../events";
|
||||
import { OlympusConfig } from "../interfaces";
|
||||
|
||||
export class AudioManager {
|
||||
#audioContext: AudioContext;
|
||||
@@ -29,21 +30,15 @@ export class AudioManager {
|
||||
Otherwise, no playback will be performed. */
|
||||
#running: boolean = false;
|
||||
#address: string = "localhost";
|
||||
#port: number = 4000;
|
||||
#endpoint: string = "audio";
|
||||
#port: number;
|
||||
#endpoint: string;
|
||||
#socket: WebSocket | null = null;
|
||||
#guid: string = makeID(22);
|
||||
#SRSClientUnitIDs: number[] = [];
|
||||
|
||||
constructor() {
|
||||
ConfigLoadedEvent.on(() => {
|
||||
let config = getApp().getConfig();
|
||||
if (config["WSPort"]) {
|
||||
this.setPort(config["WSPort"]);
|
||||
}
|
||||
if (config["WSAddress"]) {
|
||||
this.setEndpoint(config["WSEndpoint"]);
|
||||
}
|
||||
ConfigLoadedEvent.on((config: OlympusConfig) => {
|
||||
config.WSPort ? this.setPort(config.WSPort) : this.setEndpoint(config.WSEndpoint);
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
@@ -61,8 +56,9 @@ export class AudioManager {
|
||||
if (res === null) res = this.#address.match(/(?:http|https):\/\/(.+)/);
|
||||
|
||||
let wsAddress = res ? res[1] : this.#address;
|
||||
if (this.#address.includes("https")) this.#socket = new WebSocket(`wss://${wsAddress}/${this.#endpoint}`);
|
||||
else this.#socket = new WebSocket(`ws://${wsAddress}:${this.#port}`);
|
||||
if (this.#endpoint) this.#socket = new WebSocket(`wss://${wsAddress}/${this.#endpoint}`);
|
||||
else if (this.#port) this.#socket = new WebSocket(`ws://${wsAddress}:${this.#port}`);
|
||||
else console.error("The audio backend was enabled but no port/endpoint was provided in the configuration");
|
||||
|
||||
this.#socket = new WebSocket(`wss://refugees.dcsolympus.com/audio`); // TODO: remove, used for testing!
|
||||
|
||||
@@ -119,13 +115,10 @@ export class AudioManager {
|
||||
}
|
||||
|
||||
stop() {
|
||||
/* Stop everything and send update event */
|
||||
this.#running = false;
|
||||
this.#sources.forEach((source) => {
|
||||
source.disconnect();
|
||||
});
|
||||
this.#sinks.forEach((sink) => {
|
||||
sink.disconnect();
|
||||
});
|
||||
this.#sources.forEach((source) => source.disconnect());
|
||||
this.#sinks.forEach((sink) => sink.disconnect());
|
||||
this.#sources = [];
|
||||
this.#sinks = [];
|
||||
|
||||
@@ -190,6 +183,7 @@ export class AudioManager {
|
||||
}
|
||||
const newRadio = new RadioSink();
|
||||
this.#sinks.push(newRadio);
|
||||
/* Set radio name by default to be incremental number */
|
||||
newRadio.setName(`Radio ${this.#sinks.length}`);
|
||||
this.#sources[0].connect(newRadio);
|
||||
AudioSinksChangedEvent.dispatch(this.#sinks);
|
||||
@@ -208,14 +202,16 @@ export class AudioManager {
|
||||
sink.disconnect();
|
||||
this.#sinks = this.#sinks.filter((v) => v != sink);
|
||||
let idx = 1;
|
||||
/* If a radio was removed, rename all the remainin radios to align the names */
|
||||
this.#sinks.forEach((sink) => {
|
||||
if (sink instanceof RadioSink) sink.setName(`Radio ${idx++}`);
|
||||
});
|
||||
AudioSinksChangedEvent.dispatch(getApp().getAudioManager().getSinks());
|
||||
|
||||
/* Disconnect all the sources that where connected to this sink */
|
||||
this.#sources.forEach((source) => {
|
||||
if (source.getConnectedTo().includes(sink))
|
||||
source.disconnect(sink)
|
||||
})
|
||||
if (source.getConnectedTo().includes(sink)) source.disconnect(sink);
|
||||
});
|
||||
}
|
||||
|
||||
getGuid() {
|
||||
@@ -239,6 +235,7 @@ export class AudioManager {
|
||||
}
|
||||
|
||||
#syncRadioSettings() {
|
||||
/* Send the radio settings of each radio to the SRS backend */
|
||||
let message = {
|
||||
type: "Settings update",
|
||||
guid: this.#guid,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AudioPacket } from "./audiopacket";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { AudioSinksChangedEvent } from "../events";
|
||||
|
||||
// TODO should this be shared or radio specific?
|
||||
let packetID = 0;
|
||||
|
||||
/* Radio sink, basically implements a simple SRS Client in Olympus. Does not support encryption at this moment */
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
import { Context, MapOptions } from "../types/types";
|
||||
|
||||
export const DEFAULT_CONTEXT: Context = "default context";
|
||||
|
||||
export enum OlympusEvent {
|
||||
STATE_CHANGED = "State changed",
|
||||
UNITS_SELECTED = "Unit selected"
|
||||
}
|
||||
import { MapOptions } from "../types/types";
|
||||
|
||||
export const UNITS_URI = "units";
|
||||
export const WEAPONS_URI = "weapons";
|
||||
@@ -252,34 +245,34 @@ export enum OlympusState {
|
||||
JTAC = "JTAC",
|
||||
OPTIONS = "Options",
|
||||
AUDIO = "Audio",
|
||||
AIRBASE = "Airbase"
|
||||
AIRBASE = "Airbase",
|
||||
}
|
||||
|
||||
export const NO_SUBSTATE = "No substate";
|
||||
|
||||
export enum UnitControlSubState {
|
||||
NO_SUBSTATE = "No substate",
|
||||
FORMATION = "Formation"
|
||||
FORMATION = "Formation",
|
||||
}
|
||||
|
||||
export enum DrawSubState {
|
||||
NO_SUBSTATE = "No substate",
|
||||
DRAW_POLYGON = "Polygon",
|
||||
DRAW_CIRCLE = "Circle",
|
||||
EDIT = "Edit"
|
||||
EDIT = "Edit",
|
||||
}
|
||||
|
||||
export enum JTACSubState {
|
||||
NO_SUBSTATE = "No substate",
|
||||
SELECT_ECHO_POINT = "ECHO",
|
||||
SELECT_IP = "IP",
|
||||
SELECT_TARGET = "Target"
|
||||
SELECT_TARGET = "Target",
|
||||
}
|
||||
|
||||
export enum SpawnSubState {
|
||||
NO_SUBSTATE = "No substate",
|
||||
SPAWN_UNIT = "Unit",
|
||||
SPAWN_EFFECT = "Effect"
|
||||
SPAWN_EFFECT = "Effect",
|
||||
}
|
||||
|
||||
export type OlympusSubState = DrawSubState | JTACSubState | SpawnSubState | string;
|
||||
@@ -292,17 +285,6 @@ export const IADSDensities: { [key: string]: number } = {
|
||||
};
|
||||
export const GROUND_UNIT_AIR_DEFENCE_REGEX: RegExp = /(\b(AAA|SAM|MANPADS?|[mM]anpads?)|[sS]tinger\b)/;
|
||||
|
||||
export const MAP_OPTIONS_TOOLTIPS = {
|
||||
hideGroupMembers: "Hide group members when zoomed out",
|
||||
hideUnitsShortRangeRings: "Hide short range units threat range rings (R)",
|
||||
showUnitContacts: "Show selected units contact lines",
|
||||
showUnitPaths: "Show selected unit paths",
|
||||
showUnitTargets: "Show selected unit targets",
|
||||
showUnitLabels: "Show unit labels (L)",
|
||||
showUnitsEngagementRings: "Show units threat range rings (Q)",
|
||||
showUnitsAcquisitionRings: "Show units detection range rings (E)",
|
||||
};
|
||||
|
||||
export const MAP_OPTIONS_DEFAULTS = {
|
||||
hideGroupMembers: true,
|
||||
hideUnitsShortRangeRings: true,
|
||||
@@ -314,7 +296,7 @@ export const MAP_OPTIONS_DEFAULTS = {
|
||||
showUnitsAcquisitionRings: true,
|
||||
fillSelectedRing: false,
|
||||
showMinimap: false,
|
||||
protectDCSUnits: true
|
||||
protectDCSUnits: true,
|
||||
} as MapOptions;
|
||||
|
||||
export const MAP_HIDDEN_TYPES_DEFAULTS = {
|
||||
@@ -383,12 +365,6 @@ export enum DataIndexes {
|
||||
endOfData = 255,
|
||||
}
|
||||
|
||||
export const MGRS_PRECISION_10KM = 2;
|
||||
export const MGRS_PRECISION_1KM = 3;
|
||||
export const MGRS_PRECISION_100M = 4;
|
||||
export const MGRS_PRECISION_10M = 5;
|
||||
export const MGRS_PRECISION_1M = 6;
|
||||
|
||||
export const DELETE_CYCLE_TIME = 0.05;
|
||||
export const DELETE_SLOW_THRESHOLD = 50;
|
||||
|
||||
@@ -410,5 +386,5 @@ export enum ContextActionColors {
|
||||
OTHER,
|
||||
ADMIN,
|
||||
ENGAGE,
|
||||
DELETE
|
||||
}
|
||||
DELETE,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AudioSink } from "./audio/audiosink";
|
||||
import { AudioSource } from "./audio/audiosource";
|
||||
import { OlympusState, OlympusSubState } from "./constants/constants";
|
||||
import { ServerStatus } from "./interfaces";
|
||||
import { OlympusConfig, ServerStatus } from "./interfaces";
|
||||
import { CoalitionCircle } from "./map/coalitionarea/coalitioncircle";
|
||||
import { CoalitionPolygon } from "./map/coalitionarea/coalitionpolygon";
|
||||
import { Airbase } from "./mission/airbase";
|
||||
@@ -54,16 +54,16 @@ export class AppStateChangedEvent {
|
||||
}
|
||||
|
||||
export class ConfigLoadedEvent {
|
||||
/* TODO add config */
|
||||
static on(callback: () => void) {
|
||||
static on(callback: (config: OlympusConfig) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback();
|
||||
callback(ev.detail);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch() {
|
||||
document.dispatchEvent(new CustomEvent(this.name));
|
||||
static dispatch(config: OlympusConfig) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: config}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
console.log(config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,19 @@ export class ServerStatusUpdatedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class UnitDatabaseLoadedEvent {
|
||||
static on(callback: () => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch() {
|
||||
document.dispatchEvent(new CustomEvent(this.name));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
/************** Map events ***************/
|
||||
export class HiddenTypesChangedEvent {
|
||||
static on(callback: (hiddenTypes: MapHiddenTypes) => void) {
|
||||
@@ -160,26 +173,26 @@ export class ContactsUpdatedEvent {
|
||||
}
|
||||
|
||||
export class ContextActionSetChangedEvent {
|
||||
static on(callback: (contextActionSet: ContextActionSet) => void) {
|
||||
static on(callback: (contextActionSet: ContextActionSet | null) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.contextActionSet);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(contextActionSet: ContextActionSet) {
|
||||
static dispatch(contextActionSet: ContextActionSet | null) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: {contextActionSet}}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextActionChangedEvent {
|
||||
static on(callback: (contextAction: ContextAction) => void) {
|
||||
static on(callback: (contextAction: ContextAction | null) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.contextActionSet);
|
||||
callback(ev.detail.contextAction);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(contextAction: ContextAction) {
|
||||
static dispatch(contextAction: ContextAction | null) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: {contextAction}}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
@@ -221,7 +234,7 @@ export class CommandModeOptionsChangedEvent {
|
||||
}
|
||||
|
||||
/************** Audio backend events ***************/
|
||||
/* TODO: split into two events for signgle source changed */
|
||||
/* TODO: split into two events for single source changed */
|
||||
export class AudioSourcesChangedEvent {
|
||||
/* TODO add audio sources */
|
||||
static on(callback: (audioSources: AudioSource[]) => void) {
|
||||
@@ -237,7 +250,7 @@ export class AudioSourcesChangedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: split into two events for signgle sink changed */
|
||||
/* TODO: split into two events for single sink changed */
|
||||
export class AudioSinksChangedEvent {
|
||||
static on(callback: (audioSinks: AudioSink[]) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { Coalition, Context } from "./types/types";
|
||||
import { AudioSink } from "./audio/audiosink";
|
||||
|
||||
class Airbase {}
|
||||
export interface OlympusConfig {
|
||||
port: number;
|
||||
elevationProvider: {
|
||||
provider: string;
|
||||
username: string | null;
|
||||
password: string | null;
|
||||
};
|
||||
mapLayers: {
|
||||
[key: string]: {
|
||||
urlTemplate: string;
|
||||
minZoom: number;
|
||||
maxZoom: number;
|
||||
attribution?: string;
|
||||
};
|
||||
};
|
||||
mapMirrors: {
|
||||
[key: string]: string;
|
||||
};
|
||||
SRSPort: number;
|
||||
WSPort?: number;
|
||||
WSEndpoint?: string;
|
||||
}
|
||||
|
||||
export interface ContextMenuOption {
|
||||
tooltip: string;
|
||||
@@ -204,11 +223,13 @@ export interface LoadoutBlueprint {
|
||||
|
||||
export interface UnitBlueprint {
|
||||
name: string;
|
||||
category: string;
|
||||
enabled: boolean;
|
||||
coalition: string;
|
||||
era: string;
|
||||
label: string;
|
||||
shortLabel: string;
|
||||
roles?: string[];
|
||||
type?: string;
|
||||
loadouts?: LoadoutBlueprint[];
|
||||
filename?: string;
|
||||
@@ -260,15 +281,9 @@ export interface AirbaseChartRunwayData {
|
||||
length: string;
|
||||
}
|
||||
|
||||
export interface Listener {
|
||||
callback: CallableFunction;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface ShortcutOptions {
|
||||
altKey?: boolean;
|
||||
callback: CallableFunction;
|
||||
context?: Context;
|
||||
ctrlKey?: boolean;
|
||||
name?: string;
|
||||
shiftKey?: boolean;
|
||||
@@ -296,6 +311,3 @@ export interface ServerStatus {
|
||||
connected: boolean;
|
||||
paused: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export var BoxSelect = Handler.extend({
|
||||
this._forceBoxSelect = false;
|
||||
map.on("unload", this._destroy, this);
|
||||
|
||||
document.addEventListener("mapForceBoxSelect", (e) => {
|
||||
document.addEventListener("forceboxselect", (e) => {
|
||||
this._forceBoxSelect = true;
|
||||
const originalEvent = (e as CustomEvent).detail;
|
||||
this._onMouseDown(originalEvent);
|
||||
@@ -23,12 +23,12 @@ export var BoxSelect = Handler.extend({
|
||||
|
||||
addHooks: function () {
|
||||
DomEvent.on(this._container, "mousedown", this._onMouseDown, this);
|
||||
DomEvent.on(this._container, "mapForceBoxSelect", this._onMouseDown, this);
|
||||
DomEvent.on(this._container, "forceboxselect", this._onMouseDown, this);
|
||||
},
|
||||
|
||||
removeHooks: function () {
|
||||
DomEvent.off(this._container, "mousedown", this._onMouseDown, this);
|
||||
DomEvent.off(this._container, "mapForceBoxSelect", this._onMouseDown, this);
|
||||
DomEvent.off(this._container, "forceboxselect", this._onMouseDown, this);
|
||||
},
|
||||
|
||||
moved: function () {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Coalition } from "../../types/types";
|
||||
import * as turf from "@turf/turf";
|
||||
import { CoalitionAreaSelectedEvent } from "../../events";
|
||||
|
||||
let totalAreas = 0;
|
||||
let totalCircles = 0;
|
||||
|
||||
export class CoalitionCircle extends Circle {
|
||||
#coalition: Coalition = "blue";
|
||||
@@ -19,7 +19,7 @@ export class CoalitionCircle extends Circle {
|
||||
constructor(latlng: LatLngExpression, options: CircleOptions) {
|
||||
if (options === undefined) options = { radius: 0 };
|
||||
|
||||
totalAreas++;
|
||||
totalCircles++;
|
||||
|
||||
options.bubblingMouseEvents = false;
|
||||
options.interactive = false;
|
||||
@@ -29,7 +29,7 @@ export class CoalitionCircle extends Circle {
|
||||
super(latlng, options);
|
||||
this.#setColors();
|
||||
|
||||
this.#labelText = `Circle ${totalAreas}`;
|
||||
this.#labelText = `Circle ${totalCircles}`;
|
||||
|
||||
if ([BLUE_COMMANDER, RED_COMMANDER].includes(getApp().getMissionManager().getCommandModeOptions().commandMode))
|
||||
this.setCoalition(getApp().getMissionManager().getCommandedCoalition());
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Coalition } from "../../types/types";
|
||||
import { polyCenter } from "../../other/utils";
|
||||
import { CoalitionAreaSelectedEvent } from "../../events";
|
||||
|
||||
let totalAreas = 0;
|
||||
let totalPolygons = 0;
|
||||
|
||||
export class CoalitionPolygon extends Polygon {
|
||||
#coalition: Coalition = "blue";
|
||||
@@ -22,7 +22,7 @@ export class CoalitionPolygon extends Polygon {
|
||||
constructor(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][], options?: PolylineOptions) {
|
||||
if (options === undefined) options = {};
|
||||
|
||||
totalAreas++;
|
||||
totalPolygons++;
|
||||
|
||||
options.bubblingMouseEvents = false;
|
||||
options.interactive = false;
|
||||
@@ -32,7 +32,7 @@ export class CoalitionPolygon extends Polygon {
|
||||
super(latlngs, options);
|
||||
this.#setColors();
|
||||
|
||||
this.#labelText = `Polygon ${totalAreas}`;
|
||||
this.#labelText = `Polygon ${totalPolygons}`;
|
||||
|
||||
if ([BLUE_COMMANDER, RED_COMMANDER].includes(getApp().getMissionManager().getCommandModeOptions().commandMode))
|
||||
this.setCoalition(getApp().getMissionManager().getCommandedCoalition());
|
||||
|
||||
@@ -22,22 +22,30 @@ import {
|
||||
} from "../constants/constants";
|
||||
import { CoalitionPolygon } from "./coalitionarea/coalitionpolygon";
|
||||
import { MapHiddenTypes, MapOptions } from "../types/types";
|
||||
import { EffectRequestTable, SpawnRequestTable } from "../interfaces";
|
||||
import { EffectRequestTable, OlympusConfig, SpawnRequestTable } from "../interfaces";
|
||||
import { ContextAction } from "../unit/contextaction";
|
||||
|
||||
/* Stylesheets */
|
||||
import "./markers/stylesheets/airbase.css";
|
||||
import "./markers/stylesheets/bullseye.css";
|
||||
import "./markers/stylesheets/units.css";
|
||||
import "./map.css";
|
||||
import "./stylesheets/map.css";
|
||||
import { CoalitionCircle } from "./coalitionarea/coalitioncircle";
|
||||
|
||||
import { initDraggablePath } from "./coalitionarea/draggablepath";
|
||||
import { faDrawPolygon, faHandPointer, faJetFighter, faMap } from "@fortawesome/free-solid-svg-icons";
|
||||
import { ExplosionMarker } from "./markers/explosionmarker";
|
||||
import { TextMarker } from "./markers/textmarker";
|
||||
import { TargetMarker } from "./markers/targetmarker";
|
||||
import { AppStateChangedEvent, CoalitionAreaSelectedEvent, ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent } from "../events";
|
||||
import {
|
||||
AppStateChangedEvent,
|
||||
CoalitionAreaSelectedEvent,
|
||||
ConfigLoadedEvent,
|
||||
ContextActionChangedEvent,
|
||||
ContextActionSetChangedEvent,
|
||||
HiddenTypesChangedEvent,
|
||||
MapOptionsChangedEvent,
|
||||
MapSourceChangedEvent,
|
||||
} from "../events";
|
||||
import { ContextActionSet } from "../unit/contextactionset";
|
||||
|
||||
/* Register the handler for the box selection */
|
||||
@@ -167,9 +175,7 @@ export class Map extends L.Map {
|
||||
this.on("dblclick", (e: any) => this.#onDoubleClick(e));
|
||||
this.on("mouseup", (e: any) => this.#onMouseUp(e));
|
||||
this.on("mousedown", (e: any) => this.#onMouseDown(e));
|
||||
this.on("contextmenu", (e: any) => {
|
||||
e.originalEvent.preventDefault();
|
||||
});
|
||||
this.on("contextmenu", (e: any) => e.originalEvent.preventDefault());
|
||||
|
||||
this.on("mousemove", (e: any) => this.#onMouseMove(e));
|
||||
|
||||
@@ -213,8 +219,7 @@ export class Map extends L.Map {
|
||||
this.updateMinimap();
|
||||
});
|
||||
|
||||
ConfigLoadedEvent.on(() => {
|
||||
let config = getApp().getConfig();
|
||||
ConfigLoadedEvent.on((config: OlympusConfig) => {
|
||||
let layerSet = false;
|
||||
|
||||
/* First load the map mirrors */
|
||||
@@ -349,66 +354,18 @@ export class Map extends L.Map {
|
||||
|
||||
setContextActionSet(contextActionSet: ContextActionSet | null) {
|
||||
this.#contextActionSet = contextActionSet;
|
||||
ContextActionSetChangedEvent.dispatch(this.#contextActionSet)
|
||||
}
|
||||
|
||||
setContextAction(contextAction: ContextAction | null) {
|
||||
this.#contextAction = contextAction;
|
||||
}
|
||||
|
||||
#onStateChanged(state: OlympusState, subState: OlympusSubState) {
|
||||
/* Operations to perform when leaving a state */
|
||||
this.getSelectedCoalitionArea()?.setEditing(false);
|
||||
this.#currentSpawnMarker?.removeFrom(this);
|
||||
this.#currentSpawnMarker = null;
|
||||
|
||||
if (state !== OlympusState.UNIT_CONTROL) {
|
||||
getApp().getUnitsManager().deselectAllUnits();
|
||||
}
|
||||
|
||||
if (state !== OlympusState.DRAW || (state === OlympusState.DRAW && subState !== DrawSubState.EDIT)) {
|
||||
this.deselectAllCoalitionAreas();
|
||||
}
|
||||
|
||||
/* Operations to perform when entering a state */
|
||||
if (state === OlympusState.IDLE) {
|
||||
getApp().getUnitsManager()?.deselectAllUnits();
|
||||
} else if (state === OlympusState.SPAWN) {
|
||||
if (subState === SpawnSubState.SPAWN_UNIT) {
|
||||
console.log(`Spawn request table:`);
|
||||
console.log(this.#spawnRequestTable);
|
||||
this.#currentSpawnMarker = new TemporaryUnitMarker(
|
||||
new L.LatLng(0, 0),
|
||||
this.#spawnRequestTable?.unit.unitType ?? "",
|
||||
this.#spawnRequestTable?.coalition ?? "neutral"
|
||||
);
|
||||
this.#currentSpawnMarker.addTo(this);
|
||||
} else if (subState === SpawnSubState.SPAWN_EFFECT) {
|
||||
console.log(`Effect request table:`);
|
||||
console.log(this.#effectRequestTable);
|
||||
// TODO
|
||||
//this.#currentEffectMarker = new TemporaryUnitMarker(new L.LatLng(0, 0), this.#spawnRequestTable?.unit.unitType ?? "", this.#spawnRequestTable?.coalition ?? "neutral")
|
||||
//this.#currentEffectMarker.addTo(this);
|
||||
}
|
||||
} else if (state === OlympusState.UNIT_CONTROL) {
|
||||
console.log(`Context action:`);
|
||||
console.log(this.#contextAction);
|
||||
} else if (state === OlympusState.DRAW) {
|
||||
if (subState == DrawSubState.DRAW_POLYGON) {
|
||||
this.#coalitionAreas.push(new CoalitionPolygon([]));
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].setSelected(true);
|
||||
} else if (subState === DrawSubState.DRAW_CIRCLE) {
|
||||
this.#coalitionAreas.push(new CoalitionCircle(new L.LatLng(0, 0), { radius: 1000 }));
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].setSelected(true);
|
||||
}
|
||||
}
|
||||
ContextActionChangedEvent.dispatch(this.#contextAction)
|
||||
}
|
||||
|
||||
getCurrentControls() {
|
||||
const touch = matchMedia("(hover: none)").matches;
|
||||
return [];
|
||||
// TODO
|
||||
// TODO, is this a good idea? I never look at the thing
|
||||
//if (getApp().getState() === IDLE) {
|
||||
// return [
|
||||
// {
|
||||
@@ -624,6 +581,14 @@ export class Map extends L.Map {
|
||||
if (this.hasLayer(coalitionArea)) this.removeLayer(coalitionArea);
|
||||
}
|
||||
|
||||
getSelectedCoalitionArea() {
|
||||
const coalitionArea = this.#coalitionAreas.find((coalitionArea: CoalitionPolygon | CoalitionCircle) => {
|
||||
return coalitionArea.getSelected();
|
||||
});
|
||||
|
||||
return coalitionArea ?? null;
|
||||
}
|
||||
|
||||
setHiddenType(key: string, value: boolean) {
|
||||
this.#hiddenTypes[key] = value;
|
||||
HiddenTypesChangedEvent.dispatch(this.#hiddenTypes);
|
||||
@@ -668,12 +633,9 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
this.setView(bounds.getCenter(), 8);
|
||||
|
||||
this.updateMinimap();
|
||||
|
||||
const boundaries = this.#getMinimapBoundaries();
|
||||
this.#miniMapPolyline.setLatLngs(boundaries[theatre as keyof typeof boundaries]);
|
||||
|
||||
this.setLayerName(this.#layerName);
|
||||
}
|
||||
|
||||
@@ -763,14 +725,6 @@ export class Map extends L.Map {
|
||||
return marker;
|
||||
}
|
||||
|
||||
getSelectedCoalitionArea() {
|
||||
const coalitionArea = this.#coalitionAreas.find((coalitionArea: CoalitionPolygon | CoalitionCircle) => {
|
||||
return coalitionArea.getSelected();
|
||||
});
|
||||
|
||||
return coalitionArea ?? null;
|
||||
}
|
||||
|
||||
setOption(key, value) {
|
||||
this.#options[key] = value;
|
||||
MapOptionsChangedEvent.dispatch(this.#options);
|
||||
@@ -851,6 +805,50 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
/* Event handlers */
|
||||
#onStateChanged(state: OlympusState, subState: OlympusSubState) {
|
||||
/* Operations to perform when leaving a state */
|
||||
this.getSelectedCoalitionArea()?.setEditing(false);
|
||||
this.#currentSpawnMarker?.removeFrom(this);
|
||||
this.#currentSpawnMarker = null;
|
||||
if (state !== OlympusState.UNIT_CONTROL) getApp().getUnitsManager().deselectAllUnits();
|
||||
if (state !== OlympusState.DRAW || (state === OlympusState.DRAW && subState !== DrawSubState.EDIT)) this.deselectAllCoalitionAreas();
|
||||
|
||||
/* Operations to perform when entering a state */
|
||||
if (state === OlympusState.IDLE) {
|
||||
getApp().getUnitsManager()?.deselectAllUnits();
|
||||
} else if (state === OlympusState.SPAWN) {
|
||||
if (subState === SpawnSubState.SPAWN_UNIT) {
|
||||
console.log(`Spawn request table:`);
|
||||
console.log(this.#spawnRequestTable);
|
||||
this.#currentSpawnMarker = new TemporaryUnitMarker(
|
||||
new L.LatLng(0, 0),
|
||||
this.#spawnRequestTable?.unit.unitType ?? "",
|
||||
this.#spawnRequestTable?.coalition ?? "neutral"
|
||||
);
|
||||
this.#currentSpawnMarker.addTo(this);
|
||||
} else if (subState === SpawnSubState.SPAWN_EFFECT) {
|
||||
console.log(`Effect request table:`);
|
||||
console.log(this.#effectRequestTable);
|
||||
// TODO add temporary effect marker
|
||||
//this.#currentEffectMarker = new TemporaryUnitMarker(new L.LatLng(0, 0), this.#spawnRequestTable?.unit.unitType ?? "", this.#spawnRequestTable?.coalition ?? "neutral")
|
||||
//this.#currentEffectMarker.addTo(this);
|
||||
}
|
||||
} else if (state === OlympusState.UNIT_CONTROL) {
|
||||
console.log(`Context action:`);
|
||||
console.log(this.#contextAction);
|
||||
} else if (state === OlympusState.DRAW) {
|
||||
if (subState == DrawSubState.DRAW_POLYGON) {
|
||||
this.#coalitionAreas.push(new CoalitionPolygon([]));
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].setSelected(true);
|
||||
} else if (subState === DrawSubState.DRAW_CIRCLE) {
|
||||
this.#coalitionAreas.push(new CoalitionCircle(new L.LatLng(0, 0), { radius: 1000 }));
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onDragStart(e: any) {
|
||||
this.#isDragging = true;
|
||||
}
|
||||
@@ -1037,14 +1035,14 @@ export class Map extends L.Map {
|
||||
if (!this.#isDragging && !this.#isZooming) {
|
||||
this.deselectAllCoalitionAreas();
|
||||
if (getApp().getState() === OlympusState.IDLE) {
|
||||
if (e.type === "touchstart") document.dispatchEvent(new CustomEvent("mapForceBoxSelect", { detail: e }));
|
||||
else document.dispatchEvent(new CustomEvent("mapForceBoxSelect", { detail: e.originalEvent }));
|
||||
if (e.type === "touchstart") document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e }));
|
||||
else document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e.originalEvent }));
|
||||
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
|
||||
if (e.originalEvent.button === 2) {
|
||||
document.dispatchEvent(new CustomEvent("showMapContextMenu", { detail: e }));
|
||||
document.dispatchEvent(new CustomEvent("showMapContextMenu", { detail: e })); // TODP
|
||||
} else {
|
||||
if (e.type === "touchstart") document.dispatchEvent(new CustomEvent("mapForceBoxSelect", { detail: e }));
|
||||
else document.dispatchEvent(new CustomEvent("mapForceBoxSelect", { detail: e.originalEvent }));
|
||||
if (e.type === "touchstart") document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e }));
|
||||
else document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e.originalEvent }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { DivIcon, Map, Marker } from "leaflet";
|
||||
import { MarkerOptions } from "leaflet";
|
||||
import { LatLngExpression } from "leaflet";
|
||||
import { DivIcon, Map, Marker, MarkerOptions, LatLngExpression } from "leaflet";
|
||||
|
||||
export class CustomMarker extends Marker {
|
||||
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { CustomMarker } from "../markers/custommarker";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
|
||||
export class DestinationPreviewHandle extends CustomMarker {
|
||||
constructor(latlng: LatLng) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../../other/utils";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
|
||||
export class TemporaryUnitMarker extends CustomMarker {
|
||||
#name: string;
|
||||
@@ -36,42 +36,43 @@ export class TemporaryUnitMarker extends CustomMarker {
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
const category = getMarkerCategoryByName(this.#name);
|
||||
const databaseEntry = getUnitDatabaseByCategory(category)?.getByName(this.#name);
|
||||
const blueprint = getApp().getUnitsManager().getDatabase().getByName(this.#name);
|
||||
|
||||
/* Set the icon */
|
||||
var icon = new DivIcon({
|
||||
className: "leaflet-unit-icon",
|
||||
iconAnchor: [25, 25],
|
||||
iconSize: [50, 50],
|
||||
});
|
||||
this.setIcon(icon);
|
||||
if (blueprint) {
|
||||
/* Set the icon */
|
||||
var icon = new DivIcon({
|
||||
className: "leaflet-unit-icon",
|
||||
iconAnchor: [25, 25],
|
||||
iconSize: [50, 50],
|
||||
});
|
||||
this.setIcon(icon);
|
||||
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("unit");
|
||||
el.setAttribute("data-object", `unit-${category}`);
|
||||
el.setAttribute("data-coalition", this.#coalition);
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("unit");
|
||||
el.setAttribute("data-object", `unit-${blueprint.category}`);
|
||||
el.setAttribute("data-coalition", this.#coalition);
|
||||
|
||||
// Main icon
|
||||
var unitIcon = document.createElement("div");
|
||||
unitIcon.classList.add("unit-icon");
|
||||
var img = document.createElement("img");
|
||||
// Main icon
|
||||
var unitIcon = document.createElement("div");
|
||||
unitIcon.classList.add("unit-icon");
|
||||
var img = document.createElement("img");
|
||||
|
||||
img.src = `/vite/images/units/${databaseEntry?.markerFile ?? category}.svg`;
|
||||
img.onload = () => SVGInjector(img);
|
||||
unitIcon.appendChild(img);
|
||||
unitIcon.toggleAttribute("data-rotate-to-heading", false);
|
||||
el.append(unitIcon);
|
||||
img.src = `/vite/images/units/${blueprint.markerFile ?? blueprint.category}.svg`;
|
||||
img.onload = () => SVGInjector(img);
|
||||
unitIcon.appendChild(img);
|
||||
unitIcon.toggleAttribute("data-rotate-to-heading", false);
|
||||
el.append(unitIcon);
|
||||
|
||||
// Short label
|
||||
if (category == "aircraft" || category == "helicopter") {
|
||||
var shortLabel = document.createElement("div");
|
||||
shortLabel.classList.add("unit-short-label");
|
||||
shortLabel.innerText = databaseEntry?.shortLabel || "";
|
||||
el.append(shortLabel);
|
||||
// Short label
|
||||
if (blueprint.category == "aircraft" || blueprint.category == "helicopter") {
|
||||
var shortLabel = document.createElement("div");
|
||||
shortLabel.classList.add("unit-short-label");
|
||||
shortLabel.innerText = blueprint?.shortLabel || "";
|
||||
el.append(shortLabel);
|
||||
}
|
||||
|
||||
this.getElement()?.appendChild(el);
|
||||
this.getElement()?.classList.add("ol-temporary-marker");
|
||||
}
|
||||
|
||||
this.getElement()?.appendChild(el);
|
||||
this.getElement()?.classList.add("ol-temporary-marker");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
:root {
|
||||
/** Colours **/
|
||||
|
||||
/*** Coalition: neutral ***/
|
||||
--primary-neutral: #949ba7;
|
||||
--secondary-neutral-outline: #111111;
|
||||
@@ -6,6 +6,7 @@ import { getApp } from "../olympusapp";
|
||||
import { OlympusState } from "../constants/constants";
|
||||
import { AirbaseSelectedEvent } from "../events";
|
||||
|
||||
// TODO add ability to select the marker
|
||||
export class Airbase extends CustomMarker {
|
||||
#name: string = "";
|
||||
#chartData: AirbaseChartData = {
|
||||
@@ -54,7 +55,7 @@ export class Airbase extends CustomMarker {
|
||||
|
||||
setCoalition(coalition: string) {
|
||||
this.#coalition = coalition;
|
||||
(<HTMLElement>this.getElement()?.querySelector(".airbase-icon")).dataset.coalition = this.#coalition;
|
||||
(this.getElement()?.querySelector(".airbase-icon") as HTMLElement).dataset.coalition = this.#coalition;
|
||||
}
|
||||
|
||||
getChartData() {
|
||||
|
||||
@@ -25,7 +25,7 @@ export class Bullseye extends CustomMarker {
|
||||
|
||||
setCoalition(coalition: string) {
|
||||
this.#coalition = coalition;
|
||||
(<HTMLElement>this.getElement()?.querySelector(".bullseye-icon")).dataset.coalition = this.#coalition;
|
||||
(this.getElement()?.querySelector(".bullseye-icon") as HTMLElement).dataset.coalition = this.#coalition;
|
||||
}
|
||||
|
||||
getCoalition() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { DivIcon, LatLng, Map } from "leaflet";
|
||||
import { DivIcon, Map } from "leaflet";
|
||||
import { Airbase } from "./airbase";
|
||||
|
||||
// TODO add more carrier types, currently works only for the Nimitz class
|
||||
export class Carrier extends Airbase {
|
||||
createIcon() {
|
||||
var icon = new DivIcon({
|
||||
|
||||
@@ -2,14 +2,7 @@ import { LatLng } from "leaflet";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { Airbase } from "./airbase";
|
||||
import { Bullseye } from "./bullseye";
|
||||
import { BLUE_COMMANDER, ERAS, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
|
||||
//import { Dropdown } from "../controls/dropdown";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
//import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
|
||||
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
|
||||
//import { Popup } from "../popups/popup";
|
||||
import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
|
||||
import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionData } from "../interfaces";
|
||||
import { Coalition } from "../types/types";
|
||||
import { Carrier } from "./carrier";
|
||||
@@ -39,8 +32,6 @@ export class MissionManager {
|
||||
};
|
||||
#remainingSetupTime: number = 0;
|
||||
#spentSpawnPoint: number = 0;
|
||||
//#commandModeDialog: HTMLElement;
|
||||
//#commandModeErasDropdown: Dropdown;
|
||||
#coalitions: { red: string[]; blue: string[] } = { red: [], blue: [] };
|
||||
|
||||
constructor() {}
|
||||
@@ -116,17 +107,6 @@ export class MissionManager {
|
||||
/* Set the command mode options */
|
||||
this.#setcommandModeOptions(data.mission.commandModeOptions);
|
||||
this.#remainingSetupTime = this.getCommandModeOptions().setupTime - this.getDateAndTime().elapsedTime;
|
||||
var commandModePhaseEl = document.querySelector("#command-mode-phase") as HTMLElement;
|
||||
if (commandModePhaseEl) {
|
||||
if (this.#remainingSetupTime > 0) {
|
||||
var remainingTime = `-${new Date(this.#remainingSetupTime * 1000).toISOString().substring(14, 19)}`;
|
||||
commandModePhaseEl.dataset.remainingTime = remainingTime;
|
||||
}
|
||||
|
||||
commandModePhaseEl.classList.toggle("setup-phase", this.#remainingSetupTime > 0 && this.getCommandModeOptions().restrictSpawns);
|
||||
//commandModePhaseEl.classList.toggle("game-commenced", this.#remainingSetupTime <= 0 || !this.getCommandModeOptions().restrictSpawns);
|
||||
//commandModePhaseEl.classList.toggle("no-restrictions", !this.getCommandModeOptions().restrictSpawns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,50 +207,6 @@ export class MissionManager {
|
||||
return this.#frameRate;
|
||||
}
|
||||
|
||||
showCommandModeDialog() {
|
||||
//const options = this.getCommandModeOptions()
|
||||
//const { restrictSpawns, restrictToCoalition, setupTime } = options;
|
||||
//this.#toggleSpawnRestrictions(restrictSpawns);
|
||||
//
|
||||
///* Create the checkboxes to select the unit eras */
|
||||
//this.#commandModeErasDropdown.setOptionsElements(
|
||||
// ERAS.sort((eraA, eraB) => {
|
||||
// return ( eraA.chronologicalOrder > eraB.chronologicalOrder ) ? 1 : -1;
|
||||
// }).map((era) => {
|
||||
// return createCheckboxOption(era.name, `Enable ${era} units spawns`, this.getCommandModeOptions().eras.includes(era.name));
|
||||
// })
|
||||
//);
|
||||
//
|
||||
//this.#commandModeDialog.classList.remove("hide");
|
||||
//
|
||||
//const restrictSpawnsCheckbox = this.#commandModeDialog.querySelector("#restrict-spawns")?.querySelector("input") as HTMLInputElement;
|
||||
//const restrictToCoalitionCheckbox = this.#commandModeDialog.querySelector("#restrict-to-coalition")?.querySelector("input") as HTMLInputElement;
|
||||
//const blueSpawnPointsInput = this.#commandModeDialog.querySelector("#blue-spawn-points")?.querySelector("input") as HTMLInputElement;
|
||||
//const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement;
|
||||
//const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement;
|
||||
//
|
||||
//restrictSpawnsCheckbox.checked = restrictSpawns;
|
||||
//restrictToCoalitionCheckbox.checked = restrictToCoalition;
|
||||
//blueSpawnPointsInput.value = String(options.spawnPoints.blue);
|
||||
//redSpawnPointsInput.value = String(options.spawnPoints.red);
|
||||
//setupTimeInput.value = String(Math.floor(setupTime / 60.0));
|
||||
}
|
||||
|
||||
#applycommandModeOptions() {
|
||||
//this.#commandModeDialog.classList.add("hide");
|
||||
//
|
||||
//const restrictSpawnsCheckbox = this.#commandModeDialog.querySelector("#restrict-spawns")?.querySelector("input") as HTMLInputElement;
|
||||
//const restrictToCoalitionCheckbox = this.#commandModeDialog.querySelector("#restrict-to-coalition")?.querySelector("input") as HTMLInputElement;
|
||||
//const blueSpawnPointsInput = this.#commandModeDialog.querySelector("#blue-spawn-points")?.querySelector("input") as HTMLInputElement;
|
||||
//const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement;
|
||||
//const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement;
|
||||
//
|
||||
//var eras: string[] = [];
|
||||
//const enabledEras = getCheckboxOptions(this.#commandModeErasDropdown);
|
||||
//Object.keys(enabledEras).forEach((key: string) => {if (enabledEras[key]) eras.push(key)});
|
||||
//getApp().getServerManager().setCommandModeOptions(restrictSpawnsCheckbox.checked, restrictToCoalitionCheckbox.checked, {blue: parseInt(blueSpawnPointsInput.value), red: parseInt(redSpawnPointsInput.value)}, eras, parseInt(setupTimeInput.value) * 60);
|
||||
}
|
||||
|
||||
#setcommandModeOptions(commandModeOptions: CommandModeOptions) {
|
||||
/* Refresh all the data if we have exited the NONE state */
|
||||
var requestRefresh = false;
|
||||
@@ -324,10 +260,4 @@ export class MissionManager {
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
#toggleSpawnRestrictions(restrictionsEnabled: boolean) {
|
||||
//this.#commandModeDialog.querySelectorAll("input, label, .ol-select").forEach( el => {
|
||||
// if (!el.closest("#restrict-spawns")) el.toggleAttribute("disabled", !restrictionsEnabled);
|
||||
//});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,9 @@ import { WeaponsManager } from "./weapon/weaponsmanager";
|
||||
import { ServerManager } from "./server/servermanager";
|
||||
import { AudioManager } from "./audio/audiomanager";
|
||||
|
||||
import { DEFAULT_CONTEXT, NO_SUBSTATE, OlympusEvent, OlympusState, OlympusSubState } from "./constants/constants";
|
||||
import { aircraftDatabase } from "./unit/databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "./unit/databases/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "./unit/databases/groundunitdatabase";
|
||||
import { navyUnitDatabase } from "./unit/databases/navyunitdatabase";
|
||||
import { Coalition, Context } from "./types/types";
|
||||
import { Unit } from "./unit/unit";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState } from "./constants/constants";
|
||||
import { AppStateChangedEvent, ConfigLoadedEvent, SelectedUnitsChangedEvent } from "./events";
|
||||
import { OlympusConfig } from "./interfaces";
|
||||
|
||||
export var VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
|
||||
export var IP = window.location.toString();
|
||||
@@ -36,15 +31,10 @@ export var connectedToServer = true; // TODO Temporary
|
||||
export class OlympusApp {
|
||||
/* Global data */
|
||||
#latestVersion: string | undefined = undefined;
|
||||
#config: any = {};
|
||||
#config: OlympusConfig | null = null;
|
||||
#state: OlympusState = OlympusState.NOT_INITIALIZED;
|
||||
#subState: OlympusSubState = NO_SUBSTATE;
|
||||
|
||||
#events = {
|
||||
[OlympusEvent.STATE_CHANGED]: [] as ((state: OlympusState, subState: OlympusSubState) => void)[],
|
||||
[OlympusEvent.UNITS_SELECTED]: [] as ((units: Unit[]) => void)[],
|
||||
};
|
||||
|
||||
/* Main leaflet map, extended by custom methods */
|
||||
#map: Map | null = null;
|
||||
|
||||
@@ -57,9 +47,6 @@ export class OlympusApp {
|
||||
#audioManager: AudioManager | null = null;
|
||||
//#pluginsManager: // TODO
|
||||
|
||||
/* Current context */
|
||||
#context: Context = DEFAULT_CONTEXT;
|
||||
|
||||
constructor() {
|
||||
SelectedUnitsChangedEvent.on((selectedUnits) => {
|
||||
if (selectedUnits.length > 0) this.setState(OlympusState.UNIT_CONTROL);
|
||||
@@ -67,10 +54,6 @@ export class OlympusApp {
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentContext() {
|
||||
return this.#context;
|
||||
}
|
||||
|
||||
getMap() {
|
||||
return this.#map as Map;
|
||||
}
|
||||
@@ -105,38 +88,6 @@ export class OlympusApp {
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The aircraft database
|
||||
*/
|
||||
getAircraftDatabase() {
|
||||
return aircraftDatabase;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The helicopter database
|
||||
*/
|
||||
getHelicopterDatabase() {
|
||||
return helicopterDatabase;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The ground unit database
|
||||
*/
|
||||
getGroundUnitDatabase() {
|
||||
return groundUnitDatabase;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The navy unit database
|
||||
*/
|
||||
getNavyUnitDatabase() {
|
||||
return navyUnitDatabase;
|
||||
}
|
||||
|
||||
start() {
|
||||
/* Initialize base functionalitites */
|
||||
this.#map = new Map("map-container");
|
||||
@@ -183,7 +134,7 @@ export class OlympusApp {
|
||||
})
|
||||
.then((res) => {
|
||||
this.#config = res;
|
||||
ConfigLoadedEvent.dispatch(); // TODO actually dispatch the config
|
||||
ConfigLoadedEvent.dispatch(this.#config as OlympusConfig);
|
||||
this.setState(OlympusState.LOGIN);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { Circle, LatLng, Polygon } from "leaflet";
|
||||
import * as turf from "@turf/turf";
|
||||
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 { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
|
||||
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
|
||||
import { DateAndTime, UnitBlueprint } from "../interfaces";
|
||||
import { Converter } from "usng";
|
||||
import { MGRS } from "../types/types";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { featureCollection } from "turf";
|
||||
import { randomUUID } from "crypto";
|
||||
|
||||
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
|
||||
const φ1 = deg2rad(lat1); // φ, λ in radians
|
||||
@@ -61,20 +55,6 @@ export function ConvertDDToDMS(D: number, lng: boolean) {
|
||||
else return zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + '"';
|
||||
}
|
||||
|
||||
export function dataPointMap(container: HTMLElement, data: any) {
|
||||
Object.keys(data).forEach((key) => {
|
||||
const val = "" + data[key]; // Ensure a string
|
||||
container.querySelectorAll(`[data-point="${key}"]`).forEach((el) => {
|
||||
// We could probably have options here
|
||||
if (el instanceof HTMLInputElement) {
|
||||
el.value = val;
|
||||
} else if (el instanceof HTMLElement) {
|
||||
el.innerText = val;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function deg2rad(deg: number) {
|
||||
var pi = Math.PI;
|
||||
return deg * (pi / 180);
|
||||
@@ -85,31 +65,11 @@ export function rad2deg(rad: number) {
|
||||
return rad / (pi / 180);
|
||||
}
|
||||
|
||||
export function generateUUIDv4() {
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
||||
var r = (Math.random() * 16) | 0,
|
||||
v = c == "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
export function keyEventWasInInput(event: KeyboardEvent) {
|
||||
const target = event.target;
|
||||
return target instanceof HTMLElement && ["INPUT", "TEXTAREA"].includes(target.nodeName);
|
||||
}
|
||||
|
||||
export function reciprocalHeading(heading: number): number {
|
||||
return heading > 180 ? heading - 180 : heading + 180;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend numbers to the start of a string
|
||||
*
|
||||
* @param num <number> subject number
|
||||
* @param places <number> places to pad
|
||||
* @param decimal <boolean> whether this is a decimal number or not
|
||||
*
|
||||
* */
|
||||
export const zeroAppend = function (num: number, places: number, decimal: boolean = false, decimalPlaces: number = 2) {
|
||||
var string = decimal ? num.toFixed(decimalPlaces) : String(num);
|
||||
while (string.length < places) {
|
||||
@@ -126,43 +86,6 @@ export const zeroPad = function (num: number, places: number) {
|
||||
return string;
|
||||
};
|
||||
|
||||
export function similarity(s1: string, s2: string) {
|
||||
var longer = s1;
|
||||
var shorter = s2;
|
||||
if (s1.length < s2.length) {
|
||||
longer = s2;
|
||||
shorter = s1;
|
||||
}
|
||||
var longerLength = longer.length;
|
||||
if (longerLength == 0) {
|
||||
return 1.0;
|
||||
}
|
||||
return (longerLength - editDistance(longer, shorter)) / longerLength;
|
||||
}
|
||||
|
||||
export function editDistance(s1: string, s2: string) {
|
||||
s1 = s1.toLowerCase();
|
||||
s2 = s2.toLowerCase();
|
||||
|
||||
var costs = new Array();
|
||||
for (var i = 0; i <= s1.length; i++) {
|
||||
var lastValue = i;
|
||||
for (var j = 0; j <= s2.length; j++) {
|
||||
if (i == 0) costs[j] = j;
|
||||
else {
|
||||
if (j > 0) {
|
||||
var newValue = costs[j - 1];
|
||||
if (s1.charAt(i - 1) != s2.charAt(j - 1)) newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1;
|
||||
costs[j - 1] = lastValue;
|
||||
lastValue = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i > 0) costs[s2.length] = lastValue;
|
||||
}
|
||||
return costs[s2.length];
|
||||
}
|
||||
|
||||
export function latLngToMGRS(lat: number, lng: number, precision: number = 4): MGRS | false {
|
||||
if (precision < 0 || precision > 6) {
|
||||
console.error("latLngToMGRS: precision must be a number >= 0 and <= 6. Given precision: " + precision);
|
||||
@@ -315,7 +238,7 @@ export function randomUnitBlueprint(
|
||||
}
|
||||
) {
|
||||
/* Start from all the unit blueprints in the database */
|
||||
var unitBlueprints = Object.values(unitDatabase.getBlueprints());
|
||||
var unitBlueprints = unitDatabase.getBlueprints();
|
||||
|
||||
/* If a specific type or role is provided, use only the blueprints of that type or role */
|
||||
if (options.type && options.role) {
|
||||
@@ -361,37 +284,6 @@ export function randomUnitBlueprint(
|
||||
return unitBlueprints[index];
|
||||
}
|
||||
|
||||
export function getMarkerCategoryByName(name: string) {
|
||||
if (aircraftDatabase.getByName(name) != null) return "aircraft";
|
||||
else if (helicopterDatabase.getByName(name) != null) return "helicopter";
|
||||
else if (groundUnitDatabase.getByName(name) != null) {
|
||||
var type = groundUnitDatabase.getByName(name)?.type ?? "";
|
||||
if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type)) return "groundunit-sam";
|
||||
else return "groundunit-other";
|
||||
} else if (navyUnitDatabase.getByName(name) != null) return "navyunit";
|
||||
else return "aircraft"; // TODO add other unit types
|
||||
}
|
||||
|
||||
export function getUnitDatabaseByCategory(category: string) {
|
||||
if (category.toLowerCase() == "aircraft") return aircraftDatabase;
|
||||
else if (category.toLowerCase() == "helicopter") return helicopterDatabase;
|
||||
else if (category.toLowerCase().includes("groundunit")) return groundUnitDatabase;
|
||||
else if (category.toLowerCase().includes("navyunit")) return navyUnitDatabase;
|
||||
else return null;
|
||||
}
|
||||
|
||||
export function getUnitCategoryByBlueprint(blueprint: UnitBlueprint) {
|
||||
for (let database of [
|
||||
getApp()?.getAircraftDatabase(),
|
||||
getApp()?.getHelicopterDatabase(),
|
||||
getApp()?.getGroundUnitDatabase(),
|
||||
getApp()?.getNavyUnitDatabase(),
|
||||
]) {
|
||||
if (blueprint.name in database.blueprints) return database.getCategory();
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
export function enumToState(state: number) {
|
||||
if (state < states.length) return states[state];
|
||||
else return states[0];
|
||||
@@ -407,7 +299,7 @@ export function enumToReactionToThreat(reactionToThreat: number) {
|
||||
else return reactionsToThreat[0];
|
||||
}
|
||||
|
||||
export function enumToEmissioNCountermeasure(emissionCountermeasure: number) {
|
||||
export function enumToEmissionCountermeasure(emissionCountermeasure: number) {
|
||||
if (emissionCountermeasure < emissionsCountermeasures.length) return emissionsCountermeasures[emissionCountermeasure];
|
||||
else return emissionsCountermeasures[0];
|
||||
}
|
||||
@@ -440,9 +332,7 @@ export function convertDateAndTimeToDate(dateAndTime: DateAndTime) {
|
||||
const date = dateAndTime.date;
|
||||
const time = dateAndTime.time;
|
||||
|
||||
if (!date) {
|
||||
return new Date();
|
||||
}
|
||||
if (!date) return new Date();
|
||||
|
||||
let year = date.Year;
|
||||
let month = date.Month - 1;
|
||||
@@ -457,6 +347,7 @@ export function convertDateAndTimeToDate(dateAndTime: DateAndTime) {
|
||||
|
||||
export function getGroundElevation(latlng: LatLng, callback: CallableFunction) {
|
||||
/* Get the ground elevation from the server endpoint */
|
||||
/* TODO */
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", `api/elevation/${latlng.lat}/${latlng.lng}`, true);
|
||||
xhr.timeout = 500; // ms
|
||||
@@ -470,49 +361,9 @@ export function getGroundElevation(latlng: LatLng, callback: CallableFunction) {
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
export function getWikipediaEntry(search: string, callback: CallableFunction) {
|
||||
/* Get the ground elevation from the server endpoint */
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(
|
||||
"GET",
|
||||
`https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts|pageimages&exintro&explaintext&generator=search&gsrsearch=intitle:${search}&gsrlimit=1&redirects=1&origin=*`,
|
||||
true
|
||||
);
|
||||
xhr.timeout = 500; // ms
|
||||
xhr.responseType = "json";
|
||||
xhr.onload = () => {
|
||||
var status = xhr?.status;
|
||||
if (status === 200) {
|
||||
callback(xhr.response);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
export function getFunctionArguments(func) {
|
||||
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;
|
||||
var ARGUMENT_NAMES = /([^\s,]+)/g;
|
||||
|
||||
var fnStr = func.toString().replace(STRIP_COMMENTS, "");
|
||||
var result = fnStr.slice(fnStr.indexOf("(") + 1, fnStr.indexOf(")")).match(ARGUMENT_NAMES);
|
||||
if (result === null) result = [];
|
||||
return result;
|
||||
}
|
||||
|
||||
export function filterBlueprintsByLabel(blueprints: UnitBlueprint[], filterString: string) {
|
||||
var filteredBlueprints: UnitBlueprint[] = [];
|
||||
if (blueprints) {
|
||||
blueprints.forEach((blueprint) => {
|
||||
if (blueprint.enabled && (filterString === "" || blueprint.label.toLowerCase().includes(filterString.toLowerCase()))) filteredBlueprints.push(blueprint);
|
||||
});
|
||||
}
|
||||
return filteredBlueprints;
|
||||
}
|
||||
|
||||
export function makeID(length) {
|
||||
let result = "";
|
||||
const characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const charactersLength = characters.length;
|
||||
let counter = 0;
|
||||
while (counter < length) {
|
||||
@@ -558,8 +409,14 @@ export function rand(min, max) {
|
||||
}
|
||||
|
||||
export function getRandomColor(seed) {
|
||||
var h = (seed * Math.PI * 100) % 360 + 1;
|
||||
var h = ((seed * Math.PI * 100) % 360) + 1;
|
||||
var s = 50;
|
||||
var l = 50;
|
||||
return 'hsl(' + h + ',' + s + '%,' + l + '%)';
|
||||
}
|
||||
return "hsl(" + h + "," + s + "%," + l + "%)";
|
||||
}
|
||||
|
||||
export function wait(time) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, time);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,10 +20,6 @@ export class ShortcutKeyboard extends Shortcut {
|
||||
super(config);
|
||||
|
||||
document.addEventListener(config.event, (ev: any) => {
|
||||
if (typeof config.context === "string" && getApp().getCurrentContext() !== config.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev instanceof KeyboardEvent === false || keyEventWasInInput(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { RadioSink } from "../audio/radiosink";
|
||||
import { DEFAULT_CONTEXT } from "../constants/constants";
|
||||
import { ShortcutKeyboardOptions, ShortcutMouseOptions } from "../interfaces";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { ShortcutKeyboard, ShortcutMouse } from "./shortcut";
|
||||
|
||||
const DEFAULT_CONTEXT = "Default context"; // TODO remove context
|
||||
|
||||
export class ShortcutManager {
|
||||
#items: { [key: string]: any } = {};
|
||||
#keysBeingHeld: string[] = [];
|
||||
@@ -29,7 +29,6 @@ export class ShortcutManager {
|
||||
getApp().getServerManager().setPaused(!getApp().getServerManager().getPaused());
|
||||
},
|
||||
code: "Space",
|
||||
context: DEFAULT_CONTEXT,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addKeyboardShortcut("deselectAll", {
|
||||
@@ -37,7 +36,6 @@ export class ShortcutManager {
|
||||
getApp().getUnitsManager().deselectAllUnits();
|
||||
},
|
||||
code: "Escape",
|
||||
context: DEFAULT_CONTEXT,
|
||||
})
|
||||
.addKeyboardShortcut("toggleUnitLabels", {
|
||||
altKey: false,
|
||||
@@ -45,7 +43,6 @@ export class ShortcutManager {
|
||||
getApp().getMap().setOption("showUnitLabels", !getApp().getMap().getOptions().showUnitLabels);
|
||||
},
|
||||
code: "KeyL",
|
||||
context: DEFAULT_CONTEXT,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
})
|
||||
@@ -55,7 +52,6 @@ export class ShortcutManager {
|
||||
getApp().getMap().setOption("showUnitsAcquisitionRings", !getApp().getMap().getOptions().showUnitsAcquisitionRings);
|
||||
},
|
||||
code: "KeyE",
|
||||
context: DEFAULT_CONTEXT,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
})
|
||||
@@ -65,7 +61,6 @@ export class ShortcutManager {
|
||||
getApp().getMap().setOption("showUnitsEngagementRings", !getApp().getMap().getOptions().showUnitsEngagementRings);
|
||||
},
|
||||
code: "KeyQ",
|
||||
context: DEFAULT_CONTEXT,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
})
|
||||
@@ -75,7 +70,6 @@ export class ShortcutManager {
|
||||
getApp().getMap().setOption("hideUnitsShortRangeRings", !getApp().getMap().getOptions().hideUnitsShortRangeRings);
|
||||
},
|
||||
code: "KeyR",
|
||||
context: DEFAULT_CONTEXT,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
})
|
||||
@@ -85,7 +79,6 @@ export class ShortcutManager {
|
||||
getApp().getMap().setOption("showUnitTargets", !getApp().getMap().getOptions().showUnitTargets);
|
||||
},
|
||||
code: "KeyF",
|
||||
context: DEFAULT_CONTEXT,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
})
|
||||
@@ -95,7 +88,6 @@ export class ShortcutManager {
|
||||
getApp().getMap().setOption("hideGroupMembers", !getApp().getMap().getOptions().hideGroupMembers);
|
||||
},
|
||||
code: "KeyG",
|
||||
context: DEFAULT_CONTEXT,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
})
|
||||
@@ -105,7 +97,6 @@ export class ShortcutManager {
|
||||
//getApp().getMap().increaseCameraZoom();
|
||||
},
|
||||
code: "Equal",
|
||||
context: DEFAULT_CONTEXT,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
})
|
||||
@@ -115,7 +106,6 @@ export class ShortcutManager {
|
||||
//getApp().getMap().decreaseCameraZoom();
|
||||
},
|
||||
code: "Minus",
|
||||
context: DEFAULT_CONTEXT,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
});
|
||||
@@ -125,26 +115,18 @@ export class ShortcutManager {
|
||||
this.addKeyboardShortcut(`PTT${idx}Active`, {
|
||||
altKey: false,
|
||||
callback: () => {
|
||||
getApp()
|
||||
.getAudioManager()
|
||||
.getSinks()
|
||||
[idx]?.setPtt(true);
|
||||
getApp().getAudioManager().getSinks()[idx]?.setPtt(true);
|
||||
},
|
||||
code: key,
|
||||
context: DEFAULT_CONTEXT,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
event: "keydown",
|
||||
}).addKeyboardShortcut(`PTT${idx}Active`, {
|
||||
altKey: false,
|
||||
callback: () => {
|
||||
getApp()
|
||||
.getAudioManager()
|
||||
.getSinks()
|
||||
[idx]?.setPtt(false);
|
||||
getApp().getAudioManager().getSinks()[idx]?.setPtt(false);
|
||||
},
|
||||
code: key,
|
||||
context: DEFAULT_CONTEXT,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
event: "keyup",
|
||||
@@ -159,7 +141,6 @@ export class ShortcutManager {
|
||||
//getApp().getMap().handleMapPanning(ev);
|
||||
},
|
||||
code: code,
|
||||
context: DEFAULT_CONTEXT,
|
||||
ctrlKey: false,
|
||||
event: "keydown",
|
||||
});
|
||||
@@ -169,7 +150,6 @@ export class ShortcutManager {
|
||||
//getApp().getMap().handleMapPanning(ev);
|
||||
},
|
||||
code: code,
|
||||
context: DEFAULT_CONTEXT,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { createContext } from "react";
|
||||
import { MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, OlympusSubState } from "./constants/constants";
|
||||
import { Unit } from "./unit/unit";
|
||||
import { AudioSource } from "./audio/audiosource";
|
||||
import { AudioSink } from "./audio/audiosink";
|
||||
import { ServerStatus } from "./interfaces";
|
||||
import { ContextActionSet } from "./unit/contextactionset";
|
||||
import { ContextAction } from "./unit/contextaction";
|
||||
|
||||
export const StateContext = createContext({
|
||||
appState: OlympusState.NOT_INITIALIZED as OlympusState,
|
||||
appSubState: NO_SUBSTATE as OlympusSubState,
|
||||
mapHiddenTypes: MAP_HIDDEN_TYPES_DEFAULTS,
|
||||
mapOptions: MAP_OPTIONS_DEFAULTS,
|
||||
mapSources: [] as string[],
|
||||
activeMapSource: "",
|
||||
selectedUnits: [] as Unit[],
|
||||
audioSources: [] as AudioSource[],
|
||||
audioSinks: [] as AudioSink[],
|
||||
audioManagerState: false,
|
||||
serverStatus: {} as ServerStatus,
|
||||
contextActionSet: null as ContextActionSet | null,
|
||||
contextAction: null as ContextAction | null
|
||||
});
|
||||
|
||||
export const StateProvider = StateContext.Provider;
|
||||
export const StateConsumer = StateContext.Consumer;
|
||||
@@ -53,4 +53,3 @@ export type MGRS = {
|
||||
|
||||
export type Coalition = "blue" | "neutral" | "red" | "all";
|
||||
|
||||
export type Context = string;
|
||||
|
||||
@@ -41,7 +41,7 @@ export function OlAccordion(props: { title: string; open: boolean, onClick: () =
|
||||
>
|
||||
<span className="font-normal">{props.title}</span>
|
||||
<svg
|
||||
data-open={open}
|
||||
data-open={props.open}
|
||||
className={`
|
||||
h-3 w-3 shrink-0 -rotate-180 transition-transform
|
||||
dark:text-olympus-50
|
||||
|
||||
@@ -7,7 +7,7 @@ export function ErrorCallout(props: { title?: string; description?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex w-fit w-full flex-row gap-2 rounded-md border-[1px] border-red-800
|
||||
flex w-full flex-row gap-2 rounded-md border-[1px] border-red-800
|
||||
bg-gray-300 p-4 text-red-700
|
||||
dark:bg-gray-800 dark:text-red-500
|
||||
`}
|
||||
@@ -19,13 +19,9 @@ export function ErrorCallout(props: { title?: string; description?: string }) {
|
||||
`}
|
||||
>
|
||||
{props.title}
|
||||
<div
|
||||
className={`
|
||||
flex whitespace-pre-line text-xs font-medium text-red-500
|
||||
`}
|
||||
>
|
||||
{props.description}
|
||||
</div>
|
||||
<div className={`
|
||||
flex whitespace-pre-line text-xs font-medium text-red-500
|
||||
`}>{props.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -36,7 +32,7 @@ export function InfoCallout(props: { title?: string; description?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex w-fit w-full flex-row gap-2 rounded-md border-[1px] border-blue-800
|
||||
flex w-full flex-row gap-2 rounded-md border-[1px] border-blue-800
|
||||
bg-gray-300 p-4 text-blue-400
|
||||
dark:bg-gray-800 dark:text-blue-400
|
||||
`}
|
||||
@@ -48,15 +44,9 @@ export function InfoCallout(props: { title?: string; description?: string }) {
|
||||
`}
|
||||
>
|
||||
{props.title}
|
||||
{props.description && (
|
||||
<div
|
||||
className={`
|
||||
flex whitespace-pre-line text-xs font-medium
|
||||
`}
|
||||
>
|
||||
{props.description}
|
||||
</div>
|
||||
)}
|
||||
{props.description && <div className={`
|
||||
flex whitespace-pre-line text-xs font-medium
|
||||
`}>{props.description}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -67,7 +57,7 @@ export function CommandCallout(props: { coalition?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex w-fit w-full flex-row gap-2 rounded-md border-[1px] border-gray-700
|
||||
flex w-full flex-row gap-2 rounded-md border-[1px] border-gray-700
|
||||
bg-gray-300 p-4 text-gray-400
|
||||
dark:bg-gray-800 dark:text-gray-400
|
||||
`}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { SelectionClearedEvent } from "../../events";
|
||||
export function MapContextMenu(props: {}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [contextActionsSet, setContextActionsSet] = useState(new ContextActionSet());
|
||||
const [activeContextAction, setActiveContextAction] = useState(null as null | ContextAction);
|
||||
const [xPosition, setXPosition] = useState(0);
|
||||
const [yPosition, setYPosition] = useState(0);
|
||||
const [latLng, setLatLng] = useState(null as null | LatLng);
|
||||
@@ -107,9 +106,7 @@ export function MapContextMenu(props: {}) {
|
||||
<>
|
||||
<div
|
||||
ref={contentRef}
|
||||
className={`
|
||||
absolute flex min-w-80 gap-2 rounded-md bg-olympus-600
|
||||
`}
|
||||
className={`absolute flex min-w-80 gap-2 rounded-md bg-olympus-600`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
|
||||
@@ -14,6 +14,7 @@ export function LoginModal(props: {
|
||||
onContinue: (username: string) => void;
|
||||
onBack: () => void;
|
||||
}) {
|
||||
// TODO: add warning if not in secure context and some features are disabled
|
||||
const [password, setPassword] = useState("");
|
||||
const [displayName, setDisplayName] = useState("Game Master");
|
||||
|
||||
|
||||
@@ -6,11 +6,13 @@ import { AppStateChangedEvent } from "../../events";
|
||||
|
||||
export function ControlsPanel(props: {}) {
|
||||
const [controls, setControls] = useState(
|
||||
null as {
|
||||
actions: (string | number | IconDefinition)[];
|
||||
target: IconDefinition;
|
||||
text: string;
|
||||
}[] | null
|
||||
null as
|
||||
| {
|
||||
actions: (string | number | IconDefinition)[];
|
||||
target: IconDefinition;
|
||||
text: string;
|
||||
}[]
|
||||
| null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -20,9 +22,7 @@ export function ControlsPanel(props: {}) {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on(() => {
|
||||
setControls(getApp().getMap().getCurrentControls());
|
||||
});
|
||||
AppStateChangedEvent.on(() => setControls(getApp().getMap().getCurrentControls()));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -53,9 +53,14 @@ export function ControlsPanel(props: {}) {
|
||||
return (
|
||||
<div key={idx} className="flex gap-1">
|
||||
<div>
|
||||
{typeof action === "string" || typeof action === "number" ? action : <FontAwesomeIcon icon={action} className={`
|
||||
my-auto ml-auto
|
||||
`} />}
|
||||
{typeof action === "string" || typeof action === "number" ? (
|
||||
action
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={action}
|
||||
className={`my-auto ml-auto`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "string" && <div>+</div>}
|
||||
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "number" && <div>x</div>}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { FaQuestionCircle, FaTrash } from "react-icons/fa";
|
||||
import { FaTrash } from "react-icons/fa";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { faDrawPolygon } from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -12,11 +12,13 @@ import { OlCheckbox } from "../components/olcheckbox";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { OlRangeSlider } from "../components/olrangeslider";
|
||||
import { CoalitionCircle } from "../../map/coalitionarea/coalitioncircle";
|
||||
import { DrawSubState, NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { StateConsumer } from "../../statecontext";
|
||||
import { CoalitionAreaSelectedEvent } from "../../events";
|
||||
import { DrawSubState, NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, CoalitionAreaSelectedEvent } from "../../events";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
|
||||
export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
|
||||
const [activeCoalitionArea, setActiveCoalitionArea] = useState(null as null | CoalitionPolygon | CoalitionCircle);
|
||||
const [areaCoalition, setAreaCoalition] = useState("blue" as Coalition);
|
||||
const [IADSDensity, setIADSDensity] = useState(50);
|
||||
@@ -27,6 +29,29 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const [erasSelection, setErasSelection] = useState({});
|
||||
const [rangesSelection, setRangesSelection] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
setAppState(state);
|
||||
setAppSubState(subState);
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* Get all the unique types and eras for groundunits */
|
||||
/* TODO move in effect */
|
||||
const blueprints = getApp()?.getUnitsManager().getDatabase().getBlueprints();
|
||||
let types: string[] = [];
|
||||
let eras: string[] = [];
|
||||
if (blueprints) {
|
||||
types = blueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit")
|
||||
.map((blueprint) => blueprint.type)
|
||||
.filter((type) => type !== undefined);
|
||||
eras = blueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit")
|
||||
.map((blueprint) => blueprint.era)
|
||||
.filter((era) => era !== undefined);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (getApp()) {
|
||||
// TODO
|
||||
@@ -49,258 +74,248 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StateConsumer>
|
||||
{(appState) => (
|
||||
<Menu
|
||||
open={props.open}
|
||||
title="Draw"
|
||||
onClose={props.onClose}
|
||||
canBeHidden={true}
|
||||
showBackButton={activeCoalitionArea !== null}
|
||||
onBack={() => {
|
||||
getApp().setState(OlympusState.DRAW, DrawSubState.NO_SUBSTATE);
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{appState.appState === OlympusState.DRAW && appState.appSubState !== DrawSubState.EDIT && (
|
||||
<div className="flex flex-col gap-2 p-6 text-sm text-gray-400">
|
||||
<OlStateButton
|
||||
className="!w-full"
|
||||
icon={faDrawPolygon}
|
||||
tooltip={"Add a new polygon"}
|
||||
checked={appState.appSubState === DrawSubState.DRAW_POLYGON}
|
||||
onClick={() => {
|
||||
if (appState.appSubState === DrawSubState.DRAW_POLYGON) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_POLYGON);
|
||||
}}
|
||||
>
|
||||
<div className="text-sm">Add polygon</div>
|
||||
</OlStateButton>
|
||||
<OlStateButton
|
||||
className="!w-full"
|
||||
icon={faCircle}
|
||||
tooltip={"Add a new circle"}
|
||||
checked={appState.appSubState === DrawSubState.DRAW_CIRCLE}
|
||||
onClick={() => {
|
||||
if (appState.appSubState === DrawSubState.DRAW_CIRCLE) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_CIRCLE);
|
||||
}}
|
||||
>
|
||||
<div className="text-sm">Add circle</div>
|
||||
</OlStateButton>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<div>
|
||||
{activeCoalitionArea !== null && appState.appSubState === DrawSubState.EDIT && (
|
||||
<div className={`flex flex-col gap-4 py-4`}>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col content-center justify-start gap-2 px-6
|
||||
text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto flex justify-between text-md">
|
||||
Area label
|
||||
<div
|
||||
className="rounded-md bg-red-800 p-2"
|
||||
onClick={() => {
|
||||
getApp().getMap().deleteCoalitionArea(activeCoalitionArea);
|
||||
setActiveCoalitionArea(null);
|
||||
}}
|
||||
>
|
||||
<FaTrash className={`text-gray-50`}></FaTrash>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className={`
|
||||
block w-full flex-grow rounded-lg border border-gray-300
|
||||
bg-gray-50 p-2.5 text-sm text-gray-900
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white
|
||||
dark:placeholder-gray-400 dark:focus:border-blue-500
|
||||
dark:focus:ring-blue-500
|
||||
focus:border-blue-500 focus:ring-blue-500
|
||||
`}
|
||||
placeholder={activeCoalitionArea.getLabelText()}
|
||||
onInput={(ev) => activeCoalitionArea.setLabelText(ev.currentTarget.value)}
|
||||
></input>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex content-center justify-start gap-4 px-6 text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto text-md">Coalition: </div>
|
||||
<OlCoalitionToggle
|
||||
coalition={areaCoalition}
|
||||
onClick={() => {
|
||||
let newCoalition = "";
|
||||
if (areaCoalition === "blue") newCoalition = "neutral";
|
||||
else if (areaCoalition === "neutral") newCoalition = "red";
|
||||
else if (areaCoalition === "red") newCoalition = "blue";
|
||||
setAreaCoalition(newCoalition as Coalition);
|
||||
activeCoalitionArea.setCoalition(newCoalition as Coalition);
|
||||
}}
|
||||
></OlCoalitionToggle>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-3 border-l-4 border-l-olympus-100
|
||||
bg-olympus-600 p-5
|
||||
`}
|
||||
>
|
||||
<div className={`
|
||||
border-b-2 border-b-olympus-100 pb-4 text-gray-300
|
||||
`}>Automatic IADS generation</div>
|
||||
<OlDropdown className="" label="Units types">
|
||||
{getApp()
|
||||
.getGroundUnitDatabase()
|
||||
.getTypes()
|
||||
.map((type, idx) => {
|
||||
if (!(type in typesSelection)) {
|
||||
typesSelection[type] = true;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem key={idx} className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={typesSelection[type]}
|
||||
onChange={(ev) => {
|
||||
typesSelection[type] = ev.currentTarget.checked;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{type}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units eras">
|
||||
{getApp()
|
||||
.getGroundUnitDatabase()
|
||||
.getEras()
|
||||
.map((era) => {
|
||||
if (!(era in erasSelection)) {
|
||||
erasSelection[era] = true;
|
||||
setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={erasSelection[era]}
|
||||
onChange={(ev) => {
|
||||
erasSelection[era] = ev.currentTarget.checked;
|
||||
setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{era}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units ranges">
|
||||
{["Short range", "Medium range", "Long range"].map((range) => {
|
||||
if (!(range in rangesSelection)) {
|
||||
rangesSelection[range] = true;
|
||||
setRangesSelection(JSON.parse(JSON.stringify(rangesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={rangesSelection[range]}
|
||||
onChange={(ev) => {
|
||||
rangesSelection[range] = ev.currentTarget.checked;
|
||||
setErasSelection(JSON.parse(JSON.stringify(rangesSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{range}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-gray-100">IADS Density</div>
|
||||
<div
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
{IADSDensity}%
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={IADSDensity}
|
||||
onChange={(ev) => {
|
||||
setIADSDensity(Number(ev.currentTarget.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-gray-100">IADS Distribution</div>
|
||||
<div
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
{IADSDistribution}%
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={IADSDistribution}
|
||||
onChange={(ev) => {
|
||||
setIADSDistribution(Number(ev.target.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div className="flex content-center gap-4 text-gray-200">
|
||||
<OlCheckbox
|
||||
checked={forceCoalitionAppropriateUnits}
|
||||
onChange={() => {
|
||||
setForceCoalitionApproriateUnits(!forceCoalitionAppropriateUnits);
|
||||
}}
|
||||
/>
|
||||
Force coalition appropriate units
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
mb-2 me-2 rounded-lg bg-blue-700 px-5 py-2.5 text-sm
|
||||
font-medium text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700
|
||||
dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
onClick={() =>
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.createIADS(
|
||||
activeCoalitionArea,
|
||||
typesSelection,
|
||||
erasSelection,
|
||||
rangesSelection,
|
||||
IADSDensity,
|
||||
IADSDistribution,
|
||||
forceCoalitionAppropriateUnits
|
||||
)
|
||||
}
|
||||
>
|
||||
Generate IADS
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Menu
|
||||
open={props.open}
|
||||
title="Draw"
|
||||
onClose={props.onClose}
|
||||
canBeHidden={true}
|
||||
showBackButton={activeCoalitionArea !== null}
|
||||
onBack={() => {
|
||||
getApp().setState(OlympusState.DRAW, DrawSubState.NO_SUBSTATE);
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{appState === OlympusState.DRAW && appSubState !== DrawSubState.EDIT && (
|
||||
<div className="flex flex-col gap-2 p-6 text-sm text-gray-400">
|
||||
<OlStateButton
|
||||
className="!w-full"
|
||||
icon={faDrawPolygon}
|
||||
tooltip={"Add a new polygon"}
|
||||
checked={appSubState === DrawSubState.DRAW_POLYGON}
|
||||
onClick={() => {
|
||||
if (appSubState === DrawSubState.DRAW_POLYGON) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_POLYGON);
|
||||
}}
|
||||
>
|
||||
<div className="text-sm">Add polygon</div>
|
||||
</OlStateButton>
|
||||
<OlStateButton
|
||||
className="!w-full"
|
||||
icon={faCircle}
|
||||
tooltip={"Add a new circle"}
|
||||
checked={appSubState === DrawSubState.DRAW_CIRCLE}
|
||||
onClick={() => {
|
||||
if (appSubState === DrawSubState.DRAW_CIRCLE) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_CIRCLE);
|
||||
}}
|
||||
>
|
||||
<div className="text-sm">Add circle</div>
|
||||
</OlStateButton>
|
||||
</div>
|
||||
</Menu>
|
||||
)}
|
||||
</StateConsumer>
|
||||
)}
|
||||
</>
|
||||
<div>
|
||||
{activeCoalitionArea !== null && appSubState === DrawSubState.EDIT && (
|
||||
<div className={`flex flex-col gap-4 py-4`}>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col content-center justify-start gap-2 px-6
|
||||
text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto flex justify-between text-md">
|
||||
Area label
|
||||
<div
|
||||
className="rounded-md bg-red-800 p-2"
|
||||
onClick={() => {
|
||||
getApp().getMap().deleteCoalitionArea(activeCoalitionArea);
|
||||
setActiveCoalitionArea(null);
|
||||
}}
|
||||
>
|
||||
<FaTrash className={`text-gray-50`}></FaTrash>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className={`
|
||||
block w-full flex-grow rounded-lg border border-gray-300
|
||||
bg-gray-50 p-2.5 text-sm text-gray-900
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white
|
||||
dark:placeholder-gray-400 dark:focus:border-blue-500
|
||||
dark:focus:ring-blue-500
|
||||
focus:border-blue-500 focus:ring-blue-500
|
||||
`}
|
||||
placeholder={activeCoalitionArea.getLabelText()}
|
||||
onInput={(ev) => activeCoalitionArea.setLabelText(ev.currentTarget.value)}
|
||||
></input>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex content-center justify-start gap-4 px-6 text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto text-md">Coalition: </div>
|
||||
<OlCoalitionToggle
|
||||
coalition={areaCoalition}
|
||||
onClick={() => {
|
||||
let newCoalition = "";
|
||||
if (areaCoalition === "blue") newCoalition = "neutral";
|
||||
else if (areaCoalition === "neutral") newCoalition = "red";
|
||||
else if (areaCoalition === "red") newCoalition = "blue";
|
||||
setAreaCoalition(newCoalition as Coalition);
|
||||
activeCoalitionArea.setCoalition(newCoalition as Coalition);
|
||||
}}
|
||||
></OlCoalitionToggle>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-3 border-l-4 border-l-olympus-100
|
||||
bg-olympus-600 p-5
|
||||
`}
|
||||
>
|
||||
<div className={`
|
||||
border-b-2 border-b-olympus-100 pb-4 text-gray-300
|
||||
`}>Automatic IADS generation</div>
|
||||
<OlDropdown className="" label="Units types">
|
||||
{types.map((type, idx) => {
|
||||
if (!(type in typesSelection)) {
|
||||
typesSelection[type] = true;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem key={idx} className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={typesSelection[type]}
|
||||
onChange={(ev) => {
|
||||
typesSelection[type] = ev.currentTarget.checked;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{type}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units eras">
|
||||
{eras.map((era) => {
|
||||
if (!(era in erasSelection)) {
|
||||
erasSelection[era] = true;
|
||||
setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={erasSelection[era]}
|
||||
onChange={(ev) => {
|
||||
erasSelection[era] = ev.currentTarget.checked;
|
||||
setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{era}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units ranges">
|
||||
{["Short range", "Medium range", "Long range"].map((range) => {
|
||||
if (!(range in rangesSelection)) {
|
||||
rangesSelection[range] = true;
|
||||
setRangesSelection(JSON.parse(JSON.stringify(rangesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={rangesSelection[range]}
|
||||
onChange={(ev) => {
|
||||
rangesSelection[range] = ev.currentTarget.checked;
|
||||
setErasSelection(JSON.parse(JSON.stringify(rangesSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{range}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-gray-100">IADS Density</div>
|
||||
<div
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
{IADSDensity}%
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={IADSDensity}
|
||||
onChange={(ev) => {
|
||||
setIADSDensity(Number(ev.currentTarget.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-gray-100">IADS Distribution</div>
|
||||
<div
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
{IADSDistribution}%
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={IADSDistribution}
|
||||
onChange={(ev) => {
|
||||
setIADSDistribution(Number(ev.target.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div className="flex content-center gap-4 text-gray-200">
|
||||
<OlCheckbox
|
||||
checked={forceCoalitionAppropriateUnits}
|
||||
onChange={() => {
|
||||
setForceCoalitionApproriateUnits(!forceCoalitionAppropriateUnits);
|
||||
}}
|
||||
/>
|
||||
Force coalition appropriate units
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
mb-2 me-2 rounded-lg bg-blue-700 px-5 py-2.5 text-sm
|
||||
font-medium text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700
|
||||
dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
onClick={() =>
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.createIADS(
|
||||
activeCoalitionArea,
|
||||
typesSelection,
|
||||
erasSelection,
|
||||
rangesSelection,
|
||||
IADSDensity,
|
||||
IADSDistribution,
|
||||
forceCoalitionAppropriateUnits
|
||||
)
|
||||
}
|
||||
>
|
||||
Generate IADS
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, { useEffect, useRef, useState } from "react";
|
||||
import { OlRoundStateButton, OlStateButton, OlLockStateButton } from "../components/olstatebutton";
|
||||
import { faSkull, faCamera, faFlag, faLink, faUnlink, faBars, faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { StateConsumer } from "../../statecontext";
|
||||
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
import { getApp, IP, connectedToServer } from "../../olympusapp";
|
||||
@@ -18,12 +17,29 @@ import {
|
||||
olButtonsVisibilityOlympus,
|
||||
} from "../components/olicons";
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
import { ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent } from "../../events";
|
||||
import { MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { OlympusConfig } from "../../interfaces";
|
||||
|
||||
export function Header() {
|
||||
const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
const [mapSource, setMapSource] = useState("");
|
||||
const [mapSources, setMapSources] = useState([] as string[]);
|
||||
const [scrolledLeft, setScrolledLeft] = useState(true);
|
||||
const [scrolledRight, setScrolledRight] = useState(false);
|
||||
const [audioEnabled, setAudioEnabled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
HiddenTypesChangedEvent.on((hiddenTypes) => setMapHiddenTypes({ ...hiddenTypes }));
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
MapSourceChangedEvent.on((source) => setMapSource(source));
|
||||
ConfigLoadedEvent.on((config: OlympusConfig) => {
|
||||
var sources = Object.keys(config.mapMirrors).concat(Object.keys(config.mapLayers));
|
||||
setMapSources(sources);
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* Initialize the "scroll" position of the element */
|
||||
var scrollRef = useRef(null);
|
||||
useEffect(() => {
|
||||
@@ -42,8 +58,6 @@ export function Header() {
|
||||
}
|
||||
|
||||
return (
|
||||
<StateConsumer>
|
||||
{(appState) => (
|
||||
<nav
|
||||
className={`
|
||||
z-10 flex w-full gap-4 border-gray-200 bg-gray-300 px-3
|
||||
@@ -107,14 +121,12 @@ export function Header() {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-row items-center justify-start gap-1
|
||||
`}
|
||||
className={`flex h-fit flex-row items-center justify-start gap-1`}
|
||||
>
|
||||
<OlLockStateButton
|
||||
checked={!appState.mapOptions.protectDCSUnits}
|
||||
checked={!mapOptions.protectDCSUnits}
|
||||
onClick={() => {
|
||||
getApp().getMap().setOption("protectDCSUnits", !appState.mapOptions.protectDCSUnits);
|
||||
getApp().getMap().setOption("protectDCSUnits", !mapOptions.protectDCSUnits);
|
||||
}}
|
||||
tooltip="Lock/unlock protected units (from scripted mission)"
|
||||
/>
|
||||
@@ -130,9 +142,7 @@ export function Header() {
|
||||
</div>
|
||||
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-row items-center justify-start gap-1
|
||||
`}
|
||||
className={`flex h-fit flex-row items-center justify-start gap-1`}
|
||||
>
|
||||
{Object.entries({
|
||||
human: olButtonsVisibilityHuman,
|
||||
@@ -143,9 +153,9 @@ export function Header() {
|
||||
<OlRoundStateButton
|
||||
key={entry[0]}
|
||||
onClick={() => {
|
||||
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
|
||||
getApp().getMap().setHiddenType(entry[0], !mapHiddenTypes[entry[0]]);
|
||||
}}
|
||||
checked={!appState.mapHiddenTypes[entry[0]]}
|
||||
checked={!mapHiddenTypes[entry[0]]}
|
||||
icon={entry[1]}
|
||||
tooltip={"Hide/show " + entry[0] + " units"}
|
||||
/>
|
||||
@@ -154,27 +164,25 @@ export function Header() {
|
||||
</div>
|
||||
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-row items-center justify-start gap-1
|
||||
`}
|
||||
className={`flex h-fit flex-row items-center justify-start gap-1`}
|
||||
>
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setHiddenType("blue", !appState.mapHiddenTypes["blue"])}
|
||||
checked={!appState.mapHiddenTypes["blue"]}
|
||||
onClick={() => getApp().getMap().setHiddenType("blue", !mapHiddenTypes["blue"])}
|
||||
checked={!mapHiddenTypes["blue"]}
|
||||
icon={faFlag}
|
||||
className={"!text-blue-500"}
|
||||
tooltip={"Hide/show blue units"}
|
||||
/>
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setHiddenType("red", !appState.mapHiddenTypes["red"])}
|
||||
checked={!appState.mapHiddenTypes["red"]}
|
||||
onClick={() => getApp().getMap().setHiddenType("red", !mapHiddenTypes["red"])}
|
||||
checked={!mapHiddenTypes["red"]}
|
||||
icon={faFlag}
|
||||
className={"!text-red-500"}
|
||||
tooltip={"Hide/show red units"}
|
||||
/>
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setHiddenType("neutral", !appState.mapHiddenTypes["neutral"])}
|
||||
checked={!appState.mapHiddenTypes["neutral"]}
|
||||
onClick={() => getApp().getMap().setHiddenType("neutral", !mapHiddenTypes["neutral"])}
|
||||
checked={!mapHiddenTypes["neutral"]}
|
||||
icon={faFlag}
|
||||
className={"!text-gray-500"}
|
||||
tooltip={"Hide/show neutral units"}
|
||||
@@ -182,9 +190,7 @@ export function Header() {
|
||||
</div>
|
||||
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-row items-center justify-start gap-1
|
||||
`}
|
||||
className={`flex h-fit flex-row items-center justify-start gap-1`}
|
||||
>
|
||||
{Object.entries({
|
||||
aircraft: olButtonsVisibilityAircraft,
|
||||
@@ -199,9 +205,9 @@ export function Header() {
|
||||
<OlRoundStateButton
|
||||
key={entry[0]}
|
||||
onClick={() => {
|
||||
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
|
||||
getApp().getMap().setHiddenType(entry[0], !mapHiddenTypes[entry[0]]);
|
||||
}}
|
||||
checked={!appState.mapHiddenTypes[entry[0]]}
|
||||
checked={!mapHiddenTypes[entry[0]]}
|
||||
icon={entry[1]}
|
||||
tooltip={"Hide/show " + entry[0] + " units"}
|
||||
/>
|
||||
@@ -211,8 +217,8 @@ export function Header() {
|
||||
|
||||
<OlLabelToggle toggled={false} leftLabel={"Live"} rightLabel={"Map"} onClick={() => {}}></OlLabelToggle>
|
||||
<OlStateButton checked={false} icon={faCamera} onClick={() => {}} tooltip="Activate/deactivate camera plugin" />
|
||||
<OlDropdown label={appState.activeMapSource} className="w-60">
|
||||
{appState.mapSources.map((source) => {
|
||||
<OlDropdown label={mapSource} className="w-60">
|
||||
{mapSources.map((source) => {
|
||||
return (
|
||||
<OlDropdownItem key={source} onClick={() => getApp().getMap().setLayerName(source)}>
|
||||
<div className="truncate">{source}</div>
|
||||
@@ -230,7 +236,5 @@ export function Header() {
|
||||
/>
|
||||
)}
|
||||
</nav>
|
||||
)}
|
||||
</StateConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,9 +85,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span
|
||||
className={`
|
||||
my-auto h-full min-w-10 text-nowrap p-2 text-center
|
||||
`}
|
||||
className={`my-auto h-full min-w-10 text-nowrap p-2 text-center`}
|
||||
>
|
||||
BP
|
||||
</span>
|
||||
@@ -122,9 +120,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span
|
||||
className={`
|
||||
my-auto h-full min-w-10 text-nowrap p-2 text-center
|
||||
`}
|
||||
className={`my-auto h-full min-w-10 text-nowrap p-2 text-center`}
|
||||
>
|
||||
IP
|
||||
</span>
|
||||
@@ -159,9 +155,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span
|
||||
className={`
|
||||
my-auto h-full min-w-10 text-nowrap p-3 text-center
|
||||
`}
|
||||
className={`my-auto h-full min-w-10 text-nowrap p-3 text-center`}
|
||||
>
|
||||
<FaBullseye />
|
||||
</span>
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import React, { useState, useEffect, useContext } from "react";
|
||||
import { zeroAppend } from "../../other/utils";
|
||||
import { DateAndTime } from "../../interfaces";
|
||||
import { DateAndTime, ServerStatus } from "../../interfaces";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { FaChevronDown, FaChevronUp } from "react-icons/fa6";
|
||||
import { StateContext } from "../../statecontext";
|
||||
import { MapOptionsChangedEvent, ServerStatusUpdatedEvent } from "../../events";
|
||||
import { MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
|
||||
export function MiniMapPanel(props: {}) {
|
||||
const appState = useContext(StateContext)
|
||||
|
||||
const [serverStatus, setServerStatus] = useState({} as ServerStatus);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
const [showMissionTime, setShowMissionTime] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
ServerStatusUpdatedEvent.on((status) => setServerStatus(status));
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
}, []);
|
||||
|
||||
// A bit of a hack to set the rounded borders to the minimap
|
||||
useEffect(() => {
|
||||
let miniMap = document.querySelector(".leaflet-control-minimap");
|
||||
@@ -24,45 +30,45 @@ export function MiniMapPanel(props: {}) {
|
||||
let seconds = 0;
|
||||
|
||||
if (showMissionTime) {
|
||||
hours = appState.serverStatus.missionTime.h;
|
||||
minutes = appState.serverStatus.missionTime.m;
|
||||
seconds = appState.serverStatus.missionTime.s;
|
||||
hours = serverStatus.missionTime.h;
|
||||
minutes = serverStatus.missionTime.m;
|
||||
seconds = serverStatus.missionTime.s;
|
||||
} else {
|
||||
hours = Math.floor(appState.serverStatus.elapsedTime / 3600);
|
||||
minutes = Math.floor(appState.serverStatus.elapsedTime / 60) % 60;
|
||||
seconds = Math.round(appState.serverStatus.elapsedTime) % 60;
|
||||
hours = Math.floor(serverStatus.elapsedTime / 3600);
|
||||
minutes = Math.floor(serverStatus.elapsedTime / 60) % 60;
|
||||
seconds = Math.round(serverStatus.elapsedTime) % 60;
|
||||
}
|
||||
|
||||
let timeString = `${zeroAppend(hours, 2)}:${zeroAppend(minutes, 2)}:${zeroAppend(seconds, 2)}`;
|
||||
|
||||
// Choose frame rate string color
|
||||
let frameRateColor = "#8BFF63";
|
||||
if (appState.serverStatus.frameRate < 30) frameRateColor = "#F05252";
|
||||
else if (appState.serverStatus.frameRate >= 30 && appState.serverStatus.frameRate < 60) frameRateColor = "#FF9900";
|
||||
if (serverStatus.frameRate < 30) frameRateColor = "#F05252";
|
||||
else if (serverStatus.frameRate >= 30 && serverStatus.frameRate < 60) frameRateColor = "#FF9900";
|
||||
|
||||
// Choose load string color
|
||||
let loadColor = "#8BFF63";
|
||||
if (appState.serverStatus.load > 1000) loadColor = "#F05252";
|
||||
else if (appState.serverStatus.load >= 100 && appState.serverStatus.load < 1000) loadColor = "#FF9900";
|
||||
if (serverStatus.load > 1000) loadColor = "#F05252";
|
||||
else if (serverStatus.load >= 100 && serverStatus.load < 1000) loadColor = "#FF9900";
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => setShowMissionTime(!showMissionTime)}
|
||||
className={`
|
||||
absolute right-[10px]
|
||||
${appState.mapOptions.showMinimap ? `top-[232px]` : `top-[70px]`}
|
||||
${mapOptions.showMinimap ? `top-[232px]` : `top-[70px]`}
|
||||
flex w-[288px] items-center justify-between
|
||||
${appState.mapOptions.showMinimap ? `rounded-b-lg` : `rounded-lg`}
|
||||
${mapOptions.showMinimap ? `rounded-b-lg` : `rounded-lg`}
|
||||
bg-gray-200 p-3 text-sm backdrop-blur-lg backdrop-grayscale
|
||||
dark:bg-olympus-800/90 dark:text-gray-200
|
||||
`}
|
||||
>
|
||||
{!appState.serverStatus.connected ? (
|
||||
{!serverStatus.connected ? (
|
||||
<div className={`flex animate-pulse items-center gap-2 font-semibold`}>
|
||||
<div className={`relative h-4 w-4 rounded-full bg-[#F05252]`}></div>
|
||||
Server disconnected
|
||||
</div>
|
||||
) : appState.serverStatus.paused ? (
|
||||
) : serverStatus.paused ? (
|
||||
<div className={`flex animate-pulse items-center gap-2 font-semibold`}>
|
||||
<div className={`relative h-4 w-4 rounded-full bg-[#FF9900]`}></div>
|
||||
Server paused
|
||||
@@ -72,13 +78,13 @@ export function MiniMapPanel(props: {}) {
|
||||
<div className="flex gap-2 font-semibold">
|
||||
FPS:
|
||||
<span style={{ color: frameRateColor }} className={`font-semibold`}>
|
||||
{appState.serverStatus.frameRate}
|
||||
{serverStatus.frameRate}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2 font-semibold">
|
||||
Load:
|
||||
<span style={{ color: loadColor }} className={`font-semibold`}>
|
||||
{appState.serverStatus.load}
|
||||
{serverStatus.load}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2 font-semibold">
|
||||
@@ -87,7 +93,7 @@ export function MiniMapPanel(props: {}) {
|
||||
<div className={`relative h-4 w-4 rounded-full bg-[#8BFF63]`}></div>
|
||||
</>
|
||||
)}
|
||||
{appState.mapOptions.showMinimap ? (
|
||||
{mapOptions.showMinimap ? (
|
||||
<FaChevronUp
|
||||
onClick={() => {
|
||||
getApp().getMap().setOption("showMinimap", false);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import {
|
||||
faGamepad,
|
||||
@@ -12,88 +12,94 @@ import {
|
||||
faVolumeHigh,
|
||||
faJ,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { StateConsumer } from "../../statecontext";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
|
||||
export function SideBar() {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StateConsumer>
|
||||
{(appState) => (
|
||||
<nav
|
||||
className={`
|
||||
z-20 flex h-full flex-col bg-gray-300
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
w-16 flex-1 flex-wrap items-center justify-center p-4
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col items-center justify-center gap-2.5
|
||||
`}
|
||||
>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.MAIN_MENU? OlympusState.MAIN_MENU: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.MAIN_MENU}
|
||||
icon={faEllipsisV}
|
||||
tooltip="Hide/show main menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.SPAWN? OlympusState.SPAWN: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.SPAWN}
|
||||
icon={faPlusSquare}
|
||||
tooltip="Hide/show unit spawn menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.UNIT_CONTROL? OlympusState.UNIT_CONTROL: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.UNIT_CONTROL}
|
||||
icon={faGamepad}
|
||||
tooltip="Hide/show selection tool and unit control menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.DRAW? OlympusState.DRAW: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.DRAW}
|
||||
icon={faPencil}
|
||||
tooltip="Hide/show drawing menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.AUDIO? OlympusState.AUDIO: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.AUDIO}
|
||||
icon={faVolumeHigh}
|
||||
tooltip="Hide/show audio menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.JTAC? OlympusState.JTAC: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.JTAC}
|
||||
icon={faJ} tooltip="Hide/show JTAC menu"></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-16 flex-wrap content-end justify-center p-4">
|
||||
<div
|
||||
className={`
|
||||
flex flex-col items-center justify-center gap-2.5
|
||||
`}
|
||||
>
|
||||
<OlStateButton
|
||||
onClick={() => window.open("https://github.com/Pax1601/DCSOlympus/wiki")}
|
||||
checked={false}
|
||||
icon={faQuestionCircle}
|
||||
tooltip="Open user guide on separate window"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.OPTIONS? OlympusState.OPTIONS: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.OPTIONS}
|
||||
icon={faCog}
|
||||
tooltip="Hide/show settings menu"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
</StateConsumer>
|
||||
<nav
|
||||
className={`
|
||||
z-20 flex h-full flex-col bg-gray-300
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
>
|
||||
<div className={`w-16 flex-1 flex-wrap items-center justify-center p-4`}>
|
||||
<div className={`flex flex-col items-center justify-center gap-2.5`}>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.MAIN_MENU ? OlympusState.MAIN_MENU : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.MAIN_MENU}
|
||||
icon={faEllipsisV}
|
||||
tooltip="Hide/show main menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.SPAWN ? OlympusState.SPAWN : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.SPAWN}
|
||||
icon={faPlusSquare}
|
||||
tooltip="Hide/show unit spawn menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.UNIT_CONTROL ? OlympusState.UNIT_CONTROL : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.UNIT_CONTROL}
|
||||
icon={faGamepad}
|
||||
tooltip="Hide/show selection tool and unit control menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.DRAW ? OlympusState.DRAW : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.DRAW}
|
||||
icon={faPencil}
|
||||
tooltip="Hide/show drawing menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.AUDIO ? OlympusState.AUDIO : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.AUDIO}
|
||||
icon={faVolumeHigh}
|
||||
tooltip="Hide/show audio menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.JTAC ? OlympusState.JTAC : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.JTAC}
|
||||
icon={faJ}
|
||||
tooltip="Hide/show JTAC menu"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-16 flex-wrap content-end justify-center p-4">
|
||||
<div className={`flex flex-col items-center justify-center gap-2.5`}>
|
||||
<OlStateButton
|
||||
onClick={() => window.open("https://github.com/Pax1601/DCSOlympus/wiki")}
|
||||
checked={false}
|
||||
icon={faQuestionCircle}
|
||||
tooltip="Open user guide on separate window"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.OPTIONS ? OlympusState.OPTIONS : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.OPTIONS}
|
||||
icon={faCog}
|
||||
tooltip="Hide/show settings menu"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,15 +16,10 @@ import {
|
||||
import { faExplosion, faSmog } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlEffectListEntry } from "../components/oleffectlistentry";
|
||||
import { EffectSpawnMenu } from "./effectspawnmenu";
|
||||
import { NO_SUBSTATE, OlympusState, SpawnSubState } from "../../constants/constants";
|
||||
import { aircraftDatabase } from "../../unit/databases/aircraftdatabase";
|
||||
import { navyUnitDatabase } from "../../unit/databases/navyunitdatabase";
|
||||
import { filterBlueprintsByLabel } from "../../other/utils";
|
||||
import { helicopterDatabase } from "../../unit/databases/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../../unit/databases/groundunitdatabase";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
import { NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
|
||||
enum Accordion {
|
||||
enum CategoryAccordion {
|
||||
NONE,
|
||||
AIRCRAFT,
|
||||
HELICOPTER,
|
||||
@@ -36,34 +31,53 @@ enum Accordion {
|
||||
}
|
||||
|
||||
export function SpawnMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [openAccordion, setOpenAccordion] = useState(Accordion.NONE);
|
||||
const [openAccordion, setOpenAccordion] = useState(CategoryAccordion.NONE);
|
||||
const [blueprint, setBlueprint] = useState(null as null | UnitBlueprint);
|
||||
const [effect, setEffect] = useState(null as null | string);
|
||||
const [filterString, setFilterString] = useState("");
|
||||
const [selectedRole, setSelectedRole] = useState(null as null | string);
|
||||
const [selectedType, setSelectedType] = useState(null as null | string);
|
||||
const [blueprints, setBlueprints] = useState([] as UnitBlueprint[]);
|
||||
const [roles, setRoles] = useState({ aircraft: [] as string[], helicopter: [] as string[] });
|
||||
const [types, setTypes] = useState({ groundunit: [] as string[], navyunit: [] as string[] });
|
||||
|
||||
const filteredAircraft = getApp()
|
||||
? filterBlueprintsByLabel(selectedRole ? aircraftDatabase.getByRole(selectedRole) : Object.values(aircraftDatabase.getBlueprints()), filterString)
|
||||
: ({} as { [key: string]: UnitBlueprint });
|
||||
const filteredHelicopters = getApp()
|
||||
? filterBlueprintsByLabel(selectedRole ? helicopterDatabase.getByRole(selectedRole) : Object.values(helicopterDatabase.getBlueprints()), filterString)
|
||||
: ({} as { [key: string]: UnitBlueprint });
|
||||
const filteredSAMs = getApp() ? filterBlueprintsByLabel(groundUnitDatabase.getByType("SAM Site"), filterString) : ({} as { [key: string]: UnitBlueprint });
|
||||
const filteredAAA = getApp() ? filterBlueprintsByLabel(groundUnitDatabase.getByType("AAA"), filterString) : ({} as { [key: string]: UnitBlueprint });
|
||||
const filteredGroundUnits = getApp()
|
||||
? filterBlueprintsByLabel(selectedType ? groundUnitDatabase.getByType(selectedType) : Object.values(groundUnitDatabase.getBlueprints()), filterString)
|
||||
: ({} as { [key: string]: UnitBlueprint });
|
||||
const filteredNavyUnits = getApp()
|
||||
? filterBlueprintsByLabel(selectedType ? navyUnitDatabase.getByType(selectedType) : Object.values(navyUnitDatabase.getBlueprints()), filterString)
|
||||
: ({} as { [key: string]: UnitBlueprint });
|
||||
useEffect(() => {
|
||||
if (selectedRole)
|
||||
setBlueprints(getApp()?.getUnitsManager().getDatabase().getByRole(selectedRole));
|
||||
else if (selectedType)
|
||||
setBlueprints(getApp()?.getUnitsManager().getDatabase().getByType(selectedType));
|
||||
else
|
||||
setBlueprints(getApp()?.getUnitsManager().getDatabase().getBlueprints())
|
||||
}, [selectedRole, selectedType, openAccordion]);
|
||||
|
||||
useEffect(() => {
|
||||
UnitDatabaseLoadedEvent.on(() => {
|
||||
setRoles({
|
||||
aircraft: getApp()?.getUnitsManager().getDatabase().getRoles((unit) => unit.category === "aircraft"),
|
||||
helicopter: getApp()?.getUnitsManager().getDatabase().getRoles((unit) => unit.category === "helicopter"),
|
||||
});
|
||||
|
||||
setTypes({
|
||||
groundunit: getApp()?.getUnitsManager().getDatabase().getTypes((unit) => unit.category === "groundunit"),
|
||||
navyunit: getApp()?.getUnitsManager().getDatabase().getTypes((unit) => unit.category === "navyunit")
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* Filter the blueprints according to the label */
|
||||
const filteredBlueprints: UnitBlueprint[] = [];
|
||||
if (blueprints) {
|
||||
blueprints.forEach((blueprint) => {
|
||||
if (blueprint.enabled && (filterString === "" || blueprint.label.toLowerCase().includes(filterString.toLowerCase()))) filteredBlueprints.push(blueprint);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.open) {
|
||||
if (blueprint !== null) setBlueprint(null);
|
||||
if (effect !== null) setEffect(null);
|
||||
if (filterString !== "") setFilterString("");
|
||||
if (openAccordion !== Accordion.NONE) setOpenAccordion(Accordion.NONE);
|
||||
if (openAccordion !== CategoryAccordion.NONE) setOpenAccordion(CategoryAccordion.NONE);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -94,36 +108,33 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
<OlSearchBar onChange={(value) => setFilterString(value)} text={filterString} />
|
||||
<OlAccordion
|
||||
title={`Aircraft`}
|
||||
open={openAccordion == Accordion.AIRCRAFT}
|
||||
open={openAccordion == CategoryAccordion.AIRCRAFT}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.AIRCRAFT ? Accordion.NONE : Accordion.AIRCRAFT);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.AIRCRAFT ? CategoryAccordion.NONE : CategoryAccordion.AIRCRAFT);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{aircraftDatabase
|
||||
.getRoles()
|
||||
.sort()
|
||||
.map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{roles.aircraft.sort().map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
@@ -131,43 +142,42 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredAircraft).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityAircraft} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "aircraft")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityAircraft} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title={`Helicopters`}
|
||||
open={openAccordion == Accordion.HELICOPTER}
|
||||
open={openAccordion == CategoryAccordion.HELICOPTER}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.HELICOPTER ? Accordion.NONE : Accordion.HELICOPTER);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.HELICOPTER ? CategoryAccordion.NONE : CategoryAccordion.HELICOPTER);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{helicopterDatabase
|
||||
.getRoles()
|
||||
.sort()
|
||||
.map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{roles.helicopter.sort().map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
@@ -175,16 +185,18 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredHelicopters).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityHelicopter} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "helicopter")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityHelicopter} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title={`Surfact to Air Missiles (SAM sites)`}
|
||||
open={openAccordion == Accordion.SAM}
|
||||
open={openAccordion == CategoryAccordion.SAM}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.SAM ? Accordion.NONE : Accordion.SAM);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.SAM ? CategoryAccordion.NONE : CategoryAccordion.SAM);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
@@ -195,16 +207,18 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredSAMs).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityGroundunitSam} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityGroundunitSam} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title={`Anti Aircraft Artillery (AAA)`}
|
||||
open={openAccordion == Accordion.AAA}
|
||||
open={openAccordion == CategoryAccordion.AAA}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.AAA ? Accordion.NONE : Accordion.AAA);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.AAA ? CategoryAccordion.NONE : CategoryAccordion.AAA);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
@@ -215,23 +229,24 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredAAA).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityGroundunitSam} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityGroundunitSam} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title={`Ground Units`}
|
||||
open={openAccordion == Accordion.GROUND_UNIT}
|
||||
open={openAccordion == CategoryAccordion.GROUND_UNIT}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.GROUND_UNIT ? Accordion.NONE : Accordion.GROUND_UNIT);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.GROUND_UNIT ? CategoryAccordion.NONE : CategoryAccordion.GROUND_UNIT);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{groundUnitDatabase
|
||||
.getTypes()
|
||||
{types.groundunit
|
||||
.sort()
|
||||
.filter((type) => {
|
||||
return type !== "AAA" && type !== "SAM Site";
|
||||
@@ -262,43 +277,42 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredGroundUnits).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityGroundunit} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityGroundunit} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title={`Ships and submarines`}
|
||||
open={openAccordion == Accordion.NAVY_UNIT}
|
||||
open={openAccordion == CategoryAccordion.NAVY_UNIT}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.NAVY_UNIT ? Accordion.NONE : Accordion.NAVY_UNIT);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.NAVY_UNIT ? CategoryAccordion.NONE : CategoryAccordion.NAVY_UNIT);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{navyUnitDatabase
|
||||
.getTypes()
|
||||
.sort()
|
||||
.map((type) => {
|
||||
return (
|
||||
<div
|
||||
key={type}
|
||||
data-selected={selectedType === type}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedType === type ? setSelectedType(null) : setSelectedType(type);
|
||||
}}
|
||||
>
|
||||
{type}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{types.navyunit.sort().map((type) => {
|
||||
return (
|
||||
<div
|
||||
key={type}
|
||||
data-selected={selectedType === type}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedType === type ? setSelectedType(null) : setSelectedType(type);
|
||||
}}
|
||||
>
|
||||
{type}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
@@ -306,16 +320,18 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredNavyUnits).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityNavyunit} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "navyunit")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityNavyunit} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title="Effects (smokes, explosions etc)"
|
||||
open={openAccordion == Accordion.EFFECT}
|
||||
open={openAccordion == CategoryAccordion.EFFECT}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.EFFECT ? Accordion.NONE : Accordion.EFFECT);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.EFFECT ? CategoryAccordion.NONE : CategoryAccordion.EFFECT);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||
import { Unit } from "../../unit/unit";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ContextActionSet } from "../../unit/contextactionset";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { getApp } from "../../olympusapp";
|
||||
@@ -8,13 +7,12 @@ import { CONTEXT_ACTION_COLORS } from "../../constants/constants";
|
||||
import { FaInfoCircle } from "react-icons/fa";
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
import { StateContext } from "../../statecontext";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent } from "../../events";
|
||||
|
||||
export function UnitMouseControlBar(props: {}) {
|
||||
const appState = useContext(StateContext);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
export function UnitControlBar(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [contextActionSet, setContextActionsSet] = useState(null as ContextActionSet | null);
|
||||
const [contextAction, setContextAction] = useState(null as ContextAction | null);
|
||||
const [scrolledLeft, setScrolledLeft] = useState(true);
|
||||
const [scrolledRight, setScrolledRight] = useState(false);
|
||||
|
||||
@@ -25,9 +23,9 @@ export function UnitMouseControlBar(props: {}) {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
setOpen(state === OlympusState.UNIT_CONTROL);
|
||||
});
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
ContextActionSetChangedEvent.on((contextActionSet) => setContextActionsSet(contextActionSet));
|
||||
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
|
||||
}, []);
|
||||
|
||||
function onScroll(el) {
|
||||
@@ -43,8 +41,8 @@ export function UnitMouseControlBar(props: {}) {
|
||||
|
||||
let reorderedActions: ContextAction[] = [];
|
||||
CONTEXT_ACTION_COLORS.forEach((color) => {
|
||||
if (appState.contextActionSet) {
|
||||
Object.values(appState.contextActionSet.getContextActions()).forEach((contextAction: ContextAction) => {
|
||||
if (contextActionSet) {
|
||||
Object.values(contextActionSet.getContextActions()).forEach((contextAction: ContextAction) => {
|
||||
if (color === null && contextAction.getOptions().buttonColor === undefined) reorderedActions.push(contextAction);
|
||||
else if (color === contextAction.getOptions().buttonColor) reorderedActions.push(contextAction);
|
||||
});
|
||||
@@ -53,7 +51,7 @@ export function UnitMouseControlBar(props: {}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{open && appState.contextActionSet && Object.keys(appState.contextActionSet.getContextActions()).length > 0 && (
|
||||
{appState === OlympusState.UNIT_CONTROL && contextActionSet && Object.keys(contextActionSet.getContextActions()).length > 0 && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
@@ -72,26 +70,26 @@ export function UnitMouseControlBar(props: {}) {
|
||||
/>
|
||||
)}
|
||||
<div className="flex gap-2 overflow-x-auto no-scrollbar p-2" onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
||||
{reorderedActions.map((contextAction: ContextAction) => {
|
||||
{reorderedActions.map((contextActionIt: ContextAction) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
key={contextAction.getId()}
|
||||
checked={contextAction === appState.contextAction}
|
||||
icon={contextAction.getIcon()}
|
||||
tooltip={contextAction.getLabel()}
|
||||
key={contextActionIt.getId()}
|
||||
checked={contextActionIt === contextAction}
|
||||
icon={contextActionIt.getIcon()}
|
||||
tooltip={contextActionIt.getLabel()}
|
||||
className={
|
||||
contextAction.getOptions().buttonColor
|
||||
contextActionIt.getOptions().buttonColor
|
||||
? `
|
||||
border-2
|
||||
border-${contextAction.getOptions().buttonColor}-500
|
||||
border-${contextActionIt.getOptions().buttonColor}-500
|
||||
`
|
||||
: ""
|
||||
}
|
||||
onClick={() => {
|
||||
if (contextAction.getOptions().executeImmediately) {
|
||||
contextAction.executeCallback(null, null);
|
||||
if (contextActionIt.getOptions().executeImmediately) {
|
||||
contextActionIt.executeCallback(null, null);
|
||||
} else {
|
||||
appState.contextAction !== contextAction ? getApp().getMap().setContextAction(contextAction) : getApp().getMap().setContextAction(null);
|
||||
contextActionIt !== contextAction ? getApp().getMap().setContextAction(contextActionIt) : getApp().getMap().setContextAction(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -108,7 +106,7 @@ export function UnitMouseControlBar(props: {}) {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{appState.contextAction && (
|
||||
{contextAction && (
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-32 flex min-w-[300px]
|
||||
@@ -130,7 +128,7 @@ export function UnitMouseControlBar(props: {}) {
|
||||
md:border-l-[1px] md:px-5
|
||||
`}
|
||||
>
|
||||
{appState.contextAction.getDescription()}
|
||||
{contextAction.getDescription()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { MutableRefObject, useContext, useEffect, useRef, useState } from "react";
|
||||
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { Unit } from "../../unit/unit";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
@@ -50,11 +50,11 @@ import { Radio, TACAN } from "../../interfaces";
|
||||
import { OlStringInput } from "../components/olstringinput";
|
||||
import { OlFrequencyInput } from "../components/olfrequencyinput";
|
||||
import { UnitSink } from "../../audio/unitsink";
|
||||
import { StateContext } from "../../statecontext";
|
||||
import { AudioManagerStateChangedEvent, SelectedUnitsChangedEvent, SelectionClearedEvent } from "../../events";
|
||||
|
||||
export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const appState = useContext(StateContext);
|
||||
|
||||
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||
const [audioManagerState, setAudioManagerState] = useState(false);
|
||||
const [selectedUnitsData, setSelectedUnitsData] = useState({
|
||||
desiredAltitude: undefined as undefined | number,
|
||||
desiredAltitudeType: undefined as undefined | string,
|
||||
@@ -110,6 +110,12 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
|
||||
var searchBarRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
SelectedUnitsChangedEvent.on((units) => setSelectedUnits(units));
|
||||
SelectionClearedEvent.on(() => setSelectedUnits([]));
|
||||
AudioManagerStateChangedEvent.on((state) => setAudioManagerState(state));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchBarRefState) setSearchBarRefState(searchBarRef);
|
||||
if (!props.open && selectionBlueprint !== null) setSelectionBlueprint(null);
|
||||
@@ -154,12 +160,12 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
},
|
||||
} as { [key in keyof typeof selectedUnitsData]: (unit: Unit) => void };
|
||||
|
||||
var updatedData = selectedUnitsData;
|
||||
var updatedData = {};
|
||||
Object.entries(getters).forEach(([key, getter]) => {
|
||||
updatedData[key] = getApp()?.getUnitsManager()?.getSelectedUnitsVariable(getter);
|
||||
});
|
||||
setSelectedUnitsData(updatedData);
|
||||
}, [appState.selectedUnits]);
|
||||
setSelectedUnitsData(updatedData as typeof selectedUnitsData);
|
||||
}, [selectedUnits]);
|
||||
|
||||
/* Count how many units are selected of each type, divided by coalition */
|
||||
var unitOccurences: {
|
||||
@@ -172,7 +178,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
neutral: {},
|
||||
};
|
||||
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
if (!(unit.getName() in unitOccurences[unit.getCoalition()]))
|
||||
unitOccurences[unit.getCoalition()][unit.getName()] = { occurences: 1, label: unit.getBlueprint()?.label };
|
||||
else unitOccurences[unit.getCoalition()][unit.getName()].occurences++;
|
||||
@@ -196,6 +202,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
return category === "Helicopter";
|
||||
});
|
||||
|
||||
// TODO: use constants
|
||||
const minAltitude = 0;
|
||||
const minSpeed = 0;
|
||||
|
||||
@@ -220,13 +227,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
return (
|
||||
<Menu
|
||||
open={props.open}
|
||||
title={appState.selectedUnits.length > 0 ? `Units selected (x${appState.selectedUnits.length})` : `No units selected`}
|
||||
title={selectedUnits.length > 0 ? `Units selected (x${selectedUnits.length})` : `No units selected`}
|
||||
onClose={props.onClose}
|
||||
canBeHidden={true}
|
||||
>
|
||||
<>
|
||||
{/* ============== Selection tool START ============== */}
|
||||
{appState.selectedUnits.length == 0 && (
|
||||
{selectedUnits.length == 0 && (
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<div className="text-lg text-bold text-gray-200">Selection tool</div>
|
||||
<div className="text-sm text-gray-400">
|
||||
@@ -429,7 +436,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{/* */}
|
||||
<>
|
||||
{/* ============== Unit control menu START ============== */}
|
||||
{appState.selectedUnits.length > 0 && (
|
||||
{selectedUnits.length > 0 && (
|
||||
<>
|
||||
{/* ============== Units list START ============== */}
|
||||
<div
|
||||
@@ -522,7 +529,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
leftLabel={"AGL"}
|
||||
rightLabel={"ASL"}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setAltitudeType(selectedUnitsData.desiredAltitudeType === "ASL" ? "AGL" : "ASL");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -534,7 +541,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setAltitude(ftToM(Number(ev.target.value)));
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -584,7 +591,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
leftLabel={"GS"}
|
||||
rightLabel={"CAS"}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setSpeedType(selectedUnitsData.desiredSpeedType === "CAS" ? "GS" : "CAS");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -597,7 +604,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setSpeed(knotsToMs(Number(ev.target.value)));
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -613,8 +620,8 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</div>
|
||||
{/* ============== Airspeed selector END ============== */}
|
||||
{/* ============== Rules of Engagement START ============== */}
|
||||
{!(appState.selectedUnits.length === 1 && appState.selectedUnits[0].isTanker()) &&
|
||||
!(appState.selectedUnits.length === 1 && appState.selectedUnits[0].isAWACS()) && (
|
||||
{!(selectedUnits.length === 1 && selectedUnits[0].isTanker()) &&
|
||||
!(selectedUnits.length === 1 && selectedUnits[0].isAWACS()) && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<span
|
||||
className={`
|
||||
@@ -630,7 +637,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setROE(ROEs[idx]);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -668,7 +675,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setReactionToThreat(reactionsToThreat[idx]);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -700,7 +707,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setEmissionsCountermeasures(emissionsCountermeasures[idx]);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -736,7 +743,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.isActiveTanker}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setAdvancedOptions(
|
||||
!selectedUnitsData.isActiveTanker,
|
||||
unit.getIsActiveAWACS(),
|
||||
@@ -770,7 +777,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.isActiveAWACS}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setAdvancedOptions(
|
||||
unit.getIsActiveTanker(),
|
||||
!selectedUnitsData.isActiveAWACS,
|
||||
@@ -789,7 +796,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
)}
|
||||
{/* ============== Tanker and AWACS available button END ============== */}
|
||||
{/* ============== Advanced settings buttons START ============== */}
|
||||
{appState.selectedUnits.length === 1 && (appState.selectedUnits[0].isTanker() || appState.selectedUnits[0].isAWACS()) && (
|
||||
{selectedUnits.length === 1 && (selectedUnits[0].isTanker() || selectedUnits[0].isAWACS()) && (
|
||||
<div className="flex content-center justify-between">
|
||||
<button
|
||||
className={`
|
||||
@@ -800,13 +807,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
`}
|
||||
onClick={() => {
|
||||
setActiveAdvancedSettings({
|
||||
radio: JSON.parse(JSON.stringify(appState.selectedUnits[0].getRadio())),
|
||||
TACAN: JSON.parse(JSON.stringify(appState.selectedUnits[0].getTACAN())),
|
||||
radio: JSON.parse(JSON.stringify(selectedUnits[0].getRadio())),
|
||||
TACAN: JSON.parse(JSON.stringify(selectedUnits[0].getTACAN())),
|
||||
});
|
||||
setShowAdvancedSettings(true);
|
||||
}}
|
||||
>
|
||||
<FaCog className="my-auto" /> {appState.selectedUnits[0].isTanker() ? "Configure tanker settings" : "Configure AWACS settings"}
|
||||
<FaCog className="my-auto" /> {selectedUnits[0].isTanker() ? "Configure tanker settings" : "Configure AWACS settings"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -834,7 +841,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.scenicAAA}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
selectedUnitsData.scenicAAA ? unit.changeSpeed("stop") : unit.scenicAAA();
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -859,7 +866,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.missOnPurpose}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
selectedUnitsData.missOnPurpose ? unit.changeSpeed("stop") : unit.missOnPurpose();
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -888,7 +895,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setShotsScatter(idx + 1);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -920,7 +927,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setShotsIntensity(idx + 1);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -950,7 +957,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlCoalitionToggle
|
||||
coalition={selectedUnitsData.operateAs as Coalition}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setOperateAs(selectedUnitsData.operateAs === "blue" ? "red" : "blue");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -975,7 +982,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.followRoads}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setFollowRoads(!selectedUnitsData.followRoads);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -999,7 +1006,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.onOff}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setOnOff(!selectedUnitsData.onOff);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -1022,11 +1029,11 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
>
|
||||
Loudspeakers
|
||||
</span>
|
||||
{appState.audioManagerState ? (
|
||||
{audioManagerState ? (
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.isAudioSink}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
if (!selectedUnitsData.isAudioSink) {
|
||||
getApp()?.getAudioManager().addUnitSink(unit);
|
||||
setSelectedUnitsData({
|
||||
@@ -1077,14 +1084,14 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<div className="flex content-center gap-2">
|
||||
<OlDropdown
|
||||
label={
|
||||
appState.selectedUnits[0].isAWACS()
|
||||
selectedUnits[0].isAWACS()
|
||||
? ["Overlord", "Magic", "Wizard", "Focus", "Darkstar"][activeAdvancedSettings ? activeAdvancedSettings.radio.callsign - 1 : 0]
|
||||
: ["Texaco", "Arco", "Shell"][activeAdvancedSettings ? activeAdvancedSettings.radio.callsign - 1 : 0]
|
||||
}
|
||||
className="my-auto w-full"
|
||||
>
|
||||
<>
|
||||
{appState.selectedUnits[0].isAWACS() && (
|
||||
{selectedUnits[0].isAWACS() && (
|
||||
<>
|
||||
{["Overlord", "Magic", "Wizard", "Focus", "Darkstar"].map((name, idx) => {
|
||||
return (
|
||||
@@ -1103,7 +1110,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
)}
|
||||
</>
|
||||
<>
|
||||
{appState.selectedUnits[0].isTanker() && (
|
||||
{selectedUnits[0].isTanker() && (
|
||||
<>
|
||||
{["Texaco", "Arco", "Shell"].map((name, idx) => {
|
||||
return (
|
||||
@@ -1238,12 +1245,12 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
`}
|
||||
onClick={() => {
|
||||
if (activeAdvancedSettings)
|
||||
appState.selectedUnits[0].setAdvancedOptions(
|
||||
appState.selectedUnits[0].getIsActiveTanker(),
|
||||
appState.selectedUnits[0].getIsActiveAWACS(),
|
||||
selectedUnits[0].setAdvancedOptions(
|
||||
selectedUnits[0].getIsActiveTanker(),
|
||||
selectedUnits[0].getIsActiveAWACS(),
|
||||
activeAdvancedSettings.TACAN,
|
||||
activeAdvancedSettings.radio,
|
||||
appState.selectedUnits[0].getGeneralSettings()
|
||||
selectedUnits[0].getGeneralSettings()
|
||||
);
|
||||
setActiveAdvancedSettings(null);
|
||||
setShowAdvancedSettings(false);
|
||||
@@ -1276,7 +1283,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{/* ============== Unit basic options END ============== */}
|
||||
<>
|
||||
{/* ============== Fuel/payload/radio section START ============== */}
|
||||
{appState.selectedUnits.length === 1 && (
|
||||
{selectedUnits.length === 1 && (
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-4 border-l-4 border-l-olympus-100
|
||||
@@ -1287,27 +1294,23 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<div
|
||||
className={`
|
||||
flex content-center gap-2 rounded-full
|
||||
${appState.selectedUnits[0].getFuel() > 40 && `
|
||||
bg-green-700
|
||||
`}
|
||||
${selectedUnits[0].getFuel() > 40 && `bg-green-700`}
|
||||
${
|
||||
appState.selectedUnits[0].getFuel() > 10 &&
|
||||
appState.selectedUnits[0].getFuel() <= 40 &&
|
||||
selectedUnits[0].getFuel() > 10 &&
|
||||
selectedUnits[0].getFuel() <= 40 &&
|
||||
`bg-yellow-700`
|
||||
}
|
||||
${appState.selectedUnits[0].getFuel() <= 10 && `
|
||||
bg-red-700
|
||||
`}
|
||||
${selectedUnits[0].getFuel() <= 10 && `bg-red-700`}
|
||||
px-2 py-1 text-sm font-bold text-white
|
||||
`}
|
||||
>
|
||||
<FaGasPump className="my-auto" />
|
||||
{appState.selectedUnits[0].getFuel()}%
|
||||
{selectedUnits[0].getFuel()}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{appState.selectedUnits[0].isControlledByOlympus() && (appState.selectedUnits[0].isTanker() || appState.selectedUnits[0].isAWACS()) && (
|
||||
{selectedUnits[0].isControlledByOlympus() && (selectedUnits[0].isTanker() || selectedUnits[0].isAWACS()) && (
|
||||
<>
|
||||
{/* ============== Radio section START ============== */}
|
||||
<div className="flex content-center justify-between">
|
||||
@@ -1329,7 +1332,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
dark:text-gray-300
|
||||
`}
|
||||
>
|
||||
{`${appState.selectedUnits[0].isTanker() ? ["Texaco", "Arco", "Shell"][appState.selectedUnits[0].getRadio().callsign - 1] : ["Overlord", "Magic", "Wizard", "Focus", "Darkstar"][appState.selectedUnits[0].getRadio().callsign - 1]}-${appState.selectedUnits[0].getRadio().callsignNumber}`}
|
||||
{`${selectedUnits[0].isTanker() ? ["Texaco", "Arco", "Shell"][selectedUnits[0].getRadio().callsign - 1] : ["Overlord", "Magic", "Wizard", "Focus", "Darkstar"][selectedUnits[0].getRadio().callsign - 1]}-${selectedUnits[0].getRadio().callsignNumber}`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1352,7 +1355,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
dark:text-gray-300
|
||||
`}
|
||||
>
|
||||
{`${(appState.selectedUnits[0].getRadio().frequency / 1000000).toFixed(3)} MHz`}
|
||||
{`${(selectedUnits[0].getRadio().frequency / 1000000).toFixed(3)} MHz`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1376,8 +1379,8 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
dark:text-gray-300
|
||||
`}
|
||||
>
|
||||
{appState.selectedUnits[0].getTACAN().isOn
|
||||
? `${appState.selectedUnits[0].getTACAN().channel}${appState.selectedUnits[0].getTACAN().XY} ${appState.selectedUnits[0].getTACAN().callsign}`
|
||||
{selectedUnits[0].getTACAN().isOn
|
||||
? `${selectedUnits[0].getTACAN().channel}${selectedUnits[0].getTACAN().XY} ${selectedUnits[0].getTACAN().callsign}`
|
||||
: "TACAN OFF"}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1386,9 +1389,9 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</>
|
||||
)}
|
||||
{/* ============== Payload section START ============== */}
|
||||
{!appState.selectedUnits[0].isTanker() &&
|
||||
!appState.selectedUnits[0].isAWACS() &&
|
||||
appState.selectedUnits[0].getAmmo().map((ammo, idx) => {
|
||||
{!selectedUnits[0].isTanker() &&
|
||||
!selectedUnits[0].isAWACS() &&
|
||||
selectedUnits[0].getAmmo().map((ammo, idx) => {
|
||||
return (
|
||||
<div className="flex content-center gap-2" key={idx}>
|
||||
<div
|
||||
|
||||
@@ -8,7 +8,7 @@ import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { LoadoutBlueprint, UnitBlueprint } from "../../interfaces";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ftToM, getUnitCategoryByBlueprint } from "../../other/utils";
|
||||
import { ftToM } from "../../other/utils";
|
||||
import { LatLng } from "leaflet";
|
||||
import { Airbase } from "../../mission/airbase";
|
||||
import { OlympusState, SpawnSubState } from "../../constants/constants";
|
||||
@@ -39,7 +39,7 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
|
||||
getApp()
|
||||
?.getMap()
|
||||
?.setSpawnRequestTable({
|
||||
category: getUnitCategoryByBlueprint(props.blueprint),
|
||||
category: props.blueprint.category,
|
||||
unit: {
|
||||
unitType: props.blueprint.name,
|
||||
location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
|
||||
@@ -58,13 +58,13 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
|
||||
if (getApp().getState() === OlympusState.SPAWN) getApp().setState(OlympusState.IDLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [props.blueprint, spawnAltitude, spawnLoadoutName, spawnCoalition]);
|
||||
|
||||
function spawnAtAirbase() {
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.spawnUnits(
|
||||
getUnitCategoryByBlueprint(props.blueprint),
|
||||
props.blueprint.category,
|
||||
[
|
||||
{
|
||||
unitType: props.blueprint.name,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import "./ui.css";
|
||||
|
||||
import { StateProvider } from "../statecontext";
|
||||
|
||||
import { Header } from "./panels/header";
|
||||
import { SpawnMenu } from "./panels/spawnmenu";
|
||||
import { UnitControlMenu } from "./panels/unitcontrolmenu";
|
||||
@@ -13,10 +11,8 @@ import { MapHiddenTypes, MapOptions } from "../types/types";
|
||||
import {
|
||||
BLUE_COMMANDER,
|
||||
GAME_MASTER,
|
||||
MAP_HIDDEN_TYPES_DEFAULTS,
|
||||
MAP_OPTIONS_DEFAULTS,
|
||||
NO_SUBSTATE,
|
||||
OlympusEvent,
|
||||
OlympusState,
|
||||
OlympusSubState,
|
||||
RED_COMMANDER,
|
||||
@@ -26,7 +22,7 @@ import { getApp, setupApp } from "../olympusapp";
|
||||
import { LoginModal } from "./modals/login";
|
||||
import { sha256 } from "js-sha256";
|
||||
import { MiniMapPanel } from "./panels/minimappanel";
|
||||
import { UnitMouseControlBar } from "./panels/unitmousecontrolbar";
|
||||
import { UnitControlBar } from "./panels/unitcontrolbar";
|
||||
import { DrawingMenu } from "./panels/drawingmenu";
|
||||
import { ControlsPanel } from "./panels/controlspanel";
|
||||
import { MapContextMenu } from "./contextmenus/mapcontextmenu";
|
||||
@@ -40,24 +36,8 @@ import { UnitExplosionMenu } from "./panels/unitexplosionmenu";
|
||||
import { JTACMenu } from "./panels/jtacmenu";
|
||||
import {
|
||||
AppStateChangedEvent,
|
||||
AudioManagerStateChangedEvent,
|
||||
AudioSinksChangedEvent,
|
||||
AudioSourcesChangedEvent,
|
||||
ConfigLoadedEvent,
|
||||
ContextActionChangedEvent,
|
||||
ContextActionSetChangedEvent,
|
||||
HiddenTypesChangedEvent,
|
||||
MapOptionsChangedEvent,
|
||||
MapSourceChangedEvent,
|
||||
SelectedUnitsChangedEvent,
|
||||
ServerStatusUpdatedEvent,
|
||||
UnitSelectedEvent,
|
||||
MapOptionsChangedEvent
|
||||
} from "../events";
|
||||
import { ServerStatus } from "../interfaces";
|
||||
import { AudioSource } from "../audio/audiosource";
|
||||
import { AudioSink } from "../audio/audiosink";
|
||||
import { ContextAction } from "../unit/contextaction";
|
||||
import { ContextActionSet } from "../unit/contextactionset";
|
||||
|
||||
export type OlympusUIState = {
|
||||
mainMenuVisible: boolean;
|
||||
@@ -74,17 +54,7 @@ export type OlympusUIState = {
|
||||
export function UI() {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
|
||||
const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
const [mapSources, setMapSources] = useState([] as string[]);
|
||||
const [activeMapSource, setActiveMapSource] = useState("");
|
||||
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||
const [audioSources, setAudioSources] = useState([] as AudioSource[]);
|
||||
const [audioSinks, setAudioSinks] = useState([] as AudioSink[]);
|
||||
const [audioManagerState, setAudioManagerState] = useState(false);
|
||||
const [serverStatus, setServerStatus] = useState({} as ServerStatus);
|
||||
const [contextActionSet, setContextActionsSet] = useState(null as ContextActionSet | null);
|
||||
const [contextAction, setContextActions] = useState(null as ContextAction | null);
|
||||
|
||||
const [checkingPassword, setCheckingPassword] = useState(false);
|
||||
const [loginError, setLoginError] = useState(false);
|
||||
@@ -105,22 +75,7 @@ export function UI() {
|
||||
setAppState(state);
|
||||
setAppSubState(subState);
|
||||
});
|
||||
ConfigLoadedEvent.on(() => {
|
||||
let config = getApp().getConfig();
|
||||
var sources = Object.keys(config.mapMirrors).concat(Object.keys(config.mapLayers));
|
||||
setMapSources(sources);
|
||||
setActiveMapSource(sources[0]);
|
||||
});
|
||||
HiddenTypesChangedEvent.on((hiddenTypes) => setMapHiddenTypes({ ...hiddenTypes }));
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
MapSourceChangedEvent.on((source) => setActiveMapSource(source));
|
||||
SelectedUnitsChangedEvent.on((units) => setSelectedUnits(units));
|
||||
AudioSourcesChangedEvent.on((sources) => setAudioSources(sources));
|
||||
AudioSinksChangedEvent.on((sinks) => setAudioSinks(sinks));
|
||||
AudioManagerStateChangedEvent.on((state) => setAudioManagerState(state));
|
||||
ServerStatusUpdatedEvent.on((status) => setServerStatus(status));
|
||||
ContextActionSetChangedEvent.on((contextActionSet) => setContextActionsSet(contextActionSet));
|
||||
ContextActionChangedEvent.on((contextAction) => setContextActions(contextAction));
|
||||
|
||||
document.addEventListener("showProtectionPrompt", (ev: CustomEventInit) => {
|
||||
setProtectionPromptVisible(true);
|
||||
@@ -168,97 +123,75 @@ export function UI() {
|
||||
`}
|
||||
onLoad={setupApp}
|
||||
>
|
||||
<StateProvider
|
||||
value={{
|
||||
appState,
|
||||
appSubState,
|
||||
mapOptions,
|
||||
mapHiddenTypes,
|
||||
mapSources,
|
||||
activeMapSource,
|
||||
selectedUnits,
|
||||
audioSources,
|
||||
audioSinks,
|
||||
audioManagerState,
|
||||
serverStatus,
|
||||
contextActionSet,
|
||||
contextAction,
|
||||
}}
|
||||
>
|
||||
<Header />
|
||||
<div className="flex h-full w-full flex-row-reverse">
|
||||
{appState === OlympusState.LOGIN && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95
|
||||
`}
|
||||
></div>
|
||||
<LoginModal
|
||||
onLogin={(password) => {
|
||||
checkPassword(password);
|
||||
}}
|
||||
onContinue={(username) => {
|
||||
connect(username);
|
||||
}}
|
||||
onBack={() => {
|
||||
setCommandMode(null);
|
||||
}}
|
||||
checkingPassword={checkingPassword}
|
||||
loginError={loginError}
|
||||
commandMode={commandMode}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{protectionPromptVisible && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95
|
||||
`}
|
||||
></div>
|
||||
<ProtectionPrompt
|
||||
onContinue={(units) => {
|
||||
protectionCallback(units);
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
onBack={() => {
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
units={protectionUnits}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div id="map-container" className="z-0 h-full w-screen" />
|
||||
<MainMenu open={appState === OlympusState.MAIN_MENU} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<SpawnMenu open={appState === OlympusState.SPAWN} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<OptionsMenu open={appState === OlympusState.OPTIONS} onClose={() => getApp().setState(OlympusState.IDLE)} options={mapOptions} />
|
||||
<Header />
|
||||
<div className="flex h-full w-full flex-row-reverse">
|
||||
{appState === OlympusState.LOGIN && (
|
||||
<>
|
||||
<div
|
||||
className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}
|
||||
></div>
|
||||
<LoginModal
|
||||
onLogin={(password) => {
|
||||
checkPassword(password);
|
||||
}}
|
||||
onContinue={(username) => {
|
||||
connect(username);
|
||||
}}
|
||||
onBack={() => {
|
||||
setCommandMode(null);
|
||||
}}
|
||||
checkingPassword={checkingPassword}
|
||||
loginError={loginError}
|
||||
commandMode={commandMode}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{protectionPromptVisible && (
|
||||
<>
|
||||
<div
|
||||
className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}
|
||||
></div>
|
||||
<ProtectionPrompt
|
||||
onContinue={(units) => {
|
||||
protectionCallback(units);
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
onBack={() => {
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
units={protectionUnits}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div id="map-container" className="z-0 h-full w-screen" />
|
||||
<MainMenu open={appState === OlympusState.MAIN_MENU} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<SpawnMenu open={appState === OlympusState.SPAWN} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<OptionsMenu open={appState === OlympusState.OPTIONS} onClose={() => getApp().setState(OlympusState.IDLE)} options={mapOptions} /* TODO remove *//>
|
||||
|
||||
<UnitControlMenu
|
||||
open={appState === OlympusState.UNIT_CONTROL && appSubState !== UnitControlSubState.FORMATION}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
/>
|
||||
<FormationMenu
|
||||
open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.FORMATION}
|
||||
leader={formationLeader}
|
||||
wingmen={formationWingmen}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
/>
|
||||
<UnitControlMenu
|
||||
open={appState === OlympusState.UNIT_CONTROL && appSubState !== UnitControlSubState.FORMATION}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
/>
|
||||
<FormationMenu
|
||||
open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.FORMATION}
|
||||
leader={formationLeader}
|
||||
wingmen={formationWingmen}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
/>
|
||||
|
||||
<DrawingMenu open={appState === OlympusState.DRAW} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<AirbaseMenu open={appState === OlympusState.AIRBASE} onClose={() => getApp().setState(OlympusState.IDLE)} airbase={airbase} />
|
||||
<AudioMenu open={appState === OlympusState.AUDIO} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<DrawingMenu open={appState === OlympusState.DRAW} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<AirbaseMenu open={appState === OlympusState.AIRBASE} onClose={() => getApp().setState(OlympusState.IDLE)} airbase={airbase} /* TODO remove */ />
|
||||
<AudioMenu open={appState === OlympusState.AUDIO} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
{/* TODO} <UnitExplosionMenu open={appState === OlympusState.MAIN_MENU} units={unitExplosionUnits} onClose={() => getApp().setState(OlympusState.IDLE)} /> {*/}
|
||||
<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
{/* TODO} <UnitExplosionMenu open={appState === OlympusState.MAIN_MENU} units={unitExplosionUnits} onClose={() => getApp().setState(OlympusState.IDLE)} /> {*/}
|
||||
<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
<MiniMapPanel />
|
||||
<ControlsPanel />
|
||||
<UnitMouseControlBar />
|
||||
<MapContextMenu />
|
||||
<SideBar />
|
||||
</div>
|
||||
</StateProvider>
|
||||
<MiniMapPanel />
|
||||
<ControlsPanel />
|
||||
<UnitControlBar />
|
||||
<MapContextMenu />
|
||||
<SideBar />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase";
|
||||
|
||||
export class AircraftDatabase extends UnitDatabase {
|
||||
constructor() {
|
||||
super(window.location.href.split("?")[0].replace("vite/", "") + "api/databases/units/aircraftdatabase");
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
return "Aircraft";
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return 0;
|
||||
|
||||
const blueprint = this.getByName(name);
|
||||
if (blueprint?.cost != undefined) return blueprint?.cost;
|
||||
|
||||
if (blueprint?.era == "WW2") return 20;
|
||||
else if (blueprint?.era == "Early Cold War") return 50;
|
||||
else if (blueprint?.era == "Mid Cold War") return 100;
|
||||
else if (blueprint?.era == "Late Cold War") return 200;
|
||||
else if (blueprint?.era == "Modern") return 400;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export var aircraftDatabase = new AircraftDatabase();
|
||||
@@ -1,30 +0,0 @@
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase";
|
||||
|
||||
export class GroundUnitDatabase extends UnitDatabase {
|
||||
constructor() {
|
||||
super(window.location.href.split("?")[0].replace("vite/", "") + "api/databases/units/groundunitdatabase");
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return 0;
|
||||
|
||||
const blueprint = this.getByName(name);
|
||||
if (blueprint?.cost != undefined) return blueprint?.cost;
|
||||
|
||||
if (blueprint?.era == "WW2") return 20;
|
||||
else if (blueprint?.era == "Early Cold War") return 50;
|
||||
else if (blueprint?.era == "Mid Cold War") return 100;
|
||||
else if (blueprint?.era == "Late Cold War") return 200;
|
||||
else if (blueprint?.era == "Modern") return 400;
|
||||
return 0;
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
return "GroundUnit";
|
||||
}
|
||||
}
|
||||
|
||||
export var groundUnitDatabase = new GroundUnitDatabase();
|
||||
@@ -1,30 +0,0 @@
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase";
|
||||
|
||||
export class HelicopterDatabase extends UnitDatabase {
|
||||
constructor() {
|
||||
super(window.location.href.split("?")[0].replace("vite/", "") + "api/databases/units/helicopterdatabase");
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return 0;
|
||||
|
||||
const blueprint = this.getByName(name);
|
||||
if (blueprint?.cost != undefined) return blueprint?.cost;
|
||||
|
||||
if (blueprint?.era == "WW2") return 20;
|
||||
else if (blueprint?.era == "Early Cold War") return 50;
|
||||
else if (blueprint?.era == "Mid Cold War") return 100;
|
||||
else if (blueprint?.era == "Late Cold War") return 200;
|
||||
else if (blueprint?.era == "Modern") return 400;
|
||||
return 0;
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
return "Helicopter";
|
||||
}
|
||||
}
|
||||
|
||||
export var helicopterDatabase = new HelicopterDatabase();
|
||||
@@ -1,30 +0,0 @@
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase";
|
||||
|
||||
export class NavyUnitDatabase extends UnitDatabase {
|
||||
constructor() {
|
||||
super(window.location.href.split("?")[0].replace("vite/", "") + "api/databases/units/navyunitdatabase");
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return 0;
|
||||
|
||||
const blueprint = this.getByName(name);
|
||||
if (blueprint?.cost != undefined) return blueprint?.cost;
|
||||
|
||||
if (blueprint?.era == "WW2") return 20;
|
||||
else if (blueprint?.era == "Early Cold War") return 50;
|
||||
else if (blueprint?.era == "Mid Cold War") return 100;
|
||||
else if (blueprint?.era == "Late Cold War") return 200;
|
||||
else if (blueprint?.era == "Modern") return 400;
|
||||
return 0;
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
return "NavyUnit";
|
||||
}
|
||||
}
|
||||
|
||||
export var navyUnitDatabase = new NavyUnitDatabase();
|
||||
@@ -1,38 +1,33 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { LoadoutBlueprint, UnitBlueprint } from "../../interfaces";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
import { UnitDatabaseLoadedEvent } from "../../events";
|
||||
|
||||
export abstract class UnitDatabase {
|
||||
export class UnitDatabase {
|
||||
blueprints: { [key: string]: UnitBlueprint } = {};
|
||||
#url: string;
|
||||
|
||||
constructor(url: string = "") {
|
||||
this.#url = url;
|
||||
this.load(() => {});
|
||||
}
|
||||
constructor() {}
|
||||
|
||||
load(callback: CallableFunction) {
|
||||
if (this.#url !== "") {
|
||||
load(url: string) {
|
||||
if (url !== "") {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", this.#url, true);
|
||||
xhr.open("GET", url, true);
|
||||
xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0");
|
||||
xhr.responseType = "json";
|
||||
xhr.onload = () => {
|
||||
var status = xhr.status;
|
||||
if (status === 200) {
|
||||
this.blueprints = xhr.response;
|
||||
callback();
|
||||
const newBlueprints = xhr.response as { [key: string]: UnitBlueprint };
|
||||
this.blueprints = { ...this.blueprints, ...newBlueprints };
|
||||
UnitDatabaseLoadedEvent.dispatch();
|
||||
} else {
|
||||
console.error(`Error retrieving database from ${this.#url}`);
|
||||
console.error(`Error retrieving database from ${url}`);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
|
||||
abstract getCategory(): string;
|
||||
|
||||
/* Gets a specific blueprint by name */
|
||||
getByName(name: string) {
|
||||
if (name in this.blueprints) return this.blueprints[name];
|
||||
@@ -48,8 +43,8 @@ export abstract class UnitDatabase {
|
||||
}
|
||||
|
||||
getBlueprints(includeDisabled: boolean = false) {
|
||||
if (!getApp()) return {};
|
||||
|
||||
if (!getApp()) return [];
|
||||
|
||||
if (
|
||||
getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER ||
|
||||
!getApp().getMissionManager().getCommandModeOptions().restrictSpawns
|
||||
@@ -59,7 +54,7 @@ export abstract class UnitDatabase {
|
||||
const blueprint = this.blueprints[unit];
|
||||
if (blueprint.enabled || includeDisabled) filteredBlueprints[unit] = blueprint;
|
||||
}
|
||||
return filteredBlueprints;
|
||||
return Object.values(filteredBlueprints);
|
||||
} else {
|
||||
var filteredBlueprints: { [key: string]: UnitBlueprint } = {};
|
||||
for (let unit in this.blueprints) {
|
||||
@@ -75,16 +70,17 @@ export abstract class UnitDatabase {
|
||||
filteredBlueprints[unit] = blueprint;
|
||||
}
|
||||
}
|
||||
return filteredBlueprints;
|
||||
return Object.values(filteredBlueprints);
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns a list of all possible roles in a database */
|
||||
getRoles() {
|
||||
getRoles(unitFilter?: (unit: UnitBlueprint) => boolean) {
|
||||
var roles: string[] = [];
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
for (let unit in filteredBlueprints) {
|
||||
var loadouts = filteredBlueprints[unit].loadouts;
|
||||
for (let unit of filteredBlueprints) {
|
||||
if (typeof unitFilter === "function" && !unitFilter(unit)) continue;
|
||||
var loadouts = unit.loadouts;
|
||||
if (loadouts) {
|
||||
for (let loadout of loadouts) {
|
||||
for (let role of loadout.roles) {
|
||||
@@ -97,30 +93,31 @@ export abstract class UnitDatabase {
|
||||
}
|
||||
|
||||
/* Returns a list of all possible types in a database */
|
||||
getTypes(unitFilter?: CallableFunction) {
|
||||
getTypes(unitFilter?: (unit: UnitBlueprint) => boolean) {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
var types: string[] = [];
|
||||
for (let unit in filteredBlueprints) {
|
||||
if (typeof unitFilter === "function" && !unitFilter(filteredBlueprints[unit])) continue;
|
||||
var type = filteredBlueprints[unit].type;
|
||||
for (let unit of filteredBlueprints) {
|
||||
if (typeof unitFilter === "function" && !unitFilter(unit)) continue;
|
||||
var type = unit.type;
|
||||
if (type && type !== "" && !types.includes(type)) types.push(type);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
/* Returns a list of all possible eras in a database */
|
||||
getEras() {
|
||||
getEras(unitFilter?: (unit: UnitBlueprint) => boolean) {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
var eras: string[] = [];
|
||||
for (let unit in filteredBlueprints) {
|
||||
var era = filteredBlueprints[unit].era;
|
||||
for (let unit of filteredBlueprints) {
|
||||
if (typeof unitFilter === "function" && !unitFilter(unit)) continue;
|
||||
var era = unit.era;
|
||||
if (era && era !== "" && !eras.includes(era)) eras.push(era);
|
||||
}
|
||||
return eras;
|
||||
}
|
||||
|
||||
/* Get all blueprints by range */
|
||||
getByRange(range: string) {
|
||||
getByRange(range: string, unitFilter?: (unit: UnitBlueprint) => boolean) {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
var unitswithrange: UnitBlueprint[] = [];
|
||||
var minRange = 0;
|
||||
@@ -137,11 +134,12 @@ export abstract class UnitDatabase {
|
||||
maxRange = 999999;
|
||||
}
|
||||
|
||||
for (let unit in filteredBlueprints) {
|
||||
var engagementRange = filteredBlueprints[unit].engagementRange;
|
||||
for (let unit of filteredBlueprints) {
|
||||
if (typeof unitFilter === "function" && !unitFilter(unit)) continue;
|
||||
var engagementRange = unit.engagementRange;
|
||||
if (engagementRange !== undefined) {
|
||||
if (engagementRange >= minRange && engagementRange < maxRange) {
|
||||
unitswithrange.push(filteredBlueprints[unit]);
|
||||
unitswithrange.push(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,27 +147,29 @@ export abstract class UnitDatabase {
|
||||
}
|
||||
|
||||
/* Get all blueprints by type */
|
||||
getByType(type: string) {
|
||||
getByType(type: string, unitFilter?: (unit: UnitBlueprint) => boolean) {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
var units: UnitBlueprint[] = [];
|
||||
for (let unit in filteredBlueprints) {
|
||||
if (filteredBlueprints[unit].type === type) {
|
||||
units.push(filteredBlueprints[unit]);
|
||||
for (let unit of filteredBlueprints) {
|
||||
if (typeof unitFilter === "function" && !unitFilter(unit)) continue;
|
||||
if (unit.type === type) {
|
||||
units.push(unit);
|
||||
}
|
||||
}
|
||||
return units;
|
||||
}
|
||||
|
||||
/* Get all blueprints by role */
|
||||
getByRole(role: string) {
|
||||
getByRole(role: string, unitFilter?: (unit: UnitBlueprint) => boolean) {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
var units: UnitBlueprint[] = [];
|
||||
for (let unit in filteredBlueprints) {
|
||||
var loadouts = filteredBlueprints[unit].loadouts;
|
||||
for (let unit of filteredBlueprints) {
|
||||
if (typeof unitFilter === "function" && !unitFilter(unit)) continue;
|
||||
var loadouts = unit.loadouts;
|
||||
if (loadouts) {
|
||||
for (let loadout of loadouts) {
|
||||
if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase())) {
|
||||
units.push(filteredBlueprints[unit]);
|
||||
units.push(unit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -224,6 +224,7 @@ export abstract class UnitDatabase {
|
||||
getUnkownUnit(name: string): UnitBlueprint {
|
||||
return {
|
||||
name: name,
|
||||
category: "aircraft",
|
||||
enabled: true,
|
||||
coalition: "neutral",
|
||||
era: "N/A",
|
||||
|
||||
@@ -2,11 +2,10 @@ import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point, Leaf
|
||||
import { getApp } from "../olympusapp";
|
||||
import {
|
||||
enumToCoalition,
|
||||
enumToEmissioNCountermeasure,
|
||||
enumToEmissionCountermeasure,
|
||||
enumToROE,
|
||||
enumToReactionToThreat,
|
||||
enumToState,
|
||||
getUnitDatabaseByCategory,
|
||||
mToFt,
|
||||
msToKnots,
|
||||
rad2deg,
|
||||
@@ -43,10 +42,8 @@ import {
|
||||
JTACSubState,
|
||||
} from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { groundUnitDatabase } from "./databases/groundunitdatabase";
|
||||
import { navyUnitDatabase } from "./databases/navyunitdatabase";
|
||||
import { Weapon } from "../weapon/weapon";
|
||||
import { Ammo, Contact, GeneralSettings, LoadoutBlueprint, ObjectIconOptions, Offset, Radio, TACAN, UnitData } from "../interfaces";
|
||||
import { Ammo, Contact, GeneralSettings, LoadoutBlueprint, ObjectIconOptions, Offset, Radio, TACAN, UnitBlueprint, UnitData } from "../interfaces";
|
||||
import { RangeCircle } from "../map/rangecircle";
|
||||
import { Group } from "./group";
|
||||
import { ContextActionSet } from "./contextactionset";
|
||||
@@ -151,6 +148,9 @@ export abstract class Unit extends CustomMarker {
|
||||
#health: number = 100;
|
||||
|
||||
/* Other members used to draw the unit, mostly ancillary stuff like targets, ranges and so on */
|
||||
#blueprint: UnitBlueprint | null = null;
|
||||
#unitWhenGrouped: string | null = null;
|
||||
#blueprintWhenGrouped: UnitBlueprint | null = null;
|
||||
#group: Group | null = null;
|
||||
#selected: boolean = false;
|
||||
#hidden: boolean = false;
|
||||
@@ -357,11 +357,12 @@ export abstract class Unit extends CustomMarker {
|
||||
this.on("mouseup", (e) => this.#onMouseUp(e));
|
||||
this.on("dblclick", (e) => this.#onDoubleClick(e));
|
||||
this.on("mouseover", () => {
|
||||
if (this.belongsToCommandedCoalition())
|
||||
this.setHighlighted(true);
|
||||
if (this.belongsToCommandedCoalition()) this.setHighlighted(true);
|
||||
});
|
||||
this.on("mouseout", () => this.setHighlighted(false));
|
||||
getApp().getMap().on("zoomend", (e: any) => this.#onZoom(e));
|
||||
getApp()
|
||||
.getMap()
|
||||
.on("zoomend", (e: any) => this.#onZoom(e));
|
||||
|
||||
/* Deselect units if they are hidden */
|
||||
HiddenTypesChangedEvent.on((hiddenTypes) => {
|
||||
@@ -535,7 +536,7 @@ export abstract class Unit extends CustomMarker {
|
||||
this.#reactionToThreat = enumToReactionToThreat(dataExtractor.extractUInt8());
|
||||
break;
|
||||
case DataIndexes.emissionsCountermeasures:
|
||||
this.#emissionsCountermeasures = enumToEmissioNCountermeasure(dataExtractor.extractUInt8());
|
||||
this.#emissionsCountermeasures = enumToEmissionCountermeasure(dataExtractor.extractUInt8());
|
||||
break;
|
||||
case DataIndexes.TACAN:
|
||||
this.#TACAN = dataExtractor.extractTACAN();
|
||||
@@ -591,6 +592,32 @@ export abstract class Unit extends CustomMarker {
|
||||
this.setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
/* If not done already, update the unit blueprint */
|
||||
if (!this.#blueprint && this.#name !== "") {
|
||||
const blueprint = getApp().getUnitsManager().getDatabase().getByName(this.#name);
|
||||
this.#blueprint = blueprint ?? null;
|
||||
/* Refresh the marker */
|
||||
this.remove();
|
||||
this.addTo(getApp().getMap());
|
||||
}
|
||||
|
||||
/* Update the blueprint to use when the unit is grouped */
|
||||
if (this.#name !== "") {
|
||||
let unitWhenGrouped: string | null = this.getBlueprint()?.unitWhenGrouped ?? null;
|
||||
let member = this.getGroupMembers().reduce((prev: Unit | null, unit: Unit, index: number) => {
|
||||
if (unit.getBlueprint()?.unitWhenGrouped != undefined) return unit;
|
||||
return prev;
|
||||
}, null);
|
||||
unitWhenGrouped = member !== null ? (member?.getBlueprint()?.unitWhenGrouped ?? null) : unitWhenGrouped;
|
||||
if (unitWhenGrouped !== this.#unitWhenGrouped) {
|
||||
this.#unitWhenGrouped = unitWhenGrouped;
|
||||
if (unitWhenGrouped) {
|
||||
const blueprint = getApp().getUnitsManager().getDatabase().getByName(unitWhenGrouped);
|
||||
this.#blueprintWhenGrouped = blueprint ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Get unit data collated into an object
|
||||
@@ -648,20 +675,12 @@ export abstract class Unit extends CustomMarker {
|
||||
};
|
||||
}
|
||||
|
||||
/** Get a database of information also in this unit's category
|
||||
*
|
||||
* @returns UnitDatabase
|
||||
*/
|
||||
getDatabase(): UnitDatabase | null {
|
||||
return getUnitDatabaseByCategory(this.getMarkerCategory());
|
||||
}
|
||||
|
||||
/** Set the unit as alive or dead
|
||||
*
|
||||
* @param newAlive (boolean) true = alive, false = dead
|
||||
*/
|
||||
setAlive(newAlive: boolean) {
|
||||
if (newAlive != this.#alive) UnitDeadEvent.dispatch(this)
|
||||
if (newAlive != this.#alive) UnitDeadEvent.dispatch(this);
|
||||
this.#alive = newAlive;
|
||||
}
|
||||
|
||||
@@ -698,7 +717,7 @@ export abstract class Unit extends CustomMarker {
|
||||
this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected);
|
||||
|
||||
/* Trigger events after all (de-)selecting has been done */
|
||||
selected? UnitSelectedEvent.dispatch(this): UnitDeselectedEvent.dispatch(this);
|
||||
selected ? UnitSelectedEvent.dispatch(this) : UnitDeselectedEvent.dispatch(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,12 +802,16 @@ export abstract class Unit extends CustomMarker {
|
||||
return "";
|
||||
}
|
||||
|
||||
getSpawnPoints() {
|
||||
return this.getDatabase()?.getSpawnPointsByName(this.getName());
|
||||
}
|
||||
|
||||
getBlueprint() {
|
||||
return this.getDatabase()?.getByName(this.#name) ?? this.getDatabase()?.getUnkownUnit(this.getName());
|
||||
if (
|
||||
!this.getSelected() &&
|
||||
this.getIsLeader() &&
|
||||
getApp().getMap().getOptions().hideGroupMembers &&
|
||||
getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION &&
|
||||
this.#blueprintWhenGrouped
|
||||
)
|
||||
return this.#blueprintWhenGrouped;
|
||||
else return this.#blueprint;
|
||||
}
|
||||
|
||||
getGroup() {
|
||||
@@ -892,7 +915,7 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
/********************** Icon *************************/
|
||||
createIcon(): void {
|
||||
async createIcon() {
|
||||
/* Set the icon */
|
||||
var icon = new DivIcon({
|
||||
className: "leaflet-unit-icon",
|
||||
@@ -1639,13 +1662,13 @@ export abstract class Unit extends CustomMarker {
|
||||
|
||||
/* Get the acquisition and engagement ranges of the entire group, not for each unit */
|
||||
if (this.getIsLeader()) {
|
||||
var engagementRange = this.getDatabase()?.getByName(this.getName())?.engagementRange ?? 0;
|
||||
var acquisitionRange = this.getDatabase()?.getByName(this.getName())?.acquisitionRange ?? 0;
|
||||
var engagementRange = this.getBlueprint()?.engagementRange ?? 0;
|
||||
var acquisitionRange = this.getBlueprint()?.acquisitionRange ?? 0;
|
||||
|
||||
this.getGroupMembers().forEach((unit: Unit) => {
|
||||
if (unit.getAlive()) {
|
||||
let unitEngagementRange = unit.getDatabase()?.getByName(unit.getName())?.engagementRange ?? 0;
|
||||
let unitAcquisitionRange = unit.getDatabase()?.getByName(unit.getName())?.acquisitionRange ?? 0;
|
||||
let unitEngagementRange = unit.getBlueprint()?.engagementRange ?? 0;
|
||||
let unitAcquisitionRange = unit.getBlueprint()?.acquisitionRange ?? 0;
|
||||
|
||||
if (unitEngagementRange > engagementRange) engagementRange = unitEngagementRange;
|
||||
|
||||
@@ -2062,28 +2085,7 @@ export class GroundUnit extends Unit {
|
||||
}
|
||||
|
||||
getType() {
|
||||
var blueprint = groundUnitDatabase.getByName(this.getName());
|
||||
return blueprint?.type ? blueprint.type : "";
|
||||
}
|
||||
|
||||
/* When a unit is a leader of a group, the map is zoomed out and grouping when zoomed out is enabled, check if the unit should be shown as a specific group. This is used to show a SAM battery instead of the group leader */
|
||||
getBlueprint() {
|
||||
let unitWhenGrouped: string | undefined | null = null;
|
||||
if (
|
||||
!this.getSelected() &&
|
||||
this.getIsLeader() &&
|
||||
getApp().getMap().getOptions().hideGroupMembers &&
|
||||
getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION
|
||||
) {
|
||||
unitWhenGrouped = this.getDatabase()?.getByName(this.getName())?.unitWhenGrouped ?? null;
|
||||
let member = this.getGroupMembers().reduce((prev: Unit | null, unit: Unit, index: number) => {
|
||||
if (unit.getBlueprint()?.unitWhenGrouped != undefined) return unit;
|
||||
return prev;
|
||||
}, null);
|
||||
unitWhenGrouped = member !== null ? member?.getBlueprint()?.unitWhenGrouped : unitWhenGrouped;
|
||||
}
|
||||
if (unitWhenGrouped) return this.getDatabase()?.getByName(unitWhenGrouped) ?? this.getDatabase()?.getUnkownUnit(this.getName());
|
||||
else return this.getDatabase()?.getByName(this.getName()) ?? this.getDatabase()?.getUnkownUnit(this.getName());
|
||||
return this.getBlueprint()?.type ?? "";
|
||||
}
|
||||
|
||||
/* When we zoom past the grouping limit, grouping is enabled and the unit is a leader, we redraw the unit to apply any possible grouped marker */
|
||||
@@ -2193,8 +2195,7 @@ export class NavyUnit extends Unit {
|
||||
}
|
||||
|
||||
getType() {
|
||||
var blueprint = navyUnitDatabase.getByName(this.getName());
|
||||
return blueprint?.type ? blueprint.type : "";
|
||||
return this.getBlueprint()?.type ?? "";
|
||||
}
|
||||
|
||||
getMarkerCategory() {
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
bearingAndDistanceToLatLng,
|
||||
deg2rad,
|
||||
getGroundElevation,
|
||||
getUnitDatabaseByCategory,
|
||||
keyEventWasInInput,
|
||||
latLngToMercator,
|
||||
mToFt,
|
||||
@@ -18,13 +17,9 @@ import {
|
||||
randomUnitBlueprint,
|
||||
} from "../other/utils";
|
||||
import { CoalitionPolygon } from "../map/coalitionarea/coalitionpolygon";
|
||||
import { groundUnitDatabase } from "./databases/groundunitdatabase";
|
||||
import { DELETE_CYCLE_TIME, DELETE_SLOW_THRESHOLD, DataIndexes, GAME_MASTER, IADSDensities, OlympusState } from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { citiesDatabase } from "./databases/citiesdatabase";
|
||||
import { aircraftDatabase } from "./databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "./databases/helicopterdatabase";
|
||||
import { navyUnitDatabase } from "./databases/navyunitdatabase";
|
||||
import { TemporaryUnitMarker } from "../map/markers/temporaryunitmarker";
|
||||
//import { Popup } from "../popups/popup";
|
||||
//import { HotgroupPanel } from "../panels/hotgrouppanel";
|
||||
@@ -40,28 +35,34 @@ import {
|
||||
ContactsUpdatedEvent,
|
||||
SelectedUnitsChangedEvent,
|
||||
SelectionClearedEvent,
|
||||
UnitDatabaseLoadedEvent,
|
||||
UnitDeselectedEvent,
|
||||
UnitSelectedEvent,
|
||||
} from "../events";
|
||||
import { UnitDatabase } from "./databases/unitdatabase";
|
||||
|
||||
/** 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
|
||||
* to avoid client/server and client/client inconsistencies.
|
||||
*/
|
||||
export class UnitsManager {
|
||||
#copiedUnits: UnitData[];
|
||||
#copiedUnits: UnitData[] = [];
|
||||
#deselectionEventDisabled: boolean = false;
|
||||
#requestDetectionUpdate: boolean = false;
|
||||
#selectionEventDisabled: boolean = false;
|
||||
//#slowDeleteDialog!: Dialog;
|
||||
#units: { [ID: number]: Unit };
|
||||
#units: { [ID: number]: Unit } = {};
|
||||
#groups: { [groupName: string]: Group } = {};
|
||||
#unitDataExport!: UnitDataFileExport;
|
||||
#unitDataImport!: UnitDataFileImport;
|
||||
#unitDatabase: UnitDatabase;
|
||||
|
||||
constructor() {
|
||||
this.#copiedUnits = [];
|
||||
this.#units = {};
|
||||
this.#unitDatabase = new UnitDatabase();
|
||||
this.#unitDatabase.load(window.location.href.split("?")[0].replace("vite/", "") + "api/databases/units/aircraftdatabase");
|
||||
this.#unitDatabase.load(window.location.href.split("?")[0].replace("vite/", "") + "api/databases/units/helicopterdatabase");
|
||||
this.#unitDatabase.load(window.location.href.split("?")[0].replace("vite/", "") + "api/databases/units/groundunitdatabase");
|
||||
this.#unitDatabase.load(window.location.href.split("?")[0].replace("vite/", "") + "api/databases/units/navyunitdatabase");
|
||||
|
||||
CommandModeOptionsChangedEvent.on(() => {
|
||||
Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility());
|
||||
@@ -69,8 +70,8 @@ export class UnitsManager {
|
||||
ContactsUpdatedEvent.on(() => {
|
||||
this.#requestDetectionUpdate = true;
|
||||
});
|
||||
UnitSelectedEvent.on((unit) => this.#onUnitDeselection(unit));
|
||||
UnitDeselectedEvent.on((unit) => this.#onUnitSelection(unit));
|
||||
UnitSelectedEvent.on((unit) => this.#onUnitSelection(unit));
|
||||
UnitDeselectedEvent.on((unit) => this.#onUnitDeselection(unit));
|
||||
|
||||
document.addEventListener("copy", () => this.copy());
|
||||
document.addEventListener("keyup", (event) => this.#onKeyUp(event));
|
||||
@@ -1384,40 +1385,44 @@ export class UnitsManager {
|
||||
getApp().getMissionManager().getRemainingSetupTime() < 0 &&
|
||||
getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER;
|
||||
|
||||
if (category === "Aircraft") {
|
||||
if (category === "aircraft") {
|
||||
if (airbase == "" && spawnsRestricted) {
|
||||
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Aircrafts can be air spawned during the SETUP phase only");
|
||||
return false;
|
||||
}
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return points + aircraftDatabase.getSpawnPointsByName(unit.unitType);
|
||||
return 0;
|
||||
// TODO return points + this.#unitIndexedDB.selectBlueprints({from:"Units", where: {name: unit.unitType}});
|
||||
}, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints, callback);
|
||||
} else if (category === "Helicopter") {
|
||||
} else if (category === "helicopter") {
|
||||
if (airbase == "" && spawnsRestricted) {
|
||||
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Helicopters can be air spawned during the SETUP phase only");
|
||||
return false;
|
||||
}
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return points + helicopterDatabase.getSpawnPointsByName(unit.unitType);
|
||||
return 0;
|
||||
//TODO return points + helicopterDatabase.getSpawnPointsByName(unit.unitType);
|
||||
}, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints, callback);
|
||||
} else if (category === "GroundUnit") {
|
||||
} else if (category === "groundunit") {
|
||||
if (spawnsRestricted) {
|
||||
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Ground units can be spawned during the SETUP phase only");
|
||||
return false;
|
||||
}
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType);
|
||||
return 0;
|
||||
//TODOreturn points + groundUnitDatabase.getSpawnPointsByName(unit.unitType);
|
||||
}, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnGroundUnits(units, coalition, country, immediate, spawnPoints, callback);
|
||||
} else if (category === "NavyUnit") {
|
||||
} else if (category === "navyunit") {
|
||||
if (spawnsRestricted) {
|
||||
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Navy units can be spawned during the SETUP phase only");
|
||||
return false;
|
||||
}
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return points + navyUnitDatabase.getSpawnPointsByName(unit.unitType);
|
||||
return 0;
|
||||
//TODOreturn points + navyUnitDatabase.getSpawnPointsByName(unit.unitType);
|
||||
}, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback);
|
||||
}
|
||||
@@ -1432,6 +1437,10 @@ export class UnitsManager {
|
||||
}
|
||||
}
|
||||
|
||||
getDatabase() {
|
||||
return this.#unitDatabase;
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
#onKeyUp(event: KeyboardEvent) {
|
||||
if (!keyEventWasInInput(event)) {
|
||||
|
||||
Reference in New Issue
Block a user