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)
|
||||
{
|
||||
delete a_Handler;
|
||||
|
@ -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 */
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
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
|
||||
{
|
||||
cCSLock Lock(m_CSChunks);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
9
src/Physics/CMakeLists.txt
Normal file
9
src/Physics/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
||||
target_sources(
|
||||
${CMAKE_PROJECT_NAME} PRIVATE
|
||||
|
||||
Explodinator.cpp
|
||||
# Lightning.cpp
|
||||
|
||||
Explodinator.h
|
||||
# Lightning.h
|
||||
)
|
295
src/Physics/Explodinator.cpp
Normal file
295
src/Physics/Explodinator.cpp
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
20
src/Physics/Explodinator.h
Normal file
20
src/Physics/Explodinator.h
Normal 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);
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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. */
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
|
||||
#include "Globals.h"
|
||||
#include "StatSerializer.h"
|
||||
#include "../Statistics.h"
|
||||
#include "NamespaceSerializer.h"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user