diff --git a/src/BlockInfo.cpp b/src/BlockInfo.cpp index 21fa9e220..62978172c 100644 --- a/src/BlockInfo.cpp +++ b/src/BlockInfo.cpp @@ -134,6 +134,15 @@ bool IsBlockFence(BLOCKTYPE a_BlockType) +bool IsBlockShulkerBox(BLOCKTYPE a_BlockType) +{ + return ((a_BlockType >= E_BLOCK_WHITE_SHULKER_BOX) && (a_BlockType <= E_BLOCK_BLACK_SHULKER_BOX)); +} + + + + + bool IsBlockMaterialWood(BLOCKTYPE a_BlockType) { switch (a_BlockType) diff --git a/src/BlockInfo.h b/src/BlockInfo.h index 7a25e6760..d40ea21ac 100644 --- a/src/BlockInfo.h +++ b/src/BlockInfo.h @@ -86,6 +86,8 @@ bool IsBlockTypeOfDirt(BLOCKTYPE a_BlockType); bool IsBlockFence(BLOCKTYPE a_BlockType); +bool IsBlockShulkerBox(BLOCKTYPE a_BlockType); + bool IsBlockMaterialWood(BLOCKTYPE a_BlockType); bool IsBlockMaterialPlants(BLOCKTYPE a_BlockType); diff --git a/src/Physics/Explodinator.cpp b/src/Physics/Explodinator.cpp index 1c6d61949..f7662861e 100644 --- a/src/Physics/Explodinator.cpp +++ b/src/Physics/Explodinator.cpp @@ -92,17 +92,44 @@ namespace Explodinator Entity.TakeDamage(dtExplosion, nullptr, FloorC(Damage), 0); } - // Impact reduced by armour: - const auto ReducedImpact = Impact - Impact * Entity.GetEnchantmentBlastKnockbackReduction(); // TODO: call is very expensive, should only apply to Pawns - Entity.SetSpeed(Direction.NormalizeCopy() * KnockbackFactor * ReducedImpact); + // Impact reduced by armour, expensive call so only apply to Pawns: + if (Entity.IsPawn()) + { + const auto ReducedImpact = Impact - Impact * Entity.GetEnchantmentBlastKnockbackReduction(); + Entity.SetSpeed(Direction.NormalizeCopy() * KnockbackFactor * ReducedImpact); + } + else + { + Entity.SetSpeed(Direction.NormalizeCopy() * KnockbackFactor * Impact); + } // Continue iteration: return false; }); } + /** Returns true if block should always drop when exploded. + Currently missing conduits from 1.13 */ + static bool BlockAlwaysDrops(const BLOCKTYPE a_Block) + { + // If it's a Shulker box + if (IsBlockShulkerBox(a_Block)) + { + return true; + } + + switch (a_Block) + { + case E_BLOCK_DRAGON_EGG: + case E_BLOCK_BEACON: + case E_BLOCK_HEAD: return true; + } + + return false; + } + /** Sets the block at the given position, updating surroundings. */ - static void DestroyBlock(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(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) { const auto DestroyedMeta = a_Chunk.GetMeta(a_RelativePosition); @@ -116,7 +143,8 @@ namespace Explodinator cBlockHandler::For(a_DestroyedBlock).OnBroken(Interface, a_World, a_AbsolutePosition, a_DestroyedBlock, DestroyedMeta, a_ExplodingEntity); } - /** Sets the block at the given Position to air, updates surroundings, and spawns pickups, fire, shrapnel according to Minecraft rules. + /** 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 unsigned a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity) { @@ -131,7 +159,8 @@ namespace Explodinator auto & World = *a_Chunk.GetWorld(); auto & Random = GetRandomProvider(); const auto Absolute = cChunkDef::RelativeToAbsolute(a_Position, a_Chunk.GetPos()); - if (DestroyedBlock == E_BLOCK_TNT) + + if (DestroyedBlock == 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); @@ -139,7 +168,7 @@ namespace Explodinator // Activate the TNT, with initial velocity and no fuse sound: World.SpawnPrimedTNT(Vector3d(0.5, 0, 0.5) + Absolute, FuseTime, 1, false); } - else if (Random.RandBool(1.f / a_Power)) + else if (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 { const auto DestroyedMeta = a_Chunk.GetMeta(a_Position); a_Chunk.GetWorld()->SpawnItemPickups(cBlockHandler::For(DestroyedBlock).ConvertToPickups(DestroyedMeta), Absolute); @@ -150,11 +179,11 @@ namespace Explodinator if ((Below.y >= 0) && cBlockInfo::FullyOccupiesVoxel(a_Chunk.GetBlock(Below))) { // Start a fire: - DestroyBlock(World, a_Chunk, Absolute, a_Position, DestroyedBlock, E_BLOCK_FIRE, a_ExplodingEntity); + SetBlock(World, a_Chunk, Absolute, a_Position, DestroyedBlock, E_BLOCK_FIRE, a_ExplodingEntity); return; } } - else if (const auto Shrapnel = World.GetTNTShrapnelLevel(); (Shrapnel > slNone) && Random.RandBool(0)) // 20% chance of flinging stuff around + else if (const auto Shrapnel = World.GetTNTShrapnelLevel(); (Shrapnel > slNone) && 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 ( @@ -169,23 +198,20 @@ namespace Explodinator } } - DestroyBlock(World, a_Chunk, Absolute, a_Position, DestroyedBlock, E_BLOCK_AIR, a_ExplodingEntity); + SetBlock(World, a_Chunk, Absolute, a_Position, DestroyedBlock, 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_Destination, const unsigned a_Power, const bool a_Fiery, float a_Intensity, const cEntity * const a_ExplodingEntity) + static void DestructionTrace(cChunk * a_Chunk, Vector3f a_Origin, const Vector3f a_Direction, const unsigned a_Power, const bool a_Fiery, float a_Intensity, const cEntity * const a_ExplodingEntity) { // The current position the ray is at. auto Checkpoint = a_Origin; - // The total displacement the ray must travel. - const auto TraceDisplacement = (a_Destination - a_Origin); + // The displacement that the ray in one iteration step should travel. + const auto Step = a_Direction.NormalizeCopy() * StepUnit; - // The displacement that they ray in one iteration step should travel. - const auto Step = TraceDisplacement.NormalizeCopy() * StepUnit; - - // Loop until we've reached the prescribed destination: - while (TraceDisplacement > (Checkpoint - a_Origin)) + // Loop until intensity runs out: + while (a_Intensity > 0) { auto Position = Checkpoint.Floor(); if (!cChunkDef::IsValidHeight(Position.y)) @@ -219,54 +245,49 @@ namespace Explodinator } } + /** Returns a random intensity for an Explosion Lazor (tm) as a function of the explosion's power. */ + static float RandomIntensity(MTRand & a_Random, const unsigned a_Power) + { + return a_Power * (0.7f + a_Random.RandReal(0.6f)); + } + /** Sends out Explosion Lazors (tm) originating from the given position that destroy blocks. */ static void DamageBlocks(cChunk & a_Chunk, const Vector3f a_Position, const unsigned a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity) { - const auto Intensity = a_Power * (0.7f + GetRandomProvider().RandReal(0.6f)); - const auto ExplosionRadius = CeilC((Intensity / StepAttenuation) * StepUnit); - // 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(); // The following loops implement the tracing algorithm described in http://minecraft.gamepedia.com/Explosion // Trace rays from the explosion centre to all points in a square of area TraceCubeSideLength * TraceCubeSideLength - // in the YM and YP directions: + // for the top and bottom sides: for (int OffsetX = -HalfSide; OffsetX < HalfSide; OffsetX++) { for (int OffsetZ = -HalfSide; OffsetZ < HalfSide; OffsetZ++) { - DestructionTrace(&a_Chunk, a_Position, a_Position + Vector3f(OffsetX, +ExplosionRadius, OffsetZ), a_Power, a_Fiery, Intensity, a_ExplodingEntity); - DestructionTrace(&a_Chunk, a_Position, a_Position + Vector3f(OffsetX, -ExplosionRadius, OffsetZ), a_Power, a_Fiery, Intensity, a_ExplodingEntity); + DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, +1, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity); + DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, -1, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity); } } - /* - Trace rays from the centre to the sides of the explosion cube, being careful to avoid duplicates: - - Top view: - ______ - . | (dot to make style checker happy) - | | - | | - | ______ - - Side view: - + + - |====== | - | | | - |====== | - + + - - */ - for (int Offset = -HalfSide; Offset < HalfSide - 1; Offset++) + // Left and right sides, avoid duplicates at top and bottom edges: + for (int OffsetX = -HalfSide; OffsetX < HalfSide; OffsetX++) { for (int OffsetY = -HalfSide + 1; OffsetY < HalfSide - 1; OffsetY++) { - DestructionTrace(&a_Chunk, a_Position, a_Position + Vector3f(ExplosionRadius, OffsetY, Offset + 1), a_Power, a_Fiery, Intensity, a_ExplodingEntity); - DestructionTrace(&a_Chunk, a_Position, a_Position + Vector3f(-ExplosionRadius, OffsetY, Offset), a_Power, a_Fiery, Intensity, a_ExplodingEntity); - DestructionTrace(&a_Chunk, a_Position, a_Position + Vector3f(Offset, OffsetY, ExplosionRadius), a_Power, a_Fiery, Intensity, a_ExplodingEntity); - DestructionTrace(&a_Chunk, a_Position, a_Position + Vector3f(Offset + 1, OffsetY, -ExplosionRadius), a_Power, a_Fiery, Intensity, a_ExplodingEntity); + DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, +1), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity); + DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, -1), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity); + } + } + + // Front and back sides, avoid all edges: + for (int OffsetZ = -HalfSide + 1; OffsetZ < HalfSide - 1; OffsetZ++) + { + for (int OffsetY = -HalfSide + 1; OffsetY < HalfSide - 1; OffsetY++) + { + DestructionTrace(&a_Chunk, a_Position, Vector3f(+1, OffsetY, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity); + DestructionTrace(&a_Chunk, a_Position, Vector3f(-1, OffsetY, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity); } } } diff --git a/src/World.cpp b/src/World.cpp index 88e76c817..c3996c8ba 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -1390,10 +1390,9 @@ bool cWorld::ForEachFurnaceInChunk(int a_ChunkX, int a_ChunkZ, cFurnaceCallback void cWorld::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, bool a_CanCauseFire, eExplosionSource a_Source, void * a_SourceData) { cLock Lock(*this); - if (!cPluginManager::Get()->CallHookExploding(*this, a_ExplosionSize, a_CanCauseFire, a_BlockX, a_BlockY, a_BlockZ, a_Source, a_SourceData) && (a_ExplosionSize > 0)) { - // TODO: CanCauseFire gets reset to false for some reason + // TODO: CanCauseFire gets reset to false for some reason, (plugin has ability to change it, might be related) const cEntity * Entity; switch (a_Source)