1
0
Fork 0

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:
Tiger Wang 2020-09-12 19:57:44 +01:00 committed by GitHub
parent 834d61dacc
commit 93adbdce9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 531 additions and 300 deletions

View File

@ -436,6 +436,127 @@ bool cBlockInfo::IsSnowable(BLOCKTYPE a_BlockType)
float cBlockInfo::GetExplosionAbsorption(const BLOCKTYPE Block)
{
switch (Block)
{
case E_BLOCK_BEDROCK:
case E_BLOCK_COMMAND_BLOCK:
case E_BLOCK_END_GATEWAY:
case E_BLOCK_END_PORTAL:
case E_BLOCK_END_PORTAL_FRAME: return 3600000;
case E_BLOCK_ANVIL:
case E_BLOCK_ENCHANTMENT_TABLE:
case E_BLOCK_OBSIDIAN: return 1200;
case E_BLOCK_ENDER_CHEST: return 600;
case E_BLOCK_LAVA:
case E_BLOCK_STATIONARY_LAVA:
case E_BLOCK_WATER:
case E_BLOCK_STATIONARY_WATER: return 100;
case E_BLOCK_DRAGON_EGG:
case E_BLOCK_END_STONE:
case E_BLOCK_END_BRICKS: return 9;
case E_BLOCK_STONE:
case E_BLOCK_BLOCK_OF_COAL:
case E_BLOCK_DIAMOND_BLOCK:
case E_BLOCK_EMERALD_BLOCK:
case E_BLOCK_GOLD_BLOCK:
case E_BLOCK_IRON_BLOCK:
case E_BLOCK_BLOCK_OF_REDSTONE:
case E_BLOCK_BRICK:
case E_BLOCK_BRICK_STAIRS:
case E_BLOCK_COBBLESTONE:
case E_BLOCK_COBBLESTONE_STAIRS:
case E_BLOCK_IRON_BARS:
case E_BLOCK_JUKEBOX:
case E_BLOCK_MOSSY_COBBLESTONE:
case E_BLOCK_NETHER_BRICK:
case E_BLOCK_NETHER_BRICK_FENCE:
case E_BLOCK_NETHER_BRICK_STAIRS:
case E_BLOCK_PRISMARINE_BLOCK:
case E_BLOCK_STONE_BRICKS:
case E_BLOCK_STONE_BRICK_STAIRS:
case E_BLOCK_COBBLESTONE_WALL: return 6;
case E_BLOCK_IRON_DOOR:
case E_BLOCK_IRON_TRAPDOOR:
case E_BLOCK_MOB_SPAWNER: return 5;
case E_BLOCK_HOPPER: return 4.8f;
case E_BLOCK_TERRACOTTA: return 4.2f;
case E_BLOCK_COBWEB: return 4;
case E_BLOCK_DISPENSER:
case E_BLOCK_DROPPER:
case E_BLOCK_FURNACE:
case E_BLOCK_OBSERVER: return 3.5f;
case E_BLOCK_BEACON:
case E_BLOCK_COAL_ORE:
case E_BLOCK_COCOA_POD:
case E_BLOCK_DIAMOND_ORE:
case E_BLOCK_EMERALD_ORE:
case E_BLOCK_GOLD_ORE:
case E_BLOCK_IRON_ORE:
case E_BLOCK_LAPIS_BLOCK:
case E_BLOCK_LAPIS_ORE:
case E_BLOCK_NETHER_QUARTZ_ORE:
case E_BLOCK_PLANKS:
case E_BLOCK_REDSTONE_ORE:
case E_BLOCK_FENCE:
case E_BLOCK_FENCE_GATE:
case E_BLOCK_WOODEN_DOOR:
case E_BLOCK_WOODEN_SLAB:
case E_BLOCK_WOODEN_STAIRS:
case E_BLOCK_TRAPDOOR: return 3;
case E_BLOCK_CHEST:
case E_BLOCK_WORKBENCH:
case E_BLOCK_TRAPPED_CHEST: return 2.5f;
case E_BLOCK_BONE_BLOCK:
case E_BLOCK_CAULDRON:
case E_BLOCK_LOG: return 2;
case E_BLOCK_CONCRETE: return 1.8f;
case E_BLOCK_BOOKCASE: return 1.5f;
case E_BLOCK_STANDING_BANNER:
case E_BLOCK_WALL_BANNER:
case E_BLOCK_JACK_O_LANTERN:
case E_BLOCK_MELON:
case E_BLOCK_HEAD:
case E_BLOCK_NETHER_WART_BLOCK:
case E_BLOCK_PUMPKIN:
case E_BLOCK_SIGN_POST:
case E_BLOCK_WALLSIGN: return 1;
case E_BLOCK_QUARTZ_BLOCK:
case E_BLOCK_QUARTZ_STAIRS:
case E_BLOCK_RED_SANDSTONE:
case E_BLOCK_RED_SANDSTONE_STAIRS:
case E_BLOCK_SANDSTONE:
case E_BLOCK_SANDSTONE_STAIRS:
case E_BLOCK_WOOL: return 0.8f;
case E_BLOCK_SILVERFISH_EGG: return 0.75f;
case E_BLOCK_ACTIVATOR_RAIL:
case E_BLOCK_DETECTOR_RAIL:
case E_BLOCK_POWERED_RAIL:
case E_BLOCK_RAIL: return 0.7f;
case E_BLOCK_GRASS_PATH:
case E_BLOCK_CLAY:
case E_BLOCK_FARMLAND:
case E_BLOCK_GRASS:
case E_BLOCK_GRAVEL:
case E_BLOCK_SPONGE: return 0.6f;
case E_BLOCK_BREWING_STAND:
case E_BLOCK_STONE_BUTTON:
case E_BLOCK_WOODEN_BUTTON:
case E_BLOCK_CAKE:
case E_BLOCK_CONCRETE_POWDER:
case E_BLOCK_DIRT:
case E_BLOCK_FROSTED_ICE:
case E_BLOCK_HAY_BALE:
case E_BLOCK_ICE: return 0.5f;
default: return 0;
}
}
void cBlockInfo::sHandlerDeleter::operator () (cBlockHandler * a_Handler)
{
delete a_Handler;

View File

@ -47,6 +47,10 @@ public:
// tolua_end
/** Returns how much of an explosion Destruction Lazor's (tm) intensity the given block attenuates.
See Physics\Explodinator.cpp for details of explosion block destruction. */
static float GetExplosionAbsorption(BLOCKTYPE Block);
inline static cBlockHandler * GetHandler (BLOCKTYPE a_Type) { return Get(a_Type).m_Handler.get(); }
/** Creates a default BlockInfo structure, initializes all values to their defaults */

View File

@ -160,7 +160,7 @@ target_sources(
set(FOLDERS
Bindings BlockEntities Blocks Entities
Generating HTTP Items mbedTLS++ Mobs Noise
OSSupport Protocol Registries Simulator
OSSupport Physics Protocol Registries Simulator
Simulator/IncrementalRedstoneSimulator UI WorldStorage
)
@ -173,7 +173,7 @@ file(WRITE "${CMAKE_BINARY_DIR}/include/Globals.h"
"/* This file allows Globals.h to be included with an absolute path */\n#include \"${PROJECT_SOURCE_DIR}/src/Globals.h\"\n")
configure_file("BuildInfo.h.cmake" "${CMAKE_BINARY_DIR}/include/BuildInfo.h")
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE "${CMAKE_BINARY_DIR}/include/")
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE "${CMAKE_BINARY_DIR}/include/" ".")
# Generate AllFiles.lst for CheckBasicStyle.lua
get_target_property(ALL_FILES ${CMAKE_PROJECT_NAME} SOURCES)

