Use tracing for explosions (#4845)
* TNT: Implement tracing algorithm + Add intensity tracing * Fix iterating over all players to SendExplosion, even those not in range * Implemented TNT entity interaction * Fixed misaligned destruction tracing * Finalise TNT algorithm - Remove BlockArea and just use chunks Using SetBlock makes it so that we can update everything properly, and does appear to be faster. * BlockInfo learns about explosion attentuation * Rename Explodinator parameters * TNT: pull block destruction into common function Co-authored-by: Alexander Harkness <me@bearbin.net>
This commit is contained in:
197
src/ChunkMap.cpp
197
src/ChunkMap.cpp
@@ -1020,203 +1020,6 @@ bool cChunkMap::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback a
|
||||
|
||||
|
||||
|
||||
void cChunkMap::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, cVector3iArray & a_BlocksAffected)
|
||||
{
|
||||
// Don't explode if outside of Y range (prevents the following test running into unallocated memory):
|
||||
if (!cChunkDef::IsValidHeight(FloorC(a_BlockY)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool ShouldDestroyBlocks = true;
|
||||
|
||||
// Don't explode if the explosion center is inside a liquid block:
|
||||
if (IsBlockLiquid(m_World->GetBlock(FloorC(a_BlockX), FloorC(a_BlockY), FloorC(a_BlockZ))))
|
||||
{
|
||||
ShouldDestroyBlocks = false;
|
||||
}
|
||||
|
||||
int ExplosionSizeInt = CeilC(a_ExplosionSize);
|
||||
int ExplosionSizeSq = ExplosionSizeInt * ExplosionSizeInt;
|
||||
|
||||
int bx = FloorC(a_BlockX);
|
||||
int by = FloorC(a_BlockY);
|
||||
int bz = FloorC(a_BlockZ);
|
||||
|
||||
int MinY = std::max(FloorC(a_BlockY - ExplosionSizeInt), 0);
|
||||
int MaxY = std::min(CeilC(a_BlockY + ExplosionSizeInt), cChunkDef::Height - 1);
|
||||
|
||||
if (ShouldDestroyBlocks)
|
||||
{
|
||||
cBlockArea area;
|
||||
a_BlocksAffected.reserve(8 * static_cast<size_t>(ExplosionSizeInt * ExplosionSizeInt * ExplosionSizeInt));
|
||||
if (!area.Read(*m_World, bx - ExplosionSizeInt, static_cast<int>(ceil(a_BlockX + ExplosionSizeInt)), MinY, MaxY, bz - ExplosionSizeInt, static_cast<int>(ceil(a_BlockZ + ExplosionSizeInt))))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int x = -ExplosionSizeInt; x < ExplosionSizeInt; x++)
|
||||
{
|
||||
for (int y = -ExplosionSizeInt; y < ExplosionSizeInt; y++)
|
||||
{
|
||||
if ((by + y >= cChunkDef::Height) || (by + y < 0))
|
||||
{
|
||||
// Outside of the world
|
||||
continue;
|
||||
}
|
||||
for (int z = -ExplosionSizeInt; z < ExplosionSizeInt; z++)
|
||||
{
|
||||
if ((x * x + y * y + z * z) > ExplosionSizeSq)
|
||||
{
|
||||
// Too far away
|
||||
continue;
|
||||
}
|
||||
|
||||
BLOCKTYPE Block = area.GetBlockType(bx + x, by + y, bz + z);
|
||||
switch (Block)
|
||||
{
|
||||
case E_BLOCK_TNT:
|
||||
{
|
||||
// Activate the TNT, with a random fuse between 10 to 30 game ticks
|
||||
int FuseTime = GetRandomProvider().RandInt(10, 30);
|
||||
m_World->SpawnPrimedTNT({a_BlockX + x + 0.5, a_BlockY + y + 0.5, a_BlockZ + z + 0.5}, FuseTime, 1, false); // Initial velocity, no fuse sound
|
||||
area.SetBlockTypeMeta(bx + x, by + y, bz + z, E_BLOCK_AIR, 0);
|
||||
a_BlocksAffected.push_back(Vector3i(bx + x, by + y, bz + z));
|
||||
break;
|
||||
}
|
||||
|
||||
case E_BLOCK_OBSIDIAN:
|
||||
case E_BLOCK_BEACON:
|
||||
case E_BLOCK_BEDROCK:
|
||||
case E_BLOCK_BARRIER:
|
||||
case E_BLOCK_WATER:
|
||||
case E_BLOCK_LAVA:
|
||||
{
|
||||
// These blocks are not affected by explosions
|
||||
break;
|
||||
}
|
||||
|
||||
case E_BLOCK_STATIONARY_WATER:
|
||||
{
|
||||
// Turn into simulated water:
|
||||
area.SetBlockType(bx + x, by + y, bz + z, E_BLOCK_WATER);
|
||||
break;
|
||||
}
|
||||
|
||||
case E_BLOCK_STATIONARY_LAVA:
|
||||
{
|
||||
// Turn into simulated lava:
|
||||
area.SetBlockType(bx + x, by + y, bz + z, E_BLOCK_LAVA);
|
||||
break;
|
||||
}
|
||||
|
||||
case E_BLOCK_AIR:
|
||||
{
|
||||
// No pickups for air
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
auto & Random = GetRandomProvider();
|
||||
if (Random.RandBool(0.25)) // 25% chance of pickups
|
||||
{
|
||||
auto pickups = area.PickupsFromBlock({bx + x, by + y, bz + z});
|
||||
m_World->SpawnItemPickups(pickups, bx + x, by + y, bz + z);
|
||||
}
|
||||
else if ((m_World->GetTNTShrapnelLevel() > slNone) && Random.RandBool(0.20)) // 20% chance of flinging stuff around
|
||||
{
|
||||
// If the block is shrapnel-able, make a falling block entity out of it:
|
||||
if (
|
||||
((m_World->GetTNTShrapnelLevel() == slAll) && cBlockInfo::FullyOccupiesVoxel(Block)) ||
|
||||
((m_World->GetTNTShrapnelLevel() == slGravityAffectedOnly) && ((Block == E_BLOCK_SAND) || (Block == E_BLOCK_GRAVEL)))
|
||||
)
|
||||
{
|
||||
m_World->SpawnFallingBlock(bx + x, by + y + 5, bz + z, Block, area.GetBlockMeta(bx + x, by + y, bz + z));
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy any block entities
|
||||
if (cBlockEntity::IsBlockEntityBlockType(Block))
|
||||
{
|
||||
Vector3i BlockPos(bx + x, by + y, bz + z);
|
||||
DoWithBlockEntityAt(BlockPos.x, BlockPos.y, BlockPos.z, [](cBlockEntity & a_BE)
|
||||
{
|
||||
a_BE.Destroy();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
area.SetBlockTypeMeta(bx + x, by + y, bz + z, E_BLOCK_AIR, 0);
|
||||
a_BlocksAffected.push_back(Vector3i(bx + x, by + y, bz + z));
|
||||
break;
|
||||
}
|
||||
} // switch (BlockType)
|
||||
} // for z
|
||||
} // for y
|
||||
} // for x
|
||||
area.Write(*m_World, bx - ExplosionSizeInt, MinY, bz - ExplosionSizeInt);
|
||||
}
|
||||
|
||||
Vector3d ExplosionPos{ a_BlockX, a_BlockY, a_BlockZ };
|
||||
cBoundingBox bbTNT(ExplosionPos, 0.5, 1);
|
||||
bbTNT.Expand(ExplosionSizeInt * 2, ExplosionSizeInt * 2, ExplosionSizeInt * 2);
|
||||
|
||||
ForEachEntity([&](cEntity & a_Entity)
|
||||
{
|
||||
if (a_Entity.IsPickup() && (a_Entity.GetTicksAlive() < 20))
|
||||
{
|
||||
// If pickup age is smaller than one second, it is invincible (so we don't kill pickups that were just spawned)
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3d DistanceFromExplosion = a_Entity.GetPosition() - ExplosionPos;
|
||||
|
||||
if (!a_Entity.IsTNT() && !a_Entity.IsFallingBlock()) // Don't apply damage to other TNT entities and falling blocks, they should be invincible
|
||||
{
|
||||
auto EntityBox = a_Entity.GetBoundingBox();
|
||||
if (!bbTNT.IsInside(EntityBox)) // If entity box is inside tnt box, not vice versa!
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure that the damage dealt is inversely proportional to the distance to the TNT centre - the closer a player is, the harder they are hit
|
||||
a_Entity.TakeDamage(dtExplosion, nullptr, static_cast<int>((1 / std::max(1.0, DistanceFromExplosion.Length())) * 8 * ExplosionSizeInt), 0);
|
||||
}
|
||||
|
||||
float EntityExposure = a_Entity.GetExplosionExposureRate(ExplosionPos, static_cast<float>(a_ExplosionSize));
|
||||
|
||||
// Exposure reduced by armor
|
||||
EntityExposure = EntityExposure * (1.0f - a_Entity.GetEnchantmentBlastKnockbackReduction());
|
||||
|
||||
auto Impact = std::pow(std::max(0.2, DistanceFromExplosion.Length()), -1);
|
||||
Impact *= EntityExposure * ExplosionSizeInt * 6.0;
|
||||
|
||||
if (Impact > 0.0)
|
||||
{
|
||||
DistanceFromExplosion.Normalize();
|
||||
DistanceFromExplosion *= Vector3d{Impact, 0.0, Impact};
|
||||
DistanceFromExplosion.y += 0.3 * Impact;
|
||||
|
||||
a_Entity.SetSpeed(DistanceFromExplosion);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// Wake up all simulators for the area, so that water and lava flows and sand falls into the blasted holes (FS #391):
|
||||
m_World->GetSimulatorManager()->WakeUp(cCuboid(
|
||||
{bx - ExplosionSizeInt - 1, MinY, bz - ExplosionSizeInt - 1},
|
||||
{bx + ExplosionSizeInt + 1, MaxY, bz + ExplosionSizeInt + 1}
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cChunkMap::DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback a_Callback) const
|
||||
{
|
||||
cCSLock Lock(m_CSChunks);
|
||||
|
||||
Reference in New Issue
Block a user