/** * Connect to ngs / hla and provide the basic interface * $Id$ * $HeadURL$ * $Author$ * Created by Christian Ehrlicher, SLSE, 2017-04-07 */ #include "ngs_connector.h" #include "ngs_handler.h" #include "ppm_handler.h" #include "damage_model.h" #include #include #include #include #include "rpr/rpr_observer_guez_human_cb_notifier.h" #include "rpr/rpr_observer_guez_groundvehicle_cb_notifier.h" #include "rpr/rpr_observer_guez_observer_controller_cb_notifier.h" #include "rpr/rpr_observer_guez_minefield_cb_notifier.h" #include "rpr/rpr_observer_guez_minefield_breach_cb_notifier.h" #include "rpr/rpr_observer_guez_fire_support_single_shot_cb_notifier.h" #include "rpr/rpr_ia_munitiondetonation.h" #include "rpr/rpr_ia_weaponfire.h" #include "rpr/rpr_ia_guez_hit.h" #include "rpr/rpr_ia_guez_init.h" #include "rpr/rpr_ia_guez_exconengagement.h" #include "rpr/rpr_ia_guez_bitereport.h" #include "rpr/rpr_ia_guez_requestbitereport.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace AreaEffectSimulation; namespace HighLevelArchitecture { static inline qint64 simTimeToMSec(const ngs::SimTime &st) { qint64 mSecs = st.getSeconds() * 1000LL; mSecs += st.getMicroSeconds() / 1000; return mSecs; } static inline rpr2::SpatialStruct spatialStructFromWorldLocation(const rpr2::WorldLocationStruct &wls) { rpr2::SpatialStaticStruct sss; sss.WorldLocation = wls; sss.IsFrozen = false; Settings::Orientation orientation = Settings::spatialStructOrientation(); sss.Orientation.Phi = orientation.phi; sss.Orientation.Psi = orientation.psi; sss.Orientation.Theta = orientation.theta; rpr2::SpatialStruct ss; ss.DeadReckoningAlgorithm__A__Alternatives.discriminant(rpr2::DeadReckoningAlgorithmEnum8::Static); ss.DeadReckoningAlgorithm__A__Alternatives.SpatialStatic(sss); return ss; } static inline rpr2::EnvironmentObjectTypeStruct dummyEOS() { return {2, 3, 4, 5}; } // // NgsConnector // NgsConnector::NgsConnector(const QString &fedName, NgsHandler *ngsHandler) : m_ngsHandler(ngsHandler) , m_fedName(fedName) , m_swadmin(&ngs::SimulatedWorldAPI::getSimulatedWorldAdmin()) , m_master(nullptr) , m_somPlayer(nullptr) , m_playerCallback(new PlayerCallback()) , m_humanCallback(new ObserverGuezHumanCBNotifier()) , m_observerControllerCallback(new ObserverGuezObserverControllerCBNotifier()) , m_groundVehicleCallback(new ObserverGuezGroundVehicleCBNotifier()) , m_minefieldCallback(new ObserverGuezMineFieldCBNotifier()) , m_minefieldBreachCallback(new ObserverGuezMineFieldBreachCBNotifier()) , m_munitionDetonationCallback(new ObserverMunitionDetonationCBNotifier()) , m_weaponfireCallback(new ObserverWeaponFireCBNotifier()) , m_hitCallback(new ObserverGuezHitCBNotifier()) , m_initCallback(new ObserverGuezInitCBNotifier()) , m_biteReportCallback(new ObserverGuezBiteReportCBNotifier()) , m_requestBiteReportCallback(new ObserverGuezRequestBiteReportCBNotifier()) , m_bInitialized(false) , m_bCanPublishData(false) , m_timer(new QTimer(this)) , m_logger(getLogger("NgsConnector")) { connect(m_timer, &QTimer::timeout, this, &NgsConnector::go); connect(m_playerCallback, &PlayerCallback::firstTick, this, &NgsConnector::onFirstTickFinished); // Ground Vehicle connectObserverGuezGroundVehicleObjectSignals(m_groundVehicleCallback); // Human connectObserverGuezHumanObjectSignals(m_humanCallback); // Observer / Controller connectObserverGuezObserverControllerObjectSignals(m_observerControllerCallback); // Mine Field connectObserverGuezMineFieldObjectSignals(m_minefieldCallback); // Mine Field Breach connectObserverGuezMineFieldBreachObjectSignals(m_minefieldBreachCallback); // Munition Detonation connect(m_munitionDetonationCallback, &ObserverMunitionDetonationCBNotifier::newMunitionDetonation, this, &NgsConnector::onNewMunitionDetonation); // Weapon Fire connect(m_weaponfireCallback, &ObserverWeaponFireCBNotifier::newWeaponFire, this, &NgsConnector::onNewWeaponFire); // Hit connect(m_hitCallback, &ObserverGuezHitCBNotifier::newGuezHit, this, &NgsConnector::onNewGuezHit); // Init connect(m_initCallback, &ObserverGuezInitCBNotifier::newGuezInit, this, &NgsConnector::onNewGuezInit); // Request Bite Report connect(m_requestBiteReportCallback, &ObserverGuezRequestBiteReportCBNotifier::newGuezRequestBiteReport, this, &NgsConnector::onNewGuezRequestBiteReport); // Bite Report connect(m_biteReportCallback, &ObserverGuezBiteReportCBNotifier::newGuezBiteReport, this, &NgsConnector::onNewGuezBiteReport); } NgsConnector::~NgsConnector() { uninitialize(); delete m_playerCallback; delete m_humanCallback; delete m_observerControllerCallback; delete m_groundVehicleCallback; delete m_minefieldCallback; delete m_minefieldBreachCallback; delete m_munitionDetonationCallback; delete m_weaponfireCallback; delete m_initCallback; delete m_hitCallback; delete m_requestBiteReportCallback; delete m_biteReportCallback; delete m_timer; } bool NgsConnector::initialize(ngs::IObserver::ProtocolType protocol, const QString &playerName, const QString &appDirPath) { QString swconf = appDirPath + QLatin1Char('/') + Settings::swConfFileName(); m_bInitialized = true; // NGS Initialisation try { m_master = &m_swadmin->getIFederationMaster(protocol, qPrintable(swconf), Settings::fomIdentifier().toStdString(), Settings::rtiIdentifier().toStdString()); // "", "MAK" } catch (const ngs::NGSSimWorldException &e) { ERROR_LOG(m_logger) << "caught exception during init (getIFederationMaster): " << e.print(); return false; } ngs::IPlayer *player = nullptr; switch (protocol) { case ngs::IObserver::HLA: { try { m_master->createFederation(m_fedName.toStdString()); } catch (const NGSException &e) { ERROR_LOG(m_logger) << "caught exception during init (createFederation): " << e.print(); return false; } player = &m_swadmin->getIPlayer(m_fedName.toStdString(), playerName.toStdString(), *m_playerCallback, Settings::swConfFileName().toLatin1().data(), Settings::fomIdentifier().toStdString(), Settings::rtiIdentifier().toStdString()); break; } case ngs::IObserver::CORBANOTIFY: case ngs::IObserver::DIS: { unsigned char federationId = m_fedName.toUShort(); m_master->createFederation(federationId); player = &m_swadmin->getIPlayer(federationId, playerName.toStdString(), *m_playerCallback, protocol, Settings::swConfFileName().toLatin1().data(), Settings::fomIdentifier().toStdString()); break; } } m_somPlayer = dynamic_cast(player); m_somPlayer->configureSimTime(ngs::IObserver::REALTIMECLOCK, ngs::SimTime(0, 0)); m_timer->start(10); return true; } void NgsConnector::uninitialize() { if (!m_bInitialized) return; m_bInitialized = false; m_timer->stop(); unsubscribeFromNgs(); if (m_swadmin) m_swadmin->resignInterface(); if (m_somPlayer) m_somPlayer->resignSimulatedWorld(); if (m_master) { m_master->destroyFederation(m_fedName.toStdString()); m_master->resignInterface(); m_master = nullptr; } } void NgsConnector::subscribeAtNgs() { m_enitityNumberCounter = 1; // restart m_eventCount = 1; INFO_LOG(m_logger) << "Subscribe at NGS - Guez Human Objects"; rpr2::IObserverGuezHumanObjClass &humanObjClass = m_somPlayer->getObserverGuezHumanObjClass(*m_humanCallback); humanObjClass.subscribeAllAttributes(); humanObjClass.subscribe(); INFO_LOG(m_logger) << "Subscribe at NGS - Guez ObserverController Objects"; rpr2::IObserverGuezObserverControllerObjClass &observerControllerObjClass = m_somPlayer->getObserverGuezObserverControllerObjClass(*m_observerControllerCallback); observerControllerObjClass.subscribeAllAttributes(); observerControllerObjClass.subscribe(); INFO_LOG(m_logger) << "Subscribe at NGS - Guez GroundVehicle Objects"; rpr2::IObserverGuezGroundVehicleObjClass &groundVehicleObjClass = m_somPlayer->getObserverGuezGroundVehicleObjClass(*m_groundVehicleCallback); groundVehicleObjClass.subscribeAllAttributes(); groundVehicleObjClass.subscribe(); INFO_LOG(m_logger) << "Subscribe at NGS - Guez MineField Objects"; rpr2::IObserverGuezMineFieldObjClass &minefieldObjClass = m_somPlayer->getObserverGuezMineFieldObjClass(*m_minefieldCallback); minefieldObjClass.subscribeAllAttributes(); minefieldObjClass.subscribe(); INFO_LOG(m_logger) << "Subscribe at NGS - Guez MineField Breach Objects"; rpr2::IObserverGuezMineFieldBreachObjClass &minefieldBreachObjClass = m_somPlayer->getObserverGuezMineFieldBreachObjClass(*m_minefieldBreachCallback); minefieldBreachObjClass.subscribeAllAttributes(); minefieldBreachObjClass.subscribe(); rpr2::IObserverMunitionDetonationIntClass &md = m_somPlayer->getObserverMunitionDetonationIntClass(*m_munitionDetonationCallback); md.subscribe(); rpr2::IObserverWeaponFireIntClass &wfic = m_somPlayer->getObserverWeaponFireIntClass(*m_weaponfireCallback); wfic.subscribe(); rpr2::IObserverGuezInitIntClass &giic = m_somPlayer->getObserverGuezInitIntClass(*m_initCallback); giic.subscribe(); rpr2::IObserverGuezHitIntClass &ghic = m_somPlayer->getObserverGuezHitIntClass(*m_hitCallback); ghic.subscribe(); rpr2::IObserverGuezRequestBiteReportIntClass &grbric = m_somPlayer->getObserverGuezRequestBiteReportIntClass(*m_requestBiteReportCallback); grbric.subscribe(); rpr2::IObserverGuezBiteReportIntClass &gbric = m_somPlayer->getObserverGuezBiteReportIntClass(*m_biteReportCallback); gbric.subscribe(); m_timer->start(); m_bCanPublishData = true; } void NgsConnector::unsubscribeFromNgs() { m_bCanPublishData = false; if (m_somPlayer) { INFO_LOG(m_logger) << "Unsubscribe at NGS - Guez Human Objects"; rpr2::IObserverGuezHumanObjClass &humanObjClass = m_somPlayer->getObserverGuezHumanObjClass(*m_humanCallback); humanObjClass.unsubscribe(); INFO_LOG(m_logger) << "Unsubscribe at NGS - Guez ObserverController Objects"; rpr2::IObserverGuezObserverControllerObjClass &observerControllerObjClass = m_somPlayer->getObserverGuezObserverControllerObjClass(*m_observerControllerCallback); observerControllerObjClass.unsubscribe(); INFO_LOG(m_logger) << "Unsubscribe at NGS - Guez GroundVehicle Objects"; rpr2::IObserverGuezGroundVehicleObjClass &groundVehicleObjClass = m_somPlayer->getObserverGuezGroundVehicleObjClass(*m_groundVehicleCallback); groundVehicleObjClass.unsubscribe(); INFO_LOG(m_logger) << "Unsubscribe at NGS - Guez MineField Objects"; rpr2::IObserverGuezMineFieldObjClass &minefieldObjClass = m_somPlayer->getObserverGuezMineFieldObjClass(*m_minefieldCallback); minefieldObjClass.unsubscribe(); INFO_LOG(m_logger) << "Unsubscribe at NGS - Guez MineField Breach Objects"; rpr2::IObserverGuezMineFieldBreachObjClass &minefieldBreachObjClass = m_somPlayer->getObserverGuezMineFieldBreachObjClass(*m_minefieldBreachCallback); minefieldBreachObjClass.unsubscribe(); } for (rpr2::IPlayerBaseEntityObjInstance *player : qAsConst(m_playerMap)) player->deleteObjectInstance(getSimTime()); m_playerMap.clear(); for (rpr2::IPlayerEnvironmentObjectObjInstance *environmentObject : qAsConst(m_environmentObjects)) environmentObject->deleteObjectInstance(getSimTime()); m_environmentObjects.clear(); for (auto it = m_observerPlayerInstances.cbegin(); it != m_observerPlayerInstances.cend(); ++it) emit virtualParticipantDeleted(it.key()); for (auto it = m_observerEnvironmentObjects.cbegin(); it != m_observerEnvironmentObjects.cend(); ++it) emit virtualParticipantDeleted(it.key()); m_observerPositions.clear(); m_newObjectsWithoutParticipantIdSet.clear(); m_observerPlayerInstances.clear(); m_observerEnvironmentObjects.clear(); qDeleteAll(m_damageModelMap); m_damageModelMap.clear(); m_enitityNumberCounter = 1; // restart m_eventCount = 1; } bool NgsConnector::addHuman(const ParticipantInformation &participantData, const rpr2::TacticalStatusStruct &state) { if (m_playerMap.contains(participantData.equipmentNo) || participantData.equipmentNo == 0) return false; DEBUG_LOG(getLogger("NgsConnector.Outbound.Startup")) << "addHuman(" << participantData.equipmentNo << ", " << participantData.partyId << ", " << participantData.typeStruct.toString() << ", " << state.TacticalStatus << ")"; rpr2::IPlayerGuezHumanObjClass &player = m_somPlayer->getPlayerGuezHumanObjClass(); rpr2::IPlayerGuezHumanObjInstance *playerInstance = &player.newGuezHumanObjInstance(); DamageModel dm(state); playerInstance->setDamageState(dm.damageState(-1)); playerInstance->setImmobilized(dm.immobilized(-1)); playerInstance->setFirePowerDisabled(dm.firePowerDisabled(-1)); playerInstance->setTacticalStatus(state); return addPhysicalEntity(playerInstance, participantData); } bool NgsConnector::addObserverController(const ParticipantInformation &participantData) { if (m_playerMap.contains(participantData.equipmentNo) || participantData.equipmentNo == 0) return false; DEBUG_LOG(getLogger("NgsConnector.Outbound.Startup")) << "addObserverController(" << participantData.equipmentNo << ", " << participantData.partyId << ", " << participantData.typeStruct.toString() << ")"; rpr2::IPlayerGuezObserverControllerObjClass &player = m_somPlayer->getPlayerGuezObserverControllerObjClass(); rpr2::IPlayerGuezObserverControllerObjInstance *playerInstance = &player.newGuezObserverControllerObjInstance(); return addPhysicalEntity(playerInstance, participantData); } bool NgsConnector::addGroundVehicle(const ParticipantInformation &participantData, const rpr2::TacticalStatusStruct &state) { if (m_playerMap.contains(participantData.equipmentNo) || participantData.equipmentNo == 0) return false; DEBUG_LOG(getLogger("NgsConnector.Outbound.Startup")) << "addGroundVehicle(" << participantData.equipmentNo << ", " << participantData.partyId << ", " << participantData.typeStruct.toString() << ", " << state.TacticalStatus << ")"; rpr2::IPlayerGuezGroundVehicleObjClass &player = m_somPlayer->getPlayerGuezGroundVehicleObjClass(); rpr2::IPlayerGuezGroundVehicleObjInstance *playerInstance = &player.newGuezGroundVehicleObjInstance(); DamageModel dm(state); playerInstance->setDamageState(dm.damageState(-1)); playerInstance->setImmobilized(dm.immobilized(-1)); playerInstance->setFirePowerDisabled(dm.firePowerDisabled(-1)); playerInstance->setTacticalStatus(state); return addPhysicalEntity(playerInstance, participantData); } bool NgsConnector::addBaseEntity(rpr2::IPlayerBaseEntityObjInstance *playerInstance, const ParticipantInformation &participantData) { if (m_playerMap.contains(participantData.equipmentNo) || participantData.equipmentNo == 0) return false; if (participantData.equipmentNo == 0) return false; IdentifierStruct is(participantData.equipmentNo); m_playerMap.insert(participantData.equipmentNo, playerInstance); playerInstance->setEntityType(participantData.typeStruct.toETS()); playerInstance->setEntityIdentifier(is.toEIS()); playerInstance->publish(getSimTime(participantData.position.msec)); DEBUG_LOG(getLogger("NgsConnector.Outbound.Startup")) << "Published Participant " << is.toString(); return true; } bool NgsConnector::addPhysicalEntity(rpr2::IPlayerPhysicalEntityObjInstance *playerInstance, const ParticipantInformation &playerData) { rpr2::MarkingStruct ms = toMarkingStruct(playerData.designator); playerInstance->setMarking(ms); rpr2::ForceIdentifierEnum8::ET forceId = m_ngsHandler->ppmHandler()->forceIdentifierFromPartyId(playerData.partyId); playerInstance->setForceIdentifier(forceId); rpr2::SpatialStruct ss(spatialStructFromWorldLocation(playerData.position.toWorldLocation())); playerInstance->setSpatial(ss); return addBaseEntity(playerInstance, playerData); } bool NgsConnector::removeBaseEntity(int eqNo) { if (!m_playerMap.contains(eqNo)) return false; DEBUG_LOG(getLogger("NgsConnector.Outbound.ShutDonw")) << "Participant " << eqNo << " removed."; m_playerMap[eqNo]->deleteObjectInstance(getSimTime()); return (m_playerMap.remove(eqNo) > 0); } bool NgsConnector::setParticipantName(int eqNo, const QString &designator) { rpr2::IPlayerPhysicalEntityObjInstance *playerInstance = dynamic_cast(m_playerMap.value(eqNo, nullptr)); if (!playerInstance) return false; DEBUG_LOG(getLogger("NgsConnector.Outbound.Participant")) << "Participant " << eqNo << " changed MARKING to " << designator; playerInstance->setMarking(toMarkingStruct(designator)); playerInstance->publish(getSimTime()); return true; } bool NgsConnector::setParticipantForce(int eqNo, const rpr2::ForceIdentifierEnum8::ET forceIdentifier) { rpr2::IPlayerPhysicalEntityObjInstance *playerInstance = dynamic_cast(m_playerMap.value(eqNo, nullptr)); if (!playerInstance) return false; DEBUG_LOG(getLogger("NgsConnector.Outbound.Participant")) << "Participant " << eqNo << "changed FORCE to " << forceIdentifier; playerInstance->setForceIdentifier(forceIdentifier); playerInstance->publish(getSimTime()); return true; } // DDS -> HLA void NgsConnector::sendMunitionDetonation(const ParticipantPosition &pos, int chargeTypeId, rpr2::ForceIdentifierEnum8::ET forceId) { DEBUG_LOG(getLogger("NgsConnector.Outbound.MunitionDetonation")) << "Munition Detonation: " << "Location: " << pos.toString() << ", Charge Type: " << TypeStruct(getEtsCharge(chargeTypeId)).toString() << ", fired by Force " << forceId << " (Force unused)"; rpr2::IPlayerMunitionDetonationIntClass &icx = m_somPlayer->getPlayerMunitionDetonationIntClass(); rpr2::IPlayerMunitionDetonationIntInstance *icb = &icx.newMunitionDetonationIntInstance(); icb->setDetonationLocation(pos.toWorldLocation()); icb->setDetonationResultCode(rpr2::DetonationResultCodeEnum8::Detonation); icb->setEventIdentifier(getEventIdentifierStruct()); // Todo: Ask Saab if and how we should encode the Party Id rpr2::RTIObjectIdStruct os; Q_UNUSED(forceId); os.ID = std::to_string(IdentifierStruct(0).toInt64()); // Own ExCon icb->setFiringObjectIdentifier(os); icb->setFuseType(rpr2::FuseTypeEnum16::Contact); icb->setMunitionType(getEtsCharge(chargeTypeId)); icb->setQuantityFired(1); icb->setRateOfFire(0); // icb.setTargetObjectIdentifier(""); icb->setWarheadType(rpr2::WarheadTypeEnum16::Kinetic); icb->publish(getSimTime(pos.msec)); } IdentifierStruct NgsConnector::getIdentifierStructbyEquipmentNo(const int eqId) const { const rpr2::IObserverBaseEntityObjInstance *beObject = nullptr; if (m_observerPlayerInstances.contains(eqId)) beObject = dynamic_cast(m_observerPlayerInstances[eqId]); else if (m_playerMap.contains(eqId)) beObject = m_playerMap[eqId]; if (!beObject || !beObject->isValidEntityIdentifier()) return IdentifierStruct(); return IdentifierStruct(beObject->getEntityIdentifier()); } void NgsConnector::sendParticipantInitRequest(const int eqId, bool resetManipulation, bool resetVulnerabilityState, bool restockCharges, qint64 simTimeMSecs) { IdentifierStruct is = getIdentifierStructbyEquipmentNo(eqId); if (is.isNull() || is.isOwnElement()) return; if (!resetVulnerabilityState) { WARNING_LOG(getLogger("NgsConnector.Outbound.GuezInit")) << "Request to " << (resetManipulation ? "reset Manipulation" : "") << (resetManipulation && restockCharges ? " and " : "") << (restockCharges ? "restock Charges" : "") << " without resetting the Player " << eqId << " to CombatReady. Not supported."; return; } // Hack as Saab returns with GuezHit -> Combat Ready, but doesn't change status if not restockCharges is true: restockCharges = true; DEBUG_LOG(getLogger("NgsConnector.Outbound.GuezInit")) << "Request to " // << (resetManipulation ? "reset Manipulation" : "") // << (resetManipulation && restockCharges ? " and " : "") << (restockCharges ? "restock Charges" : "") << " for Player " << eqId << " to Combat Ready. (Reset Manipulation is implicit for SAAB)."; rpr2::IPlayerGuezInitIntClass &icx = m_somPlayer->getPlayerGuezInitIntClass(); rpr2::IPlayerGuezInitIntInstance *icb = &icx.newGuezInitIntInstance(); icb->setActivateHelmetObservation(rpr2::ActivateHelmetObservationEnum8::NoChange); icb->setEntityIdentifier(is.toEIS()); icb->setResetPlayer(restockCharges ? rpr2::ResetPlayerEnum8::CombatReadyWithReammunition : rpr2::ResetPlayerEnum8::CombatReadyWithoutReammunition); icb->publish(getSimTime(simTimeMSecs)); } void NgsConnector::sendParticipantBiteRequest(const int eqId, bool withSelfTest, qint64 simTimeMsecs) { IdentifierStruct is = getIdentifierStructbyEquipmentNo(eqId); if (is.isNull() || is.isOwnElement()) return; rpr2::IPlayerGuezRequestBiteReportIntClass &icx = m_somPlayer->getPlayerGuezRequestBiteReportIntClass(); rpr2::IPlayerGuezRequestBiteReportIntInstance *icb = &icx.newGuezRequestBiteReportIntInstance(); icb->setBiteReportType(withSelfTest ? rpr2::BiteReportTypeEnum8::StatusAfterSelftest : rpr2::BiteReportTypeEnum8::CurrentStatus); icb->setEntityIdentifier(is.toEIS()); icb->publish(getSimTime(simTimeMsecs)); DEBUG_LOG(getLogger("NgsConnector.Outbound.GuezBiteRequest")) << "Request for Bite Report for Player " << eqId << " with" << (withSelfTest ? "" : "out") << " selftest."; } bool NgsConnector::guezMineField(const AreaEffectSimulation::MineArea &data) { IdentifierStruct is = IdentifierStruct::fromInt64(data.externalId()); if (!is.isOwnElement()) return false; rpr2::IPlayerGuezMineFieldObjClass &ic = m_somPlayer->getPlayerGuezMineFieldObjClass(); const QList icbs = m_environmentObjects.values(data.id()); // Check if it is expired or removed (now), then delete the segments. if (data.effectStateId() == AreaEffectSimulation::AesEffectState_def::EsExpired || data.effectStateId() == AreaEffectSimulation::AesEffectState_def::EsRemoved) { for (rpr2::IPlayerEnvironmentObjectObjInstance *item : icbs) item->deleteObjectInstance(ngs::SimTime(true)); // And we're done. Don't publish anything if called by startup. m_environmentObjects.remove(data.id()); return true; } if (data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsCreated && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsPrepared && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsActivated && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsPaused && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsReceived) return false; QVector minefields; minefields.reserve(std::max(icbs.size(), int(data.segments().size()))); for (rpr2::IPlayerEnvironmentObjectObjInstance *item : icbs) minefields.push_back(dynamic_cast(item)); int offset = 0; // If size increased, add the needed segments. while (minefields.size() < int(data.segments().size())) { rpr2::IPlayerGuezMineFieldObjInstance *icb = &ic.newGuezMineFieldObjInstance(); // Todo: Generate an external ID is.setEntityNumber(offset++ + 10 * data.id()); icb->setObjectIdentifier(is.toEIS()); icb->setObjectType(dummyEOS()); minefields.push_back(icb); } // If size is reduced, remove those that are too many. while (minefields.size() > int(data.segments().size())) { rpr2::IPlayerGuezMineFieldObjInstance *icb = minefields.takeLast(); if (icb) icb->deleteObjectInstance(ngs::SimTime(true)); DEBUG_LOG(getLogger("NgsConnector.Outbound.MineField")) << "Minefield: Id: " << is.toString() << ", Name: " << data.name() << ", number of segments reduced, removing earlier published segment"; } rpr2::ForceIdentifierEnum8::ET forceId = data.partyId().is_set() ? m_ngsHandler->ppmHandler()->forceIdentifierFromPartyId(data.partyId().get()) : rpr2::ForceIdentifierEnum8::Other; m_environmentObjects.remove(data.id()); for (int i = 0; i < minefields.size(); ++i) { rpr2::IPlayerGuezMineFieldObjInstance *icb = minefields.at(i); if (!icb) continue; // should not happen const AreaEffectSimulation::MineSegment &segment = data.segments()[i]; icb->setActivationTime(getClockTimeStruct(data.effectStart().is_set() ? data.effectStart().get() : 0)); icb->setDeactivated(data.effectStateId().underlying() != AreaEffectSimulation::AesEffectState_def::EsActivated); if (data.effectStateId().underlying() == AreaEffectSimulation::AesEffectState_def::EsExpired || data.effectStateId().underlying() == AreaEffectSimulation::AesEffectState_def::EsRemoved) icb->setPercentComplete(100); else icb->setPercentComplete(0); icb->setAreaEffectSimulationName(rpr2::OMT13string(data.name())); icb->setDuration(data.effectDuration() / 1000 / 60 / 60); icb->setMineType(getEtsCharge(data.chargeStruct().chargeTypeId())); icb->setMineCount((data.density() - 0.1) / 0.008); icb->setForceIdentifier(forceId); icb->setPointsData(positionArray(mineAreaSegment(segment, data.width(), data.density(), data.isCenterLine()))); icb->setObjectType(dummyEOS()); icb->publish(getSimTime(data.sampleTime())); DEBUG_LOG(getLogger("NgsConnector.Outbound.MineField")) << "Minefield: Id: " << is.toString() << ", Name: " << data.name() << ". Published Segment."; m_environmentObjects.insertMulti(data.id(), icb); } return true; } bool NgsConnector::guezMineFieldBreach(const AreaEffectSimulation::MineCorridor &data) { IdentifierStruct is = IdentifierStruct::fromInt64(data.externalId()); if (!is.isOwnElement()) return false; rpr2::IPlayerGuezMineFieldBreachObjClass &ic = m_somPlayer->getPlayerGuezMineFieldBreachObjClass(); const QList icbs = m_environmentObjects.values(data.id()); // Check if it is expired or removed (now), then delete the segments. if (data.effectStateId() == AreaEffectSimulation::AesEffectState_def::EsExpired || data.effectStateId() == AreaEffectSimulation::AesEffectState_def::EsRemoved) { for (rpr2::IPlayerEnvironmentObjectObjInstance *item : icbs) item->deleteObjectInstance(ngs::SimTime(true)); m_environmentObjects.remove(data.id()); // And we're done. Don't publish anything if called by startup. return true; } if (data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsCreated && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsReceived && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsPrepared && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsActivated && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsPaused) return false; QMap map; int c = 0; for (rpr2::IPlayerEnvironmentObjectObjInstance *item : icbs) map.insert(c++, dynamic_cast(item)); // If size increased, add the needed segments. if (map.size() < int(data.segments().size())) { int offset = 0; for (int i = map.size(); i < int(data.segments().size()); ++i) { rpr2::IPlayerGuezMineFieldBreachObjInstance *icb = &ic.newGuezMineFieldBreachObjInstance(); // TODO: why not is.setEntityNumber(offset++ + 10 * data.id()); as in guezMineField() ? const IdentifierStruct isSegment(offset++ + 10 * data.id()); icb->setObjectIdentifier(isSegment.toEIS()); map.insert(i, icb); } } // If size is reduced, remove those that are too many. for (int i = int(data.segments().size()); i < map.size(); ++i) { rpr2::IPlayerGuezMineFieldBreachObjInstance *icb = map.value(i); if (icb) icb->deleteObjectInstance(ngs::SimTime(true)); DEBUG_LOG(getLogger("NgsConnector.Outbound.MineFieldBreach")) << "Minefield Breach: Id: " << is.toString() << ", Name: " << data.name() << ", number of segments reduced, removing earlier published segment"; map.remove(i); } m_environmentObjects.remove(data.id()); for (int i = 0; i < map.size() - 1; ++i) { rpr2::IPlayerGuezMineFieldBreachObjInstance *icb = map.value(i); if (!icb) continue; // should not happen icb->setActivationTime(getClockTimeStruct(data.effectStart().is_set() ? data.effectStart().get() : 0)); icb->setAreaEffectSimulationName(rpr2::OMT13string(data.name())); icb->setPointsData(positionArray(mineCorridorSegment(data.segments().at(i), data.segments().at(i + 1), data.width()))); icb->setDeactivated(data.effectStateId().underlying() != AreaEffectSimulation::AesEffectState_def::EsActivated); if (data.effectStateId().underlying() == AreaEffectSimulation::AesEffectState_def::EsExpired || data.effectStateId().underlying() == AreaEffectSimulation::AesEffectState_def::EsRemoved) icb->setPercentComplete(100); else icb->setPercentComplete(0); icb->setObjectType(dummyEOS()); icb->publish(getSimTime(data.sampleTime())); DEBUG_LOG(getLogger("NgsConnector.Outbound.MineFieldBreach")) << "Minefield Breach: Id: " << is.toString() << ", Name: " << data.name() << ". Published Segment."; m_environmentObjects.insertMulti(data.id(), icb); } return true; } bool NgsConnector::guezNuclearAgent(const AreaEffectSimulation::NContamination &data) { IdentifierStruct is = IdentifierStruct::fromInt64(data.externalId()); if (!is.isOwnElement()) return false; rpr2::IPlayerGuezNuclearAgentAreaObjClass &ic = m_somPlayer->getPlayerGuezNuclearAgentAreaObjClass(); rpr2::IPlayerGuezNuclearAgentAreaObjInstance *icb = dynamic_cast(m_environmentObjects.value(data.id())); // Check if it is expired or removed (now), then delete the segments. if (icb && (data.effectStateId() == AreaEffectSimulation::AesEffectState_def::EsExpired || data.effectStateId() == AreaEffectSimulation::AesEffectState_def::EsRemoved)) { icb->deleteObjectInstance(ngs::SimTime(true)); m_environmentObjects.remove(data.id()); // And we're done. Don't publish anything if called by startup. return true; } if (data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsCreated && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsReceived && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsPrepared && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsActivated && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsPaused) return false; if (!icb) icb = &ic.newGuezNuclearAgentAreaObjInstance(); ParticipantPosition pos; pos.lon = data.impactPosition().lon(); pos.lat = data.impactPosition().lat(); // Todo: Generate an external ID is.setEntityNumber(10 * data.id()); icb->setObjectIdentifier(is.toEIS()); icb->setActivationTime(getClockTimeStruct(data.effectStart().is_set() ? data.effectStart().get() : 0)); icb->setAreaEffectSimulationName(rpr2::OMT13string(data.name())); icb->setDuration(data.effectDuration()); icb->setDetonationLocation(pos.toWorldLocation()); icb->setPointsData(positionArray(nContamination(data))); icb->setRadius(data.impactRadius()); icb->setObjectType(dummyEOS()); icb->publish(getSimTime(data.sampleTime())); m_environmentObjects.insert(data.id(), icb); DEBUG_LOG(getLogger("NgsConnector.Outbound.NuclearAgentArea")) << "Nuclear Agent Area: Id: " << is.toString() << ", Name: " << data.name() << ". Published Area."; return true; } bool NgsConnector::guezChemicalAgent(const AreaEffectSimulation::BCContamination &data) { IdentifierStruct is = IdentifierStruct::fromInt64(data.externalId()); if (!is.isOwnElement()) return false; rpr2::IPlayerGuezChemicalAgentAreaObjClass &ic = m_somPlayer->getPlayerGuezChemicalAgentAreaObjClass(); rpr2::IPlayerGuezChemicalAgentAreaObjInstance *icb = dynamic_cast(m_environmentObjects.value(data.id())); // Check if it is expired or removed (now), then delete the item. if (icb && (data.effectStateId() == AreaEffectSimulation::AesEffectState_def::EsExpired || data.effectStateId() == AreaEffectSimulation::AesEffectState_def::EsRemoved)) { icb->deleteObjectInstance(ngs::SimTime(true)); m_environmentObjects.remove(data.id()); // And we're done. Don't publish anything if called by startup. return true; } if (data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsCreated && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsReceived && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsPrepared && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsActivated && data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsPaused) return false; if (!icb) icb = &ic.newGuezChemicalAgentAreaObjInstance(); ParticipantPosition pos; pos.lon = data.impactPosition().lon(); pos.lat = data.impactPosition().lat(); // Better: Don't use 400x, obtain volatility type from chargeType-Topic rpr2::ChemicalAgentTypeEnum8::ET type = rpr2::ChemicalAgentTypeEnum8::Other; switch (data.chargeStruct().chargeTypeId()) { case 341: type = rpr2::ChemicalAgentTypeEnum8::Sarin; break; case 342: type = rpr2::ChemicalAgentTypeEnum8::VX; break; case 343: type = rpr2::ChemicalAgentTypeEnum8::SLost; break; default: type = rpr2::ChemicalAgentTypeEnum8::Other; } is.setEntityNumber(10 * data.id()); icb->setObjectIdentifier(is.toEIS()); icb->setDeactivated(data.effectStateId().underlying() != AreaEffectSimulation::AesEffectState_def::EsActivated); icb->setActivationTime(getClockTimeStruct(data.effectStart().is_set() ? data.effectStart().get() : 0)); icb->setAreaEffectSimulationName(rpr2::OMT13string(data.name())); icb->setDuration(data.effectDuration() / 1000 / 60); // Duration in Minutes... icb->setDetonationLocation(pos.toWorldLocation()); if (data.effectStateId().underlying() == AreaEffectSimulation::AesEffectState_def::EsExpired || data.effectStateId().underlying() == AreaEffectSimulation::AesEffectState_def::EsRemoved) icb->setPercentComplete(100); else icb->setPercentComplete(0); // PointsData will be set by AesItemViewChanged() icb->setChemicalAgentType(type); icb->setRadius(data.impactRadius()); rpr2::ForceIdentifierEnum8::ET forceId = data.partyId().is_set() ? m_ngsHandler->ppmHandler()->forceIdentifierFromPartyId(data.partyId().get()) : rpr2::ForceIdentifierEnum8::Other; icb->setForceIdentifier(forceId); icb->setIsPermanentAgent(type != rpr2::ChemicalAgentTypeEnum8::Sarin); icb->setObjectType(dummyEOS()); icb->publish(getSimTime(data.sampleTime())); m_environmentObjects.insert(data.id(), icb); DEBUG_LOG(getLogger("NgsConnector.Outbound.ChemicalAgentArea")) << "Chemical Agent Area: Id: " << is.toString() << ", Name: " << data.name() << ". Published Area."; return true; } bool NgsConnector::setItemForm(int aesItemId, int aesTypeId, const std::vector &form) { if (form.size() == 0) m_arealObjectForms.remove(aesItemId); else m_arealObjectForms.insert(aesItemId, form); if (!m_environmentObjects.contains(aesItemId)) return true; std::vector reducedForm; switch (aesTypeId) { case AesType_def::AtBCContamination: reducedForm = bcReduction(form); break; case AesType_def::AtMineCorridor: case AesType_def::AtExplosionArea: case AesType_def::AtExplosionSingle: case AesType_def::AtMineArea: case AesType_def::AtNContamination: // Change only occurs when changing base data. return true; case AesType_def::AtUnknown: case AesType_def::AtDangerArea: case AesType_def::AtFunctionalArea: case AesType_def::AtBorderline: case AesType_def::AtTechnicalArea: case AesType_def::AtReconArea: case AesType_def::AtDusSingleMine: case AesType_def::AtExplosionAreaSingleExplosions: case AesType_def::AtMineAreaSingleMines: case AesType_def::AtGasPipe: // Not published. return true; } rpr2::IPlayerArealObjectObjInstance *ao = dynamic_cast(m_environmentObjects.value(aesItemId)); if (!ao) return false; ao->setPointsData(positionArray(reducedForm)); ao->publish(getSimTime()); // No time given, take current time // Note: Case returns whenever Type is NOT Chemical Agent. If that changes, we have to update the logger channel DEBUG_LOG(getLogger("NgsConnector.Outbound.ChemicalAgentArea")) << "Chemical Agent Area: Id: " << aesItemId << ". Shape Changed. Updated on HLA."; return true; } bool NgsConnector::setWorldLocation(int eqNo, const ParticipantPosition &pos, int eqNoHost) { rpr2::IPlayerPhysicalEntityObjInstance *playerInstance = dynamic_cast(m_playerMap.value(eqNo, nullptr)); if (!playerInstance) return false; rpr2::SpatialStruct ss(spatialStructFromWorldLocation(pos.toWorldLocation())); rpr2::ConstituentPartRelationshipStruct cprs; cprs.Nature = rpr2::ConstituentPartNatureEnum16::Other; cprs.Position = rpr2::ConstituentPartPositionEnum16::Inside; rpr2::RTIObjectIdStruct rtiObjectIdHost; rpr2::RelativeRangeBearingStruct rrbs; rrbs.Bearing = 0.0; rrbs.Range = 0.0; // NGS Codegen does not automatically fill structure with 8 Bytes, when NamedLocationStruct is not defined with // a valid alternative: rpr2::NamedLocationStruct__StationName nlssn(rpr2::ConstituentPartStationNameEnum16::OnStationRangeBearing); nlssn.RelativeRangeAndBearing(rrbs); rpr2::NamedLocationStruct nlStruct; nlStruct.StationNumber = 0; nlStruct.StationName__A__Alternatives = nlssn; rpr2::EntityIdentifierStruct eis; eis.EntityNumber = 0; eis.FederateIdentifier.ApplicationID = 0; eis.FederateIdentifier.SiteID = 0; // Mounted? if (eqNoHost > 0) { // Get host from our players const rpr2::IObserverBaseEntityObjInstance *observerInstanceHost = m_playerMap.value(eqNoHost); // Not ours? Get from their players (our observed ones) if (!observerInstanceHost) observerInstanceHost = dynamic_cast(m_observerPlayerInstances.value(eqNoHost)); // If found and valid data, fill IsPartOf struct and apply to player if (observerInstanceHost && observerInstanceHost->isValidObjectName() && observerInstanceHost->isValidEntityIdentifier()) { cprs.Position = rpr2::ConstituentPartPositionEnum16::Inside; rtiObjectIdHost.ID = observerInstanceHost->getObjectName(); eis = observerInstanceHost->getEntityIdentifier(); } } rpr2::IsPartOfStruct ipos; ipos.HostEntityIdentifier = eis; ipos.HostRTIObjectIdentifier = rtiObjectIdHost; ipos.NamedLocation = nlStruct; ipos.Relationship = cprs; playerInstance->setIsPartOf(ipos); playerInstance->setSpatial(ss); playerInstance->publish(getSimTime(pos.msec)); DEBUG_LOG(getLogger("NgsConnector.Outbound.Player.Position")) << "Player: " << eqNo << ", Position: " << pos.toString() << ", Time: " << pos.msec << ", Mounted on: " << eqNoHost; DEBUG_LOG(getLogger("NgsConnector.Outbound.Player.Orientation")) << "Player: " << eqNo << ", Orientation(Phi, Psi, Theta): " << ss.DeadReckoningAlgorithm__A__Alternatives.SpatialStatic().Orientation.Phi << ", " << ss.DeadReckoningAlgorithm__A__Alternatives.SpatialStatic().Orientation.Psi << ", " << ss.DeadReckoningAlgorithm__A__Alternatives.SpatialStatic().Orientation.Theta; return true; } ngs::SimTime NgsConnector::getSimTime(qint64 msecsSinceEpoch) { if (msecsSinceEpoch == 0) msecsSinceEpoch = QDateTime::currentDateTimeUtc().toMSecsSinceEpoch(); return ngs::SimTime(msecsSinceEpoch / 1000, (msecsSinceEpoch % 1000) * 1000); } ngs::SimTime NgsConnector::getSimTime(const QDateTime &dateTime) { return getSimTime(dateTime.toMSecsSinceEpoch()); } void NgsConnector::go() { if (!m_somPlayer) return; try { m_somPlayer->go(ngs::IObserver::GOSTEP); } catch (const NGSException &ex) { ERROR_LOG(m_logger) << "NGS Exception caught in Event Loop" << ex.print(); } catch (const std::exception &e) { ERROR_LOG(m_logger) << "Standard Exception caught in Event Loop: " << e.what(); } catch (...) { // ERROR_LOG(m_logger) << "Generic Exception caught in Event Loop"; } } void NgsConnector::onFirstTickFinished() { unsubscribeFromNgs(); m_bCanPublishData = true; subscribeAtNgs(); emit firstTickFinished(); } void NgsConnector::connectObserverObjectSignals(ObserverObjectCB *entity) { connect(entity, &ObserverObjectCB::newObjectInstance2, this, &NgsConnector::addObserverObjectInstance, Qt::UniqueConnection); connect(entity, &ObserverObjectCB::objectInstanceDeleted, this, &NgsConnector::removeObserverObjectInstance, Qt::UniqueConnection); } void NgsConnector::connectObserverBaseEntityObjectSignals(ObserverBaseEntityObjectCB *entity) { connectObserverObjectSignals(entity); connect(entity, &ObserverBaseEntityObjectCB::entityIdentifierChanged, this, &NgsConnector::onEntityIdentifierChanged, Qt::UniqueConnection); connect(entity, &ObserverBaseEntityObjectCB::spatialChanged, this, &NgsConnector::onSpatialChanged, Qt::UniqueConnection); connect(entity, &ObserverBaseEntityObjectCB::entityTypeChanged, this, &NgsConnector::onEntityTypeChanged, Qt::UniqueConnection); connect(entity, &ObserverBaseEntityObjectCB::isPartOfChanged, this, &NgsConnector::onIsPartOfChanged, Qt::UniqueConnection); } void NgsConnector::connectObserverPhysicalEntityObjectSignals(ObserverPhysicalEntityObjectCB *entity) { connectObserverBaseEntityObjectSignals(entity); connect(entity, &ObserverPhysicalEntityObjectCB::markingChanged, this, &NgsConnector::onMarkingChanged, Qt::UniqueConnection); connect(entity, &ObserverPhysicalEntityObjectCB::forceIdentifierChanged, this, &NgsConnector::onForceIdentifierChanged, Qt::UniqueConnection); } void NgsConnector::connectObserverPlatformObjectSignals(ObserverPlatformObjectCB *entity) { connectObserverPhysicalEntityObjectSignals(entity); // No extra signals } void NgsConnector::connectObserverGroundVehicleObjectSignals(ObserverGroundVehicleObjectCB *entity) { connectObserverPlatformObjectSignals(entity); // No extra signals } void NgsConnector::connectObserverGuezGroundVehicleObjectSignals(ObserverGuezGroundVehicleObjectCB *entity) { connectObserverGroundVehicleObjectSignals(entity); connect(entity, &ObserverGuezGroundVehicleObjectCB::tacticalStatusChanged, this, &NgsConnector::onTacticalStatusChanged, Qt::UniqueConnection); } void NgsConnector::connectObserverLifeformObjectSignals(ObserverLifeformObjectCB *entity) { connectObserverPhysicalEntityObjectSignals(entity); // No extra signals } void NgsConnector::connectObserverHumanObjectSignals(ObserverHumanObjectCB *entity) { connectObserverLifeformObjectSignals(entity); // No extra signals } void NgsConnector::connectObserverGuezHumanObjectSignals(ObserverGuezHumanObjectCB *entity) { connectObserverHumanObjectSignals(entity); connect(entity, &ObserverGuezHumanObjectCB::tacticalStatusChanged, this, &NgsConnector::onTacticalStatusChanged, Qt::UniqueConnection); } void NgsConnector::connectObserverGuezObserverControllerObjectSignals(ObserverGuezObserverControllerObjectCB *entity) { connectObserverHumanObjectSignals(entity); // No extra signals } void NgsConnector::connectObserverEnvironmentObjectObjectSignals(ObserverEnvironmentObjectObjectCB *entity) { connectObserverObjectSignals(entity); connect(entity, &ObserverEnvironmentObjectObjectCB::objectIdentifierChanged, this, &NgsConnector::onObjectIdentifierChanged, Qt::UniqueConnection); connect(entity, &ObserverEnvironmentObjectObjectCB::forceIdentifierChangedEO, this, &NgsConnector::onForceIdentifierChangedEO, Qt::UniqueConnection); connect(entity, &ObserverEnvironmentObjectObjectCB::spatialChanged, this, &NgsConnector::onSpatialChanged, Qt::UniqueConnection); } void NgsConnector::connectObserverArealObjectObjectSignals(ObserverArealObjectObjectCB *entity) { connectObserverEnvironmentObjectObjectSignals(entity); connect(entity, &ObserverArealObjectObjectCB::pointsDataChanged, this, &NgsConnector::onPointsDataChanged, Qt::UniqueConnection); connect(entity, &ObserverArealObjectObjectCB::percentCompleteChanged, this, &NgsConnector::onPercentCompleteChanged, Qt::UniqueConnection); connect(entity, &ObserverArealObjectObjectCB::deactivatedChanged, this, &NgsConnector::onDeactivatedChanged, Qt::UniqueConnection); } void NgsConnector::connectObserverPointObjectObjectSignals(ObserverPointObjectObjectCB *entity) { connectObserverEnvironmentObjectObjectSignals(entity); } void NgsConnector::connectObserverOtherArealObjectObjectSignals(ObserverOtherArealObjectObjectCB *entity) { connectObserverArealObjectObjectSignals(entity); } void NgsConnector::connectObserverOtherPointObjectObjectSignals(ObserverOtherPointObjectObjectCB *entity) { connectObserverPointObjectObjectSignals(entity); } void NgsConnector::connectObserverGuezMineFieldObjectSignals(ObserverGuezMineFieldObjectCB *entity) { connectObserverOtherArealObjectObjectSignals(entity); connect(entity, &ObserverGuezMineFieldObjectCB::areaEffectSimulationNameChanged, this, &NgsConnector::onAreaEffectSimulationNameChanged, Qt::UniqueConnection); connect(entity, &ObserverGuezMineFieldObjectCB::activationTimeChanged, this, &NgsConnector::onActivationTimeChanged, Qt::UniqueConnection); connect(entity, &ObserverGuezMineFieldObjectCB::durationChanged, this, &NgsConnector::onDurationChanged, Qt::UniqueConnection); connect(entity, &ObserverGuezMineFieldObjectCB::mineTypeChanged, this, &NgsConnector::onMineTypeChanged, Qt::UniqueConnection); } void NgsConnector::connectObserverGuezMineFieldBreachObjectSignals(ObserverGuezMineFieldBreachObjectCB *entity) { connectObserverOtherArealObjectObjectSignals(entity); connect(entity, &ObserverGuezMineFieldBreachObjectCB::areaEffectSimulationNameChanged, this, &NgsConnector::onAreaEffectSimulationNameChanged, Qt::UniqueConnection); connect(entity, &ObserverGuezMineFieldBreachObjectCB::activationTimeChanged, this, &NgsConnector::onActivationTimeChanged, Qt::UniqueConnection); } void NgsConnector::addObserverObjectInstance(const ngs::IObserverObjectInstance &obj) { // Put it into the Observer list later on, when the slot "onEntityIdentifierChanged()" is called // Then we can be sure, the EntityIdentifier is definetely set. const rpr2::IObserverBaseEntityObjInstance *beObject = dynamic_cast(&obj); if (beObject && beObject->isValidEntityIdentifier()) { IdentifierStruct is(beObject->getEntityIdentifier()); DEBUG_LOG(getLogger("NgsConnector.Inbound.Player")) << "Player: " << is.toString() << " added."; m_newObjectsWithoutParticipantIdSet.insert(&obj); onEntityIdentifierChanged(beObject->getEntityIdentifier(), beObject); } const rpr2::IObserverEnvironmentObjectObjInstance *eoObject = dynamic_cast(&obj); if (eoObject && eoObject->isValidObjectIdentifier()) { IdentifierStruct is(eoObject->getObjectIdentifier()); DEBUG_LOG(getLogger("NgsConnector.Inbound.Object")) << "Object: " << is.toString() << " added."; m_newObjectsWithoutParticipantIdSet.insert(&obj); onObjectIdentifierChanged(eoObject->getObjectIdentifier(), eoObject); } } void NgsConnector::removeObserverObjectInstance(const ngs::IObserverObjectInstance &obj) { const rpr2::IObserverBaseEntityObjInstance *beObject = dynamic_cast(&obj); const rpr2::IObserverEnvironmentObjectObjInstance *eoObject = dynamic_cast(&obj); IdentifierStruct is; if (beObject && beObject->isValidEntityIdentifier()) { is = IdentifierStruct(beObject->getEntityIdentifier()); DEBUG_LOG(getLogger("NgsConnector.Inbound.Player")) << "Player: " << is.toString() << " removed."; } else if (eoObject && eoObject->isValidObjectIdentifier()) { is = IdentifierStruct(eoObject->getObjectIdentifier()); DEBUG_LOG(getLogger("NgsConnector.Inbound.Object")) << "Object: " << is.toString() << " removed."; } else return; if (is.isOwnElement()) { // TODO: handle signal emit virtualParticipantDeleted(is.entityNumber()); } // really neccessary? //int *ptr = static_cast(obj.getUserObjectID()); //delete[] ptr; m_newObjectsWithoutParticipantIdSet.remove(&obj); if (beObject) { m_observerPlayerInstances.remove(is.entityNumber()); auto it = m_damageModelMap.find(is.entityNumber()); if (it != m_damageModelMap.end()) { delete it.value(); m_damageModelMap.erase(it); } } if (eoObject) { m_observerEnvironmentObjects.remove(is.entityNumber()); emit aesRemoveItem(is.toInt64()); } } void NgsConnector::ensureDamageModelExists(const IdentifierStruct &is) { if (m_damageModelMap.contains(is.entityNumber())) return; DamageModel *dmgModel(new DamageModel(is)); // connect(dmgModel, &DamageModel::damageStateChanged, // this, &NgsConnector::onDamageStateChangedTimeout); connect(dmgModel, &DamageModel::playerManipulationChanged, this, &NgsConnector::playerManipulationChanged); m_damageModelMap.insert(is.entityNumber(), dmgModel); } rpr2::MarkingStruct NgsConnector::toMarkingStruct(const QString &designator) { rpr2::MarkingStruct ms; const QString string = designator.leftJustified(ms.MarkingData.size(), QLatin1Char('\0'), true); const QByteArray ba = string.toUtf8(); for (size_t i = 0; i < ms.MarkingData.size(); ++i) ms.MarkingData[i] = ba.at(i); // this is wrong but there is no 'UTF8' ... ms.MarkingEncodingType = rpr2::MarkingEncodingEnum8::ASCII; return ms; } rpr2::ClockTimeStruct NgsConnector::getClockTimeStruct(qint64 msecsSinceEpoch) { // Todo: Wrong! rpr2::ClockTimeStruct cts; // New: // cts.Hours = msecsSinceEpoch / (1000LL * 60 * 60); // qint64 msecsAfterTheHour = msecsSinceEpoch - (cts.Hours * 1000 * 60 * 60); // double portionOfAnHour = msecsAfterTheHour / (1000 * 60 * 60); // cts.TimePastTheHour = lrint(portionOfAnHour * (2LL<<31); cts.Hours = msecsSinceEpoch / (1000 * 60 * 60); cts.TimePastTheHour = msecsSinceEpoch - (cts.Hours * 1000 * 60 * 60) ; // This is wrong! return cts; } qint64 NgsConnector::getMSecsSinceEpoch(const rpr2::ClockTimeStruct &cts) { // Todo: Wrong! qint64 time = 0; time += (1000LL * 60 * 60 * cts.Hours); time += cts.TimePastTheHour; // Contains MSecs at the moment // New: // time += 1000LL * 60 * 60 * (cts.TimePastTheHour / (2LL<<31)) ; return time; } rpr2::EntityTypeStruct NgsConnector::getEtsCharge(int chargeType) const { const auto ct = m_ngsHandler->ppmHandler()->chargeTypeFromId(chargeType); if (ct.disEntity().size() == 0) return TypeStruct::fromString(QLatin1String("2.0.0.0.0.0.1")).toETS(); // Default return TypeStruct::fromString(QString::fromStdString(ct.disEntity())).toETS(); } template rpr2::ArrayOfWorldLocationStructDynamicHLAlengthlessVarArray NgsConnector::positionArray(const T &data) { rpr2::ArrayOfWorldLocationStructDynamicHLAlengthlessVarArray positions; if (data.size() == 0) return positions; positions.resize(data.size()); unsigned int i = 0; for (const auto &item : data) positions[i++] = ParticipantPosition(item).toWorldLocation(); return positions; } QPolygonF NgsConnector::mineAreaSegment(const AreaEffectSimulation::MineSegment &segment, double width, double density, bool bIsCenterline) { if (qFuzzyIsNull(density)) return QPolygonF(); LtsCoordTrans::UtmCoordinate startPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(segment.positionStart().lon(), segment.positionStart().lat()); LtsCoordTrans::UtmCoordinate endPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(segment.positionEnd().lon(), segment.positionEnd().lat(), startPositionUtm.zone); const QLineF line({startPositionUtm.x, startPositionUtm.y}, {endPositionUtm.x, endPositionUtm.y}); const auto yOfs1 = bIsCenterline ? (width / 2) : -width; const auto yOfs2 = bIsCenterline ? -(width / 2) : 0; QPolygonF polyUtm = QPolygonF({QPointF(0, yOfs2), QPointF(0, yOfs1), QPointF(line.length(), yOfs1), QPointF(line.length(), yOfs2) }); // Compass, not mathematical! polyUtm = fromRotation(0.0 - line.angle()).map(polyUtm); const QPolygonF poly(utmToGeogrphic(polyUtm, startPositionUtm)); return poly; } QPolygonF NgsConnector::mineCorridorSegment(const Core::GeoPosition2D &start, const Core::GeoPosition2D &end, double width) { QPointF startPoint(start.lon(), start.lat()); QPointF endPoint(end.lon(), end.lat()); LtsCoordTrans::UtmCoordinate startPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(startPoint); LtsCoordTrans::UtmCoordinate endPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(endPoint, startPositionUtm.zone); QLineF lengthVector(QPointF(startPositionUtm.x, startPositionUtm.y), QPointF(endPositionUtm.x, endPositionUtm.y)); QPolygonF segment = QPolygonF({QPointF(0, width), QPointF(lengthVector.length(), width), QPointF(lengthVector.length(), 0), QPointF(0, 0) }); segment.translate(QPointF(0, -(width / 2))); segment = fromRotation(0 - lengthVector.angle()).map(segment); return utmToGeogrphic(segment, startPositionUtm); } template static inline T compass2rad(T degree) { static_assert(!std::is_integral::value, "Floating Point Value Required! Integer may be in Centidegree?!?"); return (M_PI / 2.0) - (degree * M_PI / 180.0); } template static inline T deg2rad(T degree) { static_assert(!std::is_integral::value, "Floating Point Value Required! Integer may be in Centidegree?!?"); return degree * M_PI / 180.0; } QPolygonF NgsConnector::nContamination(const AreaEffectSimulation::NContamination &awf) { // Special Case: BorderLengthInner == 0 LtsCoordTrans::UtmCoordinate impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(awf.impactPosition().lon(), awf.impactPosition().lat()); if (qFuzzyIsNull(awf.borderLengthInner())) { if (!qFuzzyIsNull(awf.borderLengthOuter())) return QPolygonF(); QPolygonF poly; poly.reserve(8); for (int i = 0; i < 360; i += 45) { double angle = compass2rad(double(i)); poly << impactPositionUtm.geographic(QPointF(cos(angle) * awf.impactRadius(), sin(angle) * awf.impactRadius())); } return poly; } if (awf.impactRadius() > awf.borderLengthInner()) return QPolygonF(); double halfSpanAngleRad = deg2rad(awf.spanAngle()) / 2.0; // Angle between MCC and MCT at C double acos_val = awf.impactRadius() / awf.borderLengthInner(); // Adjacent / Hypotenuse (Angle between SCT and MCT at C) double scAngleRad = acos(acos_val) + halfSpanAngleRad; // Thus, Angle between MCC and SCT at C. ("Small Circle Angle") // Calculate Points: QPointF sct(cos(scAngleRad) * awf.impactRadius(), sin(scAngleRad) * awf.impactRadius()); QPointF scb(cos(scAngleRad) * awf.impactRadius(), -sin(scAngleRad) * awf.impactRadius()); QPointF mct(cos(halfSpanAngleRad) * (awf.borderLengthInner()), sin(halfSpanAngleRad) * (awf.borderLengthInner())); QPointF mcb(cos(halfSpanAngleRad) * (awf.borderLengthInner()), -sin(halfSpanAngleRad) * (awf.borderLengthInner())); QPointF bct(cos(halfSpanAngleRad) * (awf.borderLengthInner() + awf.borderLengthOuter()), sin(halfSpanAngleRad) * (awf.borderLengthInner() + awf.borderLengthOuter())); QPointF bcb(cos(halfSpanAngleRad) * (awf.borderLengthInner() + awf.borderLengthOuter()), -sin(halfSpanAngleRad) * (awf.borderLengthInner() + awf.borderLengthOuter())); QPolygonF polyUtm; polyUtm.reserve(8); polyUtm << sct; polyUtm << mct; polyUtm << bct; // In For polyUtm << QPointF(awf.borderLengthInner() + awf.borderLengthOuter(), 0); polyUtm << bcb; // May not be in for, if (spanAngle % steps != 0) polyUtm << mcb; polyUtm << scb; // In For polyUtm << QPointF(-awf.impactRadius(), 0); polyUtm = fromRotation(90.0 - awf.rotationAngle()).map(polyUtm); // The cunstructed poly ist heading eastwards but must be rotated as from north const QPolygonF poly(utmToGeogrphic(polyUtm, impactPositionUtm)); return poly; } std::vector NgsConnector::bcReduction(const std::vector &form) { std::vector vec; vec.resize(8); if (form.size() == 72) // 0, 45, 90, 135, 180, 225, 270, 315° (Kreis!) { vec[0] = form[0]; vec[1] = form[9]; vec[2] = form[18]; vec[3] = form[27]; vec[4] = form[36]; vec[5] = form[45]; vec[6] = form[54]; vec[7] = form[63]; } else if (form.size() == 74) // 0, 60, 120, 180° (rechts), 180, 240, 300, 360° (links) (Wurscht!) { vec[0] = form[0]; vec[1] = form[12]; vec[2] = form[24]; vec[3] = form[36]; vec[4] = form[37]; vec[5] = form[49]; vec[6] = form[61]; vec[7] = form[73]; } return vec; } rpr2::ArrayOfHLAoctetN_slsh_AperfectalwaysN_slsh_ADynamicHLApaddingTo32Array NgsConnector::triggeredImpactsArray(const std::vector &data) { rpr2::ArrayOfHLAoctetN_slsh_AperfectalwaysN_slsh_ADynamicHLApaddingTo32Array impacts; if (data.size() == 0) return impacts; impacts.resize(data.size()); unsigned int i = 0; for (const auto &item : data) impacts[i++] = static_cast(item); return impacts; } rpr2::HitResultEnum16::ET NgsConnector::getHitResultEnum(const ProjectDataManagement::VulnerabilityState &ts) { switch (ts.underlying()) { case ProjectDataManagement::VulnerabilityState_def::CombatReady: return rpr2::HitResultEnum16::Reset; case ProjectDataManagement::VulnerabilityState_def::LightDamage: return rpr2::HitResultEnum16::LightDamage; case ProjectDataManagement::VulnerabilityState_def::MediumDamage: return rpr2::HitResultEnum16::MediumDamage; case ProjectDataManagement::VulnerabilityState_def::HeavyDamage: return rpr2::HitResultEnum16::HeavyDamage; case ProjectDataManagement::VulnerabilityState_def::MobilityKill: return rpr2::HitResultEnum16::MobilityKill; case ProjectDataManagement::VulnerabilityState_def::VisibleMobilityKill: return rpr2::HitResultEnum16::MobilityKillVisible; case ProjectDataManagement::VulnerabilityState_def::WeaponSystemDamage: return rpr2::HitResultEnum16::WeaponDamage; case ProjectDataManagement::VulnerabilityState_def::VisibleWeaponSystemDamage: return rpr2::HitResultEnum16::WeaponDamageVisible; case ProjectDataManagement::VulnerabilityState_def::CatastrophicKill: return rpr2::HitResultEnum16::TotalKill; case ProjectDataManagement::VulnerabilityState_def::TemporarySeverance: return rpr2::HitResultEnum16::TemporaryFailure; case ProjectDataManagement::VulnerabilityState_def::NearMiss: return rpr2::HitResultEnum16::NearMiss; case ProjectDataManagement::VulnerabilityState_def::HitWithNoEffect: return rpr2::HitResultEnum16::HitWithNoEffect; case ProjectDataManagement::VulnerabilityState_def::NContamination: case ProjectDataManagement::VulnerabilityState_def::BContamination: case ProjectDataManagement::VulnerabilityState_def::CContamination: return rpr2::HitResultEnum16::Contaminated; default: return rpr2::HitResultEnum16::Reset; } return rpr2::HitResultEnum16::Reset; } ProjectDataManagement::VulnerabilityState NgsConnector::getTacticalStatusEnum(const rpr2::HitResultEnum16::ET &hr) { switch (hr) { case rpr2::HitResultEnum16::Reset: return ProjectDataManagement::VulnerabilityState_def::CombatReady; case rpr2::HitResultEnum16::LightDamage: return ProjectDataManagement::VulnerabilityState_def::LightDamage; case rpr2::HitResultEnum16::MediumDamage: return ProjectDataManagement::VulnerabilityState_def::MediumDamage; case rpr2::HitResultEnum16::HeavyDamage: return ProjectDataManagement::VulnerabilityState_def::HeavyDamage; case rpr2::HitResultEnum16::MobilityKill: return ProjectDataManagement::VulnerabilityState_def::MobilityKill; case rpr2::HitResultEnum16::MobilityKillVisible: return ProjectDataManagement::VulnerabilityState_def::VisibleMobilityKill; case rpr2::HitResultEnum16::WeaponDamage: return ProjectDataManagement::VulnerabilityState_def::WeaponSystemDamage; case rpr2::HitResultEnum16::WeaponDamageVisible: return ProjectDataManagement::VulnerabilityState_def::VisibleWeaponSystemDamage; case rpr2::HitResultEnum16::TotalKill: return ProjectDataManagement::VulnerabilityState_def::CatastrophicKill; case rpr2::HitResultEnum16::CheatingKill: return ProjectDataManagement::VulnerabilityState_def::CatastrophicKill; case rpr2::HitResultEnum16::NearMiss: return ProjectDataManagement::VulnerabilityState_def::NearMiss; case rpr2::HitResultEnum16::HitWithNoEffect: return ProjectDataManagement::VulnerabilityState_def::HitWithNoEffect; case rpr2::HitResultEnum16::Contaminated: return ProjectDataManagement::VulnerabilityState_def::CContamination; case rpr2::HitResultEnum16::TemporaryFailure: return ProjectDataManagement::VulnerabilityState_def::TemporarySeverance; default: return ProjectDataManagement::VulnerabilityState_def::NoChange; } return ProjectDataManagement::VulnerabilityState_def::NoChange; } QPolygonF NgsConnector::utmToGeogrphic(const QPolygonF &polyUtm, const LtsCoordTrans::UtmCoordinate &base) { QPolygonF poly; poly.reserve(polyUtm.size()); for (const QPointF &point : qAsConst(polyUtm)) poly << base.geographic(point); return poly; } void NgsConnector::onEntityIdentifierChanged(const rpr2::EntityIdentifierStruct &eis, const rpr2::IObserverBaseEntityObjInstance *obj) { IdentifierStruct is(eis); // Own players are irrelevant here if (is.isOwnElement()) { m_newObjectsWithoutParticipantIdSet.remove(obj); return; } // We cannot do anything here then. Who was it before? if (m_observerPlayerInstances.contains(is.entityNumber())) return; DEBUG_LOG(getLogger("NgsConnector.Inbound.Player")) << "Player: " << is.toString() << " will be added."; m_observerPlayerInstances.insert(is.entityNumber(), obj); m_newObjectsWithoutParticipantIdSet.remove(obj); GuezHitStruct hs; hs.originatorType = ProjectDataManagement::StateChangeOriginator_def::Initialization; hs.sampleTime = QDateTime::currentDateTimeUtc().toMSecsSinceEpoch(); // We don't have anything better hs.targetId = is.entityNumber(); hs.vulnerabilityState = ProjectDataManagement::VulnerabilityState_def::VulnerabilityBitsChanged; // --> Evaluate State from Bits (done in PPM Service). QTimer::singleShot(STATUS_WAIT_INTERVAL, this, [this, hs, obj]() mutable { auto ptr = m_damageModelMap.value(hs.targetId); if (!ptr) { ERROR_LOG(getLogger("NgsConnector.Inbound.Player")) << "Damage State of entity " << hs.targetId << " didn't exist 50msecs after EntityIdentifier Changed. Setting Initial Status not possible!"; return; } DEBUG_LOG(getLogger("NgsConnector.Inbound.Player")) << "Player: " << hs.targetId << ". Setting initial Status, initial Position and Manipulation."; const auto vulnerabilityBits = ptr->totalState(hs.sampleTime); hs.vulnerabilityBits = (vulnerabilityBits & ~rpr2::TotalStatusEnum32::Manipulation); DEBUG_LOG(getLogger("NgsConnector.Inbound.Player.Status")) << "Player: " << hs.targetId << " New Total Status: " << hs.vulnerabilityBits; emit guezHit(hs); ParticipantPosition pos(obj->getSpatial().DeadReckoningAlgorithm__A__Alternatives.SpatialStatic().WorldLocation); long time = obj->getLatestTimeStamp().getSeconds() * 1000; if (time == 0) time = getSimTime().getSeconds() * 1000; DEBUG_LOG(getLogger("NgsConnector.Inbound.Player.Position")) << "Player: " << hs.targetId << ", Initial Position: " << pos.toString() << ", Time: " << pos.msec; emit spatialChanged(hs.targetId, pos, time); // That may be too much but better safe than sorry... // TODO: set to DamageModel, use emit from there? emit playerManipulationChanged(hs.targetId, vulnerabilityBits & rpr2::TotalStatusEnum32::Manipulation, hs.sampleTime); }); } bool NgsConnector::onObjectIdentifierChanged(const rpr2::EntityIdentifierStruct &eis, const rpr2::IObserverEnvironmentObjectObjInstance *obj) { IdentifierStruct is(eis); // Own players are irrelevant here if (is.isOwnElement()) { m_newObjectsWithoutParticipantIdSet.remove(obj); return false; } // Already created -> ignore if (m_observerEnvironmentObjects.contains(is.entityNumber())) return true; DEBUG_LOG(getLogger("NgsConnector.Inbound.Object")) << "Object: " << is.toString() << " will be added."; m_observerEnvironmentObjects.insert(is.entityNumber(), obj); m_nonpublishedEnvironmentObjects.insert(is.entityNumber()); m_newObjectsWithoutParticipantIdSet.remove(obj); const rpr2::IObserverGuezMineFieldObjInstance *minefield = dynamic_cast(obj); if (minefield) return addMinefield(minefield); const rpr2::IObserverGuezMineFieldBreachObjInstance *minefieldBreach = dynamic_cast(obj); if (minefieldBreach) return addMinefieldBreach(minefieldBreach); return false; } bool getCorridorSegment(const rpr2::ArrayOfWorldLocationStructDynamicHLAlengthlessVarArray &pointsDataIn, std::vector &segmentsOut, double &widthOut) { // P0 P3 // L *-------------------* L // i | | i // n * S0 S1 * n // e | | e // 1 *-------------------* 2 // P1 P2 // Calculate width = P0->P1 // Calculate S0, S1 if (pointsDataIn.size() != 4) return false; ParticipantPosition p0(pointsDataIn[0]); ParticipantPosition p1(pointsDataIn[1]); ParticipantPosition p2(pointsDataIn[2]); ParticipantPosition p3(pointsDataIn[3]); LtsCoordTrans::UtmCoordinate utmP0 = LtsCoordTrans::UtmCoordinate::fromGeographic(p0.toPoint()); LtsCoordTrans::UtmCoordinate utmP1 = LtsCoordTrans::UtmCoordinate::fromGeographic(p1.toPoint(), utmP0.zone); LtsCoordTrans::UtmCoordinate utmP2 = LtsCoordTrans::UtmCoordinate::fromGeographic(p2.toPoint(), utmP0.zone); LtsCoordTrans::UtmCoordinate utmP3 = LtsCoordTrans::UtmCoordinate::fromGeographic(p3.toPoint(), utmP0.zone); QLineF line1(utmP0.x, utmP0.y, utmP1.x, utmP1.y); widthOut = line1.length(); line1.setLength(0.5 * line1.length()); LtsCoordTrans::UtmCoordinate utmS0(utmP0.zone, line1.p2().x(), line1.p2().y(), utmP0.northp); QLineF line2(utmP2.x, utmP2.y, utmP3.x, utmP3.y); widthOut = line2.length(); line2.setLength(0.5 * line2.length()); LtsCoordTrans::UtmCoordinate utmS1(utmP0.zone, line2.p2().x(), line2.p2().y(), utmP0.northp); segmentsOut.resize(2); segmentsOut[0] = Core::GeoPosition2D(utmS0.geographic().x(), utmS0.geographic().y()); segmentsOut[1] = Core::GeoPosition2D(utmS1.geographic().x(), utmS1.geographic().y()); return true; } bool NgsConnector::addMinefield(const rpr2::IObserverGuezMineFieldObjInstance *minefield) { int id = minefield->isValidObjectIdentifier() ? IdentifierStruct(minefield->getObjectIdentifier()).entityNumber() : 0; QTimer::singleShot(STATUS_WAIT_INTERVAL, this, [this, minefield, id]() mutable { if (!m_observerEnvironmentObjects.contains(id)) return; if (!minefield->isValidObjectIdentifier() || !minefield->isValidMineType() || !minefield->isValidMineCount() || !minefield->isValidActivationTime() || !minefield->isValidAreaEffectSimulationName() || !minefield->isValidForceIdentifier() || !minefield->isValidPointsData()) { ERROR_LOG(getLogger("NgsConnector.Inbound.MineField")) << "Wanted to create Minefield but not all necessary data is available yet. :("; return; } if (minefield->isValidPercentComplete() && minefield->getPercentComplete() > 99) return; IdentifierStruct is(minefield->getObjectIdentifier()); AreaEffectSimulation::MineAreaModifyRequest ma; const auto ct = m_ngsHandler->ppmHandler()->chargeTypeFromDisEntity(TypeStruct(minefield->getMineType()).toString()); ma.externalId(is.toInt64()); ma.chargeStruct().chargeTypeId(ct.id()); ma.density(0.008 * minefield->getMineCount() + 0.1); // 0 -> 0.1 , 100 -> 0.9 ma.effectStart(getMSecsSinceEpoch(minefield->getActivationTime())); ma.effectSourceId(ProjectDataManagement::EffectSource::ES_HLA); ma.name(minefield->getAreaEffectSimulationName().str()); if (minefield->isValidDuration()) ma.effectDuration(60LL * 60 * 1000 * minefield->getDuration()); // Hours to MSecs else ma.effectDuration(60LL * 60 * 1000 * 4); // 4 Hour Default int partyId = m_ngsHandler->ppmHandler()->partyIdFromForceIdentifier(minefield->getForceIdentifier()); ma.partyId(partyId); double width; std::vector segments; if (!getCorridorSegment(minefield->getPointsData(), segments, width)) { ERROR_LOG(getLogger("NgsConnector.Inbound.MineField")) << "Minefield Id: " << is.toString() << ", Name: " << ma.name() << ". Could not calculate segment from given points data."; return; } DEBUG_LOG(getLogger("NgsConnector.Inbound.MineField")) << "Minefield added/changed: Id: " << is.toString() << ", Name: " << ma.name(); ma.segments(segments); ma.width(width); ma.isCenterLine(true); emit newMinefield(ma); m_nonpublishedEnvironmentObjects.remove(id); }); return true; } bool NgsConnector::addMinefieldBreach(const rpr2::IObserverGuezMineFieldBreachObjInstance *minefieldBreach) { QTimer::singleShot(STATUS_WAIT_INTERVAL, this, [this, minefieldBreach]() mutable { if (!minefieldBreach->isValidObjectIdentifier() || !minefieldBreach->isValidActivationTime() || !minefieldBreach->isValidAreaEffectSimulationName() || !minefieldBreach->isValidForceIdentifier() || !minefieldBreach->isValidPointsData()) return; IdentifierStruct is(minefieldBreach->getObjectIdentifier()); // If it was deleted, don't do anything! if (!m_observerEnvironmentObjects.contains(is.entityNumber())) return; AreaEffectSimulation::MineCorridorModifyRequest mc; mc.externalId(is.toInt64()); mc.effectStart(getMSecsSinceEpoch(minefieldBreach->getActivationTime())); mc.effectSourceId(ProjectDataManagement::EffectSource::ES_HLA); mc.name(minefieldBreach->getAreaEffectSimulationName().str()); std::vector vec; double width; if (!getCorridorSegment(minefieldBreach->getPointsData(), vec, width)) { ERROR_LOG(getLogger("NgsConnector.Inbound.MineFieldBreach")) << "Minefield Breach Id: " << is.toString() << ", Name: " << mc.name() << ". Could not calculate segment from given points data."; return; } mc.segments(vec); mc.width(width); int partyId = m_ngsHandler->ppmHandler()->partyIdFromForceIdentifier(minefieldBreach->getForceIdentifier()); mc.partyId(partyId); DEBUG_LOG(getLogger("NgsConnector.Inbound.MineFieldBreach")) << "Minefield added/changed: Id: " << is.toString() << ", Name: " << mc.name(); emit newMinefieldBreach(mc); m_nonpublishedEnvironmentObjects.remove(is.entityNumber()); }); return true; } bool NgsConnector::onSpatialChanged(const rpr2::EntityIdentifierStruct &eis, const rpr2::WorldLocationStruct &wl, qint64 simTimeMSecs) { IdentifierStruct is(eis); ParticipantPosition newParticipantPosition(wl); if (is.isOwnElement()) return true; ParticipantPosition &posInMapRef = m_observerPositions[is.entityNumber()]; if (!posInMapRef.hasMoved(newParticipantPosition)) //Hat sich die Position zum letzen Mal signifkant geändert? return true; DEBUG_LOG(getLogger("NgsConnector.Inbound.Player.Position")) << "Player: " << is.toString() << ", Position: " << newParticipantPosition.toString() << ", Time: " << newParticipantPosition.msec; posInMapRef = newParticipantPosition; emit spatialChanged(is.entityNumber(), newParticipantPosition, simTimeMSecs); return true; } void NgsConnector::onEntityTypeChanged(const rpr2::EntityIdentifierStruct &eis, const rpr2::EntityTypeStruct &ets, qint64 simTimeMSecs) { Q_UNUSED(simTimeMSecs); IdentifierStruct is(eis); if (is.isOwnElement()) return; TypeStruct ts(ets); WARNING_LOG(getLogger("NgsConnector.Inbound.Player")) << "Player: " << is.toString() << ". New Entity Type: " << ts.toString() << ". This has no effect!"; // Todo? } void NgsConnector::onIsPartOfChanged(const rpr2::EntityIdentifierStruct &eis, const rpr2::IsPartOfStruct &ipos, qint64 simTimeMSecs) { // We cannot handle Saab players being mounted on GUEZ vehicles and vice versa as on radio the HOST (party A) tells about its mounted players, // whereas on HLA the player (party B) has to tell where he's mounted on. Party A's Excon cannot publish (IsPartOf) data of the player that // belongs to party B. IdentifierStruct is(eis); if (is.isOwnElement()) return; int eqNo = is.entityNumber(); int eqNoHost = ipos.HostEntityIdentifier.EntityNumber; DEBUG_LOG(getLogger("NgsConnector.Inbound.Player")) << "Player: " << is.toString() << " is now part of " << IdentifierStruct(ipos.HostEntityIdentifier).toString(); emit isPartOfChanged(eqNo, eqNoHost, simTimeMSecs); } bool NgsConnector::onMarkingChanged(const rpr2::EntityIdentifierStruct &eis, const rpr2::MarkingStruct &marking, qint64 simTimeMSecs) { IdentifierStruct is(eis); if (is.isOwnElement()) return true; QByteArray ba; for (size_t i = 0; i < marking.MarkingData.size(); ++i) ba += marking.MarkingData[i]; const QString str(QString::fromUtf8(ba)); DEBUG_LOG(getLogger("NgsConnector.Inbound.Object")) << "Object: " << is.toString() << " has new Marking: '" << str << "'."; emit markingChanged(eis.EntityNumber, str, simTimeMSecs); return true; } bool NgsConnector::onPointsDataChanged(const rpr2::EntityIdentifierStruct &eis, const rpr2::ArrayOfWorldLocationStructDynamicHLAlengthlessVarArray &points, qint64 simTimeMSecs) { IdentifierStruct is(eis); if (is.isOwnElement()) return true; if (!m_observerEnvironmentObjects.contains(is.entityNumber())) return false; if (m_nonpublishedEnvironmentObjects.contains(is.entityNumber())) return false; std::vector pointsData; for (size_t i = 0; i < points.size(); ++i) pointsData.resize(pointsData.size() + 1, ParticipantPosition(points[i]).toGeoPosition2D()); DEBUG_LOG(getLogger("NgsConnector.Inbound.Object")) << "Object: " << is.toString() << " has new Points data (not printed) of size " << pointsData.size(); emit aesPointsDataChanged(is.toInt64(), pointsData, simTimeMSecs); return true; } bool NgsConnector::onForceIdentifierChanged(const rpr2::EntityIdentifierStruct &eis, rpr2::ForceIdentifierEnum8::ET identifier, qint64 simTimeMSecs) { Q_UNUSED(simTimeMSecs); IdentifierStruct is(eis); if (is.isOwnElement()) return true; WARNING_LOG(getLogger("NgsConnector.Inbound.Player")) << "Player: " << is.toString() << " has new Force Identifier: " << identifier << ". This has NO EFFECT."; return true; } bool NgsConnector::onForceIdentifierChangedEO(const rpr2::EntityIdentifierStruct &eis, rpr2::ForceIdentifierEnum8::ET identifier, qint64 simTimeMSecs) { Q_UNUSED(simTimeMSecs); IdentifierStruct is(eis); if (is.isOwnElement()) return true; if (!m_observerEnvironmentObjects.contains(is.entityNumber())) return false; if (m_nonpublishedEnvironmentObjects.contains(is.entityNumber())) return false; DEBUG_LOG(getLogger("NgsConnector.Inbound.Object")) << "Object: " << is.toString() << " has new Force Identifier: " << identifier << ". Updating AES Item."; int partyId = m_ngsHandler->ppmHandler()->partyIdFromForceIdentifier(identifier); emit aesForceIdentifierChanged(is.toInt64(), partyId); return true; } bool NgsConnector::onTacticalStatusChanged(const rpr2::EntityIdentifierStruct &eis, const rpr2::TacticalStatusStruct &rprStatus, qint64 simTimeMSecs) { IdentifierStruct is(eis); if (is.isOwnElement()) return true; ensureDamageModelExists(is); m_damageModelMap[is.entityNumber()]->setTacticalStatusStruct(rprStatus, simTimeMSecs); DEBUG_LOG(getLogger("NgsConnector.Inbound.Player.Status")) << "Player: " << is.toString() << " New Total Status: " << rprStatus.TotalStatus << ", Tactical Status (unused): " << rprStatus.TacticalStatus; if (rprStatus.TotalStatus == rpr2::TotalStatusEnum32::CombatReady) { DEBUG_LOG(getLogger("NgsConnector.Inbound.Player.Status")) << "New Status is CombarReady. Therfore we 'init' the player."; emit participantReceivedInit(is.entityNumber(), simTimeMSecs); return true; } // Todo: Tactical Status Change on SAAB players can occur at any time (without known reason). Therefore, we MUST handle this. // Change of Tactical Status with no other Interaction cannot be handled (at the moment). The Interaction forwards the status change to PPM. // Note: We could start a timer per eqNo. If the Timer is not deleted by an interaction that handles it, send it out. GuezHitStruct hitStruct; hitStruct.originatorChargeTypeEtsString = TypeStruct::fromDisInt(0).toString(); hitStruct.originatorId = 0; hitStruct.originatorType = ProjectDataManagement::StateChangeOriginator_def::Unknown; hitStruct.position = m_observerPositions[is.entityNumber()].toGeoPosition(); hitStruct.sampleTime = simTimeMSecs; hitStruct.targetId = is.entityNumber(); hitStruct.vulnerabilityState = ProjectDataManagement::VulnerabilityState_def::VulnerabilityBitsChanged; // --> Evaluate State from Bits (done in PPM Service). hitStruct.vulnerabilityKind = 0; const auto vulnerabilityBits = m_damageModelMap[is.entityNumber()]->totalState(hitStruct.sampleTime); hitStruct.vulnerabilityBits = (vulnerabilityBits & ~rpr2::TotalStatusEnum32::Manipulation); if (m_guezHitActive.contains(hitStruct.targetId)) { DEBUG_LOG(getLogger("NgsConnector.Inbound.Player.StatusChange")) << "Tactical Status changed, but GuezHit was detected before. Not doing anything!"; return true; } QTimer::singleShot(Settings::guezHitGraceTimeMSecs(), this, [this, hitStruct]() mutable { if (m_guezHitActive.contains(hitStruct.targetId)) { DEBUG_LOG(getLogger("NgsConnector.Inbound.Player.StatusChange")) << "Tactical Status changed, Timeout happened, but GuezHit was detected before. Not doing anything!"; return; } WARNING_LOG(getLogger("NgsConnector.Inbound.Player.StatusChange")) << "Tactical Status changed, Timeout happened. NO corresponding GuezHit detected"; emit guezHit(hitStruct); }); return true; } bool NgsConnector::onDeactivatedChanged(const rpr2::EntityIdentifierStruct &eis, bool state, qint64 simTimeMSecs) { Q_UNUSED(simTimeMSecs); IdentifierStruct is(eis); if (is.isOwnElement()) return true; if (!m_observerEnvironmentObjects.contains(is.entityNumber())) return false; if (m_nonpublishedEnvironmentObjects.contains(is.entityNumber())) return false; DEBUG_LOG(getLogger("NgsConnector.Inbound.Object")) << "Object: " << is.toString() << " has changed 'Deactivated' State. New State: " << state; if (state) // deactivated { qint64 activationTime = 0; const rpr2::IObserverGuezMineFieldObjInstance *minefield = dynamic_cast(m_observerEnvironmentObjects[is.entityNumber()]); if (minefield && minefield->isValidActivationTime()) activationTime = getMSecsSinceEpoch(minefield->getActivationTime()); const rpr2::IObserverGuezMineFieldBreachObjInstance *minefieldBreach = dynamic_cast(m_observerEnvironmentObjects[is.entityNumber()]); if (minefieldBreach && minefieldBreach->isValidActivationTime()) activationTime = getMSecsSinceEpoch(minefieldBreach->getActivationTime()); // again! if (activationTime > 0) emit aesRemoveItem(is.toInt64()); } return true; } bool NgsConnector::onPercentCompleteChanged(const rpr2::EntityIdentifierStruct &eis, ngs::UINT32 state, qint64 simTimeMSecs) { Q_UNUSED(simTimeMSecs); IdentifierStruct is(eis); if (is.isOwnElement()) return true; DEBUG_LOG(getLogger("NgsConnector.Inbound.Object")) << "Object: " << is.toString() << " has changed 'Percent Complete'. New Completeness: " << state; // Find out, if it was active before // Todo: This might not work as the instance's deactivated flag most likely already is set... if (!m_observerEnvironmentObjects.contains(is.entityNumber())) return false; if (state > 99) emit aesRemoveItem(is.toInt64()); return true; } // Minefield // Minefield Breach void NgsConnector::onAreaEffectSimulationNameChanged(const rpr2::EntityIdentifierStruct &eis, const rpr2::OMT13string &value, qint64 simTimeMSecs) { Q_UNUSED(simTimeMSecs); IdentifierStruct is(eis); if (is.isOwnElement()) return; if (m_nonpublishedEnvironmentObjects.contains(is.entityNumber())) return; if (!m_observerEnvironmentObjects.contains(eis.EntityNumber)) return; DEBUG_LOG(getLogger("NgsConnector.Inbound.Object")) << "Object: " << is.toString() << " has changed Name. New Name: " << QString::fromStdString(value); emit aesNameChanged(is.toInt64(), value); } // Minefield // Minefield Breach void NgsConnector::onActivationTimeChanged(const rpr2::EntityIdentifierStruct &eis, const rpr2::ClockTimeStruct &value, qint64 simTimeMSecs) { Q_UNUSED(simTimeMSecs); IdentifierStruct is(eis); if (is.isOwnElement()) return; if (m_nonpublishedEnvironmentObjects.contains(is.entityNumber())) return; if (!m_observerEnvironmentObjects.contains(eis.EntityNumber)) return; qint64 msecs = getMSecsSinceEpoch(value); DEBUG_LOG(getLogger("NgsConnector.Inbound.Object")) << "Object: " << is.toString() << " has changed Activation Time: " << msecs; emit aesActivationTimeChanged(is.toInt64(), msecs); } // Minefield void NgsConnector::onDurationChanged(const rpr2::EntityIdentifierStruct &eis, ngs::UINT32 value, qint64 simTimeMSecs) { Q_UNUSED(simTimeMSecs); IdentifierStruct is(eis); if (is.isOwnElement()) return; if (m_nonpublishedEnvironmentObjects.contains(is.entityNumber())) return; if (!m_observerEnvironmentObjects.contains(eis.EntityNumber)) return; DEBUG_LOG(getLogger("NgsConnector.Inbound.Object")) << "Object: " << is.toString() << " has changed Duration: " << value; emit aesDurationChanged(is.toInt64(), value); } // Minefield void NgsConnector::onMineTypeChanged(const rpr2::EntityIdentifierStruct &eis, const rpr2::EntityTypeStruct &ets, qint64 simTimeMSecs) { IdentifierStruct is(eis); Q_UNUSED(simTimeMSecs); if (is.isOwnElement()) return; if (!m_observerEnvironmentObjects.contains(is.entityNumber())) return; if (m_observerEnvironmentObjects.contains(eis.EntityNumber)) return; const auto ct = m_ngsHandler->ppmHandler()->chargeTypeFromDisEntity(TypeStruct(ets).toString()); DEBUG_LOG(getLogger("NgsConnector.Inbound.Object")) << "Object: " << is.toString() << " has changed Munition: " << ct.disEntity() << " / internal id: " << ct.id(); emit aesChargeChanged(is.toInt64(), ct.id()); } // HLA -> DDS void NgsConnector::onNewMunitionDetonation(const rpr2::IObserverMunitionDetonationIntInstance &interaction) { // Own Item? if (std::stol(interaction.getEventIdentifier().IssuingObjectIdentifier.ID) == IdentifierStruct(0).toInt64()) return; // // create effect at EXCON ... // // // ... based on ExplosionClusterSingleEffectOnly // { // try to create an ExplosionCluster 'light' (point geometry, no trajectory) ExplosionClusterSingleEffectOnlyRequest request; // id request.id(0); // externalId IdentifierStruct is(interaction.getEventIdentifier().EventCount); is.setSiteId(255); // Dummy - Must not be of either site. request.externalId(is.toInt64()); // dummy val, inserted to prevent aes_handler from dropping this telegram // effectStart, duration request.effectStart(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch() + ACTIVATION_LEAD_TIME); // geometry request.impactPosition( ParticipantPosition(interaction.getDetonationLocation()).toGeoPosition2D() ); // charge const auto ct = m_ngsHandler->ppmHandler()->chargeTypeFromDisEntity(TypeStruct(interaction.getMunitionType()).toString()); request.chargeTypeId(ct.id()); // names const QString name = tr("SAAB Fire: %1").arg(interaction.getEventIdentifier().EventCount); request.name(name.toStdString()); // ZpNummer request.element(request.name()); // element DEBUG_LOG(getLogger("NgsConnector.Inbound.MunitionDetonation")) << "Munition Detonation: " << "Location: " << ParticipantPosition(interaction.getDetonationLocation()).toString() << ", Charge Type: " << ct.disEntity(); emit munitionDetonationExplosionClusterSingleEffectOnly(request); return; } // // ... based on ExplosionSingleModifyRequest // AreaEffectSimulation::ExplosionSingleModifyRequest req; // req.altitude(); const auto ct = m_ngsHandler->ppmHandler()->chargeTypeFromDisEntity(TypeStruct(interaction.getMunitionType()).toString()); req.chargeTypeId(ct.id()); req.id(0); req.effectStart(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch() + ACTIVATION_LEAD_TIME); req.effectSourceId(ProjectDataManagement::EffectSource::ES_HLA); IdentifierStruct is(interaction.getEventIdentifier().EventCount); is.setSiteId(255); // Dummy - Must not be of either site. req.externalId(is.toInt64()); // dummy val, inserted to prevent aes_handler from dropping this telegram req.fireRequestTime(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()); req.impactPosition(ParticipantPosition(interaction.getDetonationLocation()).toGeoPosition2D()); // IdentifierStruct is = IdentifierStruct::fromInt64(QString::fromStdString(interaction.getEventIdentifier().IssuingObjectIdentifier.ID).toLongLong()); req.name(tr("SAAB Fire: %1").arg(interaction.getEventIdentifier().EventCount).toStdString()); // Todo: If SAAB sends the Party in interaction.getFiringObjectIdentifier().ID like "F12" -> "ForceIdentifierEnum -> Value 12" // req.partyId(interaction.getFiringObjectIdentifier()) // req.purpose() // req.rotation() DEBUG_LOG(getLogger("NgsConnector.Inbound.MunitionDetonation")) << "Munition Detonation: " << "Location: " << ParticipantPosition(interaction.getDetonationLocation()).toString() << ", Charge Type: " << ct.disEntity(); emit munitionDetonation(req); } int NgsConnector::getEquipmentNoByRTIObjectId(const std::string &sender) const { // Own Players for (auto it(m_playerMap.cbegin()); it != m_playerMap.cend(); ++it) { if (it.value()->isValidObjectName()) { if (it.value()->getObjectName() == sender) return it.key(); } } // Saab Players for (auto it(m_observerPlayerInstances.cbegin()); it != m_observerPlayerInstances.cend(); ++it) { if (it.value()->isValidObjectName()) { if (it.value()->getObjectName() == sender) return it.key(); } } return 0; } // HLA -> DDS void NgsConnector::onNewWeaponFire(const rpr2::IObserverWeaponFireIntInstance &interaction) { // Self issued? if (std::stol(interaction.getEventIdentifier().IssuingObjectIdentifier.ID) == IdentifierStruct(0).toInt64()) return; TrainingAreaBase::EquipmentGearEvent event; event.equipmentNumber(getEquipmentNoByRTIObjectId(interaction.getFiringObjectIdentifier().ID)); // only send for others, not for own players if (m_playerMap.contains(event.equipmentNumber())) return; TrainingAreaBase::GearCharge charge; const auto ct = m_ngsHandler->ppmHandler()->chargeTypeFromDisEntity(TypeStruct(interaction.getMunitionType()).toString()); charge.chargeTypeId(ct.id()); // charge.gearId(); // charge.gearTypeId(); // charge.remain(); event.charge(charge); event.chargeAmount(interaction.getQuantityFired()); event.sampleTime(simTimeToMSec(interaction.getReceiveTimeStamp())); DEBUG_LOG(getLogger("NgsConnector.Inbound.WeaponFire")) << "Weapon Fire: EventID: " << interaction.getEventIdentifier().EventCount << ", Charge Type: " << ct.disEntity() << ", Shooter: " << event.equipmentNumber() << ", Rounds: " << interaction.getQuantityFired() << ")"; emit weaponFire(event); } void NgsConnector::onNewGuezHit(const rpr2::IObserverGuezHitIntInstance &interaction) { IdentifierStruct is(interaction.getTargetIdentifier()); if (is.isOwnElement()) return; if (!m_observerPlayerInstances.contains(is.entityNumber())) { ERROR_LOG(getLogger("NgsConnector.Inbound.GuezHit")) << "Player: " << is.toString() << ", but there's no observer Object for it."; return; } const int eqNo = interaction.getTargetIdentifier().EntityNumber; auto ptr = m_damageModelMap.value(eqNo); if (!ptr) { WARNING_LOG(getLogger("NgsConnector.Inbound.GuezHit")) << "Player: " << eqNo << ", Damage Model no longer exists. Not processing GuezHit!"; return; } ptr->setTacticalStatus(interaction.getHitResult(), simTimeToMSec(interaction.getReceiveTimeStamp())); GuezHitStruct hitStruct; hitStruct.originatorChargeTypeEtsString = TypeStruct(interaction.getMunition()).toString(); hitStruct.originatorId = interaction.getOriginatorIdentifier().EntityNumber; hitStruct.originatorType = ProjectDataManagement::StateChangeOriginator_def::Unknown; switch (interaction.getOriginatorType()) { case rpr2::OriginatorTypeEnum16::Unknown: hitStruct.originatorType = ProjectDataManagement::StateChangeOriginator_def::Unknown; break; case rpr2::OriginatorTypeEnum16::NoMedicalTreatment: // If no medical treatment has been conducted, and there's no originator, he's bleeding for himself... hitStruct.originatorType = ProjectDataManagement::StateChangeOriginator_def::Participant; if (hitStruct.originatorId == 0) // for sanity, there can't be a participant with EqId = 0 hitStruct.originatorId = hitStruct.targetId; break; case rpr2::OriginatorTypeEnum16::Participant: hitStruct.originatorType = ProjectDataManagement::StateChangeOriginator_def::Participant; if (hitStruct.originatorId == 0) // for sanity, there can't be a participant with EqId = 0 hitStruct.originatorType = ProjectDataManagement::StateChangeOriginator_def::ExerciseControl; break; case rpr2::OriginatorTypeEnum16::AreaEffectSimulation: hitStruct.originatorType = ProjectDataManagement::StateChangeOriginator_def::AreaEffectSimulation; hitStruct.originatorId = IdentifierStruct(interaction.getOriginatorIdentifier()).toInt64(); break; case rpr2::OriginatorTypeEnum16::ObserverController: hitStruct.originatorType = ProjectDataManagement::StateChangeOriginator_def::ObserverController; break; case rpr2::OriginatorTypeEnum16::ExerciseControl: hitStruct.originatorType = ProjectDataManagement::StateChangeOriginator_def::ExerciseControl; break; } hitStruct.position = ParticipantPosition(interaction.getTargetLocation()).toGeoPosition(); hitStruct.sampleTime = simTimeToMSec(interaction.getReceiveTimeStamp()); hitStruct.targetId = interaction.getTargetIdentifier().EntityNumber; // hitStruct.vulnerabilityState = ProjectDataManagement::VulnerabilityState_def::VulnerabilityBitsChanged; // --> Evaluate State from Bits (done in PPM Service). // hitStruct.vulnerabilityKind = 0; hitStruct.vulnerabilityState = getTacticalStatusEnum(interaction.getHitResult()); // HAck for resetting a player, whilst SAAB does not send the correct HitResult... if ((hitStruct.originatorType == ProjectDataManagement::StateChangeOriginator_def::ExerciseControl || hitStruct.originatorType == ProjectDataManagement::StateChangeOriginator_def::ObserverController ) && hitStruct.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::HitWithNoEffect) { auto ptr = m_damageModelMap.value(eqNo); if (ptr) { hitStruct.vulnerabilityState = ptr->vulnerabilityState(hitStruct.sampleTime); if (hitStruct.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CombatReady) ptr->setTacticalStatus(rpr2::HitResultEnum16::Reset, simTimeToMSec(interaction.getReceiveTimeStamp())); } } DEBUG_LOG(getLogger("NgsConnector.Inbound.GuezHit")) << "Player: " << is.toString() << ", Originator Type: " << hitStruct.originatorType << ", Originator Id: " << hitStruct.originatorId << ", Charge Type: " << hitStruct.originatorChargeTypeEtsString << ", HitResult: " << hitStruct.vulnerabilityState; m_guezHitActive.insert(hitStruct.targetId); QTimer::singleShot(Settings::guezHitGraceTimeMSecs(), this, [this, hitStruct, eqNo]() mutable { auto ptr = m_damageModelMap.value(eqNo); if (!ptr) { WARNING_LOG(getLogger("NgsConnector.Inbound.GuezHit")) << "Player: " << eqNo << ", Damage Model no longer exists. Not processing GuezHit!"; return; } const auto vulnerabilityBits = ptr->totalState(hitStruct.sampleTime); hitStruct.vulnerabilityBits = (vulnerabilityBits & ~rpr2::TotalStatusEnum32::Manipulation); DEBUG_LOG(getLogger("NgsConnector.Inbound.GuezHit")) << "vBits: " << vulnerabilityBits; DEBUG_LOG(getLogger("NgsConnector.Inbound.GuezHit")) << "vState: " << hitStruct.vulnerabilityState; m_guezHitActive.remove(hitStruct.targetId); emit guezHit(hitStruct); }); } void NgsConnector::onNewGuezInit(const rpr2::IObserverGuezInitIntInstance &interaction) { // ExConEngagement FROM HLA TO US! Our Player shall be affected! IdentifierStruct is(interaction.getEntityIdentifier()); if (!is.isOwnElement()) return; if (!m_playerMap.contains(is.entityNumber())) { ERROR_LOG(getLogger("NgsConnector.Inbound.GuezInit")) << "Player: " << is.toString() << " is not our player..."; return; } rpr2::ResetPlayerEnum8::ET rp = interaction.getResetPlayer(); rpr2::ActivateHelmetObservationEnum8::ET aho = interaction.getActivateHelmetObservation(); bool resetVulnerabilityState = true; if (rp != rpr2::ResetPlayerEnum8::CombatReadyWithoutReammunition && rp != rpr2::ResetPlayerEnum8::CombatReadyWithReammunition) resetVulnerabilityState = false; if (aho != rpr2::ActivateHelmetObservationEnum8::NoChange) { WARNING_LOG(getLogger("NgsConnector.Inbound.GuezInit")) << "Player: " << is.toString() << "Incoming Activation Helmet/Vest on GuezInit not supported yet"; } DEBUG_LOG(getLogger("NgsConnector.Inbound.GuezInit")) << "Player: " << is.toString() << ", Reset Type: " << rp; emit guezInit(is.entityNumber(), false, rp == rpr2::ResetPlayerEnum8::CombatReadyWithReammunition, resetVulnerabilityState); } void NgsConnector::onNewGuezBiteReport(const rpr2::IObserverGuezBiteReportIntInstance &interaction) { IdentifierStruct is(interaction.getEntityIdentifier()); if (is.isOwnElement()) return; TrainingAreaBase::EquipmentBite eb; QStringList result; bool leave = false; if (!interaction.isValidBiteResult()) { result << QStringLiteral("BiteResult not valid"); leave = true; } if (!interaction.isValidConfigurationComparisonResult()) { result << QStringLiteral("ConfigurationComparison not valid"); leave = true; } if (!interaction.isValidDiscoveredDevices()) { result << QStringLiteral("DiscoveredDevices not valid"); leave = true; } if (!interaction.isValidEntityIdentifier()) { result << QStringLiteral("EntityIdentifier not valid"); leave = true; } if (!interaction.isValidRegistrationType()) { result << QStringLiteral("RegistrationType not valid"); leave = true; } if (!interaction.isValidReportTime()) { result << QStringLiteral("ReportTime not valid"); leave = true; } if (leave) { ERROR_LOG(getLogger("NgsConnector.Inbound.GuezBiteReport")) << "Player: " << is.toString() << ", Cannot process Interaction. Reason(s): " << result.join(QLatin1String(", ")); return; } rpr2::BiteResultStruct brs = interaction.getBiteResult(); eb.configState(brs.DeviceOkay); eb.batteryState(brs.BatteryWarning); std::vector be; be.resize(brs.ErrorCodes.size()); for (unsigned int i = 0; i < brs.ErrorCodes.size(); ++i) be[i].canDeviceNumber(brs.ErrorCodes[i].DeviceId); eb.biteErrors(be); eb.biteState(interaction.getDiscoveredDevices()); eb.configError(interaction.getConfigurationComparisonResult()); eb.equipmentNumber(is.entityNumber()); eb.registerType(interaction.getRegistrationType()); eb.sampleTime(getMSecsSinceEpoch(interaction.getReportTime())); DEBUG_LOG(getLogger("NgsConnector.Inbound.GuezBiteReport")) << "Player: " << is.toString() << ", Device Okay: " << brs.DeviceOkay << ", Battery Warning: " << brs.BatteryWarning << ", Bite Error Count: " << be.size() << ", Registration Type: " << eb.registerType() << ", Config Error: " << eb.configError(); emit biteReport(eb); } void NgsConnector::onNewGuezRequestBiteReport(const rpr2::IObserverGuezRequestBiteReportIntInstance &interaction) { IdentifierStruct is(interaction.getEntityIdentifier()); if (!is.isOwnElement()) return; DEBUG_LOG(getLogger("NgsConnector.Inbound.GuezBiteRequest")) << "Player: " << is.toString() << ", Bite Report Type: " << interaction.getBiteReportType(); TrainingAreaBase::DemandBiteRequest dbr; dbr.equipmentNumbers().push_back(is.entityNumber()); dbr.requestId(1); dbr.withSelfTest(interaction.getBiteReportType() == rpr2::BiteReportTypeEnum8::StatusAfterSelftest); emit biteRequest(dbr); } rpr2::EventIdentifierStruct NgsConnector::getEventIdentifierStruct() { rpr2::EventIdentifierStruct evis; evis.EventCount = m_eventCount++ % std::numeric_limits::max(); evis.IssuingObjectIdentifier.ID = std::to_string(IdentifierStruct(0).toInt64()); if (m_eventCount > std::numeric_limits::max()) emit eventCountOverflow(); return evis; } rpr2::TacticalStatusStruct NgsConnector::getTacticalStatusFromState(const ParticipantManagement::StateInformation &state) { rpr2::TacticalStatusStruct ts; ts.TacticalStatus = static_cast(state.vulnerabilityState().underlying()); ts.TotalStatus = static_cast(state.vulnerabilityBits()); ts.VulnerabilityKind = state.vulnerabilityKindId(); return ts; } bool NgsConnector::setVulnerabilityState(int eqNo, const ParticipantManagement::StateInformation &state) { rpr2::TacticalStatusStruct ts = getTacticalStatusFromState(state); rpr2::IPlayerPhysicalEntityObjInstance *playerInstance = dynamic_cast(m_playerMap.value(eqNo, nullptr)); if (!playerInstance) return false; DamageModel dm(ts); playerInstance->setDamageState(dm.damageState(-1)); playerInstance->setImmobilized(dm.immobilized(-1)); playerInstance->setFirePowerDisabled(dm.firePowerDisabled(-1)); DEBUG_LOG(getLogger("NgsConnector.Outbound.Player.Status")) << "Player: " << eqNo << ", New Total Status: " << ts.TotalStatus << ", New Tactical Status (shall be unused): " << ts.TacticalStatus; rpr2::IPlayerGuezHumanObjInstance *humanInstance = dynamic_cast(playerInstance); if (humanInstance) { humanInstance->setTacticalStatus(ts); playerInstance->publish(getSimTime(state.stateTime())); return true; } rpr2::IPlayerGuezGroundVehicleObjInstance *groundVehicleInstance = dynamic_cast(playerInstance); if (groundVehicleInstance) { groundVehicleInstance->setTacticalStatus(ts); playerInstance->publish(getSimTime(state.stateTime())); return true; } ERROR_LOG(getLogger("NgsConnector.Outbound.Player.Status")) << "Player: " << eqNo << " is not Human or GroundVehicle. Cannot set status."; return false; } // Hit from Participant Management bool NgsConnector::setParticipantHit(int eqNo, const ParticipantManagement::StateInformation &state, const ParticipantPosition &partPos, int chargeType, ProjectDataManagement::StateChangeOriginator originatorType, int originatorEqNo) { // Set Tactical Status on Player auto *playerInstanceDest = m_playerMap.value(eqNo, nullptr); if (!playerInstanceDest || !playerInstanceDest->isValidEntityIdentifier()) return false; if (!setVulnerabilityState(eqNo, state)) return false; // Put Munition Detonation on HLA rpr2::IPlayerGuezHitIntClass &ic = m_somPlayer->getPlayerGuezHitIntClass(); rpr2::IPlayerGuezHitIntInstance &icb = ic.newGuezHitIntInstance(); icb.setMunition(getEtsCharge(chargeType)); icb.setTargetIdentifier(playerInstanceDest->getEntityIdentifier()); icb.setTargetLocation(partPos.toWorldLocation()); icb.setHitResult(getHitResultEnum(state.vulnerabilityState())); IdentifierStruct is(0); // own excon rpr2::OriginatorTypeEnum16::ET originatorTypeRpr = rpr2::OriginatorTypeEnum16::Unknown; switch (originatorType.underlying()) { case ProjectDataManagement::StateChangeOriginator_def::AreaEffectSimulation: { if (m_environmentObjects.contains(originatorEqNo)) // It's our AesItem, originatorEqNo is AesItemId is Entity Number is = IdentifierStruct(10 * originatorEqNo); else { // Obtain ExternalId int64_t externalId = m_ngsHandler->aesHandler()->externalId(originatorEqNo); // At this map, Entities and Objects are if (m_observerPlayerInstances.contains(externalId)) { const rpr2::IObserverBaseEntityObjInstance *instance = dynamic_cast(m_observerPlayerInstances[originatorEqNo]); if (instance->isValidEntityIdentifier()) is = IdentifierStruct(instance->getEntityIdentifier()); } } originatorTypeRpr = rpr2::OriginatorTypeEnum16::AreaEffectSimulation; break; } case ProjectDataManagement::StateChangeOriginator_def::ExerciseControl: originatorTypeRpr = rpr2::OriginatorTypeEnum16::ExerciseControl; break; case ProjectDataManagement::StateChangeOriginator_def::ObserverController: originatorTypeRpr = rpr2::OriginatorTypeEnum16::ObserverController; break; case ProjectDataManagement::StateChangeOriginator_def::Participant: if (originatorType == ProjectDataManagement::StateChangeOriginator_def::Participant) { if (m_playerMap.contains(originatorEqNo)) is = IdentifierStruct(originatorEqNo); else if (m_observerPlayerInstances.contains(originatorEqNo)) { // Works as long as externalID == equipmentNo const rpr2::IObserverBaseEntityObjInstance *instance = dynamic_cast(m_observerPlayerInstances[originatorEqNo]); if (instance->isValidEntityIdentifier()) is = IdentifierStruct(instance->getEntityIdentifier()); } originatorTypeRpr = rpr2::OriginatorTypeEnum16::Participant; break; } Q_FALLTHROUGH(); case ProjectDataManagement::StateChangeOriginator_def::Initialization: case ProjectDataManagement::StateChangeOriginator_def::FunctionalArea: case ProjectDataManagement::StateChangeOriginator_def::MaintainanceDevice: case ProjectDataManagement::StateChangeOriginator_def::Unknown: case ProjectDataManagement::StateChangeOriginator_def::WoundTreatmentDevice: case ProjectDataManagement::StateChangeOriginator_def::MedicalTimeout: case ProjectDataManagement::StateChangeOriginator_def::Sanitation: // Todo: Check, if IED and DUSMine actually can be handled correctly: case ProjectDataManagement::StateChangeOriginator_def::ImprovisedExplosiveDevice: case ProjectDataManagement::StateChangeOriginator_def::DusMine: originatorTypeRpr = rpr2::OriginatorTypeEnum16::Unknown; break; } icb.setOriginatorIdentifier(is.toEIS()); icb.setOriginatorType(originatorTypeRpr); DEBUG_LOG(getLogger("NgsConnector.Outbound.GuezHit")) << "Player: " << eqNo << ", Originator Type: " << originatorType << ", Originator EIS: " << is.toString() << ", Charge Type: " << TypeStruct(getEtsCharge(chargeType)).toString() << ", Vulnerability State: " << state.vulnerabilityState(); try { icb.publish(getSimTime(state.stateTime())); } catch (...) { ERROR_LOG(getLogger("NgsConnector.Outbound.GuezHit")) << "Player: " << eqNo << ", could not publish GuezHit!"; } return true; } bool NgsConnector::setWeaponFire(int eqNo, const ParticipantManagement::ParticipantGearEvent &data) { rpr2::IPlayerWeaponFireIntClass &ic = m_somPlayer->getPlayerWeaponFireIntClass(); rpr2::IPlayerWeaponFireIntInstance &icb = ic.newWeaponFireIntInstance(); // FiringInstance ermitteln rpr2::RTIObjectIdStruct rSource; rSource.ID = std::to_string(IdentifierStruct(0).toInt64()); // Own Excon if (eqNo > 0) { auto *playerInstanceSource = m_playerMap.value(eqNo); if (!playerInstanceSource || !playerInstanceSource->isValidObjectName() || !playerInstanceSource->isValidSpatial()) return false; rSource.ID = playerInstanceSource->getObjectName(); icb.setFiringLocation(playerInstanceSource->getSpatial().DeadReckoningAlgorithm__A__Alternatives.SpatialStatic().WorldLocation); } icb.setEventIdentifier(getEventIdentifierStruct()); // icb.setFireControlSolutionRange(); // icb.setFireMissionIndex(); icb.setFiringObjectIdentifier(rSource); icb.setFuseType(rpr2::FuseTypeEnum16::Other); icb.setInitialVelocityVector(rpr2::VelocityVectorStruct()); // icb.setMunitionObjectIdentifier(); icb.setMunitionType(getEtsCharge(data.charge().chargeTypeId())); icb.setQuantityFired(data.chargeAmount()); // icb.setRateOfFire(); // icb.setTargetObjectIdentifier(); icb.setWarheadType(rpr2::WarheadTypeEnum16::Kinetic); icb.publish(getSimTime(data.sampleTime())); DEBUG_LOG(getLogger("NgsConnector.Outbound.WeaponFire")) << "Weapon Fire: " << "Charge Type: " << data.charge().chargeTypeId() << ", Shooter: " << eqNo << ", Rounds: " << data.chargeAmount(); return true; } bool NgsConnector::setEquipmentBite(int eqNo, const TrainingAreaBase::EquipmentBite &data) { IdentifierStruct is = getIdentifierStructbyEquipmentNo(eqNo); if (is.isNull() || !is.isOwnElement()) return false; rpr2::IPlayerGuezBiteReportIntClass &icx = m_somPlayer->getPlayerGuezBiteReportIntClass(); rpr2::IPlayerGuezBiteReportIntInstance *icb = &icx.newGuezBiteReportIntInstance(); rpr2::BiteResultStruct brs; brs.BatteryWarning = static_cast(data.batteryState()); brs.DeviceOkay = static_cast(data.configState()); rpr2::ErrorStructVariableArray errorCodes; errorCodes.resize(data.biteErrors().size()); int i = 0; for (const auto &error : data.biteErrors()) { errorCodes[i].DeviceId = error.canDeviceNumber(); errorCodes[i].SubDeviceId = error.subdeviceNumber(); errorCodes[i].UniqueSubdeviceId = error.uniqueId(); errorCodes[i].ErrorId = error.errorNumber(); ++i; } brs.ErrorCodes = errorCodes; icb->setBiteResult(brs); icb->setConfigurationComparisonResult(data.configError()); icb->setDiscoveredDevices(static_cast(data.biteState())); icb->setEntityIdentifier(is.toEIS()); icb->setRegistrationType(static_cast(data.registerType())); icb->setReportTime(getClockTimeStruct(data.sampleTime())); icb->publish(getSimTime(data.sampleTime())); DEBUG_LOG(getLogger("NgsConnector.Outbound.GuezBiteReport")) << "Player: " << is.toString() << ", Device Okay: " << brs.DeviceOkay << ", Battery Warning: " << brs.BatteryWarning << ", Bite Error Count: " << errorCodes.size() << ", Registration Type: " << data.registerType() << ", Config Error: " << data.configError(); return true; } } // namespace HighLevelArchitecture