osb/source/game/StarDamageManager.cpp

265 lines
10 KiB
C++
Raw Normal View History

2023-06-20 04:33:09 +00:00
#include "StarDamageManager.hpp"
#include "StarDataStreamExtra.hpp"
#include "StarIterator.hpp"
#include "StarEntityMap.hpp"
#include "StarLogging.hpp"
#include "StarColor.hpp"
#include "StarWorld.hpp"
namespace Star {
ConnectionId RemoteHitRequest::destinationConnection() const {
return connectionForEntity(causingEntityId);
}
DataStream& operator<<(DataStream& ds, RemoteHitRequest const& hitRequest) {
ds << hitRequest.causingEntityId;
ds << hitRequest.targetEntityId;
ds << hitRequest.damageRequest;
return ds;
}
DataStream& operator>>(DataStream& ds, RemoteHitRequest& hitRequest) {
ds >> hitRequest.causingEntityId;
ds >> hitRequest.targetEntityId;
ds >> hitRequest.damageRequest;
return ds;
}
ConnectionId RemoteDamageRequest::destinationConnection() const {
return connectionForEntity(targetEntityId);
}
DataStream& operator<<(DataStream& ds, RemoteDamageRequest const& damageRequest) {
ds << damageRequest.causingEntityId;
ds << damageRequest.targetEntityId;
ds << damageRequest.damageRequest;
return ds;
}
DataStream& operator>>(DataStream& ds, RemoteDamageRequest& damageRequest) {
ds >> damageRequest.causingEntityId;
ds >> damageRequest.targetEntityId;
ds >> damageRequest.damageRequest;
return ds;
}
DataStream& operator<<(DataStream& ds, RemoteDamageNotification const& damageNotification) {
ds << damageNotification.sourceEntityId;
ds << damageNotification.damageNotification;
return ds;
}
DataStream& operator>>(DataStream& ds, RemoteDamageNotification& damageNotification) {
ds >> damageNotification.sourceEntityId;
ds >> damageNotification.damageNotification;
return ds;
}
DamageManager::DamageManager(World* world, ConnectionId connectionId) : m_world(world), m_connectionId(connectionId) {}
void DamageManager::update(float dt) {
2023-06-20 04:33:09 +00:00
float const DefaultDamageTimeout = 1.0f;
auto damageIt = makeSMutableMapIterator(m_recentEntityDamages);
while (damageIt.hasNext()) {
auto& events = damageIt.next().second;
auto eventIt = makeSMutableIterator(events);
while (eventIt.hasNext()) {
auto& event = eventIt.next();
event.timeout -= dt;
2023-06-20 04:33:09 +00:00
auto entityIdTimeoutGroup = event.timeoutGroup.maybe<EntityId>();
if (event.timeout <= 0.0f || (entityIdTimeoutGroup && !m_world->entity(*entityIdTimeoutGroup)))
eventIt.remove();
}
if (events.empty())
damageIt.remove();
}
m_world->forAllEntities([&](EntityPtr const& causingEntity) {
for (auto& damageSource : causingEntity->damageSources()) {
if (damageSource.trackSourceEntity)
damageSource.translate(causingEntity->position());
2023-06-27 14:50:47 +00:00
2023-06-20 04:33:09 +00:00
if (auto poly = damageSource.damageArea.ptr<PolyF>())
SpatialLogger::logPoly("world", *poly, Color::Orange.toRgba());
else if (auto line = damageSource.damageArea.ptr<Line2F>())
SpatialLogger::logLine("world", *line, Color::Orange.toRgba());
for (auto const& hitResultPair : queryHit(damageSource, causingEntity->entityId())) {
auto targetEntity = m_world->entity(hitResultPair.first);
if (!isAuthoritative(causingEntity, targetEntity))
continue;
auto& eventList = m_recentEntityDamages[hitResultPair.first];
// Guard against rapidly repeating damages by either the causing
// entity id, or optionally the repeat group if specified.
bool allowDamage = true;
for (auto const& event : eventList) {
if (damageSource.damageRepeatGroup) {
if (event.timeoutGroup == *damageSource.damageRepeatGroup)
allowDamage = false;
} else {
if (event.timeoutGroup == causingEntity->entityId())
allowDamage = false;
}
}
if (allowDamage) {
float timeout = damageSource.damageRepeatTimeout.value(DefaultDamageTimeout);
if (damageSource.damageRepeatGroup)
eventList.append({*damageSource.damageRepeatGroup, timeout});
else
eventList.append({causingEntity->entityId(), timeout});
auto damageRequest = DamageRequest(hitResultPair.second, damageSource.damageType, damageSource.damage,
damageSource.knockbackMomentum(m_world->geometry(), targetEntity->position()),
damageSource.sourceEntityId, damageSource.damageSourceKind, damageSource.statusEffects);
addHitRequest({causingEntity->entityId(), targetEntity->entityId(), damageRequest});
if (damageSource.damageType != NoDamage)
addDamageRequest({causingEntity->entityId(), targetEntity->entityId(), move(damageRequest)});
}
}
}
for (auto const& damageNotification : causingEntity->selfDamageNotifications())
addDamageNotification({causingEntity->entityId(), damageNotification});
});
}
void DamageManager::pushRemoteHitRequest(RemoteHitRequest const& remoteHitRequest) {
if (remoteHitRequest.destinationConnection() != m_connectionId)
throw StarException("RemoteDamageRequest routed to wrong DamageManager");
if (auto causingEntity = m_world->entity(remoteHitRequest.causingEntityId)) {
starAssert(causingEntity->isMaster());
causingEntity->hitOther(remoteHitRequest.targetEntityId, remoteHitRequest.damageRequest);
}
}
void DamageManager::pushRemoteDamageRequest(RemoteDamageRequest const& remoteDamageRequest) {
if (remoteDamageRequest.destinationConnection() != m_connectionId)
throw StarException("RemoteDamageRequest routed to wrong DamageManager");
if (auto targetEntity = m_world->entity(remoteDamageRequest.targetEntityId)) {
starAssert(targetEntity->isMaster());
for (auto& damageNotification : targetEntity->applyDamage(remoteDamageRequest.damageRequest))
addDamageNotification({remoteDamageRequest.damageRequest.sourceEntityId, move(damageNotification)});
}
}
void DamageManager::pushRemoteDamageNotification(RemoteDamageNotification remoteDamageNotification) {
if (auto sourceEntity = m_world->entity(remoteDamageNotification.sourceEntityId)) {
if (sourceEntity->isMaster()
&& sourceEntity->entityId() != remoteDamageNotification.damageNotification.targetEntityId)
sourceEntity->damagedOther(remoteDamageNotification.damageNotification);
}
m_pendingNotifications.append(move(remoteDamageNotification.damageNotification));
}
List<RemoteHitRequest> DamageManager::pullRemoteHitRequests() {
return take(m_pendingRemoteHitRequests);
}
List<RemoteDamageRequest> DamageManager::pullRemoteDamageRequests() {
return take(m_pendingRemoteDamageRequests);
}
List<RemoteDamageNotification> DamageManager::pullRemoteDamageNotifications() {
return take(m_pendingRemoteNotifications);
}
List<DamageNotification> DamageManager::pullPendingNotifications() {
return take(m_pendingNotifications);
}
SmallList<pair<EntityId, HitType>, 4> DamageManager::queryHit(DamageSource const& source, EntityId causingId) const {
SmallList<pair<EntityId, HitType>, 4> resultList;
auto doQueryHit = [&source, &resultList, causingId, this](EntityPtr const& targetEntity) {
if (targetEntity->entityId() == causingId)
return;
if (!source.team.canDamage(targetEntity->getTeam(), targetEntity->entityId() == source.sourceEntityId))
return;
if (source.rayCheck) {
if (auto poly = source.damageArea.ptr<PolyF>()) {
if (auto sourceEntity = m_world->entity(source.sourceEntityId)) {
auto overlap = m_world->geometry().rectOverlap(targetEntity->metaBoundBox().translated(targetEntity->position()), poly->boundBox());
if (!overlap.isEmpty() && m_world->lineTileCollision(overlap.center(), sourceEntity->position()))
return;
}
} else if (auto line = source.damageArea.ptr<Line2F>()) {
if (auto hitPoly = targetEntity->hitPoly()) {
if (auto intersection = m_world->geometry().lineIntersectsPolyAt(*line, *hitPoly)) {
if (m_world->lineTileCollision(line->min(), *intersection))
return;
}
}
}
}
if (auto hitResult = targetEntity->queryHit(source))
resultList.append({targetEntity->entityId(), *hitResult});
return;
};
if (auto poly = source.damageArea.ptr<PolyF>())
m_world->forEachEntity(poly->boundBox(), doQueryHit);
else if (auto line = source.damageArea.ptr<Line2F>())
m_world->forEachEntityLine(line->min(), line->max(), doQueryHit);
return resultList;
}
bool DamageManager::isAuthoritative(EntityPtr const& causingEntity, EntityPtr const& targetEntity) {
// Damage manager is authoritative if either one of the entities is
// masterOnly, OR the manager is server-side and both entities are
// server-side master entities, OR the damage manager is server-side and both
// entities are different clients, OR if the manager is client-side and the
// source is client-side master and the target is server-side master, OR if
// the manager is client-side and the target is client-side master.
//
// This means that PvE and EvP are both decided on the player doing the
// hitting or getting hit, and PvP is decided on the server, except for
// master-only entities whose interactions are always decided on the machine
// they are residing on.
auto causingClient = connectionForEntity(causingEntity->entityId());
auto targetClient = connectionForEntity(targetEntity->entityId());
if (causingEntity->masterOnly() || targetEntity->masterOnly())
return true;
else if (causingClient == ServerConnectionId && targetClient == ServerConnectionId)
return m_connectionId == ServerConnectionId;
else if (causingClient != ServerConnectionId && targetClient != ServerConnectionId && causingClient != targetClient)
return m_connectionId == ServerConnectionId;
else if (targetClient == ServerConnectionId)
return causingClient == m_connectionId;
else
return targetClient == m_connectionId;
}
void DamageManager::addHitRequest(RemoteHitRequest const& remoteHitRequest) {
if (remoteHitRequest.destinationConnection() == m_connectionId)
pushRemoteHitRequest(remoteHitRequest);
else
m_pendingRemoteHitRequests.append(remoteHitRequest);
}
void DamageManager::addDamageRequest(RemoteDamageRequest remoteDamageRequest) {
if (remoteDamageRequest.destinationConnection() == m_connectionId)
pushRemoteDamageRequest(move(remoteDamageRequest));
else
m_pendingRemoteDamageRequests.append(move(remoteDamageRequest));
}
void DamageManager::addDamageNotification(RemoteDamageNotification remoteDamageNotification) {
pushRemoteDamageNotification(remoteDamageNotification);
m_pendingRemoteNotifications.append(move(remoteDamageNotification));
}
}