Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
2a1c8d7
Added config for testing
AlexisBalzano Sep 18, 2025
e66b60d
Updated config
AlexisBalzano Sep 18, 2025
2b94941
Update (:
AlexisBalzano Sep 18, 2025
bd210cb
updated config
AlexisBalzano Sep 18, 2025
a562de5
Ground traffic stand occupiance detection
AlexisBalzano Sep 18, 2025
f74df5f
Fixed runwayUpdate issue on load
AlexisBalzano Sep 18, 2025
2316a29
Added config.json
AlexisBalzano Sep 19, 2025
8826082
Added LFMN Config
AlexisBalzano Sep 19, 2025
5a2ea99
Added setting parsing
AlexisBalzano Sep 19, 2025
aa5ec8a
Removed excessive logging
AlexisBalzano Sep 19, 2025
b656f9c
Adde getAllStand functions
AlexisBalzano Sep 19, 2025
516ffc8
Added updateStandMenu function
AlexisBalzano Sep 19, 2025
4b85f5c
Fixed a lot
AlexisBalzano Sep 25, 2025
d97123a
Updated config
AlexisBalzano Sep 25, 2025
09237e9
Added lists to config
AlexisBalzano Sep 25, 2025
bc403d7
Added isArrival boolean function
AlexisBalzano Sep 25, 2025
d2b6ee8
Added new list parsing
AlexisBalzano Sep 25, 2025
eca564a
Remove departure pilot aftergiven distance
AlexisBalzano Sep 25, 2025
3973f27
Switched isArrival from pilot to callsign
AlexisBalzano Sep 25, 2025
19c01b7
Optimised departures stand freeing
AlexisBalzano Sep 26, 2025
d2ab013
Switched to v1.0.0
AlexisBalzano Sep 26, 2025
2da4571
Added github config url setting
AlexisBalzano Sep 26, 2025
b134137
Added auto airport config download support
AlexisBalzano Sep 26, 2025
d2e0122
Added config file to final compiled library
AlexisBalzano Sep 26, 2025
23fd53c
Fixed include
AlexisBalzano Sep 26, 2025
5b540d6
Added dev as action starter
AlexisBalzano Sep 26, 2025
1381292
Fixed standmenu button not appearing
AlexisBalzano Sep 26, 2025
3e4b930
Remove excessive logging
AlexisBalzano Sep 26, 2025
bf43498
Made isAircraftOnStand able to receive an iCAO
AlexisBalzano Sep 26, 2025
559939d
Added No FP pilot detection
AlexisBalzano Sep 26, 2025
4ffd48e
Added "None" option in stand menu
AlexisBalzano Sep 26, 2025
5916fee
Added support for multiple uses stands
AlexisBalzano Sep 27, 2025
ce7f7f2
Fixed no FP trafic stand occupiance detection
AlexisBalzano Sep 27, 2025
222508b
Added Config Debug mode for extra logging
AlexisBalzano Sep 27, 2025
badfd3b
Removed unused setting
AlexisBalzano Sep 27, 2025
6fedf51
Added dump command
AlexisBalzano Sep 27, 2025
305fc27
Removed unused configuration
AlexisBalzano Sep 27, 2025
bc535e2
Switched to v1.0.1
AlexisBalzano Sep 27, 2025
fd1e8d7
Change .stand help command formating
AlexisBalzano Sep 27, 2025
f0ba0c9
Added priority check for stands
AlexisBalzano Sep 27, 2025
d4cab34
Eased ignoredCallsign conditions
AlexisBalzano Sep 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- main
- Dev
pull_request:
branches:
- main
Expand Down Expand Up @@ -145,11 +146,10 @@ jobs:
with:
path: all_builds

# - name: Prepare config files for packaging
# run: |
# mkdir -p plugins/NeoSTAND
# cp src/config/config.json plugins/NeoSTAND/
# cp src/config/AircraftData.json plugins/NeoSTAND/
- name: Prepare config files for packaging
run: |
mkdir -p plugins/NeoSTAND
cp src/config/config.json plugins/NeoSTAND/

- name: List downloaded files
run: ls -R all_builds
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.14)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake)
project(NeoSTAND VERSION "0.0.1")
project(NeoSTAND VERSION "1.0.1")

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand Down
3 changes: 1 addition & 2 deletions CMakeSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
"cmakeCommandArgs": "-DDEV=1",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "msvc_x64" ],
"variables": []
"inheritEnvironments": [ "msvc_x64" ]
}
]
}
167 changes: 158 additions & 9 deletions src/NeoSTAND.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ void NeoSTAND::Initialize(const PluginMetadata &metadata, CoreAPI *coreAPI, Clie
logger_ = &lcoreAPI->logger();
tagInterface_ = lcoreAPI->tag().getInterface();
dataManager_ = std::make_unique<DataManager>(this);
dataManager_->PopulateActiveAirports();
configVersion = getLatestConfigVersion();

#ifndef DEV
std::pair<bool, std::string> updateAvailable = newVersionAvailable();
Expand Down Expand Up @@ -91,6 +93,92 @@ std::pair<bool, std::string> stand::NeoSTAND::newVersionAvailable()
}
}

