diff --git a/Server/Plugins/APIDump/Classes/Geometry.lua b/Server/Plugins/APIDump/Classes/Geometry.lua index cd7793ee9..32a4bbb27 100644 --- a/Server/Plugins/APIDump/Classes/Geometry.lua +++ b/Server/Plugins/APIDump/Classes/Geometry.lua @@ -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.

-

-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.

-

- The cTracer is still a work in progress and is not documented at all.

-

- See also the {{cLineBlockTracer}} class for an alternative approach using callbacks. + This class is OBSOLETE, do not use it. + See the {{cLineBlockTracer}} class for the replacement. ]], Functions = { - + Trace = + { + Notes = "OBSOLETE, use the {{cLineBlockTracer}} class instead.", + }, }, }, Vector3d = diff --git a/Server/Plugins/Debuggers/Debuggers.lua b/Server/Plugins/Debuggers/Debuggers.lua index 7d7246484..fb18a0c19 100644 --- a/Server/Plugins/Debuggers/Debuggers.lua +++ b/Server/Plugins/Debuggers/Debuggers.lua @@ -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) diff --git a/Server/Plugins/Debuggers/Info.lua b/Server/Plugins/Debuggers/Info.lua index 8b2e7017b..dea656837 100644 --- a/Server/Plugins/Debuggers/Info.lua +++ b/Server/Plugins/Debuggers/Info.lua @@ -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, diff --git a/src/Bindings/DeprecatedBindings.cpp b/src/Bindings/DeprecatedBindings.cpp index 0ffc58cbb..029d4edb7 100644 --- a/src/Bindings/DeprecatedBindings.cpp +++ b/src/Bindings/DeprecatedBindings.cpp @@ -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", 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); diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index c2392063a..1ad427331 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -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")) + { + // This is the Vector3d-based variant of the call: + if ( + !L.CheckParamUserType(idx + 1, "Vector3", 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")) + { + // This is the Vector3d-based variant of the call: + if ( + !L.CheckParamUserType(idx + 1, "Vector3", 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"); diff --git a/src/BlockTracer.h b/src/BlockTracer.h index 7444af488..2cb97c569 100644 --- a/src/BlockTracer.h +++ b/src/BlockTracer.h @@ -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. diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 7b1849b9f..dff7114d5 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -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 - // - 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); diff --git a/src/Entities/ProjectileEntity.cpp b/src/Entities/ProjectileEntity.cpp index 3bded2b56..64522acef 100644 --- a/src/Entities/ProjectileEntity.cpp +++ b/src/Entities/ProjectileEntity.cpp @@ -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: diff --git a/src/Items/ItemBoat.h b/src/Items/ItemBoat.h index ced5ec3a8..493605483 100644 --- a/src/Items/ItemBoat.h +++ b/src/Items/ItemBoat.h @@ -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) { diff --git a/src/Items/ItemBottle.h b/src/Items/ItemBottle.h index d2ca30def..092d5d81a 100644 --- a/src/Items/ItemBottle.h +++ b/src/Items/ItemBottle.h @@ -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)) { diff --git a/src/Items/ItemBucket.h b/src/Items/ItemBucket.h index 180a363ec..18b7bb46f 100644 --- a/src/Items/ItemBucket.h +++ b/src/Items/ItemBucket.h @@ -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(a_CBEntryFace); if (!cFluidSimulator::CanWashAway(a_CBBlockType) && !IsBlockLiquid(a_CBBlockType)) { - AddFaceDirection(a_CBBlockX, a_CBBlockY, a_CBBlockZ, static_cast(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)) { diff --git a/src/Items/ItemLilypad.h b/src/Items/ItemLilypad.h index 7eede5864..3d07efb03 100644 --- a/src/Items/ItemLilypad.h +++ b/src/Items/ItemLilypad.h @@ -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)) { diff --git a/src/LineBlockTracer.cpp b/src/LineBlockTracer.cpp index c2e078faf..42e827cb9 100644 --- a/src/LineBlockTracer.cpp +++ b/src/LineBlockTracer.cpp @@ -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); diff --git a/src/LineBlockTracer.h b/src/LineBlockTracer.h index 37493ce5c..2851cfbd7 100644 --- a/src/LineBlockTracer.h +++ b/src/LineBlockTracer.h @@ -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 */ diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp index d8bdc4af5..fec14e6e9 100644 --- a/src/Mobs/AggressiveMonster.cpp +++ b/src/Mobs/AggressiveMonster.cpp @@ -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(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); diff --git a/src/Mobs/Enderman.cpp b/src/Mobs/Enderman.cpp index f58e2132c..3c0717f8f 100644 --- a/src/Mobs/Enderman.cpp +++ b/src/Mobs/Enderman.cpp @@ -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(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; diff --git a/src/Tracer.h b/src/Tracer.h index 8d1f494f7..e4ff1b12c 100644 --- a/src/Tracer.h +++ b/src/Tracer.h @@ -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. */ diff --git a/src/World.cpp b/src/World.cpp index 2614ead7b..447e2cf25 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -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 #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((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; }