461 lines
17 KiB
C++
Executable File
461 lines
17 KiB
C++
Executable File
/**
|
|
* Handler for the HMI language
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
*/
|
|
|
|
#include "object_explosion_area.h"
|
|
#include "special_area_provider.h"
|
|
|
|
#include "Utils/polygons.h"
|
|
#include <LtsUtilsCore/dds_bitarray.h>
|
|
#include "handler_charge_administration.h"
|
|
#include <QPolygonF>
|
|
#include <QTimer>
|
|
#include <LtsUtilsCore/dds_bitarray.h>
|
|
|
|
namespace AreaEffectSimulation
|
|
{
|
|
|
|
ObjectExplosionArea::ObjectExplosionArea(MainHandler *mainHandler,
|
|
QObject *parent)
|
|
: ObjectBaseTmpl(mainHandler, QStringLiteral("ExplosionArea"), parent)
|
|
, m_singleExplosionTriggerTimer(new QTimer(this))
|
|
, m_lastSentTriggeredExplosionNumber(-1)
|
|
, m_chargeCountUntilLastStart(0)
|
|
{
|
|
connect(m_singleExplosionTriggerTimer, &QTimer::timeout,
|
|
this, &ObjectExplosionArea::checkNextSingleExplosion);
|
|
m_bSupportsPausing = true;
|
|
}
|
|
|
|
ObjectExplosionArea::~ObjectExplosionArea()
|
|
{
|
|
removeTopics();
|
|
}
|
|
|
|
Core::FunctionReplyObject ObjectExplosionArea::createObject(const Database::AesExplosionArea &data)
|
|
{
|
|
Core::FunctionReplyObject ret;
|
|
// No need to create object, if there is not enough charge left.
|
|
if (data.id == 0) // new, not read from db
|
|
ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, data.cannonCount * data.cannonRounds);
|
|
|
|
if (ret.error())
|
|
return ret;
|
|
|
|
ret = ObjectBaseTmpl::createObject(data);
|
|
|
|
if (ret.error())
|
|
{
|
|
if (data.id == 0)
|
|
m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, (-1) * data.cannonCount * data.cannonRounds);
|
|
return ret;
|
|
}
|
|
|
|
if (data.id <= 0) // new, not read from db
|
|
{
|
|
m_data.originalEffectDuration = data.effectDuration;
|
|
m_data.originalEffectStart = data.effectStart;
|
|
}
|
|
else
|
|
checkNextSingleExplosion();
|
|
return ret;
|
|
}
|
|
|
|
Core::FunctionReplyObject ObjectExplosionArea::updateObject(const Database::AesExplosionArea &data)
|
|
{
|
|
Core::FunctionReplyObject ret = ObjectBaseTmpl::updateObject(data);
|
|
if (ret.error() || (isOnlyObjectNameModifiable() && (data.id > 0))) // data.id > 0 -> negative values from oc always get a full update
|
|
return ret;
|
|
|
|
// if (data.id == 0) // should not happen, maybe an assert here ?
|
|
// return ret;
|
|
|
|
// only allow some fields
|
|
auto tmpData(m_data);
|
|
tmpData.id = std::abs(data.id);
|
|
tmpData.name = data.name;
|
|
tmpData.effectStart = data.effectStart;
|
|
tmpData.effectDuration = data.effectDuration;
|
|
tmpData.impactPosition = data.impactPosition;
|
|
tmpData.participantId = data.participantId;
|
|
tmpData.partyId = data.partyId;
|
|
tmpData.chargeTypeId = data.chargeTypeId;
|
|
tmpData.fireRequestTime = data.fireRequestTime;
|
|
tmpData.altitude = data.altitude;
|
|
tmpData.firePurpose = data.firePurpose;
|
|
tmpData.effectMarkingInTa = data.effectMarkingInTa;
|
|
tmpData.rotationAngle = data.rotationAngle;
|
|
tmpData.impactWidth = data.impactWidth;
|
|
tmpData.impactLength = data.impactLength;
|
|
tmpData.effectStateId = data.effectStateId; // overwrite for oc
|
|
tmpData.properties = data.properties;
|
|
|
|
if (!isOnlyObjectNameModifiable()) // check again, this time also if oc
|
|
{
|
|
tmpData.originalEffectDuration = data.effectDuration;
|
|
tmpData.originalEffectStart = data.effectStart;
|
|
tmpData.cannonCount = data.cannonCount;
|
|
tmpData.cannonRounds = data.cannonRounds;
|
|
tmpData.properties = data.properties;
|
|
}
|
|
|
|
if (!m_mainHandler->db()->update(tmpData))
|
|
{
|
|
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error updating explosion area with id %1 in database.")).arg(m_data.id);
|
|
}
|
|
|
|
// this is for party change or charge type change
|
|
if (data.partyId != m_data.partyId ||
|
|
data.chargeTypeId != m_data.chargeTypeId)
|
|
{
|
|
ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(m_data.chargeTypeId, m_data.partyId, (-1) * m_data.cannonRounds * m_data.cannonCount);
|
|
if (ret.error())
|
|
return ret;
|
|
ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, data.cannonRounds * data.cannonCount);
|
|
if (ret.error())
|
|
return ret;
|
|
}
|
|
else
|
|
// this is the case when cannonCount or cannonRounds changed
|
|
{
|
|
int diff = data.cannonCount * data.cannonRounds - m_data.cannonRounds * m_data.cannonCount;
|
|
if (diff != 0)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Claiming planned charge " << diff;
|
|
ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, diff);
|
|
if (ret.error())
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
m_data = std::move(tmpData);
|
|
//m_lastSentTriggeredExplosionNumber = -1;
|
|
|
|
m_publishDataTimer->start();
|
|
checkEnterNewState();
|
|
|
|
return Core::FunctionReplyObject(data.id); // data not m_data for the possible negative id in the oc-laptop
|
|
}
|
|
|
|
Core::FunctionReplyObject ObjectExplosionArea::removeObject()
|
|
{
|
|
Core::FunctionReplyObject ret = ObjectBaseTmpl::removeObject();
|
|
if (ret.error())
|
|
return ret;
|
|
|
|
int chargeLeft = m_data.cannonCount * m_data.cannonRounds;
|
|
|
|
DEBUG_LOG(m_logger) << "Impact count: " << chargeLeft;
|
|
DEBUG_LOG(m_logger) << "Last triggered explosion number: " << m_lastSentTriggeredExplosionNumber;
|
|
|
|
if (isActivationTimeReached())
|
|
chargeLeft -= m_lastSentTriggeredExplosionNumber;
|
|
|
|
if (chargeLeft < 0)
|
|
chargeLeft = 0;
|
|
|
|
DEBUG_LOG(m_logger) << "Charge left: " << chargeLeft;
|
|
|
|
// restore original stuff
|
|
m_data.effectDuration = m_data.originalEffectDuration;
|
|
m_data.effectStart = m_data.originalEffectStart;
|
|
|
|
// Negative, as we free the charge
|
|
ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(m_data.chargeTypeId, m_data.partyId, 0 - chargeLeft);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool ObjectExplosionArea::removeObjectFromDb()
|
|
{
|
|
return m_mainHandler->db()->removeAesExplosionArea(m_data.id);
|
|
}
|
|
|
|
Core::FunctionReplyObject ObjectExplosionArea::resumePause(const ExplosionAreaPauseResumeRequest &req)
|
|
{
|
|
if (req.resumeExecution())
|
|
{
|
|
if (req.timeToResume().is_set())
|
|
m_data.effectStart = QDateTime::fromMSecsSinceEpoch(req.timeToResume().get());
|
|
else
|
|
m_data.effectStart = partitionTime();
|
|
|
|
if (req.remainingDuration().is_set())
|
|
m_data.effectDuration = req.remainingDuration().get();
|
|
|
|
// req.resumeSeed() currently not used, as it is calculated internaly. maybe in the future
|
|
|
|
if (!trySwitchToActivated())
|
|
return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Explosion Area is currently not paused."));
|
|
}
|
|
else
|
|
if (!trySwitchToPaused())
|
|
return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Switching explosion area to pause is not possible in the current state."));
|
|
|
|
//m_lastSentTriggeredExplosionNumber = -1;
|
|
return Core::FunctionReplyObject(m_data.id);
|
|
}
|
|
|
|
void ObjectExplosionArea::setEffectState(AesEffectState newState)
|
|
{
|
|
if (newState == AesEffectState::EsPaused ||
|
|
newState == AesEffectState::EsExpired ||
|
|
newState == AesEffectState::EsRemoved ||
|
|
newState == AesEffectState::EsUnknown)
|
|
stopSingleExplosionHandling(newState);
|
|
|
|
ObjectBaseTmpl::setEffectState(newState);
|
|
|
|
if (newState == AesEffectState::EsActivated)
|
|
startSingleExplosionHandling();
|
|
}
|
|
|
|
void ObjectExplosionArea::onPublishData()
|
|
{
|
|
ObjectBaseTmpl::onPublishData();
|
|
|
|
if (!m_mainHandler->db()->update(m_data))
|
|
ERROR_LOG(m_logger) << "Error updating AesIndirectFireSingle-data in database";
|
|
|
|
ExplosionArea ddsData(m_data.id,
|
|
m_data.name.toStdString(),
|
|
effectState() == AesEffectState::EsRemoved,
|
|
false,
|
|
partitionTimeMSec(),
|
|
{m_data.chargeTypeId,0,0},
|
|
m_data.effectDuration,
|
|
toOptional(toMSec(m_data.effectStart)),
|
|
m_data.effectStateId,
|
|
m_data.effectSourceId,
|
|
toOptional(m_data.partyId),
|
|
toOptional(m_data.participantId),
|
|
m_data.effectMarkingInTa,
|
|
m_data.externalId,
|
|
m_data.isCentral,
|
|
m_data.properties.toStdString(),
|
|
Area(Core::GeoPosition2D(m_data.impactPosition.x(),
|
|
m_data.impactPosition.y()),
|
|
m_data.impactWidth,
|
|
m_data.impactLength,
|
|
m_data.rotationAngle),
|
|
m_data.cannonCount,
|
|
m_data.cannonRounds,
|
|
toMSec(m_data.originalEffectStart),
|
|
m_data.originalEffectDuration,
|
|
currentExplosionNumber(),
|
|
toMSec(m_data.fireRequestTime),
|
|
m_data.altitude,
|
|
m_data.firePurpose.toStdString());
|
|
m_mainHandler->dds()->dts()->publish(ddsData);
|
|
publishAiv();
|
|
}
|
|
|
|
void ObjectExplosionArea::publishAiv()
|
|
{
|
|
// Todo: calculate unique id.
|
|
// Better than aiv.id(1000 * aesItemId + AesType::AtExplosionArea);
|
|
|
|
const QPolygonF poly = Polygons::explosionArea(m_data);
|
|
const QRectF bounds(poly.boundingRect());
|
|
|
|
AesItemView aiv(m_aiv);
|
|
aiv.aesItemId(m_data.id);
|
|
aiv.chargeStruct().chargeTypeId(m_data.chargeTypeId);
|
|
aiv.effectStart(toMSec(m_data.effectStart));
|
|
ddsVecFromPoly(poly, aiv.form());
|
|
aiv.boundingBoxBottomLeft(Core::GeoPosition2D(bounds.topLeft().x(), bounds.topLeft().y())); // QRectF's bigger values are at bottom
|
|
aiv.boundingBoxTopRight(Core::GeoPosition2D(bounds.bottomRight().x(), bounds.bottomRight().y()));
|
|
aiv.aesTypeId(AesType::AtExplosionArea);
|
|
aiv.effectStateId(effectState());
|
|
aiv.id(1000 * m_data.id + AesType::AtExplosionArea);
|
|
if (m_data.partyId != 0)
|
|
aiv.partyId(m_data.partyId);
|
|
|
|
checkIsCentralAndPublish(aiv.form(), m_aiv.form(), m_data.isCentral);
|
|
|
|
const QVector<QPointF> singleExplosions = Polygons::explosionAreaSingleExplosions(data());
|
|
|
|
AesItemView aiv2(m_aiv2);
|
|
aiv2.aesItemId(m_data.id);
|
|
aiv2.chargeStruct().chargeTypeId(m_data.chargeTypeId);
|
|
aiv2.effectStart(toMSec(m_data.effectStart));
|
|
ddsVecFromPoly(singleExplosions, aiv2.form());
|
|
aiv2.boundingBoxBottomLeft(aiv.boundingBoxBottomLeft());
|
|
aiv2.boundingBoxTopRight(aiv.boundingBoxTopRight());
|
|
aiv2.aesTypeId(AesType::AtExplosionAreaSingleExplosions);
|
|
aiv2.id(1000 * m_data.id + AesType::AtExplosionAreaSingleExplosions);
|
|
aiv2.effectStateId(effectState());
|
|
if (m_data.partyId != 0)
|
|
aiv2.partyId(m_data.partyId);
|
|
|
|
if (aiv != m_aiv)
|
|
{
|
|
aiv.sampleTime(partitionTimeMSec());
|
|
m_aiv = aiv;
|
|
m_mainHandler->dds()->dts()->publish(aiv);
|
|
emit newAiv(aiv);
|
|
}
|
|
|
|
if (aiv2 != m_aiv2)
|
|
{
|
|
aiv2.sampleTime(partitionTimeMSec());
|
|
m_aiv2 = aiv2;
|
|
m_mainHandler->dds()->dts()->publish(aiv2);
|
|
emit newAiv(aiv2);
|
|
}
|
|
}
|
|
|
|
void ObjectExplosionArea::removeTopics()
|
|
{
|
|
auto dts = m_mainHandler->dds()->dts();
|
|
dts->removeExplosionAreaByKey(m_data.id);
|
|
dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtExplosionArea);
|
|
dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtExplosionAreaSingleExplosions);
|
|
emit removeAiv(m_data.id);
|
|
}
|
|
|
|
|
|
void ObjectExplosionArea::startSingleExplosionHandling()
|
|
{
|
|
DEBUG_LOG(m_logger) << "Starting area explosion handling";
|
|
if (!m_data.effectStart.isValid()) // 0 -> paused
|
|
{
|
|
// trim to second (really? yes since we can only enter seconds)
|
|
m_data.effectStart = partitionTime();
|
|
m_publishDataTimer->start();
|
|
}
|
|
m_singleExplosionTriggerTimer->setInterval(impactDeltaMSec());
|
|
m_singleExplosionTriggerTimer->start();
|
|
checkNextSingleExplosion(); // for second 0
|
|
}
|
|
|
|
void ObjectExplosionArea::stopSingleExplosionHandling(AesEffectState newState)
|
|
{
|
|
m_singleExplosionTriggerTimer->stop();
|
|
|
|
if (m_data.effectStateId != AesEffectState_def::EsActivated)
|
|
return;
|
|
|
|
// int chargeCountUntilLastStart = lrint((m_data.originalEffectDuration - m_data.effectDuration) / impactDeltaMSec());
|
|
int chargeCountUntilLastStart = m_chargeCountUntilLastStart;
|
|
qint64 msecsSinceLastStart = m_data.effectStart.msecsTo(partitionTime());
|
|
int impactCount = m_data.cannonCount * m_data.cannonRounds;
|
|
int curExplosionNumber = m_lastSentTriggeredExplosionNumber; // keep before modification !? Here -1 because we want to consume the proper amount of charge!
|
|
if (curExplosionNumber > impactCount)
|
|
curExplosionNumber = impactCount;
|
|
|
|
DEBUG_LOG(m_logger) << "impactCount: " << impactCount;
|
|
DEBUG_LOG(m_logger) << "chargeCountUntilLastStart: " << chargeCountUntilLastStart;
|
|
DEBUG_LOG(m_logger) << "curExplosionNumber: " << curExplosionNumber;
|
|
|
|
m_data.effectDuration -= msecsSinceLastStart;
|
|
DEBUG_LOG(m_logger) << "stopSingleExplosionHandling: " << m_data.effectDuration;
|
|
|
|
Core::FunctionReplyObject ret = m_mainHandler->chargeAdministrationHandler()->consumeCharge(m_data.chargeTypeId,
|
|
m_data.partyId,
|
|
curExplosionNumber - chargeCountUntilLastStart);
|
|
DEBUG_LOG(m_logger) << "Charge marked used in last lap: " << curExplosionNumber - chargeCountUntilLastStart;
|
|
if (ret.error())
|
|
ERROR_LOG(m_logger) << "Could not consume Charge! " << ret();
|
|
|
|
m_chargeCountUntilLastStart = curExplosionNumber;
|
|
|
|
if (m_data.effectDuration < 0 || newState == AesEffectState::EsExpired)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Expired!";
|
|
m_data.effectDuration = m_data.originalEffectDuration;
|
|
m_data.effectStart = m_data.originalEffectStart;
|
|
|
|
}
|
|
else // Paused
|
|
m_data.effectStart = QDateTime(); // Invalid QDateTime to mark it paused
|
|
|
|
m_publishDataTimer->start();
|
|
}
|
|
|
|
// called every second
|
|
void ObjectExplosionArea::checkNextSingleExplosion()
|
|
{
|
|
const int curExplosionNumber = currentExplosionNumber();
|
|
const int impactCount = m_data.cannonCount * m_data.cannonRounds;
|
|
if (curExplosionNumber > impactCount)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Remaining effect duration is zero - no more single explosions";
|
|
m_singleExplosionTriggerTimer->stop();
|
|
m_data.effectDuration = m_data.originalEffectDuration;
|
|
m_data.effectStart = m_data.originalEffectStart;
|
|
m_publishDataTimer->start();
|
|
}
|
|
else
|
|
{
|
|
// check if a new explosion is triggered
|
|
if (curExplosionNumber == m_lastSentTriggeredExplosionNumber)
|
|
return; // nothing changed -> do not publish anything
|
|
m_lastSentTriggeredExplosionNumber = curExplosionNumber;
|
|
|
|
DEBUG_LOG(m_logger) << "checkNextSingleExplosion: curExplosionNumber = " << curExplosionNumber;
|
|
DEBUG_LOG(m_logger) << "checkNextSingleExplosion: m_lastSentTriggeredExplosionNumber = " << m_lastSentTriggeredExplosionNumber;
|
|
|
|
// publish new triggered explosions data here
|
|
TriggeredImpacts ti;
|
|
ti.id(1000 * m_data.id + AesType::AtExplosionAreaSingleExplosions);
|
|
ti.aesItemId(m_data.id);
|
|
ti.aesTypeId(AesType::AtExplosionAreaSingleExplosions);
|
|
ti.sampleTime(partitionTimeMSec());
|
|
|
|
ti.impactCount(impactCount);
|
|
ti.bitfield().resize((impactCount + 7) / 8);
|
|
// not very fast but ok for now
|
|
for(int i = 0; i < impactCount; ++i)
|
|
{
|
|
// Triggered Impact == 0, if impact has taken place
|
|
LtsUtils::BitArray::setBit(ti.bitfield(), i, i >= curExplosionNumber); // not "<" -> explosion NUMBER
|
|
}
|
|
|
|
DEBUG_LOG(m_logger) << "Publishing TriggeredExplosions with curExplosionNumber " << curExplosionNumber;
|
|
m_mainHandler->dds()->dts()->publish(ti);
|
|
}
|
|
}
|
|
|
|
qint64 ObjectExplosionArea::impactDeltaMSec() const
|
|
{
|
|
const qint64 impactCount = m_data.cannonCount * m_data.cannonRounds; // double for calculation below
|
|
if (impactCount <= 0)
|
|
return 24 * 60 * 60 * 1000; // 1day - more or less invalid
|
|
if (m_data.originalEffectDuration > 0)
|
|
return m_data.originalEffectDuration / impactCount; // msecs between impacts
|
|
else
|
|
return 500; // In case of invalid (0) effect duration time, return 500 msecs.
|
|
}
|
|
|
|
int ObjectExplosionArea::currentExplosionNumber() const
|
|
{
|
|
const qint64 delta_t = impactDeltaMSec();
|
|
const double exhausedDuration = m_data.originalEffectDuration - m_data.effectDuration; // exhaused till last start
|
|
const bool haveEffectStart = m_data.effectStart.isValid();
|
|
const double curDuration = haveEffectStart ? partitionTimeMSec() - m_data.effectStart.toMSecsSinceEpoch() : 0; // exhaused between last start and now
|
|
const double completedEffectDuration = exhausedDuration + curDuration;
|
|
|
|
const int ret = lrint(completedEffectDuration / delta_t);
|
|
DEBUG_LOG(m_logger) << "Current explosion number is " << ret
|
|
<< ", ic: " << m_data.cannonCount * m_data.cannonRounds
|
|
<< ", dt: " << delta_t
|
|
<< ", cd: " << QString::number(completedEffectDuration,'f', 2)
|
|
<< ", exd: " << QString::number(exhausedDuration,'f', 2)
|
|
<< ", curD: " << QString::number(curDuration,'f', 2)
|
|
<< ", ed: " << m_data.effectDuration
|
|
;
|
|
return ret + 1; // start at 1 for proper bit setting
|
|
}
|
|
|
|
QPolygonF ObjectExplosionArea::generateForm()
|
|
{
|
|
return Polygons::explosionArea(m_data);
|
|
}
|
|
|
|
} // Namespace AreaEffectSimulation
|
|
|