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:
parent
7f5873b5b8
commit
636803b2ac
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,7 @@
|
||||
"@types/turf": "^3.5.32",
|
||||
"buffer": "^6.0.3",
|
||||
"js-sha256": "^0.11.0",
|
||||
"jsstore": "^4.8.2",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-control-mini-map": "^0.4.0",
|
||||
"leaflet-path-drag": "^1.9.5",
|
||||
@ -54,6 +55,8 @@
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript-eslint": "^7.14.1",
|
||||
"vite": "^5.2.0",
|
||||
"vite-plugin-externals": "^0.6.2",
|
||||
"vite-plugin-file": "^1.0.5",
|
||||
"web-audio-peak-meter": "^3.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"backend": {
|
||||
"address": "localhost",
|
||||
"port": 4512
|
||||
"address": "88.99.250.188",
|
||||
"port": 3001
|
||||
},
|
||||
"authentication": {
|
||||
"gameMasterPassword": "4b8823ed9e5c2392ab4a791913bb8ce41956ea32e308b760eefb97536746dd33",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user