DRAFT DRAFT DRAFT
Lunatic is in preview stage. Modifications or TCs may be started, but must not be released. The interfaces (including constants, functions, methods and structure members) described in this document may be assumed to not change significantly until release, but undocumented API may change without further notice.
DRAFT DRAFT DRAFT
1. Introduction
2. Language
The base language for writing Lunatic user code is Lua 5.1. It is extensively documented in a Reference Manual, as well as described more broadly and accessibly in the Programming in Lua books.
Because Lunatic is implemented using LuaJIT, a just-in-time compiler
for the Lua language, some extensions to the core language are
automatically available. They may be used if no compatibility with Rio Lua 5.1
is desired.
[Not all extensions from LuaJIT are available, since some
like the FFI are targeted at C programmers rather than scripting coders.]
The length operator (# ) for table arguments should be taken to be
defined by the stricter wording
of Lua 5.2. |
3. Environment and Usage
Lunatic aims to provide a safe scripting environment for EDuke32. Thus, not all of Lua’s functionality is available in Lunatic, and some is slightly altered to play well with being embedded into a game, or to prevent commonly occurring mistakes.
-
Creating new variables in the global environment is forbidden.
-
Referencing a non-existent variable (i.e. one that has value nil) is an error.
EDuke32 can load multiple Lunatic modules when starting up. This is always
done after translating CON code to Lua and loading it. Such modules must reside
in files named *.lua
(case-sensitive) and should be passed directly, without
any option letters, at the command line. Directory separators must be forward
slashes.
The command-line option -Lopts=strict
enables errors on certain conditions
that may indicate problems in user Lua or CON code. Currently, strict mode
checks for
-
Accesses to sprites not in the game world
-
Indexing the tables returned by
require("CON.DEFS")
and the like with nonexistent keys. This may indicate a missing file from a bundle belonging together.
eduke32 -nologo MYTC.CON -mx addition.con test.lua weapons/nuke.lua -v1 -l1
If the OS environment variable LUNATIC_TIMING_BASEFN
is defined, EDuke32 will
write out aggregate timing results for actors and events in a comma-separated
value format, obtained by suffixing the base name with “.actors.csv
” and
“.events.csv
”.
4. Global Environment
When a Lua module is loaded, its global environment contains both selected functions from core Lua, as well as Lunatic’s own pre-defined variables. These allow access and manipulation of EDuke32 engine and game data.
If an attempt is made to create a new variable in the global environment or to assign any value to an existing variable, the behavior is undefined. |
For convenience of debugging from the command line (using the lua
OSD
command), the global environment contains a table d
which is initially
empty. It should only be used to temporarily store values of interest in a
debugging session and should never be referenced from actual script code.
4.1. Lua functions
The following base Lua functions are available in Lunatic’s global environment:
assert
, error
, ipairs
, pairs
, pcall
, print
, module
, next
,
require
, select
, tostring
, tonumber
, type
, unpack
, xpcall
.
The bold ones add functionality or behave slightly differently, described below.
Additionally, printf
is provided for convenience.
-
error(message [, level])
-
In Lunatic, errors also print to the on-screen display (OSD) and are written to the log (unless a maximum error count is exceeded). These also have a backtrace added. Additionally, errors not caught by a
pcall
result a permanent message to appear on the screen, containing the source file name and line number. -
print(str)
-
Prints a single message to the OSD and the log. Color codes available in EDuke32 (e.g.
^10
for dark red) are interpreted. Overridingtostring
has no effect on Lunatic’sprint
as it uses the initial, built-intostring
function instead of looking it up in the global environment. -
printf(fmt, ...)
-
Calls
print
with the result ofstring.format(fmt, ...)
.
4.2. Writing and using modules
In Lunatic, like in Lua, a module is a conceptually a sort of package that unites related bits of functionality. Language-wise, it is simply a Lua table holding its contents, as described and motivated at length in Programming in Lua (second edition).
The “creation” and “usage” sides of the modularity concept are reflected in
two functions known from Lua 5.1, module
and require
. The former is a
convenient way of separating the import, potential gamevar, and main body
sections when writing a package. It is not required to use module
, but it is
the only way to declare game variables, that is, variables that EDuke32
stores within savegames, recreating their values when they are loaded.
The other side of the coin, require
, is also used like in Lua: “including”
a named module is requested by passing its name to require
. This searches for
a matching module, loading and running it if the request happens for the first
time.
4.2.1. The function require(modname, ...)
Attempts to find a Lua or Lunatic module named modname
. The name can refer to
a built-in module, of which the following ones are allowed:
-
The
bit
module for bitwise operations -
math
,string
andtable
, base modules from Lua -
Lua’s
os
module, containing a single function,clock
. Like the timing functions, it should only be used to profile bits of code. -
Modules provided by Lunatic, which are described in their own section.
If modname
does not designate a built-in module, Lunatic first replaces every
dot contained in it with a directory separator. Then, it looks for a file with
that base name suffixed with .lua
in the EDuke32 search path (virtual file
system, GRP, ZIP). Using directory separators directly is not allowed.
The loaded module is protected so that write accesses to its table yield
errors. Unlike in Lua, our require
does not return true when a module is
requested that has not yet finished loading (that is, the inclusion chain
contains a loop). Instead, an error is raised.
Lunatic’s require
allows passing additional arguments to the module to load.
On the module side, they can be obtained by examining the vararg expression
“...
” at file scope. Given a definition of args
as {...}
, its first
element args[1]
would contain modname
and the following entries the values
passed in addition to require
. This feature is useful for parametrizing a
module: for example, the module could provide a way alter the starting tile
number of an actor it defines.
Issuing require
for some special names listed below has a predefined meaning
that cannot be overridden by the user.
-
CON.DEFS
: returns a table mapping labelsdefine
d from CON to their values. Values pre-defined by the system are not included. -
CON.ACTION
: returns a table mapping labels ofaction
s defined from CON to immutablecon.action
objects. -
CON.MOVE
: returns a table mapping labels ofmove
s defined from CON to immutablecon.move
objects. -
CON.AI
: returns a table mapping labels ofai
s defined from CON to immutablecon.ai
objects. -
end_gamevars
: used to mark the end of a gamevar block, described below.
4.2.2. The module()
function
Initiates a module environment by creating a new empty table and setting it
as the global environment of the chunk. Subsequently creating global variables
will place them into this “hidden” table. Assuming no subsequent return
is placed at file scope, this table will be the one obtained by a require
for
the module at the client side.
module
-- Import section: cache everything the module needs into locals local print = print local gameactor = gameactor local MYDEFS = require("MyEnemyDefs", 1200) -- see next example -- After this, the global environment will be swept clean! module(...) -- Our enemy at last gameactor{ MYDEFS.tilenum, strength=MYDEFS.strength, function(aci) print("My tilenum is "..sprite[aci].picnum.." and I " ..(MYDEFS.canfly and "can" or "can't").." fly") end }
Unlike Lua 5.1, our module
takes neither a name argument nor “option”
varargs. A Lunatic file may have at most one call to module
, which (if there
is one) must be called at file scope.
If the Lua file doesn’t need to register any game variables, it is also
possible to return its table directly instead of using module
. However, due
to the way modules are loaded, a trailing return
statement must be wrapped
in a do
…end
block, like this:
MyEnemyDefs.lua
explicitly returning a tablelocal args = { ... } do return { tilenum = args[1] or 1000, strength = 100, canfly = true, } end
Game variables
Lunatic has a special mechanism to mark variables that represent persistent
state and whose values should be stored in savegames. If such variables are
desired, they must be initialized between the module
call in a Lua file and a
closing require("end_gamevars")
.
[The reason that the initialization
has to happen between the module
and the require('end_gamevars')
is that on
savegame loading, gamevars are restored from the latter.]
These variables may
also be local
.
Game variables may take on only values of types that Lunatic knows how to serialize into savegames. These are the following:
-
Booleans, numbers, and strings, collectively called the basic types
-
Custom Lunatic types that are labeled serializable in their documentation
-
Tables, but with the following restrictions on their contents:
-
A table key may only be of basic type.
-
A table value may be (a reference to) any serializable object, but tables or Lunatic objects that are so referenced must have originated in the gamevar section of the same module. Beyond that, there are no restrictions on the table topology.
-
If a gamevar contains a value that is not serializable at any point in the execution, the behavior is undefined. Note that in particular, gamevars are not allowed to take on the value nil. |
4.3. The gv
variable
Some constants, global C variables, and miscellaneous functions and structures
are accessible via the global gv
variable.
4.3.1. Constants
-
gv.MAXSECTORS
,gv.MAXWALLS
,gv.MAXSPRITES
-
The hard engine limits on the number of sectors, walls, and sprites. These constants must be used instead of any literal numeric values because they can change depending on how EDuke32 was configured and built.
-
gv.CEILING
,gv.FLOOR
,gv.BOTH_CF
-
Constants permissible to the
sectorsofbunch
iterator.
-
gv.CLIPMASK0
-
A clipping (collision detection) mask specifying to consider only blocking walls and sprites.
-
gv.CLIPMASK1
-
A clipping (collision detection) mask specifying to consider only hitscan sensitive walls and sprites. The set bits of
CLIPMASK1
andCLIPMASK0
are disjoint. -
gv.REND
-
A mapping of names to values representing rendering modes:
CLASSIC
,POLYMOST
,POLYMER
.
-
gv.GTICSPERSEC
-
The number of times in a second each actor executes its code and updates its position (“game tics”).
-
gv.GET
-
A mapping of names to inventory indices:
STEROIDS
,SHIELD
,SCUBA
,HOLODUKE
,JETPACK
,DUMMY1
,ACCESS
,HEATS
,DUMMY2
,FIRSTAID
,BOOTS
. -
gv.GET_MAX
-
The maximum permissible inventory index plus one.
-
gv.WEAPON
-
A mapping of names to weapon indices:
KNEE
,PISTOL
,SHOTGUN
,CHAINGUN
,RPG
,HANDBOMB
,SHRINKER
,DEVISTATOR
,TRIPBOMB
,FREEZE
,HANDREMOTE
,GROW
. Note that “DEVISTATOR
” is misspelled. -
gv.MAX_WEAPONS
-
The maximum permissible weapon index plus one.
-
gv.LUNATIC_CLIENT
-
A constant indicating which program Lua is embedded into. It can be compared for (in)equality with:
-
gv.LUNATIC_CLIENT_EDUKE32
-
gv.LUNATIC_CLIENT_MAPSTER32
(Mapster32 supports a subset of Lunatic that is not documented and subject to change at any time.)
-
4.3.2. Variables
-
gv.numsectors
(read-only) -
The total number of sectors in the currently loaded map.
-
gv.numwalls
(read-only) -
The total number of walls in the currently loaded map.
-
gv.totalclock
(read-only) -
The current value of the engine timer that increments at a rate of 120 per second under default settings. (Thus, one game tic corresponds to four
totalclock
increments.) When staying within one “mode” such as in-menu or in-game, it is guaranteed to not decrease. However, going from one mode to another may produce discontinuities. -
gv.gametic
(read-only) -
The number of game tics that have elapsed since starting the current level. This value is guaranteed to not decrease during a game, and is restored from savegames.
-
gv.screenpeek
(read-only) -
The player index of the player from whose position the scene is being displayed.
-
gv.rendmode
(read-only) -
The current rendering mode as a value that can be compared against those in
gv.REND
. -
gv.hudweap
-
A structure containing information about the currently displayed HUD weapon. Contains the following members, which are set from C before entering
EVENT_DISPLAYWEAPONS
:cur
,count
,gunposx
,gunposy
,lookhalfang
,lookhoriz
,shade
. -
gv.cam
-
A structure that, prior to entering
EVENT_DISPLAYROOMS
, is populated with the position and orientation of the “camera” from which the scene would be drawn. Contains the following members:pos
,dist
,clock
,ang
,horiz
,sect
.
-
gv.RETURN
-
A special variable that is used by the game to pass specific values to game events, or to examine values passed back from events and act upon them accordingly. Refer to Appendix B for a list of how the various events interpret this variable.
4.3.3. Functions
-
gv.getangle(x, y)
-
Returns an approximation of the angle between the line segments (0,0)→(1,0) and (0,0)→(
x
,y
) in BUILD angle units in the range [0 .. 2047]. -
gv.krand()
-
Returns one value from the global engine-side pseudo-random number generator in the integer range [0 .. 65535].
-
gv.getticks()
,gv.gethiticks()
-
Each of these functions return a number that increases at a rate of 1 per millisecond. Their only intended application is to profile bits of code; they should not be used to control the game world. The two functions differ in their precision:
getticks()
always returns integral values, while the result ofgethiticks()
also has an unspecified precision in the fractional part. (It can be expected to give a time precision of at least one microsecond.) -
gv.doQuake(gametics [, snd])
-
Requests from the game to perform the “quake” effect that shakes the screen etc. for the next
gametics
game tics. If a sound indexsnd
is passed, also start playing that sound. -
gv.currentEpisode()
-
Returns the one-based number of the episode currently being played.
-
gv.currentLevel()
-
Returns the one-based number of the level currently being played.
4.4. Lunatic structures
The primary means of effecting game state in Lunatic is via composite variables defined in the global environment. These provide direct, but restricted, access to C structure arrays of the EDuke32 engine or game.
If an attempt is made to access any composite variable outside of event or actor code, the behavior is undefined. |
Composite variables can be used in various ways. All of them allow indexing
with an integer value from 0
to some maximum (sometimes the size of the array
minus one, but occasionally less). For example, the code snippet
local sec = sector[0]
gets a reference to the first sector of the loaded map into the local sec
.
This reference can then be used to both read and write its members.
Various structures also provide methods in Lunatic to modify their state,
usable with Lua’s v:func(args...)
syntax. Building on the previous example,
local cz = sec:ceilingzat(wall[sec.wallptr])
would get into cz
the ceiling z position at the first sector’s first
wall-point.
Finally, some composite variables offer static data, which can contain
functions or tables of constants. These are accessed using the dot notation on
the composite variable, not its constituents. For instance, the following can
be used to change the sector number of the sprite with index i
manually:
sprite.changesect(i, sectnum)
4.4.1. Type of structure members
In the following, some structure members will be annotated with their integer
type, for example i16
or u8
. The letter i
denotes a signed
integer whereas a u
designates an unsigned one. The number following that
letter indicates the bit width of that integer.
-
A member of signed integer type and bit width B can contain any whole number from –2B–1 to 2B–1–1.
-
A member of unsigned integer type and bit width B can contain any whole number from 0 to 2B–1.
-
If an assignment to a member having signed integer type is made, the “right-hand side” value must be a number in the closed interval [–231 .. 231–1].
-
If an assignment to a member having unsigned integer type and bit width B is made, the “right-hand side” value must be in the closed interval [–231 .. 231–1] if B is less than 32, or in [0 .. 232–1] otherwise.
-
If the appropriate requirements hold, an assignment from a Lua number to a member having integer type begins by discarding the fractional part (“truncation”). Otherwise, the behavior is undefined.
-
If the truncated value is outside the range of representable values for the corresponding integer type of bit width B, the final value is obtained by successively adding or subtracting 2B, until the value falls inside that range.
-
Assignments to
u8
membervisibility
-
sec.visibility=3.94159
results the member to contain the integer3
-
sec.visibility=1/0
is undefined (attempt to assign an infinity) -
sec.visibility=-1
results the member to contain the integer255
-
-
Assignments to
i16
memberlotag
-
sec.lotag=32768
results the member to contain-32768
-
sec.lotag=2^32
is undefined
-
4.4.2. Bit fields
Some structures contain members that represent a collection of flags, for
example sprite[].cstat
or actor[].flags
. These flags can be toggled by
setting or clearing their respective bits (integer numbers that are powers of
two).
For convenience, Lunatic provides alternative names for some of these members,
together with methods to examine or modify any number of bits of such a member
in one expression. Whenever there is such an alternative name available, it is
declared as having type bitfield
in the listings of the structure members.
methods of the bitfield
type
-
bf:set(bits)
-
Sets (toggles to an “on” state in a boolean sense) those bits of
bf
that are set inbits
. -
bf:clear(bits)
-
Clears (toggles to an “off” state in a boolean sense) those bits of
bf
that are set inbits
. -
bf:flip(bits)
-
Flips those bits of
bf
that are set inbits
, that is, reverses their boolean state. -
bf:test(bits)
-
Returns a boolean that indicates whether
bf
has any of the bits set inbits
set. -
bf:mask(bits)
-
Returns a number containing the bits of
bf
bitwise ANDed with those inbits
.
After the lines setting sprite i
to 33% translucent and blocking,
local CS = sprite.CSTAT local spr = sprite[i] spr.cstat = CS.TRANS1 + CS.BLOCK
one could proceed as follows for the sake of example:
spr.cstatbits:set(CS.TRANS2) -- make the sprite 66% translucent now spr.cstatbits:flip(CS.BLOCK + CS.HITSCAN) -- make it hitscan-sensitive and non-blocking local isflipped = spr.cstatbits:test(CS.FLIP_BITMASK) -- is it x- or y-flipped? (no)
4.4.3. Engine-side composites
The composite variables described in this subsection provide access to
engine-side structures. The first three, sector
, wall
, and sprite
,
are part of a BUILD map saved to disk. The other ones only exist when
running the game.
sector
Accessible from 0
to gv.numsectors-1
. Each element has the following
members:
-
wallptr
,wallnum
(read-only) -
The index of the sector’s first wall and the number of walls in the sector, respectively.
-
u8
visibility
-
Determines the amount of distance fading. The sector
visibility
member is biased: linear visibility is determined from it by adding 16 and taking the result mod 16. This linear visibility’s interpretation is that larger values correspond to a steeper “darkening” (or fogging) attenuation with distance. Linear visibility 0 — corresponding tosector[].visibility
of 240 — means no attenuation with distance. -
u8
fogpal
-
In the OpenGL modes that don’t use per-pixel shade table lookups, setting
fogpal
to a non-zero value overrides the fog color of the sector to that of the corresponding pal number. These are either expected to have been defined using DEF tokensfogpal
ormakepalookup
, or created by EDuke32 at startup. -
i16
lotag
,hitag
,extra
-
General-purpose “tags” provided for game programming. They may be used by various EDuke32 sector effects, so it is not recommended to use them in scripting code.
In addition to the members described above, each sector has two sets of members
for its ceiling and floor. A sector reference can be indexed with the strings
ceiling
or floor
to get references to the respective “parts”, or one can
access the consituent members by prefixing ceiling
or floor
to the base
member names given below.
After the code lines
local sec = sector[0] local ceil = sec.ceiling
the following expressions all denote the same location, both if read or written
to: sec.ceilingheinum
, ceil.heinum
, sector[0].ceiling.heinum
,
sector[0].ceilingheinum
.
In the following, cf
will stand for a ceiling or floor reference, while sec
will label a sector reference.
-
cf.picnum
(read-only) -
The tile number of the ceiling or floor.
-
u16
cf.stat
,bitfield
cf.statbits
-
A bit field holding various flags about how the ceiling or floor should be displayed, how collision detection should be handled, etc. The
sector.STAT
object should be used to obtain the values for applicable flags. -
i16
cf.heinum
-
If
cf.stat
has bitsector.STAT.SLOPE
set, the tangent of the slope angle, multiplied by 4096. Positive values make the ceiling or floor slope towards the floor, negative ones slope upward. -
i32
cf.z
-
The BUILD z coordinate (scaled by 16 compared to the x and y directions) of the pivoting line of the ceiling or floor.
-
cf.bunch
(read-only) -
The “bunch” number of the ceiling or floor used for True Room over Room. One bunch comprises N ceilings and M floors (N ≥ 1, M ≥ 1) such that each set covers the same planar, connected area.
-
i8
cf.shade
-
The shade of the ceiling or floor. Larger values mean a more darker appearance.
-
u8
cf.pal
-
The “palette swap” index of the ceiling or floor.
-
u8
cf.xpanning
,cf.ypanning
-
The panning values of the ceiling or floor. One full cycle is covered by values from
0
to255
.
ceiling-or-floor methods
-
cf:set_picnum(tilenum)
-
Set the tile number of the ceiling-or-floor
cf
.
sector
methods
-
sec:set_ceilingpicnum(tilenum)
,sec:set_floorpicnum(tilenum)
-
Set the tile number of the ceiling or the floor.
-
sec:ceilingzat(pos)
,sec:floorzat(pos)
-
Return the z coordinate of sector
sec
's ceiling or floor at positionpos
, which can be anything indexable with the stringsx
andy
. -
sec:zrangeat(pos, walldist, clipmask)
→hit
-
Given a starting point
pos
assumed to be contained in the sector, calculate the z coordinates of the objects that would be first hit by a quadratic, floor-aligned sprite pointing parallel to the grid and having side length2*walldist
, when travelling in a straight line up- and downwards.The argument
clipmask
is a number specifying which objects should be checked for collision: its least significant 16 bits are bitwise-ANDed withwall[].cstat
values, while the high 16 bits are ANDed withsprite[].cstat
. Whenever the so masked values are non-zero, the objects are considered for collision.The method returns an immutable structure
hit
, containing the sub-structuresc
andf
(for movement upwards and downwards, respectively) with the following members:-
spritep
: a boolean signifying whether a sprite was hit -
num
: ifspritep
is true, the index of the hit sprite, otherwise the index of the hit ceiling/floor’s sector -
z
: the z coordinate of where the sprite would end up on collision
-
sector
static data
-
sector.STAT
-
Provides a mapping of symbolic names to values applicable to
cf.stat
. These name single bits:PARALLAX
,SLOPE
,SWAPXY
,SMOOSH
,FLIPX
,FLIPY
,RELATIVE
,MASK
,TRANS1
,TRANS2
,BLOCK
,HITSCAN
, while the following denote bit masks:FLIP_BITMASK
,ORIENT_BITMASK
,TRANS_BITMASK
. -
sector.UPDATE_FLAGS
-
Contains flags permissible to
updatesector
and other sector updating functions.BREADTH
,Z
.
wall
Accessible from 0
to gv.numwalls-1
. Each element has the following
members:
-
i32
x
,y
-
The 2D coordinates or this wall point. Should not be set directly.
-
z
(read-only) -
Always yields
0
. The primary purpose of this field is to make wall references permissible as arguments toxmath
vector operations. -
point2
(read-only) -
The index of the second wall point.
-
nextwall
,nextsector
(read-only) -
If the wall is “white”, these members equal
-1
. For “red” walls, they contain the wall and sector indices (respectively) of the wall on the other side. -
upwall
,dnwall
(read-only) -
For walls constrained by TROR extension, the upper and lower neighbor walls, respectively. Any of them may be
-1
, meaning that the wall is not attached to a neighbor in this direction. -
u16
cstat
,bitfield
cstatbits
-
A bit field holding various flags about how the wall should be displayed, how collision detection should be handled, etc. The
wall.CSTAT
object should be used to obtain the values for applicable flags. -
picnum
(read-only) -
The tile number of the non-masked portion of the wall. If
wall.CSTAT.BOTTOMSWAP
is set on this wall’s.cstat
, it is only displayed in the upper portion. (The lower portion takes it from this wall’s.nextwall
then; this will be labeled use-other-bottom in the following.) -
overpicnum
(read-only) -
The tile number of the masked portion of the wall, i.e. that which is drawn if this wall’s
.cstat
has bitwall.CSTAT.MASK
orwall.CSTAT.ONEWAY
set. -
i8
shade
(use-other-bottom) -
The shade of the wall for both non-masked and masked portions. Larger values mean a more darker appearance.
-
u8
pal
(use-other-bottom) -
The “palette swap” index of the wall.
-
u8
xrepeat
,yrepeat
-
Values that are proportional to the number of times that the wall’s texture repeats in each direction per given wall length/height. A value of
8
renders 64 texels across a length of 1024 x/y map units or a height of 16384 z units. -
u8
xpanning
,ypanning
(use-other-bottom) -
The panning values of both masked and non-masked portions of the wall. One full cycle is covered by values from
0
to255
. -
i16
lotag
,hitag
,extra
-
General-purpose “tags” provided for game programming. They may be used by various EDuke32 effects internally, so it is advised to do some research before claiming them for oneself.
wall
methods
-
wal:set_picnum(tilenum)
,wal:set_overpicnum(tilenum)
-
Set the tile number of the wall or its masked portion.
wall
static functions
-
wall.dragto(i, pos)
-
Set the position of the point of the wall with index
i
topos
, which can be anything indexable withx
andy
. This function is the preferred way of changing wall coordinates, since it takes care to reposition dependent wall points, too.
wall
static data
-
wall.CSTAT
-
Provides a mapping of symbolic names to values applicable to
wall[i].cstat
. These name single bits:BLOCK
,BOTTOMSWAP
,ALIGNBOTTOM
,FLIPX
,MASK
,ONEWAY
,HITSCAN
,TRANS1
,FLIPY
,TRANS2
, while the following denote bit masks:FLIP_BITMASK
,TRANS_BITMASK
.
sprite
The sprite
composite is accessible with indices from 0
to
gv.MAXSPRITES-1
, but accesses to sprites that do not exist in the game world
have no meaning. Each element has the following members:
-
i32
x
,y
,z
-
The BUILD coordinates of the sprite. It is not advisable to set these directly.
-
u16
cstat
,bitfield
cstatbits
-
A bit field holding various flags about how the sprite should be displayed, how collision detection should be handled, etc. The
sprite.CSTAT
object should be used to obtain the values for applicable flags. -
picnum
(read-only) -
The tile number of the sprite, also used to determine which actor code is run if this sprite has a
statnum
ofactor.STAT.ACTOR
. -
i8
shade
-
The shade of the sprite. This may not be the shade that this sprite is ultimately drawn with, though.
-
u8
pal
-
The “palette swap” index of the sprite. This may not be the palette swap that this sprite is ultimately drawn with, though.
-
u8
blend
-
The blending table index of the sprite. See Blending table interfaces for more details.
-
u8
clipdist
-
In the engine, this member is used in the
clipmove()
function: it controls the distance at which another moving object is considered to be in collision with this view-aligned and stationary sprite.
[Theclipmove()
function is called for the moving object. The stationary one is a candidate for collision out of potentially many.]
It does not control the inverse case: the distance at which the moving object is considered in collision with the stationary one is determined by thewalldist
argument toclipmove()
.
More precisely, it designates half the side-length of the bounding square divided by 4. Thus, a value of255
keeps moving objects away from this one at a max-norm distance
[The max-norm distance between points p1=(x1, y1) and p2=(x2, y2) is defined as max(abs(x2 — x1), abs(y2 — y1)).]
of at least 1020 BUILD x/y units.In the Duke3D game code, it is also used to determine said
walldist
argument for certainclipmove()
calls, i.e. to control (1/8th of) the base side length of the moving sprite’s clipping cuboid: this is the case for-
user actors declared as
actor.FLAGS.enemy
oractor.FLAGS.enemystayput
in the Luagameactor
call (or which were provided the corresponsing flags for CON’suseractor
) having anxrepeat
less than or equal to 60, or -
non-enemies that are either not projectiles, or projectiles that have a certain projectile bit set (TODO; these are not yet documented.)
-
-
u8
xrepeat
,yrepeat
-
The size of the sprite in each dimension. For wall- and floor- aligned sprites, a value of
64
means a width of 16 x/y BUILD units or a height of 256 z BUILD units per texel. -
sectnum
(read-only) -
The index of the sector that this sprite is currently contained in.
-
statnum
(read-only) -
The current status number of this sprite. Applicable values are contained in
actor.STAT
. -
owner
(read-only) -
The index of the sprite from which this sprite was spawned. If this sprite is not a “child” of another one, then
owner
is the index of this sprite itself. -
i16
xvel
,zvel
-
For actors and other moving sprite kinds, the horizontal and vertical components of the current velocity. See the description of
con.move
for more details. -
i16
yvel
(read-only) -
A member used for different purposes by Duke3D. Setting it is only allowed through the
set_yvel
method. -
i16
lotag
,hitag
,extra
-
General-purpose “tags” provided for game programming. They may be used by hard-coded actors internally, so it is advised to do some research before claiming them for oneself.
sprite
methods
-
spr:set_picnum(tilenum)
-
Sets the tile number of sprite
spr
totilenum
. It is disallowed to issueset_picnum
on anAPLAYER
sprite or change the tile number of a sprite toAPLAYER
. -
spr:set_yvel(yvel)
-
Sets the
yvel
member of spritespr
toyvel
. It is disallowed to issueset_yvel
on anAPLAYER
sprite. Further restrictions are reserved. -
spr:getheightofs()
→height
,offset
-
Returns the
height
and zoffset
of spritespr
in BUILD z units. Adding theoffset
tospr.z
yields the z coordinate at the bottom of the sprite. Subtracting from that theheight
results the z coordinate at its top. However, the per-tile z offset is not taken into account. -
spr:setpos(pos [, newsect])
-
Unconditionally sets the position of
spr
topos
, which can be anything indexable withx
,y
andz
. Thus, in effect a shorthand forspr.x, spr.y, spr.z = pos.x, pos.y, pos.z
If
newsect
is passed, additionally callsspr:changesect(newsect)
.
Returnsspr
. -
spr:changesect(sectnum)
-
An alternative way to call
sprite.changesect(index_of_spr, sectnum)
, whereindex_of_spr
is the sprite index corresponding tospr
. This method is provided for convenience, but may be slower than the static functionchangesect
. -
spr:updatesect([flags])
-
An alternative way to call
sprite.updatesect(index_of_spr, flags)
, whereindex_of_spr
is the sprite index corresponding tospr
. This method is provided for convenience, but may be slower than the static functionupdatesect
. -
spr:isenemy()
-
TODO
sprite
iterators
-
for i in sprite.all()
-
Iterates over all sprites currently in the game world.
sprite
static functions
-
sprite.changestat(i, statnum)
-
Allows to manually change the status number of the sprite with index
i
tostatnum
. Ifi
is an invalid sprite index or the index of a sprite not in the game world, orstatnum
is an invalid status number, an error is thrown. -
sprite.changesect(i, sectnum)
-
Allows to manually change the sector number of the sprite with index
i
tosectnum
. Ifi
is an invalid sprite index or the index of a sprite not in the game world, orsectnum
is an invalid sector index, an error is thrown. -
sprite.updatesect(i [, flags])
-
Updates the sector number of the sprite with index
i
, in effect settingsprite[i]
's sector number to the result ofupdatesector(sprite[i].pos, sprite[i].sectnum, flags)
If the
updatesector
call returns-1
, the sprite’s sector number remains unchanged.
Returns the result of theupdatesector
call.
sprite
overridden operators
-
spr^zofs
-
Returns an
xmath.ivec3
object that contains the position of this sprite, diminished byzofs
in the z direction. Because in BUILD, z coordinates increase toward the floor, the^
can be thought of as the position of the sprite “raised byzofs
units”.
sprite
static data
-
sprite.CSTAT
-
Provides a mapping of symbolic names to values applicable to
sprite[i].cstat
. These name single bits:BLOCK
,TRANS1
,XFLIP
,YFLIP
,ALIGNWALL
,ALIGNFLOOR
,ONESIDE
,CENTER
,HITSCAN
,TRANS2
,INVISIBLE
, while the following denote bit masks:ALIGN_BITMASK
,TRANS_BITMASK
.
spriteext
Accessible with the same indices and with the same restrictions as
sprite
.
4.4.4. Game-side composites
actor
The actor
composite holds various run-time data about a sprite. Like
sprite
, it is accessible with indices from 0
to
gv.MAXSPRITES-1
, but accesses to actors that do not exist in the game world
have no meaning. Each element has the following members:
-
u16
movflags
,bitfield
movflagsbits
-
The actor’s current movement flags.
actor
methods
The following methods query or set properties related to actor behavior.
-
a:set_action(act)
-
Sets the action of actor
a
toact
, which may be either an object returned bycon.action
or a number 0 or 1. Resets the actor’s action count and current frame offset to 0. -
a:has_action(act)
-
Returns a boolean of whether the current action of actor
a
isact
, which can be any value permissible toa:set_action()
. For composite action objects, equality is established using its hidden ID, not the public members. Refer to Actions, moves and AIs for further details. -
a:set_action_delay()
-
Overrides the
delay
of the current action of actora
without changing the action’s ID. -
a:set_count(count)
-
Sets the actor’s execution count to
count
. The execution count of an actor increments after each time its callback function has run once. -
a:get_count()
-
Returns the actor’s execution count.
-
a:reset_acount()
-
Resets the action count of the actor to 0. The action count is incremented on each frame advance of the actor’s current action. Also see the
delay
argument tocon.action
. -
a:get_acount()
-
Returns the actor’s action count.
-
a:set_move(mov [, movflags])
-
Sets the move of actor
a
tomov
, which may be either an object returned bycon.move
or a number 0 or 1. See themove
argument togameactor
for further details. Ifmovflags
is omitted, it defaults to 0.The
set_move
method resets the actor’s execution count. Also, ifmoveflags
has bitactor.MOVFLAGS.randomangle
set and the actor is not an enemy or a live enemy, itssprite[].ang
value is set to a “random” value usinggv.krand
. -
a:has_move(mov)
-
Returns a boolean of whether the current move of actor
a
ismov
, which can be any value permissible toa:set_move()
. Like witha:has_action()
, equality is established using the move ID in case a composite one is passed. -
a:set_hvel(hvel)
-
Overrides the horizontal velocity of the actor’s current move to
hvel
without changing its ID. -
a:set_vvel(vvel)
-
Overrides the vertical velocity of the actor’s current move to
vvel
without changing its ID. -
a:set_ai(ai)
-
Sets the AI of actor
a
toai
, which must be an object returned bycon.ai
. In addition to setting the current AI ID of the actor,a:set_ai(ai)
is equivalent to the sequencea:set_action(ai.act) a:set_move(ai.mov, ai.movflags)
-
a:has_ai(ai)
-
Returns a boolean of whether the current AI of actor
a
isai
.
Various methods query whether the last movement step of the actor made it collide with another object, or allow getting this object’s index then.
-
a:checkhit()
-
Returns a boolean of whether the actor hit any object in the world, including ceilings and floors.
-
a:checkbump()
-
Returns a boolean of whether the actor bumped into another actor or a wall.
-
a:hitwall()
-
If the actor hit a wall with the last movement, returns that wall’s index. Otherwise, returns nil.
-
a:hitsprite()
-
If the actor hit a sprite with the last movement, returns that sprite’s index. Otherwise, returns nil.
actor
static functions
-
actor.fall(i)
-
Causes the actor with index
i
to fall in a “hard-coded”, not further specified fashion. -
actor.move(i, vec, cliptype [, clipdist])
-
TODO
actor
static data
-
actor.STAT
-
Provides a mapping of symbolic names to values applicable as sprite status numbers.
DEFAULT
,ACTOR
,ZOMBIEACTOR
,EFFECTOR
,PROJECTILE
,MISC
,STANDABLE
,LOCATOR
,ACTIVATOR
,TRANSPORT
,PLAYER
,FX
,FALLER
,DUMMYPLAYER
,LIGHT
. -
actor.FLAGS
-
Contains symbolic names of values applicable to
gameactor
'sflags
input argument, most of which are described there.
SHADOW
,NVG
,NOSHADE
,NOPAL
,NOEVENTS
,NOLIGHT
,USEACTIVATOR
,NOCLIP
,SMOOTHMOVE
,NOTELEPORT
,NODAMAGEPUSH
.
-
actor.MOVFLAGS
-
Contains symbolic names of values applicable
gameactor
'smovflags
input argument,actor[]:set_move()
, and the like.
-
faceplayer
,geth
,getv
,randomangle
,faceplayerslow
,spin
,faceplayersmart
,fleeenemy
,seekplayer
,furthestdir
,dodgebullet
, all naming single bits -
jumptoplayer_bits
: the bitwise-OR of two bits, one of which isfaceplayer
-
jumptoplayer_only
: the same asjumptoplayer_bits
, but without thefaceplayer
bit set -
jumptoplayer
: deprecated
-
player
Accessible with the index 0 and any nonnegative pli
index passed to a
game actor or event callback function.
The structures obtained by indexing player
contain arrays whose elements may
be referred to either by index of by a particular name. Thus, the array can be
thought to be overlaid with a structure containing as many members as the array
has elements, all having the element’s type.
Currently, there are with two kinds of names: weapon names and inventory names. They will be marked using notation like this:
i16
ammo_amount[weapon]
means that the structure member ammo_amount
is an array with named elements,
themselves being of signed 16-bit integer type. The array can be indexed with
valid weapon numbers, or weapon names. In the following (constructed) example,
the first player’s current pistol ammo count is set to that of the currently
selected weapon:
local ps = player[0]
ps.ammo_amount.PISTOL = ps.ammo_amount[ps.curr_weapon]
The inventory names are the same as the keys of gv.GET
, those for
the weapons coincide with the keys of gv.WEAPON
.
Player members marked bool
in the listing below yield Lua true or false on
reading and expect one of these values when assigned to.
player
members
-
xmath.ivec3
vel
-
The vector of the player’s current velocity, accounted each time a player’s input is processed. The x and y components are in BUILD x/y units per game tic, scaled by 214. The z component is given in BUILD z units per game tic. Processing a player’s input and other EDuke32 game code may change
vel
“behind a user’s back” in a further unspecified fashion. -
i32
runspeed
-
The factor, scaled by 216, with which the player’s current horizontal velocity is multiplied before calculating its new position. On various occasions, the ultimate scaled factor is the difference of
runspeed
and at most 8192. -
curr_weapon
-
The index of the player’s currently selected weapon.
-
u16
gotweapon
,bitfield
gotweaponbits
-
Indicates whether each weapon is in the possession of the player. If bit 2
w
is set for a weapon indexw
, the player has got this weapon. The player methodshas_weapon
,give_weapon
andtake_weapon
can be used to query or modify this member. -
i16
ammo_amount[weapon]
-
The current ammo amount for each weapon.
-
i16
max_ammo_amount[weapon]
-
The maximum ammo amount for each weapon. Because
ammo_amount
amount andmax_ammo_amount
are writable without restriction, it is the user’s responsibility to make sure that the former never exceeds the latter and that both are non-negative at all times. Otherwise, erratic behavior may ensue. -
bool
jetpack_on
,scuba_on
,heat_on
-
Whether the player currently has the jetpack, scuba gear, or night vision goggles activated, respectively.
-
weapondata_t
weapon[weapon]
-
A struct containing information about the behavior each of weapon for this player. In CON, these are available as separate gamevars named
WEAPONx_*
for weapon indicesx
and members*
.
weapondata_t
members
In Lunatic, some members of the weapondata_t
structure are checked when
being assigned to and issue an error on attempting to set them to an invalid
value. All these members assume zero to mean the neutral/no-op value (instead
of –1, as would seem more logical), and consequently, Lunatic only allows
values greater or equal zero to be assigned to them.
-
clip
-
reload
-
firedelay
-
totaltime
-
holddelay
-
flags
-
shoots
(checked) -
spawntime
-
spawn
(checked) -
shotsperburst
-
initialsound
(checked) -
firesound
(checked) -
sound2time
// This is a time number, not a sound number -
sound2sound
(checked) -
reloadsound1
(checked) -
reloadsound2
(checked) -
selectsound
(checked) -
flashcolor
player
methods
-
ps:has_weapon(widx)
-
Returns a boolean of whether player
ps
has got the weapon with indexwidx
. -
ps:give_weapon(widx)
-
Adds the weapon given by index
widx
to playerps
's possession without changing the currently held one. -
ps:take_weapon(widx)
-
Removes the weapon given by index
widx
from playerps
's possession. If an attempt is made to remove the currently selected weapon, the behavior is undefined. -
ps:fadecol(fadefrac, r, g, b [, speed [, prio]])
-
Initiates a tinting that linearly fades over time and is blended with the whole screen contents whenever player
ps
's view is displayed.
[The behavior is unspecified should more than one player’s view be displayed at one time.]
The first argumentfadefrac
specifies the starting blending coefficient;r
,g
andb
specify the intensities of the red, green and blue color components, respectively.Both
fadefrac
and the component intensities are first clamped to the range [0.0 .. 1.0]. The resulting values are interpreted as a proportion, 0.0 meaning no blending/no color and 1.0 meaning full blending/full color.
[Currently, for implementation reasons, a value of 1.0 results in only almost full blending or presence of the specified color component.]The fourth, optional argument
speed
controls the rate at which the tinting diminishes. At a value of1
(the default), a tint with afadefrac
of 0.5 finishes in approximately one second.The last, optional argument
prio
must be an integer in the range [-128
..127
], the default being0
. When afadecol
is issued in the presence of another tint fading in progress, and theprio
given by the arrivingfadecol
is greater or equal than theprio
of the ongoing one, the latter is canceled and the arriving fading is initiated in its place. (There is no support for tint fades that overlap in time.)If Lunatic code that uses fadecol
is loaded together with CON code that writes to the player’spals
members directly at any point, the behavior is undefined.
player
iterators
-
for i in player.all()
-
Iterates over the indices of all active players.
g_tile
An array of size gv.MAXTILES
. Currently, access with numeric indices by the
user is disallowed for g_tile
and its elements have no public
members.
g_tile
static data
-
g_tile.sizx
,g_tile.sizy
-
Arrays indexable with tile numbers [
0
..gv.MAXTILES-1
] that hold the horizontal and vertical texel sizes of each tile.
4.5. Lunatic functions
4.5.1. Engine-side iterators
-
for w in wallsofsect(sectnum)
-
Iterates over the indices of all walls of the sector with index
sectnum
. -
for s in spritesofstat(statnum [, maydelete])
-
Iterates over the indices of all sprites with status number
statnum
. Ifmaydelete
is omitted or false, there must be no deletion of any sprite while the loop is active. Ifmaydelete
is true, deleting sprites inside the loop is allowed. Inserting sprites is always allowed. -
for s in spritesofsect(sectnum [, maydelete])
-
Iterates over the indices of all sprites contained in the sector with index
sectnum
with the same meaning formaydelete
as withspritesofstat
. -
for s in sectorsofbunch(bunchnum, cf)
-
Iterates over the indices of the sectors whose ceiling’s or floor’s bunch equals
bunchnum
, selected by passinggv.CEILING
orgv.FLOOR
(respectively) tocf
. -
for s, what in sectorsofbunch(bunchnum, gv.BOTH_CF)
-
Iterates over the indices of the sectors whose ceiling’s and floor’s bunch equals
bunchnum
. On each iteration,what
is one of the strings'ceiling'
or'floor'
, denoting whether it’s the ceiling or floor of sectors
that has the given bunch number.
4.5.2. Sector containment functions
-
inside(pos, sectnum)
-
Returns a boolean of whether the position
pos
(which can be anything indexable withx
andy
) is considered inside the sector with numbersectnum
, which must be valid. Thez
component is not taken into account.
Sector updating
A number of engine functions take a position pos
and a starting sector number
sectnum
and try to find a sector that contains pos
, assuming it started out
in sectnum
.
[Note that this is different from CON’s updatesector
,
which takes the starting sector to be the one of the current sprite.]
If a valid sector numeric is passed for sectnum
, these functions first check
whether that sector already contains pos
(i.e. the position stays in the same
sector) and then attempt to search neighboring sectors. Unless breadth-first
search is requested (see below), if the passed sectnum
is -1
, all sectors
are searched in an unspecified order.
On success, these functions return the sector number of the “updated” sector,
otherwise -1
.
-
updatesector(pos, sectnum [, flags])
-
Searches for a sector containing
pos
, which can be anything indexable withx
andy
. Thus, thez
component is not taken into account. Ifsectnum
is a valid sector number, first all its neighboring walls (wall[].nextwall
, “red wall links”) are searched, falling back to a linear search on failure.However, this strategy can be problematic if the position passes multiple thin sectors that overlap another, potentially unrelated sector (for example, in a TROR or sector-over-sector construction). If such a sector is passed without an
updatesector
call ever made when it contains the position, the next call may not find the real sector (as it is now two nextwall links away from the initial one), and wrongly suggest the overlapping one instead.For this reason, the optional argument
flags
acceptssector.UPDATE_FLAGS.BREADTH
, instructingupdatesector
to look for matching sectors in a breadth-first search starting fromsectnum
, and following all nextwall links. With this strategy, there is no fallback to a linear search if no matching sector is found.Passing
sector.UPDATE_FLAGS.Z
toflags
makes the sector updating behave as ifupdatesectorz
was called. This is mainly useful ifupdatesector
is called via the sector updating functions of sprites. -
updatesectorz(pos, sectnum)
-
Searches for a sector containing
pos
, which can be any value indexable withx
,y
andz
. Thus, it additionally takes thez
component into account by checking against the bounds that would be returned using a sector’sceilingzat
/floorzat
methods.The
updatesectorz
function first checks the initial sector for containment ofpos
, then it tries any TROR neighbors ofsectnum
. Finally, it proceeds likeupdatesector
as far as the searching order is concerned.
Collision detection and related functions
-
hitscan(pos, sectnum, ray, clipmask)
-
Starting from the position
pos
(which is assumed to be contained insectnum
), thehitscan
function determines the object that would be first hit by a ray emanating frompos
into the direction given byray
. Bothpos
andray
may be any object indexable withx
,y
andz
, but the components are converted to signed 32-bit integers prior to being passed to the actual engine function. Note thatray
is interpreted in BUILD scaling: the z component has 16 times the precision for a given game-world length compared to x or y.The
clipmask
argument determines what objects are considered being hittable by the ray and is expected to be an integral number. It is interpreted as two separate bit masks: the low 16 bits for walls and the high 16 bits for sprites. Each time there is a potential collision, the respective mask is ANDed with thecstat
member of the object, and if the result is non-zero, the ray is considered having hit the object.The
hitscan
function returns an object with information about the hit object (if any) as well as the position of its intersection with the ray. It contains the following members:-
sector
: The sector number of the hit object, or-1
if no object was hit. Thus, testing it for being greater or equal to zero is a quick way of finding out whether any object (ceiling/floor, wall, or sprite) was hit at all.
[It is recommended to carry out this check for the sake of cautiousness: while properhitscan
invocations should always hit something, the function may come up empty in certain corner cases (such as a starting position outside of the designated sector).]
-
wall
: If a wall was hit, its index. Otherwise,-1
. -
sprite
: If a sprite was hit, its index. Otherwise,-1
. -
pos
: The position that is the intersection of the emanated ray with the hit object, indexable withx
,y
andz
. Undefined if no object was hit.
-
4.5.3. Customizing the game
In Lunatic, there are two main ways of customizing the game’s behavior. Both involve the user providing functions that are called back at certain points in the execution of EDuke32. Game actors are simply sprites that run a particular piece of code based on their tile number each game tic, unless they are in a “dormant” state. Game events are invoked at predetermined points in the program flow.
To register custom actor and event code with the game (and define additional
actor information), Lunatic provides two functions in the global environment,
gameactor
and gameevent
. As their sole argument, they take a table
containing the appropriate data.
The function gameactor{tilenum [, ...], func}
Registers custom code for the actor given by tile number tilenum
. For each
non-sleeping actor, the function func
is called every game tic with three
input arguments: func(aci, pli, dist)
.
-
aci
: the sprite number of the actor invokingfunc
-
pli
: the index of the player that is nearest to this actor -
dist
: the 3D Manhattan distance
[The Manhattan distance between points p1=(x1, y1, z1) and p2=(x2, y2, z2) is defined as abs(x2 — x1) + abs(y2 — y1) + abs(z2 — z1).]
between actoraci
and playerpli
Additionally, gameactor
accepts optional input arguments. They can be
specified positionally by following tilenum
, or be given as values to string
keys of the argument table. Each such input argument may be provided in at most
one of these two forms. Furthermore, func
may be provided as value to the
key 'func'
as well.
-
[2] flags
-
A number that controls both certain aspects of the
gameactor
call as well as the run-time behavior of the actor itself. A couple of bits for the latter type are listed inactor.FLAGS
, abbreviatedAF
in the following.These values describe the “type” of the actor:
AF.enemy
,AF.enemystayput
andAF.rotfixed
. Except forenemystayput
, they name single bits (enemystayput
impliesenemy
).In Lunatic, game actors can be chained, that is, a callback function can be either appended to the end of an already registered one, or prefixed at its front. In this case, a previous
gameactor
call must have taken place for that actor (this may have happened from a CONuseractor
block, which gets translated togameactor
). Moreover, this mechanism allows to add run-time flags to the actor in question.Chaining two callback functions is achieved by creating a new one that calls the first one, followed by a tail call of the second one. This has certain implications if control is transferred non-locally, for example by using
con.longjmp
.Several flags in
AF
are provided to control how agameactor
invocation handles chaining. In all cases, the actor tile flags are bitwise-ORed with the existing ones.-
AF.replace
: Replace the callback function. This is the way CON’suseractor
behaves and is also the Lunatic default. -
AF.replace_soft
: deprecated alias forAF.replace
-
AF.replace_hard
: deprecated -
AF.chain_beg
: Prepend the providedfunc
to the existing callback function. -
AF.chain_end
: Append the providedfunc
to the existing callback function.The following members all default to 0 if omitted.
-
-
[3] strength
-
The initial strength or health of the actor.
-
[4] action
-
The initial action of the actor. May be either an object returned by
con.action
, or the numbers 0 or 1. Both represent actions with that ID, but all public members set to 0. -
[5] move
-
The initial move of the actor. May be either an object returned by
con.move
, or the numbers 0 or 1. Both represent moves with that ID, but all public members set to 0. A move of 0 disables the usual actor movement, even if itshvel
orvvel
subsequently get overridden (and the correspondingmovflags
set). -
[6] movflags
-
The actor’s initial movement flags. Applicable bits are available in the
actor.MOVFLAGS
object.
The function gameevent{evtlabel [, flags], func}
Registers custom code to be run at specific points in the program. The first
argument evtlabel
should be a string naming the event. A complete
list of events can be found at the EDuke32 wiki. The
label may be stripped of the leading “EVENT_
”, so that e.g. EVENT_JUMP
and simply JUMP
denote the same event.
The arguments flags
and func
can alternatively be passed as values to the
same-named keys of the input argument table to gameevent
. Like with
gameactor
, each may be provided in at most one of the two forms.
The callback func
is invoked with the same arguments and meaning as for
gameactor
, but certain events are run in contexts where no
meaningful aci
and/or pli
value can be assigned. In this case, func
receives -1
for the respective input arguments.
Like with actors, game events may be chained or replaced by passing an
appropriate flags
value. However, it is not necessary for an event to be
already defined when chaining is requested. In that case, it is simply
registered initially. Permissible values for these flags are provided in
actor.FLAGS
as well (abbreviated AF
here):
-
AF.replace
: Replace any previously defined event code with the given one. -
AF.chain_beg
: Prepend the providedfunc
to the existing callback function. This is the behavior of CON’sonevent
. -
AF.chain_end
: Append the providedfunc
to the existing callback function. This is the default.
5. Extended API (Lunatic modules)
5.1. The xmath
module
5.1.1. Mathematical functions
Lunatic, being a Lua-based scripting system, provides the user with a single
numeric data type that variables can contain on the Lua side — double-precision floating point.
[In LuaJIT, variables additionally can
take on “boxed” 64-bit integer numeric types, but these should not be used
for numeric calculations.]
However, since BUILD, and in turn, EDuke32, almost
exclusively use integer types to represent quantities such as angles or carry
out e.g. trigonometrical calculations, there is a need for convenient
interoperability between the two “worlds”.
Two pairs of functions calculate the trigonometric sine and cosine, both
accepting a BUILD angle as input argument, but differing in the scaling of
their result. Using these functions is recommended over Lua’s math.sin
or
math.cos
in cases where the argument is a BUILD angle in the first place, for
example because it is read from an engine or game structure. The computation is
both faster (because it is essentially only a bitwise-AND operation followed by
a table lookup) and the results have the symmetry expected from the
mathematical counterparts.
-
xmath.sinb(bang)
,xmath.cosb(bang)
-
Returns the sine/cosine of the given BUILD angle
bang
, which can be any whole number in [–231 .. 231–1].
[Passing fractional values is possible, but discouraged. See the relevant subsection of the BitOp documentation for more details.]
In BUILD, one full cycle is covered by values from 0 to 2047; in other words, an angle of 2048 corresponds to 360 degrees.The
sinb
andcosb
functions return values in the range [–1 .. 1], just like their mathematical counterparts.The following guarantees are made for
sinb
whenever its argument expression is permissible:-
sinb(-a) == -sinb(a)
(point symmetry around the origin) -
sinb(a + i*2048) == sinb(a)
, wherei
is any whole number (periodicity) -
sinb(1024 - a) == sinb(a)
(mirror symmetry arounda
=512) -
sinb(a - 1024) == -sinb(a)
(point symmetry arounda
=1024) -
The value for
cosb(a)
is derived assinb(a + 512)
.
-
-
xmath.ksin(bang)
,xmath.kcos(bang)
-
Returns the sine/cosine of the given BUILD angle
bang
, multiplied with 16384 and rounded towards zero. The same guarantees as for thesinb
/cosb
pair apply. -
xmath.angvec(ang)
-
Returns a
vec3
with the componentsmath.cos(ang)
,math.sin(ang)
and 0 for x, y and z, respectively. -
xmath.bangvec(bang)
-
Returns a
vec3
with the componentsxmath.cosb(bang)
,xmath.sinb(bang)
and 0 for x, y and z, respectively. -
xmath.kangvec(bang [, z])
-
Returns an
ivec3
with the componentsxmath.kcos(bang)
,xmath.ksin(bang)
for x and y, respectively. The z component can be passed in the optional argumentz
, which defaults to 0 if omitted. -
xmath.dist(pos1, pos2)
-
Returns an approximation of the 3D Euclidean distance between points
pos1
andpos2
, both of which can be any object indexable withx
,y
andz
. BUILD z units are assumed. -
xmath.ldist(pos1, pos2)
-
Returns an approximation of the 2D Euclidean distance between points
pos1
andpos2
, both of which can be any object indexable withx
andy
. -
xmath.rotate(point, bang [, pivot])
-
Returns as an
xmath.vec3
the position ofpoint
rotated around the line parallel to the z axis going throughpivot
bybang
BUILD angle units in the mathematically negative (clockwise) direction. The argumentspoint
andpivot
can be anything indexable withx
,y
andz
. The z component of the result is the difference between that ofpoint
andpivot
. Ifpivot
is omitted, it defaults to the origin vector containing zeros for all components.
5.1.2. The types xmath.vec3
and xmath.ivec3
[serializable]
Another purpose of the xmath
module is to provide vector types that allow
writing concise and clear code involving geometrical calculations. There are
two types, both containing three components (x
, y
and z
), but differing
in their numeric type. For the most part, vec3
should be used, whose
components are Lua numbers, i.e. floating point. The other type, ivec3
, is
part of some game structures, and consequently uses 32-bit integers for its
components. With minor differences, the vec3
and ivec3
types share the same
operations and methods.
The constructors of the vector types can be called in several ways. In the
following, they are only described for vec3
. The conventions for ivec3
are
completely analogous, but since their creation involves a number type
conversion, the rules about assignment to integer types
apply.
-
v = xmath.vec3([x [, y [, z]]])
-
Create a 3-element vector
v
by passing thex
,y
andz
components separately. Trailing components can be omitted, in which case they are initialized to 0. -
v = xmath.vec3(t)
-
Create a 3-element vector
v
by passingt
, which can be any variable indexable with the stringsx
,y
andz
(and yielding numbers for these lookups). For example,t
can be another (i
)vec3
, asprite
or evenwall
reference, as each of them can be indexed with these three keys.
Since the vector types are compound objects, they are always passed around by reference. For example, consider executing
v = xmath.vec3(0, 10) w = v w.y = 20
After this code, the expression v.y
yields 20 instead of the initial value
10.
Operations for vec3
and ivec3
In the following, v
denotes a vec3
or ivec3
object reference while t
denotes any object indexable with x
, y
and z
. Note that for binary
operations, Lua looks for overridden operators in the left operand first and
the right one next. So, where t
appears on the left hand side of an
arithmetic expression, it is assumed that t
's type does not overload the
corresponding operation or provides the same semantics. Arithmetic operations
always return a (reference to a) new vec3
object, even if any or both of the
operands have ivec3
type.
-
v + t
,t + v
-
Returns a new
vec3
object whose components are the sum of the respective components ofv
andt
. -
v - t
,t - v
-
Returns a new
vec3
object whose components are the difference of the respective components ofv
andt
(in the first case) ort
andv
(in the second case). -
-v
-
Returns a new
vec3
object with the components ofv
negated. -
a*v
,v*a
-
For a scalar number
a
, returns a newvec3
object whose components are those ofv
multiplied witha
. -
v/a
-
For a scalar number
a
, returns a newvec3
object whose components are those ofv
divided bya
. -
v^zofs
-
Returns an object of the same type as
v
and with the same components, except thatv.z
is diminished byzofs
. Also see the power operation forsprite
objects. -
tostring(v)
-
Returns a string representation of
v
for display purposes: “vec3
” or “ivec3
”, followed by the components ofv
in parentheses.
Methods for vec3
and ivec3
-
v:len()
-
Returns the Euclidean length of
v
in three dimensions. -
v:lensq()
-
Returns the squared Euclidean length of
v
in three dimensions. -
v:len2()
-
Returns the Euclidean length of
v
, taking only thex
andy
components into account. -
v:len2sq()
-
Returns the squared Euclidean length of
v
, taking only thex
andy
components into account. -
v:mhlen()
-
Returns the length of
v
calculated using the Manhattan distance
[mhdist_def]
in three dimensions between the origin and the endpoint. -
v:toivec3()
-
Returns a new
ivec3
object with the same components asv
, but converted to integers. -
v:touniform()
-
Returns a new vector of the same type as
v
which has the samex
andy
components asv
, but thez
element divided by 16 (ifv
is avec3
) or arithmetically right-shifted by 4 (ifv
is anivec3
).
[Right-shifting by 4 can be seen as a division by 16 with subsequent rounding to an integer towards negative infinity.]
Also see the description of the ceiling/floorz
member. -
v:tobuild()
-
Returns a new vector of the same type as
v
which has the samex
andy
components asv
, but thez
element multiplied with 16. -
v:rotate(bang [, pivot])
-
Equivalent to
xmath.rotate(v, bang [, pivot])
.
5.2. The con
module — game control
The con
module on one hand provides functionality to control certain aspects
of the game, such as defining game-side animations for actors. On the other
hand, it hosts various functions that are familiar from CON, although sometimes
under a different name.
5.2.1. Actions, moves and AIs — customizing actor behavior
In CON, three aspects of an actor can changed using a mechanism that is syntactically similar for each. Actions carry information about a particular animation for an actor, such as how many frames it contains. Moves define the potential motion characteristics of an actor. Finally, AIs are aggregates of an action and a move, plus movement flags that toggle certain movement aspects.
At file scope, one first defines each of these composites using directives such
as action
, and inside code blocks to be executed at game time, one instructs
the game to use a particular composite by passing the chosen label to the
same-named command (i.e. action
for our example).
Lunatic provides a similar mechanism, but the main difference from CON is that the definition functions merely return the composite structures, without registering them under any particular name. Thus, it is up to the user to organize them, for example by storing those for each class in a separate table indexed by chosen names. Similar to CON, the creation functions may only be called when the game is not yet running.
Actor behavior composites contain an additional “hidden” ID. Each invocation
of a creation function such as con.action
returns a composite with a unique
ID per composite type. This ID is used to compare for equality in actor methods
such as actor[]:has_action()
. All returned composites are immutable.
The function con.action{ ... }
Returns a new action composite created with the given values. The
con.action
function expects a single table as input argument, each of whose
keys denote an action member. Each member may be provided by an index in the
table or its name, but not both. Members may be omitted, in which case they
default to either 0 or 1.
Refer to the Table Constructors subsection of the Lua 5.1 Reference Manual for the syntax and semantics of table initialization.
-
[1] startframe
-
The offset of the starting frame of the animation, relative to the
sprite[].picnum
of an actor (i.e. the tile number, selecting which actor code is run). -
[2] numframes
(default: 1) -
The total number of frames in the animation.
-
[3] viewtype
(default: 1) -
The number of consecutive tiles used to construct one frame of the animation, as viewed from different angles. Valid values are 1, 3, 5, 7 and 8. In addition, -5 and -7 are allowed, which behave like the corresponding positive
viewtype
s, but effectively mirror the actor around the vertical axis. This can be useful if tile data is available that has the opposite orientation of what EDuke32 uses. See the Action entry in the EDuke32 wiki for how the views are constructed for differentviewtype
values. -
[4] incval
(default: 1) -
The value to add the actor’s current frame on each frame advance. May be –1, 0, or 1.
-
[5] delay
-
Roughly, the number of
gv.totalclock
time units (120 per second) after which a frame is advanced, at a granularity of one game tic (30 per second, corresponding to adelay
difference of 4).
[The reason for the granularity is due to the implementation: each execution of an actor’s code increments its hidden “action tics” counter by four (= 120/30).]
action
definitionsEach of the following calls return an action with the same public members:
con.action{0, 4, delay=20} con.action{0, 4, 1, 1, 20} con.action{startframe=0, numframes=4, viewtype=1, incval=1, delay=20}
The function con.move{ ... }
Returns a new move composite created with the given values, expecting a
single table as input argument. The same conventions as with con.action
apply
for the following members:
-
[1] hvel
-
The (most often) doubled horizontal velocity of the actor, in BUILD x/y units per game tic. An actor’s
sprite[].xvel
will approach this value in an “exponential” fashion, halving the difference between the current velocity and this goal velocity each movement update from the actor execution loop. For most objects,sprite[].xvel
is then further halved to yield the ultimate velocity. Only effective when the actor’smovflags
hasactor.MOVFLAGS.geth
is set. -
[2] vvel
-
The (most often) doubled horizontal velocity of the actor, in BUILD x/y (not z) units per game tic. An actor’s
sprite[].zvel
will approach 16 times this value in an “exponential” fashion. For most objects,sprite[].zvel
is then further halved to yield the ultimate velocity. Only effective when the actor’smovflags
hasactor.MOVFLAGS.getv
is set.
The function con.ai([action [, move [, movflags]]])
The con.ai
function differs from con.action
and con.move
in that it is of
“second order”, i.e. it accepts two composites as input arguments, returning
an AI object containing these. Also, it is called with three positional,
optional arguments. Like for gameactor
, the numbers 0 and 1 are
permissible for action
and move
. Applicable bits for movflags
are
available in the actor.MOVFLAGS
object.
5.2.2. Non-local control flow
Two functions in the con
module make the executed function abort early,
jumping directly to the end of the innermost event or actor callback
“block”. They are used to implement among others CON’s killit
and
(confusingly named) return
commands. If these functions are used when none of
the mentioned callback functions are active, the behavior is undefined.
-
con.longjmp()
-
Silently transfers control to the end of the innermost actor or event callback block, to the same point an
error()
call would do. Note that since callback chaining is achieved by creating a new function for each function pair, calling both unprotected, issuing acon.longjmp()
inside any “part” of a chain aborts the whole block — functions in the chain that are called later will not be reached. In contrast, returning from one function transfers control to the beginning of the next in the chain if it exists.Example 5.con.longjmp
with chained eventsThe following two chained
EVENT_JUMP
definitions,gameevent{"JUMP", function(_, pli) print("jump:first") if (player[pli].curr_weapon==gv.WEAPON.PISTOL) then return end print("aborting") con.longjmp() end} gameevent{"JUMP", function() -- second function in EVENT_JUMP chain print("jump:second") end}
would print “
jump:first
” and “jump:second
” when holding a pistol. Otherwise, “jump:first
” and “aborting
” would be printed and the second chained callback function would not be reached. -
con.killit()
-
Silently transfers control to the end of the active actor callback block, notifying the game to delete the executing actor’s sprite. If
con.killit
is called while no execution of actor code is active, the behavior is undefined.
5.2.3. Per-actor variables
Since in EDuke32, sprites in general exist in the world only for a limited
duration, it is wasteful to allocate an array of fixed size gv.MAXSPRITES
for
the purpose of a variable that holds a value for each actor sprite. On the Lua
side, one could use a plain table, but for actors that are created and
destroyed during the course of a game, this would accumulate “garbage” — values for sprites that have been deleted. Moreover, per-actor variables tend
to be “topical”, one such variable being potentially only used for a very
specific actor tile. For this reason, per-actor variables are implemented in a
“sparse” fashion in Lunatic, but provide to the user the illusion of having a
value for every sprite index. They are also “cleaned” at unspecified
intervals.
The type con.actorvar(defaultval)
[serializable]
Creates and returns a new per-actor variable with default value defaultval
which can be indexed for reading or assignment in the range
[0 .. gv.MAXSPRITES-1
], but access to it is subject to the same
restrictions as to sprite
and other per-sprite structures.
When a sprite is created using con.insertsprite
or con.spawn
, its value at
the index of this new sprite is cleared (reset to defaultval
). After a sprite
has been deleted, the value of a per-actor variable is indeterminate — it may
be cleared by Lunatic at any point.
Per-actor variables may contain values of any permitted type, which currently are boolean and number. Mixing values of different types is allowed: per-actor variables are heterogenous containers.
5.2.4. Sprite insertion
In Lunatic, there are two functions that insert a sprite into the game world. They mainly differ in how they are used, and to which extent they imply “hard-coded” behavior.
The function con.insertsprite{tilenum, pos, sectnum [, statnum [, owner]] [key=val...]}
Inserts a new sprite into the game with the properties given as input
arguments. If the world already contains the maximum number of sprites
(gv.MAXSPRITES
), currently the game is aborted and EDuke32
exits.
[This is subject to change and must not be relied on.]
Otherwise, relevant per-sprite data for the newly inserted sprite is
cleared. No additional “hard-wired” C code is run.
Returns the index of the inserted sprite.
The function con.insertsprite
can be used in one of two forms:
-
In the
table-call
form specified above, a single table is passed whose values are taken as the actual arguments. The first three,tilenum
,pos
andsectnum
, are passed positionally. All other input arguments are passed as key-value pairs, butowner
andstatnum
may be provided either positionally or as key/value pair. -
Passing only the three to five positional arguments is also directly possible. For example, all of the following calls are equivalent:
local i = con.insertsprite(tile, pos, sect, stat, ow) -- direct-call local i = con.insertsprite{tile, pos, sect, stat, ow} -- table-call local i = con.insertsprite{tile, pos, sect, statnum=stat, owner=ow} -- table-call with 2 k/v args local i = con.insertsprite{tile, pos, sect, stat, owner=ow} -- table-call with one k/v arg
The five main arguments are as follows:
-
[1]
(tilenum
) -
The tile number of the sprite to insert.
-
[2]
(pos
) -
The position at which to insert the sprite (anything indexable with
x
,y
andz
). -
[3]
(sectnum
) -
The index of the sector in which to insert the sprite.
-
[4] statnum
(default:actor.STAT.DEFAULT
) -
The initial status number of the inserted sprite.
-
[5] owner
(default: see below) -
The index of the sprite that is in some sense the “parent” of the newly created one. If omitted, it is set to the index of the newly spawned sprite.
These keys are permissible as optional input arguments in the table-call form,
corresponding to the same-named sprite
members:
-
shade
,ang
,xvel
,zvel
(default: 0) -
xrepeat
andyrepeat
(default: 48)
The function con.spawn(tilenum, parentspritenum)
Spawns a new sprite with tile number tilenum
from a given “parent” sprite
with index parentspritenum
, which must be valid. The sprite is spawned at the
same position as its parent and its owner
member is set to
parentspritenum
. Additional “hard-wired” code dependent on tilenum
may be
run afterwards, possibly modifying the sprite.
Returns the index of the spawned sprite on success.
The function con.shoot(tilenum, parentspritenum [, zvel])
Attempts to shoot a projectile with tile number tilenum
from the sprite with
index parentspritenum
. The z velocity can be overridden by passing zvel
.
Returns the index of the spawned sprite on success, or –1 otherwise.
5.3. The engine
module
5.3.1. Base palette interfaces
The 8-bit (“classic”) Build renderer operates in indexed-color mode: the pixels of the frame buffer do not contain red/green/blue values themselves, but only indexes into a color table with 256 entries, the base palette.
The following functions provide a bridge between color indices and their corresponding color components.
-
r, g, b = engine.getrgb(i)
-
Returns the red, green and blue color components of the default base palette for color index
i
. The color components are in the range [0 .. 63].If
i
is 255,r
,g
andb
are all returned as 0, even if the actual base palette may contain different values for that index. -
i = engine.nearcolor(r, g, b [, lastokcol])
-
Given the red, green and blue components
r
,g
andb
of a query color, returns the color indexi
whose color in the default base palette is closest to the query color. “Closeness” is established using an Euclidean distance with a weighting on the color components.The optional argument
lastokcol
can be used to restrict the range of color indices to search: the returned color index is guaranteed to be in the range [0 ..lastokcol
]. It defaults 255, so that all colors are searched.
[For example, it may be desirable to omit “fullbright” colors from being returned. The shade table loaded from Duke3D’s PALETTE.DAT makes color indices 240—254 fullbright, so passing 239 tolastokcol
achieves the mentioned filtering.]
5.3.2. Shade table interfaces
To implement shading and visibility attenuation, Build maintains tables mapping pairs of a color index and a shade level (Duke3D’s table uses 32 such gradients) to a color index representing the darkness-faded color. Each such table is called shade or palookup table.
When a pixel is about to be drawn, a palookup table chosen depending on the
object’s pal
is consulted to determine its ultimate color index (in the
absence of blending with the translucency table or see-through texels). Given a
texel’s color index as $i_{\mathrm{in}}$, the resulting pixel’s one
$i_{\mathrm{out}}$ is computed as
$s_1 = \mathrm{shade} + C \cdot \mathrm{visdist}$
$s_2 = \mathrm{clamp}(s_1, \: 0, \: \mathrm{Numshades}-1)$
$\mathrm{shade_index} = \mathrm{round}(s_2)$
$i_{\mathrm{out}} = \mathrm{palookup}(\mathrm{shade_index}, i_{\mathrm{in}})$
// This is only a table lookup, palookup[shade_index][$i_{\mathrm{in}}$]
Here, $C$ is a positive constant and
$\mathrm{visdist}$ is the product of a. the distance of an object’s
sampled pixel to the view plane with b. the object’s
“visibility”.
[Visibility would be more appropriately called
“anti-visibility” or “co-visibility”: greater values make objects appear
more faded for the same distance. Also, the visibility that is meant here has
the origin at 0, unlike sector[].visibility
.]
Thus, shade and visibility are
inherently confounded in the 8-bit mode and the ultimate shade_index is bounded
below by (the minimum of $\mathrm{Numshades}-1$ and) the shade of
the object.
Examples of effects using shade tables
While palookup tables are primarily used for shading and visibility attenuation, they can be set up in other ways to yield different effects with respect to how pixels of objects farther away are drawn. For example:
-
Distance fading with fog. For a fog color $\mathbf{c} = (c_r, c_g, c_b)$, the table is set up so that for a source color index $i$ and a shade level sh, palookup[sh][$i$] contains a color index whose color is close to that of $i$ blended with $\mathbf{c}$,
$\frac{\mathrm{sh} + 0.5}{\mathrm{Numshades}} \cdot \mathrm{basepal}(i) + \frac{\mathrm{Numshades}-\mathrm{sh}+0.5}{\mathrm{Numshades}} \cdot \mathbf{c}$.
Note that distance fading to black can be seen as a special case of this fogging effect. However, Duke3D’s base shade table (i.e. the table for pal 0) is not constructed in this way. -
Color index remapping. Given a mapping $m: \: \mathrm{ColorIndex} \rightarrow \mathrm{ColorIndex}$, the table is set up so that for each shade level sh, the 256 color indices are selected or reordered in the same way: for all color indices $i$, palookup[sh][$i$] = original_palookup[sh][$m(i)$].
For example, pal 21 remaps the fifth and sixth blocks of consecutive 16-tuples of color indices (a ramp of blue colors) to the fourth and 14th such blocks (red colors, the first one part of a 32-color ramp). -
“Fullbright” colors — those that aren’t affected by distance — with index $i$ are achieved by setting palookup[sh][$i$] to palookup[0][$i$] for each shade sh.
Shade table functions
-
sht = engine.shadetab()
-
Creates and returns a new shade table object
sht
with all entries initialized to zero. This object can be indexed once with a shade index from 0 to 31, yielding a reference to an array of 256 8-bit unsigned integers. Thus, shade table objects can be used just as indicated in the notation above:sht[sh][i]
is the resulting color index for shadesh
and input color indexi
. -
sht = engine.getshadetab(palnum)
-
Returns a new shade table object
sht
containing the values for the palookup table of palpalnum
, provided it is not an alias for the default one (see below). Modifying the returnedsht
does not alter the actual engine-side shade table. An unspecified number of pal indices are reserved from the end of the hard limit 255; attempting to retrieve a shade table for a reserved pal raises as error.At engine initialization, the shade tables for all non-zero pal numbers are aliased to the default pal 0 one. Subsequently, custom palookup tables are either loaded from LOOKUP.DAT, created by appropriate DEF directives (
fogpal
ormakepalookup
), or assigned usingengine.setshadetab
. If the table for palpalnum
is aliased to the default one whengetshadetab
is called, nil is returned. -
engine.setshadetab(palnum, sht)
-
Copies the shade table
sht
to the engine-side palookup table for palpalnum
. An error is issued if an attempt is made to copy to a reservedpalnum
.When running in EDuke32, there are additional restrictions:
-
A
palnum
for which a shade table has already been registered (that is, one which is not aliased to the default one) cannot be re-assigned to. -
setshadetab
may only be called at first initialization time, that is, when the value ofLUNATIC_FIRST_TIME
is true. (LUNATIC_FIRST_TIME
is a variable in the global environment that indicates whether the Lua game state is created for the very first time and the game is not yet running.)
-
Shade table methods
-
newsht = sht:remap16(tab)
-
Returns a new shade table with consecutive runs of 16 values of every 256-tuple of
sht
remapped as specified bytab
. Specifically,tab
must be a table whose keys in [0 .. 15] may be set to values in [0 .. 15]. For a shade indexsh
and a color indexi
, the returned shade tablenewsht
then hasnewsht[sh][i]
=sht[sh][newi]
,where
newi
=16*tab[math.floor(i/16)] + i%16
// iftab[math.floor(i/16)]
is non-nil
newi
=i
// otherwiseExample-- Creates a shade table with the same remapping as pal 21 (blue -> red) and -- registers it under pal 22, overwriting its previous contents. local newsht = engine.getshadetab(0):remap16({[4]=13, [5]=8}) engine.setshadetab(22, newsht)
5.3.3. Blending table interfaces
EDuke32 supports installing multiple blending tables used by the 8-bit renderer. A blending (or translucency) table is used whenever an object with the “translucent” bit set is drawn, and maps pairs of color indices to a blended color index.
Given a background color index bi
and a color index of an incoming foreground
fragment fi
(usually obtained by looking up a shade table), when fi
is
different from 255, the resulting color index is
transluc[bi][fi]
if the “reverse translucency” bit is clear, and
transluc[fi][bi]
if reverse tranlucency is set. If the fragment sampled from the foreground object has color index 255, it is discarded: translucent rendering is always “masking”.
Currently, only sprites support rendering with custom blending tables, by
setting their .blend
member to the number of a blending table.
Blending table functions
-
tab = engine.blendtab()
-
Creates and returns a new blending table object
tab
with all entries initialized to zero. This object can be indexed once with a color index from 0 to 255, yielding a reference to an array of 256 8-bit unsigned integers. Thus, blending table objects can be used just as indicated in the notation above:tab[i][j]
is the blended color index for input color indicesi
andj
. -
tab = engine.getblendtab(blendnum)
-
Returns a new blending table object
tab
containing the values for the engine-side translucency table numberedblendnum
, or nil if no blending table with that number is registered. Modifying the returnedtab
does not alter the actual engine-side blending table. -
engine.setblendtab(blendnum, tab)
-
Copies the blending table
tab
to the engine-side translucency table with numberblendnum
.Similar to
engine.setshadetab
, there are certain restrictions when running in EDuke32:-
A
blendnum
for which a table has already been registered cannot be re-assigned to. -
setblendtab
may only be called at first initialization time, that is, when the value ofLUNATIC_FIRST_TIME
is true.
-
5.3.4. Mapster32-only functions
engine.savePaletteDat(filename [, palnum [, blendnum [, moreblends]]])
→ ok, errmsg
Writes out a full PALETTE.DAT-formatted file named filename
with the base
shade table numbered palnum
and the base translucency table numbered
blendnum
, both defaulting to 0.
Passing moreblends
allows to specify additional blending tables to store in
EDuke32’s extended PALETTE.DAT format. These must have previously been
registered with engine.setblendtab
. The moreblends
argument must be a
sequence table with each element being either
-
a blending table number in the range [1 .. 255]
-
a table
t
containing a pair of such numbers, in which case it is taken to mean the inclusive range [t[1]
..t[2]
]
There must be no duplicate blending table numbers.
The function returns a status ok
which is true on success and nil on
failure. In the latter case, errmsg
is a diagnostic error message.
engine.saveLookupDat(filename, lookups)
→ ok, errmsg
Writes out a LOOKUP.DAT-formatted file named filename
with the lookup tables
specified by lookups
at the beginning and the five additional base palettes
at the end.
The lookups
argument is interpreted analogously to the moreblends
argument
of engine.savePaletteDat
(with the numbers being palookup numbers instead of
blending table numbers) and the return values ok
and errmsg
have the same
meaning as well.
5.4. The fs
module — virtual file system facilities
files = fs.listpath(path, mask)
Returns a sequence table files
of file names that can be found in a directory
constructed as concatenation of any directory in the search path with path
,
and matching the wildcard mask
. Currently, neither ZIP nor GRP files
registered as file containers with EDuke32 are searched.
The path
argument must separate directories by forward slashes (/
). If no
suffix is desired (i.e. the directories in the search path themselves are to be
searched), '/'
should be passed.
The file name mask
is applied case-insensitively for the 26 characters of the
basic Latin alphabet. It may contain the following meta-characters which are
interpreted in a special way:
-
a
*
matches any (potentially empty) sequence of characters -
a
?
matches any single character
While the match with mask
proceeds case-insensitively, file names are
returned named exactly like on the file system. However, names differing only
in case appear exactly once in the output list.
For portability, it is crucial that path is specified with the same
case as the actual directory on the file system. |
Suppose the search path contains two directories foo
and bar
with the
following file listing:
foo/
myhouse1.MAP
myhouse2.map
bar/
MYHOUSE1.map
MYHOUSE10.MAP
README.txt
Then a query with
fs.listpath("/", "myhouse?.map")
will return a table with these strings:
-
myhouse1.MAP
orMYHOUSE1.map
, but not both -
myhouse2.map
Appendix A: How to read Programming in Lua
On the Lua homepage, the first edition of Programming in Lua is available online. While targeting Lua 5.0, it still remains applicable to Lua 5.1 to a large extent. This section gives hints for reading it when learning to code for Lunatic.
Preface
May be interesting to get an idea of the philosophy behind Lua. A Few Typographical Conventions should be read to be familiar with them.
1 — Getting Started
Mentions the stand-alone Lua interpreter. When none is available, a LuaJIT stand-alone binary can be used for experimentation as well.
1.1 — Chunks
Introduces chunks as the basic “blocks” of Lua code and notes how whitespace
is treated. Mentions dofile
, which is not available in Lunatic
(require
is preferred).
1.2 — Global Variables
Section may be read, but usage of local
variables is strongly recommended
whenever possible. Also, trying to read a non-existent global or to write any
value to the global environment gives an error in Lunatic (except that global
writes are allowed in in module context, i.e. after module(...)
).
1.3 — Some Lexical Conventions
Must read.
2 — Types and Values
Must read, also subsections. However, “2.7 — Userdata and Threads” is not relevant.
3 — Expressions
Must read, also all subsections.
4 — Statements
Must read, also all subsections.
5 — Functions
Must read, also all subsections. Subsection 5.2 mentions io.write
, which is
not available in Lunatic.
6 — More about Functions
May be read (subsections as well) for a more complete understanding of functions in Lua, as well as the utility of lexical scoping.
7 — Iterators and the Generic for
May be read (subsections as well), but for basic programming, the knowledge of
how to merely use (as opposed to write) iterators such as
spritesofsect
suffices.
8.3 — Errors
May be read. Provides guidelines on how to write error handling (status code vs. error).
8.4 — Error Handling and Exceptions
May be read. Discusses protected calls, which should be used sparingly.
11 — Data Structures
May be read, also subsections. The most relevant subsections are 11.1 — Arrays, 11.5 — Sets and Bags and to some extent, 11.6 — String Buffers. The way “11.2 — Matrices and Multi-Dimensional Arrays” suggests to construct matrices is rather memory-intensive; also it and “11.3 — Linked Lists” and “11.4 — Queues and Double Queues” are not relevant to simple Lunatic coding.
15 — Packages
Lua package system received various additions in 5.1, so the PiL first
edition’s section is out-of-sync. For Lunatic, the modules section
of this manual should be consulted.
(The rest of Part II deals with advanced concepts not needed for simple Lunatic
coding.)
19.3 — Sort
May be read if needed.
20 — The String Library
May be skimmed (also subsections), though ultimately the Lua Reference should be consulted for the exact semantics.
Appendix B: Game event RETURN
usage
The following list notes how the special gv.RETURN
variable
(known simply as RETURN
from CON) is treated for the various events. The game
may either pass some value to a particular event as additional information, or
it can examine RETURN
after the event has finished, invoking some behavior
conditional on its value. If an event is not listed here, its usage of RETURN
is unspecified.
Receives zero, checks for non-zero
These events get passed a value of 0 for RETURN
, and after finishing, check
it for being non-zero, in which case some hard-coded behavior is suppressed:
AIMDOWN
, AIMUP
, CROUCH
, DISPLAYSBAR
, DISPLAYWEAPON
, DOFIRE
,
DRAWWEAPON
, FIRE
, HOLODUKEOFF
, HOLODUKEON
, HOLSTER
, INCURDAMAGE
,
INVENTORY
, JUMP
, LOOKDOWN
, LOOKLEFT
, LOOKRIGHT
, LOOKUP
,
QUICKKICK
, RETURNTOCENTER
, SOARDOWN
, SOARUP
, SWIMDOWN
, SWIMUP
,
TURNAROUND
, USEJETPACK
, USEMEDKIT
, USENIGHTVISION
, USESTEROIDS
.
-
KILLIT
(deprecated from Lua) -
If non-zero, the pending sprite deletion is aborted.
-
DISPLAYROOMS
-
Don’t draw scene if
RETURN
is 1. Values other than 0 and 1 are reserved. -
PRESSEDFIRE
,USE
-
If non-zero, the “fire” or “use” shared key bits are cleared (respectively).
Game considers post-event RETURN
an index
For some events, the game examines RETURN
after they have finished, and
potentially uses its value as some kind of index.
-
CHANGEMENU
(deprecated from Lua) -
Receives and examines
RETURN
as a menu index to change to. -
DISPLAYCROSSHAIR
-
Receives 0. If the post-event
RETURN
equals 1, no crosshair is drawn. If it is greater than one,RETURN
is the tile index of the crosshair to be displayed. The value 0 makes it draw using theCROSSHAIR
tile. -
GETLOADTILE
-
Receives and examines
RETURN
as a background tile for the loading screen. A negative value suppresses drawing it and running the subsequentEVENT_DISPLAYLOADINGSCREEN
entirely. -
GETMENUTILE
-
Receives and examines
RETURN
as a background tile for the menu. -
SOUND
-
Receives and examines
RETURN
as an index of a sound to start playing.
Cheat events
The cheat events receive a hard-coded, item-dependent target amount in
RETURN
. It can be overridden to a different value to make the respective
cheat give a different “full” amount. A negative value is ignored.
CHEATGETSTEROIDS
, CHEATGETHEAT
, CHEATGETBOOT
, CHEATGETSHIELD
,
CHEATGETSCUBA
, CHEATGETHOLODUKE
, CHEATGETJETPACK
, CHEATGETFIRSTAID
.
EVENT_DAMAGEHPLANE
Triggered when a ceiling or a floor (collectively called “hplane”) is
determined as being damaged. The event receives RETURN
in the variable
gv.RETURN
as well as the third, dist
argument to the event
callback function.
[Passing RETURN
in the dist
argument serves the possibility to create chained callbacks for
EVENT_DAMAGEHPLANE
. Otherwise, once gv.RETURN
were assigned to, there would
be no way to obtain its original value in subsequent chained callbacks.]
This
value can be decoded into two parts by passing it to
sector.damagehplane_whatsect
:
function(aci, pli, RETURN) local what, sectnum = sector.damagehplane_whatsect(RETURN) -- (...)
Then,
-
what
is one of the strings'ceiling'
or'floor'
and -
sectnum
is the sector whose hplane is considered to be damaged.
When EVENT_DAMAGEHPLANE
is left, gv.RETURN
is examined to determine the
further action. It may be one of three values given by sector.DAMAGEHPLANE
(abbreviated DHP
in the following):
-
DHP.SUPPRESS
: the hard-wired code that would subsequently be run is suppressed entirely -
DHP.DEFAULT
: The default code for hplane damaging is run. For floors, it does nothing. For ceilings, it checks whether it has a tile number in a hard-coded set of values depicting a breakable light. In that case, the tile number is changed to the “broken” version and a “glass breaking” effect consisting of playing a sound and spawning glass sprites is started. Also, certain code related to SE3 and SE12 effects is run. -
DHP.GLASSBREAK
: The light-breaking effect described above is run unconditionally, but without changing the hplane’s tile number, which is assumed to have been done by the event.
If value last assigned to RETURN
is not one in the above-mentioned set when
EVENT_DAMAGEHPLANE
is left, the behavior is undefined.
TODO
CHANGEWEAPON
, CHECKFLOORDAMAGE
, CHECKTOUCHDAMAGE
, NEXTWEAPON
,
PREVIOUSWEAPON
, SELECTWEAPON
, WEAPKEY*
.