bool stand::NeoSTAND::downloadAirportConfig(std::string icao)
{
std::transform(icao.begin(), icao.end(), icao.begin(), ::toupper);

httplib::SSLClient cli("raw.githubusercontent.com");
cli.set_follow_location(true);
cli.set_connection_timeout(5, 0);
cli.set_read_timeout(5, 0);

httplib::Headers headers = { {"User-Agent", "NEOSTANDconfigDownloader"}, {"Accept", "application/json"} };
std::string repoUrl = dataManager_->getConfigUrl(); // OWNER/REPO/BRANCH

if (repoUrl.empty()) {
logger_->error("Configuration URL is not set.");
return false;
}

std::string apiEndpoint = "/" + repoUrl + "/NeoSTAND/" + icao + ".json";

bool success = false;

if (auto res = cli.Get(apiEndpoint.c_str(), headers); res && res->status == 200) {
try {
nlohmann::ordered_json json = nlohmann::ordered_json::parse(res->body);
success = dataManager_->saveDownloadedAirportConfig(json, icao);
}
catch (const std::exception& e) {
logger_->error(std::string("Failed to parse airport configuration from GitHub: ") + e.what());
}
}
else {
int status = res ? res->status : 0;
std::string extra;
if (res && (status == 301 || status == 302 || status == 307 || status == 308)) {
auto it = res->headers.find("Location");
if (it != res->headers.end()) {
extra = " Redirect Location: " + it->second;
}
}
logger_->error("Failed to download airport configuration. HTTP status: " + std::to_string(status) + extra);
}

return success;
}

std::string stand::NeoSTAND::getLatestConfigVersion()
{
httplib::SSLClient cli("raw.githubusercontent.com");
cli.set_follow_location(true);
cli.set_connection_timeout(5, 0);
cli.set_read_timeout(5, 0);

httplib::Headers headers = { {"User-Agent", "NEOSTANDconfigDownloader"} };
std::string repoUrl = dataManager_->getConfigUrl(); // OWNER/REPO/BRANCH

if (repoUrl.empty()) {
logger_->error("Configuration URL is not set.");
return "";
}

std::string apiEndpoint = "/" + repoUrl + "/version.json";

if (auto res = cli.Get(apiEndpoint.c_str(), headers); res && res->status == 200) {
try {
nlohmann::ordered_json json = nlohmann::ordered_json::parse(res->body);
return json["version"].get<std::string>();
}
catch (const std::exception& e) {
logger_->error(std::string("Failed to parse version information from GitHub: ") + e.what());
return "";
}
}
else {
int status = res ? res->status : 0;
std::string extra;
if (res && (status == 301 || status == 302 || status == 307 || status == 308)) {
auto it = res->headers.find("Location");
if (it != res->headers.end()) {
extra = " Redirect Location: " + it->second;
}
}
logger_->error("Failed to check for latest configuration version. HTTP status: " + std::to_string(status) + extra);
return "";
}
}

