From 93adbdce9a769b42baeb70f9ead5c7c6a35834b5 Mon Sep 17 00:00:00 2001 From: Tiger Wang Date: Sat, 12 Sep 2020 19:57:44 +0100 Subject: 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 --- src/ChunkMap.cpp | 197 ------------------------------------------------------- 1 file changed, 197 deletions(-) (limited to 'src/ChunkMap.cpp') diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index 92cce9b55..71b8a7a24 100644 --- a/src/ChunkMap.cpp +++ b/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(ExplosionSizeInt * ExplosionSizeInt * ExplosionSizeInt)); - if (!area.Read(*m_World, bx - ExplosionSizeInt, static_cast(ceil(a_BlockX + ExplosionSizeInt)), MinY, MaxY, bz - ExplosionSizeInt, static_cast(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((1 / std::max(1.0, DistanceFromExplosion.Length())) * 8 * ExplosionSizeInt), 0); - } - - float EntityExposure = a_Entity.GetExplosionExposureRate(ExplosionPos, static_cast(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); -- cgit v1.2.3