/** * Handler for Mine Laying Artillery * $Id$ * $HeadURL$ * $Author$ * Created by Nino Pinzer, ESZS, 12.03.2026 */ #include "handler_mine_laying_artillery.h" #include #include #include #include #include #include namespace MineLayingArtillery { HandlerMineLayingArtillery::HandlerMineLayingArtillery(const std::shared_ptr &ddsHandler, QObject *parent) : QObject(parent) , m_effectClusterTimer(new QTimer(this)) , m_ddsHandler(ddsHandler) , m_logger(CentralEffectSimulation::getLogger("Plugin.MineLayingArtillery.HandlerMineLayingArtillery")) { INFO_LOG(m_logger) << "HandlerMineLayingArtillery::HandlerMineLayingArtillery"; m_effectClusterTimer->setInterval(500); } HandlerMineLayingArtillery::~HandlerMineLayingArtillery() { INFO_LOG(m_logger) << "HandlerMineLayingArtillery::~HandlerMineLayingArtillery"; } bool HandlerMineLayingArtillery::init() { INFO_LOG(m_logger) << "HandlerMineLayingArtillery::init"; m_dtcAes = m_ddsHandler->dtcAes(); m_ftcAes = m_ddsHandler->ftcAes(); m_dtcPdm = m_ddsHandler->dtcPdm(); m_dtcPdm->enableChargeType(); m_dtcAes->enableChargeEffect(); connect(m_dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::chargeEffectChanged, this, &HandlerMineLayingArtillery::onChargeEffectChanged); connect(m_dtcPdm.get(), &ProjectDataManagement::Interface::Dtc::chargeTypeChanged, this, &HandlerMineLayingArtillery::onChargeTypeChanged); connect(m_effectClusterTimer, &QTimer::timeout, this, &HandlerMineLayingArtillery::onEffectClusterTimerTimeout); const QMap &chargeTypes = m_dtcPdm->chargeTypes(); for (const auto &chargeType : chargeTypes) onChargeTypeChanged(chargeType, DataModification::eAdded); return true; } double HandlerMineLayingArtillery::calculateAngle(const AreaEffectSimulation::TrajectoryTimeAware &trajectory) { Core::GeoPositionRelativeTime trajectoryGeometryFirst = trajectory.geometry().front(); Core::GeoPositionRelativeTime trajectoryGeometryLast = trajectory.geometry().back(); const LtsCoordTrans::UtmCoordinate &trajectoryGeometryFirstUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(QPointF(trajectoryGeometryFirst.lon(), trajectoryGeometryFirst.lat())); const LtsCoordTrans::UtmCoordinate &trajectoryGeometryLastUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(QPointF(trajectoryGeometryLast.lon(), trajectoryGeometryLast.lat()), trajectoryGeometryFirstUtm.zone); QLineF lineFromStartToImpact = QLineF(QPointF(trajectoryGeometryFirstUtm.x, trajectoryGeometryFirstUtm.y), QPointF(trajectoryGeometryLastUtm.x, trajectoryGeometryLastUtm.y)); return lineFromStartToImpact.angle(); } std::vector HandlerMineLayingArtillery::calculateImpacts(const QJsonObject &mineLayingPattern, const Core::GeoPosition2D &impactPositon, double angle, qint64 time, int patternCounter) { QJsonArray patternsArray = mineLayingPattern[QStringLiteral("pattern")].toArray(); if (patternsArray.isEmpty() || patternCounter < 0 || patternCounter >= patternsArray.size()) return std::vector(); const double appearanceTimeOffset = mineLayingPattern[QStringLiteral("appearanceTimeOffset")].toDouble(); const double appearanceJitter = mineLayingPattern[QStringLiteral("appearanceJitter")].toDouble(); const double armingTimeOffset = mineLayingPattern[QStringLiteral("armingTimeOffset")].toDouble(); const double armingJitter = mineLayingPattern[QStringLiteral("armingJitter")].toDouble(); DEBUG_LOG(m_logger) << "[calculateImpacts] ## \n\tappearanceTimeOffset: " << appearanceTimeOffset << "\n\tappearanceJitter: " << appearanceJitter << "\n\tarmingTimeOffset: " << armingTimeOffset<<"\n\ttime: "< transformedImpacts; transformedImpacts.reserve(positionsArray.size()); for (const QJsonValue &posValue : positionsArray) { AreaEffectSimulation::MineData mineData; QJsonObject posObj = posValue.toObject(); double easting = posObj[QStringLiteral("easting")].toDouble(); double northing = posObj[QStringLiteral("northing")].toDouble(); const double newAngle = static_cast(angle + 90.0) % 360; QTransform t; t.rotate(-(newAngle)); QPointF positionRot = t.map(QPointF(easting, northing)); QPointF transformedImpactPosition = impactPostionUtm.geographic(positionRot.x(), positionRot.y()); const double appearenceTime = time + appearanceTimeOffset + QRandomGenerator::global()->bounded(0, appearanceJitter * 2) - appearanceJitter; const double armingTime = time + armingTimeOffset + QRandomGenerator::global()->bounded(0, armingJitter * 2) - armingJitter; DEBUG_LOG(m_logger) <<"[calculateImpacts] patternCount: "<isMineLaying(data.id(), 0, 0)) return; QJsonObject mineLayingPattern = m_dtcPdm->mineLayingPattern(data.id(), 0, 0); m_mineLayingChargeTypes.insert(data.id(), mineLayingPattern); return; } case DataModification::eRemoved: m_mineLayingChargeTypes.remove(data.id()); return; } } void HandlerMineLayingArtillery::onChargeEffectModifyReply(const Core::FunctionReplyObject &replyObject) { if (replyObject.error()) ERROR_LOG(m_logger)<<"[CES ChargeEffectModifyReply]: "<effectCluster(chargeEffect.originatorId()); DEBUG_LOG(m_logger)<<" [onChargeEffectChanged] effectClusterId: "<trajectoryTimeAwares()) { if (trajectory.originatorId() != chargeEffectId && trajectory.originatorTypeId().underlying() != ProjectDataManagement::StateChangeOriginator::ChargeEffect) continue; if (!m_impactCounters.contains(chargeEffect.originatorId())) m_impactCounters[chargeEffect.originatorId()] = 0; const int impactCounter = m_impactCounters.value(chargeEffect.originatorId()); const double angle = calculateAngle(trajectory); const qint64 timeFromLastTrajectoryPointAbsolute = chargeEffect.effectStart() + trajectory.geometry().back().relativeTime(); const int patternCount = mineLayingProperties.value(QStringLiteral("pattern")).toArray().size(); const int patternCounter = patternCounterByImpactCount(ec, patternCount, impactCounter); const int effectorIndex = effectorIndexByImpactCount(ec, impactCounter); const Core::GeoPosition mhpPositionGeo = ec.effectors().at(effectorIndex).mhpPosition(); const Core::GeoPosition2D mhpPositionGeo2D(mhpPositionGeo.lon(), mhpPositionGeo.lat()); const std::vector mineImpacts = calculateImpacts(mineLayingProperties, mhpPositionGeo2D, angle, timeFromLastTrajectoryPointAbsolute, patternCounter); AreaEffectSimulation::MineAreaReferenceModifyRequest request; request.chargeTypeId(mineChargeTypeId); request.referenceAesItemId(chargeEffect.originatorId()); request.mineData(mineImpacts); QJsonObject chargeEffectPropsObj = QJsonDocument::fromJson(QByteArray::fromStdString(chargeEffect.properties())).object(); request.effectDurationMsec(chargeEffectPropsObj[QStringLiteral("effectDurationMsec")].toInt(3 * 60 * 60 * 1000)); const int ret = m_ftcAes->mineAreaReferenceModifyRequest(request); DEBUG_LOG(m_logger)<<"[onChargeEffectChanged] ****** mineAreaReferenceModifyRequest \n\tret id: "<< ret << "\n\tchargeTypeId: " << chargeTypeId << "\n\tchargeEffectId: " << chargeEffectId << "\n\ttrajectoryId: " << trajectory.id() << "\n\tangle: " << angle << "\n\ttimeFromLastTrajectoryPointAbsolute: " << timeFromLastTrajectoryPointAbsolute; AreaEffectSimulation::ChargeEffectModifyRequest chargeEffectRequest; chargeEffectRequest.chargeTypeId(chargeEffect.chargeTypeId()); chargeEffectRequest.effectDuration(chargeEffect.effectDuration()); chargeEffectRequest.effectStart(chargeEffect.effectStart()); chargeEffectRequest.gearTypeId(chargeEffect.gearTypeId()); chargeEffectRequest.id(chargeEffect.id()); chargeEffectRequest.igniterTypeId(chargeEffect.igniterTypeId()); chargeEffectRequest.originatorId(chargeEffect.originatorId()); chargeEffectRequest.originatorTypeId(chargeEffect.originatorTypeId()); chargeEffectRequest.position(chargeEffect.position()); chargeEffectPropsObj.insert(QStringLiteral("used"), QJsonValue::fromVariant(true)); chargeEffectRequest.properties(QJsonDocument(chargeEffectPropsObj).toJson().toStdString()); m_ftcAes->chargeEffectModifyRequest(chargeEffectRequest); ++m_impactCounters[chargeEffect.originatorId()]; break; } if (m_effectClusterQue.isEmpty()) m_effectClusterTimer->stop(); } int HandlerMineLayingArtillery::patternCounterByImpactCount(const AreaEffectSimulation::EffectCluster &effectCluster, int patternCount, int impactCounter) const { return impactCounter / effectCluster.effectors().size() % patternCount; } int HandlerMineLayingArtillery::effectorIndexByImpactCount(const AreaEffectSimulation::EffectCluster &effectCluster, int impactCounter) const { return impactCounter % effectCluster.effectors().size(); } void HandlerMineLayingArtillery::onChargeEffectChanged(const AreaEffectSimulation::ChargeEffect &data, DataModification modType) { Q_UNUSED(modType); if (data.state() != AreaEffectSimulation::AesEffectState::EsActivated) return; if (data.originatorTypeId() != ProjectDataManagement::StateChangeOriginator::AreaEffectSimulation) return; // since there are some mines missing when adding them too fast, they are queued m_effectClusterQue.enqueue(data); if (!m_effectClusterTimer->isActive()) m_effectClusterTimer->start(); } } // namespace MineLayingArtillery