void NeoSTAND::Shutdown()
{
if (initialized_)
Expand Down Expand Up @@ -133,23 +221,31 @@ void NeoSTAND::DisplayMessage(const std::string &message, const std::string &sen
}

void NeoSTAND::runScopeUpdate() {
updateStandMenuButtons("LFPG"); // need to find a way to get the current pilot ICAO

if (!dataManager_) return;
dataManager_->updateAllPilots();

std::vector<DataManager::Pilot> pilots = dataManager_->getAllPilots();

for (auto& pilot : pilots) {
if (pilot.stand.empty()) dataManager_->assignStands(pilot);
if (!dataManager_->isArrival(pilot.callsign)) {
std::optional<double> distance = aircraftAPI_->getDistanceFromOrigin(pilot.callsign);
if (distance.has_value() && distance.value() > 5.0) {
dataManager_->removePilot(pilot.callsign);
}
}
if (pilot.stand.empty()) dataManager_->assignStands(pilot.callsign);
this->UpdateTagItems(pilot.callsign);
}
}

void NeoSTAND::OnTimer(int Counter) {
if (Counter % 5 == 0 && autoMode) this->runScopeUpdate();
if (Counter % dataManager_->getUpdateInterval() == 0 && autoMode) this->runScopeUpdate();
}

void stand::NeoSTAND::OnAirportConfigurationsUpdated(const Airport::AirportConfigurationsUpdatedEvent* event)
{
LOG_DEBUG(Logger::LogLevel::Info, "Airport configurations updated, reloading data.");
ClearAllTagCache();
dataManager_->removeAllPilots();
dataManager_->PopulateActiveAirports();
Expand All @@ -160,40 +256,84 @@ void stand::NeoSTAND::OnPositionUpdate(const Aircraft::PositionUpdateEvent* even
for (const auto& aircraft : event->aircrafts) {
if (aircraft.callsign.empty())
continue;
std::optional<Flightplan::Flightplan> fp = flightplanAPI_->getByCallsign(aircraft.callsign);

if (ignoredCallsigns_.contains(aircraft.callsign)) {
continue;
}

if (aircraft.position.groundSpeed > 3) {
if (!dataManager_->isArrival(aircraft.callsign)) {
dataManager_->removePilot(aircraft.callsign);
continue;
}
}
if (!fp.has_value()) {
// static & no flightplan -> check against all Stands
std::vector<std::string> activeAirports = dataManager_->getAllActiveAirports();
for (const auto& icao : activeAirports) {
std::string currentStand = dataManager_->isAircraftOnStand(aircraft.callsign, icao);
if (!currentStand.empty()) {
std::string icao = currentStand.substr(currentStand.length() - 4, 4);
currentStand = currentStand.substr(0, currentStand.length() - 5);
DataManager::Stand stand;
stand.name = currentStand;
stand.callsign = aircraft.callsign;
stand.icao = icao;
dataManager_->addStandToOccupied(stand);
break;
}
}
ignoredCallsigns_.insert(aircraft.callsign);
continue;
}

std::string currentStand = dataManager_->isAircraftOnStand(aircraft.callsign);
if (!currentStand.empty()) {
std::string icao = currentStand.substr(currentStand.length() - 4, 4);
currentStand = currentStand.substr(0, currentStand.length() - 5);

DataManager::Stand stand;
stand.name = currentStand;
stand.callsign = aircraft.callsign;
stand.icao = icao;
dataManager_->addStandToOccupied(stand);

UpdateTagItems(aircraft.callsign);
}
}
}

void stand::NeoSTAND::OnFlightplanUpdated(const Flightplan::FlightplanUpdatedEvent* event)
{
dataManager_->removePilot(event->callsign); // Force recompute
ignoredCallsigns_.erase(event->callsign);
ClearTagCache(event->callsign);
UpdateTagItems(event->callsign);
dataManager_->updatePilot(event->callsign);
}

void stand::NeoSTAND::OnFlightplanRemoved(const Flightplan::FlightplanRemovedEvent* event)
{
dataManager_->removePilot(event->callsign);
ignoredCallsigns_.insert(event->callsign);
ClearTagCache(event->callsign);
}

void stand::NeoSTAND::OnAircraftDisconnected(const Aircraft::AircraftDisconnectedEvent* event)
{
dataManager_->removePilot(event->callsign);
ignoredCallsigns_.erase(event->callsign);
ClearTagCache(event->callsign);
}

void NeoSTAND::UpdateTagItems(std::string callsign) {
dataManager_->updatePilot(callsign);
DataManager::Pilot pilot = dataManager_->getPilotByCallsign(callsign);
if (pilot.empty()) return;
DataManager::Pilot* pilot = dataManager_->getPilotByCallsign(callsign);
if (!pilot || pilot->empty()) return;

Tag::TagContext tagContext;
tagContext.callsign = callsign;
tagContext.colour = ColorizeStand();

std::string stand = pilot.stand.empty() ? "N/A" : pilot.stand;
std::string stand = pilot->stand.empty() ? "N/A" : pilot->stand;

updateTagValueIfChanged(callsign, standItemId_, stand, tagContext);
}
Expand All @@ -218,6 +358,9 @@ bool NeoSTAND::updateTagValueIfChanged(const std::string& callsign, const std::s
if (!needsUpdate)
return false;

if (!tagInterface_)
return false;

tagInterface_->UpdateTagValue(tagId, value, context);

{
Expand All @@ -241,6 +384,12 @@ void NeoSTAND::ClearAllTagCache()
tagCache_.clear();
}

bool NeoSTAND::toggleAutoMode()
{
autoMode = !autoMode;
return autoMode;
}

PluginSDK::PluginMetadata NeoSTAND::GetMetadata() const
{
return {"NeoSTAND", PLUGIN_VERSION, "French vACC"};
Expand Down
26 changes: 20 additions & 6 deletions src/NeoSTAND.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include "core/DataManager.h"
#include "utils/Color.h"

constexpr const char* NEOSTAND_VERSION = "v0.0.1";
constexpr const char* NEOSTAND_VERSION = "v1.0.1";

using namespace PluginSDK;

Expand All @@ -25,6 +25,8 @@ namespace stand {
// Plugin lifecycle methods
void Initialize(const PluginMetadata& metadata, CoreAPI* coreAPI, ClientInformation info) override;
std::pair<bool, std::string> newVersionAvailable();
bool downloadAirportConfig(std::string icao);
std::string getLatestConfigVersion();
void Shutdown() override;
void Reset();
PluginMetadata GetMetadata() const override;
Expand Down Expand Up @@ -53,7 +55,12 @@ namespace stand {
Fsd::FsdAPI* GetFsdAPI() const { return fsdAPI_; }
PluginSDK::ControllerData::ControllerDataAPI* GetControllerDataAPI() const { return controllerDataAPI_; }
Tag::TagInterface* GetTagInterface() const { return tagInterface_; }
ClientInformation GetClientInfo() const { return clientInfo_; };
DataManager* GetDataManager() const { return dataManager_.get(); }
std::unordered_set<std::string> GetIgnoredCallsigns() const { return ignoredCallsigns_; }

// Getters
std::string getConfigVersion() const { return configVersion; }

private:
void runScopeUpdate();
Expand All @@ -71,21 +78,27 @@ namespace stand {
std::string airportsCommandId_;
std::string occupiedCommandId_;
std::string blockedCommandId_;
std::string pilotCommandId_;
std::string dumpCommandId_;


private:
// Plugin state
bool initialized_ = false;
std::thread m_worker;
bool m_stop;
bool autoMode = true;
struct TagRenderState {
std::string value;
Color colour;
Color background;
};

// Plugin state
bool initialized_ = false;
std::thread m_worker;
bool m_stop;
bool autoMode = true;
std::unordered_map<std::string, std::unordered_map<std::string, TagRenderState>> tagCache_;
std::mutex tagCacheMutex_;
std::string configVersion;
std::unordered_set<std::string> ignoredCallsigns_;


// APIs
PluginMetadata metadata_;
Expand All @@ -110,6 +123,7 @@ namespace stand {
void OnTagDropdownAction(const Tag::DropdownActionEvent* event) override;
void UpdateTagItems(std::string Callsign);
Color ColorizeStand();
void updateStandMenuButtons(const std::string& icao);

// TAG Items IDs
std::string standItemId_;
Expand Down
24 changes: 13 additions & 11 deletions src/Version.h.in
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
#pragma once
// clang-format off
// prevents C2018 during compilation
#include <cstdint>

namespace {
const char *NeoSTAND{ "NeoSTAND" };
#if DEV
const char *PLUGIN_VERSION{ "@CMAKE_PROJECT_VERSION@-dev"};
constexpr const char *NeoSTAND = "NeoSTAND";

#if defined(DEV)
constexpr const char *PLUGIN_VERSION = "@CMAKE_PROJECT_VERSION@-dev";
#else
const char *PLUGIN_VERSION{ "@CMAKE_PROJECT_VERSION@" };
constexpr const char *PLUGIN_VERSION = "@CMAKE_PROJECT_VERSION@";
#endif
const char *PLUGIN_AUTHOR{ "vSID Team+vACC-FR" };
const char *PLUGIN_LICENSE{ "GPLv3" };

static constexpr std::uint8_t PLUGIN_VERSION_MAJOR = @CMAKE_PROJECT_VERSION_MAJOR@;
static constexpr std::uint8_t PLUGIN_VERSION_MINOR = @CMAKE_PROJECT_VERSION_MINOR@;
static constexpr std::uint8_t PLUGIN_VERSION_PATCH = @CMAKE_PROJECT_VERSION_PATCH@;
constexpr const char *PLUGIN_AUTHOR = "vSID Team+vACC-FR";
constexpr const char *PLUGIN_LICENSE = "GPLv3";

constexpr std::uint8_t PLUGIN_VERSION_MAJOR = @CMAKE_PROJECT_VERSION_MAJOR@;
constexpr std::uint8_t PLUGIN_VERSION_MINOR = @CMAKE_PROJECT_VERSION_MINOR@;
constexpr std::uint8_t PLUGIN_VERSION_PATCH = @CMAKE_PROJECT_VERSION_PATCH@;
}
Loading
Loading