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:
parent
834d61dacc
commit
93adbdce9a
|
@ -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)
|
void cBlockInfo::sHandlerDeleter::operator () (cBlockHandler * a_Handler)
|
||||||
{
|
{
|
||||||
delete a_Handler;
|
delete a_Handler;
|
||||||
|
|
|
@ -47,6 +47,10 @@ public:
|
||||||
|
|
||||||
// tolua_end
|
// 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(); }
|
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 */
|
/** Creates a default BlockInfo structure, initializes all values to their defaults */
|
||||||
|
|
|
@ -160,7 +160,7 @@ target_sources(
|
||||||
set(FOLDERS
|
set(FOLDERS
|
||||||
Bindings BlockEntities Blocks Entities
|
Bindings BlockEntities Blocks Entities
|
||||||
Generating HTTP Items mbedTLS++ Mobs Noise
|
Generating HTTP Items mbedTLS++ Mobs Noise
|
||||||
OSSupport Protocol Registries Simulator
|
OSSupport Physics Protocol Registries Simulator
|
||||||
Simulator/IncrementalRedstoneSimulator UI WorldStorage
|
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")
|
"/* 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")
|
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
|
# Generate AllFiles.lst for CheckBasicStyle.lua
|
||||||
get_target_property(ALL_FILES ${CMAKE_PROJECT_NAME} SOURCES)
|
get_target_property(ALL_FILES ${CMAKE_PROJECT_NAME} SOURCES)
|
||||||
|
|
|
@ -419,6 +419,7 @@ void cChunk::WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlock
|
||||||
{
|
{
|
||||||
if (affectedArea.IsInside(itr->second->GetPos()))
|
if (affectedArea.IsInside(itr->second->GetPos()))
|
||||||
{
|
{
|
||||||
|
itr->second->Destroy();
|
||||||
itr = m_BlockEntities.erase(itr);
|
itr = m_BlockEntities.erase(itr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
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
|
bool cChunkMap::DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback a_Callback) const
|
||||||
{
|
{
|
||||||
cCSLock Lock(m_CSChunks);
|
cCSLock Lock(m_CSChunks);
|
||||||
|
|
|
@ -222,9 +222,6 @@ public:
|
||||||
If any chunk in the box is missing, ignores the entities in that chunk silently. */
|
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
|
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.
|
/** 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. */
|
Returns true if entity found and callback returned false. */
|
||||||
bool DoWithEntityByID(UInt32 a_EntityID, cEntityCallback a_Callback) const; // Lua-accessible
|
bool DoWithEntityByID(UInt32 a_EntityID, cEntityCallback a_Callback) const; // Lua-accessible
|
||||||
|
|
|
@ -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)
|
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:
|
// Update the statistics:
|
||||||
m_NumExplosionsThisTick++;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,7 @@ public: // tolua_export
|
||||||
void SendEntityVelocity (const cEntity & a_Entity);
|
void SendEntityVelocity (const cEntity & a_Entity);
|
||||||
void SendExperience (void);
|
void SendExperience (void);
|
||||||
void SendExperienceOrb (const cExpOrb & a_ExpOrb);
|
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 SendGameMode (eGameMode a_GameMode);
|
||||||
void SendHealth (void);
|
void SendHealth (void);
|
||||||
void SendHeldItemChange (int a_ItemIndex);
|
void SendHeldItemChange (int a_ItemIndex);
|
||||||
|
|
|
@ -241,6 +241,7 @@ public:
|
||||||
int GetChunkX(void) const { return FloorC(m_Position.x / cChunkDef::Width); }
|
int GetChunkX(void) const { return FloorC(m_Position.x / cChunkDef::Width); }
|
||||||
int GetChunkZ(void) const { return FloorC(m_Position.z / 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()); }
|
cBoundingBox GetBoundingBox() const { return cBoundingBox(GetPosition(), GetWidth() / 2, GetHeight()); }
|
||||||
|
|
||||||
void SetHeadYaw (double a_HeadYaw);
|
void SetHeadYaw (double a_HeadYaw);
|
||||||
|
|
|
@ -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),
|
Super(etTNT, a_Pos, 0.98, 0.98),
|
||||||
m_FuseTicks(a_FuseTicks)
|
m_FuseTicks(a_FuseTicks)
|
||||||
{
|
{
|
||||||
|
@ -33,10 +33,14 @@ void cTNTEntity::SpawnOn(cClientHandle & a_ClientHandle)
|
||||||
|
|
||||||
void cTNTEntity::Explode(void)
|
void cTNTEntity::Explode(void)
|
||||||
{
|
{
|
||||||
m_FuseTicks = 0;
|
|
||||||
Destroy();
|
|
||||||
FLOGD("BOOM at {0}", GetPosition());
|
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
|
// The base class tick destroyed us
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BroadcastMovementUpdate();
|
BroadcastMovementUpdate();
|
||||||
|
|
||||||
m_FuseTicks -= 1;
|
m_FuseTicks -= 1;
|
||||||
|
|
|
@ -19,7 +19,7 @@ public: // tolua_export
|
||||||
|
|
||||||
CLASS_PROTODEF(cTNTEntity)
|
CLASS_PROTODEF(cTNTEntity)
|
||||||
|
|
||||||
cTNTEntity(Vector3d a_Pos, int a_FuseTicks = 80);
|
cTNTEntity(Vector3d a_Pos, unsigned a_FuseTicks = 80);
|
||||||
|
|
||||||
// cEntity overrides:
|
// cEntity overrides:
|
||||||
virtual void SpawnOn(cClientHandle & a_ClientHandle) override;
|
virtual void SpawnOn(cClientHandle & a_ClientHandle) override;
|
||||||
|
@ -31,17 +31,13 @@ public: // tolua_export
|
||||||
void Explode(void);
|
void Explode(void);
|
||||||
|
|
||||||
/** Returns the fuse ticks until the tnt will explode */
|
/** 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 */
|
/** 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
|
// tolua_end
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int m_FuseTicks; ///< How much ticks is left, while the tnt will explode
|
int m_FuseTicks; ///< How much ticks is left, while the tnt will explode
|
||||||
}; // tolua_export
|
}; // tolua_export
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
target_sources(
|
||||||
|
${CMAKE_PROJECT_NAME} PRIVATE
|
||||||
|
|
||||||
|
Explodinator.cpp
|
||||||
|
# Lightning.cpp
|
||||||
|
|
||||||
|
Explodinator.h
|
||||||
|
# Lightning.h
|
||||||
|
)
|
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -374,7 +374,7 @@ public:
|
||||||
virtual void SendEntityProperties (const cEntity & a_Entity) = 0;
|
virtual void SendEntityProperties (const cEntity & a_Entity) = 0;
|
||||||
virtual void SendEntityStatus (const cEntity & a_Entity, char a_Status) = 0;
|
virtual void SendEntityStatus (const cEntity & a_Entity, char a_Status) = 0;
|
||||||
virtual void SendEntityVelocity (const cEntity & a_Entity) = 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 SendGameMode (eGameMode a_GameMode) = 0;
|
||||||
virtual void SendHealth (void) = 0;
|
virtual void SendHealth (void) = 0;
|
||||||
virtual void SendHeldItemChange (int a_ItemIndex) = 0;
|
virtual void SendHeldItemChange (int a_ItemIndex) = 0;
|
||||||
|
|
|
@ -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?
|
ASSERT(m_State == 3); // In game mode?
|
||||||
|
|
||||||
cPacketizer Pkt(*this, pktExplosion);
|
cPacketizer Pkt(*this, pktExplosion);
|
||||||
Pkt.WriteBEFloat(static_cast<float>(a_BlockX));
|
Pkt.WriteBEFloat(a_Position.x);
|
||||||
Pkt.WriteBEFloat(static_cast<float>(a_BlockY));
|
Pkt.WriteBEFloat(a_Position.y);
|
||||||
Pkt.WriteBEFloat(static_cast<float>(a_BlockZ));
|
Pkt.WriteBEFloat(a_Position.z);
|
||||||
Pkt.WriteBEFloat(static_cast<float>(a_Radius));
|
Pkt.WriteBEFloat(a_Power);
|
||||||
Pkt.WriteBEUInt32(static_cast<UInt32>(a_BlocksAffected.size()));
|
Pkt.WriteBEUInt32(0);
|
||||||
for (cVector3iArray::const_iterator itr = a_BlocksAffected.begin(), end = a_BlocksAffected.end(); itr != end; ++itr)
|
Pkt.WriteBEFloat(0);
|
||||||
{
|
Pkt.WriteBEFloat(0);
|
||||||
Pkt.WriteBEInt8(static_cast<Int8>(itr->x));
|
Pkt.WriteBEFloat(0);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ public:
|
||||||
virtual void SendEntityVelocity (const cEntity & a_Entity) override;
|
virtual void SendEntityVelocity (const cEntity & a_Entity) override;
|
||||||
virtual void SendExperience (void) override;
|
virtual void SendExperience (void) override;
|
||||||
virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) 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 SendGameMode (eGameMode a_GameMode) override;
|
||||||
virtual void SendHealth (void) override;
|
virtual void SendHealth (void) override;
|
||||||
virtual void SendHeldItemChange (int a_ItemIndex) override;
|
virtual void SendHeldItemChange (int a_ItemIndex) override;
|
||||||
|
|
|
@ -67,8 +67,7 @@ public:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::swap(Result->second, Power);
|
return std::exchange(Result->second, Power);
|
||||||
return Power;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adjust From-relative coordinates into To-relative coordinates. */
|
/** Adjust From-relative coordinates into To-relative coordinates. */
|
||||||
|
|
|
@ -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)
|
void cSandSimulator::AddBlock(cChunk & a_Chunk, Vector3i a_Position, BLOCKTYPE a_Block)
|
||||||
{
|
{
|
||||||
if (!IsAllowedBlock(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)
|
void cSandSimulator::DoInstantFall(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ)
|
||||||
{
|
{
|
||||||
// Remove the original block:
|
// Remove the original block:
|
||||||
|
|
|
@ -51,13 +51,13 @@ public:
|
||||||
BLOCKTYPE a_FallingBlockType, NIBBLETYPE a_FallingBlockMeta
|
BLOCKTYPE a_FallingBlockType, NIBBLETYPE a_FallingBlockMeta
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static bool IsAllowedBlock(BLOCKTYPE a_BlockType);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
virtual void Simulate(float a_Dt) override { UNUSED(a_Dt);} // not used
|
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;
|
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
|
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
|
int m_TotalBlocks; // Total number of blocks currently in the queue for simulating
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "World.h"
|
#include "World.h"
|
||||||
#include "BlockInfo.h"
|
#include "BlockInfo.h"
|
||||||
#include "ClientHandle.h"
|
#include "ClientHandle.h"
|
||||||
|
#include "Physics/Explodinator.h"
|
||||||
#include "Server.h"
|
#include "Server.h"
|
||||||
#include "Root.h"
|
#include "Root.h"
|
||||||
#include "IniFile.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)
|
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);
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
|
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "StatSerializer.h"
|
|
||||||
#include "../Statistics.h"
|
#include "../Statistics.h"
|
||||||
#include "NamespaceSerializer.h"
|
#include "NamespaceSerializer.h"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue