/** * Handler for player data * $Id$ * $HeadURL$ * $Author$ * Created by hjl, SLSE, 2017-02-20 */ #include "handler_explosions.h" #include "expand_polygon.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace CentralEffectSimulation; using namespace AreaEffectSimulation; using namespace LtsCoordTrans; namespace CesExplosions { HandlerExplosions::HandlerExplosions(const std::shared_ptr &ddsHandler, const std::shared_ptr &participantHandler, const std::shared_ptr &proxyBuilding, QObject *parent) : QObject(parent) , m_ddsHandler(ddsHandler) , m_handlerParticipant(participantHandler) , m_proxyBuilding(proxyBuilding) , m_logger(CentralEffectSimulation::getLogger("Plugin.Explosions.HandlerExplosions")) { } // end of ctor() //----------------------------------------------------------------------------- HandlerExplosions::~HandlerExplosions() { } // end of dtor() //----------------------------------------------------------------------------- bool HandlerExplosions::init() { DEBUG_LOG(m_logger) << "HandlerExplosions::init()"; auto dtcAes = m_ddsHandler->dtcAes(); auto ftcTab = m_ddsHandler->ftcTab(); auto dtcCes = m_ddsHandler->dtsCes(); ftcTab->enableSendCesHit(); dtcCes->enableParticipantEvent(); dtcAes->enableAesItemView(); connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::aesItemViewChanged, this, &HandlerExplosions::onAesItemViewChanged); dtcAes->enableTriggeredImpacts(); connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::triggeredImpactsChanged, this, &HandlerExplosions::onTriggeredImpactsChanged); dtcAes->enableExplosionSingle(); connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::explosionSingleChanged, this, &HandlerExplosions::onExplosionSingleChanged); dtcAes->enableEffects(); connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::effectsChanged, this, &HandlerExplosions::onEffectsChanged, Qt::UniqueConnection); connect(m_handlerParticipant.get(), &CentralEffectSimulation::HandlerParticipant::newOutdoorPosition, this, &HandlerExplosions::onNewParticipantCesPosition); connect(m_handlerParticipant.get(), &CentralEffectSimulation::HandlerParticipant::newIndoorPosition, this, &HandlerExplosions::onNewParticipantCesPosition); connect(m_handlerParticipant.get(), &CentralEffectSimulation::HandlerParticipant::restoreRadioReadinessChanged, this, &HandlerExplosions::onRestoreRadioReadinessChanged); dtcAes->enableChargeEffect(); connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::chargeEffectChanged, this, &HandlerExplosions::onChargeEffectChanged); return true; } // end of init() //----------------------------------------------------------------------------- void HandlerExplosions::onSendCesHitReply(const Core::FunctionReplyObject &replyObject) { if (replyObject.error()) WARNING_LOG(m_logger) << "Can not send CesHitRequest: " << replyObject(); else DEBUG_LOG(m_logger) << "onSendCesHitReply for id " << replyObject.id() << " success\n" << " replyObject.str() = " << replyObject.str(); } // /////////////////////////////////// // A E S (AreaEffectSimulation) // /////////////////////////////////// void HandlerExplosions::onAesItemViewChanged(const AreaEffectSimulation::AesItemView &data, DataModification modType) { if (modType == DataModification::eRemoved) return; // NASAT, MIDAS, AirSupport // Der explizite HandlerAirSupport wird gestrichen. // Der HandlerExplosions übernimmt die Aufgaben mit, da quasi identisch!! // if (m_ddsHandler->dtcPdm()->gearClassFromChargeTypeId(data.chargeTypeId()) == ProjectDataManagement::GearClass_def::AirSupport) // return; static const QVector validAesTypes = {AesType_def::AtExplosionAreaSingleExplosions, AesType_def::AtExplosionSingle, AesType_def::AtExplosionCluster}; if (!validAesTypes.contains(data.aesTypeId())) return; const AreaEffectSimulation::AesType aesTypeId = data.aesTypeId(); const int aesItemId = data.aesItemId(); const int chargeTypeId = data.chargeStruct().chargeTypeId(); const ProjectDataManagement::GearClass &gearClass = m_ddsHandler->dtcPdm()->gearClassFromChargeTypeId(chargeTypeId); const double impactRadius = m_ddsHandler->chargeTypeRadius(data.chargeStruct()); bool isCentral = false; DEBUG_LOG(m_logger) << "onAesItemViewChanged 1 " << ", aesItemId=" << aesItemId << ", aesTypeId=" << aesTypeId << ", chargeTypeId=" << chargeTypeId << ", gearClass=" << gearClass << ", impactRadius=" << impactRadius << ", effectStateId=" << data.effectStateId() << ", chargeStruct=" << data.chargeStruct(); switch (aesTypeId.underlying()) { case AreaEffectSimulation::AesType_def::AtExplosionSingle: { const AreaEffectSimulation::ExplosionSingle &explosionSingle = m_ddsHandler->dtcAes()->explosionSingle(aesItemId); isCentral = explosionSingle.isCentral(); DEBUG_LOG(m_logger) << "ExplosionSingle"; break; } case AreaEffectSimulation::AesType::AtExplosionArea: case AreaEffectSimulation::AesType::AtExplosionAreaSingleExplosions: { const AreaEffectSimulation::ExplosionArea &explosionArea = m_ddsHandler->dtcAes()->explosionArea(aesItemId); isCentral = explosionArea.isCentral(); DEBUG_LOG(m_logger) << "explosionArea"; break; } case AreaEffectSimulation::AesType::AtExplosionCluster: { const AreaEffectSimulation::ExplosionCluster &explosionCluster = m_ddsHandler->dtcAes()->explosionCluster(aesItemId); isCentral = explosionCluster.isCentral() || m_ddsHandler->isLaserGuidable(explosionCluster.chargeStruct()); DEBUG_LOG(m_logger) << "explosionCluster"; break; } default: DEBUG_LOG(m_logger) << "default, data.aesTypeId=" << aesTypeId; } // switch // skip SMART if (m_ddsHandler->isSmartChargeType(data.chargeStruct())) { DEBUG_LOG(m_logger) << " S M A R T return onAesItemViewChanged"; return; } // if if (!isCentral) return; // <-------------------------- return // // just for isCentral == true // DEBUG_LOG(m_logger) << "onAesItemViewChanged 2 aesItemId=" << aesItemId << " isCentral=" << isCentral; const bool isSupplyTruck = false; static CentralEffectSimulation::AesItemViewUtm prevData_empty_justForSignatur; // hazardDistance and buffer will be done later (prepared, activated) CentralEffectSimulation::AesItemViewUtm aesItemViewUtm = CentralEffectSimulation::AesItemViewUtm::create(data, prevData_empty_justForSignatur, impactRadius, isSupplyTruck); // default for useHazardDistance, hazardDistance DEBUG_LOG(m_logger) << "onAesItemViewChanged 3" << ", aesItemId=" << aesItemId << ", aesTypeId=" << aesTypeId << ", chargeTypeId=" << chargeTypeId << ", gearClass=" << gearClass << ", isCentral=" << isCentral << ", impactRadius=" << impactRadius << ", effectStateId=" << data.effectStateId(); switch (data.effectStateId().underlying()) { case AreaEffectSimulation::AesEffectState_def::EsUnknown: case AreaEffectSimulation::AesEffectState_def::EsError: case AreaEffectSimulation::AesEffectState_def::EsExpired: case AreaEffectSimulation::AesEffectState_def::EsRemoved: { DEBUG_LOG(m_logger) << " remove aesItemId=" << data.aesItemId(); // do some cleanup: m_handledImpactsStorage.removeAesItem(aesItemId); Q_FALLTHROUGH(); } case AreaEffectSimulation::AesEffectState_def::EsPaused: { // Man kann den m_enteredHazardDistanceStore nutzen. // Und allen darin für dieses area/aesItemView gespeicherten participants // ein sendLeaveHazardDistance senden... const QVector &allParticipantIdsInArea = m_enteredHazardDistanceStore.allParticipantIdsOfArea(aesItemId); if (m_logger.isDebugEnabled()) qCritical() << "allParticipantIdsInArea = " << allParticipantIdsInArea; const int remaining = 1; // ICD GUEZSB60_ICD-0052_Zentrale_Tne.doc Wirkfeldtyp Gefahrenbereich Steilfeuer const auto &participantPositions = m_handlerParticipant->positions(); for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos : qAsConst(participantPositions)) { if (!allParticipantIdsInArea.contains(pPos.participantId())) continue; sendLeaveHazardDistance(pPos.participantId(), aesItemId, remaining); } // for // jetzt sollte m_enteredHazardDistanceStore eigentlich bereinigt sein: const int remaingPartIdsInArea = m_enteredHazardDistanceStore.allParticipantIdsOfArea(aesItemId).size(); DEBUG_LOG(m_logger) << " remaingParticipantIdsInArea=" << remaingPartIdsInArea; Q_ASSERT(remaingPartIdsInArea == 0); m_enteredHazardDistanceStore.removeArea(aesItemId); // cleanup: if (data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsPaused) { DEBUG_LOG(m_logger) << "Expired, remove aesItemId=" << aesItemId << ", m_aesItemViewsUtm.remove(" << aesItemId << ")"; m_aesItemViewsUtm.remove(aesItemId); m_aesItemViewsImpactsUtm.remove(aesItemId); #ifdef ORIGINATOR_AT_SENDCESHITREQUEST m_aesItemId2partIdMap.remove(aesItemId); #endif // ORIGINATOR_AT_SENDCESHITREQUEST } break; } case AreaEffectSimulation::AesEffectState_def::EsPrepared: case AreaEffectSimulation::AesEffectState_def::EsFired: case AreaEffectSimulation::AesEffectState_def::EsActivated: { DEBUG_LOG(m_logger) << "Got AesItemView: " << data.effectStateId() << " aesItemId=" << aesItemId << " isCentral=" << isCentral; DEBUG_LOG(m_logger) << "insert AesItemView: " << data.effectStateId() << " aesItemViewUtm.effectStateId=" << aesItemViewUtm.effectStateId << " chargeTypeId" << aesItemViewUtm.chargeStruct.chargeTypeId(); // pre-calculate 'hazardDistanceForm' only in case of -> EsPrepared and EsActivated // race condition: what comes first? // a) Aiv changes to EsActivated or // b) Effects ? // So be prepared and calculate hazardDistanceForm already at EsPrepared if (aesTypeId == AreaEffectSimulation::AesType_def::AtExplosionCluster) { // useHazardDistance = (aesTypeId == AreaEffectSimulation::AesType_def::AtExplosionCluster); const double hazardDistance = m_ddsHandler->dtcPdm()->hazardDistance(data.chargeStruct().chargeTypeId(), data.chargeStruct().igniterTypeId(), data.chargeStruct().igniterFunctionId()); DEBUG_LOG(m_logger) << "+++++++++++ ExplosionCluster: hazardDistance=" << hazardDistance << ", impactRadius =" << impactRadius; aesItemViewUtm.hazardDistance_meter = hazardDistance; aesItemViewUtm.hazardDistanceForm = ExpandPolygon::bufferUtmPolygon(aesItemViewUtm.form, aesItemViewUtm.zone, hazardDistance); } if ((aesTypeId == AreaEffectSimulation::AesType_def::AtExplosionClusterSingleExplosions) || (aesTypeId == AreaEffectSimulation::AesType_def::AtExplosionAreaSingleExplosions)) m_aesItemViewsImpactsUtm.insert(aesItemId, aesItemViewUtm); else { m_aesItemViewsUtm.insert(aesItemId, aesItemViewUtm); DEBUG_LOG(m_logger) << "m_aesItemViewsUtm.insert aesItemId=" << aesItemId; } // // checkPartEnterLeaveArea // if ((aesItemViewUtm.effectStateId == AreaEffectSimulation::AesEffectState::EsActivated) && aesItemViewUtm.hasHazardDistance()) { // const reference (but if required we can benefit from implicit data sharing) // make m_aesItemViewsUtm const to be able to initialze CONST iterator const QHash &m_aesItemViewsUtmConstRef = m_aesItemViewsUtm; const AES_ITEM_VIEW_UTM_CONST_ITER &curAivIt = m_aesItemViewsUtmConstRef.find(aesItemId); if (curAivIt != m_aesItemViewsUtm.constEnd()) { const auto &participantPositions = m_handlerParticipant->positions(); const int numberOfParametrizedObjects = checkPartEnterLeaveArea(m_aesItemViewsUtm.constBegin(), m_aesItemViewsUtm.constEnd(), // parametrize a single item participantPositions.constBegin(), participantPositions.constEnd()); DEBUG_LOG(m_logger) << "++++++++ checkPartEnterLeaveArea checkPartEnterLeaveAreaNumber=" << numberOfParametrizedObjects; } } // if (aesItemViewUtm.effectStart == AreaEffectSimulation::AesEffectState::EsActivated) break; } case AreaEffectSimulation::AesEffectState_def::EsCreated: case AreaEffectSimulation::AesEffectState_def::EsReceived: break; } // switch } // END onAesItemViewChanged void HandlerExplosions::onTriggeredImpactsChanged(const AreaEffectSimulation::TriggeredImpacts &data, DataModification modType) { DEBUG_LOG(m_logger) << "onTriggeredImpactsChanged"; if (modType == DataModification::eRemoved) return; DEBUG_LOG(m_logger) << "Got TriggeredImpacts: " << data << " bitfield().size=" << data.bitfield().size(); switch (data.aesTypeId().underlying()) { case AreaEffectSimulation::AesType_def::AtExplosionArea: case AreaEffectSimulation::AesType_def::AtExplosionSingle: case AreaEffectSimulation::AesType_def::AtExplosionAreaSingleExplosions: case AreaEffectSimulation::AesType_def::AtExplosionClusterSingleExplosions: // explosion at the position of participant calculateImpacts(data); break; default: //nothing break; } // switch } // END onTriggeredImpactsChanged void HandlerExplosions::calculateImpact(const AreaEffectSimulation::ChargeEffect &data) { int zone = -1; // Now call calculateImpacts() (createEvent if in circle...) const auto &participantPositions = m_handlerParticipant->positions(); // GeoPosition to UTM const QPointF impactPositionUtm = CentralEffectSimulation::geographic2Utm(data.position(), zone); AreaEffectSimulation::ChargeStruct chargeStruct(data.chargeTypeId(),data.igniterTypeId(), data.igniterFunctionId()); const double impactRadius = m_ddsHandler->chargeTypeRadius(chargeStruct); for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos : qAsConst(participantPositions)) { if (m_handlerParticipant->isOutdated(pPos)) // Participant Position is not valid Todo Magic Number continue; DEBUG_LOG(m_logger) << "pPos.participantId=" << pPos.participantId(); if (pPos.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill) continue; DEBUG_LOG(m_logger) << "pPos.vulnerabilityState=" << pPos.vulnerabilityState; if (pPos.insideBuilding()) continue; const QPointF participantPosUtm = CentralEffectSimulation::geographic2Utm(pPos.pos(), zone); int equipNo = m_ddsHandler->dtcPpm()->equipmentNumberByParticipantId(pPos.participantId()); ParticipantManagement::DynamicParticipantData dynPart = m_ddsHandler->dtcPpm()->dynamicParticipantData(pPos.participantId()); ParticipantManagement::Participant originator = m_ddsHandler->dtcPpm()->participant(pPos.participantId()); int remainEffectPercent = 100; // TODO hier ist keine Gebäudewand dazwischen der Effekt wirkt komplett auf den Teilnehmer. Ist 100 richtig oder 0? if (inCircle(impactPositionUtm, impactRadius, participantPosUtm)) { DEBUG_LOG(m_logger) << "Teilnehmer getroffen => Send CesHitRequest "; TrainingAreaBase::TabPosition tabPosition; TrainingAreaBase::SendCesHitRequest requestTab; tabPosition.position().lat(data.position().lat()); tabPosition.position().lon(data.position().lon()); tabPosition.position().z(data.position().z()); requestTab.equipmentNumber(equipNo); requestTab.participantId(pPos.participantId()); requestTab.chargeTypeId(data.chargeTypeId()); requestTab.aesId(0); if (data.originatorTypeId() == ProjectDataManagement::StateChangeOriginator::Participant) { // if we get the equipmentNumber from the participant we will send this (needed by tab) // if not fall back to participantId, not negated yet as it's not tested and probalbly handled by the TNE const ParticipantManagement::Participant &part = m_ddsHandler->dtcPpm()->participant(data.originatorId()); if (part.equipmentNumber().is_set()) requestTab.originatorEquipmentNumber(part.equipmentNumber().get()); else { WARNING_LOG(m_logger) << "Sending CesHitRequest without EquipNo, using negative ParticipantID : " << data.originatorId(); requestTab.originatorEquipmentNumber(-data.originatorId()); } } else if (data.originatorTypeId() == ProjectDataManagement::StateChangeOriginator::Party) { requestTab.originatorEquipmentNumber(data.originatorId()); } requestTab.remainEffectPercent(remainEffectPercent); requestTab.position(tabPosition); requestTab.effectTime(data.effectStart() / 1000); requestTab.eventType(ProjectDataManagement::EventType_def::FireEvent); ParticipantManagement::Participant originator = m_ddsHandler->dtcPpm()->participant(data.originatorId()); requestTab.partyId(originator.partyId()); requestTab.mineId(0); requestTab.partyId(originator.partyId()); DEBUG_LOG(m_logger) << " participantId=" << pPos.participantId() << ", chargeTypeId=" << data.chargeTypeId() << ", m_sendCesHitRequestQueue.size()=" << m_sendCesHitRequestQueue.size() << ", duration=" << data.effectDuration() << " \n" << requestTab; DEBUG_LOG(m_logger) << "ftcTab->SendCesHitRequest: " << requestTab; m_ddsHandler->ftcTab()->sendCesHitRequest(requestTab, this); } } // for } void HandlerExplosions::startTimer(int id, int msec) { auto *timer = new QTimer(this); timer->setProperty("chargeEffectId", id); // Timeout-Slot mit echtem SLOT connect(timer, &QTimer::timeout, this, &HandlerExplosions::onStartTimeReached); timer->start(msec); DEBUG_LOG(m_logger) << "Timer " << id << " gestartet fuer " << msec << " ms."; } void HandlerExplosions::onStartTimeReached() { if (!sender()) return; bool bOk; int chargeEffectId = sender()->property("chargeEffectId").toInt(&bOk); if (!bOk) return; DEBUG_LOG(m_logger) << "Timer " << chargeEffectId << " ausgeloest!"; // Timer stoppen und loeschen AreaEffectSimulation::ChargeEffect data = m_chargeEffects.value(chargeEffectId); calculateImpact(data); sender()->deleteLater(); m_chargeEffects.remove(chargeEffectId); } void HandlerExplosions::onChargeEffectChanged(const AreaEffectSimulation::ChargeEffect &data, DataModification modType) { DEBUG_LOG(m_logger) << "HandlerExplosions::onChargeEffectChanged"; if (modType == DataModification::eRemoved) return; const long long effectStart = data.effectStart(); DEBUG_LOG(m_logger) << "HandlerExplosions::onChargeEffectChanged" << ", effectStart=" << effectStart << ", currentPartitionTime=" << m_ddsHandler->currentPartitionTime(); // Startzeit in der Vergangenheit: aktuelle Zeit - 3 Sekunden if ( effectStart < m_ddsHandler->currentPartitionTime() - 3000 ) { DEBUG_LOG(m_logger) << "Startzeit in der Vergangenheit: aktuelle Zeit - 3 Sekunden"; return; } // Startzeit entspricht der aktuellen Zeit if ( effectStart > m_ddsHandler->currentPartitionTime() - 3000 && effectStart < m_ddsHandler->currentPartitionTime() + data.effectDuration() ) { DEBUG_LOG(m_logger) << "Startzeit entspricht der aktuellen Zeit"; calculateImpact(data); } // Startzeit in der Zukunft: else if (effectStart > m_ddsHandler->currentPartitionTime() + data.effectDuration()) { // Start mit Timer DEBUG_LOG(m_logger) << "Startzeit in der Zukunft" << ", id :" << data.id() << ", duration=" << effectStart - m_ddsHandler->currentPartitionTime(); m_chargeEffects.insert(data.id(), data); startTimer(data.id(), effectStart - m_ddsHandler->currentPartitionTime()); } DEBUG_LOG(m_logger) << "END onChargeEffectChanged"; } void HandlerExplosions::onExplosionSingleChanged(const AreaEffectSimulation::ExplosionSingle &data, DataModification modType) { DEBUG_LOG(m_logger) << "\n onExplosionSingleChanged id=" << data.id() << " AesEffectState=" << data.effectStateId(); if (modType == DataModification::eRemoved) return; DEBUG_LOG(m_logger) << "onExplosionSingleChanged 1"; if (data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsActivated) return; DEBUG_LOG(m_logger) << "onExplosionSingleChanged" << " isCentral=" << data.isCentral(); if (!data.isCentral()) return; DEBUG_LOG(m_logger) << "onExplosionSingle Activated isCentral=" << data.isCentral() << " id=" << data.id(); // get access to the aesItemViewUtm storing the data for bbox check: CentralEffectSimulation::AesItemViewUtm aesItemViewUtm; if (m_aesItemViewsUtm.contains(data.id())) aesItemViewUtm = m_aesItemViewsUtm.value(data.id()); else if (m_aesItemViewsImpactsUtm.contains(data.id())) aesItemViewUtm = m_aesItemViewsImpactsUtm.value(data.id()); else ERROR_LOG(m_logger) << "Kein AesItemViewUtm "; // Now call calculateImpacts() (createEvent if in circle...) const auto &participantPositions = m_handlerParticipant->positions(); // GeoPosition to UTM QVector triggeredEffectsUtmPos; triggeredEffectsUtmPos.reserve(1); const QPointF &posUtm = CentralEffectSimulation::geographic2Utm(data.impactPosition(), aesItemViewUtm.zone); triggeredEffectsUtmPos.push_back(posUtm); if (m_ddsHandler->isSmartChargeType(data.chargeStruct())) { DEBUG_LOG(m_logger) << " S M A R T " << " aesItemId=" << aesItemViewUtm.aesItemId << " chargeTypeId=" << data.chargeStruct().chargeTypeId() << " partyId=" << aesItemViewUtm.partyId << " sampleTime=" << data.sampleTime(); calculateSmartExplosion(aesItemViewUtm.aesItemId, data.chargeStruct(), aesItemViewUtm.partyId, data.impactPosition(), data.sampleTime()); return; } for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos : qAsConst(participantPositions)) { if (m_handlerParticipant->isOutdated(pPos)) // Participant Position is not valid Todo Magic Number continue; DEBUG_LOG(m_logger) << "pPos.participantId=" << pPos.participantId(); if (pPos.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill) continue; DEBUG_LOG(m_logger) << "pPos.vulnerabilityState=" << pPos.vulnerabilityState; if (pPos.insideBuilding()) continue; DEBUG_LOG(m_logger) << "pPos.insideBuilding=" << pPos.insideBuilding(); if (!inBoundingBoxCheck(aesItemViewUtm, pPos)) continue; DEBUG_LOG(m_logger) << "calculateImpacts zone=" << aesItemViewUtm.zone; const Core::GeoPosition impactPosition3D(data.impactPosition().lon(), data.impactPosition().lat(), 0.0); Options options; calculateImpactsEffect_outdated(aesItemViewUtm, {impactPosition3D}, triggeredEffectsUtmPos, pPos, options); } // for } void HandlerExplosions::onEffectsChanged(const AreaEffectSimulation::Effects &effects, DataModification modType) { QElapsedTimer timer; if (m_logger.isInfoEnabled()) timer.start(); // The topic Effects is the successor of TriggeredImpacts & AesItemView (to avoid race condition) Q_UNUSED(modType); // Until now (04/2024) AreaEffectSimulation::Effects is used at ExplosionCluster only: if (effects.aesTypeId().underlying() != AreaEffectSimulation::AesType_def::AtExplosionClusterSingleExplosions) return; // older then 10sec? ignore if (effects.sampleTime() < m_ddsHandler->currentPartitionTime() - 10 * 1000) return; const int aesItemId = effects.aesItemId(); AreaEffectSimulation::AesItem aesItem; bool itemAvailable = m_ddsHandler->dtcAes()->aesItemFromId(effects.aesItemId(), aesItem); if ( itemAvailable) { if (m_ddsHandler->isSmartChargeType(aesItem.chargeStruct())) { DEBUG_LOG(m_logger) << "Calculate SMART"; calculateSmartEffect(effects); return; } } else { DEBUG_LOG(m_logger) << "No aesItem found for effect aesItemId=" << effects.aesItemId(); } // get access to the aesItemViewUtm storing the data for bbox check: const CentralEffectSimulation::AesItemViewUtm &aesItemViewUtm = m_aesItemViewsUtm.value(aesItemId); // Is the m_aesItemViewsUtm filled with data _BEFORE_ onEffectsChanged is called? // AesItem will be created _BEOFRE_ effects (Topic Effects) could be published: if (aesItemViewUtm.aesItemId != aesItemId) { DEBUG_LOG(m_logger) << "aesItemViewUtm (with extent) not found for aesItemId =" << aesItemId << " perhaps the field is not central, expired or SMART return onEffectsChanged"; return; } // // assume that areas with (isCentral = false) are not stored at m_aesItemViewsUtm // const int chargeTypeId = aesItemViewUtm.chargeStruct.chargeTypeId(); const ProjectDataManagement::GearClass &gearClass = m_ddsHandler->dtcPdm()->gearClassFromChargeTypeId(chargeTypeId); DEBUG_LOG(m_logger) << "onEffectsChanged, aesItemId=" << aesItemId << ", chargeTypeId = " << chargeTypeId << ", gearClass = " << gearClass; // determine relevant positions: const std::vector &singleEffectsRef = effects.singleEffects(); QVector triggeredEffectsLtsCoord; triggeredEffectsLtsCoord.reserve(singleEffectsRef.size()); int curSeIndex = -1; for (const AreaEffectSimulation::SingleEffect &se : qAsConst(singleEffectsRef)) { ++curSeIndex; if (se.singleEffectState() != AreaEffectSimulation::AesSingleEffectState_def::SesTriggered) continue; // compare current triggered idx with triggered idx of the past if (m_handledImpactsStorage.isIndexProcessed(aesItemId, curSeIndex)) continue; // okay, the current idx is triggered now and was not triggered before -> changing triggeredEffectsLtsCoord.push_back(LtsCoordinate(se.position())); // ZpDraped and ZpAbsolute should have a valid zValue // update storage ('remember') m_handledImpactsStorage.setIndexProcessed(aesItemId, curSeIndex); } // for const AreaEffectSimulation::SingleEffect &se // now triggeredEffectsLtsCoord stores the (new) GeoPositions to be calculated: DEBUG_LOG(m_logger) << "Effects of aesItemId =" << aesItemId << ", number of new SingleEffects (in this timeslot) = " << triggeredEffectsLtsCoord.size(); // Now call calculateImpacts() (createEvent if in circle...) const auto &participantPositions = m_handlerParticipant->positions(); for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos : qAsConst(participantPositions)) { if (pPos.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill) continue; if (m_handlerParticipant->isOutdated(pPos)) // Participant Position is not valid Todo Magic Number continue; DEBUG_LOG(m_logger) << "pPos.participantId=" << pPos.participantId() << ", vulnerabilityState=" << pPos.vulnerabilityState; if (pPos.insideBuilding()) continue; DEBUG_LOG(m_logger) << "pPos.insideBuilding=" << pPos.insideBuilding(); // skip inBoundingBoxCheck for ExplosionCluster since impacts could be OUTSIDE of bbox (intersection with bldg) // if (!inBoundingBoxCheck(aesItemViewUtm, pPos)) // continue; DEBUG_LOG(m_logger) << "calculateImpactsEffect for participantId " << pPos.participantId() << " Pos=" << pPos.ltsCoordinate().toString() << " ...."; // both: BLDG_LOS, ELEVATION_LOS Options options = EXECUTE_BLDG_LOS_CALC | EXECUTE_ELEVATION_LOS_CALC; calculateImpactsEffect(aesItemViewUtm, triggeredEffectsLtsCoord, pPos, options); DEBUG_LOG(m_logger) << "calculateImpactsEffect for participantId " << pPos.participantId() << " DONE"; } // for const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos INFO_LOG(m_logger) << " onEffectsChanged took [ms] " << timer.elapsed(); } // [GUEZE-2257]3D Wirksim - Gefaehrdungsabstand Zentral void HandlerExplosions::onNewParticipantCesPosition(const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantData) { if (m_aesItemViewsUtm.empty()) { TRACE_LOG(m_logger) << "++++++++ onNewParticipantOutdoorPosition participantId=" << participantData.participantId() << ", m_aesItemViewsUtm.size=" << m_aesItemViewsUtm.size(); return; } // // checkPartEnterLeaveArea // // const reference (but if required we can benefit from implicit data sharing) const auto &participantPositions = m_handlerParticipant->positions(); const HandlerParticipant::POSITIONS_CONST_ITER curPartIt = participantPositions.find(participantData.participantId()); if (curPartIt == participantPositions.constEnd()) return; const int numberOfParametrizedObjects = checkPartEnterLeaveArea(m_aesItemViewsUtm.constBegin(), m_aesItemViewsUtm.constEnd(), curPartIt, curPartIt + 1); // parametrize a single item DEBUG_LOG(m_logger) << "++++++++ checkPartEnterLeaveArea checkPartEnterLeaveAreaNumber=" << numberOfParametrizedObjects; } int HandlerExplosions::checkPartEnterLeaveArea(AES_ITEM_VIEW_UTM_CONST_ITER currentAreaIt, const AES_ITEM_VIEW_UTM_CONST_ITER &endingAreaIt, HandlerParticipant::POSITIONS_CONST_ITER currentParticipantIt, const HandlerParticipant::POSITIONS_CONST_ITER &endingParticipantIt) { DEBUG_LOG(m_logger) << "checkPartEnterLeaveArea..."; int numberOfParametrizedObjects = 0; // outer loop (aesitemViewsUtm) for (; currentAreaIt != endingAreaIt; ++currentAreaIt) { const CentralEffectSimulation::AesItemViewUtm &curAivUtmArea = *currentAreaIt; // check aiv: if (curAivUtmArea.effectStateId != AreaEffectSimulation::AesEffectState_def::EsActivated) continue; if (!curAivUtmArea.hasHazardDistance()) continue; int zoneInOut = curAivUtmArea.zone; // inner loop (participants) for (; currentParticipantIt != endingParticipantIt; ++currentParticipantIt) { ++numberOfParametrizedObjects; const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantData = *currentParticipantIt; const int participantId = participantData.participantId(); const Core::GeoPosition &participantGeoPos = participantData.pos(); if (LtsCoordTrans::LtsCoordinate::valueIsNullOrNan(participantGeoPos.lon()) || LtsCoordTrans::LtsCoordinate::valueIsNullOrNan(participantGeoPos.lat())) continue; if (m_handlerParticipant->isOutdated(participantData)) // Participant Position is not valid Todo Magic Number continue; // participant insideBuilding should also be 'effected' regarding hazardDistance // process following type of positions: // - outdoor positions (based on GPS or IndoorPositioningSystem) // - indoor positions (basedIndoorPositioningSystem) - Not implemented yet // see also : https://devhub.defence-elec.de/confluence/display/ORGSL/Wirkung+CES // // if (participantData.insideBuilding()) // { // DEBUG_LOG(m_logger) << "Participant:" << participantData.participantId() << " inside Building"; // continue; // } const QPointF &posUtm = participantData.ltsCoordinate().toUtm(zoneInOut); DEBUG_LOG(m_logger) << "Check, if participant with id = " << participantId << " is inside hazardDistanceForm: " << ", hasHazardDistance=" << curAivUtmArea.hasHazardDistance() << ", hazardDistance=" << curAivUtmArea.hazardDistance_meter << ", posUtm=" << posUtm << ", zone=" << zoneInOut; if (m_logger.isDebugEnabled()) { for (int i = 0; i < curAivUtmArea.form.size(); ++i) DEBUG_LOG(m_logger) << "form[" << i << "]=" << curAivUtmArea.form[i].x() << "/" << curAivUtmArea.form[i].y(); if (m_logger.isTraceEnabled()) for (int i = 0; i < curAivUtmArea.hazardDistanceForm.size(); ++i) TRACE_LOG(m_logger) << "hazardDistanceForm[" << i << "]=" << curAivUtmArea.hazardDistanceForm[i].x() << "/" << curAivUtmArea.hazardDistanceForm[i].y(); } // Laut ICD GUEZSB60_ICD-0052_Zentrale_Tne.doc Wirkfeldtyp Gefahrenbereich Steilfeuer // ist der Gefahrenbereich/Gefährdungsabstand // der a) INNERHALB des hazardDistance, aber b) AUSSERHALB des eigentlichen Wirkfeldes. // also: bRealInHazardDistance = participantIsInsideHazardDistance && !participantIsInsideForm // assume hazardDistanceForm is bigger than aiv :-) const bool participantIsInsideHazardDistance = curAivUtmArea.hazardDistanceForm.containsPoint(posUtm, Qt::FillRule::OddEvenFill); const bool participantIsInsideForm = participantIsInsideHazardDistance && curAivUtmArea.form.containsPoint(posUtm, Qt::FillRule::OddEvenFill); const bool bRealInHazardDistance = participantIsInsideHazardDistance && !participantIsInsideForm; const bool bWasRealInHazardDistance = m_enteredHazardDistanceStore.hasParticipantAlreadyEnteredArea(participantId, curAivUtmArea.aesItemId); DEBUG_LOG(m_logger) << "participantId = " << participantId << ", participantIsInsideHazardDistance = " << participantIsInsideHazardDistance << ", participantIsInsideForm = " << participantIsInsideForm << ", bRealInHazardDistance = " << bRealInHazardDistance << ", bWasRealInHazardDistanc = " << bWasRealInHazardDistance; // N. Thoden, 19.11.2024 // Ein Übergang von ganz innen nach ganz aussen oder umgekehrt, scheint nicht definiert zu sein. // Mit Hilfe der Verwendung von b___Real___InHazardDistance würde keine Modifikation erkannt werden... // does state changes? if (bWasRealInHazardDistance != bRealInHazardDistance) { // now IN? if (bRealInHazardDistance) { // part is ENTERING DEBUG_LOG(m_logger) << "Send HazardDistanceMessage to participant (ENTERING)..."; sendEnterHazardDistance(participantId, curAivUtmArea.aesItemId); } else { // logical consequence: part is LEAVING int remaining = 2; // ICD GUEZSB60_ICD-0052_Zentrale_Tne.doc Wirkfeldtyp Gefahrenbereich Steilfeuer // 1 = verlassen in Richtung Wirkfeld (nach innen) // 2 = verlassen nach aussen // 3 = Info // 0 = betreten // Die Meldung wird einmalig bei Betreten des Gefahrbereichs und bei Verlassen gesendet. // Die Meldung erfolgt fuer jeden Gefahrbereich, in dem sich der TN zu einem Zeitpunkt befindet // (sich ueberlappende Gefahrbereiche sind moeglich). Die Warnmeldung auf dem CAN-Bus erfolgt im 30 Sekunden-Takt, // solange sich der TN in einem oder mehreren Gefahrbereich(en) befindet. Betritt ein TN den Wirkbereich selber, // kommt eine Verlassensmeldung fuer das betreffende Wirkfeld ("Verlassen nach innen"). // Erst wenn der TN alle Gefahrbereiche - entweder nach innen in Richtung Wirkfeld selber oder nach aussen - verlassen hat, // unterbleiben die zyklischen Warnmeldungen auf dem CAN-Bus. // Bei Wirkung = 3 (Info) wird nur einmalig eine Warnmeldung ausgegeben. // Wird eine TNE stromlos (Restart) oder verliert zwischenzeitlich die Funkbereitschaft, // kommen fuer alle betretenen Gefahrbereich neue Betreten-Meldungen von der Zentrale (inkl. Wirkbeginn + Wirkdauer). if (participantIsInsideForm) remaining = 1; DEBUG_LOG(m_logger) << "Send HazardDistanceMessage to participant (LEAVING)... remaining =" << remaining; sendLeaveHazardDistance(participantData.participantId(), curAivUtmArea.aesItemId, remaining); } } // if (bWasRealInHazardDistance != bRealInHazardDistance) } // for } // for return numberOfParametrizedObjects; } void HandlerExplosions::onRestoreRadioReadinessChanged(const int participantId) { DEBUG_LOG(m_logger) << "HandlerExplosions::onRestoreRadioReadinessChanged participantId=" << participantId; m_enteredHazardDistanceStore.removeParticipant(participantId); } // return true, if participant is inside of the bounding Box bool HandlerExplosions::inBoundingBoxCheck(const CentralEffectSimulation::AesItemViewUtm &itemViewUtm, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition) { DEBUG_LOG(m_logger) << "inBoundingBoxCheck 1" << ", aesItemId=" << itemViewUtm.aesItemId << ", effectStateId=" << itemViewUtm.effectStateId; if ((itemViewUtm.effectStateId != AreaEffectSimulation::AesEffectState_def::EsActivated) && (itemViewUtm.effectStateId != AreaEffectSimulation::AesEffectState_def::EsPrepared) && (itemViewUtm.effectStateId != AreaEffectSimulation::AesEffectState_def::EsFired)) return false; DEBUG_LOG(m_logger) << "inBoundingBoxCheck 2"; // QRectF(topLeft, bottomRight) -> no need to normalize, QRectF:contains() handles it correct const QRectF boundingBoxImpact(itemViewUtm.boundingBoxBottomLeft, itemViewUtm.boundingBoxTopRight); const QPointF participantPosUtmPoint = CentralEffectSimulation::geographic2Utm(participantPosition.pos(), itemViewUtm.zone); if (boundingBoxImpact.contains(participantPosUtmPoint)) { DEBUG_LOG(m_logger) << "inBoundingBoxCheck Paricipant " << participantPosition.participantId() << " inside AesItemView:" << itemViewUtm.aesItemId; return true; } DEBUG_LOG(m_logger) << "inBoundingBoxCheck Paricipant " << participantPosition.participantId() << " outside AesItemView:" << itemViewUtm.aesItemId; return false; } // END inBoundingBoxCheck // [GUEZE-3526] SMART Munition ueberpruefen // Nach der Umstellung auf 3D-WS wird der Einschlag von SMART aus dem DDS-Topic AES::Effects genommen. void HandlerExplosions::calculateSmartEffect(const AreaEffectSimulation::Effects &effects) { DEBUG_LOG(m_logger) << "calculateSmartEffect"; AreaEffectSimulation::AesItem aesItem; bool itemAvailable = m_ddsHandler->dtcAes()->aesItemFromId(effects.aesItemId(), aesItem); if ( itemAvailable) { for (const AreaEffectSimulation::SingleEffect &se : qAsConst(effects.singleEffects())) { DEBUG_LOG(m_logger) << LOGFUNCTION << " ---> singleEffect=" << se << ", aesItem= " << aesItem; calculateSmartExplosion(effects.aesItemId(), aesItem.chargeStruct(), aesItem.partyId().is_set() ? aesItem.partyId().get() : 0, Core::GeoPosition(se.position().lon(), se.position().lat(), 0), effects.sampleTime()); } // for } // if else { WARNING_LOG(m_logger) << "No aesItem found for effect aesItemId=" << effects.aesItemId(); } // else } // END calculateSmartEffect void HandlerExplosions::calculateSmartExplosion(const int32_t aesItemId, const AreaEffectSimulation::ChargeStruct &chargeStruct, int32_t partyId, const Core::GeoPosition2D &impactPosition, qint64 impactTime) { DEBUG_LOG(m_logger) << "calculateSmartExplosion " << " partyId=" << partyId; if (partyId == 0) return; int impactPosZone = -1; const QPointF impactPos(CentralEffectSimulation::geographic2Utm(impactPosition, &impactPosZone)); QVector vehiclesInSmartExplosion; DEBUG_LOG(m_logger) << "calculateSmartExplosion chargeTypeId=" << chargeStruct.chargeTypeId(); // Insert all Vehicles inside of explosion-Radius in list vehiclesInSmartExplosion const double searchRadius = m_ddsHandler->searchRadius(chargeStruct); // where the smart munition should search for targets const double impactRadius = m_ddsHandler->chargeTypeRadius(chargeStruct); // the real explosion area radius DEBUG_LOG(m_logger) << "Handle ExplosionSingle (EsActivated)"; bool bHumanFound = false; const auto &participantPositions = m_handlerParticipant->positions(); for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition : qAsConst(participantPositions)) { if (m_handlerParticipant->isOutdated(participantPosition)) // Participant Position is not valid Todo Magic Number continue; const QPointF participantPosUtmPoint = CentralEffectSimulation::geographic2Utm(participantPosition.pos(), impactPosZone); ParticipantManagement::TypeClass typeClass = m_handlerParticipant->typeClassFromTypeId(participantPosition.participantTypeId()); DEBUG_LOG(m_logger) << "participant id=" << participantPosition.participantId() << " participantTypeId=" << participantPosition.participantTypeId() << " partyType =" << participantPosition.partyId() << " typeClass=" << typeClass; // Only send, if Any Participant is in Smart-Range (save Tetra load) if (!bHumanFound && typeClass == ParticipantManagement::TypeClass_def::Human) bHumanFound = inCircle(impactPos, impactRadius, participantPosUtmPoint); if (typeClass != ParticipantManagement::TypeClass_def::GroundVehicle && typeClass != ParticipantManagement::TypeClass_def::VehicleWithGear && typeClass != ParticipantManagement::TypeClass_def::WeaponSystem) continue; DEBUG_LOG(m_logger) << "participantPosition.timestamp =" << participantPosition.sampleTime() << " effectTime = " << m_ddsHandler->currentPartitionTime(); DEBUG_LOG(m_logger) << "Vehicle => calculate SMART participantId=" << participantPosition.participantId() << " ImpactPos=" << CentralEffectSimulation::debug(impactPos) << " searchRadius=" << searchRadius << " participantPosition=" << CentralEffectSimulation::debug(participantPosUtmPoint); if (inCircle(impactPos, searchRadius, participantPosUtmPoint)) { ParticipantDistance oneEntry; oneEntry.squareDistance = CentralEffectSimulation::getSquareDistance(impactPos, participantPosUtmPoint); oneEntry.participantPosition = participantPosition; DEBUG_LOG(m_logger) << "squareDistance = " << oneEntry.squareDistance << " append participantId=" << participantPosition.participantId(); vehiclesInSmartExplosion.append(std::move(oneEntry)); } } CentralEffectSimulation::AesItemViewUtm itemViewUtm = m_aesItemViewsImpactsUtm.value(aesItemId); itemViewUtm.chargeStruct = chargeStruct; // SingleExplosion: no Entry itemViewUtm.aesItemId = aesItemId; DEBUG_LOG(m_logger) << "aesItemViewUtm itemView.chargeTypeId =" << itemViewUtm.chargeStruct.chargeTypeId(); if (vehiclesInSmartExplosion.size() == 0) { DEBUG_LOG(m_logger) << "size=0"; if (bHumanFound) { CentralEffectSimulation::HandlerParticipant::CesParticipantData pos; pos.pos(Core::GeoPosition(impactPosition.lon(), impactPosition.lat(), 0)); // send single fire to all if at least one participant is inside circle // REGG-4160 no SMART-reachtion in GUEZ // sendCesHitRequest(itemView, pos, impactTime); } } else if (vehiclesInSmartExplosion.size() == 1) { DEBUG_LOG(m_logger) << "size=1"; // send two single fire to the same vehicle sendCesHitRequest( itemViewUtm, vehiclesInSmartExplosion.first().participantPosition, impactTime , 100, ProjectDataManagement::EventType_def::HitEffectSimulation); // 100 Percent sendCesHitRequest( itemViewUtm, vehiclesInSmartExplosion.first().participantPosition, impactTime + 1000, 100, ProjectDataManagement::EventType_def::HitEffectSimulation); // 100 Percent sendParticipantEvent(itemViewUtm, vehiclesInSmartExplosion.first().participantPosition, ProjectDataManagement::EventType_def::HitEffectSimulation); sendParticipantEvent(itemViewUtm, vehiclesInSmartExplosion.first().participantPosition, ProjectDataManagement::EventType_def::HitEffectSimulation); } else { DEBUG_LOG(m_logger) << "size=" << vehiclesInSmartExplosion.size(); // send single fire to the two nearest vehicles // sort the list by the distance const auto mapSortLessThan = [](const ParticipantDistance & s1, const ParticipantDistance & s2) -> bool { return s1.squareDistance < s2.squareDistance; }; std::sort(vehiclesInSmartExplosion.begin(), vehiclesInSmartExplosion.end(), mapSortLessThan); sendCesHitRequest( itemViewUtm, vehiclesInSmartExplosion.at(0).participantPosition, impactTime, 100, ProjectDataManagement::EventType_def::HitEffectSimulation); // 100 Percent sendCesHitRequest( itemViewUtm, vehiclesInSmartExplosion.at(1).participantPosition, impactTime, 100, ProjectDataManagement::EventType_def::HitEffectSimulation); // 100 Percent sendParticipantEvent(itemViewUtm, vehiclesInSmartExplosion.at(0).participantPosition, ProjectDataManagement::EventType_def::HitEffectSimulation); sendParticipantEvent(itemViewUtm, vehiclesInSmartExplosion.at(1).participantPosition, ProjectDataManagement::EventType_def::HitEffectSimulation); } } // calculateSmartExplosion // Check for all participants wheter he is in the explosion area void HandlerExplosions::calculateImpacts(const AreaEffectSimulation::TriggeredImpacts &data) { DEBUG_LOG(m_logger) << "calculateImpacts m_aesItemViewsImpactsUtm.size " << m_aesItemViewsImpactsUtm.size(); // older then 10sec? ignore if (data.sampleTime() < m_ddsHandler->currentPartitionTime() - 10 * 1000) return; if (!m_aesItemViewsImpactsUtm.contains(data.aesItemId())) { DEBUG_LOG(m_logger) << "calculateImpacts no aesItemId " << data.aesItemId(); return; } //CentralEffectSimulation::AesItemViewUtm aesItemViewUtm = m_aesItemViewsImpactsUtm.value(static_cast(data.aesItemId())); CentralEffectSimulation::AesItemViewUtm aesItemViewUtm; if ((data.aesTypeId().underlying() == AreaEffectSimulation::AesType_def::AtExplosionClusterSingleExplosions) || (data.aesTypeId().underlying() == AreaEffectSimulation::AesType_def::AtExplosionAreaSingleExplosions)) aesItemViewUtm = m_aesItemViewsImpactsUtm.value(static_cast(data.aesItemId())); else aesItemViewUtm = m_aesItemViewsUtm.value(static_cast(data.aesItemId())); DEBUG_LOG(m_logger) << "m_aesItemViewsUtm.insert aesItemId=" << aesItemViewUtm.aesItemId; const bool bIsSmartChargeType = m_ddsHandler->isSmartChargeType(aesItemViewUtm.chargeStruct); DEBUG_LOG(m_logger) << "calculateImpacts, isSMart: " << bIsSmartChargeType; DEBUG_LOG(m_logger) << "XXXXXXXXXXXX calculateImpacts1 " << data.aesItemId(); QVector bitsToCheck; for (int bitFieldIdx = 0; bitFieldIdx < data.impactCount(); ++bitFieldIdx) { // not yet triggered (bit is still 1) if (LtsUtils::BitArray::testBit(data.bitfield(), bitFieldIdx)) continue; if (m_handledImpactsStorage.isIndexProcessed(data.aesItemId(), bitFieldIdx)) continue; bitsToCheck += bitFieldIdx; DEBUG_LOG(m_logger) << "bitsToCheck+=" << bitFieldIdx; // update storage ('remember') m_handledImpactsStorage.setIndexProcessed(data.aesItemId(), bitFieldIdx); if (bIsSmartChargeType) { DEBUG_LOG(m_logger) << "XXXXXXXXXXXX SMART"; calculateSmartExplosion(aesItemViewUtm.aesItemId, aesItemViewUtm.chargeStruct, aesItemViewUtm.partyId, aesItemViewUtm.formGeo[bitFieldIdx], m_ddsHandler->currentPartitionTime()); } } if (bIsSmartChargeType) return; const auto &participantPositions = m_handlerParticipant->positions(); for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos : qAsConst(participantPositions)) { if (m_handlerParticipant->isOutdated(pPos)) // Participant Position is not valid Todo Magic Number continue; DEBUG_LOG(m_logger) << "pPos.participantId=" << pPos.participantId(); if (pPos.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill) continue; DEBUG_LOG(m_logger) << "pPos.vulnerabilityState=" << pPos.vulnerabilityState; if (pPos.insideBuilding()) continue; DEBUG_LOG(m_logger) << "pPos.insideBuilding=" << pPos.insideBuilding(); DEBUG_LOG(m_logger) << "calculateImpacts"; calculateImpacts(aesItemViewUtm, bitsToCheck, pPos); } } // END calculateImpacts // calculate all impacts of the field for a participant void HandlerExplosions::calculateImpacts(const CentralEffectSimulation::AesItemViewUtm &itemView, const QVector &impactsToCheck, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition) { DEBUG_LOG(m_logger) << "calculateImpacts"; // get radius of munition const double impactRadius = m_ddsHandler->chargeTypeRadius(itemView.chargeStruct); const QPointF &participantPosUtmPoint = CentralEffectSimulation::geographic2Utm(participantPosition.pos(), itemView.zone); DEBUG_LOG(m_logger) << "XXXXXXXXXXXX calculateImpacts2a impactsToCheck.size " << impactsToCheck.size(); for (int bitFieldIdx : qAsConst(impactsToCheck)) { if (itemView.form.size() <= bitFieldIdx) continue; // get position of impact to corresponding bit const QPointF &impactPos = itemView.form.value(bitFieldIdx); DEBUG_LOG(m_logger) << "XXXXXXXXXXXX calculateImpacts2b check impactPos: x= " << impactPos.x() << " y=" << impactPos.y(); if (inCircle(impactPos, impactRadius, participantPosUtmPoint)) sendEffectTypeEvent(itemView, participantPosition, EffectType::ExplosionArea, m_ddsHandler->currentPartitionTime()); } // foreach Impact } void HandlerExplosions::calculateImpactsEffect(const CentralEffectSimulation::AesItemViewUtm &itemView, const QVector &triggeredEffectsLtsCoord, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition, Options lineOfSightOptions) { const LtsCoordinate &partLtsCoordinate = participantPosition.ltsCoordinate(); // WITH zValue const int participantId = participantPosition.participantId(); INFO_LOG(m_logger) << "calculateImpactsEffect" << " START" << ", participantId = " << participantId << ", participantPosition.ltsCoordinate = '" << participantPosition.ltsCoordinate().toString() << "'" << ", triggeredEffectsLtsCoord.size() = " << triggeredEffectsLtsCoord.size() << ", -> partLtsCoordinate " << partLtsCoordinate << ", lineOfSightOptions " << lineOfSightOptions << ", chargeStruct= " << itemView.chargeStruct; if (triggeredEffectsLtsCoord.empty()) return; // get radius of munition const ProjectDataManagement::GearClass &gearClass = m_ddsHandler->dtcPdm()->gearClassFromChargeTypeId(itemView.chargeStruct.chargeTypeId()); const bool bIsAirSupport = (gearClass.underlying() == ProjectDataManagement::GearClass_def::AirSupport); const QVector &rangesInMeter = chargeRanges(itemView.chargeStruct); DEBUG_LOG(m_logger) << "calculateImpactsEffect" << " chargeRanges:" << ", chargeTypeId = " << itemView.chargeStruct.chargeTypeId() << ", gearClass = " << gearClass << ", bIsAirSupport = " << bIsAirSupport << ", chargeTypeId = " << itemView.chargeStruct.chargeTypeId() << ", ranges.size = " << rangesInMeter.size(); Q_ASSERT(!rangesInMeter.isEmpty()); // Der z-Wert duerfte der Unterkante des Participants entsprechen. // Wir heben pauschal um 1.0m an, um leichte Bodenunebenheiten zu kompensieren: // Muss dies evtl. Typ-anhaengig geschehen? LtsCoordinate shiftedParticipantLtsCoord = partLtsCoordinate; // WITH zValue shiftedParticipantLtsCoord.rz() += 1.0; // shift 1.0m DEBUG_LOG(m_logger) << "shiftedParticipantLtsCoord = " << shiftedParticipantLtsCoord.toString(true); for (const LtsCoordinate &newTriggeredImpactLtsCoord : qAsConst(triggeredEffectsLtsCoord)) { // const int zoneToUse = itemView.zone; // 2D or 3D?? const double planarDistancePartToImpact = newTriggeredImpactLtsCoord.distanceUtm2D(partLtsCoordinate); // is distance > max range? if (planarDistancePartToImpact > rangesInMeter.back()) continue; DEBUG_LOG(m_logger) << " planarDistancePartToImpact = " << planarDistancePartToImpact << ", bIsAirSupport = " << bIsAirSupport; // Wir heben pauschal um 1.0m an, um leichte Bodenunebenheiten zu kompensieren: LtsCoordinate shiftedNewTriggeredImpact = newTriggeredImpactLtsCoord; // WITH zValue shiftedNewTriggeredImpact.rz() += 1.0; // shift 1.0m // // 1 range -> typical explosionCluster // == impactRadius // 3 ranges -> typical explosionCluster with chargeTypeId of GearClass_def::AirSupport // == lethalRadius, nonLethalRadius, hazardDistance // // GUEZE-3136 Prüfauftrag - Gefährdungsabstand - Meldung if (bIsAirSupport) { int effect = 3; const RANGE mostInnerRange = getMostInnerRange(planarDistancePartToImpact, itemView.chargeStruct, effect); DEBUG_LOG(m_logger) << "getMostInnerRange, participantId=" << participantId << ", planarDistancePartToImpact=" << planarDistancePartToImpact << " -> RANGE=" << toString(mostInnerRange); switch (mostInnerRange) { case RANGE::HAZARD_DISTANCE: { effect = 3; // hazardDistance DEBUG_LOG(m_logger) << "Sende CES Hit Request: participantId=" << participantId << ", RANGE=" << toString(mostInnerRange) << ", effect=" << effect; sendCesHitRequest(itemView, participantPosition, QDateTime::currentMSecsSinceEpoch(), effect, ProjectDataManagement::EventType_def::EnterHazardDistance); } break; case RANGE::NON_LETHAL: case RANGE::LETHAL: { // Wenn verdeckt, dann sendCesHitRequest mit EnterHazardDistance: bool bLOS = true; const bool success = lineOfSight_elevationAndBuilding(m_ddsHandler->getExerciseId(), shiftedNewTriggeredImpact, shiftedParticipantLtsCoord, lineOfSightOptions, bLOS, m_proxyBuilding); INFO_LOG(m_logger) << " lineOfSight_elevationAndBuilding " << ", success=" << success << ", shiftedNewTriggeredImpact = " << shiftedNewTriggeredImpact.toString(true); // check LOS / LineOfSight DEBUG_LOG(m_logger) << "calculateImpactsEffect" << " check LOS / LineOfSight bLOS=" << bLOS << " participantId=" << participantId; if (bLOS) { DEBUG_LOG(m_logger) << "Sende CES Hit Request: participantId=" << participantId << ", RANGE=" << toString(mostInnerRange) << ", effect=" << effect; sendCesHitRequest(itemView, participantPosition, QDateTime::currentMSecsSinceEpoch(), effect, ProjectDataManagement::EventType_def::AirSupport); // AirSupport for both: LETHAL_RADIUS, NONLETHAL_RADIUS } #ifdef GUEZE_3136_IMPLEMENTED else { DEBUG_LOG(m_logger) << "Sende CES Hit Request: participantId=" << participantId << ", RANGE=" << toString(mostInnerRange) << " (Verdeckung -> EnterHazardDistance)"; sendCesHitRequest(itemView, participantPosition, QDateTime::currentMSecsSinceEpoch(), effect, ProjectDataManagement::EventType_def::EnterHazardDistance); } #endif // GUEZE_3136_IMPLEMENTED } // case case RANGE::OUTSIDE: // empty ; } // switch } else // if (bIsAirSupport) { QElapsedTimer timer; DEBUG_LOG(m_logger) << "calculateImpactsEffect" << " vor lineOfSightOptions start"; if (m_logger.isInfoEnabled()) timer.start(); bool bLOS = true; const bool success = lineOfSight_elevationAndBuilding(m_ddsHandler->getExerciseId(), shiftedNewTriggeredImpact, shiftedParticipantLtsCoord, lineOfSightOptions, bLOS, m_proxyBuilding); INFO_LOG(m_logger) << " lineOfSight_elevationAndBuilding took [ms] " << timer.elapsed() << ", success=" << success; // check LOS / LineOfSight DEBUG_LOG(m_logger) << "calculateImpactsEffect" << " check LOS / LineOfSight bLOS=" << bLOS << " participantId=" << participantId; if (bLOS) { INFO_LOG(m_logger) << "calculateImpactsEffect 3 participantId = " << participantId << " -> bInCircle and given LOS -> sendEffectTypeEvent ..."; sendEffectTypeEvent(itemView, participantPosition, EffectType::ExplosionArea, m_ddsHandler->currentPartitionTime()); } } // if (bIsAirSupport) } // for newTriggeredImpactLtsCoord } void HandlerExplosions::calculateImpactsEffect_outdated(const CentralEffectSimulation::AesItemViewUtm &itemView, const std::vector &triggeredEffectsGeoPos, const QVector &triggeredEffectsUtmPos, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition, Options lineOfSightOptions) { const LtsCoordTrans::LtsCoordinate &partLtsCoordinate = participantPosition.ltsCoordinate(); // WITH zValue INFO_LOG(m_logger) << LOGFUNCTION << " START" << " participantId = " << participantPosition.participantId() << " participantPosition.ltsCoordinate = '" << participantPosition.ltsCoordinate().toString() << "'" << ", triggeredEffectsGeoPos.size() = " << triggeredEffectsGeoPos.size() << ", triggeredEffectsUtmPos.size() = " << triggeredEffectsUtmPos.size() << ", participantPosition = " << participantPosition << ", -> partLtsCoordinate " << partLtsCoordinate << ", lineOfSightOptions " << lineOfSightOptions; INFO_LOG(m_logger) << LOGFUNCTION << participantPosition; if (triggeredEffectsGeoPos.empty()) return; if (static_cast(triggeredEffectsGeoPos.size()) != triggeredEffectsUtmPos.size()) return; DEBUG_LOG(m_logger) << "calculateImpactsEffect, triggeredEffectsUtmPos.size: " << triggeredEffectsGeoPos.size() << " participantPosition = " << participantPosition; // get radius of munition const double impactRadius = m_ddsHandler->chargeTypeRadius(itemView.chargeStruct); // Der z-Wert duerfte der Unterkante des Participants entsprechen. // Wir heben pauschal um 0.5m an. // Muss dies evtl. Typ-anhaengig geschehen? Core::GeoPosition participantPositionGeoPos = partLtsCoordinate.geoPosition(); // WITH zValue participantPositionGeoPos.z() += 0.5; // shift 0.5m const QPointF &participantPosUtmPoint = CentralEffectSimulation::geographic2Utm(participantPositionGeoPos, itemView.zone); DEBUG_LOG(m_logger) << " participantPositionGeoPos=" << participantPositionGeoPos.lon() << "/" << participantPositionGeoPos.lat() << " itemView.zone=" << itemView.zone << " participantPosUtmPoint=" << participantPosUtmPoint.x() << "/" << participantPosUtmPoint.y(); DEBUG_LOG(m_logger) << "calculateImpactsEffect 1 triggeredEffectsUtmPos.size " << triggeredEffectsUtmPos.size(); int idx = -1; for (const QPointF &newTriggeredImpactUtm : qAsConst(triggeredEffectsUtmPos)) { ++idx; const bool bInCircle = inCircle(newTriggeredImpactUtm, impactRadius, participantPosUtmPoint); DEBUG_LOG(m_logger) << "calculateImpactsEffect 2a check impactPos: x= " << newTriggeredImpactUtm.x() << " y=" << newTriggeredImpactUtm.y() << " participantPosUtmPoint=" << participantPosUtmPoint.x() << "/" << participantPosUtmPoint.y() << " impactRadius=" << impactRadius << ", inCircle = " << bInCircle << " participantId=" << participantPosition.participantId(); if (!bInCircle) { DEBUG_LOG(m_logger) << "Not in Circle => continue"; continue; } const Core::GeoPosition &newTriggeredImpactGeo = triggeredEffectsGeoPos[idx]; const int exerciseId = m_ddsHandler->getExerciseId(); QElapsedTimer timer; DEBUG_LOG(m_logger) << LOGFUNCTION << " vor lineOfSightOptions start"; if (m_logger.isInfoEnabled()) timer.start(); bool bLOS = true; const bool success = lineOfSight_elevationAndBuilding(exerciseId, LtsCoordTrans::LtsCoordinate(newTriggeredImpactGeo), LtsCoordTrans::LtsCoordinate(participantPositionGeoPos), lineOfSightOptions, bLOS, m_proxyBuilding); INFO_LOG(m_logger) << " lineOfSight_elevationAndBuilding took [ms] " << timer.elapsed() << " success=" << success; // check LOS / LineOfSight DEBUG_LOG(m_logger) << LOGFUNCTION << " check LOS / LineOfSight bLOS=" << bLOS << " participantId=" << participantPosition.participantId(); if (bLOS) { INFO_LOG(m_logger) << "calculateImpactsEffect 3 participantId = " << participantPosition.participantId() << " -> bInCircle and given LOS -> sendEffectTypeEvent ..."; sendEffectTypeEvent(itemView, participantPosition, EffectType::ExplosionArea, m_ddsHandler->currentPartitionTime()); } } // foreach newTriggeredImpactUtm } // Anmerkung: Im SFM werden Quadrate und keine Kreise genommen. ( Wird hier nach Absprache mit Manfred nicht gemacht) bool HandlerExplosions::inCircle(const QPointF ¢erUtm, const double radius, const QPointF &pointUtm) { double squareDist = getSquareDistance(centerUtm, pointUtm); double squareRadius = radius * radius; DEBUG_LOG(m_logger) << "squareDist: " << squareDist << ", squareRadius: " << squareRadius << " radius=" << radius << " inCircle=" << (squareDist < squareRadius) << " centerUtm=" << centerUtm.x() << "/" << centerUtm.y() << " pointUtm=" << pointUtm.x() << "/" << pointUtm.y(); return (squareDist < squareRadius); } QVector HandlerExplosions::chargeRanges(const AreaEffectSimulation::ChargeStruct &chargeStruct) const { const ProjectDataManagement::GearClass &gearClass = m_ddsHandler->dtcPdm()->gearClassFromChargeTypeId(chargeStruct.chargeTypeId()); DEBUG_LOG(m_logger) << "chargeTypeId = " << chargeStruct.chargeTypeId() << ", gearClass = " << gearClass; if (gearClass.underlying() == ProjectDataManagement::GearClass_def::AirSupport) { const double lethalRadius = m_ddsHandler->getLethalRadius(chargeStruct); const double nonLethalRadius = m_ddsHandler->getNonLethalRadius(chargeStruct); const double hazardDistance = m_ddsHandler->getHazardDistance(chargeStruct); return {lethalRadius, nonLethalRadius, hazardDistance}; } const double impactRadius = m_ddsHandler->chargeTypeRadius(chargeStruct); return {impactRadius}; } int HandlerExplosions::iedClassToEffect(int iedClass) const { switch (iedClass) { case 1: return 48; case 2: return 49; case 3: return 68; case 4: return 69; case 5: return 70; case 255: return 255; default: return 0; } } RANGE HandlerExplosions::getMostInnerRange(const QVector &ranges, double currentDistance) { static_assert((int)RANGE::LETHAL == 1, "RANGE mismatch"); static_assert((int)RANGE::NON_LETHAL == 2, "RANGE mismatch"); static_assert((int)RANGE::HAZARD_DISTANCE == 3, "RANGE mismatch"); static_assert((int)RANGE::OUTSIDE == 4, "RANGE mismatch"); int cnt = 4; // OUTSIDE QVector::const_reverse_iterator rit = ranges.crbegin(); while (rit != ranges.crend()) { double currentGearRange = *rit; if (currentGearRange == 0) { --cnt; ++rit; } if (currentDistance < currentGearRange ) --cnt; else break; ++rit; } return static_cast(cnt); } RANGE HandlerExplosions::getMostInnerRange(double currentDistance, const AreaEffectSimulation::ChargeStruct &chargeStruct, int &retEffect) { int hazard_Distance = m_ddsHandler->dtcPdm()->hazardDistance(chargeStruct.chargeTypeId(), chargeStruct.igniterTypeId(), chargeStruct.igniterFunctionId()); if (currentDistance > hazard_Distance) return RANGE::OUTSIDE; int lastRange = 9999; ProjectDataManagement::SeverityType lastST = ProjectDataManagement::SeverityType::noEffect; for (const auto &casEffect : m_ddsHandler->getCasEffects(chargeStruct)) { if (currentDistance < casEffect.impactRadius() && casEffect.impactRadius() < lastRange) { lastRange = casEffect.impactRadius(); lastST = casEffect.severityType(); retEffect = iedClassToEffect(casEffect.iedClass()); } } switch (lastST.underlying()) { case ProjectDataManagement::SeverityType::noEffect: return RANGE::HAZARD_DISTANCE; case ProjectDataManagement::SeverityType::lethal: return RANGE::LETHAL; case ProjectDataManagement::SeverityType::light: case ProjectDataManagement::SeverityType::medium: case ProjectDataManagement::SeverityType::heavy: return RANGE::NON_LETHAL; default: break; } return RANGE::OUTSIDE; } void HandlerExplosions::sendEffectTypeEvent(const CentralEffectSimulation::AesItemViewUtm &itemView, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition, EffectType effectType, qint64 effectTime) { Q_UNUSED(effectTime); // Trefferereignis Sekundaerwirkung const int ctasEffectType = static_cast(effectType) & 0xffff; const int participantId = participantPosition.participantId(); DEBUG_LOG(m_logger) << "Sending EffectType event " << ctasEffectType << ", participant: " << participantId << ", aesItemId: " << itemView.aesItemId << ", ctasEffectType: " << ctasEffectType; sendCesHitRequest(itemView, participantPosition, //effectTime >= 0 ? effectTime : participantPosition.sampleTime(), QDateTime::currentMSecsSinceEpoch(), ctasEffectType, ProjectDataManagement::EventType_def::HitEffectSimulation); } // Send via TAB-Proxy to battlefield void HandlerExplosions::sendCesHitRequest(const CentralEffectSimulation::AesItemViewUtm &itemView, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition, const qint64 effectTime, const int remaining, const ProjectDataManagement::EventType eventType) { int32_t legacyChargeTypeId = 0; legacyChargeTypeId = m_ddsHandler->getLegacyIdFromChargeTypeId(itemView.chargeStruct); // [E01911-49] This is just a band-aid. if (m_ddsHandler->isSmartChargeType(itemView.chargeStruct)) legacyChargeTypeId = 0; if (remaining < 0) return; DEBUG_LOG(m_logger) << "sendCesHitRequest" << " participantId=" << participantPosition.participantId() << " remaining=" << remaining << " eventType=" << eventType << " chargeTypeId=" << itemView.chargeStruct.chargeTypeId(); // Bei Nebel soll die Weste nur einmal piepen und nicht regelmaessig. Das wird mit Wirkung = 3 uebertragen int effect = remaining; if ((m_ddsHandler->isSmoke(itemView.chargeStruct)) && (remaining == 0)) effect = 3; const int equipNo = m_ddsHandler->participantId2EquipNo(participantPosition.participantId()); DEBUG_LOG(m_logger) << " ftc sendCESHit to Participant participantId = " << participantPosition.participantId() << " equipNo = " << equipNo << " aesItemId =" << itemView.aesItemId; // participantPosition.participantPosition == 0 -> send to all if ((participantPosition.participantId() != 0) && (equipNo <= 0)) { DEBUG_LOG(m_logger) << "participantPosition.participantId != 0 && equipNo <= 0" ; return; } if (participantPosition.participantId() != 0 && participantPosition.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill) { DEBUG_LOG(m_logger) << "participantPosition.participantId != 0 CatastrophicKill)"; return; } #ifdef ORIGINATOR_AT_SENDCESHITREQUEST const int32_t originatorParticipantId = m_aesItemId2partIdMap.value(itemView.aesItemId, 0); const int originatorEquipNo = m_ddsHandler->participantId2EquipNo(originatorParticipantId); #else const int originatorEquipNo = 0; #endif // ORIGINATOR_AT_SENDCESHITREQUEST const TrainingAreaBase::TabPosition pos(participantPosition.pos(), Core::PositionReportingSource_def::Gps, // irrelevant participantPosition.buildingId(), participantPosition.roomId()); // mineFieldBitIndex wird als reserve1 uegertragen und hier wird die Wirkdauer uebertragen. const int duration = m_ddsHandler->dtcAes().get()->explosionCluster(itemView.aesItemId).effectDuration() / 1000; // mineFieldBitIndex wird als reserve2 uebertragen und hier wird die Wirkbeginn uebertragen. int effectTime2 = 0; if (eventType == ProjectDataManagement::EventType::EnterHazardDistance) { const auto &ec = m_ddsHandler->dtcAes().get()->explosionCluster(itemView.aesItemId); if (ec.effectStart().is_set()) effectTime2 = ec.effectStart().get() / 1000; // msec to sec } TrainingAreaBase::SendCesHitRequest requestTab( equipNo, // equipmentNumber (injured/damaged) participantPosition.participantId(), // participantId legacyChargeTypeId > 0 ? legacyChargeTypeId : itemView.chargeStruct.chargeTypeId(), // chargeTypeId itemView.aesItemId, // aesId originatorEquipNo, // originatorEquipmentNumber effect, // Wirkung pos, effectTime2 > 0 ? effectTime2 : effectTime, // effectTime eventType, itemView.partyId, // originPartyId duration ); DEBUG_LOG(m_logger) << " participantId=" << participantPosition.participantId() << ", chargeTypeId=" << itemView.chargeStruct.chargeTypeId() << ", legacyChargeTypeId=" << legacyChargeTypeId << ", m_sendCesHitRequestQueue.size()=" << m_sendCesHitRequestQueue.size() << ", duration=" << duration; DEBUG_LOG(m_logger) << "ftcTab->SendCesHitRequest: " << requestTab; m_ddsHandler->ftcTab()->sendCesHitRequest(requestTab, this); if (!m_ddsHandler->isSmartChargeType(itemView.chargeStruct)) sendParticipantEvent(m_aesItemViewsUtm.value(itemView.aesItemId), participantPosition, ProjectDataManagement::EventType_def::HitEffectSimulation); } double HandlerExplosions::getSquareDistance(const QPointF ¢erUtm, const QPointF &pointUtm) { QPointF p(pointUtm - centerUtm); return p.x() * p.x() + p.y() * p.y(); } void HandlerExplosions::on_newHazardInfo(int participantId, int aesId) { DEBUG_LOG(m_logger) << "HandlerFunctionalAreas::on_HazardFinished participantId=" << participantId << " aesId =" << aesId << " TODO: Nach Start des Wirkfeldes müssen Teilnehmer alle 30 Sekunden bis Ablauf des Wirkfelds darüber informiert werden, dass sie sich im Gefährdungsabstand befinden"; } void HandlerExplosions::sendEnterHazardDistance(const int participantId, const int aesItemId) { DEBUG_LOG(m_logger) << "sendEnterHazardDistance: participantId=" << participantId << " aesItemId=" << aesItemId; DEBUG_LOG(m_logger) << "sendCesHitRequest"; const int ctasEffectType = 0; const CentralEffectSimulation::HandlerParticipant::CesParticipantData &cesParticipantData = m_handlerParticipant->position(participantId); m_enteredHazardDistanceStore.participantEnteringArea(participantId, aesItemId); ProjectDataManagement::EventType eventType = ProjectDataManagement::EventType_def::EnterHazardDistance; sendCesHitRequest(m_aesItemViewsUtm.value(aesItemId), cesParticipantData, QDateTime::currentMSecsSinceEpoch(), ctasEffectType, eventType); sendParticipantEvent(m_aesItemViewsUtm.value(aesItemId), cesParticipantData, ProjectDataManagement::EventType_def::EnterHazardDistance); } void HandlerExplosions::sendLeaveHazardDistance(const int participantId, const int aesItemId, const int remaining) { DEBUG_LOG(m_logger) << "sendLeaveHazardDistance: participantId=" << participantId << " aesItemId=" << aesItemId; DEBUG_LOG(m_logger) << "sendCesHitRequest" << " aesItemId=" << aesItemId; const int ctasEffectType = remaining; const CentralEffectSimulation::HandlerParticipant::CesParticipantData &cesParticipantData = m_handlerParticipant->position(participantId); const CentralEffectSimulation::AesItemViewUtm itemView = m_aesItemViewsUtm.value(aesItemId); m_enteredHazardDistanceStore.participantLeavingArea(participantId, aesItemId); sendCesHitRequest(itemView, cesParticipantData, QDateTime::currentMSecsSinceEpoch(), ctasEffectType, ProjectDataManagement::EventType_def::LeaveHazardDistance); sendParticipantEvent(itemView, cesParticipantData, ProjectDataManagement::EventType_def::LeaveHazardDistance); } void HandlerExplosions::sendParticipantEvent(const CentralEffectSimulation::AesItemViewUtm &item, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition, const ProjectDataManagement::EventType eventType) { DEBUG_LOG(m_logger) << "sendParticipantEvent participantId=" << participantPosition.participantId(); ParticipantManagement::TypeClass typeClass = m_handlerParticipant->typeClassFromTypeId(participantPosition.participantTypeId()); if (typeClass == ParticipantManagement::TypeClass_def::Unit || typeClass == ParticipantManagement::TypeClass_def::SupportUnit || typeClass == ParticipantManagement::TypeClass_def::SupportEntity || typeClass == ParticipantManagement::TypeClass_def::Unknown) return; DEBUG_LOG(m_logger) << " sendParticipantEvent participantId=" << participantPosition.participantId(); int equipmentNumber = m_ddsHandler->participantId2EquipNo(participantPosition.participantId()); if (equipmentNumber <= 0) return; DEBUG_LOG(m_logger) << " sendParticipantEvent participantId=" << participantPosition.participantId(); //const AreaEffectSimulation::OffRouteMine &of = m_ddsHandler->dtcAes()->offRouteMine(offRouteItem.aesItemId); const AreaEffectSimulation::ExplosionCluster &explosionCluster = m_ddsHandler->dtcAes()->explosionCluster(item.aesItemId); CentralEffectSimulation::ParticipantEvent pe; DEBUG_LOG(m_logger) << " sendParticipantEvent participantId=" << participantPosition.participantId() << " lat= " << participantPosition.pos().lat() << " lon= " << participantPosition.pos().lon() << " equipmentNumber=" << equipmentNumber << " eventType=" << eventType << " Name=" << explosionCluster.name() << " position=" << participantPosition.pos().lat() << "/" << participantPosition.pos().lon(); pe.id(m_ddsHandler->getParticipantEventId()); pe.eventTypeId(eventType); pe.participantId(participantPosition.participantId()); pe.eventTime(participantPosition.sampleTime()); pe.aesItemId(item.aesItemId); pe.partyId(participantPosition.partyId()); pe.aesName(explosionCluster.name()); pe.equipmentNumber(equipmentNumber); pe.friendlyFire(item.partyId == participantPosition.partyId()); pe.position(participantPosition.pos()); m_ddsHandler->dtsCes()->publish(pe); qint64 id = pe.id(); QTimer::singleShot(1 * 60 * 1000, this, [id, this]() { m_ddsHandler->dtsCes()->removeParticipantEventByKey(id); }); } // // class HandledImpactsStorage::Private // class EnteredHarzardDistanceStorage::Private { public: Private() = default; ~Private() = default; // data: QHash> m_participantId2aesItemId; // Key = participantId ; entered areas }; // // class HandledImpactsStorage // EnteredHarzardDistanceStorage::EnteredHarzardDistanceStorage() : d(new Private) {} EnteredHarzardDistanceStorage::~EnteredHarzardDistanceStorage() { delete d; } void EnteredHarzardDistanceStorage::participantEnteringArea(int participantId, int areaId) { // Key = participantId ; entered areas d->m_participantId2aesItemId[participantId].insert(areaId); // QSet: no duplicate } void EnteredHarzardDistanceStorage::participantLeavingArea(int participantId, int areaId) { // Key = participantId ; entered areas const auto &areaIt = d->m_participantId2aesItemId.find(participantId); if (areaIt != d->m_participantId2aesItemId.end()) areaIt->remove(areaId); } bool EnteredHarzardDistanceStorage::hasParticipantAlreadyEnteredArea(int participantId, int areaId) const { // Key = participantId ; entered areas const auto &areaIt = d->m_participantId2aesItemId.find(participantId); if (areaIt != d->m_participantId2aesItemId.end()) return areaIt->contains(areaId); return false; } void EnteredHarzardDistanceStorage::removeParticipant(int participantId) { // Key = participantId ; entered areas d->m_participantId2aesItemId.remove(participantId); // QSet: } void EnteredHarzardDistanceStorage::removeArea(int aesItemId) { // Key = participantId ; entered areas QMutableHashIterator> it(d->m_participantId2aesItemId); while (it.hasNext()) { it.next(); QSet &aesItemsRef = it.value(); aesItemsRef.remove(aesItemId); // is based on indxOf ... } // while } QVector EnteredHarzardDistanceStorage::allParticipantIdsOfArea(int areaId) const { // Key = participantId ; entered areas QVector allIds; allIds.reserve(d->m_participantId2aesItemId.size()); QHashIterator> it(d->m_participantId2aesItemId); while (it.hasNext()) { it.next(); const QSet &aesItemsRef = it.value(); if (aesItemsRef.contains(areaId)) allIds.push_back(it.key()); } // while return allIds; } QString toString(RANGE range) { switch (range) { case RANGE::LETHAL: return QStringLiteral("LETHAL"); case RANGE::NON_LETHAL: return QStringLiteral("NON_LETHAL"); case RANGE::HAZARD_DISTANCE: return QStringLiteral("HAZARD_DISTANCE"); case RANGE::OUTSIDE: return QStringLiteral("OUTSIDE"); } return QStringLiteral("RANGE_Error"); } } // namespace CesExplosions