mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Completed custom weapon wizard modal
This commit is contained in:
@@ -185,6 +185,7 @@ struct SpawnOptions {
|
|||||||
string skill;
|
string skill;
|
||||||
string liveryID;
|
string liveryID;
|
||||||
double heading;
|
double heading;
|
||||||
|
string payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CloneOptions {
|
struct CloneOptions {
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ string SpawnAircrafts::getString()
|
|||||||
<< "alt = " << spawnOptions[i].location.alt << ", "
|
<< "alt = " << spawnOptions[i].location.alt << ", "
|
||||||
<< "heading = " << spawnOptions[i].heading << ", "
|
<< "heading = " << spawnOptions[i].heading << ", "
|
||||||
<< "loadout = \"" << spawnOptions[i].loadout << "\"" << ", "
|
<< "loadout = \"" << spawnOptions[i].loadout << "\"" << ", "
|
||||||
|
<< "payload = " << spawnOptions[i].payload << ", "
|
||||||
<< "liveryID = " << "\"" << spawnOptions[i].liveryID << "\"" << ", "
|
<< "liveryID = " << "\"" << spawnOptions[i].liveryID << "\"" << ", "
|
||||||
<< "skill = \"" << spawnOptions[i].skill << "\"" << "}, ";
|
<< "skill = \"" << spawnOptions[i].skill << "\"" << "}, ";
|
||||||
}
|
}
|
||||||
@@ -132,6 +133,7 @@ string SpawnHelicopters::getString()
|
|||||||
<< "alt = " << spawnOptions[i].location.alt << ", "
|
<< "alt = " << spawnOptions[i].location.alt << ", "
|
||||||
<< "heading = " << spawnOptions[i].heading << ", "
|
<< "heading = " << spawnOptions[i].heading << ", "
|
||||||
<< "loadout = \"" << spawnOptions[i].loadout << "\"" << ", "
|
<< "loadout = \"" << spawnOptions[i].loadout << "\"" << ", "
|
||||||
|
<< "payload = " << spawnOptions[i].payload << ", "
|
||||||
<< "liveryID = " << "\"" << spawnOptions[i].liveryID << "\"" << ", "
|
<< "liveryID = " << "\"" << spawnOptions[i].liveryID << "\"" << ", "
|
||||||
<< "skill = \"" << spawnOptions[i].skill << "\"" << "}, ";
|
<< "skill = \"" << spawnOptions[i].skill << "\"" << "}, ";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,7 +223,11 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
|||||||
string liveryID = to_string(unit[L"liveryID"]);
|
string liveryID = to_string(unit[L"liveryID"]);
|
||||||
string skill = to_string(unit[L"skill"]);
|
string skill = to_string(unit[L"skill"]);
|
||||||
|
|
||||||
spawnOptions.push_back({ unitType, location, loadout, skill, liveryID, heading });
|
string payload = "nil";
|
||||||
|
if (unit.has_string_field(L"payload"))
|
||||||
|
payload = to_string(unit[L"payload"]);
|
||||||
|
|
||||||
|
spawnOptions.push_back({ unitType, location, loadout, skill, liveryID, heading, payload });
|
||||||
log(username + " spawned a " + coalition + " " + unitType, true);
|
log(username + " spawned a " + coalition + " " + unitType, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ export enum SpawnSubState {
|
|||||||
NO_SUBSTATE = "No substate",
|
NO_SUBSTATE = "No substate",
|
||||||
SPAWN_UNIT = "Unit",
|
SPAWN_UNIT = "Unit",
|
||||||
SPAWN_EFFECT = "Effect",
|
SPAWN_EFFECT = "Effect",
|
||||||
|
LOADOUT_WIZARD = "Loadout wizard"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OptionsSubstate {
|
export enum OptionsSubstate {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,403 +2,405 @@ import { LatLng } from "leaflet";
|
|||||||
import { AudioOptions, Coalition, MapOptions } from "./types/types";
|
import { AudioOptions, Coalition, MapOptions } from "./types/types";
|
||||||
|
|
||||||
export interface OlympusConfig {
|
export interface OlympusConfig {
|
||||||
/* Set by user */
|
/* Set by user */
|
||||||
frontend: {
|
frontend: {
|
||||||
port: number;
|
port: number;
|
||||||
elevationProvider: {
|
elevationProvider: {
|
||||||
provider: string;
|
provider: string;
|
||||||
username: string | null;
|
username: string | null;
|
||||||
password: string | null;
|
password: string | null;
|
||||||
};
|
};
|
||||||
mapLayers: {
|
mapLayers: {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
urlTemplate: string;
|
urlTemplate: string;
|
||||||
minZoom: number;
|
minZoom: number;
|
||||||
maxZoom: number;
|
maxZoom: number;
|
||||||
attribution?: string;
|
attribution?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
mapMirrors: {
|
mapMirrors: {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
/* New with v2.0.0 */
|
||||||
|
customAuthHeaders?: {
|
||||||
|
enabled: boolean;
|
||||||
|
username: string;
|
||||||
|
group: string;
|
||||||
|
};
|
||||||
|
autoconnectWhenLocal?: boolean;
|
||||||
};
|
};
|
||||||
/* New with v2.0.0 */
|
/* New with v2.0.0 */
|
||||||
customAuthHeaders?: {
|
audio?: {
|
||||||
enabled: boolean;
|
SRSPort: number;
|
||||||
username: string;
|
WSPort?: number;
|
||||||
group: string;
|
WSEndpoint?: string;
|
||||||
};
|
};
|
||||||
autoconnectWhenLocal?: boolean;
|
controllers?: [{ type: string; coalition: Coalition; frequency: number; modulation: number; callsign: string }];
|
||||||
};
|
profiles?: { [key: string]: ProfileOptions };
|
||||||
/* New with v2.0.0 */
|
|
||||||
audio?: {
|
|
||||||
SRSPort: number;
|
|
||||||
WSPort?: number;
|
|
||||||
WSEndpoint?: string;
|
|
||||||
};
|
|
||||||
controllers?: [{ type: string; coalition: Coalition; frequency: number; modulation: number; callsign: string }];
|
|
||||||
profiles?: { [key: string]: ProfileOptions };
|
|
||||||
|
|
||||||
/* Set by server */
|
/* Set by server */
|
||||||
local?: boolean;
|
local?: boolean;
|
||||||
authentication?: {
|
authentication?: {
|
||||||
// Only sent when in localhost mode for autologin
|
// Only sent when in localhost mode for autologin
|
||||||
gameMasterPassword: string;
|
gameMasterPassword: string;
|
||||||
blueCommanderPassword: string;
|
blueCommanderPassword: string;
|
||||||
redCommanderPassword: string;
|
redCommanderPassword: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SessionData {
|
export interface SessionData {
|
||||||
radios?: { frequency: number; modulation: number; pan: number }[];
|
radios?: { frequency: number; modulation: number; pan: number }[];
|
||||||
fileSources?: { filename: string; volume: number }[];
|
fileSources?: { filename: string; volume: number }[];
|
||||||
unitSinks?: { ID: number }[];
|
unitSinks?: { ID: number }[];
|
||||||
connections?: any[];
|
connections?: any[];
|
||||||
coalitionAreas?: (
|
coalitionAreas?: (
|
||||||
| { type: "circle"; label: string; latlng: { lat: number; lng: number }; radius: number; coalition: Coalition }
|
| { type: "circle"; label: string; latlng: { lat: number; lng: number }; radius: number; coalition: Coalition }
|
||||||
| { type: "polygon"; label: string; latlngs: { lat: number; lng: number }[]; coalition: Coalition }
|
| { type: "polygon"; label: string; latlngs: { lat: number; lng: number }[]; coalition: Coalition }
|
||||||
)[];
|
)[];
|
||||||
hotgroups?: { [key: string]: number[] };
|
hotgroups?: { [key: string]: number[] };
|
||||||
starredSpawns?: { [key: number]: SpawnRequestTable };
|
starredSpawns?: { [key: number]: SpawnRequestTable };
|
||||||
drawings?: { [key: string]: { visibility: boolean; opacity: number; name: string; guid: string; containers: any; drawings: any } };
|
drawings?: { [key: string]: { visibility: boolean; opacity: number; name: string; guid: string; containers: any; drawings: any } };
|
||||||
navpoints?: { [key: string]: { visibility: boolean; opacity: number; name: string; guid: string; containers: any; drawings: any } };
|
navpoints?: { [key: string]: { visibility: boolean; opacity: number; name: string; guid: string; containers: any; drawings: any } };
|
||||||
mapSource?: { id: string };
|
mapSource?: { id: string };
|
||||||
|
customLoadouts?: { [key: string]: LoadoutBlueprint[] };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProfileOptions {
|
export interface ProfileOptions {
|
||||||
mapOptions?: MapOptions;
|
mapOptions?: MapOptions;
|
||||||
shortcuts?: { [key: string]: ShortcutOptions };
|
shortcuts?: { [key: string]: ShortcutOptions };
|
||||||
audioOptions?: AudioOptions;
|
audioOptions?: AudioOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContextMenuOption {
|
export interface ContextMenuOption {
|
||||||
tooltip: string;
|
tooltip: string;
|
||||||
src: string;
|
src: string;
|
||||||
callback: CallableFunction;
|
callback: CallableFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AirbasesData {
|
export interface AirbasesData {
|
||||||
airbases: { [key: string]: any };
|
airbases: { [key: string]: any };
|
||||||
sessionHash: string;
|
sessionHash: string;
|
||||||
time: number;
|
time: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BullseyesData {
|
export interface BullseyesData {
|
||||||
bullseyes: {
|
bullseyes: {
|
||||||
[key: string]: { latitude: number; longitude: number; coalition: string };
|
[key: string]: { latitude: number; longitude: number; coalition: string };
|
||||||
};
|
};
|
||||||
sessionHash: string;
|
sessionHash: string;
|
||||||
time: number;
|
time: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpotsData {
|
export interface SpotsData {
|
||||||
spots: {
|
spots: {
|
||||||
[key: string]: { active: boolean; type: string; targetPosition: { lat: number; lng: number }; sourceUnitID: number; code?: number };
|
[key: string]: { active: boolean; type: string; targetPosition: { lat: number; lng: number }; sourceUnitID: number; code?: number };
|
||||||
};
|
};
|
||||||
sessionHash: string;
|
sessionHash: string;
|
||||||
time: number;
|
time: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MissionData {
|
export interface MissionData {
|
||||||
mission: {
|
mission: {
|
||||||
theatre: string;
|
theatre: string;
|
||||||
dateAndTime: DateAndTime;
|
dateAndTime: DateAndTime;
|
||||||
commandModeOptions: CommandModeOptions;
|
commandModeOptions: CommandModeOptions;
|
||||||
coalitions: { red: string[]; blue: string[] };
|
coalitions: { red: string[]; blue: string[] };
|
||||||
};
|
};
|
||||||
time: number;
|
time: number;
|
||||||
sessionHash: string;
|
sessionHash: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommandModeOptions {
|
export interface CommandModeOptions {
|
||||||
commandMode: string;
|
commandMode: string;
|
||||||
restrictSpawns: boolean;
|
restrictSpawns: boolean;
|
||||||
restrictToCoalition: boolean;
|
restrictToCoalition: boolean;
|
||||||
setupTime: number;
|
setupTime: number;
|
||||||
spawnPoints: {
|
spawnPoints: {
|
||||||
red: number;
|
red: number;
|
||||||
blue: number;
|
blue: number;
|
||||||
};
|
};
|
||||||
eras: string[];
|
eras: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DateAndTime {
|
export interface DateAndTime {
|
||||||
date: { Year: number; Month: number; Day: number };
|
date: { Year: number; Month: number; Day: number };
|
||||||
time: { h: number; m: number; s: number };
|
time: { h: number; m: number; s: number };
|
||||||
elapsedTime: number;
|
elapsedTime: number;
|
||||||
startTime: number;
|
startTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LogData {
|
export interface LogData {
|
||||||
logs: { [key: string]: string };
|
logs: { [key: string]: string };
|
||||||
sessionHash: string;
|
sessionHash: string;
|
||||||
time: number;
|
time: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerRequestOptions {
|
export interface ServerRequestOptions {
|
||||||
time?: number;
|
time?: number;
|
||||||
commandHash?: string;
|
commandHash?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpawnRequestTable {
|
export interface SpawnRequestTable {
|
||||||
category: string;
|
category: string;
|
||||||
coalition: string;
|
coalition: string;
|
||||||
unit: UnitSpawnTable;
|
unit: UnitSpawnTable;
|
||||||
amount: number;
|
amount: number;
|
||||||
quickAccessName?: string;
|
quickAccessName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EffectRequestTable {
|
export interface EffectRequestTable {
|
||||||
type: string;
|
type: string;
|
||||||
explosionType?: string;
|
explosionType?: string;
|
||||||
smokeColor?: string;
|
smokeColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnitSpawnTable {
|
export interface UnitSpawnTable {
|
||||||
unitType: string;
|
unitType: string;
|
||||||
location: LatLng;
|
location: LatLng;
|
||||||
skill: string;
|
skill: string;
|
||||||
liveryID: string;
|
liveryID: string;
|
||||||
altitude?: number;
|
altitude?: number;
|
||||||
loadout?: string;
|
loadout?: string;
|
||||||
heading?: number;
|
heading?: number;
|
||||||
|
payload?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ObjectIconOptions {
|
export interface ObjectIconOptions {
|
||||||
showState: boolean;
|
showState: boolean;
|
||||||
showVvi: boolean;
|
showVvi: boolean;
|
||||||
showHealth: boolean;
|
showHealth: boolean;
|
||||||
showHotgroup: boolean;
|
showHotgroup: boolean;
|
||||||
showUnitIcon: boolean;
|
showUnitIcon: boolean;
|
||||||
showShortLabel: boolean;
|
showShortLabel: boolean;
|
||||||
showFuel: boolean;
|
showFuel: boolean;
|
||||||
showAmmo: boolean;
|
showAmmo: boolean;
|
||||||
showSummary: boolean;
|
showSummary: boolean;
|
||||||
showCallsign: boolean;
|
showCallsign: boolean;
|
||||||
rotateToHeading: boolean;
|
rotateToHeading: boolean;
|
||||||
showCluster: boolean;
|
showCluster: boolean;
|
||||||
showAlarmState: boolean;
|
showAlarmState: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GeneralSettings {
|
export interface GeneralSettings {
|
||||||
prohibitJettison: boolean;
|
prohibitJettison: boolean;
|
||||||
prohibitAA: boolean;
|
prohibitAA: boolean;
|
||||||
prohibitAG: boolean;
|
prohibitAG: boolean;
|
||||||
prohibitAfterburner: boolean;
|
prohibitAfterburner: boolean;
|
||||||
prohibitAirWpn: boolean;
|
prohibitAirWpn: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TACAN {
|
export interface TACAN {
|
||||||
isOn: boolean;
|
isOn: boolean;
|
||||||
channel: number;
|
channel: number;
|
||||||
XY: string;
|
XY: string;
|
||||||
callsign: string;
|
callsign: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Radio {
|
export interface Radio {
|
||||||
frequency: number;
|
frequency: number;
|
||||||
callsign: number;
|
callsign: number;
|
||||||
callsignNumber: number;
|
callsignNumber: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Ammo {
|
export interface Ammo {
|
||||||
quantity: number;
|
quantity: number;
|
||||||
name: string;
|
name: string;
|
||||||
guidance: number;
|
guidance: number;
|
||||||
category: number;
|
category: number;
|
||||||
missileCategory: number;
|
missileCategory: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Contact {
|
export interface Contact {
|
||||||
ID: number;
|
ID: number;
|
||||||
detectionMethod: number;
|
detectionMethod: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Offset {
|
export interface Offset {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
z: number;
|
z: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DrawingArgument {
|
export interface DrawingArgument {
|
||||||
argument: number;
|
argument: number;
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnitData {
|
export interface UnitData {
|
||||||
category: string;
|
category: string;
|
||||||
markerCategory: string;
|
markerCategory: string;
|
||||||
ID: number;
|
ID: number;
|
||||||
alive: boolean;
|
alive: boolean;
|
||||||
alarmState: AlarmState;
|
alarmState: AlarmState;
|
||||||
human: boolean;
|
human: boolean;
|
||||||
controlled: boolean;
|
controlled: boolean;
|
||||||
coalition: string;
|
coalition: string;
|
||||||
country: number;
|
country: number;
|
||||||
name: string;
|
name: string;
|
||||||
unitName: string;
|
unitName: string;
|
||||||
callsign: string;
|
callsign: string;
|
||||||
unitID: number;
|
unitID: number;
|
||||||
groupID: number;
|
groupID: number;
|
||||||
groupName: string;
|
groupName: string;
|
||||||
state: string;
|
state: string;
|
||||||
task: string;
|
task: string;
|
||||||
hasTask: boolean;
|
hasTask: boolean;
|
||||||
position: LatLng;
|
position: LatLng;
|
||||||
speed: number;
|
speed: number;
|
||||||
horizontalVelocity: number;
|
horizontalVelocity: number;
|
||||||
verticalVelocity: number;
|
verticalVelocity: number;
|
||||||
heading: number;
|
heading: number;
|
||||||
track: number;
|
track: number;
|
||||||
isActiveTanker: boolean;
|
isActiveTanker: boolean;
|
||||||
isActiveAWACS: boolean;
|
isActiveAWACS: boolean;
|
||||||
onOff: boolean;
|
onOff: boolean;
|
||||||
followRoads: boolean;
|
followRoads: boolean;
|
||||||
fuel: number;
|
fuel: number;
|
||||||
desiredSpeed: number;
|
desiredSpeed: number;
|
||||||
desiredSpeedType: string;
|
desiredSpeedType: string;
|
||||||
desiredAltitude: number;
|
desiredAltitude: number;
|
||||||
desiredAltitudeType: string;
|
desiredAltitudeType: string;
|
||||||
leaderID: number;
|
leaderID: number;
|
||||||
formationOffset: Offset;
|
formationOffset: Offset;
|
||||||
targetID: number;
|
targetID: number;
|
||||||
targetPosition: LatLng;
|
targetPosition: LatLng;
|
||||||
ROE: string;
|
ROE: string;
|
||||||
reactionToThreat: string;
|
reactionToThreat: string;
|
||||||
emissionsCountermeasures: string;
|
emissionsCountermeasures: string;
|
||||||
TACAN: TACAN;
|
TACAN: TACAN;
|
||||||
radio: Radio;
|
radio: Radio;
|
||||||
generalSettings: GeneralSettings;
|
generalSettings: GeneralSettings;
|
||||||
ammo: Ammo[];
|
ammo: Ammo[];
|
||||||
contacts: Contact[];
|
contacts: Contact[];
|
||||||
activePath: LatLng[];
|
activePath: LatLng[];
|
||||||
isLeader: boolean;
|
isLeader: boolean;
|
||||||
operateAs: string;
|
operateAs: string;
|
||||||
shotsScatter: number;
|
shotsScatter: number;
|
||||||
shotsIntensity: number;
|
shotsIntensity: number;
|
||||||
health: number;
|
health: number;
|
||||||
racetrackLength: number;
|
racetrackLength: number;
|
||||||
racetrackAnchor: LatLng;
|
racetrackAnchor: LatLng;
|
||||||
racetrackBearing: number;
|
racetrackBearing: number;
|
||||||
timeToNextTasking: number;
|
timeToNextTasking: number;
|
||||||
barrelHeight: number;
|
barrelHeight: number;
|
||||||
muzzleVelocity: number;
|
muzzleVelocity: number;
|
||||||
aimTime: number;
|
aimTime: number;
|
||||||
shotsToFire: number;
|
shotsToFire: number;
|
||||||
shotsBaseInterval: number;
|
shotsBaseInterval: number;
|
||||||
shotsBaseScatter: number;
|
shotsBaseScatter: number;
|
||||||
engagementRange: number;
|
engagementRange: number;
|
||||||
targetingRange: number;
|
targetingRange: number;
|
||||||
aimMethodRange: number;
|
aimMethodRange: number;
|
||||||
acquisitionRange: number;
|
acquisitionRange: number;
|
||||||
airborne: boolean;
|
airborne: boolean;
|
||||||
cargoWeight: number;
|
cargoWeight: number;
|
||||||
drawingArguments: DrawingArgument[];
|
drawingArguments: DrawingArgument[];
|
||||||
customString: string;
|
customString: string;
|
||||||
customInteger: number;
|
customInteger: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadoutItemBlueprint {
|
export interface LoadoutItemBlueprint {
|
||||||
name: string;
|
name: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
type: string;
|
|
||||||
effectiveAgainst?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadoutBlueprint {
|
export interface LoadoutBlueprint {
|
||||||
fuel: number;
|
items: LoadoutItemBlueprint[];
|
||||||
items: LoadoutItemBlueprint[];
|
roles: string[];
|
||||||
roles: string[];
|
code: string;
|
||||||
code: string;
|
name: string;
|
||||||
name: string;
|
enabled: boolean;
|
||||||
enabled: boolean;
|
isCustom?: boolean;
|
||||||
|
persistent?: boolean;
|
||||||
|
payload?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnitBlueprint {
|
export interface UnitBlueprint {
|
||||||
name: string;
|
name: string;
|
||||||
category: string;
|
category: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
coalition: string;
|
coalition: string;
|
||||||
era: string;
|
era: string;
|
||||||
label: string;
|
label: string;
|
||||||
shortLabel: string;
|
shortLabel: string;
|
||||||
roles?: string[];
|
roles?: string[];
|
||||||
type?: string;
|
type?: string;
|
||||||
loadouts?: LoadoutBlueprint[];
|
loadouts?: LoadoutBlueprint[];
|
||||||
acceptedPayloads?: { [key: string]: { clsids: string; name: string, weight: number }[] };
|
acceptedPayloads?: { [key: string]: { clsid: string; name: string; weight: number }[] };
|
||||||
filename?: string;
|
filename?: string;
|
||||||
liveries?: { [key: string]: { name: string; countries: string[] } };
|
liveries?: { [key: string]: { name: string; countries: string[] } };
|
||||||
cost?: number;
|
cost?: number;
|
||||||
barrelHeight?: number;
|
barrelHeight?: number;
|
||||||
muzzleVelocity?: number;
|
muzzleVelocity?: number;
|
||||||
aimTime?: number;
|
aimTime?: number;
|
||||||
shotsToFire?: number;
|
shotsToFire?: number;
|
||||||
shotsBaseInterval?: number;
|
shotsBaseInterval?: number;
|
||||||
shotsBaseScatter?: number;
|
shotsBaseScatter?: number;
|
||||||
description?: string;
|
description?: string;
|
||||||
abilities?: string;
|
abilities?: string;
|
||||||
tags?: string;
|
tags?: string;
|
||||||
acquisitionRange?: number;
|
acquisitionRange?: number;
|
||||||
engagementRange?: number;
|
engagementRange?: number;
|
||||||
targetingRange?: number;
|
targetingRange?: number;
|
||||||
aimMethodRange?: number;
|
aimMethodRange?: number;
|
||||||
alertnessTimeConstant?: number;
|
alertnessTimeConstant?: number;
|
||||||
canTargetPoint?: boolean;
|
canTargetPoint?: boolean;
|
||||||
canRearm?: boolean;
|
canRearm?: boolean;
|
||||||
canAAA?: boolean;
|
canAAA?: boolean;
|
||||||
indirectFire?: boolean;
|
indirectFire?: boolean;
|
||||||
markerFile?: string;
|
markerFile?: string;
|
||||||
unitWhenGrouped?: string;
|
unitWhenGrouped?: string;
|
||||||
mainRole?: string;
|
mainRole?: string;
|
||||||
length?: number;
|
length?: number;
|
||||||
carrierFilename?: string;
|
carrierFilename?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AirbaseOptions {
|
export interface AirbaseOptions {
|
||||||
name: string;
|
name: string;
|
||||||
position: L.LatLng;
|
position: L.LatLng;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AirbaseChartData {
|
export interface AirbaseChartData {
|
||||||
elevation: string;
|
elevation: string;
|
||||||
ICAO: string;
|
ICAO: string;
|
||||||
TACAN: string;
|
TACAN: string;
|
||||||
runways: AirbaseChartRunwayData[];
|
runways: AirbaseChartRunwayData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AirbaseChartRunwayHeadingData {
|
export interface AirbaseChartRunwayHeadingData {
|
||||||
[index: string]: {
|
[index: string]: {
|
||||||
magHeading: string;
|
magHeading: string;
|
||||||
ILS: string;
|
ILS: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AirbaseChartRunwayData {
|
export interface AirbaseChartRunwayData {
|
||||||
headings: AirbaseChartRunwayHeadingData[];
|
headings: AirbaseChartRunwayHeadingData[];
|
||||||
length: string;
|
length: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShortcutOptions {
|
export interface ShortcutOptions {
|
||||||
label: string;
|
label: string;
|
||||||
keyUpCallback: (e: KeyboardEvent) => void;
|
keyUpCallback: (e: KeyboardEvent) => void;
|
||||||
keyDownCallback?: (e: KeyboardEvent) => void;
|
keyDownCallback?: (e: KeyboardEvent) => void;
|
||||||
code: string;
|
code: string;
|
||||||
altKey?: boolean;
|
altKey?: boolean;
|
||||||
ctrlKey?: boolean;
|
ctrlKey?: boolean;
|
||||||
shiftKey?: boolean;
|
shiftKey?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerStatus {
|
export interface ServerStatus {
|
||||||
frameRate: number;
|
frameRate: number;
|
||||||
load: number;
|
load: number;
|
||||||
elapsedTime: number;
|
elapsedTime: number;
|
||||||
missionTime: DateAndTime["time"];
|
missionTime: DateAndTime["time"];
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
paused: boolean;
|
paused: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DrawingPoint = {
|
export type DrawingPoint = {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PolygonPoints = DrawingPoint[] | DrawingPoint;
|
export type PolygonPoints = DrawingPoint[] | DrawingPoint;
|
||||||
@@ -406,36 +408,36 @@ export type PolygonPoints = DrawingPoint[] | DrawingPoint;
|
|||||||
export type DrawingPrimitiveType = "TextBox" | "Polygon" | "Line" | "Icon";
|
export type DrawingPrimitiveType = "TextBox" | "Polygon" | "Line" | "Icon";
|
||||||
|
|
||||||
export interface Drawing {
|
export interface Drawing {
|
||||||
name: string;
|
name: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
mapX: number;
|
mapX: number;
|
||||||
mapY: number;
|
mapY: number;
|
||||||
layerName: string;
|
layerName: string;
|
||||||
layer: string;
|
layer: string;
|
||||||
primitiveType: DrawingPrimitiveType;
|
primitiveType: DrawingPrimitiveType;
|
||||||
colorString: string;
|
colorString: string;
|
||||||
fillColorString?: string;
|
fillColorString?: string;
|
||||||
borderThickness?: number;
|
borderThickness?: number;
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
font?: string;
|
font?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
angle?: number;
|
angle?: number;
|
||||||
radius?: number;
|
radius?: number;
|
||||||
points?: PolygonPoints;
|
points?: PolygonPoints;
|
||||||
style?: string;
|
style?: string;
|
||||||
polygonMode?: string;
|
polygonMode?: string;
|
||||||
thickness?: number;
|
thickness?: number;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
closed?: boolean;
|
closed?: boolean;
|
||||||
lineMode?: string;
|
lineMode?: string;
|
||||||
hiddenOnPlanner?: boolean;
|
hiddenOnPlanner?: boolean;
|
||||||
file?: string;
|
file?: string;
|
||||||
scale?: number;
|
scale?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AlarmState {
|
export enum AlarmState {
|
||||||
RED = 'red',
|
RED = "red",
|
||||||
GREEN = 'green',
|
GREEN = "green",
|
||||||
AUTO = 'auto'
|
AUTO = "auto",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -310,11 +310,13 @@ export class OlympusApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setState(state: OlympusState, subState: OlympusSubState = NO_SUBSTATE) {
|
setState(state: OlympusState, subState: OlympusSubState = NO_SUBSTATE) {
|
||||||
|
const previousState = this.#state;
|
||||||
|
const previousSubState = this.#subState;
|
||||||
this.#state = state;
|
this.#state = state;
|
||||||
this.#subState = subState;
|
this.#subState = subState;
|
||||||
|
|
||||||
console.log(`App state set to ${state}, substate ${subState}`);
|
console.log(`App state set to ${state}, substate ${subState}`);
|
||||||
AppStateChangedEvent.dispatch(state, subState);
|
AppStateChangedEvent.dispatch(state, subState, previousState, previousSubState);
|
||||||
}
|
}
|
||||||
|
|
||||||
getState() {
|
getState() {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
AudioSinksChangedEvent,
|
AudioSinksChangedEvent,
|
||||||
AudioSourcesChangedEvent,
|
AudioSourcesChangedEvent,
|
||||||
CoalitionAreasChangedEvent,
|
CoalitionAreasChangedEvent,
|
||||||
|
CustomLoadoutsUpdatedEvent,
|
||||||
DrawingsUpdatedEvent,
|
DrawingsUpdatedEvent,
|
||||||
HotgroupsChangedEvent,
|
HotgroupsChangedEvent,
|
||||||
MapSourceChangedEvent,
|
MapSourceChangedEvent,
|
||||||
@@ -16,7 +17,7 @@ import {
|
|||||||
SessionDataSavedEvent,
|
SessionDataSavedEvent,
|
||||||
StarredSpawnsChangedEvent,
|
StarredSpawnsChangedEvent,
|
||||||
} from "./events";
|
} from "./events";
|
||||||
import { SessionData } from "./interfaces";
|
import { LoadoutBlueprint, SessionData } from "./interfaces";
|
||||||
import { CoalitionCircle } from "./map/coalitionarea/coalitioncircle";
|
import { CoalitionCircle } from "./map/coalitionarea/coalitioncircle";
|
||||||
import { CoalitionPolygon } from "./map/coalitionarea/coalitionpolygon";
|
import { CoalitionPolygon } from "./map/coalitionarea/coalitionpolygon";
|
||||||
import { getApp } from "./olympusapp";
|
import { getApp } from "./olympusapp";
|
||||||
@@ -124,7 +125,7 @@ export class SessionDataManager {
|
|||||||
HotgroupsChangedEvent.on((hotgroups) => {
|
HotgroupsChangedEvent.on((hotgroups) => {
|
||||||
this.#sessionData.hotgroups = {};
|
this.#sessionData.hotgroups = {};
|
||||||
Object.keys(hotgroups).forEach((hotgroup) => {
|
Object.keys(hotgroups).forEach((hotgroup) => {
|
||||||
(this.#sessionData.hotgroups as { [key: string]: number[] })[hotgroup] = hotgroups[hotgroup].map((unit) => unit.ID);
|
(this.#sessionData.hotgroups as { [key: string]: number[] })[hotgroup] = hotgroups[parseInt(hotgroup)].map((unit) => unit.ID);
|
||||||
});
|
});
|
||||||
this.#saveSessionData();
|
this.#saveSessionData();
|
||||||
});
|
});
|
||||||
@@ -146,6 +147,16 @@ export class SessionDataManager {
|
|||||||
this.#sessionData.mapSource = { id: source };
|
this.#sessionData.mapSource = { id: source };
|
||||||
this.#saveSessionData();
|
this.#saveSessionData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CustomLoadoutsUpdatedEvent.on((unitName, loadout) => {
|
||||||
|
// If the loadout is of type isPersistent, update the session data
|
||||||
|
if (loadout.persistent) {
|
||||||
|
if (!this.#sessionData.customLoadouts) this.#sessionData.customLoadouts = {};
|
||||||
|
if (!this.#sessionData.customLoadouts[unitName]) this.#sessionData.customLoadouts[unitName] = [];
|
||||||
|
this.#sessionData.customLoadouts[unitName].push({...loadout});
|
||||||
|
}
|
||||||
|
this.#saveSessionData();
|
||||||
|
});
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ export function Modal(props: {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
children?: JSX.Element | JSX.Element[];
|
children?: JSX.Element | JSX.Element[];
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: "sm" | "md" | "lg" | "full";
|
size?: "sm" | "md" | "lg" | "full" | "tall";
|
||||||
disableClose?: boolean;
|
disableClose?: boolean;
|
||||||
|
onClose?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [splash, setSplash] = useState(Math.ceil(Math.random() * 7));
|
const [splash, setSplash] = useState(Math.ceil(Math.random() * 7));
|
||||||
|
|
||||||
@@ -54,6 +55,14 @@ export function Modal(props: {
|
|||||||
`
|
`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
|
${
|
||||||
|
props.size === "tall"
|
||||||
|
? `
|
||||||
|
h-[80%] w-[800px]
|
||||||
|
max-md:h-full max-md:w-full
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
${props.size === "full" ? "h-full w-full" : ""}
|
${props.size === "full" ? "h-full w-full" : ""}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
@@ -90,7 +99,7 @@ export function Modal(props: {
|
|||||||
>
|
>
|
||||||
<FaXmark
|
<FaXmark
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
getApp().setState(OlympusState.IDLE);
|
props.onClose ? props.onClose() : getApp().setState(OlympusState.IDLE);
|
||||||
}}
|
}}
|
||||||
/>{" "}
|
/>{" "}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
160
frontend/react/src/ui/modals/loadoutwizardmodal.tsx
Normal file
160
frontend/react/src/ui/modals/loadoutwizardmodal.tsx
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Modal } from "./components/modal";
|
||||||
|
import { FaMagic, FaStar } from "react-icons/fa";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { getApp } from "../../olympusapp";
|
||||||
|
import { NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||||
|
import { AppStateChangedEvent, CustomLoadoutsUpdatedEvent, SetLoadoutWizardBlueprintEvent } from "../../events";
|
||||||
|
import { WeaponsWizard } from "../panels/components/weaponswizard";
|
||||||
|
import { LoadoutBlueprint, LoadoutItemBlueprint, UnitBlueprint } from "../../interfaces";
|
||||||
|
import { OlToggle } from "../components/oltoggle";
|
||||||
|
|
||||||
|
export function LoadoutWizardModal(props: { open: boolean }) {
|
||||||
|
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||||
|
const [appSubState, setAppSubState] = useState(NO_SUBSTATE);
|
||||||
|
const [previousState, setPreviousState] = useState(OlympusState.NOT_INITIALIZED);
|
||||||
|
const [previousSubState, setPreviousSubState] = useState(NO_SUBSTATE);
|
||||||
|
const [blueprint, setBlueprint] = useState(null as UnitBlueprint | null);
|
||||||
|
const [isPersistent, setIsPersistent] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
AppStateChangedEvent.on((appState, appSubState, previousState, previousSubState) => {
|
||||||
|
setAppState(appState);
|
||||||
|
setAppSubState(appSubState);
|
||||||
|
setPreviousState(previousState);
|
||||||
|
setPreviousSubState(previousSubState);
|
||||||
|
});
|
||||||
|
SetLoadoutWizardBlueprintEvent.on((blueprint) => {
|
||||||
|
setBlueprint(blueprint);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Clear blueprint when modal is closed
|
||||||
|
if (!props.open) {
|
||||||
|
setBlueprint(null);
|
||||||
|
}
|
||||||
|
}, [props.open]);
|
||||||
|
|
||||||
|
const [selectedWeapons, setSelectedWeapons] = useState({} as { [key: string]: { clsid: string; name: string; weight: number } });
|
||||||
|
const [loadoutName, setLoadoutName] = useState("New loadout");
|
||||||
|
const [loadoutRole, setLoadoutRole] = useState("Custom");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedWeapons({});
|
||||||
|
}, [props.open]);
|
||||||
|
|
||||||
|
// If "New Loadout" already exists in the blueprint loadouts, append a number to make it unique
|
||||||
|
useEffect(() => {
|
||||||
|
if (!blueprint) return;
|
||||||
|
let name = "New loadout";
|
||||||
|
let counter = 1;
|
||||||
|
const existingLoadoutNames = blueprint.loadouts?.map((loadout) => loadout.name) || [];
|
||||||
|
|
||||||
|
while (existingLoadoutNames.includes(name)) {
|
||||||
|
name = `New loadout ${counter}`;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadoutName(name);
|
||||||
|
}, [blueprint]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={props.open} size={"tall"} onClose={() => getApp().setState(previousState, previousSubState)}>
|
||||||
|
<div className="flex gap-4 text-xl text-white">
|
||||||
|
<FaMagic
|
||||||
|
className={`
|
||||||
|
my-auto text-4xl text-gray-300
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
<div className="my-auto">Loadout wizard</div>
|
||||||
|
</div>
|
||||||
|
<WeaponsWizard
|
||||||
|
selectedWeapons={selectedWeapons}
|
||||||
|
setSelectedWeapons={setSelectedWeapons}
|
||||||
|
weaponsByPylon={blueprint?.acceptedPayloads ?? {}}
|
||||||
|
loadoutName={loadoutName}
|
||||||
|
setLoadoutName={setLoadoutName}
|
||||||
|
loadoutRole={loadoutRole}
|
||||||
|
setLoadoutRole={setLoadoutRole}
|
||||||
|
/>
|
||||||
|
<div className="mt-auto flex justify-between">
|
||||||
|
<div className="flex gap-2 text-gray-200">
|
||||||
|
<FaStar className={`my-auto text-2xl text-gray-200`}/>
|
||||||
|
<div className={`my-auto mr-auto`}>Keep for the rest of the session</div>
|
||||||
|
<OlToggle toggled={isPersistent} onClick={() => setIsPersistent(!isPersistent)} />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
// Add a new loadout to the blueprint if it doesn't exist already
|
||||||
|
if (blueprint) {
|
||||||
|
const items: LoadoutItemBlueprint[] = [];
|
||||||
|
for (const pylon in selectedWeapons) {
|
||||||
|
const weapon = selectedWeapons[pylon];
|
||||||
|
items.push({
|
||||||
|
name: weapon.name,
|
||||||
|
quantity: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group the weapon items and sum their quantities if there are duplicates
|
||||||
|
const groupedItems: LoadoutItemBlueprint[] = [];
|
||||||
|
const itemMap: { [key: string]: LoadoutItemBlueprint } = {};
|
||||||
|
for (const item of items) {
|
||||||
|
if (itemMap[item.name]) {
|
||||||
|
itemMap[item.name].quantity += item.quantity;
|
||||||
|
} else {
|
||||||
|
itemMap[item.name] = { ...item };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const itemName in itemMap) {
|
||||||
|
groupedItems.push(itemMap[itemName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble the loadout payload section as a stringified lua table containing the payload number as key and the clsid as values
|
||||||
|
// This must already be lua compatible
|
||||||
|
let payloadLuaTable = "{pylons = {";
|
||||||
|
for (const pylon in selectedWeapons) {
|
||||||
|
const weapon = selectedWeapons[pylon];
|
||||||
|
if (weapon) payloadLuaTable += `[${pylon}] = {CLSID = "${weapon.clsid}"},`;
|
||||||
|
}
|
||||||
|
payloadLuaTable += "}, fuel = 999999, flare=60, chaff=60, gun=100, ammo_type = 1}";
|
||||||
|
|
||||||
|
const newLoadout: LoadoutBlueprint = {
|
||||||
|
items: groupedItems,
|
||||||
|
roles: [loadoutRole],
|
||||||
|
code: "",
|
||||||
|
name: loadoutName,
|
||||||
|
enabled: true,
|
||||||
|
isCustom: true,
|
||||||
|
persistent: isPersistent,
|
||||||
|
payload: payloadLuaTable,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!blueprint.loadouts) {
|
||||||
|
blueprint.loadouts = [];
|
||||||
|
}
|
||||||
|
blueprint.loadouts.push(newLoadout);
|
||||||
|
CustomLoadoutsUpdatedEvent.dispatch(blueprint.name, newLoadout);
|
||||||
|
}
|
||||||
|
getApp().setState(previousState, previousSubState);
|
||||||
|
}}
|
||||||
|
className={`
|
||||||
|
mb-2 me-2 flex content-center items-center gap-2
|
||||||
|
rounded-sm 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
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
<FontAwesomeIcon className={`my-auto`} icon={faArrowRight} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,20 +1,38 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { OlDropdown, OlDropdownItem } from "../../components/oldropdown";
|
|
||||||
import { FaArrowsRotate, FaTrash, FaXmark } from "react-icons/fa6";
|
import { FaArrowsRotate, FaTrash, FaXmark } from "react-icons/fa6";
|
||||||
import { OlSearchBar } from "../../components/olsearchbar";
|
import { OlSearchBar } from "../../components/olsearchbar";
|
||||||
import { OlCheckbox } from "../../components/olcheckbox";
|
|
||||||
import { OlToggle } from "../../components/oltoggle";
|
import { OlToggle } from "../../components/oltoggle";
|
||||||
export function WeaponsWizard(props: {
|
export function WeaponsWizard(props: {
|
||||||
selectedWeapons: { [key: string]: { clsids: string; name: string; weight: number } };
|
selectedWeapons: { [key: string]: { clsid: string; name: string; weight: number } };
|
||||||
setSelectedWeapons: (weapons: { [key: string]: { clsids: string; name: string; weight: number } }) => void;
|
setSelectedWeapons: (weapons: { [key: string]: { clsid: string; name: string; weight: number } }) => void;
|
||||||
weaponsByPylon: { [key: string]: { clsids: string; name: string; weight: number }[] };
|
weaponsByPylon: { [key: string]: { clsid: string; name: string; weight: number }[] };
|
||||||
|
loadoutName: string;
|
||||||
|
setLoadoutName: (name: string) => void;
|
||||||
|
loadoutRole: string;
|
||||||
|
setLoadoutRole: (role: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [selectedPylons, setSelectedPylons] = useState<string[]>([]);
|
const [selectedPylons, setSelectedPylons] = useState<string[]>([]);
|
||||||
const [autofillPylons, setAutofillPylons] = useState(false);
|
const [autofillPylons, setAutofillPylons] = useState(false);
|
||||||
|
const [fillEmptyOnly, setFillEmptyOnly] = useState(true);
|
||||||
|
const [weaponLetters, setWeaponLetters] = useState<{ [key: string]: string }>({}); // Letter to weapon name mapping
|
||||||
|
const [hoveredWeapon, setHoveredWeapon] = useState<string>("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// If autofill is enabled, clear selected pylons
|
||||||
|
if (autofillPylons) {
|
||||||
|
setSelectedPylons([]);
|
||||||
|
}
|
||||||
|
}, [autofillPylons]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Clear search text when weaponsByPylon changes
|
||||||
|
setSearchText("");
|
||||||
|
setSelectedPylons([]);
|
||||||
|
}, [props.weaponsByPylon]);
|
||||||
|
|
||||||
// Find the weapons that are availabile in all the selected pylons, meaning the intersection of the weapons in each pylon
|
// Find the weapons that are availabile in all the selected pylons, meaning the intersection of the weapons in each pylon
|
||||||
let availableWeapons: { clsids: string; name: string; weight: number }[] = [];
|
let availableWeapons: { clsid: string; name: string; weight: number }[] = [];
|
||||||
if (autofillPylons) {
|
if (autofillPylons) {
|
||||||
// If autofill is enabled, show all weapons
|
// If autofill is enabled, show all weapons
|
||||||
availableWeapons = Object.values(props.weaponsByPylon).flat();
|
availableWeapons = Object.values(props.weaponsByPylon).flat();
|
||||||
@@ -39,91 +57,266 @@ export function WeaponsWizard(props: {
|
|||||||
availableWeapons = availableWeapons.filter((weapon) => weapon.name.toLowerCase().includes(searchText.toLowerCase()));
|
availableWeapons = availableWeapons.filter((weapon) => weapon.name.toLowerCase().includes(searchText.toLowerCase()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If autofill is enabled and fillEmptyOnly is enabled, remove weapons that have no compatible empty pylons
|
||||||
|
if (autofillPylons && fillEmptyOnly) {
|
||||||
|
availableWeapons = availableWeapons.filter((weapon) => {
|
||||||
|
// Check if there is at least one pylon that is compatible with this weapon and is empty
|
||||||
|
return Object.keys(props.weaponsByPylon).some((pylon) => {
|
||||||
|
const weaponsInPylon = props.weaponsByPylon[pylon];
|
||||||
|
return weaponsInPylon.some((w) => w.name === weapon.name) && !props.selectedWeapons[pylon];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign a letter to each indiviual type of weapon selected in selectedWeapons for display in the pylon selection
|
||||||
|
// Find the first unused letter
|
||||||
|
Object.values(props.selectedWeapons).forEach((weapon) => {
|
||||||
|
if (Object.entries(weaponLetters).findIndex(([letter, name]) => name === weapon.name) === -1) {
|
||||||
|
// Find the first unused letter starting from A
|
||||||
|
let currentLetter = "A";
|
||||||
|
while (weaponLetters[currentLetter]) {
|
||||||
|
currentLetter = String.fromCharCode(currentLetter.charCodeAt(0) + 1);
|
||||||
|
}
|
||||||
|
weaponLetters[currentLetter] = weapon.name;
|
||||||
|
currentLetter = String.fromCharCode(currentLetter.charCodeAt(0) + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove letters for weapons that are no longer selected
|
||||||
|
Object.entries(weaponLetters).forEach(([letter, name]) => {
|
||||||
|
if (Object.values(props.selectedWeapons).findIndex((weapon) => weapon.name === name) === -1) {
|
||||||
|
delete weaponLetters[letter];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (JSON.stringify(weaponLetters) !== JSON.stringify(weaponLetters)) setWeaponLetters({ ...weaponLetters });
|
||||||
|
|
||||||
|
// List of very bright and distinct colors
|
||||||
|
const colors = {
|
||||||
|
A: "#FF5733",
|
||||||
|
B: "#33FF57",
|
||||||
|
C: "#3357FF",
|
||||||
|
D: "#F333FF",
|
||||||
|
E: "#33FFF5",
|
||||||
|
F: "#F5FF33",
|
||||||
|
G: "#FF33A8",
|
||||||
|
H: "#A833FF",
|
||||||
|
I: "#33FFA8",
|
||||||
|
J: "#FFA833",
|
||||||
|
K: "#33A8FF",
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-6 text-white" onMouseEnter={() => setHoveredWeapon("")}>
|
||||||
<div className="flex justify-center">
|
<div className="flex flex-col gap-2">
|
||||||
{Object.keys(props.weaponsByPylon).map((pylon) => (
|
<div className="flex justify-between">
|
||||||
<div key={pylon} className={``}>
|
<div className="my-auto font-semibold">Loadout Name</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={props.loadoutName}
|
||||||
|
onChange={(e) => props.setLoadoutName(e.target.value)}
|
||||||
|
className={`
|
||||||
|
rounded-md border border-gray-300 bg-gray-800 p-2
|
||||||
|
text-sm text-white
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div className="my-auto font-semibold">Loadout Role</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={props.loadoutRole}
|
||||||
|
onChange={(e) => props.setLoadoutRole(e.target.value)}
|
||||||
|
className={`
|
||||||
|
rounded-md border border-gray-300 bg-gray-800 p-2
|
||||||
|
text-sm text-white
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="text-gray-400">Select weapons for each pylon</span>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="mx-auto flex flex-col gap-2">
|
||||||
|
{/* Draw an airplane seen from the front using only gray lines */}
|
||||||
|
<div className="flex justify-center">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
flex h-20 flex-col items-center justify-center
|
border-b-2 border- b-2 w-full border-gray-300
|
||||||
rounded-md border px-1
|
|
||||||
${
|
|
||||||
autofillPylons
|
|
||||||
? `
|
|
||||||
text-gray-400
|
|
||||||
`
|
|
||||||
: `
|
|
||||||
cursor-pointer
|
|
||||||
hover:bg-gray-700
|
|
||||||
`
|
|
||||||
}
|
|
||||||
${
|
|
||||||
selectedPylons.includes(pylon)
|
|
||||||
? `
|
|
||||||
border-gray-200
|
|
||||||
`
|
|
||||||
: `border-transparent`
|
|
||||||
}
|
|
||||||
`}
|
`}
|
||||||
onClick={() => {
|
></div>
|
||||||
if (autofillPylons) return;
|
<div
|
||||||
if (selectedPylons.includes(pylon)) {
|
className={`
|
||||||
setSelectedPylons(selectedPylons.filter((p) => p !== pylon));
|
h-14 min-w-14 rounded-full border-2
|
||||||
} else {
|
border-gray-300
|
||||||
setSelectedPylons([...selectedPylons, pylon]);
|
`}
|
||||||
}
|
></div>
|
||||||
}}
|
<div
|
||||||
>
|
className={`
|
||||||
<div className={`text-center text-xs`}>{pylon}</div>
|
border-b-2 border- b-2 w-full border-gray-300
|
||||||
<div
|
`}
|
||||||
data-autofill={autofillPylons ? "true" : "false"}
|
></div>
|
||||||
className={`
|
|
||||||
h-3 w-0 rounded-full border
|
|
||||||
data-[autofill='false']:border-white
|
|
||||||
data-[autofill='true']:border-gray-400
|
|
||||||
`}
|
|
||||||
></div>
|
|
||||||
{props.selectedWeapons[pylon] ? (
|
|
||||||
<div
|
|
||||||
data-autofill={autofillPylons ? "true" : "false"}
|
|
||||||
className={`
|
|
||||||
flex h-6 w-6 items-center
|
|
||||||
justify-center rounded-full border
|
|
||||||
data-[autofill='false']:border-white
|
|
||||||
data-[autofill='true']:border-gray-400
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-autofill={autofillPylons ? "true" : "false"}
|
|
||||||
className={`
|
|
||||||
h-5 w-5 rounded-full
|
|
||||||
data-[autofill='false']:bg-white
|
|
||||||
data-[autofill='true']:bg-gray-400
|
|
||||||
`}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
data-autofill={autofillPylons ? "true" : "false"}
|
|
||||||
className={`
|
|
||||||
h-6 w-6 rounded-full border
|
|
||||||
data-[autofill='false']:border-white
|
|
||||||
data-[autofill='true']:border-gray-400
|
|
||||||
`}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Buttons to select/deselect all pylons, clear all weapons and remove weapons from selected pylons */}
|
<div className="flex justify-center gap-1">
|
||||||
<div>
|
{Object.keys(props.weaponsByPylon).map((pylon) => {
|
||||||
<div className="flex justify-center gap-2">
|
let weapon = props.selectedWeapons[pylon];
|
||||||
{selectedPylons.length > 0 && (
|
let letter = Object.entries(weaponLetters).find(([letter, name]) => name === weapon?.name)?.[0] || "";
|
||||||
<>
|
// If the currently hovered weapon is compatible with this pylon, show "Hovered" else "Not Hovered"
|
||||||
|
let isHovered = props.weaponsByPylon[pylon].some((w) => w.name === hoveredWeapon);
|
||||||
|
return (
|
||||||
|
<div key={pylon} className={``}>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
flex h-20 flex-col items-center
|
||||||
|
justify-center rounded-md border
|
||||||
|
px-1
|
||||||
|
${
|
||||||
|
autofillPylons
|
||||||
|
? `text-gray-400`
|
||||||
|
: `
|
||||||
|
cursor-pointer
|
||||||
|
hover:bg-gray-700
|
||||||
|
`
|
||||||
|
}
|
||||||
|
${
|
||||||
|
selectedPylons.includes(pylon)
|
||||||
|
? `
|
||||||
|
border-gray-200
|
||||||
|
`
|
||||||
|
: `
|
||||||
|
border-transparent
|
||||||
|
`
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
onClick={() => {
|
||||||
|
if (autofillPylons) return;
|
||||||
|
if (selectedPylons.includes(pylon)) {
|
||||||
|
setSelectedPylons(selectedPylons.filter((p) => p !== pylon));
|
||||||
|
} else {
|
||||||
|
setSelectedPylons([...selectedPylons, pylon]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={`text-center text-xs`}>{pylon}</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
data-autofill={autofillPylons ? "true" : "false"}
|
||||||
|
className={`
|
||||||
|
h-3 w-0 border
|
||||||
|
data-[autofill='false']:border-white
|
||||||
|
data-[autofill='true']:border-gray-400
|
||||||
|
`}
|
||||||
|
></div>
|
||||||
|
{props.selectedWeapons[pylon] ? (
|
||||||
|
<div
|
||||||
|
data-autofill={autofillPylons ? "true" : "false"}
|
||||||
|
data-hovered={isHovered ? "true" : "false"}
|
||||||
|
className={`
|
||||||
|
flex h-6 w-6 items-center
|
||||||
|
justify-center
|
||||||
|
rounded-full border
|
||||||
|
data-[autofill='false']:border-white
|
||||||
|
data-[autofill='true']:border-gray-400
|
||||||
|
data-[hovered='true']:border-green-400
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{/* Show the letter of the group the weapon belongs to from weaponLetters */}
|
||||||
|
<span
|
||||||
|
className={`
|
||||||
|
text-sm font-bold
|
||||||
|
`}
|
||||||
|
style={{
|
||||||
|
color: letter in colors ? colors[letter as keyof typeof colors] : "inherit",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{letter}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
data-autofill={autofillPylons ? "true" : "false"}
|
||||||
|
data-hovered={isHovered ? "true" : "false"}
|
||||||
|
className={`
|
||||||
|
h-6 w-6 rounded-full
|
||||||
|
border
|
||||||
|
data-[autofill='false']:border-white
|
||||||
|
data-[autofill='true']:border-gray-400
|
||||||
|
data-[hovered='true']:border-green-400
|
||||||
|
`}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* List all the groups from weaponLetters */}
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
{Object.entries(weaponLetters).map(([letter, weapon]) => (
|
||||||
|
<div
|
||||||
|
key={letter}
|
||||||
|
className={`
|
||||||
|
flex items-center text-sm
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<span className="font-bold" style={{ color: letter in colors ? colors[letter as keyof typeof colors] : "inherit" }}>
|
||||||
|
{letter}:
|
||||||
|
</span>
|
||||||
|
<span className="ml-1 text-gray-400">{weapon}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Buttons to select/deselect all pylons, clear all weapons and remove weapons from selected pylons */}
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-center gap-2">
|
||||||
|
{selectedPylons.length > 0 && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className={`
|
||||||
|
text-nowrap rounded-md bg-gray-700
|
||||||
|
px-2 py-1 text-sm
|
||||||
|
hover:bg-gray-600
|
||||||
|
`}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedPylons([]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FaArrowsRotate className="inline" /> Reset selection
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{
|
||||||
|
/* Checjk if any of the selected pylons have a weapon selected */
|
||||||
|
props.selectedWeapons && selectedPylons.some((pylon) => props.selectedWeapons[pylon] !== undefined) && (
|
||||||
|
<button
|
||||||
|
className={`
|
||||||
|
text-nowrap rounded-md
|
||||||
|
bg-gray-700 px-2 py-1 text-sm
|
||||||
|
hover:bg-gray-600
|
||||||
|
`}
|
||||||
|
onClick={() => {
|
||||||
|
// Remove weapons from selected pylons
|
||||||
|
let newSelectedWeapons = { ...props.selectedWeapons };
|
||||||
|
selectedPylons.forEach((pylon) => {
|
||||||
|
delete newSelectedWeapons[pylon];
|
||||||
|
});
|
||||||
|
props.setSelectedWeapons(newSelectedWeapons);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FaXmark
|
||||||
|
className={`
|
||||||
|
inline text-red-500
|
||||||
|
`}
|
||||||
|
/>{" "}
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{props.selectedWeapons && Object.keys(props.selectedWeapons).length > 0 && (
|
||||||
<button
|
<button
|
||||||
className={`
|
className={`
|
||||||
text-nowrap rounded-md bg-gray-700 px-2
|
text-nowrap rounded-md bg-gray-700 px-2
|
||||||
@@ -131,56 +324,14 @@ export function WeaponsWizard(props: {
|
|||||||
hover:bg-gray-600
|
hover:bg-gray-600
|
||||||
`}
|
`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedPylons([]);
|
// Clear all selected weapons
|
||||||
|
props.setSelectedWeapons({});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FaArrowsRotate className="inline" /> Reset selection
|
<FaTrash className="inline text-red-500" /> Delete all
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
{
|
</div>
|
||||||
/* Checjk if any of the selected pylons have a weapon selected */
|
|
||||||
props.selectedWeapons && selectedPylons.some((pylon) => props.selectedWeapons[pylon] !== undefined) && (
|
|
||||||
<button
|
|
||||||
className={`
|
|
||||||
text-nowrap rounded-md bg-gray-700
|
|
||||||
px-2 py-1 text-sm
|
|
||||||
hover:bg-gray-600
|
|
||||||
`}
|
|
||||||
onClick={() => {
|
|
||||||
// Remove weapons from selected pylons
|
|
||||||
let newSelectedWeapons = { ...props.selectedWeapons };
|
|
||||||
selectedPylons.forEach((pylon) => {
|
|
||||||
delete newSelectedWeapons[pylon];
|
|
||||||
});
|
|
||||||
props.setSelectedWeapons(newSelectedWeapons);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FaXmark
|
|
||||||
className={`
|
|
||||||
inline text-red-500
|
|
||||||
`}
|
|
||||||
/>{" "}
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{props.selectedWeapons && Object.keys(props.selectedWeapons).length > 0 && (
|
|
||||||
<button
|
|
||||||
className={`
|
|
||||||
text-nowrap rounded-md bg-gray-700 px-2 py-1
|
|
||||||
text-sm
|
|
||||||
hover:bg-gray-600
|
|
||||||
`}
|
|
||||||
onClick={() => {
|
|
||||||
// Clear all selected weapons
|
|
||||||
props.setSelectedWeapons({});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FaTrash className="inline text-red-500" /> Delete all
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -193,6 +344,17 @@ export function WeaponsWizard(props: {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{autofillPylons && (
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<span className="ml-2 text-sm">Only fill empty pylons</span>
|
||||||
|
<OlToggle
|
||||||
|
toggled={fillEmptyOnly}
|
||||||
|
onClick={() => {
|
||||||
|
setFillEmptyOnly(!fillEmptyOnly);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<OlSearchBar onChange={setSearchText} text={searchText} />
|
<OlSearchBar onChange={setSearchText} text={searchText} />
|
||||||
|
|
||||||
@@ -202,13 +364,35 @@ export function WeaponsWizard(props: {
|
|||||||
border-gray-700 px-2
|
border-gray-700 px-2
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{availableWeapons.length === 0 ? (
|
{selectedPylons.length === 0 && !autofillPylons && (
|
||||||
selectedPylons.length === 0 ? (
|
<div
|
||||||
<div className="p-2 text-sm text-gray-400">No pylons selected</div>
|
className={`
|
||||||
) : (
|
p-2 text-sm text-gray-400
|
||||||
<div className="p-2 text-sm text-gray-400">No weapons compatible with all selected pylons</div>
|
`}
|
||||||
)
|
>
|
||||||
) : (
|
No pylons selected
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{availableWeapons.length === 0 && selectedPylons.length !== 0 && !autofillPylons && (
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
p-2 text-sm text-gray-400
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
No weapons compatible with all selected pylons
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{availableWeapons.length === 0 && selectedPylons.length === 0 && autofillPylons && (
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
p-2 text-sm text-gray-400
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
No empty pylons available
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{availableWeapons.length !== 0 &&
|
||||||
availableWeapons.map((weapon) => (
|
availableWeapons.map((weapon) => (
|
||||||
<div
|
<div
|
||||||
key={weapon.name}
|
key={weapon.name}
|
||||||
@@ -218,6 +402,10 @@ export function WeaponsWizard(props: {
|
|||||||
let newSelectedWeapons = { ...props.selectedWeapons };
|
let newSelectedWeapons = { ...props.selectedWeapons };
|
||||||
Object.keys(props.weaponsByPylon).forEach((pylon) => {
|
Object.keys(props.weaponsByPylon).forEach((pylon) => {
|
||||||
const weaponsInPylon = props.weaponsByPylon[pylon];
|
const weaponsInPylon = props.weaponsByPylon[pylon];
|
||||||
|
if (fillEmptyOnly && props.selectedWeapons[pylon]) {
|
||||||
|
// If "Only fill empty pylons" is enabled, skip filled pylons
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (weaponsInPylon.some((w) => w.name === weapon.name)) {
|
if (weaponsInPylon.some((w) => w.name === weapon.name)) {
|
||||||
newSelectedWeapons[pylon] = weapon;
|
newSelectedWeapons[pylon] = weapon;
|
||||||
}
|
}
|
||||||
@@ -233,6 +421,8 @@ export function WeaponsWizard(props: {
|
|||||||
setSelectedPylons([]);
|
setSelectedPylons([]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onMouseEnter={() => setHoveredWeapon(weapon.name)}
|
||||||
|
onMouseLeave={() => setHoveredWeapon("")}
|
||||||
className={`
|
className={`
|
||||||
cursor-pointer rounded-md p-1 text-sm
|
cursor-pointer rounded-md p-1 text-sm
|
||||||
hover:bg-gray-700
|
hover:bg-gray-700
|
||||||
@@ -240,8 +430,7 @@ export function WeaponsWizard(props: {
|
|||||||
>
|
>
|
||||||
{weapon.name}
|
{weapon.name}
|
||||||
</div>
|
</div>
|
||||||
))
|
))}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { OlNumberInput } from "../components/olnumberinput";
|
|||||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||||
import { OlRangeSlider } from "../components/olrangeslider";
|
import { OlRangeSlider } from "../components/olrangeslider";
|
||||||
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||||
import { LoadoutBlueprint, SpawnRequestTable, UnitBlueprint } from "../../interfaces";
|
import { LoadoutBlueprint, SpawnRequestTable, UnitBlueprint, UnitSpawnTable } from "../../interfaces";
|
||||||
import { OlStateButton } from "../components/olstatebutton";
|
import { OlStateButton } from "../components/olstatebutton";
|
||||||
import { Coalition } from "../../types/types";
|
import { Coalition } from "../../types/types";
|
||||||
import { getApp } from "../../olympusapp";
|
import { getApp } from "../../olympusapp";
|
||||||
@@ -17,11 +17,10 @@ import { faArrowLeft, faStar } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { OlStringInput } from "../components/olstringinput";
|
import { OlStringInput } from "../components/olstringinput";
|
||||||
import { countryCodes } from "../data/codes";
|
import { countryCodes } from "../data/codes";
|
||||||
import { OlAccordion } from "../components/olaccordion";
|
import { OlAccordion } from "../components/olaccordion";
|
||||||
import { AppStateChangedEvent, SpawnHeadingChangedEvent } from "../../events";
|
import { AppStateChangedEvent, CustomLoadoutsUpdatedEvent, SetLoadoutWizardBlueprintEvent, SpawnHeadingChangedEvent } from "../../events";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { FaQuestionCircle } from "react-icons/fa";
|
import { FaMagic, FaQuestionCircle } from "react-icons/fa";
|
||||||
import { OlExpandingTooltip } from "../components/olexpandingtooltip";
|
import { OlExpandingTooltip } from "../components/olexpandingtooltip";
|
||||||
import { WeaponsWizard } from "./components/weaponswizard";
|
|
||||||
import { LoadoutViewer } from "./components/loadoutviewer";
|
import { LoadoutViewer } from "./components/loadoutviewer";
|
||||||
|
|
||||||
enum OpenAccordion {
|
enum OpenAccordion {
|
||||||
@@ -59,7 +58,7 @@ export function UnitSpawnMenu(props: {
|
|||||||
const [spawnAltitudeType, setSpawnAltitudeType] = useState(false);
|
const [spawnAltitudeType, setSpawnAltitudeType] = useState(false);
|
||||||
const [spawnLiveryID, setSpawnLiveryID] = useState("");
|
const [spawnLiveryID, setSpawnLiveryID] = useState("");
|
||||||
const [spawnSkill, setSpawnSkill] = useState("High");
|
const [spawnSkill, setSpawnSkill] = useState("High");
|
||||||
const [selectedWeapons, setSelectedWeapons] = useState({} as { [key: string]: { clsids: string; name: string; weight: number } });
|
|
||||||
const [quickAccessName, setQuickAccessName] = useState("Preset 1");
|
const [quickAccessName, setQuickAccessName] = useState("Preset 1");
|
||||||
const [key, setKey] = useState("");
|
const [key, setKey] = useState("");
|
||||||
const [spawnRequestTable, setSpawnRequestTable] = useState(null as null | SpawnRequestTable);
|
const [spawnRequestTable, setSpawnRequestTable] = useState(null as null | SpawnRequestTable);
|
||||||
@@ -70,13 +69,16 @@ export function UnitSpawnMenu(props: {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAppState(getApp()?.getState());
|
setAppState(getApp()?.getState());
|
||||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||||
|
CustomLoadoutsUpdatedEvent.on((unitName, loadout) => {
|
||||||
|
setSpawnRole(loadout.roles[0]);
|
||||||
|
setSpawnLoadout(loadout);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSpawnRole("");
|
setSpawnRole("");
|
||||||
setSpawnLoadout(null);
|
setSpawnLoadout(null);
|
||||||
setSpawnLiveryID("");
|
setSpawnLiveryID("");
|
||||||
setSelectedWeapons({});
|
|
||||||
}, [props.blueprint]);
|
}, [props.blueprint]);
|
||||||
|
|
||||||
/* When the menu is opened show the unit preview on the map as a cursor */
|
/* When the menu is opened show the unit preview on the map as a cursor */
|
||||||
@@ -115,16 +117,25 @@ export function UnitSpawnMenu(props: {
|
|||||||
/* Callback and effect to update the spawn request table */
|
/* Callback and effect to update the spawn request table */
|
||||||
const updateSpawnRequestTable = useCallback(() => {
|
const updateSpawnRequestTable = useCallback(() => {
|
||||||
if (props.blueprint !== null) {
|
if (props.blueprint !== null) {
|
||||||
|
const loadoutCode = spawnLoadout ? (props.blueprint.loadouts?.find((loadout) => loadout.name === spawnLoadout.name)?.code ?? "") : "";
|
||||||
|
const loadoutPayload = spawnLoadout
|
||||||
|
? (props.blueprint.loadouts?.find((loadout) => loadout.name === spawnLoadout.name)?.payload ?? undefined)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const unitTable: UnitSpawnTable = {
|
||||||
|
unitType: props.blueprint?.name,
|
||||||
|
location: props.latlng ?? new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
|
||||||
|
skill: spawnSkill,
|
||||||
|
liveryID: spawnLiveryID,
|
||||||
|
altitude: ftToM(spawnAltitude),
|
||||||
|
loadout: loadoutCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loadoutPayload) unitTable.payload = loadoutPayload;
|
||||||
|
|
||||||
setSpawnRequestTable({
|
setSpawnRequestTable({
|
||||||
category: props.blueprint?.category,
|
category: props.blueprint?.category,
|
||||||
unit: {
|
unit: unitTable,
|
||||||
unitType: props.blueprint?.name,
|
|
||||||
location: props.latlng ?? new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
|
|
||||||
skill: spawnSkill,
|
|
||||||
liveryID: spawnLiveryID,
|
|
||||||
altitude: ftToM(spawnAltitude),
|
|
||||||
loadout: props.blueprint?.loadouts?.find((loadout) => loadout.name === spawnLoadout?.name)?.code ?? "",
|
|
||||||
},
|
|
||||||
amount: spawnNumber,
|
amount: spawnNumber,
|
||||||
coalition: spawnCoalition,
|
coalition: spawnCoalition,
|
||||||
});
|
});
|
||||||
@@ -192,10 +203,49 @@ export function UnitSpawnMenu(props: {
|
|||||||
|
|
||||||
/* Initialize the role */
|
/* Initialize the role */
|
||||||
let allRoles = props.blueprint?.loadouts?.flatMap((loadout) => loadout.roles).filter((role) => role !== "No task");
|
let allRoles = props.blueprint?.loadouts?.flatMap((loadout) => loadout.roles).filter((role) => role !== "No task");
|
||||||
let mainRole = roles[0];
|
|
||||||
|
// If there are loadouts with Custom role, include Custom in the role selection
|
||||||
|
const hasCustomRoleLoadouts = props.blueprint?.loadouts?.some((loadout) => loadout.roles.includes("Custom"));
|
||||||
|
if (hasCustomRoleLoadouts && allRoles) allRoles.push("Custom");
|
||||||
|
|
||||||
|
// If there are custom loadouts, select "Custom" as the main role
|
||||||
|
let mainRole = hasCustomRoleLoadouts ? "Custom" : roles[0];
|
||||||
if (allRoles !== undefined) mainRole = mode(allRoles);
|
if (allRoles !== undefined) mainRole = mode(allRoles);
|
||||||
spawnRole === "" && roles.length > 0 && setSpawnRole(mainRole);
|
spawnRole === "" && roles.length > 0 && setSpawnRole(mainRole);
|
||||||
|
|
||||||
|
// Filter the loadouts based on the selected role
|
||||||
|
const filteredLoadouts = props.blueprint?.loadouts?.filter((loadout) => loadout.roles.includes(spawnRole));
|
||||||
|
|
||||||
|
// Order the loadouts so that custom loadouts appear first and "Empty loadout" appears last
|
||||||
|
if (filteredLoadouts) {
|
||||||
|
filteredLoadouts.sort((a, b) => {
|
||||||
|
if (a.isCustom && !b.isCustom) return -1;
|
||||||
|
if (!a.isCustom && b.isCustom) return 1;
|
||||||
|
if (a.name === "Empty loadout") return 1;
|
||||||
|
if (b.name === "Empty loadout") return -1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Effect to reset the loadout if the role changes */
|
||||||
|
useEffect(() => {
|
||||||
|
// If the current loadout is not in the filtered loadouts, reset it
|
||||||
|
if (spawnLoadout && filteredLoadouts && !filteredLoadouts.includes(spawnLoadout)) {
|
||||||
|
setSpawnLoadout(null);
|
||||||
|
}
|
||||||
|
}, [spawnRole, props.blueprint]);
|
||||||
|
|
||||||
|
/* Initialize the loadout */
|
||||||
|
const initializeLoadout = useCallback(() => {
|
||||||
|
if (spawnLoadout && filteredLoadouts && filteredLoadouts.includes(spawnLoadout)) return;
|
||||||
|
if (filteredLoadouts && filteredLoadouts.length > 0) {
|
||||||
|
if (filteredLoadouts.filter((loadout) => loadout.name !== "Empty loadout").length > 0)
|
||||||
|
setSpawnLoadout(filteredLoadouts.filter((loadout) => loadout.name !== "Empty loadout")[0]);
|
||||||
|
else setSpawnLoadout(filteredLoadouts[0]);
|
||||||
|
}
|
||||||
|
}, [filteredLoadouts, spawnLoadout]);
|
||||||
|
useEffect(initializeLoadout, [filteredLoadouts]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{props.compact ? (
|
{props.compact ? (
|
||||||
@@ -207,7 +257,7 @@ export function UnitSpawnMenu(props: {
|
|||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div className="mr-2">
|
<div className="mr-2">
|
||||||
<div className="flex h-fit flex-col gap-3">
|
<div className="flex h-fit flex-col gap-2">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
onClick={props.onBack}
|
onClick={props.onBack}
|
||||||
@@ -246,10 +296,11 @@ export function UnitSpawnMenu(props: {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
my-auto text-sm text-white
|
my-auto text-nowrap text-sm
|
||||||
|
text-white
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
Quick access:{" "}
|
Quick access:
|
||||||
</div>
|
</div>
|
||||||
<OlStringInput
|
<OlStringInput
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -372,6 +423,87 @@ export function UnitSpawnMenu(props: {
|
|||||||
})}
|
})}
|
||||||
</OlDropdown>
|
</OlDropdown>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
flex flex-col content-center
|
||||||
|
justify-between gap-2
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`
|
||||||
|
my-auto font-normal
|
||||||
|
dark:text-gray-400
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Loadout
|
||||||
|
</span>
|
||||||
|
<OlDropdown
|
||||||
|
label={spawnLoadout ? spawnLoadout.name : "Default"}
|
||||||
|
className={`w-full`}
|
||||||
|
tooltip={() => (
|
||||||
|
<OlExpandingTooltip
|
||||||
|
title="Unit loadout"
|
||||||
|
content="Selects the loadout of the spawned unit. Depending on the selection, the unit will be equipped with different weapons and equipment."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
tooltipRelativeToParent={true}
|
||||||
|
>
|
||||||
|
{filteredLoadouts?.map((loadout) => {
|
||||||
|
return (
|
||||||
|
<OlDropdownItem
|
||||||
|
key={loadout.name}
|
||||||
|
onClick={() => {
|
||||||
|
setSpawnLoadout(loadout);
|
||||||
|
}}
|
||||||
|
className={`
|
||||||
|
w-full
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`
|
||||||
|
w-full
|
||||||
|
content-center
|
||||||
|
overflow-hidden
|
||||||
|
text-ellipsis
|
||||||
|
text-nowrap
|
||||||
|
text-left
|
||||||
|
w-max-full
|
||||||
|
flex gap-2
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
my-auto
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{loadout.name}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</OlDropdownItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</OlDropdown>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`
|
||||||
|
flex w-full justify-center
|
||||||
|
gap-2 rounded-md
|
||||||
|
border-[1px] p-2
|
||||||
|
align-middle text-sm
|
||||||
|
dark:text-white
|
||||||
|
hover:bg-white/10
|
||||||
|
`}
|
||||||
|
onClick={() => {
|
||||||
|
SetLoadoutWizardBlueprintEvent.dispatch(props.blueprint!);
|
||||||
|
getApp().setState(OlympusState.SPAWN, SpawnSubState.LOADOUT_WIZARD);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FaMagic className="my-auto" />
|
||||||
|
Loadout wizard
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<OlAccordion
|
<OlAccordion
|
||||||
@@ -640,21 +772,6 @@ export function UnitSpawnMenu(props: {
|
|||||||
>
|
>
|
||||||
{props.blueprint ? <OlUnitSummary blueprint={props.blueprint} coalition={spawnCoalition} /> : <span></span>}
|
{props.blueprint ? <OlUnitSummary blueprint={props.blueprint} coalition={spawnCoalition} /> : <span></span>}
|
||||||
</OlAccordion>
|
</OlAccordion>
|
||||||
|
|
||||||
<OlAccordion
|
|
||||||
onClick={() => {
|
|
||||||
setOpenAccordion(openAccordion === OpenAccordion.LOADOUT ? OpenAccordion.NONE : OpenAccordion.LOADOUT);
|
|
||||||
}}
|
|
||||||
open={openAccordion === OpenAccordion.LOADOUT}
|
|
||||||
title="Loadout wizard"
|
|
||||||
>
|
|
||||||
<WeaponsWizard
|
|
||||||
selectedWeapons={selectedWeapons}
|
|
||||||
setSelectedWeapons={setSelectedWeapons}
|
|
||||||
weaponsByPylon={props.blueprint?.acceptedPayloads ?? {}}
|
|
||||||
/>
|
|
||||||
</OlAccordion>
|
|
||||||
|
|
||||||
{spawnLoadout && spawnLoadout.items.length > 0 && (
|
{spawnLoadout && spawnLoadout.items.length > 0 && (
|
||||||
<OlAccordion
|
<OlAccordion
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -1056,6 +1173,75 @@ export function UnitSpawnMenu(props: {
|
|||||||
})}
|
})}
|
||||||
</OlDropdown>
|
</OlDropdown>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
flex flex-col content-center
|
||||||
|
justify-between gap-2
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`
|
||||||
|
my-auto font-normal
|
||||||
|
dark:text-gray-400
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Loadout
|
||||||
|
</span>
|
||||||
|
<OlDropdown
|
||||||
|
label={spawnLoadout ? spawnLoadout.name : "Default"}
|
||||||
|
className={`w-full`}
|
||||||
|
tooltip={() => (
|
||||||
|
<OlExpandingTooltip
|
||||||
|
title="Unit loadout"
|
||||||
|
content="Selects the loadout of the spawned unit. Depending on the selection, the unit will be equipped with different weapons and equipment."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
tooltipRelativeToParent={true}
|
||||||
|
>
|
||||||
|
{filteredLoadouts?.map((loadout) => {
|
||||||
|
return (
|
||||||
|
<OlDropdownItem
|
||||||
|
key={loadout.name}
|
||||||
|
onClick={() => {
|
||||||
|
setSpawnLoadout(loadout);
|
||||||
|
}}
|
||||||
|
className={`w-full`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`
|
||||||
|
w-full content-center
|
||||||
|
overflow-hidden
|
||||||
|
text-ellipsis
|
||||||
|
text-nowrap text-left
|
||||||
|
w-max-full flex gap-2
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div className="my-auto">{loadout.name}</div>
|
||||||
|
</span>
|
||||||
|
</OlDropdownItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</OlDropdown>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`
|
||||||
|
flex w-full justify-center gap-2
|
||||||
|
rounded-md border-[1px] p-2
|
||||||
|
align-middle text-sm
|
||||||
|
dark:text-white
|
||||||
|
hover:bg-white/10
|
||||||
|
`}
|
||||||
|
onClick={() => {
|
||||||
|
SetLoadoutWizardBlueprintEvent.dispatch(props.blueprint!);
|
||||||
|
getApp().setState(OlympusState.SPAWN, SpawnSubState.LOADOUT_WIZARD);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FaMagic className="my-auto" />
|
||||||
|
Loadout wizard
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="my-5 flex justify-between">
|
<div className="my-5 flex justify-between">
|
||||||
<div className="my-auto flex flex-col gap-2">
|
<div className="my-auto flex flex-col gap-2">
|
||||||
<span className="text-white">Spawn heading</span>
|
<span className="text-white">Spawn heading</span>
|
||||||
@@ -1069,13 +1255,7 @@ export function UnitSpawnMenu(props: {
|
|||||||
my-auto
|
my-auto
|
||||||
`}
|
`}
|
||||||
/>{" "}
|
/>{" "}
|
||||||
<div
|
<div className={`my-auto`}>Drag to change</div>
|
||||||
className={`
|
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
Drag to change
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1128,38 +1308,8 @@ export function UnitSpawnMenu(props: {
|
|||||||
></img>
|
></img>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="text-gray-200">Loadout</div>
|
||||||
|
{spawnLoadout && <LoadoutViewer spawnLoadout={spawnLoadout} />}
|
||||||
<div
|
|
||||||
className={`
|
|
||||||
flex h-fit flex-col gap-1 px-4 py-2
|
|
||||||
dark:bg-olympus-200/30
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<OlAccordion
|
|
||||||
onClick={() => {
|
|
||||||
setOpenAccordion(openAccordion === OpenAccordion.LOADOUT ? OpenAccordion.NONE : OpenAccordion.LOADOUT);
|
|
||||||
}}
|
|
||||||
open={openAccordion === OpenAccordion.LOADOUT}
|
|
||||||
title="Loadout wizard"
|
|
||||||
>
|
|
||||||
<WeaponsWizard
|
|
||||||
selectedWeapons={selectedWeapons}
|
|
||||||
setSelectedWeapons={setSelectedWeapons}
|
|
||||||
weaponsByPylon={props.blueprint?.acceptedPayloads ?? {}}
|
|
||||||
/>
|
|
||||||
</OlAccordion>
|
|
||||||
{spawnLoadout && spawnLoadout.items.length > 0 && (
|
|
||||||
<OlAccordion
|
|
||||||
onClick={() => {
|
|
||||||
setShowLoadout(!showLoadout);
|
|
||||||
}}
|
|
||||||
open={showLoadout}
|
|
||||||
title="Loadout"
|
|
||||||
>
|
|
||||||
<LoadoutViewer spawnLoadout={spawnLoadout} />
|
|
||||||
</OlAccordion>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{props.airbase && (
|
{props.airbase && (
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { UnitControlMenu } from "./panels/unitcontrolmenu";
|
|||||||
import { MainMenu } from "./panels/mainmenu";
|
import { MainMenu } from "./panels/mainmenu";
|
||||||
import { SideBar } from "./panels/sidebar";
|
import { SideBar } from "./panels/sidebar";
|
||||||
import { OptionsMenu } from "./panels/optionsmenu";
|
import { OptionsMenu } from "./panels/optionsmenu";
|
||||||
import { NO_SUBSTATE, OlympusState, OlympusSubState, OptionsSubstate, UnitControlSubState } from "../constants/constants";
|
import { NO_SUBSTATE, OlympusState, OlympusSubState, OptionsSubstate, SpawnSubState, UnitControlSubState } from "../constants/constants";
|
||||||
import { getApp, setupApp } from "../olympusapp";
|
import { getApp, setupApp } from "../olympusapp";
|
||||||
import { LoginModal } from "./modals/loginmodal";
|
import { LoginModal } from "./modals/loginmodal";
|
||||||
|
|
||||||
@@ -32,6 +32,7 @@ import { WarningModal } from "./modals/warningmodal";
|
|||||||
import { TrainingModal } from "./modals/trainingmodal";
|
import { TrainingModal } from "./modals/trainingmodal";
|
||||||
import { AdminModal } from "./modals/adminmodal";
|
import { AdminModal } from "./modals/adminmodal";
|
||||||
import { ImageOverlayModal } from "./modals/imageoverlaymodal";
|
import { ImageOverlayModal } from "./modals/imageoverlaymodal";
|
||||||
|
import { LoadoutWizardModal } from "./modals/loadoutwizardmodal";
|
||||||
|
|
||||||
export function UI() {
|
export function UI() {
|
||||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||||
@@ -76,6 +77,7 @@ export function UI() {
|
|||||||
<TrainingModal open={appState === OlympusState.TRAINING} />
|
<TrainingModal open={appState === OlympusState.TRAINING} />
|
||||||
<AdminModal open={appState === OlympusState.ADMIN} />
|
<AdminModal open={appState === OlympusState.ADMIN} />
|
||||||
<ImageOverlayModal open={appState === OlympusState.IMPORT_IMAGE_OVERLAY} />
|
<ImageOverlayModal open={appState === OlympusState.IMPORT_IMAGE_OVERLAY} />
|
||||||
|
<LoadoutWizardModal open={appState === OlympusState.SPAWN && appSubState === SpawnSubState.LOADOUT_WIZARD} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
import { getApp } from "../../olympusapp";
|
import { getApp } from "../../olympusapp";
|
||||||
import { GAME_MASTER } from "../../constants/constants";
|
import { GAME_MASTER } from "../../constants/constants";
|
||||||
import { UnitBlueprint } from "../../interfaces";
|
import { UnitBlueprint } from "../../interfaces";
|
||||||
import { UnitDatabaseLoadedEvent } from "../../events";
|
import { SessionDataLoadedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||||
|
|
||||||
export class UnitDatabase {
|
export class UnitDatabase {
|
||||||
blueprints: { [key: string]: UnitBlueprint } = {};
|
blueprints: { [key: string]: UnitBlueprint } = {};
|
||||||
|
|
||||||
constructor() {}
|
constructor() {
|
||||||
|
SessionDataLoadedEvent.on((sessionData) => {
|
||||||
|
// Check if the sessionData customloadouts contains any loadouts for units, and if so, update the blueprints
|
||||||
|
if (sessionData.customLoadouts) {
|
||||||
|
for (let unitName in sessionData.customLoadouts) {
|
||||||
|
if (this.blueprints[unitName]) {
|
||||||
|
if (!this.blueprints[unitName].loadouts) this.blueprints[unitName].loadouts = [];
|
||||||
|
sessionData.customLoadouts[unitName].forEach((loadout) => {
|
||||||
|
this.blueprints[unitName].loadouts?.push(loadout);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
load(url: string, category?: string) {
|
load(url: string, category?: string) {
|
||||||
if (url !== "") {
|
if (url !== "") {
|
||||||
@@ -204,7 +218,7 @@ export class UnitDatabase {
|
|||||||
getLoadoutNamesByRole(name: string, role: string) {
|
getLoadoutNamesByRole(name: string, role: string) {
|
||||||
var filteredBlueprints = this.getBlueprints();
|
var filteredBlueprints = this.getBlueprints();
|
||||||
var loadoutsByRole: string[] = [];
|
var loadoutsByRole: string[] = [];
|
||||||
var loadouts = filteredBlueprints[name].loadouts;
|
var loadouts = filteredBlueprints[name as any].loadouts;
|
||||||
if (loadouts) {
|
if (loadouts) {
|
||||||
for (let loadout of loadouts) {
|
for (let loadout of loadouts) {
|
||||||
if (loadout.roles.includes(role) || loadout.roles.includes("")) {
|
if (loadout.roles.includes(role) || loadout.roles.includes("")) {
|
||||||
|
|||||||
Reference in New Issue
Block a user