View File

@ -419,6 +419,7 @@ void cChunk::WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlock
{
if (affectedArea.IsInside(itr->second->GetPos()))
{
itr->second->Destroy();
itr = m_BlockEntities.erase(itr);
}
else

View File

@ -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);

View File

@ -222,9 +222,6 @@ public:
If any chunk in the box is missing, ignores the entities in that chunk silently. */
bool ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback a_Callback); // Lua-accessible
/** Destroys and returns a list of blocks destroyed in the explosion at the specified coordinates */
void DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, cVector3iArray & a_BlockAffected);
/** Calls the callback if the entity with the specified ID is found, with the entity object as the callback param.
Returns true if entity found and callback returned false. */
bool DoWithEntityByID(UInt32 a_EntityID, cEntityCallback a_Callback) const; // Lua-accessible

View File

@ -2637,7 +2637,7 @@ void cClientHandle::SendEntityVelocity(const cEntity & a_Entity)
void cClientHandle::SendExplosion(const Vector3d a_Pos, float a_Radius, const cVector3iArray & a_BlocksAffected, const Vector3d a_PlayerMotion)
void cClientHandle::SendExplosion(const Vector3f a_Position, const float a_Power)
{
if (m_NumExplosionsThisTick > MAX_EXPLOSIONS_PER_TICK)
{
@ -2648,7 +2648,27 @@ void cClientHandle::SendExplosion(const Vector3d a_Pos, float a_Radius, const cV
// Update the statistics:
m_NumExplosionsThisTick++;
m_Protocol->SendExplosion(a_Pos.x, a_Pos.y, a_Pos.z, a_Radius, a_BlocksAffected, a_PlayerMotion);
auto & Random = GetRandomProvider();
const auto SoundPitchMultiplier = 1.0f + (Random.RandReal() - Random.RandReal()) * 0.2f;
// Sound:
SendSoundEffect("entity.generic.explode", a_Position, 4.0f, SoundPitchMultiplier * 0.7f);
const auto ParticleFormula = a_Power * 0.33f;
auto Spread = ParticleFormula * 0.5f;
auto ParticleCount = std::min(static_cast<int>(ParticleFormula * 125), 600);
// Dark smoke particles:
SendParticleEffect("largesmoke", a_Position.x, a_Position.y, a_Position.z, 0, 0, 0, Spread, static_cast<int>(ParticleCount));
Spread = ParticleFormula * 0.35f;
ParticleCount = std::min(static_cast<int>(ParticleFormula * 550), 1800);
// Light smoke particles:
SendParticleEffect("explode", a_Position.x, a_Position.y, a_Position.z, 0, 0, 0, Spread, static_cast<int>(ParticleCount));
// Shockwave effect:
m_Protocol->SendExplosion(a_Position, a_Power);
}

View File

@ -175,7 +175,7 @@ public: // tolua_export
void SendEntityVelocity (const cEntity & a_Entity);
void SendExperience (void);
void SendExperienceOrb (const cExpOrb & a_ExpOrb);
void SendExplosion (const Vector3d a_Pos, float a_Radius, const cVector3iArray & a_BlocksAffected, const Vector3d a_PlayerMotion);
void SendExplosion (Vector3f a_Position, float a_Power);
void SendGameMode (eGameMode a_GameMode);
void SendHealth (void);
void SendHeldItemChange (int a_ItemIndex);

View File

@ -241,6 +241,7 @@ public:
int GetChunkX(void) const { return FloorC(m_Position.x / cChunkDef::Width); }
int GetChunkZ(void) const { return FloorC(m_Position.z / cChunkDef::Width); }
// Get the Entity's axis aligned bounding box, with absolute (world-relative) coordinates.
cBoundingBox GetBoundingBox() const { return cBoundingBox(GetPosition(), GetWidth() / 2, GetHeight()); }
void SetHeadYaw (double a_HeadYaw);

View File

@ -8,7 +8,7 @@
cTNTEntity::cTNTEntity(Vector3d a_Pos, int a_FuseTicks) :
cTNTEntity::cTNTEntity(Vector3d a_Pos, unsigned a_FuseTicks) :
Super(etTNT, a_Pos, 0.98, 0.98),
m_FuseTicks(a_FuseTicks)
{
@ -33,10 +33,14 @@ void cTNTEntity::SpawnOn(cClientHandle & a_ClientHandle)
void cTNTEntity::Explode(void)
{
m_FuseTicks = 0;
Destroy();
FLOGD("BOOM at {0}", GetPosition());
m_World->DoExplosionAt(4.0, GetPosX(), GetPosY(), GetPosZ(), true, esPrimedTNT, this);
// Destroy first so the Explodinator doesn't find us (when iterating through entities):
Destroy();
// TODO: provided centred coordinates to all calls to DoExplosionAt, from entities and blocks
// This is to ensure maximum efficiency of explosions
m_World->DoExplosionAt(4.0, GetPosX(), GetPosY() + GetHeight() / 2, GetPosZ(), true, esPrimedTNT, this);
}
@ -51,6 +55,7 @@ void cTNTEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
// The base class tick destroyed us
return;
}
BroadcastMovementUpdate();
m_FuseTicks -= 1;

View File

@ -19,7 +19,7 @@ public: // tolua_export
CLASS_PROTODEF(cTNTEntity)
cTNTEntity(Vector3d a_Pos, int a_FuseTicks = 80);
cTNTEntity(Vector3d a_Pos, unsigned a_FuseTicks = 80);
// cEntity overrides:
virtual void SpawnOn(cClientHandle & a_ClientHandle) override;
@ -31,17 +31,13 @@ public: // tolua_export
void Explode(void);
/** Returns the fuse ticks until the tnt will explode */
int GetFuseTicks(void) const { return m_FuseTicks; }
unsigned GetFuseTicks(void) const { return m_FuseTicks; }
/** Set the fuse ticks until the tnt will explode */
void SetFuseTicks(int a_FuseTicks) { m_FuseTicks = a_FuseTicks; }
void SetFuseTicks(unsigned a_FuseTicks) { m_FuseTicks = a_FuseTicks; }
// tolua_end
protected:
int m_FuseTicks; ///< How much ticks is left, while the tnt will explode
}; // tolua_export

View File

@ -0,0 +1,9 @@
target_sources(
${CMAKE_PROJECT_NAME} PRIVATE
Explodinator.cpp
# Lightning.cpp
Explodinator.h
# Lightning.h
)

View File

@ -0,0 +1,295 @@
#include "Globals.h"
#include "BlockInfo.h"
#include "Blocks/BlockHandler.h"
#include "Blocks/ChunkInterface.h"
#include "Chunk.h"
#include "ClientHandle.h"
#include "Entities/FallingBlock.h"
#include "LineBlockTracer.h"
#include "Simulator/SandSimulator.h"
namespace Explodinator
{
const auto StepUnit = 0.3f;
const auto KnockbackFactor = 25U;
const auto StepAttenuation = 0.225f;
const auto TraceCubeSideLength = 16U;
const auto BoundingBoxStepUnit = 0.5f;
/** 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
};
}
/** Calculates the approximate percentage of an Entity's bounding box that is exposed to an explosion centred at Position. */
static float CalculateEntityExposure(cChunk & a_Chunk, const cEntity & a_Entity, const Vector3f a_Position, const float a_SquareRadius)
{
unsigned Unobstructed = 0, Total = 0;
const auto Box = a_Entity.GetBoundingBox();
for (float X = Box.GetMinX(); X < Box.GetMaxX(); X += BoundingBoxStepUnit)
{
for (float Y = Box.GetMinY(); Y < Box.GetMaxY(); Y += BoundingBoxStepUnit)
{
for (float Z = Box.GetMinZ(); Z < Box.GetMaxZ(); Z += BoundingBoxStepUnit)
{
const auto Destination = Vector3f(X, Y, Z);
if ((Destination - a_Position).SqrLength() > a_SquareRadius)
{
// Don't bother with points outside our designated area-of-effect
// This is, surprisingly, a massive amount of work saved (~3m to detonate a sphere of 37k TNT before, ~1m after):
continue;
}
if (cLineBlockTracer::LineOfSightTrace(*a_Chunk.GetWorld(), a_Position, Destination, cLineBlockTracer::eLineOfSight::losAir))
{
Unobstructed++;
}
Total++;
}
}
}
return static_cast<float>(Unobstructed) / Total;
}
/** Applies distance-based damage and knockback to all entities within the explosion's effect range. */
static void DamageEntities(cChunk & a_Chunk, const Vector3f a_Position, const unsigned a_Power)
{
const auto Radius = a_Power * 2.f;
const auto SquareRadius = Radius * Radius;
a_Chunk.GetWorld()->ForEachEntityInBox({ a_Position, Radius * 2 }, [&a_Chunk, a_Position, a_Power, Radius, SquareRadius](cEntity & Entity)
{
// Percentage of rays unobstructed.
const auto Exposure = CalculateEntityExposure(a_Chunk, Entity, a_Position, SquareRadius);
const auto Direction = Entity.GetPosition() - a_Position;
const auto Impact = (1 - (static_cast<float>(Direction.Length()) / Radius)) * Exposure;
// Don't apply damage to other TNT entities and falling blocks, they should be invincible:
if (!Entity.IsTNT() && !Entity.IsFallingBlock())
{
const auto Damage = (Impact * Impact + Impact) * 7 * a_Power + 1;
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);
// Continue iteration:
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 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());
cBlockInfo::GetHandler(a_DestroyedBlock)->OnBroken(Interface, a_World, a_AbsolutePosition, a_DestroyedBlock, DestroyedMeta);
}
/** Sets the block at the given Position to air, updates surroundings, and spawns pickups, fire, 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 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)
{
// Random fuse between 10 to 30 game ticks.
const int FuseTime = 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);
}
else if (Random.RandBool(1.f / a_Power))
{
const auto DestroyedMeta = a_Chunk.GetMeta(a_Position);
a_Chunk.GetWorld()->SpawnItemPickups(
cBlockInfo::GetHandler(DestroyedBlock)->ConvertToPickups(DestroyedMeta, a_Chunk.GetBlockEntityRel(a_Position)),
Absolute
);
}
else if (a_Fiery && Random.RandBool(1.f / 3.f)) // 33% chance of starting fires if it can start fires
{
const auto Below = a_Position.addedY(-1);
if ((Below.y >= 0) && cBlockInfo::FullyOccupiesVoxel(a_Chunk.GetBlock(Below)))
{
// Start a fire:
DestroyBlock(World, a_Chunk, Absolute, a_Position, DestroyedBlock, E_BLOCK_FIRE);
return;
}
}
else if (const auto Shrapnel = World.GetTNTShrapnelLevel(); (Shrapnel > slNone) && Random.RandBool(0)) // 20% 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))
)
{
const auto DestroyedMeta = a_Chunk.GetMeta(a_Position);
auto FallingBlock = std::make_unique<cFallingBlock>(Vector3d(0.5, 0, 0.5) + Absolute, DestroyedBlock, DestroyedMeta);
// TODO: correct velocity FallingBlock->SetSpeedY(40);
FallingBlock->Initialize(std::move(FallingBlock), World);
}
}
DestroyBlock(World, a_Chunk, Absolute, a_Position, DestroyedBlock, E_BLOCK_AIR);
}
/** 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)
{
// 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 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))
{
auto Position = Checkpoint.Floor();
if (!cChunkDef::IsValidHeight(Position.y))
{
break;
}
const auto Neighbour = a_Chunk->GetRelNeighborChunkAdjustCoords(Position);
if ((Neighbour == nullptr) || !Neighbour->IsValid())
{
break;
}
a_Intensity -= 0.3f * (0.3f + cBlockInfo::GetExplosionAbsorption(Neighbour->GetBlock(Position)));
if (a_Intensity <= 0)
{
// The ray is exhausted:
break;
}
DestroyBlock(*Neighbour, Position, a_Power, a_Fiery);
// 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;
// Increment the simulation, weaken the ray:
Checkpoint += Step;
a_Intensity -= StepAttenuation;
}
}
/** 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 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;
// 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 (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);
DestructionTrace(&a_Chunk, a_Position, a_Position + Vector3f(OffsetX, -ExplosionRadius, OffsetZ), a_Power, a_Fiery, Intensity);
}
}
/*
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++)
{
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);
DestructionTrace(&a_Chunk, a_Position, a_Position + Vector3f(-ExplosionRadius, OffsetY, Offset), a_Power, a_Fiery, Intensity);
DestructionTrace(&a_Chunk, a_Position, a_Position + Vector3f(Offset, OffsetY, ExplosionRadius), a_Power, a_Fiery, Intensity);
DestructionTrace(&a_Chunk, a_Position, a_Position + Vector3f(Offset + 1, OffsetY, -ExplosionRadius), a_Power, a_Fiery, Intensity);
}
}
}
/** Sends an explosion packet to all clients in the given chunk. */
static void LagTheClient(cChunk & a_Chunk, const Vector3f a_Position, const unsigned a_Power)
{
for (const auto Client : a_Chunk.GetAllClients())
{
Client->SendExplosion(a_Position, a_Power);
}
}
void Kaboom(cWorld & a_World, const Vector3f a_Position, const unsigned a_Power, const bool a_Fiery)
{
a_World.DoWithChunkAt(a_Position.Floor(), [a_Position, a_Power, a_Fiery](cChunk & a_Chunk)
{
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);
return false;
});
}
}

View File

@ -0,0 +1,20 @@
#pragma once
class cWorld;
namespace Explodinator
{
/** Creates an explosion of Power, centred at Position, with ability to set fires as provided.
For maximum efficiency, Position should be in the centre of the entity or block that exploded.
Kaboom indeed, you drunken wretch. */
void Kaboom(cWorld & World, Vector3f Position, unsigned Power, bool Fiery);
}

View File

@ -374,7 +374,7 @@ public:
virtual void SendEntityProperties (const cEntity & a_Entity) = 0;
virtual void SendEntityStatus (const cEntity & a_Entity, char a_Status) = 0;
virtual void SendEntityVelocity (const cEntity & a_Entity) = 0;
virtual void SendExplosion (double a_BlockX, double a_BlockY, double a_BlockZ, float a_Radius, const cVector3iArray & a_BlocksAffected, const Vector3d & a_PlayerMotion) = 0;
virtual void SendExplosion (Vector3f a_Position, float a_Power) = 0;
virtual void SendGameMode (eGameMode a_GameMode) = 0;
virtual void SendHealth (void) = 0;
virtual void SendHeldItemChange (int a_ItemIndex) = 0;

View File

@ -620,25 +620,19 @@ void cProtocol_1_8_0::SendExperienceOrb(const cExpOrb & a_ExpOrb)
void cProtocol_1_8_0::SendExplosion(double a_BlockX, double a_BlockY, double a_BlockZ, float a_Radius, const cVector3iArray & a_BlocksAffected, const Vector3d & a_PlayerMotion)
void cProtocol_1_8_0::SendExplosion(const Vector3f a_Position, const float a_Power)
{
ASSERT(m_State == 3); // In game mode?
cPacketizer Pkt(*this, pktExplosion);
Pkt.WriteBEFloat(static_cast<float>(a_BlockX));
Pkt.WriteBEFloat(static_cast<float>(a_BlockY));
Pkt.WriteBEFloat(static_cast<float>(a_BlockZ));
Pkt.WriteBEFloat(static_cast<float>(a_Radius));
Pkt.WriteBEUInt32(static_cast<UInt32>(a_BlocksAffected.size()));
for (cVector3iArray::const_iterator itr = a_BlocksAffected.begin(), end = a_BlocksAffected.end(); itr != end; ++itr)
{
Pkt.WriteBEInt8(static_cast<Int8>(itr->x));
Pkt.WriteBEInt8(static_cast<Int8>(itr->y));
Pkt.WriteBEInt8(static_cast<Int8>(itr->z));
} // for itr - a_BlockAffected[]
Pkt.WriteBEFloat(static_cast<float>(a_PlayerMotion.x));
Pkt.WriteBEFloat(static_cast<float>(a_PlayerMotion.y));
Pkt.WriteBEFloat(static_cast<float>(a_PlayerMotion.z));
Pkt.WriteBEFloat(a_Position.x);
Pkt.WriteBEFloat(a_Position.y);
Pkt.WriteBEFloat(a_Position.z);
Pkt.WriteBEFloat(a_Power);
Pkt.WriteBEUInt32(0);
Pkt.WriteBEFloat(0);
Pkt.WriteBEFloat(0);
Pkt.WriteBEFloat(0);
}

View File

@ -64,7 +64,7 @@ public:
virtual void SendEntityVelocity (const cEntity & a_Entity) override;
virtual void SendExperience (void) override;
virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override;
virtual void SendExplosion (double a_BlockX, double a_BlockY, double a_BlockZ, float a_Radius, const cVector3iArray & a_BlocksAffected, const Vector3d & a_PlayerMotion) override;
virtual void SendExplosion (Vector3f a_Position, float a_Power) override;
virtual void SendGameMode (eGameMode a_GameMode) override;
virtual void SendHealth (void) override;
virtual void SendHeldItemChange (int a_ItemIndex) override;

View File

@ -67,8 +67,7 @@ public:
return 0;
}
std::swap(Result->second, Power);
return Power;
return std::exchange(Result->second, Power);
}
/** Adjust From-relative coordinates into To-relative coordinates. */

