Raycast #
Raycasts are used to simulate rays and determine which selection boxes they intersect with.
With some limitations, they work almost exactly like the player pointing at things.
Pointed Thing #
Pointed things in-game as passed to various callbacks are either
- Nothing (
{type = "nothing"}
), or - Objects (entities or players), or
- Nodes (solids or liquids)
Pointed thing tables are thus a “tagged union” type, taking the following forms for objects & nodes:
Objects #
type
-{type-string}
:"object"
ref
- ObjectRef: The pointed ObjectRef
Nodes #
type
-{type-string}
:"node"
under
-{type-vector}
: The position of the pointed node (the node which would be dug if using an appropriate tool)above
-{type-vector}
: The position of the pointed node plus the pointed face normal (the position of the node which would be placed if you were building)
Additional Raycast Fields #
These fields are only available in pointed things returned by raycasts.
box_id
-{type-number}
: Only relevant for nodes: 1-indexed ID of the pointed selection box of the nodeintersection_normal
-{type-vector}
: Normal vector pointing out of the face of the pointed thing the ray first hitintersection_point
-{type-vector}
: Point on both face & ray where the ray & the selection box(es) of the pointed thing first intersect (may be in the selection box if the raycast starts in the box)
Raycasts do not emit “nothing” pointed things.
core.line_of_sight(pos1, pos2)
#
Checks whether any non-air node is blocking the line of sight between two positions.
You may use this to determine e.g. whether a target would be visible to a mob. Raycasts will often be preferable since they provide more fine-grained control (objects, liquids, looping over possibly blocking things in the line of sight).
Arguments:
pos1
-{type-vector}
: Starting position of the line (“segment”) of sightpos2
-{type-vector}
: Ending position of the line (“segment”) of sight
Returns:
free_sight
-{type-bool}
: Whether any node is blocking the sightpos
-{type-vector}
: Position of the “first” node (the node closest topos1
) blocking the sight (only returned iffree_sight
isfalse
)
Raycast(from_pos, to_pos, include_objects, include_liquid_nodes)
#
Creates a raycast object; OOP-constructor-style alias for core.raycast
.
Tip
Have selection boxes, collision boxes (and ideally even visuals) match for all nodes & entities if possible.
Arguments:
from_pos
-{type-vector}
: Starting position of the raycast (usually eye position)to_pos
-{type-vector}
: Ending position of the raycast (usually eye position + look direction times range)include_objects
-{type-bool}
: Whether objects are included (considered pointable). Optional, defaults totrue
.include_liquids
-{type-bool}
: Whether liquids are included (considered pointable). Optional, defaults totrue
.
Returns:
ray
- Raycast iterator object: Non-restartable, stateful iterator of pointed things
The Raycast iterator object is callable. Calling ray()
is syntactic sugar for ray:next()
.
ray:next()
returns the next pointed thing or {type-nil}
if there is none and advances the iterator state.
Pointed things are returned sorted by the intersection point, from from_pos
to to_pos
, from nearest to farthest.
Usage #
Since raycasts are iterators, you can loop over them using a for
-loop. You will usually use the concise
local ray = Raycast(...)
for pointed_thing in ray do
-- do something
end
which is just shorthand for
local ray = Raycast(...)
for pointed_thing in ray.next, ray do
-- do something
end
which again is syntactic sugar for
local ray = Raycast(...)
local pointed_thing = ray:next()
while pointed_thing ~= nil do
-- do something
pointed_thing = ray:next()
end
There is absolutely no need to manually step through raycasts using the latter two more verbose loops.
Info
Restartability is the ability of an iterator to start again. For example, ipairs
is resumable:
local t = {1, 2, 3}
for _ = 1, 3 do
for i, v in ipairs(t) do
print(i, v)
end
end
will work just expected, printing the contents of t
three times.
Raycasts are not restartable. Iterating them “consumes” the pointed things; they can’t be iterated again. The following will not work as expected:
local ray = Raycast(...)
for _ = 1, 3 do
for pt in ray do
print(pt)
end
end
this will print the pointed things (table addresses) exactly once: after the first run of the inner for
loop, all pointed things have been consumed; subsequent runs exit immediately, not entering the for
loop at all.
One advantage of this is that Raycasts remember their looping state. The following is thus possible:
local ray = Raycast(...)
for pt in ray do
if ... then break end
end
for pt in ray do
-- will resume looping with the next pointed thing
end
Examples #
Redo tool raycasts #
Useful to obtain the additional raycast pointed thing fields, to dynamically decide what is pointable and what is not, to determine the pointed thing periodically (“what am I looking at?”) etc.
-- Calculate eye position including eye offset
local eye_pos = player:get_pos()
eye_pos.y = eye_pos.y + player:get_properties().eye_height
local first, third = player:get_eye_offset()
if not vector.equals(first, third) then
core.log("warning", "First & third person eye offsets don't match, assuming first person")
end
eye_pos = vector.add(eye_pos, vector.divide(first, 10)) -- eye offsets are in block space (10x), transform them back to metric
local def = player:get_wielded_item():get_definition()
for pointed_thing in core.raycast(eye_pos, vector.add(eye_pos, vector.multiply(player:get_look_dir(), def.range or 4)), true, def.liquids_pointable) do
if pointed_thing.ref ~= player then -- exclude the player
-- do something
end
end
(TODO: compare players by player name rather than by reference in the above example?)