Explosions: improve performance

This commit is contained in:
Tiger Wang 2022-07-10 23:56:25 +01:00
parent 9d0623a665
commit 284f54ed81
22 changed files with 380 additions and 553 deletions

View File

@ -32,7 +32,7 @@
#include "../Generating/ChunkDesc.h"
#include "../HTTP/UrlParser.h"
#include "../Item.h"
#include "../LineBlockTracer.h"
#include "../Physics/Tracers/LineBlockTracer.h"
#include "../Server.h"
#include "../Root.h"
#include "../StringCompression.h"
@ -2871,9 +2871,9 @@ tolua_lerror:
/** Provides interface between a Lua table of callbacks and the cBlockTracer::cCallbacks */
/** Provides interface between a Lua table of callbacks and the BlockTracerCallbacks */
class cLuaBlockTracerCallbacks :
public cBlockTracer::cCallbacks
public BlockTracerCallbacks
{
public:
cLuaBlockTracerCallbacks(cLuaState::cTableRefPtr && a_Callbacks):
@ -2962,7 +2962,7 @@ protected:
/** Provides interface between a Lua table of callbacks and the cBlockTracer::cCallbacks
/** Provides interface between a Lua table of callbacks and the BlockTracerCallbacks
This is the deprecated version of cLuaBlockTracerCallback, used when the plugin calls
the Trace function with number-based coords. */
class cLuaBlockTracerCallbacksOld :
@ -3093,7 +3093,7 @@ static int tolua_cLineBlockTracer_FirstSolidHitTrace(lua_State * tolua_S)
Vector3d hitCoords;
Vector3i hitBlockCoords;
eBlockFace hitBlockFace;
auto isHit = cLineBlockTracer::FirstSolidHitTrace(*world, Vector3d(startX, startY, startZ), Vector3d(endX, endY, endZ), hitCoords, hitBlockCoords, hitBlockFace);
auto isHit = LineBlockTracer::FirstSolidHitTrace(*world, Vector3d(startX, startY, startZ), Vector3d(endX, endY, endZ), hitCoords, hitBlockCoords, hitBlockFace);
L.Push(isHit);
if (!isHit)
{
@ -3129,7 +3129,7 @@ static int tolua_cLineBlockTracer_FirstSolidHitTrace(lua_State * tolua_S)
Vector3d hitCoords;
Vector3i hitBlockCoords;
eBlockFace hitBlockFace;
auto isHit = cLineBlockTracer::FirstSolidHitTrace(*world, start, end, hitCoords, hitBlockCoords, hitBlockFace);
auto isHit = LineBlockTracer::FirstSolidHitTrace(*world, start, end, hitCoords, hitBlockCoords, hitBlockFace);
L.Push(isHit);
if (!isHit)
{
@ -3197,9 +3197,9 @@ static int tolua_cLineBlockTracer_LineOfSightTrace(lua_State * tolua_S)
L.LogStackValues("Values on the stack");
return 0;
}
int lineOfSight = cLineBlockTracer::losAir | cLineBlockTracer::losWater;
int lineOfSight = LineBlockTracer::LineOfSight::AirWater;
L.GetStackValue(idx + 7, lineOfSight);
L.Push(cLineBlockTracer::LineOfSightTrace(*world, Vector3d(startX, startY, startZ), Vector3d(endX, endY, endZ), lineOfSight));
L.Push(LineBlockTracer::LineOfSightTrace(*world, Vector3d(startX, startY, startZ), Vector3d(endX, endY, endZ), static_cast<LineBlockTracer::LineOfSight>(lineOfSight)));
return 1;
}
@ -3225,9 +3225,9 @@ static int tolua_cLineBlockTracer_LineOfSightTrace(lua_State * tolua_S)
L.LogStackValues("Values on the stack");
return 0;
}
int lineOfSight = cLineBlockTracer::losAirWater;
int lineOfSight = LineBlockTracer::LineOfSight::AirWater;
L.GetStackValue(idx + 7, lineOfSight);
L.Push(cLineBlockTracer::LineOfSightTrace(*world, start, end, lineOfSight));
L.Push(LineBlockTracer::LineOfSightTrace(*world, start, end, static_cast<LineBlockTracer::LineOfSight>(lineOfSight)));
return 1;
}
@ -3292,7 +3292,7 @@ static int tolua_cLineBlockTracer_Trace(lua_State * tolua_S)
L.LogStackTrace();
// Trace:
cLuaBlockTracerCallbacksOld tracerCallbacks(std::move(callbacks));
bool res = cLineBlockTracer::Trace(*world, tracerCallbacks, start, end);
bool res = LineBlockTracer::Trace(*world, tracerCallbacks, start, end);
tolua_pushboolean(L, res ? 1 : 0);
return 1;
}
@ -3311,7 +3311,7 @@ static int tolua_cLineBlockTracer_Trace(lua_State * tolua_S)
}
// Trace:
cLuaBlockTracerCallbacks tracerCallbacks(std::move(callbacks));
bool res = cLineBlockTracer::Trace(*world, tracerCallbacks, start, end);
bool res = LineBlockTracer::Trace(*world, tracerCallbacks, start, end);
tolua_pushboolean(L, res ? 1 : 0);
return 1;
}
@ -4640,9 +4640,9 @@ void cManualBindings::Bind(lua_State * tolua_S)
tolua_function(tolua_S, "LineOfSightTrace", tolua_cLineBlockTracer_LineOfSightTrace);
tolua_function(tolua_S, "Trace", tolua_cLineBlockTracer_Trace);
tolua_constant(tolua_S, "losAir", cLineBlockTracer::losAir);
tolua_constant(tolua_S, "losWater", cLineBlockTracer::losWater);
tolua_constant(tolua_S, "losLava", cLineBlockTracer::losLava);
tolua_constant(tolua_S, "losAir", LineBlockTracer::LineOfSight::Air);
tolua_constant(tolua_S, "losWater", LineBlockTracer::LineOfSight::Water);
tolua_constant(tolua_S, "losLava", LineBlockTracer::LineOfSight::Lava);
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cLuaWindow");

View File