View File

@ -74,29 +74,6 @@ void cSandSimulator::SimulateChunk(std::chrono::milliseconds a_Dt, int a_ChunkX,
bool cSandSimulator::IsAllowedBlock(BLOCKTYPE a_BlockType)
{
switch (a_BlockType)
{
case E_BLOCK_ANVIL:
case E_BLOCK_CONCRETE_POWDER:
case E_BLOCK_DRAGON_EGG:
case E_BLOCK_GRAVEL:
case E_BLOCK_SAND:
{
return true;
}
default:
{
return false;
}
}
}
void cSandSimulator::AddBlock(cChunk & a_Chunk, Vector3i a_Position, BLOCKTYPE a_Block)
{
if (!IsAllowedBlock(a_Block))
@ -286,6 +263,29 @@ void cSandSimulator::FinishFalling(
bool cSandSimulator::IsAllowedBlock(BLOCKTYPE a_BlockType)
{
switch (a_BlockType)
{
case E_BLOCK_ANVIL:
case E_BLOCK_CONCRETE_POWDER:
case E_BLOCK_DRAGON_EGG:
case E_BLOCK_GRAVEL:
case E_BLOCK_SAND:
{
return true;
}
default:
{
return false;
}
}
}
void cSandSimulator::DoInstantFall(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ)
{
// Remove the original block:

View File

@ -51,13 +51,13 @@ public:
BLOCKTYPE a_FallingBlockType, NIBBLETYPE a_FallingBlockMeta
);
static bool IsAllowedBlock(BLOCKTYPE a_BlockType);
private:
virtual void Simulate(float a_Dt) override { UNUSED(a_Dt);} // not used
virtual void SimulateChunk(std::chrono::milliseconds a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk) override;
static bool IsAllowedBlock(BLOCKTYPE a_BlockType);
bool m_IsInstantFall; // If set to true, blocks don't fall using cFallingBlock entity, but instantly instead
int m_TotalBlocks; // Total number of blocks currently in the queue for simulating

View File

@ -4,6 +4,7 @@
#include "World.h"
#include "BlockInfo.h"
#include "ClientHandle.h"
#include "Physics/Explodinator.h"
#include "Server.h"
#include "Root.h"
#include "IniFile.h"
@ -1393,47 +1394,13 @@ 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))
if (!cPluginManager::Get()->CallHookExploding(*this, a_ExplosionSize, a_CanCauseFire, a_BlockX, a_BlockY, a_BlockZ, a_Source, a_SourceData) && (a_ExplosionSize > 0))
{
return;
// TODO: CanCauseFire gets reset to false for some reason
Explodinator::Kaboom(*this, Vector3f(a_BlockX, a_BlockY, a_BlockZ), a_ExplosionSize, a_CanCauseFire);
cPluginManager::Get()->CallHookExploded(*this, a_ExplosionSize, a_CanCauseFire, a_BlockX, a_BlockY, a_BlockZ, a_Source, a_SourceData);
}
// TODO: Implement block hardiness
cVector3iArray BlocksAffected;
m_ChunkMap->DoExplosionAt(a_ExplosionSize, a_BlockX, a_BlockY, a_BlockZ, BlocksAffected);
auto & Random = GetRandomProvider();
auto SoundPitchMultiplier = 1.0f + (Random.RandReal(1.0f) - Random.RandReal(1.0f)) * 0.2f;
BroadcastSoundEffect("entity.generic.explode", Vector3d(a_BlockX, a_BlockY, a_BlockZ), 4.0f, SoundPitchMultiplier * 0.7f);
Vector3d ExplosionPos(a_BlockX, a_BlockY, a_BlockZ);
for (auto Player : m_Players)
{
cClientHandle * ch = Player->GetClientHandle();
if (ch == nullptr)
{
continue;
}
bool InRange = (Player->GetExplosionExposureRate(ExplosionPos, static_cast<float>(a_ExplosionSize)) > 0);
auto Speed = InRange ? Player->GetSpeed() : Vector3d{};
ch->SendExplosion({a_BlockX, a_BlockY, a_BlockZ}, static_cast<float>(a_ExplosionSize), BlocksAffected, Speed);
}
auto Position = Vector3d(a_BlockX, a_BlockY - 0.5f, a_BlockZ);
auto ParticleFormula = a_ExplosionSize * 0.33f;
auto Spread = ParticleFormula * 0.5f;
auto ParticleCount = std::min((ParticleFormula * 125), 600.0);
BroadcastParticleEffect("largesmoke", Position, Vector3f{}, static_cast<float>(Spread), static_cast<int>(ParticleCount));
Spread = ParticleFormula * 0.35f;
ParticleCount = std::min((ParticleFormula * 550), 1800.0);
BroadcastParticleEffect("explode", Position, Vector3f{}, static_cast<float>(Spread), static_cast<int>(ParticleCount));
cPluginManager::Get()->CallHookExploded(*this, a_ExplosionSize, a_CanCauseFire, a_BlockX, a_BlockY, a_BlockZ, a_Source, a_SourceData);
}

View File

@ -3,7 +3,6 @@
#include "Globals.h"
#include "StatSerializer.h"
#include "../Statistics.h"
#include "NamespaceSerializer.h"