396 lines
14 KiB
C++
Executable File
396 lines
14 KiB
C++
Executable File
/**
|
|
* Base class for all aes objects
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <QObject>
|
|
#include <QPolygonF>
|
|
#include <QTimer>
|
|
|
|
#include "aes_enums.hpp"
|
|
#include "main_handler.h"
|
|
#include "special_area_provider.h"
|
|
|
|
#include "logger.h"
|
|
#include <Core/function_reply_object.h>
|
|
#include <ProjectDataManagement/pdm_dtc.h>
|
|
#include <Trajectory/trajectory_generator.h>
|
|
|
|
namespace AreaEffectSimulation
|
|
{
|
|
|
|
// Keep it here as we need the main handler
|
|
inline TrajectoryGenerator *getTrajectoryInstance(MainHandler *mainHandler)
|
|
{
|
|
int exerciseId = mainHandler->dds()->exerciseId();
|
|
return TrajectoryGenerator::getInstance(exerciseId, mainHandler->dds()->exmDtc(), mainHandler->dds()->asmDtc());
|
|
}
|
|
|
|
template <typename T> void dbSegment2DdsSegment(const T &dbSegments, std::vector<Core::GeoPosition2D> &ddsSegments)
|
|
{
|
|
ddsSegments.clear();
|
|
ddsSegments.reserve(dbSegments.size());
|
|
for (const auto &segment : dbSegments)
|
|
ddsSegments.emplace_back(segment.position.x(), segment.position.y());
|
|
}
|
|
|
|
class ObjectBase : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
ObjectBase(MainHandler *mainHandler, const QString &typeName, QObject *parent);
|
|
~ObjectBase();
|
|
|
|
virtual AesEffectState effectState() const = 0;
|
|
virtual QDateTime activationTime() const = 0;
|
|
virtual QDateTime endTime() const = 0;
|
|
virtual int id() const = 0;
|
|
virtual const QString &objectName() const = 0;
|
|
virtual void setUseEffectStateReceived(bool useReceived) = 0;
|
|
virtual qint32 flightDurationMsec() const { return 0; }
|
|
|
|
// this function tries to advance to the next state if possible.
|
|
// the two special cases (user input) 'Delete' and 'Pause' must
|
|
// be handled on its own (by calling trySwitchToPause/Delete() directly)
|
|
AesEffectState checkEnterNewState();
|
|
|
|
// Possible transitions
|
|
|
|
// Unknown -> Received
|
|
bool trySwitchToReceived();
|
|
|
|
// Prepared, Received, Unknown -> Created
|
|
bool trySwitchToCreated();
|
|
|
|
// Created, Received -> Prepared
|
|
bool trySwitchToPrepared();
|
|
|
|
// Prepared -> Fired (only Artillery)
|
|
bool trySwitchToFired();
|
|
|
|
// Prepared, Fired, Paused -> Activated
|
|
bool trySwitchToActivated();
|
|
|
|
// Activated -> Paused
|
|
bool trySwitchToPaused();
|
|
|
|
// Activated -> Done
|
|
bool trySwitchToExpired();
|
|
|
|
// -> Deleted
|
|
bool trySwitchToRemoved();
|
|
|
|
// state machine conditions
|
|
bool isActivationTimeSet() const { return activationTime().isValid(); }
|
|
virtual bool isActivationTimeReached() const { return isActivationTimeSet() && partitionTime() >= activationTime(); }
|
|
virtual bool isFiringTimeReached() const { return false; }
|
|
bool isPausingSupported() const { return m_bSupportsPausing; }
|
|
bool isEndTimeSet() const { return endTime().isValid(); }
|
|
bool isEndTimeReached() const { return isEndTimeSet() && partitionTime() >= endTime(); }
|
|
virtual bool isUsingEffectStateReceived() const = 0;
|
|
|
|
// other conditions
|
|
virtual bool isObjectModifiable() const; // is at least one parameter in the object modifyable
|
|
virtual bool isOnlyObjectNameModifiable() const; // is only the name modifyable (e.g. in Active State)
|
|
bool isObjectInTrainingArea() const; // is available in TA (important due to limited count per object type)
|
|
|
|
virtual void setEndTimeToNow() = 0; // adjust the duration so that the end time is reached now
|
|
|
|
virtual QPolygonF generateForm() = 0;
|
|
virtual std::vector<Core::GeoPosition> form() const = 0;
|
|
virtual AesType aesType() const = 0;
|
|
virtual bool isCentral() const = 0;
|
|
|
|
template <typename T> static inline void ddsVecFromPoly(const T &in, std::vector<Core::GeoPosition> &out)
|
|
{
|
|
out.clear();
|
|
out.reserve(in.size());
|
|
for (const QPointF &pt : in)
|
|
out.emplace_back(Core::GeoPosition(pt.x(), pt.y(), 0.0));
|
|
}
|
|
|
|
protected Q_SLOTS:
|
|
virtual void onPublishData();
|
|
Q_SIGNALS:
|
|
void effectStateChanged();
|
|
void newAiv(const AreaEffectSimulation::AesItemView &item);
|
|
void removeAiv(int32_t aesItemId);
|
|
void changedToCentral();
|
|
|
|
protected:
|
|
virtual void setEffectState(AesEffectState newState);
|
|
virtual qint64 partitionTimeMSec() const; // only virtual for statemachine test
|
|
virtual QDateTime partitionTime() const; // only virtual for statemachine test
|
|
|
|
static qint64 toMSec(const QDateTime &dt);
|
|
template <typename T> dds::core::optional<T> toOptional(T inVal) { return inVal != T() ? dds::core::optional<T>(inVal) : dds::core::optional<T>(); }
|
|
|
|
dds::core::optional<qint64> toOptional(const QDateTime &dt)
|
|
{
|
|
qint64 t = (dt.isValid() && !dt.isNull()) ? dt.toMSecsSinceEpoch() : 0;
|
|
return toOptional(t);
|
|
}
|
|
|
|
template <typename T, typename U> static inline void ddsVecFromPoly(const T &in, const U &heights, std::vector<Core::GeoPosition> &out)
|
|
{
|
|
Q_ASSERT(in.size() == heights.size());
|
|
out.clear();
|
|
out.reserve(in.size());
|
|
for (int i = 0; i < in.size(); ++i)
|
|
out.emplace_back(Core::GeoPosition(in[i].x(), in[i].y(), heights[i]));
|
|
}
|
|
|
|
template <typename T> static inline QPolygonF polyFromDdsVec(const T &in)
|
|
{
|
|
QPolygonF out;
|
|
out.reserve(in.size());
|
|
for (const auto &pos : qAsConst(in))
|
|
out.push_back(QPointF(pos.lon(), pos.lat()));
|
|
|
|
return out;
|
|
}
|
|
template <typename T, typename U> static inline bool intersects(const T &vec, const U &specialArea)
|
|
{
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
|
return polyFromDdsVec(vec).intersected(polyFromDdsVec(specialArea)).size() > 0;
|
|
#else
|
|
return polyFromDdsVec(vec).intersects(polyFromDdsVec(specialArea));
|
|
#endif
|
|
}
|
|
|
|
protected:
|
|
QTimer *m_aesTriggerTimer; // for state changes (activation time, end time)
|
|
QTimer *m_publishDataTimer; // for publishing data when data changed
|
|
MainHandler *m_mainHandler;
|
|
bool m_bSupportsPausing; // can this entity be paused (e.g. artillery)
|
|
QString m_typeName;
|
|
LOGGER_T m_logger;
|
|
};
|
|
|
|
template <typename T> class ObjectBaseTmpl : public ObjectBase
|
|
{
|
|
public:
|
|
ObjectBaseTmpl(MainHandler *mainHandler, const QString &typeName, QObject *parent)
|
|
: ObjectBase(mainHandler, typeName, parent)
|
|
{
|
|
}
|
|
~ObjectBaseTmpl() = default;
|
|
|
|
AesEffectState effectState() const override { return m_data.effectStateId; }
|
|
QDateTime activationTime() const override { return m_data.effectStart; }
|
|
QDateTime endTime() const override { return m_data.effectDuration <= 0 ? QDateTime() : m_data.effectStart.addMSecs(m_data.effectDuration); }
|
|
int id() const override { return m_data.id; }
|
|
qint64 externalId() const { return m_data.externalId; }
|
|
void setUseEffectStateReceived(bool useReceived) override { m_data.isUsingEffectStateReceived = useReceived; }
|
|
bool isUsingEffectStateReceived() const override { return m_data.isUsingEffectStateReceived; }
|
|
|
|
const QString &objectName() const override { return m_data.name; }
|
|
|
|
virtual Core::FunctionReplyObject createObject(const T &data, bool ignoreActivationTimeCheck = false, bool setIsCentral = false)
|
|
{
|
|
bool originOc = false;
|
|
m_isAlwaysCentral = setIsCentral;
|
|
m_data = data;
|
|
// recalc effect state except it's already expired, removed, paused or received.
|
|
if (data.effectStateId != AesEffectState::EsRemoved && data.effectStateId != AesEffectState::EsExpired && data.effectStateId != AesEffectState::EsPaused)
|
|
m_data.effectStateId = AesEffectState::EsUnknown;
|
|
|
|
if (m_data.id <= 0)
|
|
{
|
|
// check effect start for new elements if not OC
|
|
if ((0 == m_data.id) && (isActivationTimeSet() && activationTime() < QDateTime::currentDateTimeUtc()) && !ignoreActivationTimeCheck)
|
|
{
|
|
DEBUG_LOG(m_logger) << "m_data.id : " << m_data.id << ", isActivationTimeSet :" << isActivationTimeSet()
|
|
<< ", activationTime :" << activationTime().toMSecsSinceEpoch() << ", ignoreActivationTimeCheck :" << ignoreActivationTimeCheck;
|
|
// << ", QDateTime::currentDateTimeUtc() :" << QDateTime::currentDateTimeUtc().msecsTo();
|
|
return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Effect start is in the past."));
|
|
}
|
|
#ifndef AES_STATEMACHINE_TEST
|
|
|
|
std::vector<Core::GeoPosition> form;
|
|
ddsVecFromPoly(generateForm(), form);
|
|
|
|
if ((aesType() == AesType::AtBoobyTrap) || (aesType() == AesType::AtOffRouteMine) || setIsCentral)
|
|
m_data.isCentral = true;
|
|
else
|
|
m_data.isCentral = checkIsCentral(form, {}, false);
|
|
|
|
if (m_data.id < 0) // is the id given as negative? make positive again -> used by OC-Laptop
|
|
{
|
|
m_data.id *= -1;
|
|
m_data.priv_entityId = m_data.id; // OC is using id from Tetra and these are unique
|
|
originOc = true;
|
|
}
|
|
|
|
if (!m_mainHandler->db()->insert(m_data))
|
|
return Core::FunctionReplyObject(m_data.id * -1, tr_lts("dds", "Error inserting effect simulation into database."));
|
|
#endif
|
|
}
|
|
// always publish on creation
|
|
m_publishDataTimer->start();
|
|
// start state machine
|
|
checkEnterNewState();
|
|
|
|
return Core::FunctionReplyObject((originOc) ? (m_data.id * -1) : (m_data.id));
|
|
}
|
|
|
|
virtual Core::FunctionReplyObject updateObject(const T &data)
|
|
{
|
|
if (m_data.id != std::abs(data.id))
|
|
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Internal error - id does not match. Is: %1, should be: %2."))
|
|
.arg(std::abs(data.id))
|
|
.arg(m_data.id); // data not m_data for the possible negative id in the oc-laptop
|
|
|
|
if (data.id > 0) // do not check if OC-Laptop
|
|
{
|
|
if (!isObjectModifiable())
|
|
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Failed to update effect simulation with id %1 as it is unmodifyable."))
|
|
.arg(data.id); // data not m_data for the possible negative id in the oc-laptop
|
|
|
|
if (isOnlyObjectNameModifiable())
|
|
{
|
|
// only rename allowed
|
|
m_data.name = data.name;
|
|
checkEnterNewState();
|
|
return Core::FunctionReplyObject(m_data.id);
|
|
}
|
|
if (isActivationTimeSet() && activationTime() < QDateTime::currentDateTimeUtc())
|
|
return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "New effect start for effect simulation is in the past."));
|
|
}
|
|
return Core::FunctionReplyObject(data.id); // data not m_data for the possible negative id in the oc-laptop
|
|
}
|
|
|
|
virtual Core::FunctionReplyObject updateName(const QString &name)
|
|
{
|
|
m_data.name = name;
|
|
checkEnterNewState();
|
|
return Core::FunctionReplyObject(m_data.id);
|
|
}
|
|
|
|
virtual Core::FunctionReplyObject removeObject()
|
|
{
|
|
|
|
if (!trySwitchToRemoved())
|
|
return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Can not remove effect simulation - wrong state (active or already expired)."));
|
|
|
|
return Core::FunctionReplyObject(m_data.id);
|
|
}
|
|
|
|
virtual Core::FunctionReplyObject setActivationOffset(qint64 msecs)
|
|
{
|
|
Core::FunctionReplyObject ret;
|
|
T data = m_data;
|
|
data.effectStart = msecs >= 0 ? QDateTime::currentDateTimeUtc().addMSecs(msecs) : QDateTime();
|
|
ret = updateObject(data);
|
|
if (ret.error() || isOnlyObjectNameModifiable())
|
|
return ret;
|
|
return Core::FunctionReplyObject(m_data.id);
|
|
}
|
|
|
|
virtual Core::FunctionReplyObject activateObjectNow()
|
|
{
|
|
const QDateTime &tTempStart = m_data.effectStart;
|
|
m_data.effectStart = QDateTime::currentDateTimeUtc();
|
|
if (!trySwitchToActivated())
|
|
{
|
|
m_data.effectStart = tTempStart;
|
|
return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Can not activate effect simulation with id %1 which is in the effect state with id %2."))
|
|
.arg(m_data.id)
|
|
.arg(m_data.effectStateId.underlying());
|
|
}
|
|
|
|
return Core::FunctionReplyObject();
|
|
}
|
|
|
|
const T &data() const { return m_data; }
|
|
|
|
void setEndTimeToNow() override
|
|
{
|
|
if (!m_data.effectStart.isValid())
|
|
m_data.effectStart = partitionTime().addMSecs(-100);
|
|
m_data.effectDuration = m_data.effectStart.msecsTo(partitionTime());
|
|
if (m_data.effectDuration < 0)
|
|
m_data.effectDuration = 1;
|
|
}
|
|
|
|
bool checkIsCentral(const std::vector<Core::GeoPosition> &_form) { return checkIsCentral(_form, form(), m_data.isCentral); }
|
|
|
|
protected:
|
|
// will be called from trySwitchFoo()
|
|
void setEffectState(AesEffectState newState) override
|
|
{
|
|
if (newState == m_data.effectStateId && newState != AesEffectState::EsUnknown)
|
|
return;
|
|
m_data.effectStateId = newState;
|
|
ObjectBase::setEffectState(newState);
|
|
}
|
|
|
|
void onPublishData() override
|
|
{
|
|
DEBUG_LOG(m_logger) << id() << " Publishing data for " << m_typeName << " with name '" << m_data.name << "'"
|
|
<< " isCentral=" << m_data.isCentral;
|
|
ObjectBase::onPublishData();
|
|
}
|
|
|
|
void checkIsCentralAndPublish(const std::vector<Core::GeoPosition> &form, const std::vector<Core::GeoPosition> &oldForm, bool wasCentral)
|
|
{
|
|
if (m_isAlwaysCentral)
|
|
return;
|
|
|
|
bool isCentral = checkIsCentral(form, oldForm, wasCentral);
|
|
if (isCentral != m_data.isCentral)
|
|
{
|
|
m_data.isCentral = isCentral;
|
|
onPublishData();
|
|
if (isOnlyObjectNameModifiable() && isCentral)
|
|
emit changedToCentral();
|
|
}
|
|
}
|
|
|
|
bool checkIsCentral(const std::vector<Core::GeoPosition> &form, const std::vector<Core::GeoPosition> &oldForm, bool wasCentral)
|
|
{
|
|
// Previously central areas stay central when state is at least activated:
|
|
if (wasCentral && (effectState() == AesEffectState::EsExpired || effectState() == AesEffectState::EsActivated || effectState() == AesEffectState::EsPaused))
|
|
return true;
|
|
|
|
if (static_cast<MineClass::type>(m_mainHandler->dds()->pdmDtc()->mineClass(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId)) == MineClass::VirtualSingle)
|
|
return true;
|
|
|
|
if (m_mainHandler->dds()->pdmDtc()->gearClassFromChargeTypeId(m_data.chargeTypeId) == ProjectDataManagement::GearClass::AirSupport)
|
|
return true;
|
|
|
|
// Laser-Guidable Weapons must always be calculated by CES
|
|
if (m_mainHandler->dds()->pdmDtc()->isLaserGuidable(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId))
|
|
return true;
|
|
|
|
// PzAbwVMi B always true
|
|
if (static_cast<MineClass::type>(m_mainHandler->dds()->pdmDtc()->mineClass(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId)) == MineClass::Concrete)
|
|
return true;
|
|
|
|
// If form didn't change, isCentral cannot change either:
|
|
if (form == oldForm)
|
|
return wasCentral;
|
|
|
|
// Now let's check wether it actually is in a CES area.
|
|
const QMap<int, InfrastructureManagement::SpecialArea> spAMap = m_mainHandler->specialAreaProvider()->getSpecialAreas();
|
|
for (const InfrastructureManagement::SpecialArea &sa : spAMap)
|
|
{
|
|
if (sa.specialType() != InfrastructureManagement::SpecialAreaType::SatCesArea)
|
|
continue;
|
|
if (intersects(form, sa.polygon()))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected:
|
|
bool m_isAlwaysCentral = false;
|
|
T m_data;
|
|
};
|
|
|
|
} // namespace AreaEffectSimulation
|