@ -785,7 +785,7 @@ void cChunk::MoveEntityToNewChunk(OwnedEntity a_Entity)
cChunk * Neighbor = GetNeighborChunk(a_Entity->GetChunkX() * cChunkDef::Width, a_Entity->GetChunkZ() * cChunkDef::Width);
if (Neighbor == nullptr)
{
LOGWARNING("%s: Entity at %p (%s, ID %d) moving to a non-existent chunk.",
LOGD("%s: Entity at %p (%s, ID %d) moving to a non-existent chunk",
__FUNCTION__, static_cast<void *>(a_Entity.get()), a_Entity->GetClass(), a_Entity->GetUniqueID()
);

View File

@ -45,7 +45,11 @@ void cEnderCrystal::SetShowBottom(bool a_ShowBottom)
void cEnderCrystal::SetBeamTarget(Vector3i a_BeamTarget)
{
m_BeamTarget = a_BeamTarget;
m_World->BroadcastEntityMetadata(*this);
if (m_DisplayBeam)
{
m_World->BroadcastEntityMetadata(*this);
}
}

View File

@ -11,7 +11,7 @@
#include "../Chunk.h"
#include "../Simulator/FluidSimulator.h"
#include "../Bindings/PluginManager.h"
#include "../LineBlockTracer.h"
#include "../Physics/Tracers/LineBlockTracer.h"
#include "../Items/ItemHandler.h"
#include "../FastRandom.h"
#include "../NetherPortalScanner.h"
@ -1157,7 +1157,7 @@ void cEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
Vector3i HitBlockCoords;
eBlockFace HitBlockFace;
Vector3d wantNextPos = NextPos + NextSpeed * DtSec.count();
auto isHit = cLineBlockTracer::FirstSolidHitTrace(*GetWorld(), NextPos, wantNextPos, HitCoords, HitBlockCoords, HitBlockFace);
auto isHit = LineBlockTracer::FirstSolidHitTrace(*GetWorld(), NextPos, wantNextPos, HitCoords, HitBlockCoords, HitBlockFace);
if (isHit)
{
// Set our position to where the block was hit:

View File

@ -116,12 +116,13 @@ private:
////////////////////////////////////////////////////////////////////////////////
// cPickup:
cPickup::cPickup(Vector3d a_Position, cItem && a_Item, Vector3d a_Speed, cTickTime a_CollectionDelay, cTickTime a_Lifetime) :
cPickup::cPickup(Vector3d a_Position, cItem && a_Item, Vector3d a_Speed, cTickTime a_CollectionDelay, cTickTime a_Lifetime, bool a_CanCombine) :
Super(etPickup, a_Position, 0.25f, 0.25f),
m_Item(std::move(a_Item)),
m_RemainingCollectionDelay(a_CollectionDelay),
m_RemainingLifetime(a_Lifetime),
m_IsCollected(false)
m_IsCollected(false),
m_IsCombinable(a_CanCombine)
{
SetGravity(-16.0f);
SetAirDrag(0.02f);

View File

@ -26,7 +26,7 @@ public: // tolua_export
CLASS_PROTODEF(cPickup)
cPickup(Vector3d a_Position, cItem && a_Item, Vector3d a_Speed, cTickTime a_CollectionDelay, cTickTime a_Lifetime);
cPickup(Vector3d a_Position, cItem && a_Item, Vector3d a_Speed, cTickTime a_CollectionDelay, cTickTime a_Lifetime, bool a_CanCombine);
cItem & GetItem(void) {return m_Item; } // tolua_export
const cItem & GetItem(void) const {return m_Item; }

View File

@ -9,7 +9,7 @@
#include "ProjectileEntity.h"
#include "../BlockInfo.h"
#include "../ClientHandle.h"
#include "../LineBlockTracer.h"
#include "../Physics/Tracers/LineBlockTracer.h"
#include "../BoundingBox.h"
#include "../ChunkMap.h"
#include "../Chunk.h"
@ -34,7 +34,7 @@
// cProjectileTracerCallback:
class cProjectileTracerCallback :
public cBlockTracer::cCallbacks
public BlockTracerCallbacks
{
public:
cProjectileTracerCallback(cProjectileEntity * a_Projectile) :
@ -411,7 +411,7 @@ void cProjectileEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a
// Trace the tick's worth of movement as a line:
cProjectileTracerCallback TracerCallback(this);
if (!cLineBlockTracer::Trace(*m_World, TracerCallback, Pos, NextPos))
if (!LineBlockTracer::Trace(*m_World, TracerCallback, Pos, NextPos))
{
// Something has been hit, abort all other processing
return;

View File

@ -2,7 +2,7 @@
#pragma once
#include "../Entities/Boat.h"
#include "../LineBlockTracer.h"
#include "../Physics/Tracers/LineBlockTracer.h"
@ -38,7 +38,7 @@ public:
// Find the actual placement position by tracing line of sight until non-air block:
class cCallbacks:
public cBlockTracer::cCallbacks
public BlockTracerCallbacks
{
public:
Vector3d m_Pos;
@ -62,7 +62,7 @@ public:
} Callbacks;
auto Start = a_Player->GetEyePosition() + a_Player->GetLookVector();
auto End = a_Player->GetEyePosition() + a_Player->GetLookVector() * 5;
cLineBlockTracer::Trace(*a_World, Callbacks, Start, End);
LineBlockTracer::Trace(*a_World, Callbacks, Start, End);
if (!Callbacks.m_HasFound)
{
return false;

View File

@ -28,7 +28,7 @@ public:
bool GetBlockFromTrace(cWorld * a_World, cPlayer * a_Player, Vector3i & a_BlockPos) const
{
class cCallbacks:
public cBlockTracer::cCallbacks
public BlockTracerCallbacks
{
public:
Vector3i m_Pos;
@ -57,7 +57,7 @@ public:
} Callbacks;
auto Start = a_Player->GetEyePosition() + a_Player->GetLookVector();
auto End = a_Player->GetEyePosition() + a_Player->GetLookVector() * 5;
cLineBlockTracer::Trace(*a_World, Callbacks, Start, End);
LineBlockTracer::Trace(*a_World, Callbacks, Start, End);
if (!Callbacks.m_HasHitFluid)
{
return false;

View File

@ -6,7 +6,7 @@
#include "../World.h"
#include "../Simulator/FluidSimulator.h"
#include "../Blocks/BlockHandler.h"
#include "../LineBlockTracer.h"
#include "../Physics/Tracers/LineBlockTracer.h"
#include "../Blocks/ChunkInterface.h"
@ -185,7 +185,7 @@ public:
bool GetBlockFromTrace(cWorld * a_World, cPlayer * a_Player, Vector3i & a_BlockPos) const
{
class cCallbacks :
public cBlockTracer::cCallbacks
public BlockTracerCallbacks
{
public:
Vector3i m_Pos;
@ -213,11 +213,10 @@ public:
}
} Callbacks;
cLineBlockTracer Tracer(*a_World, Callbacks);
Vector3d Start(a_Player->GetEyePosition() + a_Player->GetLookVector());
Vector3d End(a_Player->GetEyePosition() + a_Player->GetLookVector() * 5);
Tracer.Trace(Start, End);
LineBlockTracer::Trace(*a_World, Callbacks, Start, End);
if (!Callbacks.m_HasHitFluid)
{
@ -236,7 +235,7 @@ public:
bool GetPlacementCoordsFromTrace(cWorld * a_World, cPlayer * a_Player, Vector3i & a_BlockPos, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta, eBlockFace & a_BlockFace) const
{
class cCallbacks :
public cBlockTracer::cCallbacks
public BlockTracerCallbacks
{
public:
Vector3i m_Pos;
@ -262,14 +261,13 @@ public:
}
} Callbacks;
cLineBlockTracer Tracer(*a_World, Callbacks);
Vector3d Start(a_Player->GetEyePosition());
Vector3d End(a_Player->GetEyePosition() + a_Player->GetLookVector() * 5);
// cLineBlockTracer::Trace() returns true when whole line was traversed. By returning true from the callback when we hit something,
// LineBlockTracer::Trace() returns true when whole line was traversed. By returning true from the callback when we hit something,
// we ensure that this never happens if liquid could be placed
// Use this to judge whether the position is valid
if (!Tracer.Trace(Start, End))
if (!LineBlockTracer::Trace(*a_World, Callbacks, Start, End))
{
a_BlockPos = Callbacks.m_Pos;
a_BlockType = Callbacks.m_ReplacedBlockType;

View File

@ -3,7 +3,7 @@
#include "ItemHandler.h"
#include "../Entities/Player.h"
#include "../LineBlockTracer.h"
#include "../Physics/Tracers/LineBlockTracer.h"
@ -89,7 +89,7 @@ public:
}
class cCallbacks:
public cBlockTracer::cCallbacks
public BlockTracerCallbacks
{
public:
@ -118,7 +118,7 @@ public:
const auto EyePosition = a_Player->GetEyePosition();
const auto End = EyePosition + a_Player->GetLookVector() * 5;
if (cLineBlockTracer::Trace(*a_Player->GetWorld(), Callbacks, EyePosition, End))
if (LineBlockTracer::Trace(*a_Player->GetWorld(), Callbacks, EyePosition, End))
{
// The line traced to completion; no suitable water was found:
return false;

View File

@ -5,7 +5,7 @@
#include "../World.h"
#include "../Entities/Player.h"
#include "../LineBlockTracer.h"
#include "../Physics/Tracers/LineBlockTracer.h"
@ -68,11 +68,11 @@ void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
if (
(GetTarget() != nullptr) &&
TargetIsInRange() &&
cLineBlockTracer::LineOfSightTrace(
LineBlockTracer::LineOfSightTrace(
*GetWorld(),
GetPosition().addedY(GetHeight()),
GetTarget()->GetPosition().addedY(GetTarget()->GetHeight()),
(IsNetherNative() ? cLineBlockTracer::losAirWaterLava : cLineBlockTracer::losAirWater)
(IsNetherNative() ? LineBlockTracer::LineOfSight::AirWaterLava : LineBlockTracer::LineOfSight::AirWater)
) &&
(GetHealth() > 0.0)
)

View File

@ -4,7 +4,7 @@
#include "Chunk.h"
#include "Enderman.h"
#include "../Entities/Player.h"
#include "../LineBlockTracer.h"
#include "../Physics/Tracers/LineBlockTracer.h"
@ -55,7 +55,7 @@ public:
}
// TODO: Check if endermen are angered through water in Vanilla
if (!cLineBlockTracer::LineOfSightTrace(*a_Player.GetWorld(), m_EndermanHeadPosition, PlayerHeadPosition, cLineBlockTracer::losAirWater))
if (!LineBlockTracer::LineOfSightTrace(*a_Player.GetWorld(), m_EndermanHeadPosition, PlayerHeadPosition, LineBlockTracer::LineOfSight::AirWater))
{
// No direct line of sight
return false;

View File

@ -2,7 +2,7 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "IncludeAllMonsters.h"
#include "LineBlockTracer.h"
#include "Physics/Tracers/LineBlockTracer.h"
#include "../BlockInfo.h"
#include "../Root.h"
#include "../Server.h"
@ -754,7 +754,7 @@ void cMonster::CheckEventSeePlayer(cChunk & a_Chunk)
// TODO: Currently all mobs see through lava, but only Nether-native mobs should be able to.
if (
(TargetDistance < ClosestDistance) &&
cLineBlockTracer::LineOfSightTrace(*GetWorld(), MyHeadPosition, TargetHeadPosition, cLineBlockTracer::losAirWaterLava)
LineBlockTracer::LineOfSightTrace(*GetWorld(), MyHeadPosition, TargetHeadPosition, LineBlockTracer::LineOfSight::AirWaterLava)
)
{
TargetPlayer = &a_Player;
@ -800,7 +800,7 @@ void cMonster::CheckEventLostPlayer(const std::chrono::milliseconds a_Dt)
const auto MyHeadPosition = GetPosition().addedY(GetHeight());
const auto TargetHeadPosition = Target->GetPosition().addedY(Target->GetHeight());
if (!cLineBlockTracer::LineOfSightTrace(*GetWorld(), MyHeadPosition, TargetHeadPosition, cLineBlockTracer::losAirWaterLava))
if (!LineBlockTracer::LineOfSightTrace(*GetWorld(), MyHeadPosition, TargetHeadPosition, LineBlockTracer::LineOfSight::AirWaterLava))
{
if ((m_LoseSightAbandonTargetTimer += a_Dt) > std::chrono::seconds(4))
{

View File

@ -3,7 +3,6 @@
#include "Zombie.h"
#include "../World.h"
#include "../LineBlockTracer.h"

View File

@ -3,7 +3,6 @@
#include "ZombieVillager.h"
#include "../World.h"
#include "../LineBlockTracer.h"
#include "../Entities/Player.h"

View File

@ -7,7 +7,7 @@
#include "Chunk.h"
#include "ClientHandle.h"
#include "Entities/FallingBlock.h"
#include "LineBlockTracer.h"
#include "Physics/Tracers/LineBlockTracer.h"
#include "Simulator/SandSimulator.h"
@ -22,27 +22,10 @@ namespace Explodinator
static const auto TraceCubeSideLength = 16U;
static const auto BoundingBoxStepUnit = 0.5;
/** 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
};
}
/** Returns how much of an explosion Destruction Lazor's (tm) intensity the given block attenuates.
Values are scaled as 0.3 * (0.3 + Wiki) since some compilers miss the constant folding optimisation.
Wiki values are https://minecraft.gamepedia.com/Explosion#Blast_resistance as of 2021-02-06. */
static float GetExplosionAbsorption(const BLOCKTYPE Block)
static constexpr float GetExplosionAbsorption(const BLOCKTYPE Block)
{
switch (Block)
{
@ -162,7 +145,7 @@ namespace Explodinator
/** Calculates the approximate percentage of an Entity's bounding box that is exposed to an explosion centred at Position. */
static float CalculateEntityExposure(const cChunk & a_Chunk, const cEntity & a_Entity, const Vector3f a_Position, const int a_SquareRadius)
{
class LineOfSightCallbacks final : public cLineBlockTracer::cCallbacks
class LineOfSightCallbacks final : public BlockTracerCallbacks
{
virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override
{
@ -173,7 +156,6 @@ namespace Explodinator
const Vector3d Position = a_Position;
unsigned Unobstructed = 0, Total = 0;
const auto Box = a_Entity.GetBoundingBox();
cLineBlockTracer Tracer(*a_Chunk.GetWorld(), Callback);
for (double X = Box.GetMinX(); X < Box.GetMaxX(); X += BoundingBoxStepUnit)
{
@ -182,6 +164,7 @@ namespace Explodinator
for (double Z = Box.GetMinZ(); Z < Box.GetMaxZ(); Z += BoundingBoxStepUnit)
{
const Vector3d Destination{X, Y, Z};
if ((Destination - Position).SqrLength() > a_SquareRadius)
{
// Don't bother with points outside our designated area-of-effect
@ -189,7 +172,7 @@ namespace Explodinator
continue;
}
if (Tracer.Trace(a_Position, Destination))
if (LineBlockTracer::Trace(a_Chunk, Callback, Position, Destination))
{
Unobstructed++;
}
@ -257,120 +240,66 @@ namespace Explodinator
}
/** Sets the block at the given position, updating surroundings. */
static void SetBlock(cWorld & a_World, cChunk & a_Chunk, const Vector3i a_AbsolutePosition, const Vector3i a_RelativePosition, const BLOCKTYPE a_DestroyedBlock, const BLOCKTYPE a_NewBlock, const cEntity * const a_ExplodingEntity)
static void SetBlock(cChunk & a_Chunk, const Vector3i a_AbsolutePosition, const Vector3i a_RelativePosition, const BLOCKTYPE a_CurrentBlock, const NIBBLETYPE a_CurrentMeta, const BLOCKTYPE a_NewBlock, const cEntity * const a_ExplodingEntity)
{
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());
cBlockHandler::For(a_DestroyedBlock).OnBroken(Interface, a_World, a_AbsolutePosition, a_DestroyedBlock, DestroyedMeta, a_ExplodingEntity);
auto & World = *a_Chunk.GetWorld();
cChunkInterface Interface(World.GetChunkMap());
cBlockHandler::For(a_CurrentBlock).OnBroken(Interface, World, a_AbsolutePosition, a_CurrentBlock, a_CurrentMeta, a_ExplodingEntity);
}
/** Work out what should happen when an explosion destroys the given block.
Tasks include lighting TNT, dropping pickups, setting fire and flinging shrapnel according to Minecraft rules.
OK, _mostly_ Minecraft rules. */
static void DestroyBlock(cChunk & a_Chunk, const Vector3i a_Position, const int a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity)
static void DestroyBlock(MTRand & a_Random, cChunk & a_Chunk, const Vector3i a_AbsolutePosition, const Vector3i a_RelativePosition, const BLOCKTYPE a_CurrentBlock, const NIBBLETYPE a_CurrentMeta, const cBoundingBox a_ExplosionBounds, const int a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity)
{
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) // If the block is TNT we should set it off
if (a_CurrentBlock == E_BLOCK_TNT) // If the block is TNT we should set it off
{
// Random fuse between 10 to 30 game ticks.
const int FuseTime = Random.RandInt(10, 30);
const int FuseTime = a_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);
World.SpawnPrimedTNT(Vector3d(0.5, 0, 0.5) + a_AbsolutePosition, FuseTime, 1, false);
}
else if ((a_ExplodingEntity != nullptr) && (a_ExplodingEntity->IsTNT() || BlockAlwaysDrops(DestroyedBlock) || Random.RandBool(1.f / a_Power))) // For TNT explosions, destroying a block that always drops, or if RandBool, drop pickups
else if ((a_ExplodingEntity != nullptr) && (a_ExplodingEntity->IsTNT() || BlockAlwaysDrops(a_CurrentBlock) || a_Random.RandBool(1.f / a_Power))) // For TNT explosions, destroying a block that always drops, or if RandBool, drop pickups
{
const auto DestroyedMeta = a_Chunk.GetMeta(a_Position);
a_Chunk.GetWorld()->SpawnItemPickups(cBlockHandler::For(DestroyedBlock).ConvertToPickups(DestroyedMeta), Absolute);
for (auto & Item : cBlockHandler::For(a_CurrentBlock).ConvertToPickups(a_CurrentMeta))
{
World.SpawnItemPickup(Vector3d(0.5, 0, 0.5) + a_AbsolutePosition, std::move(Item), Vector3d(), a_ExplosionBounds);
}
}
else if (a_Fiery && Random.RandBool(1 / 3.0)) // 33% chance of starting fires if it can start fires
else if (a_Fiery && a_Random.RandBool(1 / 3.0)) // 33% chance of starting fires if it can start fires
{
const auto Below = a_Position.addedY(-1);
const auto Below = a_AbsolutePosition.addedY(-1);
if ((Below.y >= 0) && cBlockInfo::FullyOccupiesVoxel(a_Chunk.GetBlock(Below)))
{
// Start a fire:
SetBlock(World, a_Chunk, Absolute, a_Position, DestroyedBlock, E_BLOCK_FIRE, a_ExplodingEntity);
SetBlock(a_Chunk, a_AbsolutePosition, a_RelativePosition, a_CurrentBlock, a_CurrentMeta, E_BLOCK_FIRE, a_ExplodingEntity);
return;
}
}
else if (const auto Shrapnel = World.GetTNTShrapnelLevel(); (Shrapnel > slNone) && Random.RandBool(0)) // Currently 0% chance of flinging stuff around
else if (const auto Shrapnel = World.GetTNTShrapnelLevel(); (Shrapnel > slNone) && a_Random.RandBool(0)) // Currently 0% chance of flinging stuff around
{
// If the block is shrapnel-able, make a falling block entity out of it:
if (
((Shrapnel == slAll) && cBlockInfo::FullyOccupiesVoxel(DestroyedBlock)) ||
((Shrapnel == slGravityAffectedOnly) && cSandSimulator::IsAllowedBlock(DestroyedBlock))
((Shrapnel == slAll) && cBlockInfo::FullyOccupiesVoxel(a_CurrentBlock)) ||
((Shrapnel == slGravityAffectedOnly) && cSandSimulator::IsAllowedBlock(a_CurrentBlock))
)
{
const auto DestroyedMeta = a_Chunk.GetMeta(a_Position);
auto FallingBlock = std::make_unique<cFallingBlock>(Vector3d(0.5, 0, 0.5) + Absolute, DestroyedBlock, DestroyedMeta);
auto FallingBlock = std::make_unique<cFallingBlock>(Vector3d(0.5, 0, 0.5) + a_AbsolutePosition, a_CurrentBlock, a_CurrentMeta);
// TODO: correct velocity FallingBlock->SetSpeedY(40);
FallingBlock->Initialize(std::move(FallingBlock), World);
}
}
SetBlock(World, a_Chunk, Absolute, a_Position, DestroyedBlock, E_BLOCK_AIR, a_ExplodingEntity);
}
/** Traces the path taken by one Explosion Lazor (tm) with given direction and intensity, that will destroy blocks until it is exhausted. */
static void DestructionTrace(cChunk * a_Chunk, Vector3f a_Origin, const Vector3f a_Direction, const int a_Power, const bool a_Fiery, float a_Intensity, const cEntity * const a_ExplodingEntity)
{
// The current position the ray is at.
auto Checkpoint = a_Origin;
// The displacement that the ray in one iteration step should travel.
const auto Step = a_Direction.NormalizeCopy() * StepUnit;
// Loop until intensity runs out:
while (a_Intensity > 0)
{
auto Position = Checkpoint.Floor();
if (!cChunkDef::IsValidHeight(Position))
{
break;
}
const auto Neighbour = a_Chunk->GetRelNeighborChunkAdjustCoords(Position);
if ((Neighbour == nullptr) || !Neighbour->IsValid())
{
break;
}
a_Intensity -= GetExplosionAbsorption(Neighbour->GetBlock(Position));
if (a_Intensity <= 0)
{
// The ray is exhausted:
break;
}
DestroyBlock(*Neighbour, Position, a_Power, a_Fiery, a_ExplodingEntity);
// 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;
}
SetBlock(a_Chunk, a_AbsolutePosition, a_RelativePosition, a_CurrentBlock, a_CurrentMeta, E_BLOCK_AIR, a_ExplodingEntity);
}
/** Returns a random intensity for an Explosion Lazor (tm) as a function of the explosion's power. */
@ -379,12 +308,78 @@ namespace Explodinator
return a_Power * (0.7f + a_Random.RandReal(0.6f));
}
/** Traces the path taken by one Explosion Lazor (tm) with given direction and random intensity, that will destroy blocks until it is exhausted. */
static void DestructionTrace(MTRand & a_Random, cChunk * a_Chunk, Vector3f a_Origin, const Vector3f a_Direction, const cBoundingBox a_ExplosionBounds, const int a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity)
{
// The current position the ray is at.
auto Checkpoint = a_Origin;
auto Position = Checkpoint.Floor();
auto Intensity = RandomIntensity(a_Random, a_Power);
// The displacement that the ray in one iteration step should travel.
const auto Step = a_Direction.NormalizeCopy() * StepUnit;
// Loop until intensity runs out:
while (Intensity > 0)
{
if (!cChunkDef::IsValidHeight(Position))
{
break;
}
Vector3i RelativePosition;
if (!a_Chunk->GetChunkAndRelByAbsolute(Position, &a_Chunk, RelativePosition))
{
break;
}
BLOCKTYPE CurrentBlock;
NIBBLETYPE CurrentMeta;
a_Chunk->GetBlockTypeMeta(RelativePosition, CurrentBlock, CurrentMeta);
Intensity -= GetExplosionAbsorption(CurrentBlock);
if (Intensity <= 0)
{
// The ray is exhausted:
break;
}
if (CurrentBlock != E_BLOCK_AIR)
{
DestroyBlock(a_Random, *a_Chunk, Position, RelativePosition, CurrentBlock, CurrentMeta, a_ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
}
// Increment the simulation, weaken the ray:
Checkpoint += Step;
Intensity -= StepAttenuation;
for (int i = 0; i != 2; i++)
{
const auto PreviousPosition = Position;
Position = Checkpoint.Floor();
if (Position != PreviousPosition)
{
break;
}
Checkpoint += Step;
Intensity -= StepAttenuation + GetExplosionAbsorption(E_BLOCK_AIR);
}
}
}
/** Sends out Explosion Lazors (tm) originating from the given position that destroy blocks. */
static void DamageBlocks(cChunk & a_Chunk, const Vector3f a_Position, const int a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity)
{
// Oh boy... Better hope you have a hot cache, 'cos this little manoeuvre's gonna cost us 1352 raytraces in one tick...
const int HalfSide = TraceCubeSideLength / 2;
auto & Random = GetRandomProvider();
const int HalfSide = TraceCubeSideLength / 2;
const cBoundingBox ExplosionBounds(a_Position, TraceCubeSideLength);
// The following loops implement the tracing algorithm described in http://minecraft.gamepedia.com/Explosion
@ -394,8 +389,8 @@ namespace Explodinator
{
for (float OffsetZ = -HalfSide; OffsetZ < HalfSide; OffsetZ++)
{
DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, +HalfSide, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity);
DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, -HalfSide, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity);
DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(OffsetX, +HalfSide, OffsetZ), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(OffsetX, -HalfSide, OffsetZ), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
}
}
@ -404,8 +399,8 @@ namespace Explodinator
{
for (float OffsetY = -HalfSide + 1; OffsetY < HalfSide - 1; OffsetY++)
{
DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, +HalfSide), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity);
DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, -HalfSide), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity);
DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, +HalfSide), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, -HalfSide), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
}
}
@ -414,8 +409,8 @@ namespace Explodinator
{
for (float OffsetY = -HalfSide + 1; OffsetY < HalfSide - 1; OffsetY++)
{
DestructionTrace(&a_Chunk, a_Position, Vector3f(+HalfSide, OffsetY, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity);
DestructionTrace(&a_Chunk, a_Position, Vector3f(-HalfSide, OffsetY, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity);
DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(+HalfSide, OffsetY, OffsetZ), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(-HalfSide, OffsetY, OffsetZ), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
}
}
}
@ -435,7 +430,7 @@ namespace Explodinator
{
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, a_ExplodingEntity);
DamageBlocks(a_Chunk, a_Position, a_Power, a_Fiery, a_ExplodingEntity);
return false;
});

View File

@ -1,7 +1,7 @@
// BlockTracer.h
// Declares the classes common for all blocktracers
// Declares the callback common to all blocktracers
@ -9,118 +9,61 @@
#pragma once
#include "Defines.h"
#include "ChunkDef.h"
// fwd: World.h
class cWorld;
class cBlockTracer abstract
/** The callback class is used to notify the caller of individual events that are being traced. */
class BlockTracerCallbacks abstract
{
public:
/** The callback class is used to notify the caller of individual events that are being traced.
*/
class cCallbacks abstract
{
public:
// Force a virtual destructor in descendants:
virtual ~cCallbacks() {}
/** Called on each block encountered along the path, including the first block (path start)
When this callback returns true, the tracing is aborted.
*/
virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) = 0;
/** Called on each block encountered along the path, including the first block (path start), if chunk data is not loaded
When this callback returns true, the tracing is aborted.
*/
virtual bool OnNextBlockNoData(Vector3i a_BlockPos, eBlockFace a_EntryFace)
{
UNUSED(a_BlockPos);
UNUSED(a_EntryFace);
return false;
}
/** Called when the path goes out of world, either below (a_BlockPos.y < 0) or above (a_BlockPos.y >= cChunkDef::Height)
The coords specify the exact point at which the path exited the world.
If this callback returns true, the tracing is aborted.
Note that some paths can go out of the world and come back again (parabola),
in such a case this callback is followed by OnIntoWorld() and further OnNextBlock() calls
*/
virtual bool OnOutOfWorld(Vector3d a_BlockPos)
{
UNUSED(a_BlockPos);
return false;
}
/** Called when the path goes into the world, from either below (a_BlockPos.y < 0) or above (a_BlockPos.y >= cChunkDef::Height)
The coords specify the exact point at which the path entered the world.
If this callback returns true, the tracing is aborted.
Note that some paths can go out of the world and come back again (parabola),
in such a case this callback is followed by further OnNextBlock() calls
*/
virtual bool OnIntoWorld(Vector3d a_BlockPos)
{
UNUSED(a_BlockPos);
return false;
}
/** Called when the path is sure not to hit any more blocks.
Note that for some shapes this might never happen (line with constant Y)
*/
virtual void OnNoMoreHits(void) {}
/** Called when the block tracing walks into a chunk that is not allocated.
This usually means that the tracing is aborted.
*/
virtual void OnNoChunk(void) {}
} ;
/** Creates the BlockTracer parent with the specified callbacks */
cBlockTracer(cWorld & a_World, cCallbacks & a_Callbacks) :
m_World(&a_World),
m_Callbacks(&a_Callbacks)
// Force a virtual destructor in descendants:
virtual ~BlockTracerCallbacks() {}
/** Called on each block encountered along the path, including the first block (path start)
When this callback returns true, the tracing is aborted. */
virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) = 0;
/** Called on each block encountered along the path, including the first block (path start), if chunk data is not loaded
When this callback returns true, the tracing is aborted. */
virtual bool OnNextBlockNoData(Vector3i a_BlockPos, eBlockFace a_EntryFace)
{
UNUSED(a_BlockPos);
UNUSED(a_EntryFace);
return false;
}
/** Sets new world, returns the old one. Note that both need to be valid */
cWorld & SetWorld(cWorld & a_World)
/** Called when the path goes out of world, either below (a_BlockPos.y < 0) or above (a_BlockPos.y >= cChunkDef::Height)
The coords specify the exact point at which the path exited the world.
If this callback returns true, the tracing is aborted.
Note that some paths can go out of the world and come back again (parabola),
in such a case this callback is followed by OnIntoWorld() and further OnNextBlock() calls. */
virtual bool OnOutOfWorld(Vector3d a_BlockPos)
{
cWorld & Old = *m_World;
m_World = &a_World;
return Old;
UNUSED(a_BlockPos);
return false;
}
/** Sets new callbacks, returns the old ones. Note that both need to be valid */
cCallbacks & SetCallbacks(cCallbacks & a_NewCallbacks)
/** Called when the path goes into the world, from either below (a_BlockPos.y < 0) or above (a_BlockPos.y >= cChunkDef::Height)
The coords specify the exact point at which the path entered the world.
If this callback returns true, the tracing is aborted.
Note that some paths can go out of the world and come back again (parabola),
in such a case this callback is followed by further OnNextBlock() calls. */
virtual bool OnIntoWorld(Vector3d a_BlockPos)
{
cCallbacks & Old = *m_Callbacks;
m_Callbacks = &a_NewCallbacks;
return Old;
UNUSED(a_BlockPos);
return false;
}
protected:
/** The world upon which to operate */
cWorld * m_World;
/** Called when the path is sure not to hit any more blocks.
Note that for some shapes this might never happen (line with constant Y). */
virtual void OnNoMoreHits(void) {}
/** The callback to use for reporting */
cCallbacks * m_Callbacks;
/** Called when the block tracing walks into a chunk that is not allocated.
This usually means that the tracing is aborted. */
virtual void OnNoChunk(void) {}
} ;

View File

@ -14,35 +14,186 @@
cLineBlockTracer::cLineBlockTracer(cWorld & a_World, cCallbacks & a_Callbacks) :
Super(a_World, a_Callbacks),
m_Start(),
m_End(),
m_Diff(),
m_Dir(),
m_Current(),
m_CurrentFace(BLOCK_FACE_NONE)
static Vector3d CalcXZIntersection(double a_Y, const Vector3d a_Start, const Vector3d a_End)
{
const double Ratio = (a_Start.y - a_Y) / (a_Start.y - a_End.y);
return { a_Start.x + (a_End.x - a_Start.x) * Ratio, a_Y, a_Start.z + (a_End.z - a_Start.z) * Ratio };
}
bool cLineBlockTracer::Trace(cWorld & a_World, cBlockTracer::cCallbacks & a_Callbacks, const Vector3d a_Start, const Vector3d a_End)
static Vector3d FixStartAboveWorld(const Vector3d a_Start, const Vector3d a_End)
{
cLineBlockTracer Tracer(a_World, a_Callbacks);
return Tracer.Trace(a_Start, a_End);
// We must set the start Y to less than cChunkDef::Height so that it is considered inside the world later on.
// Therefore we use an EPS-offset from the height, as small as reasonably possible.
const double Height = static_cast<double>(cChunkDef::Height) - 0.00001;
return CalcXZIntersection(Height, a_Start, a_End);
}
bool cLineBlockTracer::LineOfSightTrace(cWorld & a_World, const Vector3d & a_Start, const Vector3d & a_End, int a_Sight)
static Vector3d FixStartBelowWorld(const Vector3d a_Start, const Vector3d a_End)
{
return CalcXZIntersection(0, a_Start, a_End);
}
static eBlockFace MoveToNextBlock(Vector3i & a_CurrentBlock, const Vector3i a_Adjustment, const Vector3d a_Direction, const Vector3d a_Start, const Vector3i a_StepOffset, const eBlockFace a_StepXFace, const eBlockFace a_StepYFace, const eBlockFace a_StepZFace)
{
const auto Coeff = (Vector3d(a_CurrentBlock + a_Adjustment) - a_Start) / a_Direction;
if (Coeff.x <= Coeff.y)
{
if (Coeff.x <= Coeff.z)
{
a_CurrentBlock.x += a_StepOffset.x;
return a_StepXFace;
}
}
else if (Coeff.y <= Coeff.z)
{
a_CurrentBlock.y += a_StepOffset.y;
return a_StepYFace;
}
a_CurrentBlock.z += a_StepOffset.z;
return a_StepZFace;
}
bool LineBlockTracer::Trace(const cChunk & a_Chunk, BlockTracerCallbacks & a_Callbacks, Vector3d a_Start, Vector3d a_End)
{
// Clamp the start coords into the world by advancing them along the line:
if (a_Start.y < 0)
{
if (a_End.y < 0)
{
// Nothing to trace:
a_Callbacks.OnNoMoreHits();
return true;
}
a_Start = FixStartBelowWorld(a_Start, a_End);
a_Callbacks.OnIntoWorld(a_Start);
}
else if (a_Start.y >= cChunkDef::Height)
{
if (a_End.y >= cChunkDef::Height)
{
a_Callbacks.OnNoMoreHits();
return true;
}
a_Start = FixStartAboveWorld(a_Start, a_End);
a_Callbacks.OnIntoWorld(a_Start);
}
const auto EndPosition = a_End.Floor();
const auto Direction = a_End - a_Start;
const bool XPositive = a_Start.x <= a_End.x;
const bool YPositive = a_Start.y <= a_End.y;
const bool ZPositive = a_Start.z <= a_End.z;
const auto StepXFace = XPositive ? BLOCK_FACE_XM : BLOCK_FACE_XP;
const auto StepYFace = YPositive ? BLOCK_FACE_YM : BLOCK_FACE_YP;
const auto StepZFace = ZPositive ? BLOCK_FACE_ZM : BLOCK_FACE_ZP;
const Vector3i Adjustment(XPositive ? 1 : 0, YPositive ? 1 : 0, ZPositive ? 1 : 0);
const Vector3i StepOffset(XPositive ? 1 : -1, YPositive ? 1 : -1, ZPositive ? 1 : -1);
auto Position = a_Start.Floor();
auto Chunk = const_cast<cChunk &>(a_Chunk).GetNeighborChunk(Position.x, Position.z);
// We should always start in a valid chunk:
ASSERT(Chunk != nullptr);
// This is guaranteed by FixStartAboveWorld() / FixStartBelowWorld():
ASSERT(cChunkDef::IsValidHeight(Position));
// This is the actual line tracing loop.
for (;;)
{
if (Position == EndPosition)
{
// We've reached the end
a_Callbacks.OnNoMoreHits();
return true;
}
// The face of the next block the line just entered.
const auto CurrentFace = MoveToNextBlock(Position, Adjustment, Direction, a_Start, StepOffset, StepXFace, StepYFace, StepZFace);
if (!cChunkDef::IsValidHeight(Position))
{
// We've gone out of the world, that's the end of this trace.
if (a_Callbacks.OnOutOfWorld(CalcXZIntersection(static_cast<double>(Position.y), a_Start, a_End)))
{
// The callback terminated the trace
return false;
}
a_Callbacks.OnNoMoreHits();
return true;
}
// Update the current chunk:
Chunk = Chunk->GetNeighborChunk(Position.x, Position.z);
if (Chunk == nullptr)
{
a_Callbacks.OnNoChunk();
return false;
}
// Report the current block through the callbacks:
if (Chunk->IsValid())
{
BLOCKTYPE BlockType;
NIBBLETYPE BlockMeta;
int RelX = Position.x - Chunk->GetPosX() * cChunkDef::Width;
int RelZ = Position.z - Chunk->GetPosZ() * cChunkDef::Width;
Chunk->GetBlockTypeMeta(RelX, Position.y, RelZ, BlockType, BlockMeta);
if (a_Callbacks.OnNextBlock(Position, BlockType, BlockMeta, CurrentFace))
{
// The callback terminated the trace.
return false;
}
}
else if (a_Callbacks.OnNextBlockNoData(Position, CurrentFace))
{
// The callback terminated the trace.
return false;
}
}
}
bool LineBlockTracer::Trace(cWorld & a_World, BlockTracerCallbacks & a_Callbacks, const Vector3d a_Start, const Vector3d a_End)
{
int BlockX = FloorC(a_Start.x);
int BlockZ = FloorC(a_Start.z);
int ChunkX, ChunkZ;
cChunkDef::BlockToChunk(BlockX, BlockZ, ChunkX, ChunkZ);
return a_World.DoWithChunk(ChunkX, ChunkZ, [&a_Callbacks, a_Start, a_End](cChunk & a_Chunk) { return Trace(a_Chunk, a_Callbacks, a_Start, a_End); });
}
bool LineBlockTracer::LineOfSightTrace(cWorld & a_World, const Vector3d & a_Start, const Vector3d & a_End, LineOfSight a_Sight)
{
static class LineOfSightCallbacks:
public cLineBlockTracer::cCallbacks
public BlockTracerCallbacks
{
bool m_IsAirOpaque;
bool m_IsWaterOpaque;
@ -67,9 +218,9 @@ bool cLineBlockTracer::LineOfSightTrace(cWorld & a_World, const Vector3d & a_Sta
}
}
} callbacks(
(a_Sight & losAir) == 0,
(a_Sight & losWater) == 0,
(a_Sight & losLava) == 0
(a_Sight & LineOfSight::Air) == 0,
(a_Sight & LineOfSight::Water) == 0,
(a_Sight & LineOfSight::Lava) == 0
);
return Trace(a_World, callbacks, a_Start, a_End);
}
@ -78,7 +229,7 @@ bool cLineBlockTracer::LineOfSightTrace(cWorld & a_World, const Vector3d & a_Sta
bool cLineBlockTracer::FirstSolidHitTrace(
bool LineBlockTracer::FirstSolidHitTrace(
cWorld & a_World,
const Vector3d & a_Start, const Vector3d & a_End,
Vector3d & a_HitCoords,
@ -86,7 +237,7 @@ bool cLineBlockTracer::FirstSolidHitTrace(
)
{
class cSolidHitCallbacks:
public cCallbacks
public BlockTracerCallbacks
{
public:
cSolidHitCallbacks(const Vector3d & a_CBStart, const Vector3d & a_CBEnd, Vector3d & a_CBHitCoords, Vector3i & a_CBHitBlockCoords, eBlockFace & a_CBHitBlockFace):
@ -129,218 +280,3 @@ bool cLineBlockTracer::FirstSolidHitTrace(
} callbacks(a_Start, a_End, a_HitCoords, a_HitBlockCoords, a_HitBlockFace);
return !Trace(a_World, callbacks, a_Start, a_End);
}
bool cLineBlockTracer::Trace(const Vector3d a_Start, const Vector3d a_End)
{
// Initialize the member veriables:
m_Start = a_Start;
m_End = a_End;
m_Dir.x = (m_Start.x < m_End.x) ? 1 : -1;
m_Dir.y = (m_Start.y < m_End.y) ? 1 : -1;
m_Dir.z = (m_Start.z < m_End.z) ? 1 : -1;
m_CurrentFace = BLOCK_FACE_NONE;
// Check the start coords, adjust into the world:
if (m_Start.y < 0)
{
if (m_End.y < 0)
{
// Nothing to trace
m_Callbacks->OnNoMoreHits();
return true;
}
FixStartBelowWorld();
m_Callbacks->OnIntoWorld(m_Start);
}
else if (m_Start.y >= cChunkDef::Height)
{
if (m_End.y >= cChunkDef::Height)
{
m_Callbacks->OnNoMoreHits();
return true;
}
FixStartAboveWorld();
m_Callbacks->OnIntoWorld(m_Start);
}
m_Current = m_Start.Floor();
m_Diff = m_End - m_Start;
// The actual trace is handled with ChunkMapCS locked by calling our ChunkCallback for the specified chunk
int BlockX = FloorC(m_Start.x);
int BlockZ = FloorC(m_Start.z);
int ChunkX, ChunkZ;
cChunkDef::BlockToChunk(BlockX, BlockZ, ChunkX, ChunkZ);
return m_World->DoWithChunk(ChunkX, ChunkZ, [this](cChunk & a_Chunk) { return ChunkCallback(&a_Chunk); });
}
void cLineBlockTracer::FixStartAboveWorld(void)
{
// We must set the start Y to less than cChunkDef::Height so that it is considered inside the world later on
// Therefore we use an EPS-offset from the height, as small as reasonably possible.
const double Height = static_cast<double>(cChunkDef::Height) - 0.00001;
CalcXZIntersection(Height, m_Start.x, m_Start.z);
m_Start.y = Height;
}
void cLineBlockTracer::FixStartBelowWorld(void)
{
CalcXZIntersection(0, m_Start.x, m_Start.z);
m_Start.y = 0;
}
void cLineBlockTracer::CalcXZIntersection(double a_Y, double & a_IntersectX, double & a_IntersectZ)
{
double Ratio = (m_Start.y - a_Y) / (m_Start.y - m_End.y);
a_IntersectX = m_Start.x + (m_End.x - m_Start.x) * Ratio;
a_IntersectZ = m_Start.z + (m_End.z - m_Start.z) * Ratio;
}
bool cLineBlockTracer::MoveToNextBlock(void)
{
// Find out which of the current block's walls gets hit by the path:
static const double EPS = 0.00001;
enum
{
dirNONE,
dirX,
dirY,
dirZ,
} Direction = dirNONE;
// Calculate the next YZ wall hit:
double Coeff = 1;
if (std::abs(m_Diff.x) > EPS)
{
double DestX = (m_Dir.x > 0) ? (m_Current.x + 1) : m_Current.x;
double CoeffX = (DestX - m_Start.x) / m_Diff.x;
if (CoeffX <= 1) // We need to include equality for the last block in the trace
{
Coeff = CoeffX;
Direction = dirX;
}
}
// If the next XZ wall hit is closer, use it instead:
if (std::abs(m_Diff.y) > EPS)
{
double DestY = (m_Dir.y > 0) ? (m_Current.y + 1) : m_Current.y;
double CoeffY = (DestY - m_Start.y) / m_Diff.y;
if (CoeffY <= Coeff) // We need to include equality for the last block in the trace
{
Coeff = CoeffY;
Direction = dirY;
}
}
// If the next XY wall hit is closer, use it instead:
if (std::abs(m_Diff.z) > EPS)
{
double DestZ = (m_Dir.z > 0) ? (m_Current.z + 1) : m_Current.z;
double CoeffZ = (DestZ - m_Start.z) / m_Diff.z;
if (CoeffZ <= Coeff) // We need to include equality for the last block in the trace
{
Direction = dirZ;
}
}
// Based on the wall hit, adjust the current coords
switch (Direction)
{
case dirX: m_Current.x += m_Dir.x; m_CurrentFace = (m_Dir.x > 0) ? BLOCK_FACE_XM : BLOCK_FACE_XP; break;
case dirY: m_Current.y += m_Dir.y; m_CurrentFace = (m_Dir.y > 0) ? BLOCK_FACE_YM : BLOCK_FACE_YP; break;
case dirZ: m_Current.z += m_Dir.z; m_CurrentFace = (m_Dir.z > 0) ? BLOCK_FACE_ZM : BLOCK_FACE_ZP; break;
case dirNONE: return false;
}
return true;
}
bool cLineBlockTracer::ChunkCallback(cChunk * a_Chunk)
{
ASSERT((m_Current.y >= 0) && (m_Current.y < cChunkDef::Height)); // This should be provided by FixStartAboveWorld() / FixStartBelowWorld()
// This is the actual line tracing loop.
for (;;)
{
// Our caller (DoWithChunk callback) should never give nothing:
ASSERT(a_Chunk != nullptr);
// Move to next block
if (!MoveToNextBlock())
{
// We've reached the end
m_Callbacks->OnNoMoreHits();
return true;
}
if ((m_Current.y < 0) || (m_Current.y >= cChunkDef::Height))
{
// We've gone out of the world, that's the end of this trace
double IntersectX, IntersectZ;
CalcXZIntersection(m_Current.y, IntersectX, IntersectZ);
if (m_Callbacks->OnOutOfWorld({IntersectX, double(m_Current.y), IntersectZ}))
{
// The callback terminated the trace
return false;
}
m_Callbacks->OnNoMoreHits();
return true;
}
// Update the current chunk
a_Chunk = a_Chunk->GetNeighborChunk(m_Current.x, m_Current.z);
if (a_Chunk == nullptr)
{
m_Callbacks->OnNoChunk();
return false;
}
// Report the current block through the callbacks:
if (a_Chunk->IsValid())
{
BLOCKTYPE BlockType;
NIBBLETYPE BlockMeta;
int RelX = m_Current.x - a_Chunk->GetPosX() * cChunkDef::Width;
int RelZ = m_Current.z - a_Chunk->GetPosZ() * cChunkDef::Width;
a_Chunk->GetBlockTypeMeta(RelX, m_Current.y, RelZ, BlockType, BlockMeta);
if (m_Callbacks->OnNextBlock(m_Current, BlockType, BlockMeta, m_CurrentFace))
{
// The callback terminated the trace
return false;
}
}
else if (m_Callbacks->OnNextBlockNoData(m_Current, m_CurrentFace))
{
// The callback terminated the trace
return false;
}
}
}

View File

@ -1,7 +1,7 @@
// LineBlockTracer.h
// Declares the cLineBlockTracer class representing a cBlockTracer that traces along a straight line between two points
// Declares the LineBlockTracer namespace representing a tracer that visits every block along a straight line between two points
@ -15,47 +15,36 @@
// fwd: Chunk.h
class cChunk;
class cWorld;
class cLineBlockTracer:
public cBlockTracer
namespace LineBlockTracer
{
using Super = cBlockTracer;
public:
enum eLineOfSight
/* Bit flags used for LineOfSightTrace's Sight parameter. */
enum LineOfSight
{
// Bit flags used for LineOfSightTrace's Sight parameter:
losAir = 1, // Can see through air
losWater = 2, // Can see through water
losLava = 4, // Can see through lava
Air = 1, // Can see through air.
Water = 2, // Can see through water.
Lava = 4, // Can see through lava.
// Common combinations:
losAirWaterLava = losAir | losWater | losLava,
losAirWater = losAir | losWater,
AirWaterLava = Air | Water | Lava,
AirWater = Air | Water,
};
cLineBlockTracer(cWorld & a_World, cCallbacks & a_Callbacks);
/** Traces one line between Start and End; returns true if the entire line was traced (until OnNoMoreHits()) */
bool Trace(const cChunk & a_Chunk, BlockTracerCallbacks & a_Callbacks, Vector3d a_Start, Vector3d a_End);
/** Traces one line between Start and End; returns true if the entire line was traced (until OnNoMoreHits()) */
bool Trace(Vector3d a_Start, Vector3d a_End);
// Utility functions for simple one-line usage:
/** Traces one line between Start and End; returns true if the entire line was traced (until OnNoMoreHits()) */
static bool Trace(cWorld & a_World, cCallbacks & a_Callbacks, const Vector3d a_Start, const Vector3d a_End);
bool Trace(cWorld & a_World, BlockTracerCallbacks & a_Callbacks, const Vector3d a_Start, const Vector3d a_End);
/** Returns true if the two positions are within line of sight (not obscured by blocks).
a_Sight specifies which blocks are considered transparent for the trace, is an OR-combination of eLineOfSight constants. */
static bool LineOfSightTrace(cWorld & a_World, const Vector3d & a_Start, const Vector3d & a_End, int a_Sight);
bool LineOfSightTrace(cWorld & a_World, const Vector3d & a_Start, const Vector3d & a_End, LineOfSight a_Sight);
/** Traces until the first solid block is hit (or until end, whichever comes first.
If a solid block was hit, returns true and fills a_HitCoords, a_HitBlockCoords and a_HitBlockFace.
@ -63,48 +52,11 @@ public:
a_HitCoords is the exact coords of the hit,
a_HitBlockCoords are the coords of the solid block that was hit,
a_HitBlockFace is the face of the solid block that was hit. */
static bool FirstSolidHitTrace(
bool FirstSolidHitTrace(
cWorld & a_World,
const Vector3d & a_Start, const Vector3d & a_End,
Vector3d & a_HitCoords,
Vector3i & a_HitBlockCoords,
eBlockFace & a_HitBlockFace
);
protected:
/** The start point of the trace */
Vector3d m_Start;
/** The end point of the trace */
Vector3d m_End;
/** The difference in coords, End - Start */
Vector3d m_Diff;
/** The increment at which the block coords are going from Start to End; either +1 or -1 */
Vector3i m_Dir;
/** The current block */
Vector3i m_Current;
/** The face through which the current block has been entered */
eBlockFace m_CurrentFace;
/** Adjusts the start point above the world to just at the world's top */
void FixStartAboveWorld(void);
/** Adjusts the start point below the world to just at the world's bottom */
void FixStartBelowWorld(void);
/** Calculates the XZ coords of an intersection with the specified Yconst plane; assumes that such an intersection exists */
void CalcXZIntersection(double a_Y, double & a_IntersectX, double & a_IntersectZ);
/** Moves m_Current to the next block on the line; returns false if no move is possible (reached the end) */
bool MoveToNextBlock(void);
bool ChunkCallback(cChunk * a_Chunk);
} ;
}

View File

@ -12,7 +12,7 @@
#include "Generating/ComposableGenerator.h"
#include "SetChunkData.h"
#include "DeadlockDetect.h"
#include "LineBlockTracer.h"
#include "Physics/Tracers/LineBlockTracer.h"
#include "UUID.h"
#include "BlockInServerPluginInterface.h"
@ -1844,7 +1844,7 @@ void cWorld::SpawnItemPickups(const cItems & a_Pickups, Vector3d a_Pos, Vector3d
UInt32 cWorld::SpawnItemPickup(Vector3d a_Position, cItem && a_Item, Vector3d a_Speed, cTickTime a_CollectionDelay, cTickTime a_Lifetime, bool a_CanCombine)
{
auto Pickup = std::make_unique<cPickup>(a_Position, std::move(a_Item), a_Speed, a_CollectionDelay, a_Lifetime);
auto Pickup = std::make_unique<cPickup>(a_Position, std::move(a_Item), a_Speed, a_CollectionDelay, a_Lifetime, a_CanCombine);
auto PickupPtr = Pickup.get();
if (!PickupPtr->Initialize(std::move(Pickup), *this))
@ -2413,7 +2413,7 @@ bool cWorld::DoWithNearestPlayer(Vector3d a_Pos, double a_RangeLimit, cPlayerLis
// Check LineOfSight, if requested:
if (
a_CheckLineOfSight &&
!cLineBlockTracer::LineOfSightTrace(*this, a_Pos, Pos, cLineBlockTracer::losAirWater)
!LineBlockTracer::LineOfSightTrace(*this, a_Pos, Pos, LineBlockTracer::LineOfSight::AirWater)
)
{
continue;

View File

@ -1942,7 +1942,7 @@ void cWSSAnvil::LoadPickupFromNBT(cEntityList & a_Entities, const cParsedNBT & a
return;
}
auto Pickup = std::make_unique<cPickup>(Vector3d(), std::move(Item), Vector3d(), 0_tick, 0_tick); // Pickup delay loaded later
auto Pickup = std::make_unique<cPickup>(Vector3d(), std::move(Item), Vector3d(), 0_tick, 0_tick, true); // Pickup delay loaded later
if (!LoadEntityBaseFromNBT(*Pickup.get(), a_NBT, a_TagIdx))
{
return;