/** * Handler for player data * $Id$ * $HeadURL$ * $Author$ * Created by gr, SLSE, 2017-02-20 */ #include "handler_mine_areas.h" #include "CentralEffectSimulation/handler_participant.h" #include "settings.h" #include #include #include #include #include #include namespace dds { namespace core { inline uint qHash(ProjectDataManagement::EventType et) { return ::qHash(et.underlying()); } } } namespace CesMineAreas { inline uint qHash(EffectType et) { return ::qHash(int(et)); } QString toString(EffectType et) { switch (et) { case EffectType::Unknown: return QStringLiteral("Unknown"); case EffectType::EffectiveMineRangeEnter: return QStringLiteral("EffectiveMineRangeEnter"); case EffectType::EffectiveMineRangeLeave: return QStringLiteral("EffectiveMineRangeLeave"); case EffectType::NContaminationLeave: return QStringLiteral("NContaminationLeave"); case EffectType::NContaminationEnterArea1: return QStringLiteral("NContaminationEnterArea1"); case EffectType::NContaminationEnterArea2: return QStringLiteral("NContaminationEnterArea2"); case EffectType::NContaminationDetonation: return QStringLiteral("NContaminationDetonation"); case EffectType::FunctionalAreaLeave: return QStringLiteral("FunctionalAreaLeave"); case EffectType::FunctionalAreaEnter: return QStringLiteral("FunctionalAreaEnter"); case EffectType::ExplosionArea: return QStringLiteral("ExplosionArea"); case EffectType::ExplosionSingle: return QStringLiteral("ExplosionSingle"); case EffectType::BCContaminationLeave: return QStringLiteral("BCContaminationLeave"); case EffectType::BCContaminationEnterFloating: return QStringLiteral("BCContaminationEnterFloating"); case EffectType::BCContaminationEnterResident: return QStringLiteral("BCContaminationEnterResident"); case EffectType::BCContaminationEnterFloatingArea: return QStringLiteral("BCContaminationEnterFloatingArea"); case EffectType::BeStillWarning: return QStringLiteral("BeStillWarning"); } return QStringLiteral("Unknown (%1)").arg(int(et)); } HandlerMineAreas::HandlerMineAreas(const std::shared_ptr &ddsHandler, const std::shared_ptr &participantHandler, QObject *parent) : QObject(parent) , m_ddsHandler(ddsHandler) , m_handlerParticipant(participantHandler) , m_logger(CentralEffectSimulation::getLogger("Plugin.MineAreas.HandlerMineAreas")) { DEBUG_LOG(m_logger) << "HandlerMineAreas::HandlerMineAreas"; m_aesItemViews.clear(); } // end of ctor() //----------------------------------------------------------------------------- HandlerMineAreas::~HandlerMineAreas() { m_aesItemViews.clear(); m_sendCesHitRequestQueue.clear(); m_eventStates.clear(); m_effectTypeStates.clear(); m_effectTypeBitFieldIndexes.clear(); } // end of dtor() //----------------------------------------------------------------------------- bool HandlerMineAreas::init() { auto dtcAes = m_ddsHandler->dtcAes(); dtcAes->waitForHistoricalDataMineArea(2000); dtcAes->waitForHistoricalDataMineCorridor(2000); DEBUG_LOG(m_logger) << "HandlerMineAreas::init"; dtcAes->enableTriggeredImpacts(); connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::triggeredImpactsChanged, this, &HandlerMineAreas::onTriggeredImpactsChanged); dtcAes->enableMineArea(); ////////////////////////////////////////////////////////////////// dtcAes->waitForHistoricalDataAesItemView(1000); connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::aesItemViewChanged, this, &HandlerMineAreas::onAesItemViewChanged); DEBUG_LOG(m_logger) << " m_aesItemViews.size=" << m_aesItemViews.size(); // Get 'static' data for all onAesItemView { auto cb = [this](const AreaEffectSimulation::AesItemView & aiv) { onAesItemViewChanged(aiv, DataModification::eAdded); }; if (!dtcAes->aesItemViews(cb)) return false; } DEBUG_LOG(m_logger) << " m_aesItemViews.size=" << m_aesItemViews.size(); connect(m_handlerParticipant.get(), &CentralEffectSimulation::HandlerParticipant::newOutdoorPosition, this, &HandlerMineAreas::onNewOutdoorPosition); connect(m_handlerParticipant.get(), &CentralEffectSimulation::HandlerParticipant::newParticipantState, this, &HandlerMineAreas::onNewParticipantState); INFO_LOG(m_logger) << "HandlerParticipantArea::init() finished."; return true; } // end of init() //----------------------------------------------------------------------------- void HandlerMineAreas::onSendCesHitReply(const Core::FunctionReplyObject &replyObject) { DEBUG_LOG(m_logger) << "onSendCesHitReply for id " << replyObject.id() << " success= " << replyObject.error(); if (replyObject.error()) WARNING_LOG(m_logger) << "Can not send enter/leave FunctionsArea to participant in training area: " << replyObject(); // Send next Request, if available if (!m_sendCesHitRequestQueue.isEmpty()) { DEBUG_LOG(m_logger) << "m_sendCesAreaActionRequestQueue.size()=" << m_sendCesHitRequestQueue.size(); m_sendCesHitRequestQueue.dequeue(); // Send next Message if (!m_sendCesHitRequestQueue.isEmpty()) { const auto &hr = m_sendCesHitRequestQueue.head(); DEBUG_LOG(m_logger) << "sendCesHitRequest: " << hr; m_ddsHandler->ftcTab()->sendCesHitRequest(hr, this); } } } void HandlerMineAreas::onMineTriggeredReply(const Core::FunctionReplyObject &replyObject) { if (replyObject.error()) ERROR_LOG(m_logger) << "Can not send MineTriggerReply: " << replyObject(); else DEBUG_LOG(m_logger) << "MineTriggerReply for id " << replyObject.id() << " success"; } // /////////////////////////////////// // A E S (AreaEffectSimulation) // /////////////////////////////////// void HandlerMineAreas::onAesItemViewChanged(const AreaEffectSimulation::AesItemView &data, DataModification modType) { if (data.aesTypeId() != AreaEffectSimulation::AesType_def::AtMineAreaSingleMines && data.aesTypeId() != AreaEffectSimulation::AesType_def::AtMineArea && data.aesTypeId() != AreaEffectSimulation::AesType_def::AtMineCorridor) { DEBUG_LOG(m_logger) << "AesItemView return"; return; } const AreaEffectSimulation::MineArea &ma = m_ddsHandler->dtcAes()->mineArea(data.aesItemId()); DEBUG_LOG(m_logger) << "aesItemId=" << data.aesItemId() << " chargeTypeId=" << data.chargeStruct().chargeTypeId() << " isCentral=" << ma.isCentral(); // 240 = Verlegemine muss immer berechnet werden. if (!ma.isCentral() && (data.chargeStruct().chargeTypeId() != 240)) return; if (data.effectStateId() == AreaEffectSimulation::AesEffectState_def::EsExpired) { DEBUG_LOG(m_logger) << "AesItemView expired return id=" << data.aesItemId(); aesItemViewRemoved(data.aesItemId()); return; } if (m_ddsHandler->isDus(data.chargeStruct())) { DEBUG_LOG(m_logger) << "isDus aesItemId=" << data.aesItemId() << " chargeTypeId=" << data.chargeStruct().chargeTypeId() << " do not consider"; return; } const int aesItemId = data.aesItemId(); DEBUG_LOG(m_logger) << "Got AesItemView id: " << aesItemId << ", type: " << data.aesTypeId() << ", effectState: " << data.effectStateId() << ", modType: " << modType; DEBUG_LOG(m_logger) << " m_aesItemViews.size=" << m_aesItemViews.size(); if (modType == DataModification::eRemoved) { DEBUG_LOG(m_logger) << " aesItemViewRemoved aesItemId=" << aesItemId; aesItemViewRemoved(aesItemId); return; } if (data.aesTypeId() == AreaEffectSimulation::AesType_def::AtMineAreaSingleMines) { if (!m_aesItemViews.contains(aesItemId)) { DEBUG_LOG(m_logger) << " !m_aesItemViews.contains aesItemId=" << aesItemId; return; } // build Polygon Mines: DEBUG_LOG(m_logger) << " m_aesItemViews.size=" << m_aesItemViews.size(); auto &formMines = m_aesItemViews[data.aesItemId()].formMines; DEBUG_LOG(m_logger) << " m_aesItemViews.size=" << m_aesItemViews.size(); int &zone = m_aesItemViews[data.aesItemId()].zone; DEBUG_LOG(m_logger) << " m_aesItemViews.size=" << m_aesItemViews.size(); formMines.clear(); formMines.reserve(data.form().size()); for (const Core::GeoPosition2D &pointGeo : data.form()) formMines.push_back(CentralEffectSimulation::geographic2Utm(pointGeo, &zone)); return; } const AreaEffectSimulation::AesEffectState &effectState = data.effectStateId(); const double impactRadius = m_ddsHandler->chargeTypeRadius(data.chargeStruct()); DEBUG_LOG(m_logger) << "impactRadius=" << impactRadius; switch (effectState.underlying()) { case AreaEffectSimulation::AesEffectState_def::EsPrepared: { break; } case AreaEffectSimulation::AesEffectState_def::EsActivated: { const auto aesItemViewUtm(CentralEffectSimulation::AesItemViewUtm::create(data, m_aesItemViews[data.aesItemId()], impactRadius)); DEBUG_LOG(m_logger) << "EsActivated m_aesItemViews.insert data.aesItemId()= " << data.aesItemId() << " m_aesItemViews.size=" << m_aesItemViews.size(); m_aesItemViews.insert(data.aesItemId(), aesItemViewUtm); DEBUG_LOG(m_logger) << " m_aesItemViews.size=" << m_aesItemViews.size(); printAesItemViews(); break; } // case EsActivated case AreaEffectSimulation::AesEffectState_def::EsRemoved: case AreaEffectSimulation::AesEffectState_def::EsExpired: { aesItemViewRemoved(aesItemId); break; } default: // do nothing break; } // switch effectStateId DEBUG_LOG(m_logger) << " m_aesItemViews.size=" << m_aesItemViews.size(); printAesItemViews(); } // onAesItemViewChanged void HandlerMineAreas::onNewOutdoorPosition(const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition) { if (m_handlerParticipant->isOutdated(participantPosition)) return; if (m_handlerParticipant->getEquipmentNumber(participantPosition.participantId()) == 0) { DEBUG_LOG(m_logger) << "Not handle position: participant has no equipment number"; return; } ParticipantManagement::TypeClass typeClass = m_handlerParticipant->typeClassFromTypeId(participantPosition.participantTypeId()); // can happen since the positions are collected for all participants (no check if it is a SupportEntity or similar) if (typeClass == ParticipantManagement::TypeClass_def::Unit || typeClass == ParticipantManagement::TypeClass_def::SupportUnit || typeClass == ParticipantManagement::TypeClass_def::SupportEntity || typeClass == ParticipantManagement::TypeClass_def::Aircraft || typeClass == ParticipantManagement::TypeClass_def::Vessel || typeClass == ParticipantManagement::TypeClass_def::Unknown) { DEBUG_LOG(m_logger) << "return New Position Participant with id " << participantPosition.participantId() << " has invalid typeClass: " << typeClass; return; } int equipmentNumber = m_ddsHandler->participantId2EquipNo(participantPosition.participantId()); if (equipmentNumber <= 0) { TRACE_LOG(m_logger) << "return New Position - participant has no equipment number" << " participantId=" << participantPosition.participantId() << " typeClass=" << typeClass; return; } if (participantPosition.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill) { DEBUG_LOG(m_logger) << "New Position CatastrophicKill return"; return; } if (m_mineLists.contains(participantPosition.participantId())) { DEBUG_LOG(m_logger) << "Naechste Mine schicken ohne Bounding-Box-Check... return" << " Anzahl Minen in Liste:" << m_mineLists[participantPosition.participantId()].size() << " cesMaxPeriodForCashedMines=" << Settings::cesPeriodStoredMinesOutdated(); //DEBUG_LOG(m_logger) << "Settings::cesMaxPeriodForCashedMines() =" << Settings::cesMaxPeriodForCashedMines(); if ((m_mineLists[participantPosition.participantId()].size() > 0) && (QDateTime::currentMSecsSinceEpoch() - m_mineLists[participantPosition.participantId()][0].time < Settings::cesPeriodStoredMinesOutdated())) { DEBUG_LOG(m_logger) << "Send Stored mines"; // Search mine with the next no distance int distance = m_mineLists[participantPosition.participantId()][0].distance; int index = 0; for (int i = 0; i < m_mineLists[participantPosition.participantId()].size(); i++) { if (m_mineLists[participantPosition.participantId()][i].distance < distance) { distance = m_mineLists[participantPosition.participantId()][i].distance; index = i; } } //m_mineLists[participantPos.participantId()].value() int mineIndex = m_mineLists[participantPosition.participantId()][index].mineIndex; int aesItemId = m_mineLists[participantPosition.participantId()][index].aesItemId; if (!m_aesItemViews.contains(aesItemId)) return; sendFtcMineTrigger(aesItemId, mineIndex - 1); sendEffectTypeEvent(m_aesItemViews[aesItemId], participantPosition, EffectType::EffectiveMineRangeEnter, mineIndex - 1, -1); CentralEffectSimulation::HandlerParticipant::CesParticipantData minePosition = participantPosition; // TODO convert minePos to geographic and set minePosGeo // DEBUG_LOG(m_logger) << "minePosGeo lat=" << minePosGeo.lat() // << " lon=" << minePosGeo.lon() // << " z=" << minePosGeo.z(); sendParticipantEvent(m_aesItemViews[aesItemId], minePosition, ProjectDataManagement::EventType_def::HitMine); m_mineLists[participantPosition.participantId()].remove(index); if (m_mineLists[participantPosition.participantId()].size() == 0) { m_mineLists[participantPosition.participantId()].clear(); m_mineLists.remove(participantPosition.participantId()); } return; } else { DEBUG_LOG(m_logger) << "Stored mines are outdated!" << " actTime=" << QDateTime::currentMSecsSinceEpoch() << " Settings::cesPeriodStoredMinesOutdated()=" << Settings::cesPeriodStoredMinesOutdated(); m_mineLists[participantPosition.participantId()].clear(); m_mineLists.remove(participantPosition.participantId()); }// end if time-check } DEBUG_LOG(m_logger) << "HandlerParticipantArea::calculateAesParticipant paricipantId: " << participantPosition.participantId() << " m_aesItemViews.size=" << m_aesItemViews.size(); // const QVector utmStepPoly = m_handlerParticipant->getParticipantStep(participantPos.participantId(), m_zone); for (const CentralEffectSimulation::AesItemViewUtm &itemView : qAsConst(m_aesItemViews)) { int remaining = m_ddsHandler->remainingTasCode(itemView.chargeStruct); DEBUG_LOG(m_logger) << " itemView: " << itemView.aesItemId << ", " << itemView.aesTypeId << " remainingTasCode=" << remaining; if (itemView.effectStateId != AreaEffectSimulation::AesEffectState_def::EsActivated) continue; if (typeClass == ParticipantManagement::TypeClass_def::Human && remaining > 50) { DEBUG_LOG(m_logger) << "ParticipantManagement::TypeClass_def::Human && remaining > 50 continue"; continue; } if (typeClass == ParticipantManagement::TypeClass_def::Human && m_ddsHandler->isAtMine(itemView.chargeStruct)) continue; if (itemView.aesTypeId.underlying() == AreaEffectSimulation::AesType_def::AtMineArea) { // checks whether the participant has been positioned in the minefield or across it. bool inBoundingBox = inBoundingBoxCheck(itemView, participantPosition); if (!inBoundingBox) { bool intersectsBoundingBox = intersectsBoundingBoxCheck(itemView, participantPosition.participantId()); if (!intersectsBoundingBox) { DEBUG_LOG(m_logger) << "Continue nach Bounding BoxCheck itemView: " << itemView.aesItemId << ", " << " paricipantId=" << participantPosition.participantId(); continue; } } DEBUG_LOG(m_logger) << "Weiter nach Bounding BoxCheck itemView: " << itemView.aesItemId << ", " << " paricipantId=" << participantPosition.participantId(); bool inFormCheckResult = inFormCheck(itemView, participantPosition); if (!inFormCheckResult) { DEBUG_LOG(m_logger) << "!inForm"; if (!intersectFormCheck(itemView, participantPosition.participantId())) { DEBUG_LOG(m_logger) << "!intersectForm continue"; continue; } } DEBUG_LOG(m_logger) << "Weiter nach inFormCheck und intersectFormCheck"; sendParticipantEventEnterLeave(itemView, participantPosition, inFormCheckResult); DEBUG_LOG(m_logger) << "intersectMineRadiusCheck "; if (intersectMineRadiusCheck(itemView, participantPosition)) { DEBUG_LOG(m_logger) << "intersectMineRadiusCheck=true return"; return; } } // if AtMineArea } // for m_aesItemViews } // calculateAesParticipant void HandlerMineAreas::onNewParticipantState(const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPos) { DEBUG_LOG(m_logger) << "onNewParticipantState "; if (participantPos.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CombatReady) { DEBUG_LOG(m_logger) << "m_mineLists.contains(participantPos.participantId():" << m_mineLists.contains(participantPos.participantId()); if (m_mineLists.contains(participantPos.participantId())) { DEBUG_LOG(m_logger) << "onNewParticipantState CombatReady, loesche Minenliste Eintrag"; m_mineLists[participantPos.participantId()].clear(); m_mineLists.remove(participantPos.participantId()); } DEBUG_LOG(m_logger) << "onNewParticipantState CombatReady, loesche Minenliste"; } } // return true, if participant crossed the dangerous area of a mine bool HandlerMineAreas::intersectMineRadiusCheck(const CentralEffectSimulation::AesItemViewUtm &itemView, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition) { qreal distance = -1.0; qreal lastDistance = -1.0; QPointF minePos = QPointF(); int mineIndex = -1; int mineListIndex = -1; int mineListItemIndex = -1; int32_t bitFieldIdx = 0; const double impactRadius = m_ddsHandler->chargeTypeRadius(itemView.chargeStruct); const QPolygonF utmStep = m_handlerParticipant->getParticipantStep(participantPosition.participantId(), itemView.zone); for (const auto &itPos : itemView.formMines) { DEBUG_LOG(m_logger) << " itemId=" << itemView.aesItemId << " chargeTypeId=" << itemView.chargeStruct.chargeTypeId() << ", MinePosUtm: " << CentralEffectSimulation::debug(itPos) << " utmStep:" << utmStep[0].x() << utmStep[0].y() << ", " << utmStep[1].x() << utmStep[1].y(); // Check, if mine is active // already triggered (bit is already 0) if (m_triggeredImpacts.contains(itemView.aesItemId)) { const AreaEffectSimulation::TriggeredImpacts &triggeredImpact = m_triggeredImpacts[itemView.aesItemId]; if (!LtsUtils::BitArray::testBit(triggeredImpact.bitfield(), bitFieldIdx)) { ++bitFieldIdx; DEBUG_LOG(m_logger) << "Mine is active continue bitFieldIdx=" << bitFieldIdx; continue; } } const int mineIdx = (itemView.aesTypeId == AreaEffectSimulation::AesType_def::AtDusSingleMine) ? itemView.dusMineId : bitFieldIdx + 1; // mineIdx in minefield starts at 1 // Check, if participant is inside danger-radius of the mine distance = crossCircle(itPos, impactRadius, participantPosition.participantId(), itemView.zone); DEBUG_LOG(m_logger) << "distance=" << distance; if (distance < 0.0) { DEBUG_LOG(m_logger) << "distance < 0.0 continue"; ++bitFieldIdx; continue; } if (lastDistance < 0.0) lastDistance = distance + 1.0; MineListItem mineListItem; mineListItem.aesItemId = itemView.aesItemId; mineListItem.distance = distance; mineListItem.mineIndex = mineIdx; mineListItem.minePos = minePos; mineListItem.time = QDateTime::currentMSecsSinceEpoch(); m_mineLists[participantPosition.participantId()].append(mineListItem); mineListIndex++; DEBUG_LOG(m_logger) << " m_mineLists[" << participantPosition.participantId() << "].length=" << m_mineLists[participantPosition.participantId()].length(); if (distance < lastDistance) { DEBUG_LOG(m_logger) << " distance < lastDistance crossCircle for aesItemId: " << itemView.aesItemId << ", mineIdx: " << mineIdx << ", impactRadius:" << impactRadius << ", participantId: " << participantPosition.participantId() << ", lastDistance:" << lastDistance << ", distance:" << distance; minePos = itPos; mineIndex = mineIdx; lastDistance = distance; mineListItemIndex = mineListIndex; } bitFieldIdx++; } // for each mine DEBUG_LOG(m_logger) << "End of for-loop participantId=" << participantPosition.participantId() << " mineIndex=" << mineIndex << " lastDistance=" << lastDistance; if (lastDistance > 0) { DEBUG_LOG(m_logger) << "sendEffectTypeEvent participantId=" << participantPosition.participantId() << " mineIndex=" << mineIndex; // TODO Minenposition fuer den Event nehmen und nicht die Participant-Position sendFtcMineTrigger(itemView.aesItemId, mineIndex - 1); sendEffectTypeEvent(itemView, participantPosition, EffectType::EffectiveMineRangeEnter, mineIndex - 1, -1); CentralEffectSimulation::HandlerParticipant::CesParticipantData minePosition = participantPosition; // TODO convert minePos to geographic and set minePosGeo // DEBUG_LOG(m_logger) << "minePosGeo lat=" << minePosGeo.lat() // << " lon=" << minePosGeo.lon() // << " z=" << minePosGeo.z(); sendParticipantEvent(itemView, minePosition, ProjectDataManagement::EventType_def::HitMine); // aktuell verarbeitete Mine aus der Liste entfernen. m_mineLists[participantPosition.participantId()].remove(mineListItemIndex); } return false; } // return true, if participant is inside of the bounding Box bool HandlerMineAreas::intersectsBoundingBoxCheck(const CentralEffectSimulation::AesItemViewUtm &itemView, const int participantId) { bool result = false; DEBUG_LOG(m_logger) << "intersectsBoundingBoxCheck participantId=" << participantId << " itemViewId=" << itemView.aesItemId << " zone=" << itemView.zone; const QPolygonF stepPoly = m_handlerParticipant->getParticipantStep(participantId, itemView.zone); if (stepPoly.size() < 2) return false; DEBUG_LOG(m_logger) << "utmStep von=" << stepPoly.at(0).x() << "/" << stepPoly.at(0).y() << " bis=" << stepPoly.at(1).x() << "/" << stepPoly.at(1).y() << " zone=" << itemView.zone; DEBUG_LOG(m_logger) << "ItemView Bounding Box BottomLeft=" << itemView.boundingBoxBottomLeft.x() << "/" << itemView.boundingBoxBottomLeft.y() << " boundingBoxTopRight=" << itemView.boundingBoxTopRight.x() << "/" << itemView.boundingBoxTopRight.y(); #if QT_VERSION < QT_VERSION_CHECK(5,10,0) result = itemView.boundingBox.intersected(stepPoly).size() > 0; #else result = itemView.boundingBox.intersects(stepPoly); #endif DEBUG_LOG(m_logger) << "HandlerMineAreas::intersectsBoundingBoxCheck result =" << result; return result; } // return true, if participant is inside of the bounding Box bool HandlerMineAreas::inBoundingBoxCheck(const CentralEffectSimulation::AesItemViewUtm &itemView, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition) { const double impactRadius = m_ddsHandler->chargeTypeRadius(itemView.chargeStruct); qreal boundingBoxBottomLeftX = itemView.boundingBoxBottomLeft.x() - impactRadius; qreal boundingBoxBottomLeftY = itemView.boundingBoxBottomLeft.y() + impactRadius; qreal boundingBoxTopRightX = itemView.boundingBoxTopRight.x() + impactRadius; qreal boundingBoxTopRightY = itemView.boundingBoxTopRight.y() - impactRadius; QPointF boundingBoxBottomLeft = QPointF(boundingBoxBottomLeftX, boundingBoxBottomLeftY); QPointF boundingBoxTopRight = QPointF(boundingBoxTopRightX, boundingBoxTopRightY); DEBUG_LOG(m_logger) << " impactRadius=" << impactRadius << " itemView.boundingBoxBottomLeft" << itemView.boundingBoxBottomLeft.x() << "/" << itemView.boundingBoxBottomLeft.y() << " itemView.boundingBoxTopRight" << itemView.boundingBoxTopRight.x() << "/" << itemView.boundingBoxTopRight.y() << " boundingBoxBottomLeft=" << boundingBoxBottomLeft.x() << "/" << boundingBoxBottomLeft.y() << " boundingBoxTopRight=" << boundingBoxTopRight.x() << "/" << boundingBoxTopRight.y(); const QRectF boundingBoxImpact(boundingBoxBottomLeft, boundingBoxTopRight); const QPointF participantPosUtmPoint = CentralEffectSimulation::geographic2Utm(participantPosition.pos(), itemView.zone); if (boundingBoxImpact.contains(participantPosUtmPoint)) { DEBUG_LOG(m_logger) << "inBoundingBoxCheck Paricipant " << participantPosition.participantId() << " inside AesItemView:" << itemView.aesItemId << " return true"; return true; } DEBUG_LOG(m_logger) << "inBoundingBoxCheck Paricipant " << participantPosition.participantId() << " outside AesItemView:" << itemView.aesItemId << " return false"; return false; } // return true, if participant is inside of the form of AesItemView bool HandlerMineAreas::inFormCheck(const CentralEffectSimulation::AesItemViewUtm &itemView, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition) { const QPointF participantPosUtmPoint = CentralEffectSimulation::geographic2Utm(participantPosition.pos(), itemView.zone); DEBUG_LOG(m_logger) << "inFormCheck start, paricipantId: " << participantPosition.participantId() << ", aesItemId: " << itemView.aesItemId << ", aesTypeId: " << itemView.aesTypeId << ", form.size: " << itemView.form.size(); if (itemView.form.containsPoint(participantPosUtmPoint, Qt::FillRule::OddEvenFill)) { DEBUG_LOG(m_logger) << "inside AES form, paricipantId: " << participantPosition.participantId() << ", aesItemId: " << itemView.aesItemId; return true; } // if inside return inExtededFormCheck(itemView, participantPosition); } // return true, if participant is inside of the form of AesItemView bool HandlerMineAreas::intersectFormCheck(const CentralEffectSimulation::AesItemViewUtm &itemView, const int participantId) { const QPolygonF utmStep = m_handlerParticipant->getParticipantStep(participantId, itemView.zone); DEBUG_LOG(m_logger) << "intersectFormCheck start, paricipantId: " << participantId << ", aesItemId: " << itemView.aesItemId << ", aesTypeId: " << itemView.aesTypeId << ", form.size: " << itemView.form.size(); #if QT_VERSION < QT_VERSION_CHECK(5,10,0) if (itemView.form.intersected(utmStep).size() > 0) #else if (itemView.form.intersects(utmStep)) #endif { DEBUG_LOG(m_logger) << "intersect AES form, paricipantId: " << participantId << ", aesItemId: " << itemView.aesItemId; return true; } // if inside return false; } void HandlerMineAreas::onTriggeredImpactsChanged(const AreaEffectSimulation::TriggeredImpacts &data, DataModification modType) { if (modType != DataModification::eAdded && modType != DataModification::eModified) return; DEBUG_LOG(m_logger) << "Got TriggeredImpacts: "; switch (data.aesTypeId().underlying()) { case AreaEffectSimulation::AesType_def::AtExplosionArea: case AreaEffectSimulation::AesType_def::AtExplosionSingle: //case AreaEffectSimulation::AesType_def::AtExplosionAreaSingleExplosions: Plugin Artillery // explosion at the position of participant calculateImpacts(data); break; case AreaEffectSimulation::AesType_def::AtMineAreaSingleMines: case AreaEffectSimulation::AesType_def::AtMineArea: // participant run on mine // => store Mine-State and take into account when a participant runs to one of this mines m_triggeredImpacts.insert(data.aesItemId(), data); break; default: //nothigng break; } } // onTriggeredImpactsChanged // Check for all participants wheter he is in the explosion area void HandlerMineAreas::calculateImpacts(const AreaEffectSimulation::TriggeredImpacts &data) { if ((data.aesTypeId() == AreaEffectSimulation::AesType_def::AtExplosionAreaSingleExplosions) || (data.aesTypeId() == AreaEffectSimulation::AesType_def::AtExplosionClusterSingleExplosions)) return; auto it = m_aesItemViews.find(data.aesItemId()); if (it == m_aesItemViews.end()) { WARNING_LOG(m_logger) << "Could not find item view for aes with id " << data.aesItemId(); return; } CentralEffectSimulation::AesItemViewUtm &aesItemView = *it; // older then 10sec? ignore if (data.sampleTime() < m_ddsHandler->currentPartitionTime() - 10 * 1000) { if (aesItemView.sampleTimeHandledImpacts < data.sampleTime()) { aesItemView.sampleTimeHandledImpacts = data.sampleTime(); aesItemView.handledImpacts = data.bitfield(); } return; } QVector bitsToCheck; aesItemView.handledImpacts.resize(data.bitfield().size(), 0xFF); for (int bitFieldIdx = 0; bitFieldIdx < data.impactCount(); ++bitFieldIdx) { // not yet triggered (bit is still 1) if (LtsUtils::BitArray::testBit(data.bitfield(), bitFieldIdx)) continue; // already checked (bit is already 0) if (!LtsUtils::BitArray::testBit(aesItemView.handledImpacts, bitFieldIdx)) continue; DEBUG_LOG(m_logger) << "New impact at idx " << bitFieldIdx; bitsToCheck += bitFieldIdx; } // remember handled impacts aesItemView.sampleTimeHandledImpacts = data.sampleTime(); aesItemView.handledImpacts = data.bitfield(); const auto participantPositions = m_handlerParticipant->positions(); for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos : participantPositions) { DEBUG_LOG(m_logger) << "pos participantId=" << pPos.participantId() << " buildingId=" << pPos.buildingId() << " insideBuilding=" << pPos.insideBuilding() << " positionValid=" << pPos.positionValid; if (pPos.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill) continue; if (pPos.insideBuilding()) continue; if (!inBoundingBoxCheck(aesItemView, pPos)) continue; calculateImpacts(aesItemView, bitsToCheck, pPos); } } // calculateImpacts // calculate all impacts of the field for a participant void HandlerMineAreas::calculateImpacts(const CentralEffectSimulation::AesItemViewUtm &itemView, const QVector &impactsToCheck, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition) { // get radius of munition const double impactRadius = m_ddsHandler->chargeTypeRadius(itemView.chargeStruct); const QPointF participantPosUtmPoint = CentralEffectSimulation::geographic2Utm(participantPosition.pos(), itemView.zone); for (int bitFieldIdx : impactsToCheck) { if (itemView.form.size() <= bitFieldIdx) continue; // get position of impact to corresponding bit const QPointF &impactPos = itemView.form.value(bitFieldIdx); if (inCircle(impactPos, impactRadius, participantPosUtmPoint)) sendEffectTypeEvent(itemView, participantPosition, EffectType::ExplosionArea, bitFieldIdx, QDateTime::currentMSecsSinceEpoch()); } // foreach Impact } // calculateImpacts // Send via TAB-Proxy to battlefield void HandlerMineAreas::sendCesHitRequest(const CentralEffectSimulation::AesItemViewUtm &itemView, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition, qint64 effectTime, int mineFieldBitIndex) { int remaining = m_ddsHandler->remainingTasCode(itemView.chargeStruct); DEBUG_LOG(m_logger) << "sendCesHitRequest " << " insideWlsArea=" << std::boolalpha << participantPosition.insideWlsArea() << " participantId=" << participantPosition.participantId(); 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; } int32_t originatorParticipantId = m_aesItemId2partIdMap.value(itemView.aesItemId, 0); const TrainingAreaBase::TabPosition pos(participantPosition.pos(), Core::PositionReportingSource_def::Gps, // irrelevant participantPosition.buildingId(), participantPosition.roomId()); const int originatorEquipNo = m_ddsHandler->participantId2EquipNo(originatorParticipantId); const ProjectDataManagement::EventType eventType = ProjectDataManagement::EventType_def::Unknown; TrainingAreaBase::SendCesHitRequest requestTab( equipNo, // equipmentNumber participantPosition.participantId(), // participantId itemView.chargeStruct.chargeTypeId(), // chargeTypeId itemView.aesItemId, // aesId originatorEquipNo, // originatorEquipmentNumber remaining, // remainEffectPercent ( no protection outside Building ) pos, effectTime, // effectTime eventType, itemView.partyId, // originPartyId mineFieldBitIndex ); DEBUG_LOG(m_logger) << " participantId=" << participantPosition.participantId() << " chargeTypeId=" << itemView.chargeStruct.chargeTypeId(); m_sendCesHitRequestQueue.enqueue(requestTab); if (m_sendCesHitRequestQueue.size() == 1) { DEBUG_LOG(m_logger) << "SendCesHitRequest: " << requestTab; m_ddsHandler->ftcTab()->sendCesHitRequest(requestTab, this); } } void HandlerMineAreas::sendCesHitRequest(const AreaEffectSimulation::AesItemView &itemView, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition, qint64 effectTime, int mineFieldBitIndex) { int remaining = m_ddsHandler->remainingTasCode(itemView.chargeStruct()); int32_t legacyChargeTypeId = 0; legacyChargeTypeId = m_ddsHandler->getLegacyIdFromChargeTypeId(itemView.chargeStruct()); DEBUG_LOG(m_logger) << "sendCesHitRequest" << " insideWlsArea=" << std::boolalpha << participantPosition.insideWlsArea() << " participantId=" << participantPosition.participantId(); const int equipmentNumber = m_ddsHandler->participantId2EquipNo(participantPosition.participantId()); DEBUG_LOG(m_logger) << " ftc sendCESHit to Participant participantId = " << participantPosition.participantId() << " equipNo = " << equipmentNumber << " aesItemId =" << itemView.aesItemId(); // participantPosition.participantPosition == 0 -> send to all if (participantPosition.participantId() != 0 && equipmentNumber <= 0) { DEBUG_LOG(m_logger) << "participantPosition.participantId != 0 && equipNo <= 0" ; return; } // if if (participantPosition.participantId() != 0 && participantPosition.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill) { DEBUG_LOG(m_logger) << "participantPosition.participantId != 0 CatastrophicKill)"; return; } // if int32_t originatorParticipantId = m_aesItemId2partIdMap.value(itemView.aesItemId(), 0); const int originatorEquipmentNumber = m_ddsHandler->participantId2EquipNo(originatorParticipantId); const TrainingAreaBase::TabPosition pos( participantPosition.pos(), Core::PositionReportingSource_def::Gps, participantPosition.buildingId(), participantPosition.roomId() ); const ProjectDataManagement::EventType eventType = ProjectDataManagement::EventType_def::Unknown; TrainingAreaBase::SendCesHitRequest requestTab( equipmentNumber, participantPosition.participantId(), legacyChargeTypeId, itemView.aesItemId(), originatorEquipmentNumber, remaining, pos, effectTime, eventType, itemView.partyId().get(), mineFieldBitIndex ); DEBUG_LOG(m_logger) << " participantId=" << participantPosition.participantId() << " chargeTypeId=" << itemView.chargeStruct().chargeTypeId() << " legacyChargeTypeId=" << legacyChargeTypeId; m_sendCesHitRequestQueue.enqueue(requestTab); if (m_sendCesHitRequestQueue.size() == 1) { DEBUG_LOG(m_logger) << "SendCesHitRequest: " << requestTab; m_ddsHandler->ftcTab()->sendCesHitRequest(requestTab, this); } // if } void HandlerMineAreas::sendParticipantEventEnterLeave(const CentralEffectSimulation::AesItemViewUtm &itemView, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition, const bool inFormCheckResult) { const ProjectDataManagement::EventType &lastEvent = m_eventStates[itemView.aesItemId][participantPosition.participantId()]; ProjectDataManagement::EventType eventType = ProjectDataManagement::EventType_def::EnterMineArea; if (inFormCheckResult) { if (lastEvent == ProjectDataManagement::EventType_def::EnterMineArea) return; else { eventType = ProjectDataManagement::EventType_def::EnterMineArea; m_eventStates[itemView.aesItemId][participantPosition.participantId()] = ProjectDataManagement::EventType_def::EnterMineArea; } } else { if (lastEvent == ProjectDataManagement::EventType_def::LeaveMineArea) return; else { eventType = ProjectDataManagement::EventType_def::LeaveMineArea; m_eventStates[itemView.aesItemId][participantPosition.participantId()] = ProjectDataManagement::EventType_def::LeaveMineArea; } } sendParticipantEvent(itemView, participantPosition, eventType); } void HandlerMineAreas::sendParticipantEvent(const CentralEffectSimulation::AesItemViewUtm &itemView, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition, const ProjectDataManagement::EventType eventType) { DEBUG_LOG(m_logger) << "sendParticipantEvent " << itemView.aesItemId << " participantId=" << participantPosition.participantId() << " eventType=" << eventType << " pos=" << participantPosition.pos().lat() << "/" << participantPosition.pos().lon() << "/" << participantPosition.pos().z(); int equipmentNumber = m_ddsHandler->participantId2EquipNo(participantPosition.participantId()); const AreaEffectSimulation::MineArea &ma = m_ddsHandler->dtcAes()->mineArea(itemView.aesItemId); CentralEffectSimulation::ParticipantEvent pe; pe.id(m_ddsHandler->getParticipantEventId()); pe.eventTypeId(eventType.underlying()); pe.participantId(participantPosition.participantId()); pe.eventTime(participantPosition.sampleTime()); pe.aesItemId(itemView.aesItemId); pe.partyId(participantPosition.partyId()); pe.aesName(ma.name()); pe.equipmentNumber(equipmentNumber); pe.vulnerability(participantPosition.vulnerabilityState.underlying()); pe.position(participantPosition.pos()); m_ddsHandler->dtsCes()->publish(pe); const auto id = pe.id(); QTimer::singleShot(1 * 60 * 1000, this, [id, this]() { m_ddsHandler->dtsCes()->removeParticipantEventByKey(id); }); } void HandlerMineAreas::sendFtcMineTrigger(int aesItemId, int mineIndex) { const AreaEffectSimulation::MineArea &ma = m_ddsHandler->dtcAes()->mineArea(aesItemId); auto ftcAes = m_ddsHandler->ftcAes(); AreaEffectSimulation::MineTriggeredRequest tr; tr.mineAreaId(aesItemId); tr.mineId(mineIndex); tr.chargeTypeId(ma.chargeStruct().chargeTypeId()); DEBUG_LOG(m_logger) << "send sendFtcMineTrigger: aesItemId=" << tr.mineAreaId() << " MineIndex=" << tr.mineId(); ftcAes->mineTriggeredRequest(tr, this); } void HandlerMineAreas::sendEffectTypeEvent(const CentralEffectSimulation::AesItemViewUtm &itemView, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition, EffectType effectType, int mineFieldBitIndex, qint64 effectTime) { DEBUG_LOG(m_logger) << "sendEffectTypeEvent aesItemId=" << itemView.aesItemId << " participantId=" << participantPosition.participantId() << " effectType=" << effectTime << " mineFieldBitIndex=" << mineFieldBitIndex; Q_UNUSED(effectTime); // Trefferereignis Sekundaerwirkung QByteArray mfb; if (mineFieldBitIndex >= 0) mfb = ", mineFieldBitIndex: " + QByteArray::number(mineFieldBitIndex); const int ctasEffectType = static_cast(effectType) & 0xffff; DEBUG_LOG(m_logger) << "Sending EffectType event " << toString(effectType) << ", participant: " << participantPosition.participantId() << ", aesItemId: " << itemView.aesItemId << mfb << ", ctasEffectType: " << ctasEffectType; sendCesHitRequest(itemView, participantPosition, //effectTime >= 0 ? effectTime : participantPosition.sampleTime(), QDateTime::currentMSecsSinceEpoch(), mineFieldBitIndex); } // Anmerkung: Im SFM werden Quadrate und keine Kreise genommen. ( Wird hier nach Absprache mit Manfred nicht gemacht) qreal HandlerMineAreas::crossCircle(const QPointF ¢erUtm, const double radius, const int participantId, int zone) { qreal result = -1.0; QPointF p1 = QPointF(centerUtm.x() + radius, centerUtm.y() + radius); QPointF p2 = QPointF(centerUtm.x() + radius, centerUtm.y() - radius); QPointF p3 = QPointF(centerUtm.x() - radius, centerUtm.y() - radius); QPointF p4 = QPointF(centerUtm.x() - radius, centerUtm.y() + radius); QPolygonF boundingBoxPolyMine; boundingBoxPolyMine << p1 << p2 << p3 << p4 << p1; const QPolygonF stepPoly = m_handlerParticipant->getParticipantStep(participantId, zone); #if QT_VERSION < QT_VERSION_CHECK(5,10,0) if (boundingBoxPolyMine.intersected(stepPoly).size() > 0) #else if (boundingBoxPolyMine.intersects(stepPoly)) #endif { QLineF line; line.setP1(stepPoly[0]); line.setP2(centerUtm); DEBUG_LOG(m_logger) << "crossCircle distance =" << line.length(); return line.length(); } return result; } // inCircle // Anmerkung: Im SFM werden Quadrate und keine Kreise genommen. ( Wird hier nach Absprache mit Manfred nicht gemacht) bool HandlerMineAreas::inCircle(const QPointF ¢erUtm, const double radius, const QPointF &pointUtm) { double squareDist = CentralEffectSimulation::getSquareDistance(centerUtm, pointUtm); double squareRadius = radius * radius; DEBUG_LOG(m_logger) << "squareDist: " << squareDist << ", squareRadius: " << squareRadius; return (squareDist < squareRadius); } // inCircle void HandlerMineAreas::aesItemViewRemoved(int aesItemId) { DEBUG_LOG(m_logger) << "HandlerMineAreas::aesItemViewRemoved aesItemId=" << aesItemId; m_effectTypeStates.remove(aesItemId); m_effectTypeBitFieldIndexes.remove(aesItemId); m_aesItemViews.remove(aesItemId); m_eventStates.remove(aesItemId); } void HandlerMineAreas::printAesItemViews() { QHashIterator i(m_aesItemViews); while (i.hasNext()) { i.next(); DEBUG_LOG(m_logger) << "printAesItemViews key=" << i.key() << ": " << " aesItemId=" << i.value().aesItemId << " aesTypeId=" << i.value().aesTypeId; } } bool HandlerMineAreas::inExtededFormCheck(const CentralEffectSimulation::AesItemViewUtm &itemView, const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition) const { const double distance = m_ddsHandler->chargeTypeRadius(itemView.chargeStruct); const QPointF participantPosUtmPoint = CentralEffectSimulation::geographic2Utm(participantPosition.pos(), itemView.zone); /////////////////////////////////////////////////////////// // Check whether the position is within an expanded form // /////////////////////////////////////////////////////////// DEBUG_LOG(m_logger) << "itemView.form.size=" << itemView.form.size(); for (int i = 0; i < itemView.form.size() ; i++) { int nextPoint = i + 1; if (i == itemView.form.size() - 1) nextPoint = 0; const QPointF l1(itemView.form[i].x(), itemView.form[i].y()); const QPointF l2(itemView.form[nextPoint].x(), itemView.form[nextPoint].y()); double distancePointLineValue = distancePointLine(participantPosUtmPoint, l1, l2); DEBUG_LOG(m_logger) << "distancePointLineValue=" << distancePointLineValue << " ParticipantPos =" << participantPosUtmPoint.x() << "/" << participantPosUtmPoint.y() << " Line=" << l1.x() << "/" << l1.y() << " " << l2.x() << "/" << l2.y(); if (distancePointLineValue < distance) { DEBUG_LOG(m_logger) << "posInExtendedForm return true"; return true; } } DEBUG_LOG(m_logger) << "posInExtendedForm return false"; return false; } double HandlerMineAreas::distancePointLine(const QPointF &p, const QPointF &l1, const QPointF &l2) const { const double u = (((p.x() - l1.x()) * (l2.x() - l1.x())) + ((p.y() - l1.y()) * (l2.y() - l1.y()))) / squareLen(l2, l1); if ((u < 0.0f) || (u > 1.0f)) { QLineF pToP1(p, l1); QLineF pToP2(p, l2); qreal length1 = pToP1.length(); qreal length2 = pToP2.length(); if (length1 < length2) { DEBUG_LOG(m_logger) << "a distancePointLine p=" << p.x() << "/" << p.y() << " line: p1=" << l1.x() << "/" << l1.y() << " p2=" << l2.x() << "/" << l2.y() << " distance=" << length1 << ", return=" << length1; return length1; } else { DEBUG_LOG(m_logger) << "b distancePointLine p=" << p.x() << "/" << p.y() << " line: p1=" << l1.x() << "/" << l1.y() << " p2=" << l2.x() << "/" << l2.y() << " distance=" << length2 << ", return=" << length2; return length2; } } QPointF intersectPnt(l1.x() + u * (l2.x() - l1.x()), l1.y() + u * (l2.y() - l1.y())); /* double distance = sqrt(squareLen(p, intersectPnt)); DEBUG_LOG(m_logger) << "c distancePointLine p=" << p.x() << "/" << p.y() << " line: p1=" << l1.x() << "/" << l1.y() << " p2=" << l2.x() << "/" << l2.y() << " distance=" << distance; return distance;*/ return 0; } double HandlerMineAreas::squareLen(const QPointF &p1, const QPointF &p2) const { double vx = p2.x() - p1.x(); double vy = p2.y() - p1.y(); return vx * vx + vy * vy; } } // namespace CesMineAreas