1
0
Fork 0

Tracer replacement (#3704)

* Replaced cTracer usage with cLineBlockTracer.

* Exported new cLineBlockTracer utility functions to Lua API.
This commit is contained in:
Mattes D 2017-05-11 14:34:36 +02:00 committed by worktycho
parent 7c4576a025
commit 2c3c1f1527
18 changed files with 689 additions and 152 deletions

View File

@ -977,52 +977,103 @@ return
{
Desc = [[
This class provides an easy-to-use interface for tracing lines through individual
blocks in the world. It will call the provided callbacks according to what events it encounters along the
way.</p>
<p>
For the Lua API, there's only one static function exported that takes all the parameters necessary to do
the tracing. The Callbacks parameter is a table containing all the functions that will be called upon the
various events. See below for further information.
blocks in the world. It can either be used to call the provided callbacks according
to what events it encounters along the way, or there are shortcut functions used for
the most popular tracing reasons - line of sight and solid hits.
]],
Functions =
{
FirstSolidHitTrace =
{
{
IsStatic = true,
Params =
{
{ Name = "World", Type = "cWorld" },
{ Name = "StartX", Type = "number" },
{ Name = "StartY", Type = "number" },
{ Name = "StartZ", Type = "number" },
{ Name = "EndX", Type = "number" },
{ Name = "EndY", Type = "number" },
{ Name = "EndZ", Type = "number" },
},
Returns =
{
{ Name = "HasHitSolid", Type = "boolean" },
{ Name = "HitCoords", Type = "Vector3d" },
{ Name = "HitBlockCoords", Type = "Vector3i" },
{ Name = "HitBlockFace", Type = "eBlockFace" },
},
Notes = "If the specified line hits a solid block, return true and the coordinates / face of the first such solid block hit. Returns false if there's no solid block on that line.",
},
{
IsStatic = true,
Params =
{
{ Name = "World", Type = "cWorld" },
{ Name = "Start", Type = "Vector3d" },
{ Name = "End", Type = "Vector3d" },
},
Returns =
{
{ Name = "HasHitSolid", Type = "boolean" },
{ Name = "HitCoords", Type = "Vector3d" },
{ Name = "HitBlockCoords", Type = "Vector3i" },
{ Name = "HitBlockFace", Type = "eBlockFace" },
},
Notes = "If the specified line hits a solid block, return true and the coordinates / face of the first such solid block hit. Returns false if there's no solid block on that line.",
},
}, -- FirstSolidHitTrace
LineOfSightTrace =
{
{
IsStatic = true,
Params =
{
{ Name = "World", Type = "cWorld" },
{ Name = "StartX", Type = "number" },
{ Name = "StartY", Type = "number" },
{ Name = "StartZ", Type = "number" },
{ Name = "EndX", Type = "number" },
{ Name = "EndY", Type = "number" },
{ Name = "EndZ", Type = "number" },
{ Name = "Sight", Type = "number" },
},
Returns =
{
{ Name = "CanSee", Type = "boolean" },
},
Notes = "Returns true if the two points specified are within line of sight of each other. The Sight parameter specifies which blocks are considered transparent for the trace, it is a combination of {{cLineBlockTracer#eLineOfSight|losXXX}} values added together."
},
{
IsStatic = true,
Params =
{
{ Name = "World", Type = "cWorld" },
{ Name = "Start", Type = "Vector3d" },
{ Name = "End", Type = "Vector3d" },
{ Name = "Sight", Type = "number" },
},
Returns =
{
{ Name = "CanSee", Type = "boolean" },
},
Notes = "Returns true if the two points specified are within line of sight of each other. The Sight parameter specifies which blocks are considered transparent for the trace, it is a combination of {{cLineBlockTracer#eLineOfSight|losXXX}} values added together."
},
}, -- LineOfSightTrace
Trace =
{
IsStatic = true,
Params =
{
{
Name = "World",
Type = "cWorld",
},
{
Name = "Callbacks",
Type = "table",
},
{
Name = "StartX",
Type = "number",
},
{
Name = "StartY",
Type = "number",
},
{
Name = "StartZ",
Type = "number",
},
{
Name = "EndX",
Type = "number",
},
{
Name = "EndY",
Type = "number",
},
{
Name = "EndZ",
Type = "number",
},
{ Name = "World", Type = "cWorld" },
{ Name = "Callbacks", Type = "table" },
{ Name = "StartX", Type = "number" },
{ Name = "StartY", Type = "number" },
{ Name = "StartZ", Type = "number" },
{ Name = "EndX", Type = "number" },
{ Name = "EndY", Type = "number" },
{ Name = "EndZ", Type = "number" },
},
Returns =
{
@ -1033,6 +1084,29 @@ various events. See below for further information.
Notes = "Performs the trace on the specified line. Returns true if the entire trace was processed (no callback returned true)",
},
},
Constants =
{
losAir =
{
Notes = "LineOfSight tracing can 'see' through air blocks.",
},
losWater =
{
Notes = "LineOfSight tracing can 'see' through water blocks.",
},
losLava =
{
Notes = "LineOfSight tracing can 'see' through lava blocks.",
},
},
ConstantGroups =
{
eLineOfSight =
{
Include = "los.*",
TextBefore = "The following constants are used to speficy which blocks are see-through when tracing a LineOfSight trace. Add them together to make up the Sight parameter.",
},
},
AdditionalInfo =
{
{
@ -1109,16 +1183,15 @@ end
cTracer =
{
Desc = [[
A cTracer object is used to trace lines in the world. One thing you can use the cTracer for, is
tracing what block a player is looking at, but you can do more with it if you want.</p>
<p>
The cTracer is still a work in progress and is not documented at all.</p>
<p>
See also the {{cLineBlockTracer}} class for an alternative approach using callbacks.
This class is <b>OBSOLETE</b>, do not use it.
See the {{cLineBlockTracer}} class for the replacement.
]],
Functions =
{
Trace =
{
Notes = "<b>OBSOLETE</b>, use the {{cLineBlockTracer}} class instead.",
},
},
},
Vector3d =

View File

@ -1838,6 +1838,44 @@ end
function HandleConsoleHitTrace(a_Split)
local world = cRoot:Get():GetDefaultWorld()
local s = Vector3d(0, 70, 0)
local e = Vector3d(100, 75, 100)
if (tonumber(a_Split[2])) then
s.x = tonumber(a_Split[2])
end
if (tonumber(a_Split[3])) then
s.y = tonumber(a_Split[3])
end
if (tonumber(a_Split[4])) then
s.z = tonumber(a_Split[4])
end
if (tonumber(a_Split[5])) then
e.x = tonumber(a_Split[5])
end
if (tonumber(a_Split[6])) then
e.y = tonumber(a_Split[6])
end
if (tonumber(a_Split[7])) then
e.z = tonumber(a_Split[7])
end
local res, hitCoords, hitBlockCoords, hitBlockFace = cLineBlockTracer:FirstSolidHitTrace(world, s, e)
if (res) then
return true, string.format("The line hits block {%d, %d, %d} at point {%f, %f, %f}, face %s",
hitBlockCoords.x, hitBlockCoords.y, hitBlockCoords.z,
hitCoords.x, hitCoords.y, hitCoords.z,
BlockFaceToString(hitBlockFace)
)
else
return true, "The two points specified don't have a solid block between them."
end
end
--- Monitors the state of the "inh" entity-spawning hook
-- if false, the hook is installed before the "inh" command processing
local isInhHookInstalled = false
@ -1954,6 +1992,40 @@ end
function HandleConsoleLosTrace(a_Split)
local world = cRoot:Get():GetDefaultWorld()
local s = Vector3d(0, 70, 0)
local e = Vector3d(100, 75, 100)
if (tonumber(a_Split[2])) then
s.x = tonumber(a_Split[2])
end
if (tonumber(a_Split[3])) then
s.y = tonumber(a_Split[3])
end
if (tonumber(a_Split[4])) then
s.z = tonumber(a_Split[4])
end
if (tonumber(a_Split[5])) then
e.x = tonumber(a_Split[5])
end
if (tonumber(a_Split[6])) then
e.y = tonumber(a_Split[6])
end
if (tonumber(a_Split[7])) then
e.z = tonumber(a_Split[7])
end
local res = cLineBlockTracer:LineOfSightTrace(world, s, e, cLineBlockTracer.losAir)
if (res) then
return true, "The two points can see each other."
else
return true, "The two points cannot see each other"
end
end
function HandleConsolePluginStats(a_Split)
cPluginManager:ForEachPlugin(
function (a_CBPlugin)

View File

@ -290,6 +290,12 @@ g_PluginInfo =
HelpString = "Tests the crypto hashing functions",
},
["hittrace"] =
{
Handler = HandleConsoleHitTrace,
HelpString = "Tests the FirstSolidHit trace",
},
["inh"] =
{
Handler = HandleConsoleInh,
@ -302,6 +308,12 @@ g_PluginInfo =
HelpString = "Loads the specified chunk into memory",
},
["lostrace"] =
{
Handler = HandleConsoleLosTrace,
HelpString = "Tests a LineOfSight trace",
},
["pluginstats"] =
{
Handler = HandleConsolePluginStats,

View File

@ -10,6 +10,7 @@
#include "../World.h"
#include "../Entities/Player.h"
#include "LuaState.h"
#include "../Tracer.h"
@ -291,6 +292,47 @@ tolua_lerror:
/* method: Trace of class cTracer */
static int tolua_cTracer_Trace(lua_State * a_LuaState)
{
// Log a deprecation warning with stacktrace:
cLuaState S(a_LuaState);
LOGWARNING("The function cTracer:Trace is obsolete, use the cLineBlockTracer instead");
S.LogStackTrace();
// Check params:
if (
!S.CheckParamUserType(1, "cTracer") ||
!S.CheckParamUserType(2, "const Vector3<float>", 3) ||
!S.CheckParamNumber (4)
)
{
return 0;
}
// Read params:
cTracer * self;
Vector3d * start;
Vector3d * direction;
int distance;
bool lineOfSight = false;
if (!S.GetStackValues(1, self, start, direction, distance))
{
LOGWARNING("Cannot retrieve parameters for cTracer::Trace. Expected a cTracer (self), Vector3d, Vector3d, number and optional boolean.");
S.LogStackValues();
return 0;
}
S.GetStackValue(5, lineOfSight);
// Call and push the result:
S.Push(self->Trace(*start, *direction, distance, lineOfSight));
return 1;
}
/** function: cWorld:SetSignLines */
static int tolua_cWorld_SetSignLines(lua_State * tolua_S)
{
@ -398,6 +440,10 @@ void DeprecatedBindings::Bind(lua_State * tolua_S)
tolua_function(tolua_S, "StringToMobType", tolua_AllToLua_StringToMobType00);
tolua_beginmodule(tolua_S, "cTracer");
tolua_function(tolua_S, "Trace", tolua_cTracer_Trace);
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cWorld");
tolua_function(tolua_S, "UpdateSign", tolua_cWorld_SetSignLines);
tolua_endmodule(tolua_S);

View File

@ -2605,7 +2605,7 @@ public:
{
}
virtual bool OnNextBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, char a_EntryFace) override
virtual bool OnNextBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override
{
bool res = false;
if (!m_Callbacks->CallTableFn(
@ -2683,6 +2683,202 @@ protected:
static int tolua_cLineBlockTracer_FirstSolidHitTrace(lua_State * tolua_S)
{
/* Supported function signatures:
cLineBlockTracer:FirstSolidHitTrace(World, StartX, StartY, StartZ, EndX, EndY, EndZ) -> bool, [Vector3d, Vector3i, eBlockFace] // Canonical
cLineBlockTracer:FirstSolidHitTrace(World, Start, End) -> bool, [Vector3d, Vector3i, eBlockFace] // Canonical
cLineBlockTracer.FirstSolidHitTrace(World, StartX, StartY, StartZ, EndX, EndY, EndZ) -> bool, [Vector3d, Vector3i, eBlockFace]
cLineBlockTracer.FirstSolidHitTrace(World, Start, End) -> bool, [Vector3d, Vector3i, eBlockFace]
*/
// If the first param is the cLineBlockTracer class, shift param index by one:
int idx = 1;
tolua_Error err;
if (tolua_isusertable(tolua_S, 1, "cLineBlockTracer", 0, &err))
{
idx = 2;
}
// Check params:
cLuaState L(tolua_S);
if (
!L.CheckParamUserType(idx, "cWorld")
)
{
return 0;
}
if (L.IsParamNumber(idx + 1))
{
// This is the number variant of the call:
if (
!L.CheckParamNumber(idx + 1, idx + 6) ||
!L.CheckParamEnd(idx + 7)
)
{
return 0;
}
// Get the params:
cWorld * world;
double startX, startY, startZ;
double endX, endY, endZ;
if (!L.GetStackValues(idx, world, startX, startY, startZ, endX, endY, endZ))
{
LOGWARNING("cLineBlockTracer:FirstSolidHitTrace(): Cannot read parameters, aborting the trace.");
L.LogStackTrace();
L.LogStackValues("Values on the stack");
return 0;
}
Vector3d hitCoords;
Vector3i hitBlockCoords;
eBlockFace hitBlockFace;
auto isHit = cLineBlockTracer::FirstSolidHitTrace(*world, Vector3d(startX, startY, startZ), Vector3d(endX, endY, endZ), hitCoords, hitBlockCoords, hitBlockFace);
L.Push(isHit);
if (!isHit)
{
return 1;
}
L.Push(hitCoords);
L.Push(hitBlockCoords);
L.Push(hitBlockFace);
return 4;
}
if (L.IsParamUserType(idx + 1, "Vector3<double>"))
{
// This is the Vector3d-based variant of the call:
if (
!L.CheckParamUserType(idx + 1, "Vector3<double>", idx + 2) ||
!L.CheckParamEnd(idx + 3)
)
{
return 0;
}
// Get the params:
cWorld * world;
Vector3d * start;
Vector3d * end;
if (!L.GetStackValues(idx, world, start, end))
{
LOGWARNING("cLineBlockTracer:FirstSolidHitTrace(): Cannot read parameters, aborting the trace.");
L.LogStackTrace();
L.LogStackValues("Values on the stack");
return 0;
}
Vector3d hitCoords;
Vector3i hitBlockCoords;
eBlockFace hitBlockFace;
auto isHit = cLineBlockTracer::FirstSolidHitTrace(*world, start, end, hitCoords, hitBlockCoords, hitBlockFace);
L.Push(isHit);
if (!isHit)
{
return 1;
}
L.Push(hitCoords);
L.Push(hitBlockCoords);
L.Push(hitBlockFace);
return 4;
}
tolua_error(L, "cLineBlockTracer:FirstSolidHitTrace(): Invalid parameters, expected either a set of coords, or two Vector3d's", nullptr);
return 0;
}
static int tolua_cLineBlockTracer_LineOfSightTrace(lua_State * tolua_S)
{
/* Supported function signatures:
cLineBlockTracer:LineOfSightTrace(World, StartX, StartY, StartZ, EndX, EndY, EndZ, Sight) -> bool // Canonical
cLineBlockTracer:LineOfSightTrace(World, Start, End, Sight) -> bool // Canonical
cLineBlockTracer.LineOfSightTrace(World, StartX, StartY, StartZ, EndX, EndY, EndZ, Sight) -> bool
cLineBlockTracer.LineOfSightTrace(World, Start, End, Sight) -> bool
*/
// If the first param is the cLineBlockTracer class, shift param index by one:
int idx = 1;
tolua_Error err;
if (tolua_isusertable(tolua_S, 1, "cLineBlockTracer", 0, &err))
{
idx = 2;
}
// Check params:
cLuaState L(tolua_S);
if (
!L.CheckParamUserType(idx, "cWorld")
)
{
return 0;
}
if (L.IsParamNumber(idx + 1))
{
// This is the number variant of the call:
if (
!L.CheckParamNumber(idx + 1, idx + 6) ||
// Optional param lineOfSight is not checked
!L.CheckParamEnd(idx + 8)
)
{
return 0;
}
// Get the params:
cWorld * world;
double startX, startY, startZ;
double endX, endY, endZ;
if (!L.GetStackValues(idx, world, startX, startY, startZ, endX, endY, endZ))
{
LOGWARNING("cLineBlockTracer:LineOfSightTrace(): Cannot read parameters, aborting the trace.");
L.LogStackTrace();
L.LogStackValues("Values on the stack");
return 0;
}
int lineOfSight = cLineBlockTracer::losAir | cLineBlockTracer::losWater;
L.GetStackValue(idx + 7, lineOfSight);
L.Push(cLineBlockTracer::LineOfSightTrace(*world, Vector3d(startX, startY, startZ), Vector3d(endX, endY, endZ), lineOfSight));
return 1;
}
if (L.IsParamUserType(idx + 1, "Vector3<double>"))
{
// This is the Vector3d-based variant of the call:
if (
!L.CheckParamUserType(idx + 1, "Vector3<double>", idx + 2) ||
// Optional param lineOfSight is not checked
!L.CheckParamEnd(idx + 4)
)
{
return 0;
}
// Get the params:
cWorld * world;
Vector3d * start;
Vector3d * end;
if (!L.GetStackValues(idx, world, start, end))
{
LOGWARNING("cLineBlockTracer:LineOfSightTrace(): Cannot read parameters, aborting the trace.");
L.LogStackTrace();
L.LogStackValues("Values on the stack");
return 0;
}
int lineOfSight = cLineBlockTracer::losAirWater;
L.GetStackValue(idx + 7, lineOfSight);
L.Push(cLineBlockTracer::LineOfSightTrace(*world, start, end, lineOfSight));
return 1;
}
tolua_error(L, "cLineBlockTracer:LineOfSightTrace(): Invalid parameters, expected either a set of coords, or two Vector3d's", nullptr);
return 0;
}
static int tolua_cLineBlockTracer_Trace(lua_State * tolua_S)
{
/* Supported function signatures:
@ -3917,7 +4113,13 @@ void cManualBindings::Bind(lua_State * tolua_S)
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cLineBlockTracer");
tolua_function(tolua_S, "Trace", tolua_cLineBlockTracer_Trace);
tolua_function(tolua_S, "FirstSolidHitTrace", tolua_cLineBlockTracer_FirstSolidHitTrace);
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_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cLuaWindow");

View File

@ -13,6 +13,12 @@
#include "Defines.h"
// fwd: World.h
class cWorld;
@ -34,7 +40,7 @@ public:
/** 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(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, char a_EntryFace) = 0;
virtual bool OnNextBlock(int a_BlockX, int a_BlockY, int a_BlockZ, 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.

View File

@ -9,7 +9,7 @@
#include "../Chunk.h"
#include "../Simulator/FluidSimulator.h"
#include "../Bindings/PluginManager.h"
#include "../Tracer.h"
#include "../LineBlockTracer.h"
#include "Player.h"
#include "Items/ItemHandler.h"
#include "../FastRandom.h"
@ -1071,29 +1071,38 @@ void cEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
// Get water direction
Direction WaterDir = m_World->GetWaterSimulator()->GetFlowingDirection(BlockX, BlockY, BlockZ);
m_WaterSpeed *= 0.9f; // Reduce speed each tick
m_WaterSpeed *= 0.9; // Reduce speed each tick
switch (WaterDir)
{
case X_PLUS:
{
m_WaterSpeed.x = 0.2f;
m_bOnGround = false;
break;
}
case X_MINUS:
{
m_WaterSpeed.x = -0.2f;
m_bOnGround = false;
break;
}
case Z_PLUS:
{
m_WaterSpeed.z = 0.2f;
m_bOnGround = false;
break;
}
case Z_MINUS:
{
m_WaterSpeed.z = -0.2f;
m_bOnGround = false;
break;
default:
break;
}
default:
{
break;
}
}
if (fabs(m_WaterSpeed.x) < 0.05)
@ -1110,60 +1119,54 @@ void cEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
if (NextSpeed.SqrLength() > 0.0f)
{
cTracer Tracer(GetWorld());
// Distance traced is an integer, so we round up from the distance we should go (Speed * Delta), else we will encounter collision detection failurse
int DistanceToTrace = CeilC((NextSpeed * DtSec.count()).SqrLength()) * 2;
bool HasHit = Tracer.Trace(NextPos, NextSpeed, DistanceToTrace);
if (HasHit)
Vector3d HitCoords;
Vector3i HitBlockCoords;
eBlockFace HitBlockFace;
if (cLineBlockTracer::FirstSolidHitTrace(*GetWorld(), NextPos, NextPos + NextSpeed, HitCoords, HitBlockCoords, HitBlockFace))
{
// Oh noez! We hit something: verify that the (hit position - current) was smaller or equal to the (position that we should travel without obstacles - current)
// This is because previously, we traced with a length that was rounded up (due to integer limitations), and in the case that something was hit, we don't want to overshoot our projected movement
if ((Tracer.RealHit - NextPos).SqrLength() <= (NextSpeed * DtSec.count()).SqrLength())
// Set our position to where the block was hit, minus a bit:
// TODO: The real entity's m_Width should be taken into account here
NextPos = HitCoords - NextSpeed.NormalizeCopy() * 0.1;
if (HitBlockFace == BLOCK_FACE_YM)
{
// Block hit was within our projected path
// Begin by stopping movement in the direction that we hit something. The Normal is the line perpendicular to a 2D face and in this case, stores what block face was hit through either -1 or 1.
// For example: HitNormal.y = -1 : BLOCK_FACE_YM; HitNormal.y = 1 : BLOCK_FACE_YP
if (Tracer.HitNormal.x != 0.0f)
{
NextSpeed.x = 0.0f;
}
if (Tracer.HitNormal.y != 0.0f)
{
NextSpeed.y = 0.0f;
}
if (Tracer.HitNormal.z != 0.0f)
{
NextSpeed.z = 0.0f;
}
// We hit the ground, adjust the position to the top of the block:
m_bOnGround = true;
NextPos.y = HitBlockCoords.y + 1;
}
// Now, set our position to the hit block (i.e. move part way along our intended trajectory)
NextPos.Set(Tracer.RealHit.x, Tracer.RealHit.y, Tracer.RealHit.z);
NextPos.x += Tracer.HitNormal.x * 0.1;
NextPos.y += Tracer.HitNormal.y * 0.05;
NextPos.z += Tracer.HitNormal.z * 0.1;
if (Tracer.HitNormal.y == 1.0f) // Hit BLOCK_FACE_YP, we are on the ground
// Avoid movement in the direction of the blockface that has been hit:
switch (HitBlockFace)
{
case BLOCK_FACE_XM:
case BLOCK_FACE_XP:
{
m_bOnGround = true;
NextPos.y = FloorC(NextPos.y); // we clamp the height to 0 cos otherwise we'll constantly be slightly above the block
NextSpeed.x = 0;
break;
}
case BLOCK_FACE_YM:
case BLOCK_FACE_YP:
{
NextSpeed.y = 0;
break;
}
case BLOCK_FACE_ZM:
case BLOCK_FACE_ZP:
{
NextSpeed.z = 0;
break;
}
default:
{
break;
}
}
else
{
// We have hit a block but overshot our intended trajectory, move normally, safe in the warm cocoon of knowledge that we won't appear to teleport forwards on clients,
// and that this piece of software will come to be hailed as the epitome of performance and functionality in C++, never before seen, and of such a like that will never
// be henceforth seen again in the time of programmers and man alike
// </&sensationalist>
NextPos += (NextSpeed * DtSec.count());
}
}
else
{
// We didn't hit anything, so move =]
NextPos += (NextSpeed * DtSec.count());
}
}
else
{
// We didn't hit anything, so move =]
NextPos += (NextSpeed * DtSec.count());
}
SetPosition(NextPos);
SetSpeed(NextSpeed);

View File

@ -56,7 +56,7 @@ protected:
double m_SlowdownCoeff;
// cCallbacks overrides:
virtual bool OnNextBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, char a_EntryFace) override
virtual bool OnNextBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override
{
/*
// DEBUG:

View File

@ -50,7 +50,7 @@ public:
{
}
virtual bool OnNextBlock(int a_CBBlockX, int a_CBBlockY, int a_CBBlockZ, BLOCKTYPE a_CBBlockType, NIBBLETYPE a_CBBlockMeta, char a_CBEntryFace) override
virtual bool OnNextBlock(int a_CBBlockX, int a_CBBlockY, int a_CBBlockZ, BLOCKTYPE a_CBBlockType, NIBBLETYPE a_CBBlockMeta, eBlockFace a_CBEntryFace) override
{
if (a_CBBlockType != E_BLOCK_AIR)
{

View File

@ -33,7 +33,7 @@ public:
{
}
virtual bool OnNextBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, char a_EntryFace) override
virtual bool OnNextBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override
{
if (IsBlockWater(a_BlockType))
{

View File

@ -196,7 +196,7 @@ public:
{
}
virtual bool OnNextBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, char a_EntryFace) override
virtual bool OnNextBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override
{
if (IsBlockWater(a_BlockType) || IsBlockLava(a_BlockType))
{
@ -241,7 +241,7 @@ public:
NIBBLETYPE m_ReplacedBlockMeta;
eBlockFace m_EntryFace;
virtual bool OnNextBlock(int a_CBBlockX, int a_CBBlockY, int a_CBBlockZ, BLOCKTYPE a_CBBlockType, NIBBLETYPE a_CBBlockMeta, char a_CBEntryFace) override
virtual bool OnNextBlock(int a_CBBlockX, int a_CBBlockY, int a_CBBlockZ, BLOCKTYPE a_CBBlockType, NIBBLETYPE a_CBBlockMeta, eBlockFace a_CBEntryFace) override
{
if (a_CBBlockType != E_BLOCK_AIR)
{
@ -250,7 +250,7 @@ public:
m_EntryFace = static_cast<eBlockFace>(a_CBEntryFace);
if (!cFluidSimulator::CanWashAway(a_CBBlockType) && !IsBlockLiquid(a_CBBlockType))
{
AddFaceDirection(a_CBBlockX, a_CBBlockY, a_CBBlockZ, static_cast<eBlockFace>(a_CBEntryFace)); // Was an unwashawayable block, can't overwrite it!
AddFaceDirection(a_CBBlockX, a_CBBlockY, a_CBBlockZ, a_CBEntryFace); // Was an unwashawayable block, can't overwrite it!
}
m_Pos.Set(a_CBBlockX, a_CBBlockY, a_CBBlockZ); // (Block could be washed away, replace it)
return true; // Abort tracing
@ -263,7 +263,8 @@ public:
Vector3d Start(a_Player->GetEyePosition());
Vector3d End(a_Player->GetEyePosition() + a_Player->GetLookVector() * 5);
// cTracer::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
// cLineBlockTracer::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.x, Start.y, Start.z, End.x, End.y, End.z))
{

View File

@ -57,7 +57,7 @@ public:
{
}
virtual bool OnNextBlock(int a_CBBlockX, int a_CBBlockY, int a_CBBlockZ, BLOCKTYPE a_CBBlockType, NIBBLETYPE a_CBBlockMeta, char a_CBEntryFace) override
virtual bool OnNextBlock(int a_CBBlockX, int a_CBBlockY, int a_CBBlockZ, BLOCKTYPE a_CBBlockType, NIBBLETYPE a_CBBlockMeta, eBlockFace a_CBEntryFace) override
{
if (IsBlockWater(a_CBBlockType))
{

View File

@ -8,6 +8,7 @@
#include "Vector3.h"
#include "World.h"
#include "Chunk.h"
#include "BoundingBox.h"
@ -31,7 +32,7 @@ cLineBlockTracer::cLineBlockTracer(cWorld & a_World, cCallbacks & a_Callbacks) :
m_CurrentX(0),
m_CurrentY(0),
m_CurrentZ(0),
m_CurrentFace(0)
m_CurrentFace(BLOCK_FACE_NONE)
{
}
@ -49,6 +50,97 @@ bool cLineBlockTracer::Trace(cWorld & a_World, cBlockTracer::cCallbacks & a_Call
bool cLineBlockTracer::LineOfSightTrace(cWorld & a_World, const Vector3d & a_Start, const Vector3d & a_End, int a_Sight)
{
static class LineOfSightCallbacks:
public cLineBlockTracer::cCallbacks
{
bool m_IsAirOpaque;
bool m_IsWaterOpaque;
bool m_IsLavaOpaque;
public:
LineOfSightCallbacks(bool a_IsAirOpaque, bool a_IsWaterOpaque, bool a_IsLavaOpaque):
m_IsAirOpaque(a_IsAirOpaque),
m_IsWaterOpaque(a_IsWaterOpaque),
m_IsLavaOpaque(a_IsLavaOpaque)
{}
virtual bool OnNextBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override
{
switch (a_BlockType)
{
case E_BLOCK_AIR: return m_IsAirOpaque;
case E_BLOCK_LAVA: return m_IsLavaOpaque;
case E_BLOCK_STATIONARY_LAVA: return m_IsLavaOpaque;
case E_BLOCK_STATIONARY_WATER: return m_IsWaterOpaque;
case E_BLOCK_WATER: return m_IsWaterOpaque;
default: return true;
}
}
} callbacks(
(a_Sight & losAir) == 0,
(a_Sight & losWater) == 0,
(a_Sight & losLava) == 0
);
return Trace(a_World, callbacks, a_Start, a_End);
}
bool cLineBlockTracer::FirstSolidHitTrace(
cWorld & a_World,
const Vector3d & a_Start, const Vector3d & a_End,
Vector3d & a_HitCoords,
Vector3i & a_HitBlockCoords, eBlockFace & a_HitBlockFace
)
{
class cSolidHitCallbacks:
public cCallbacks
{
public:
cSolidHitCallbacks(const Vector3d & a_CBStart, const Vector3d & a_CBEnd, Vector3d & a_CBHitCoords, Vector3i & a_CBHitBlockCoords, eBlockFace & a_CBHitBlockFace):
m_Start(a_CBStart),
m_End(a_CBEnd),
m_HitCoords(a_CBHitCoords),
m_HitBlockCoords(a_CBHitBlockCoords),
m_HitBlockFace(a_CBHitBlockFace)
{
}
virtual bool OnNextBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override
{
if (!cBlockInfo::IsSolid(a_BlockType))
{
return false;
}
// We hit a solid block, calculate the exact hit coords and abort trace:
m_HitBlockCoords.Set(a_BlockX, a_BlockY, a_BlockZ);
m_HitBlockFace = a_EntryFace;
cBoundingBox bb(a_BlockX, a_BlockX + 1, a_BlockY, a_BlockY + 1, a_BlockZ, a_BlockZ + 1); // Bounding box of the block hit
double LineCoeff = 0; // Used to calculate where along the line an intersection with the bounding box occurs
eBlockFace Face; // Face hit
VERIFY(bb.CalcLineIntersection(m_Start, m_End, LineCoeff, Face));
m_HitCoords = m_Start + (m_End - m_Start) * LineCoeff; // Point where projectile goes into the hit block
return true;
}
protected:
const Vector3d & m_Start;
const Vector3d & m_End;
Vector3d & m_HitCoords;
Vector3i & m_HitBlockCoords;
eBlockFace & m_HitBlockFace;
} callbacks(a_Start, a_End, a_HitCoords, a_HitBlockCoords, a_HitBlockFace);
return !Trace(a_World, callbacks, a_Start, a_End);
}
bool cLineBlockTracer::Trace(cWorld & a_World, cBlockTracer::cCallbacks &a_Callbacks, double a_StartX, double a_StartY, double a_StartZ, double a_EndX, double a_EndY, double a_EndZ)
{
cLineBlockTracer Tracer(a_World, a_Callbacks);

View File

@ -33,6 +33,19 @@ class cLineBlockTracer :
typedef cBlockTracer super;
public:
enum eLineOfSight
{
// 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
// Common combinations:
losAirWaterLava = losAir | losWater | losLava,
losAirWater = losAir | losWater,
};
cLineBlockTracer(cWorld & a_World, cCallbacks & a_Callbacks);
/** Traces one line between Start and End; returns true if the entire line was traced (until OnNoMoreHits()) */
@ -46,6 +59,24 @@ public:
/** 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);
/** 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);
/** 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.
If a_End is encountered without hitting any solid block, returns false and doesn't touch a_HitCoords, a_HitBlockCoords nor a_HitBlockFace.
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(
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 */
double m_StartX, m_StartY, m_StartZ;
@ -63,7 +94,7 @@ protected:
int m_CurrentX, m_CurrentY, m_CurrentZ;
/** The face through which the current block has been entered */
char m_CurrentFace;
eBlockFace m_CurrentFace;
/** Adjusts the start point above the world to just at the world's top */

View File

@ -5,7 +5,7 @@
#include "../World.h"
#include "../Entities/Player.h"
#include "../Tracer.h"
#include "../LineBlockTracer.h"
@ -70,17 +70,20 @@ void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
CheckEventSeePlayer(a_Chunk);
}
if (GetTarget() == nullptr)
auto target = GetTarget();
if (target == nullptr)
{
return;
}
cTracer LineOfSight(GetWorld());
// TODO: Currently all mobs see through lava, but only Nether-native mobs should be able to.
Vector3d MyHeadPosition = GetPosition() + Vector3d(0, GetHeight(), 0);
Vector3d AttackDirection(GetTarget()->GetPosition() + Vector3d(0, GetTarget()->GetHeight(), 0) - MyHeadPosition);
if (TargetIsInRange() && !LineOfSight.Trace(MyHeadPosition, AttackDirection, static_cast<int>(AttackDirection.Length())) && (GetHealth() > 0.0))
Vector3d TargetPosition = target->GetPosition() + Vector3d(0, target->GetHeight(), 0);
if (
TargetIsInRange() &&
cLineBlockTracer::LineOfSightTrace(*GetWorld(), MyHeadPosition, TargetPosition, cLineBlockTracer::losAirWaterLava) &&
(GetHealth() > 0.0)
)
{
// Attack if reached destination, target isn't null, and have a clear line of sight to target (so won't attack through walls)
Attack(a_Dt);

View File

@ -3,7 +3,7 @@
#include "Enderman.h"
#include "../Entities/Player.h"
#include "../Tracer.h"
#include "../LineBlockTracer.h"
@ -29,9 +29,8 @@ public:
return false;
}
Vector3d Direction = m_EndermanPos - a_Player->GetPosition();
// Don't check players who are more then SightDistance (64) blocks away
// Don't check players who are more than SightDistance (64) blocks away
auto Direction = m_EndermanPos - a_Player->GetPosition();
if (Direction.Length() > m_SightDistance)
{
return false;
@ -43,19 +42,16 @@ public:
return false;
}
Vector3d LookVector = a_Player->GetLookVector();
double dot = Direction.Dot(LookVector);
// 0.09 rad ~ 5 degrees
// If the player's crosshair is within 5 degrees of the enderman, it counts as looking
if (dot <= cos(0.09))
auto LookVector = a_Player->GetLookVector();
auto dot = Direction.Dot(LookVector);
if (dot <= cos(0.09)) // 0.09 rad ~ 5 degrees
{
return false;
}
cTracer LineOfSight(a_Player->GetWorld());
if (LineOfSight.Trace(m_EndermanPos, Direction, static_cast<int>(Direction.Length())))
// TODO: Check if endermen are angered through water in Vanilla
if (!cLineBlockTracer::LineOfSightTrace(*a_Player->GetWorld(), m_EndermanPos, a_Player->GetPosition(), cLineBlockTracer::losAirWater))
{
// No direct line of sight
return false;

View File

@ -35,7 +35,10 @@ public:
cTracer(cWorld * a_World);
~cTracer();
/** Determines if a collision occures along a line. Returns true if a collision occurs. */
// tolua_end
/** Determines if a collision occures along a line. Returns true if a collision occurs.
Exported manually to add deprecation warnings. */
bool Trace(const Vector3f & a_Start, const Vector3f & a_Direction, int a_Distance)
{
return Trace(a_Start, a_Direction, a_Distance, false);
@ -44,9 +47,12 @@ public:
/** Determines if a collision occures along a line. Returns true if a collision occurs.
When a_LineOfSight is true, we don't use the standard collision detection rules. Instead we use
the rules for monster vision. E.g. Only water and air do not block vision.
a_Distance is the number of iterations (blocks hits) that are tested. */
a_Distance is the number of iterations (blocks hits) that are tested.
Exported manually to add deprecation warnings. */
bool Trace(const Vector3f & a_Start, const Vector3f & a_Direction, int a_Distance, bool a_LineOfSight);
// tolua_begin
private:
/** Preps Tracer object for call of Trace function. Only used internally. */

View File

@ -13,6 +13,7 @@
#include "Generating/ChunkDesc.h"
#include "SetChunkData.h"
#include "DeadlockDetect.h"
#include "LineBlockTracer.h"
// Serializers
#include "WorldStorage/ScoreboardSerializer.h"
@ -50,11 +51,6 @@
#include "Bindings/PluginManager.h"
#include "Blocks/BlockHandler.h"
#include "Tracer.h"
// DEBUG: Test out the cLineBlockTracer class by tracing a few lines:
#include "LineBlockTracer.h"
#ifndef _WIN32
#include <stdlib.h>
#endif
@ -3190,11 +3186,8 @@ bool cWorld::DoWithPlayerByUUID(const AString & a_PlayerUUID, cLambdaPlayerCallb
// TODO: This interface is dangerous!
cPlayer * cWorld::FindClosestPlayer(const Vector3d & a_Pos, float a_SightLimit, bool a_CheckLineOfSight)
{
cTracer LineOfSight(this);
double ClosestDistance = a_SightLimit;
cPlayer * ClosestPlayer = nullptr;
@ -3208,22 +3201,23 @@ cPlayer * cWorld::FindClosestPlayer(const Vector3d & a_Pos, float a_SightLimit,
Vector3f Pos = (*itr)->GetPosition();
double Distance = (Pos - a_Pos).Length();
if (Distance < ClosestDistance)
// If the player is too far, skip them:
if (Distance > ClosestDistance)
{
if (a_CheckLineOfSight)
{
if (!LineOfSight.Trace(a_Pos, (Pos - a_Pos), static_cast<int>((Pos - a_Pos).Length())))
{
ClosestDistance = Distance;
ClosestPlayer = *itr;
}
}
else
{
ClosestDistance = Distance;
ClosestPlayer = *itr;
}
continue;
}
// Check LineOfSight, if requested:
if (
a_CheckLineOfSight &&
!cLineBlockTracer::LineOfSightTrace(*this, a_Pos, Pos, cLineBlockTracer::losAirWater)
)
{
continue;
}
ClosestDistance = Distance;
ClosestPlayer = *itr;
}
return ClosestPlayer;
}