diff options
Diffstat (limited to 'src/Physics/Explodinator.cpp')
-rw-r--r-- | src/Physics/Explodinator.cpp | 159 |
1 files changed, 77 insertions, 82 deletions
diff --git a/src/Physics/Explodinator.cpp b/src/Physics/Explodinator.cpp index ba1a8cbcf..058078cdc 100644 --- a/src/Physics/Explodinator.cpp +++ b/src/Physics/Explodinator.cpp @@ -7,7 +7,7 @@ #include "Chunk.h" #include "ClientHandle.h" #include "Entities/FallingBlock.h" -#include "LineBlockTracer.h" +#include "Physics/Tracers/LineBlockTracer.h" #include "Simulator/SandSimulator.h" @@ -22,27 +22,10 @@ namespace Explodinator static const auto TraceCubeSideLength = 16U; static const auto BoundingBoxStepUnit = 0.5; - /** Converts an absolute floating-point Position into a Chunk-relative one. */ - static Vector3f AbsoluteToRelative(const Vector3f a_Position, const cChunkCoords a_ChunkPosition) - { - return { a_Position.x - a_ChunkPosition.m_ChunkX * cChunkDef::Width, a_Position.y, a_Position.z - a_ChunkPosition.m_ChunkZ * cChunkDef::Width }; - } - - /** Make a From Chunk-relative Position into a To Chunk-relative position. */ - static Vector3f RebaseRelativePosition(const cChunkCoords a_From, const cChunkCoords a_To, const Vector3f a_Position) - { - return - { - a_Position.x + (a_From.m_ChunkX - a_To.m_ChunkX) * cChunkDef::Width, - a_Position.y, - a_Position.z + (a_From.m_ChunkZ - a_To.m_ChunkZ) * cChunkDef::Width - }; - } - /** Returns how much of an explosion Destruction Lazor's (tm) intensity the given block attenuates. Values are scaled as 0.3 * (0.3 + Wiki) since some compilers miss the constant folding optimisation. Wiki values are https://minecraft.gamepedia.com/Explosion#Blast_resistance as of 2021-02-06. */ - static float GetExplosionAbsorption(const BLOCKTYPE Block) + static constexpr float GetExplosionAbsorption(const BLOCKTYPE Block) { switch (Block) { @@ -162,7 +145,7 @@ namespace Explodinator /** Calculates the approximate percentage of an Entity's bounding box that is exposed to an explosion centred at Position. */ static float CalculateEntityExposure(const cChunk & a_Chunk, const cEntity & a_Entity, const Vector3f a_Position, const int a_SquareRadius) { - class LineOfSightCallbacks final : public cLineBlockTracer::cCallbacks + class LineOfSightCallbacks final : public BlockTracerCallbacks { virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override { @@ -173,7 +156,6 @@ namespace Explodinator const Vector3d Position = a_Position; unsigned Unobstructed = 0, Total = 0; const auto Box = a_Entity.GetBoundingBox(); - cLineBlockTracer Tracer(*a_Chunk.GetWorld(), Callback); for (double X = Box.GetMinX(); X < Box.GetMaxX(); X += BoundingBoxStepUnit) { @@ -182,6 +164,7 @@ namespace Explodinator for (double Z = Box.GetMinZ(); Z < Box.GetMaxZ(); Z += BoundingBoxStepUnit) { const Vector3d Destination{X, Y, Z}; + if ((Destination - Position).SqrLength() > a_SquareRadius) { // Don't bother with points outside our designated area-of-effect @@ -189,7 +172,7 @@ namespace Explodinator continue; } - if (Tracer.Trace(a_Position, Destination)) + if (LineBlockTracer::Trace(a_Chunk, Callback, Position, Destination)) { Unobstructed++; } @@ -257,134 +240,146 @@ namespace Explodinator } /** Sets the block at the given position, updating surroundings. */ - static void SetBlock(cWorld & a_World, cChunk & a_Chunk, const Vector3i a_AbsolutePosition, const Vector3i a_RelativePosition, const BLOCKTYPE a_DestroyedBlock, const BLOCKTYPE a_NewBlock, const cEntity * const a_ExplodingEntity) + static void SetBlock(cChunk & a_Chunk, const Vector3i a_AbsolutePosition, const Vector3i a_RelativePosition, const BLOCKTYPE a_CurrentBlock, const NIBBLETYPE a_CurrentMeta, const BLOCKTYPE a_NewBlock, const cEntity * const a_ExplodingEntity) { - const auto DestroyedMeta = a_Chunk.GetMeta(a_RelativePosition); - // SetBlock wakes up all simulators for the area, so that water and lava flows and sand falls into the blasted holes // It also is responsible for calling cBlockHandler::OnNeighborChanged to pop off blocks that fail CanBeAt // An explicit call to cBlockHandler::OnBroken handles the destruction of multiblock structures // References at (FS #391, GH #4418): a_Chunk.SetBlock(a_RelativePosition, a_NewBlock, 0); - cChunkInterface Interface(a_World.GetChunkMap()); - cBlockHandler::For(a_DestroyedBlock).OnBroken(Interface, a_World, a_AbsolutePosition, a_DestroyedBlock, DestroyedMeta, a_ExplodingEntity); + auto & World = *a_Chunk.GetWorld(); + cChunkInterface Interface(World.GetChunkMap()); + cBlockHandler::For(a_CurrentBlock).OnBroken(Interface, World, a_AbsolutePosition, a_CurrentBlock, a_CurrentMeta, a_ExplodingEntity); } /** Work out what should happen when an explosion destroys the given block. Tasks include lighting TNT, dropping pickups, setting fire and flinging shrapnel according to Minecraft rules. OK, _mostly_ Minecraft rules. */ - static void DestroyBlock(cChunk & a_Chunk, const Vector3i a_Position, const int a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity) + static void DestroyBlock(MTRand & a_Random, cChunk & a_Chunk, const Vector3i a_AbsolutePosition, const Vector3i a_RelativePosition, const BLOCKTYPE a_CurrentBlock, const NIBBLETYPE a_CurrentMeta, const cBoundingBox a_ExplosionBounds, const int a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity) { - const auto DestroyedBlock = a_Chunk.GetBlock(a_Position); - if (DestroyedBlock == E_BLOCK_AIR) - { - // There's nothing left for us here, but a barren and empty land - // Let's go. - return; - } - auto & World = *a_Chunk.GetWorld(); - auto & Random = GetRandomProvider(); - const auto Absolute = cChunkDef::RelativeToAbsolute(a_Position, a_Chunk.GetPos()); - if (DestroyedBlock == E_BLOCK_TNT) // If the block is TNT we should set it off + if (a_CurrentBlock == E_BLOCK_TNT) // If the block is TNT we should set it off { // Random fuse between 10 to 30 game ticks. - const int FuseTime = Random.RandInt(10, 30); + const int FuseTime = a_Random.RandInt(10, 30); // Activate the TNT, with initial velocity and no fuse sound: - World.SpawnPrimedTNT(Vector3d(0.5, 0, 0.5) + Absolute, FuseTime, 1, false); + World.SpawnPrimedTNT(Vector3d(0.5, 0, 0.5) + a_AbsolutePosition, FuseTime, 1, false); } - else if ((a_ExplodingEntity != nullptr) && (a_ExplodingEntity->IsTNT() || BlockAlwaysDrops(DestroyedBlock) || Random.RandBool(1.f / a_Power))) // For TNT explosions, destroying a block that always drops, or if RandBool, drop pickups + else if ((a_ExplodingEntity != nullptr) && (a_ExplodingEntity->IsTNT() || BlockAlwaysDrops(a_CurrentBlock) || a_Random.RandBool(1.f / a_Power))) // For TNT explosions, destroying a block that always drops, or if RandBool, drop pickups { - const auto DestroyedMeta = a_Chunk.GetMeta(a_Position); - a_Chunk.GetWorld()->SpawnItemPickups(cBlockHandler::For(DestroyedBlock).ConvertToPickups(DestroyedMeta), Absolute); + for (auto & Item : cBlockHandler::For(a_CurrentBlock).ConvertToPickups(a_CurrentMeta)) + { + World.SpawnItemPickup(Vector3d(0.5, 0, 0.5) + a_AbsolutePosition, std::move(Item), Vector3d(), a_ExplosionBounds); + } } - else if (a_Fiery && Random.RandBool(1 / 3.0)) // 33% chance of starting fires if it can start fires + else if (a_Fiery && a_Random.RandBool(1 / 3.0)) // 33% chance of starting fires if it can start fires { - const auto Below = a_Position.addedY(-1); + const auto Below = a_AbsolutePosition.addedY(-1); if ((Below.y >= 0) && cBlockInfo::FullyOccupiesVoxel(a_Chunk.GetBlock(Below))) { // Start a fire: - SetBlock(World, a_Chunk, Absolute, a_Position, DestroyedBlock, E_BLOCK_FIRE, a_ExplodingEntity); + SetBlock(a_Chunk, a_AbsolutePosition, a_RelativePosition, a_CurrentBlock, a_CurrentMeta, E_BLOCK_FIRE, a_ExplodingEntity); return; } } - else if (const auto Shrapnel = World.GetTNTShrapnelLevel(); (Shrapnel > slNone) && Random.RandBool(0)) // Currently 0% chance of flinging stuff around + else if (const auto Shrapnel = World.GetTNTShrapnelLevel(); (Shrapnel > slNone) && a_Random.RandBool(0)) // Currently 0% chance of flinging stuff around { // If the block is shrapnel-able, make a falling block entity out of it: if ( - ((Shrapnel == slAll) && cBlockInfo::FullyOccupiesVoxel(DestroyedBlock)) || - ((Shrapnel == slGravityAffectedOnly) && cSandSimulator::IsAllowedBlock(DestroyedBlock)) + ((Shrapnel == slAll) && cBlockInfo::FullyOccupiesVoxel(a_CurrentBlock)) || + ((Shrapnel == slGravityAffectedOnly) && cSandSimulator::IsAllowedBlock(a_CurrentBlock)) ) { - const auto DestroyedMeta = a_Chunk.GetMeta(a_Position); - auto FallingBlock = std::make_unique<cFallingBlock>(Vector3d(0.5, 0, 0.5) + Absolute, DestroyedBlock, DestroyedMeta); + auto FallingBlock = std::make_unique<cFallingBlock>(Vector3d(0.5, 0, 0.5) + a_AbsolutePosition, a_CurrentBlock, a_CurrentMeta); // TODO: correct velocity FallingBlock->SetSpeedY(40); FallingBlock->Initialize(std::move(FallingBlock), World); } } - SetBlock(World, a_Chunk, Absolute, a_Position, DestroyedBlock, E_BLOCK_AIR, a_ExplodingEntity); + SetBlock(a_Chunk, a_AbsolutePosition, a_RelativePosition, a_CurrentBlock, a_CurrentMeta, E_BLOCK_AIR, a_ExplodingEntity); } - /** Traces the path taken by one Explosion Lazor (tm) with given direction and intensity, that will destroy blocks until it is exhausted. */ - static void DestructionTrace(cChunk * a_Chunk, Vector3f a_Origin, const Vector3f a_Direction, const int a_Power, const bool a_Fiery, float a_Intensity, const cEntity * const a_ExplodingEntity) + /** Returns a random intensity for an Explosion Lazor (tm) as a function of the explosion's power. */ + static float RandomIntensity(MTRand & a_Random, const int a_Power) + { + return a_Power * (0.7f + a_Random.RandReal(0.6f)); + } + + /** Traces the path taken by one Explosion Lazor (tm) with given direction and random intensity, that will destroy blocks until it is exhausted. */ + static void DestructionTrace(MTRand & a_Random, cChunk * a_Chunk, Vector3f a_Origin, const Vector3f a_Direction, const cBoundingBox a_ExplosionBounds, const int a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity) { // The current position the ray is at. auto Checkpoint = a_Origin; + auto Position = Checkpoint.Floor(); + + auto Intensity = RandomIntensity(a_Random, a_Power); + // The displacement that the ray in one iteration step should travel. const auto Step = a_Direction.NormalizeCopy() * StepUnit; // Loop until intensity runs out: - while (a_Intensity > 0) + while (Intensity > 0) { - auto Position = Checkpoint.Floor(); if (!cChunkDef::IsValidHeight(Position)) { break; } - const auto Neighbour = a_Chunk->GetRelNeighborChunkAdjustCoords(Position); - if ((Neighbour == nullptr) || !Neighbour->IsValid()) + Vector3i RelativePosition; + + if (!a_Chunk->GetChunkAndRelByAbsolute(Position, &a_Chunk, RelativePosition)) { break; } - a_Intensity -= GetExplosionAbsorption(Neighbour->GetBlock(Position)); - if (a_Intensity <= 0) + BLOCKTYPE CurrentBlock; + NIBBLETYPE CurrentMeta; + a_Chunk->GetBlockTypeMeta(RelativePosition, CurrentBlock, CurrentMeta); + + Intensity -= GetExplosionAbsorption(CurrentBlock); + if (Intensity <= 0) { // The ray is exhausted: break; } - DestroyBlock(*Neighbour, Position, a_Power, a_Fiery, a_ExplodingEntity); - - // Adjust coordinates to be relative to the neighbour chunk: - Checkpoint = RebaseRelativePosition(a_Chunk->GetPos(), Neighbour->GetPos(), Checkpoint); - a_Origin = RebaseRelativePosition(a_Chunk->GetPos(), Neighbour->GetPos(), a_Origin); - a_Chunk = Neighbour; + if (CurrentBlock != E_BLOCK_AIR) + { + DestroyBlock(a_Random, *a_Chunk, Position, RelativePosition, CurrentBlock, CurrentMeta, a_ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity); + } // Increment the simulation, weaken the ray: Checkpoint += Step; - a_Intensity -= StepAttenuation; - } - } + Intensity -= StepAttenuation; - /** Returns a random intensity for an Explosion Lazor (tm) as a function of the explosion's power. */ - static float RandomIntensity(MTRand & a_Random, const int a_Power) - { - return a_Power * (0.7f + a_Random.RandReal(0.6f)); + for (int i = 0; i != 2; i++) + { + const auto PreviousPosition = Position; + Position = Checkpoint.Floor(); + + if (Position != PreviousPosition) + { + break; + } + + Checkpoint += Step; + Intensity -= StepAttenuation + GetExplosionAbsorption(E_BLOCK_AIR); + } + } } /** Sends out Explosion Lazors (tm) originating from the given position that destroy blocks. */ static void DamageBlocks(cChunk & a_Chunk, const Vector3f a_Position, const int a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity) { // Oh boy... Better hope you have a hot cache, 'cos this little manoeuvre's gonna cost us 1352 raytraces in one tick... - const int HalfSide = TraceCubeSideLength / 2; + auto & Random = GetRandomProvider(); + const int HalfSide = TraceCubeSideLength / 2; + const cBoundingBox ExplosionBounds(a_Position, TraceCubeSideLength); // The following loops implement the tracing algorithm described in http://minecraft.gamepedia.com/Explosion @@ -394,8 +389,8 @@ namespace Explodinator { for (float OffsetZ = -HalfSide; OffsetZ < HalfSide; OffsetZ++) { - DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, +HalfSide, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity); - DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, -HalfSide, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity); + DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(OffsetX, +HalfSide, OffsetZ), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity); + DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(OffsetX, -HalfSide, OffsetZ), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity); } } @@ -404,8 +399,8 @@ namespace Explodinator { for (float OffsetY = -HalfSide + 1; OffsetY < HalfSide - 1; OffsetY++) { - DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, +HalfSide), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity); - DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, -HalfSide), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity); + DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, +HalfSide), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity); + DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, -HalfSide), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity); } } @@ -414,8 +409,8 @@ namespace Explodinator { for (float OffsetY = -HalfSide + 1; OffsetY < HalfSide - 1; OffsetY++) { - DestructionTrace(&a_Chunk, a_Position, Vector3f(+HalfSide, OffsetY, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity); - DestructionTrace(&a_Chunk, a_Position, Vector3f(-HalfSide, OffsetY, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity); + DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(+HalfSide, OffsetY, OffsetZ), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity); + DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(-HalfSide, OffsetY, OffsetZ), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity); } } } @@ -435,7 +430,7 @@ namespace Explodinator { LagTheClient(a_Chunk, a_Position, a_Power); DamageEntities(a_Chunk, a_Position, a_Power); - DamageBlocks(a_Chunk, AbsoluteToRelative(a_Position, a_Chunk.GetPos()), a_Power, a_Fiery, a_ExplodingEntity); + DamageBlocks(a_Chunk, a_Position, a_Power, a_Fiery, a_ExplodingEntity); return false; }); |