From 93adbdce9a769b42baeb70f9ead5c7c6a35834b5 Mon Sep 17 00:00:00 2001 From: Tiger Wang Date: Sat, 12 Sep 2020 19:57:44 +0100 Subject: Use tracing for explosions (#4845) * TNT: Implement tracing algorithm + Add intensity tracing * Fix iterating over all players to SendExplosion, even those not in range * Implemented TNT entity interaction * Fixed misaligned destruction tracing * Finalise TNT algorithm - Remove BlockArea and just use chunks Using SetBlock makes it so that we can update everything properly, and does appear to be faster. * BlockInfo learns about explosion attentuation * Rename Explodinator parameters * TNT: pull block destruction into common function Co-authored-by: Alexander Harkness --- src/BlockInfo.cpp | 121 +++++++++ src/BlockInfo.h | 4 + src/CMakeLists.txt | 4 +- src/Chunk.cpp | 1 + src/ChunkMap.cpp | 197 -------------- src/ChunkMap.h | 3 - src/ClientHandle.cpp | 24 +- src/ClientHandle.h | 2 +- src/Entities/Entity.h | 1 + src/Entities/TNTEntity.cpp | 13 +- src/Entities/TNTEntity.h | 10 +- src/Physics/CMakeLists.txt | 9 + src/Physics/Explodinator.cpp | 295 +++++++++++++++++++++ src/Physics/Explodinator.h | 20 ++ src/Protocol/Protocol.h | 2 +- src/Protocol/Protocol_1_8.cpp | 24 +- src/Protocol/Protocol_1_8.h | 2 +- .../RedstoneSimulatorChunkData.h | 3 +- src/Simulator/SandSimulator.cpp | 46 ++-- src/Simulator/SandSimulator.h | 4 +- src/World.cpp | 43 +-- src/WorldStorage/StatSerializer.cpp | 1 - 22 files changed, 530 insertions(+), 299 deletions(-) create mode 100644 src/Physics/CMakeLists.txt create mode 100644 src/Physics/Explodinator.cpp create mode 100644 src/Physics/Explodinator.h diff --git a/src/BlockInfo.cpp b/src/BlockInfo.cpp index f096ccc80..a967b9acf 100644 --- a/src/BlockInfo.cpp +++ b/src/BlockInfo.cpp @@ -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; diff --git a/src/BlockInfo.h b/src/BlockInfo.h index 9644eb208..5992db9d9 100644 --- a/src/BlockInfo.h +++ b/src/BlockInfo.h @@ -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 */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fd8d83e17..9f18fd9d0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/Chunk.cpp b/src/Chunk.cpp index eeeff3e8b..3f6a653a4 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -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 diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index 92cce9b55..71b8a7a24 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -1020,203 +1020,6 @@ bool cChunkMap::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback a -void cChunkMap::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, cVector3iArray & a_BlocksAffected) -{ - // Don't explode if outside of Y range (prevents the following test running into unallocated memory): - if (!cChunkDef::IsValidHeight(FloorC(a_BlockY))) - { - return; - } - - bool ShouldDestroyBlocks = true; - - // Don't explode if the explosion center is inside a liquid block: - if (IsBlockLiquid(m_World->GetBlock(FloorC(a_BlockX), FloorC(a_BlockY), FloorC(a_BlockZ)))) - { - ShouldDestroyBlocks = false; - } - - int ExplosionSizeInt = CeilC(a_ExplosionSize); - int ExplosionSizeSq = ExplosionSizeInt * ExplosionSizeInt; - - int bx = FloorC(a_BlockX); - int by = FloorC(a_BlockY); - int bz = FloorC(a_BlockZ); - - int MinY = std::max(FloorC(a_BlockY - ExplosionSizeInt), 0); - int MaxY = std::min(CeilC(a_BlockY + ExplosionSizeInt), cChunkDef::Height - 1); - - if (ShouldDestroyBlocks) - { - cBlockArea area; - a_BlocksAffected.reserve(8 * static_cast(ExplosionSizeInt * ExplosionSizeInt * ExplosionSizeInt)); - if (!area.Read(*m_World, bx - ExplosionSizeInt, static_cast(ceil(a_BlockX + ExplosionSizeInt)), MinY, MaxY, bz - ExplosionSizeInt, static_cast(ceil(a_BlockZ + ExplosionSizeInt)))) - { - return; - } - - for (int x = -ExplosionSizeInt; x < ExplosionSizeInt; x++) - { - for (int y = -ExplosionSizeInt; y < ExplosionSizeInt; y++) - { - if ((by + y >= cChunkDef::Height) || (by + y < 0)) - { - // Outside of the world - continue; - } - for (int z = -ExplosionSizeInt; z < ExplosionSizeInt; z++) - { - if ((x * x + y * y + z * z) > ExplosionSizeSq) - { - // Too far away - continue; - } - - BLOCKTYPE Block = area.GetBlockType(bx + x, by + y, bz + z); - switch (Block) - { - case E_BLOCK_TNT: - { - // Activate the TNT, with a random fuse between 10 to 30 game ticks - int FuseTime = GetRandomProvider().RandInt(10, 30); - m_World->SpawnPrimedTNT({a_BlockX + x + 0.5, a_BlockY + y + 0.5, a_BlockZ + z + 0.5}, FuseTime, 1, false); // Initial velocity, no fuse sound - area.SetBlockTypeMeta(bx + x, by + y, bz + z, E_BLOCK_AIR, 0); - a_BlocksAffected.push_back(Vector3i(bx + x, by + y, bz + z)); - break; - } - - case E_BLOCK_OBSIDIAN: - case E_BLOCK_BEACON: - case E_BLOCK_BEDROCK: - case E_BLOCK_BARRIER: - case E_BLOCK_WATER: - case E_BLOCK_LAVA: - { - // These blocks are not affected by explosions - break; - } - - case E_BLOCK_STATIONARY_WATER: - { - // Turn into simulated water: - area.SetBlockType(bx + x, by + y, bz + z, E_BLOCK_WATER); - break; - } - - case E_BLOCK_STATIONARY_LAVA: - { - // Turn into simulated lava: - area.SetBlockType(bx + x, by + y, bz + z, E_BLOCK_LAVA); - break; - } - - case E_BLOCK_AIR: - { - // No pickups for air - break; - } - - default: - { - auto & Random = GetRandomProvider(); - if (Random.RandBool(0.25)) // 25% chance of pickups - { - auto pickups = area.PickupsFromBlock({bx + x, by + y, bz + z}); - m_World->SpawnItemPickups(pickups, bx + x, by + y, bz + z); - } - else if ((m_World->GetTNTShrapnelLevel() > slNone) && Random.RandBool(0.20)) // 20% chance of flinging stuff around - { - // If the block is shrapnel-able, make a falling block entity out of it: - if ( - ((m_World->GetTNTShrapnelLevel() == slAll) && cBlockInfo::FullyOccupiesVoxel(Block)) || - ((m_World->GetTNTShrapnelLevel() == slGravityAffectedOnly) && ((Block == E_BLOCK_SAND) || (Block == E_BLOCK_GRAVEL))) - ) - { - m_World->SpawnFallingBlock(bx + x, by + y + 5, bz + z, Block, area.GetBlockMeta(bx + x, by + y, bz + z)); - } - } - - // Destroy any block entities - if (cBlockEntity::IsBlockEntityBlockType(Block)) - { - Vector3i BlockPos(bx + x, by + y, bz + z); - DoWithBlockEntityAt(BlockPos.x, BlockPos.y, BlockPos.z, [](cBlockEntity & a_BE) - { - a_BE.Destroy(); - return true; - } - ); - } - - area.SetBlockTypeMeta(bx + x, by + y, bz + z, E_BLOCK_AIR, 0); - a_BlocksAffected.push_back(Vector3i(bx + x, by + y, bz + z)); - break; - } - } // switch (BlockType) - } // for z - } // for y - } // for x - area.Write(*m_World, bx - ExplosionSizeInt, MinY, bz - ExplosionSizeInt); - } - - Vector3d ExplosionPos{ a_BlockX, a_BlockY, a_BlockZ }; - cBoundingBox bbTNT(ExplosionPos, 0.5, 1); - bbTNT.Expand(ExplosionSizeInt * 2, ExplosionSizeInt * 2, ExplosionSizeInt * 2); - - ForEachEntity([&](cEntity & a_Entity) - { - if (a_Entity.IsPickup() && (a_Entity.GetTicksAlive() < 20)) - { - // If pickup age is smaller than one second, it is invincible (so we don't kill pickups that were just spawned) - return false; - } - - Vector3d DistanceFromExplosion = a_Entity.GetPosition() - ExplosionPos; - - if (!a_Entity.IsTNT() && !a_Entity.IsFallingBlock()) // Don't apply damage to other TNT entities and falling blocks, they should be invincible - { - auto EntityBox = a_Entity.GetBoundingBox(); - if (!bbTNT.IsInside(EntityBox)) // If entity box is inside tnt box, not vice versa! - { - return false; - } - - // Ensure that the damage dealt is inversely proportional to the distance to the TNT centre - the closer a player is, the harder they are hit - a_Entity.TakeDamage(dtExplosion, nullptr, static_cast((1 / std::max(1.0, DistanceFromExplosion.Length())) * 8 * ExplosionSizeInt), 0); - } - - float EntityExposure = a_Entity.GetExplosionExposureRate(ExplosionPos, static_cast(a_ExplosionSize)); - - // Exposure reduced by armor - EntityExposure = EntityExposure * (1.0f - a_Entity.GetEnchantmentBlastKnockbackReduction()); - - auto Impact = std::pow(std::max(0.2, DistanceFromExplosion.Length()), -1); - Impact *= EntityExposure * ExplosionSizeInt * 6.0; - - if (Impact > 0.0) - { - DistanceFromExplosion.Normalize(); - DistanceFromExplosion *= Vector3d{Impact, 0.0, Impact}; - DistanceFromExplosion.y += 0.3 * Impact; - - a_Entity.SetSpeed(DistanceFromExplosion); - } - - return false; - } - ); - - // Wake up all simulators for the area, so that water and lava flows and sand falls into the blasted holes (FS #391): - m_World->GetSimulatorManager()->WakeUp(cCuboid( - {bx - ExplosionSizeInt - 1, MinY, bz - ExplosionSizeInt - 1}, - {bx + ExplosionSizeInt + 1, MaxY, bz + ExplosionSizeInt + 1} - )); -} - - - - - bool cChunkMap::DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback a_Callback) const { cCSLock Lock(m_CSChunks); diff --git a/src/ChunkMap.h b/src/ChunkMap.h index 795672135..4a7e952b5 100644 --- a/src/ChunkMap.h +++ b/src/ChunkMap.h @@ -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 diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 86e90aa8b..098a67afd 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -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(ParticleFormula * 125), 600); + + // Dark smoke particles: + SendParticleEffect("largesmoke", a_Position.x, a_Position.y, a_Position.z, 0, 0, 0, Spread, static_cast(ParticleCount)); + + Spread = ParticleFormula * 0.35f; + ParticleCount = std::min(static_cast(ParticleFormula * 550), 1800); + + // Light smoke particles: + SendParticleEffect("explode", a_Position.x, a_Position.y, a_Position.z, 0, 0, 0, Spread, static_cast(ParticleCount)); + + // Shockwave effect: + m_Protocol->SendExplosion(a_Position, a_Power); } diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 755e176dd..cf5139fef 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -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); diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 8bc941354..627c1ce71 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -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); diff --git a/src/Entities/TNTEntity.cpp b/src/Entities/TNTEntity.cpp index aaf3261b4..6aea6e228 100644 --- a/src/Entities/TNTEntity.cpp +++ b/src/Entities/TNTEntity.cpp @@ -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; diff --git a/src/Entities/TNTEntity.h b/src/Entities/TNTEntity.h index ef8b8ec3c..5b1c853bd 100644 --- a/src/Entities/TNTEntity.h +++ b/src/Entities/TNTEntity.h @@ -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 - - - - diff --git a/src/Physics/CMakeLists.txt b/src/Physics/CMakeLists.txt new file mode 100644 index 000000000..a4c9bcc65 --- /dev/null +++ b/src/Physics/CMakeLists.txt @@ -0,0 +1,9 @@ +target_sources( + ${CMAKE_PROJECT_NAME} PRIVATE + + Explodinator.cpp + # Lightning.cpp + + Explodinator.h + # Lightning.h +) diff --git a/src/Physics/Explodinator.cpp b/src/Physics/Explodinator.cpp new file mode 100644 index 000000000..3bfb78611 --- /dev/null +++ b/src/Physics/Explodinator.cpp @@ -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(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(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(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; + }); + } +} diff --git a/src/Physics/Explodinator.h b/src/Physics/Explodinator.h new file mode 100644 index 000000000..daf86456a --- /dev/null +++ b/src/Physics/Explodinator.h @@ -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); +} diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 2456ac976..222e6031e 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -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; diff --git a/src/Protocol/Protocol_1_8.cpp b/src/Protocol/Protocol_1_8.cpp index 39c20b6cf..e5ba82111 100644 --- a/src/Protocol/Protocol_1_8.cpp +++ b/src/Protocol/Protocol_1_8.cpp @@ -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(a_BlockX)); - Pkt.WriteBEFloat(static_cast(a_BlockY)); - Pkt.WriteBEFloat(static_cast(a_BlockZ)); - Pkt.WriteBEFloat(static_cast(a_Radius)); - Pkt.WriteBEUInt32(static_cast(a_BlocksAffected.size())); - for (cVector3iArray::const_iterator itr = a_BlocksAffected.begin(), end = a_BlocksAffected.end(); itr != end; ++itr) - { - Pkt.WriteBEInt8(static_cast(itr->x)); - Pkt.WriteBEInt8(static_cast(itr->y)); - Pkt.WriteBEInt8(static_cast(itr->z)); - } // for itr - a_BlockAffected[] - Pkt.WriteBEFloat(static_cast(a_PlayerMotion.x)); - Pkt.WriteBEFloat(static_cast(a_PlayerMotion.y)); - Pkt.WriteBEFloat(static_cast(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); } diff --git a/src/Protocol/Protocol_1_8.h b/src/Protocol/Protocol_1_8.h index f48ed4f9c..120676dd8 100644 --- a/src/Protocol/Protocol_1_8.h +++ b/src/Protocol/Protocol_1_8.h @@ -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; diff --git a/src/Simulator/IncrementalRedstoneSimulator/RedstoneSimulatorChunkData.h b/src/Simulator/IncrementalRedstoneSimulator/RedstoneSimulatorChunkData.h index 2d2010d57..02da327a1 100644 --- a/src/Simulator/IncrementalRedstoneSimulator/RedstoneSimulatorChunkData.h +++ b/src/Simulator/IncrementalRedstoneSimulator/RedstoneSimulatorChunkData.h @@ -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. */ diff --git a/src/Simulator/SandSimulator.cpp b/src/Simulator/SandSimulator.cpp index 95f514cc9..72b1a8378 100644 --- a/src/Simulator/SandSimulator.cpp +++ b/src/Simulator/SandSimulator.cpp @@ -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: diff --git a/src/Simulator/SandSimulator.h b/src/Simulator/SandSimulator.h index 113484158..e05b0de80 100644 --- a/src/Simulator/SandSimulator.h +++ b/src/Simulator/SandSimulator.h @@ -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 diff --git a/src/World.cpp b/src/World.cpp index f5b64f297..d99c9c607 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -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)) - { - return; - } - - // 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) + if (!cPluginManager::Get()->CallHookExploding(*this, a_ExplosionSize, a_CanCauseFire, a_BlockX, a_BlockY, a_BlockZ, a_Source, a_SourceData) && (a_ExplosionSize > 0)) { - cClientHandle * ch = Player->GetClientHandle(); - if (ch == nullptr) - { - continue; - } - - bool InRange = (Player->GetExplosionExposureRate(ExplosionPos, static_cast(a_ExplosionSize)) > 0); - auto Speed = InRange ? Player->GetSpeed() : Vector3d{}; - ch->SendExplosion({a_BlockX, a_BlockY, a_BlockZ}, static_cast(a_ExplosionSize), BlocksAffected, Speed); + // 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); } - - 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(Spread), static_cast(ParticleCount)); - - Spread = ParticleFormula * 0.35f; - ParticleCount = std::min((ParticleFormula * 550), 1800.0); - - BroadcastParticleEffect("explode", Position, Vector3f{}, static_cast(Spread), static_cast(ParticleCount)); - - cPluginManager::Get()->CallHookExploded(*this, a_ExplosionSize, a_CanCauseFire, a_BlockX, a_BlockY, a_BlockZ, a_Source, a_SourceData); } diff --git a/src/WorldStorage/StatSerializer.cpp b/src/WorldStorage/StatSerializer.cpp index 66be4fc7f..95652a8d7 100644 --- a/src/WorldStorage/StatSerializer.cpp +++ b/src/WorldStorage/StatSerializer.cpp @@ -3,7 +3,6 @@ #include "Globals.h" -#include "StatSerializer.h" #include "../Statistics.h" #include "NamespaceSerializer.h" -- cgit v1.2.3