mirror of
https://github.com/ZDoom/raze-gles.git
synced 2024-12-26 03:30:46 +00:00
3835019c70
git-svn-id: https://svn.eduke32.com/eduke32@3964 1a8010ca-5511-0410-912e-c29ae57300e0
1220 lines
34 KiB
Lua
1220 lines
34 KiB
Lua
|
|
-- This file contains LuaJIT definitions of stuff that's common to the game and
|
|
-- editor. The 'decl' function is expected to be defined in the global
|
|
-- environment.
|
|
-- See the included license file "BUILDLIC.TXT" for license info.
|
|
|
|
-- Will be 'true' if running from engine Lua state:
|
|
local _LUNATIC_AUX = _LUNATIC_AUX
|
|
|
|
local ffi = require("ffi")
|
|
local ffiC = ffi.C
|
|
local bit = require("bit")
|
|
|
|
-- Lunatic debugging (mostly bitfield):
|
|
-- ~=0: print diagnostic information
|
|
-- 2: disable JIT compilation
|
|
-- 4: load LuaJIT's 'v' module, printing trace info
|
|
-- 8: load LuaJIT's 'dump' module, printing generated IR/machine code
|
|
ffi.cdef "enum { _DEBUG_LUNATIC=1 }"
|
|
|
|
if (bit.band(ffiC._DEBUG_LUNATIC, 2)~=0) then
|
|
require("jit").off()
|
|
end
|
|
|
|
if (not _LUNATIC_AUX) then
|
|
if (bit.band(ffiC._DEBUG_LUNATIC, 8)~=0) then
|
|
require("dump").on("+rs")
|
|
elseif (bit.band(ffiC._DEBUG_LUNATIC, 4)~=0) then
|
|
require("v").on()
|
|
end
|
|
end
|
|
|
|
local math = require("math")
|
|
local string = require("string")
|
|
local table = require("table")
|
|
|
|
local assert = assert
|
|
local error = error
|
|
local pairs = pairs
|
|
local require = require
|
|
local setmetatable = setmetatable
|
|
local tostring = tostring
|
|
local type = type
|
|
|
|
local decl = assert(decl)
|
|
local getfenv = getfenv
|
|
|
|
decl "void OSD_Printf(const char *fmt, ...);"
|
|
print = function(str)
|
|
-- our "print" doesn't use the global "tostring", but the initial one
|
|
str = tostring(str)
|
|
if (type(str) ~= "string") then
|
|
error("invalid argument to print: must be convertible to a string")
|
|
end
|
|
ffiC.OSD_Printf("%s\n", str)
|
|
end
|
|
|
|
local print=print
|
|
|
|
|
|
module(...)
|
|
|
|
|
|
local band = bit.band
|
|
local bor = bit.bor
|
|
local bnot = bit.bnot
|
|
local lshift = bit.lshift
|
|
local rshift = bit.rshift
|
|
local xor = bit.bxor
|
|
|
|
|
|
--== bitint type factory ==--
|
|
|
|
-- Metatable for an integer type that is treated as bitfield. The integer
|
|
-- itself must be named '_v'.
|
|
local bitint_mt = {
|
|
__index = {
|
|
set = function(self, bits)
|
|
self._v = bor(self._v, bits)
|
|
end,
|
|
|
|
clear = function(self, bits)
|
|
self._v = band(self._v, bnot(bits))
|
|
end,
|
|
|
|
flip = function(self, bits)
|
|
self._v = xor(self._v, bits)
|
|
end,
|
|
|
|
test = function(self, bits)
|
|
return (band(self._v, bits) ~= 0)
|
|
end,
|
|
|
|
mask = function(self, bits)
|
|
return band(self._v, bits)
|
|
end,
|
|
},
|
|
|
|
__metatable = true,
|
|
}
|
|
|
|
local bitint_to_base_type = {}
|
|
|
|
function bitint_new_struct_type(basetypename, newtypename)
|
|
assert(bitint_to_base_type[newtypename] == nil)
|
|
assert(type(basetypename)=="string")
|
|
assert(type(newtypename)=="string")
|
|
|
|
local bitint_struct_t = ffi.typeof("struct { $ _v; }", ffi.typeof(basetypename))
|
|
ffi.metatype(bitint_struct_t, bitint_mt)
|
|
ffi.cdef("typedef $ $;", bitint_struct_t, newtypename)
|
|
|
|
bitint_to_base_type[newtypename] = basetypename
|
|
end
|
|
|
|
function bitint_member(bitint_struct_typename, membname)
|
|
return string.format("union { %s %s; %s %sbits; };",
|
|
bitint_to_base_type[bitint_struct_typename], membname,
|
|
bitint_struct_typename, membname)
|
|
end
|
|
|
|
bitint_new_struct_type("uint8_t", "UBit8")
|
|
bitint_new_struct_type("uint16_t", "UBit16")
|
|
|
|
|
|
--== Core engine structs ==--
|
|
|
|
local CF_MEMBERS = [[
|
|
const int16_t ~picnum;
|
|
int16_t ~heinum;
|
|
const int16_t ~bunch;
|
|
]]..bitint_member("UBit16", "~stat")..[[
|
|
int32_t ~z;
|
|
int8_t ~shade;
|
|
uint8_t ~pal, ~xpanning, ~ypanning;
|
|
]]
|
|
|
|
ffi.cdef("typedef struct { "..CF_MEMBERS:gsub("~","").." } ceiling_or_floor_t;")
|
|
|
|
local SECTOR_STRUCT = [[
|
|
struct {
|
|
const int16_t wallptr, wallnum;
|
|
]]..
|
|
string.format([[
|
|
union {
|
|
struct { ceiling_or_floor_t ceiling, floor; };
|
|
struct { %s %s };
|
|
};
|
|
]], CF_MEMBERS:gsub("~","ceiling"), CF_MEMBERS:gsub("~","floor"))
|
|
..[[
|
|
uint8_t visibility, filler;
|
|
int16_t lotag, hitag; // NOTE: signed for Lunatic
|
|
int16_t extra;
|
|
}]]
|
|
|
|
local SPRITE_STRUCT = [[
|
|
struct {
|
|
// TODO: transparent union with vec3_t pos?
|
|
int32_t x, y, z;
|
|
]]..bitint_member("UBit16", "cstat")..[[
|
|
const int16_t picnum;
|
|
int8_t shade;
|
|
uint8_t pal, clipdist, filler;
|
|
uint8_t xrepeat, yrepeat;
|
|
int8_t xoffset, yoffset;
|
|
const int16_t sectnum, statnum;
|
|
int16_t ang;
|
|
|
|
const int16_t owner;
|
|
int16_t xvel;
|
|
// NOTE: yvel is often used as player index in game code.
|
|
const int16_t yvel;
|
|
int16_t zvel;
|
|
|
|
int16_t lotag, hitag, extra;
|
|
}]]
|
|
|
|
local WALL_STRUCT = [[
|
|
struct {
|
|
int32_t x, y;
|
|
const int16_t point2, nextwall, nextsector;
|
|
const int16_t upwall, dnwall;
|
|
]]..bitint_member("UBit16", "cstat")..[[
|
|
const int16_t picnum, overpicnum;
|
|
int8_t shade;
|
|
uint8_t pal, xrepeat, yrepeat, xpanning, ypanning;
|
|
int16_t lotag, hitag, extra;
|
|
}]]
|
|
|
|
-- Converts a template struct definition to an internal, unrestricted one.
|
|
-- NOTE: "[^ ]*" for const decorations in defs.ilua.
|
|
function strip_const(structstr)
|
|
return (string.gsub(structstr, "const[^ ]* ", ""));
|
|
end
|
|
|
|
local function maybe_strip_const(str)
|
|
return _LUNATIC_AUX and strip_const(str) or str
|
|
end
|
|
|
|
-- NOTE for FFI definitions: we're compiling EDuke32 with -funsigned-char, so
|
|
-- we need to take care to declare chars as unsigned whenever it matters, for
|
|
-- example if it represents a palette index. (I think it's harmless for stuff
|
|
-- like passing a function argument, but it should be done there for clarity.)
|
|
|
|
-- TODO: provide getters for unsigned {hi,lo}tag?
|
|
ffi.cdef([[
|
|
typedef $ sectortype;
|
|
typedef $ walltype;
|
|
// NOTE: spritetype and tspritetype are different types with the same data members.
|
|
typedef $ spritetype;
|
|
typedef struct { spritetype; } tspritetype;
|
|
|
|
typedef struct {
|
|
int32_t x, y, z;
|
|
} vec3_t;
|
|
|
|
typedef struct {
|
|
const uint32_t mdanimtims;
|
|
const int16_t mdanimcur;
|
|
int16_t angoff, pitch, roll;
|
|
vec3_t mdoff;
|
|
]]..bitint_member("UBit8", "flags")..[[
|
|
uint8_t xpanning, ypanning;
|
|
const uint8_t filler;
|
|
float alpha;
|
|
// NOTE: const aggregate fixed with LuaJIT git fe9934feea0a8d580de1
|
|
// ("FFI: Fix handling of qualified transparent structs/unions.")
|
|
const union {
|
|
intptr_t _tspr;
|
|
struct { int32_t _dummy0, _dummy1; };
|
|
};
|
|
} spriteext_t;
|
|
|
|
typedef struct {
|
|
vec3_t pos;
|
|
int16_t sprite, wall, sect;
|
|
} hitdata_t;
|
|
]],
|
|
ffi.typeof(maybe_strip_const(SECTOR_STRUCT)),
|
|
ffi.typeof(maybe_strip_const(WALL_STRUCT)),
|
|
ffi.typeof(maybe_strip_const(SPRITE_STRUCT)))
|
|
|
|
if (not _LUNATIC_AUX) then
|
|
-- Define the "palette_t" type, which for us has .{r,g,b} fields and a
|
|
-- bound-checking array of length 3 overlaid.
|
|
local rgbarray_t = require("bcarray").new("uint8_t", 3, "RGB array", "palette_t",
|
|
{ "r", "g", "b", "f" })
|
|
ffi.cdef("typedef union { \
|
|
struct { uint8_t r, g, b, f; }; \
|
|
$ col; \
|
|
} palette_t", rgbarray_t)
|
|
|
|
assert(ffi.alignof("palette_t")==1)
|
|
end
|
|
|
|
local vec3_ct = ffi.typeof("vec3_t") -- will be metatype'd in xmath.lua:
|
|
|
|
if (not _LUNATIC_AUX) then
|
|
require("xmath")
|
|
end
|
|
|
|
local hitdata_ct = ffi.typeof("hitdata_t")
|
|
|
|
decl[[
|
|
const int32_t engine_main_arrays_are_static;
|
|
const int32_t engine_v8;
|
|
]]
|
|
|
|
|
|
--== Engine data and functions ==--
|
|
|
|
|
|
-- NOTE TO SELF: This is not C, never EVER write
|
|
-- if (x)
|
|
-- when checking a C variable x for 'thuthiness'
|
|
if (ffiC.engine_main_arrays_are_static ~= 0) then
|
|
decl[[
|
|
sectortype sector[];
|
|
walltype wall[];
|
|
spritetype sprite[];
|
|
tspritetype tsprite[];
|
|
spriteext_t spriteext[];
|
|
]]
|
|
else
|
|
decl[[
|
|
sectortype *sector;
|
|
walltype *wall;
|
|
spritetype *sprite;
|
|
tspritetype *tsprite;
|
|
spriteext_t *spriteext;
|
|
]]
|
|
end
|
|
|
|
if (ffiC.engine_v8 == 0) then
|
|
-- V7
|
|
ffi.cdef[[
|
|
enum
|
|
{
|
|
MAXSECTORS = 1024,
|
|
MAXWALLS = 8192,
|
|
MAXSPRITES = 4096,
|
|
}
|
|
]]
|
|
else
|
|
-- V8
|
|
ffi.cdef[[
|
|
enum
|
|
{
|
|
MAXSECTORS = 4096,
|
|
MAXWALLS = 16384,
|
|
MAXSPRITES = 16384,
|
|
}
|
|
]]
|
|
end
|
|
|
|
ffi.cdef[[
|
|
enum {
|
|
MAXSTATUS = 1024,
|
|
MAXTILES = 30720,
|
|
MAXSPRITESONSCREEN = 4096,
|
|
|
|
MAXBUNCHES = 512,
|
|
CEILING = 0,
|
|
FLOOR = 1,
|
|
|
|
CLIPMASK0 = (1<<16)+1, // blocking
|
|
CLIPMASK1 = (256<<16)+64, // hittable
|
|
};
|
|
]]
|
|
|
|
ffi.cdef(maybe_strip_const("const int16_t numsectors, numwalls;"))
|
|
|
|
ffi.cdef[[
|
|
const int32_t numyaxbunches; // XXX
|
|
const int32_t totalclock;
|
|
int32_t randomseed; // DEPRECATED
|
|
const int32_t xdim, ydim;
|
|
const int32_t windowx1, windowy1, windowx2, windowy2;
|
|
]]
|
|
|
|
decl[[
|
|
int32_t kopen4load(const char *filename, char searchfirst);
|
|
int32_t kfilelength(int32_t handle);
|
|
void kclose(int32_t handle);
|
|
int32_t kread(int32_t handle, void *buffer, int32_t leng);
|
|
int32_t klseek(int32_t handle, int32_t offset, int32_t whence);
|
|
|
|
int32_t sectorofwall_noquick(int16_t theline);
|
|
]]
|
|
|
|
-- Reads the whole file given by the k* file descriptor into a Lua string.
|
|
-- Always closes the file descriptor.
|
|
function readintostr(fd, kopen4load_func)
|
|
-- XXX: this is pretty much the same as the code in L_RunOnce()
|
|
|
|
local sz = ffiC.kfilelength(fd)
|
|
if (sz == 0) then
|
|
ffiC.kclose(fd)
|
|
return ""
|
|
end
|
|
|
|
if (sz < 0) then
|
|
ffi.kclose(fd)
|
|
error("INTERNAL ERROR: kfilelength() returned negative length")
|
|
end
|
|
|
|
local str = ffi.new("char [?]", sz)
|
|
local readlen = ffiC.kread(fd, str, sz)
|
|
|
|
ffiC.kclose(fd); fd=-1
|
|
|
|
if (readlen ~= sz) then
|
|
error("INTERNAL ERROR: couldn't read file wholly")
|
|
end
|
|
|
|
return ffi.string(str, sz)
|
|
end
|
|
|
|
if (_LUNATIC_AUX) then
|
|
-- XXX: The global doesn't show up in 'engine_maptext'.
|
|
-- I guess I still haven't fully grokked globals in Lua.
|
|
string.readintostr = readintostr
|
|
require "engine_maptext"
|
|
return
|
|
end
|
|
|
|
ffi.cdef "const int32_t rendmode;"
|
|
|
|
decl[[
|
|
int32_t yxaspect;
|
|
int32_t viewingrange;
|
|
int32_t spritesortcnt;
|
|
int32_t guniqhudid;
|
|
const int16_t headspritesect[MAXSECTORS+1], headspritestat[MAXSTATUS+1];
|
|
const int16_t prevspritesect[MAXSPRITES], prevspritestat[MAXSPRITES];
|
|
const int16_t nextspritesect[MAXSPRITES], nextspritestat[MAXSPRITES];
|
|
const int16_t tilesizx[MAXTILES], tilesizy[MAXTILES];
|
|
|
|
uint8_t show2dsector[(MAXSECTORS+7)>>3];
|
|
|
|
const int16_t headsectbunch[2][MAXBUNCHES], nextsectbunch[2][MAXSECTORS];
|
|
|
|
int16_t yax_getbunch(int16_t i, int16_t cf);
|
|
|
|
int32_t getceilzofslopeptr(const sectortype *sec, int32_t dax, int32_t day);
|
|
int32_t getflorzofslopeptr(const sectortype *sec, int32_t dax, int32_t day);
|
|
void getzsofslopeptr(const sectortype *sec, int32_t dax, int32_t day,
|
|
int32_t *ceilz, int32_t *florz);
|
|
int32_t spriteheightofsptr(const spritetype *spr, int32_t *height, int32_t alsotileyofs);
|
|
|
|
int32_t changespritesect(int16_t spritenum, int16_t newsectnum);
|
|
int32_t changespritestat(int16_t spritenum, int16_t newstatnum);
|
|
|
|
int32_t hitscan(const vec3_t *sv, int16_t sectnum, int32_t vx, int32_t vy, int32_t vz,
|
|
hitdata_t *hitinfo, uint32_t cliptype);
|
|
int32_t cansee(int32_t x1, int32_t y1, int32_t z1, int16_t sect1,
|
|
int32_t x2, int32_t y2, int32_t z2, int16_t sect2);
|
|
void neartag(int32_t xs, int32_t ys, int32_t zs, int16_t sectnum, int16_t ange, int16_t *neartagsector, int16_t *neartagwall,
|
|
int16_t *neartagsprite, int32_t *neartaghitdist, int32_t neartagrange, uint8_t tagsearch,
|
|
int32_t (*blacklist_sprite_func)(int32_t));
|
|
void dragpoint(int16_t pointhighlight, int32_t dax, int32_t day, uint8_t flags);
|
|
void getzrange(const vec3_t *pos, int16_t sectnum,
|
|
int32_t *ceilz, int32_t *ceilhit, int32_t *florz, int32_t *florhit,
|
|
int32_t walldist, uint32_t cliptype);
|
|
int32_t clipmovex(vec3_t *pos, int16_t *sectnum, int32_t xvect, int32_t yvect,
|
|
int32_t walldist, int32_t ceildist, int32_t flordist, uint32_t cliptype,
|
|
uint8_t noslidep);
|
|
|
|
int32_t ldist(const spritetype *s1, const spritetype *s2);
|
|
int32_t dist(const spritetype *s1, const spritetype *s2);
|
|
|
|
int32_t inside(int32_t x, int32_t y, int16_t sectnum);
|
|
void updatesector(int32_t x, int32_t y, int16_t *sectnum);
|
|
void updatesectorbreadth(int32_t x, int32_t y, int16_t *sectnum);
|
|
void updatesectorz(int32_t x, int32_t y, int32_t z, int16_t *sectnum);
|
|
|
|
void rotatesprite_(int32_t sx, int32_t sy, int32_t z, int16_t a, int16_t picnum,
|
|
int8_t dashade, unsigned char dapalnum, int32_t dastat, uint8_t alpha,
|
|
int32_t cx1, int32_t cy1, int32_t cx2, int32_t cy2);
|
|
|
|
void setaspect(int32_t daxrange, int32_t daaspect);
|
|
]]
|
|
|
|
-- misc. functions
|
|
ffi.cdef[[
|
|
uint32_t getticks(void);
|
|
double gethiticks(void);
|
|
|
|
int32_t krand(void);
|
|
int32_t ksqrt(uint32_t num);
|
|
int32_t __fastcall getangle(int32_t xvect, int32_t yvect);
|
|
int32_t Mulscale(int32_t a, int32_t b, int32_t sh);
|
|
]]
|
|
|
|
local bcheck = require("bcheck")
|
|
local check_sector_idx = bcheck.sector_idx
|
|
local check_wall_idx = bcheck.wall_idx
|
|
local check_sprite_idx = bcheck.sprite_idx
|
|
local check_tile_idx = bcheck.tile_idx
|
|
|
|
local wallsofsec -- fwd-decl
|
|
|
|
local sectortype_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(SECTOR_STRUCT)))
|
|
|
|
local function get_sector_idx(sec)
|
|
local i = ffi.cast(sectortype_ptr_ct, sec)-ffi.cast(sectortype_ptr_ct, ffiC.sector)
|
|
-- assert(i >= 0 and i < ffiC.numsectors)
|
|
return i
|
|
end
|
|
|
|
local zret = ffi.new("int32_t [4]")
|
|
local zret_t = ffi.typeof[[const struct {
|
|
struct {
|
|
bool spritep;
|
|
int32_t num; // number of sector or sprite
|
|
int32_t z;
|
|
} c, f;
|
|
}]]
|
|
|
|
local sectortype_mt = {
|
|
__index = {
|
|
--- Setters
|
|
set_ceilingpicnum = function(s, picnum)
|
|
check_tile_idx(picnum)
|
|
ffi.cast(sectortype_ptr_ct, s).ceilingpicnum = picnum
|
|
end,
|
|
|
|
set_floorpicnum = function(s, picnum)
|
|
check_tile_idx(picnum)
|
|
ffi.cast(sectortype_ptr_ct, s).floorpicnum = picnum
|
|
end,
|
|
|
|
--- Other methods
|
|
ceilingzat = function(s, pos)
|
|
return ffiC.getceilzofslopeptr(s, pos.x, pos.y)
|
|
end,
|
|
|
|
floorzat = function(s, pos)
|
|
return ffiC.getflorzofslopeptr(s, pos.x, pos.y)
|
|
end,
|
|
|
|
-- getzrange() interface
|
|
zrangeat = function(s, pos, walldist, cliptype)
|
|
local sectnum = get_sector_idx(s)
|
|
local ipos = vec3_ct(pos.x, pos.y, pos.z)
|
|
walldist = walldist or 128
|
|
cliptype = cliptype or ffiC.CLIPMASK0
|
|
|
|
ffiC.getzrange(ipos, sectnum, zret+0, zret+1, zret+2, zret+3,
|
|
walldist, cliptype)
|
|
local ceilz, ceilhit, florz, florhit = zret[0], zret[1], zret[2], zret[3]
|
|
|
|
return zret_t({ ceilhit>=49152, bit.band(ceilhit,16383), ceilz },
|
|
{ florhit>=49152, bit.band(florhit,16383), florz })
|
|
end,
|
|
|
|
-- inside() port, OUTOFSYNC with engine.c
|
|
contains = function(s, pos)
|
|
local x, y = pos.x, pos.y
|
|
local cnt = 0
|
|
|
|
for w in wallsofsec(s) do
|
|
local wal2 = ffiC.wall[ffiC.wall[w].point2]
|
|
local y1, y2 = ffiC.wall[w].y-y, wal2.y-y
|
|
|
|
if (xor(y1, y2) < 0) then
|
|
local x1, x2 = ffiC.wall[w].x-x, wal2.x-x
|
|
|
|
if (xor(x1, x2)>=0) then
|
|
cnt = xor(cnt, x1)
|
|
else
|
|
cnt = xor(cnt, xor(x1*y2-x2*y1, y2))
|
|
end
|
|
end
|
|
end
|
|
|
|
return (cnt < 0)
|
|
end,
|
|
}
|
|
}
|
|
ffi.metatype("sectortype", sectortype_mt)
|
|
|
|
local walltype_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(WALL_STRUCT)))
|
|
|
|
local walltype_mt = {
|
|
__index = {
|
|
--- Setters
|
|
set_picnum = function(w, picnum)
|
|
check_tile_idx(picnum)
|
|
ffi.cast(walltype_ptr_ct, w).picnum = picnum
|
|
end,
|
|
|
|
set_overpicnum = function(w, picnum)
|
|
check_tile_idx(picnum)
|
|
ffi.cast(walltype_ptr_ct, w).overpicnum = picnum
|
|
end,
|
|
|
|
_set_nextwall = function(w, nextwall)
|
|
-- NOTE: Allow setting a wall to white too, but no checking of the
|
|
-- consistency invariant ".nextwall>=0 iff .nextsector>=0".
|
|
if (not (nextwall < 0)) then
|
|
check_wall_idx(nextwall)
|
|
end
|
|
ffi.cast(walltype_ptr_ct, w).nextwall = nextwall
|
|
end,
|
|
|
|
_set_nextsector = function(w, nextsector)
|
|
if (not (nextsector < 0)) then
|
|
check_sector_idx(nextsector)
|
|
end
|
|
ffi.cast(walltype_ptr_ct, w).nextsector = nextsector
|
|
end,
|
|
|
|
--- Predicates
|
|
isblocking = function(w)
|
|
return (band(w.cstat, 1)~=0)
|
|
end,
|
|
|
|
ismasked = function(w)
|
|
return (band(w.cstat, 16)~=0)
|
|
end,
|
|
|
|
isoneway = function(w)
|
|
return (band(w.cstat, 32)~=0)
|
|
end,
|
|
|
|
ishittable = function(w)
|
|
return (band(w.cstat, 64)~=0)
|
|
end,
|
|
|
|
-- Indexing a wall with 'z' gets 0, so that you can e.g. use a wall as
|
|
-- RHS to vec3_t addition.
|
|
z = 0,
|
|
}
|
|
}
|
|
ffi.metatype("walltype", walltype_mt)
|
|
|
|
local spriteext_mt = {
|
|
__index = {
|
|
-- Enable EVENT_ANIMATESPRITES for this sprite.
|
|
-- XXX: unused?
|
|
make_animated = function(sx)
|
|
sx.flags = bor(sx.flags, 16)
|
|
end,
|
|
},
|
|
}
|
|
ffi.metatype("spriteext_t", spriteext_mt)
|
|
|
|
local spritetype_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(SPRITE_STRUCT)))
|
|
-- NOTE: this is the *protected* tspritetype pointer.
|
|
local tspritetype_ptr_ct = ffi.typeof("$ *", ffi.typeof("tspritetype"))
|
|
|
|
local intarg = ffi.new("int32_t[1]")
|
|
|
|
local spritetype_mt = {
|
|
__pow = function(s, zofs)
|
|
return vec3_ct(s.x, s.y, s.z-zofs)
|
|
end,
|
|
|
|
__index = {
|
|
--- Setters
|
|
set_picnum = function(s, tilenum)
|
|
check_tile_idx(tilenum)
|
|
ffi.cast(spritetype_ptr_ct, s).picnum = tilenum
|
|
end,
|
|
|
|
_set_yvel = function(s, yvel)
|
|
-- XXX: no protection against malicious use (might set picnum to
|
|
-- another one temporarily)
|
|
-- XXX: this belongs into game-side Lunatic
|
|
if (s.picnum==1405) then -- APLAYER
|
|
error("setting yvel on an APLAYER sprite forbidden", 2)
|
|
end
|
|
ffi.cast(spritetype_ptr_ct, s).yvel = yvel
|
|
end,
|
|
|
|
_set_owner = function(s, owner)
|
|
-- XXX: AMC TC sets owner to -1 in the cutscene.
|
|
check_sprite_idx(owner)
|
|
ffi.cast(spritetype_ptr_ct, s).owner = owner
|
|
end,
|
|
|
|
--- Custom methods ---
|
|
|
|
getheightofs = function(s)
|
|
-- XXX: better reimplement in Lua?
|
|
local zofs = ffiC.spriteheightofsptr(s, intarg, 0)
|
|
return intarg[0], zofs
|
|
end,
|
|
},
|
|
}
|
|
|
|
local function deep_copy(tab)
|
|
local ntab = {}
|
|
for key, val in pairs(tab) do
|
|
if (type(val)=="table") then
|
|
ntab[key] = deep_copy(val)
|
|
else
|
|
assert(type(val)=="function")
|
|
ntab[key] = val
|
|
end
|
|
end
|
|
return ntab
|
|
end
|
|
|
|
local tspritetype_mt = deep_copy(spritetype_mt)
|
|
|
|
-- Get the sprite index of a sprite reference.
|
|
-- This is relatively slow if the code doesn't get compiled, see related
|
|
-- discussion here:
|
|
-- http://www.freelists.org/post/luajit/FFI-versus-Lua-C-API-in-purely-interpreted-mode
|
|
local function get_sprite_idx(spr)
|
|
local i = ffi.cast(spritetype_ptr_ct, spr)-ffi.cast(spritetype_ptr_ct, ffiC.sprite)
|
|
-- assert(i >= 0 and i < ffiC.MAXSPRITES)
|
|
return i
|
|
end
|
|
|
|
---=== Methods that are specific to sprites ===---
|
|
|
|
local l_updatesector -- fwd-decl
|
|
local l_changesect -- fwd-decl
|
|
|
|
-- The 'setpos' method is available for sprite and tsprite objects.
|
|
-- spr:setpos(pos [, newsect]),
|
|
-- where <newsect> is taken to mean "set the sprite's sector number to <newsect>";
|
|
-- don't run update/search routines or anything like this.
|
|
function spritetype_mt.__index.setpos(spr, pos, newsect)
|
|
spr.x, spr.y, spr.z = pos.x, pos.y, pos.z
|
|
if (newsect ~= nil) then
|
|
spr:changesect(newsect)
|
|
end
|
|
return spr
|
|
end
|
|
|
|
-- spr:changesect(newsect)
|
|
-- changes the sector number of <spr> in an 'unforgiving' fashion (<newsect> of
|
|
-- -1 is error)
|
|
function spritetype_mt.__index.changesect(spr, newsect)
|
|
if (newsect ~= spr.sectnum) then
|
|
l_changesect(get_sprite_idx(spr), newsect)
|
|
end
|
|
end
|
|
|
|
-- spr:updatesect(flags)
|
|
-- updates <spr>'s sectnum; if no matching sector is found, no-op
|
|
-- returns the updated sector number
|
|
function spritetype_mt.__index.updatesect(spr, flags)
|
|
local newsect = l_updatesector(spr, spr.sectnum, flags)
|
|
if (newsect ~= -1) then
|
|
spr:changesect(newsect)
|
|
end
|
|
return newsect
|
|
end
|
|
|
|
|
|
---=== Methods that are specific to tsprites ===---
|
|
|
|
-- This ought to be called 'set_sectnum', but 'changesect' is for consistency
|
|
-- with the sprite method.
|
|
function tspritetype_mt.__index.changesect(tspr, sectnum)
|
|
if (tspr.sectnum ~= sectnum) then
|
|
check_sector_idx(sectnum)
|
|
ffi.cast(spritetype_ptr_ct, tspr).sectnum = sectnum
|
|
end
|
|
end
|
|
|
|
function tspritetype_mt.__index.setpos(tspr, pos, newsect)
|
|
tspr.x, tspr.y, tspr.z = pos.x, pos.y, pos.z
|
|
if (newsect ~= nil) then
|
|
tspr:changesect(newsect)
|
|
end
|
|
return tspr
|
|
end
|
|
|
|
function tspritetype_mt.__index.updatesect(tspr, flags)
|
|
local newsect = l_updatesector(tspr, tspr.sectnum, flags)
|
|
if (newsect ~= -1 and newsect ~= tspr.sectnum) then
|
|
tspr:changesect(newsect)
|
|
end
|
|
return newsect
|
|
end
|
|
|
|
function tspritetype_mt.__index.dup(tspr)
|
|
if (ffiC.spritesortcnt >= ffiC.MAXSPRITESONSCREEN+0ULL) then
|
|
return nil
|
|
end
|
|
|
|
local newtspr = ffiC.tsprite[ffiC.spritesortcnt]
|
|
ffi.copy(newtspr, tspr, ffi.sizeof(tspr))
|
|
ffiC.spritesortcnt = ffiC.spritesortcnt+1
|
|
|
|
return newtspr
|
|
end
|
|
|
|
function tspritetype_mt.__index.getspr(tspr)
|
|
check_sprite_idx(tspr.owner)
|
|
return ffiC.sprite[tspr.owner]
|
|
end
|
|
|
|
---======---
|
|
|
|
-- The user of this module can insert additional "spritetype" index
|
|
-- methods and register them with "ffi.metatype".
|
|
function finish_spritetype(mt_index)
|
|
for name, func in pairs(mt_index) do
|
|
spritetype_mt.__index[name] = func
|
|
tspritetype_mt.__index[name] = func
|
|
end
|
|
ffi.metatype("spritetype", spritetype_mt)
|
|
ffi.metatype("tspritetype", tspritetype_mt)
|
|
end
|
|
|
|
|
|
---=== Restricted access to C variables from Lunatic ===---
|
|
|
|
-- set metatable and forbid setting it further
|
|
function setmtonce(tab, mt)
|
|
mt.__metatable = true
|
|
return setmetatable(tab, mt)
|
|
end
|
|
|
|
---- indirect C array access ----
|
|
|
|
-- create a safe indirection for an ffi.C array
|
|
function creategtab(ctab, maxidx, name)
|
|
local tab = {}
|
|
local tmpmt = {
|
|
__index = function(tab, key)
|
|
if (key>=0 and key < maxidx) then
|
|
return ctab[key]
|
|
end
|
|
error('out-of-bounds '..name..' read access', 2)
|
|
end,
|
|
__newindex = function()
|
|
error('cannot write directly to '..name, 2)
|
|
end,
|
|
}
|
|
|
|
return setmtonce(tab, tmpmt)
|
|
end
|
|
|
|
-- Construct const struct from table
|
|
function conststruct(tab)
|
|
local strtab = { "const struct { int32_t " }
|
|
local vals = {}
|
|
|
|
for member, val in pairs(tab) do
|
|
strtab[#strtab+1] = member..","
|
|
vals[#vals+1] = val
|
|
end
|
|
strtab[#strtab] = strtab[#strtab]:gsub(',',';')
|
|
strtab[#strtab+1] = "}"
|
|
|
|
return ffi.new(table.concat(strtab), vals)
|
|
end
|
|
|
|
-- Static, non-instance members. Used to hold constants, for example
|
|
-- sprite.CSTAT.TRANS1
|
|
local static_members = { sector={}, wall={}, sprite={} }
|
|
|
|
static_members.sector.STAT = conststruct
|
|
{
|
|
PARALLAX = 1,
|
|
SLOPE = 2,
|
|
SWAPXY = 4,
|
|
SMOOSH = 8,
|
|
FLIPX = 16,
|
|
FLIPY = 32,
|
|
RELATIVE = 64,
|
|
MASK = 128,
|
|
-- NOTE the reversed order
|
|
TRANS2 = 128,
|
|
TRANS1 = 256,
|
|
BLOCK = 512,
|
|
HITSCAN = 1024,
|
|
|
|
FLIP_BITMASK = 16+32,
|
|
ORIENT_BITMASK = 4+16+32,
|
|
TRANS_BITMASK = 128+256,
|
|
}
|
|
|
|
static_members.sector.NEARTAG_FLAGS = conststruct
|
|
{
|
|
LOTAG = 1,
|
|
HITAG = 2,
|
|
NOSPRITES = 4,
|
|
}
|
|
|
|
static_members.sector.UPDATE_FLAGS = conststruct
|
|
{
|
|
BREADTH = 1,
|
|
Z = 2,
|
|
}
|
|
|
|
static_members.wall.CSTAT = conststruct
|
|
{
|
|
BLOCK = 1,
|
|
BOTTOMSWAP = 2,
|
|
ALIGNBOTTOM = 4,
|
|
FLIPX = 8,
|
|
MASK = 16,
|
|
ONEWAY = 32,
|
|
HITSCAN = 64,
|
|
TRANS1 = 128,
|
|
FLIPY = 256,
|
|
TRANS2 = 512,
|
|
|
|
FLIP_BITMASK = 8+256,
|
|
TRANS_BITMASK = 128+512,
|
|
}
|
|
|
|
static_members.sprite.CSTAT = conststruct
|
|
{
|
|
BLOCK = 1,
|
|
TRANS1 = 2,
|
|
XFLIP = 4,
|
|
YFLIP = 8,
|
|
ALIGNWALL = 16,
|
|
ALIGNFLOOR = 32,
|
|
ONESIDE = 64,
|
|
CENTER = 128,
|
|
HITSCAN = 256,
|
|
TRANS2 = 512,
|
|
|
|
ALIGN_BITMASK = 16+32,
|
|
TRANS_BITMASK = 2+512,
|
|
}
|
|
|
|
local bitar = require("bitar")
|
|
-- XXX: bitar uses int32_t arrays, while show2dsector[] is a uint8_t
|
|
-- array. Potential unaligned access. Also, only works on little-endian
|
|
-- machines. This sucks.
|
|
static_members.sector.showbitmap = bitar.new(ffiC.MAXSECTORS-1, ffi.cast("int32_t *", ffiC.show2dsector))
|
|
|
|
local sms = static_members.sprite
|
|
sms._headspritesect = creategtab(ffiC.headspritesect, ffiC.MAXSECTORS, 'headspritesect[]')
|
|
-- NOTE: don't allow freelist access
|
|
sms._headspritestat = creategtab(ffiC.headspritestat, ffiC.MAXSTATUS, 'headspritestat[]')
|
|
sms._nextspritesect = creategtab(ffiC.nextspritesect, ffiC.MAXSPRITES, 'nextspritesect[]')
|
|
sms._nextspritestat = creategtab(ffiC.nextspritestat, ffiC.MAXSPRITES, 'nextspritestat[]')
|
|
sms._prevspritesect = creategtab(ffiC.prevspritesect, ffiC.MAXSPRITES, 'prevspritesect[]')
|
|
sms._prevspritestat = creategtab(ffiC.prevspritestat, ffiC.MAXSPRITES, 'prevspritestat[]')
|
|
|
|
function static_members.wall.dragto(wallnum, pos)
|
|
check_wall_idx(wallnum)
|
|
|
|
-- TODO: some kind of validation of the position?
|
|
ffiC.dragpoint(wallnum, pos.x, pos.y, 0)
|
|
end
|
|
|
|
-- Functions changing the sector/status number of a sprite, without asking.
|
|
|
|
-- Changes sector number of sprite with index <spritenum> to <sectnum>,
|
|
-- unconditionally and "unforgiving" (oob <sectnum> gives error).
|
|
-- <noerr> is for CON compatibility and prevents error on *sprite not in the
|
|
-- game world* if true.
|
|
function static_members.sprite.changesect(spritenum, sectnum, noerr)
|
|
check_sprite_idx(spritenum)
|
|
check_sector_idx(sectnum)
|
|
if (ffiC.changespritesect(spritenum, sectnum)==-1 and not noerr) then
|
|
error("cannot change sector number of sprite not in the game world", 2)
|
|
end
|
|
end
|
|
|
|
l_changesect = static_members.sprite.changesect
|
|
|
|
function static_members.sprite.changestat(spritenum, statnum, noerr)
|
|
-- TODO: see gameexec.c's CON_CHANGESPRITESTAT.
|
|
check_sprite_idx(spritenum)
|
|
if (not (statnum >= 0 and statnum < ffiC.MAXSTATUS)) then
|
|
error("invalid status number "..statnum, 2)
|
|
end
|
|
if (ffiC.changespritestat(spritenum, statnum)==-1 and not noerr) then
|
|
error("cannot change status number of sprite not in the game world", 2)
|
|
end
|
|
end
|
|
|
|
-- Update a sprite's sector number from its current position and sector number.
|
|
function static_members.sprite.updatesect(spritenum, flags)
|
|
check_sprite_idx(spritenum)
|
|
local spr = ffiC.sprite[spritenum]
|
|
|
|
local newsect = l_updatesector(spr, spr.sectnum, flags)
|
|
if (newsect ~= -1 and newsect ~= spr.sectnum) then
|
|
l_changesect(spritenum, newsect)
|
|
end
|
|
return newsect
|
|
end
|
|
|
|
function GenStructMetatable(Structname, Boundname, StaticMembersTab)
|
|
StaticMembersTab = StaticMembersTab or static_members[Structname]
|
|
|
|
return {
|
|
__index = function(tab, key)
|
|
if (type(key)=="number") then
|
|
if (key >= 0 and key < ffiC[Boundname]) then
|
|
return ffiC[Structname][key]
|
|
end
|
|
error("out-of-bounds "..Structname.."[] read access with index "..key, 2)
|
|
elseif (type(key)=="string") then
|
|
return StaticMembersTab[key]
|
|
end
|
|
end,
|
|
|
|
__newindex = function() error("cannot write directly to "..Structname.."[]", 2) end,
|
|
}
|
|
end
|
|
|
|
local sector_mt = GenStructMetatable("sector", "numsectors")
|
|
local wall_mt = GenStructMetatable("wall", "numwalls")
|
|
local sprite_mt = GenStructMetatable("sprite", "MAXSPRITES")
|
|
|
|
local atsprite_mt = {
|
|
__index = function(tab, idx)
|
|
check_sprite_idx(idx)
|
|
|
|
local tspr = ffi.cast(tspritetype_ptr_ct, ffiC.spriteext[idx]._tspr)
|
|
if (tspr == nil) then
|
|
error("tsprite of actor "..idx.." unavailable", 2)
|
|
end
|
|
|
|
-- Return a reference to a tsprite[] element.
|
|
return tspr[0]
|
|
end,
|
|
|
|
__newindex = function() error('cannot write directly to atsprite[]', 2) end,
|
|
}
|
|
|
|
|
|
local vars_to_ignore = {}
|
|
for varname,_ in pairs(getfenv(1)) do
|
|
if (ffiC._DEBUG_LUNATIC ~= 0) then
|
|
print("IGNORE "..varname)
|
|
end
|
|
vars_to_ignore[varname] = true
|
|
end
|
|
|
|
--== ALL GLOBALS FROM HERE ON ARE EXPORTED UPWARDS (see create_globals() below) ==--
|
|
|
|
sector = setmtonce({}, sector_mt)
|
|
local sector = sector
|
|
wall = setmtonce({}, wall_mt)
|
|
sprite = setmtonce({}, sprite_mt)
|
|
spriteext = creategtab(ffiC.spriteext, ffiC.MAXSPRITES, 'spriteext[]')
|
|
_atsprite = setmtonce({}, atsprite_mt)
|
|
|
|
local function iter_wallsofsec(endwall, w)
|
|
w = w+1
|
|
if (w < endwall) then
|
|
return w
|
|
end
|
|
end
|
|
|
|
wallsofsec = function(sec) -- local
|
|
return iter_wallsofsec, sec.wallptr+sec.wallnum, sec.wallptr-1
|
|
end
|
|
|
|
function wallsofsect(sect)
|
|
check_sector_idx(sect)
|
|
return iter_wallsofsec, sector[sect].wallptr+sector[sect].wallnum, sector[sect].wallptr-1
|
|
end
|
|
|
|
--== Per-sector/per-statnum sprite iterators ==--
|
|
local function iter_spritesofsect(sect, i)
|
|
if (i < 0) then
|
|
i = ffiC.headspritesect[sect]
|
|
else
|
|
i = ffiC.nextspritesect[i]
|
|
end
|
|
|
|
if (i >= 0) then return i end
|
|
end
|
|
|
|
-- sprites of sectnum iterator that allows deleting the iterated sprite
|
|
local function iter_spritesofsect_safe(tab, i)
|
|
if (i < 0) then
|
|
i = ffiC.headspritesect[-i]
|
|
else
|
|
i = tab[1]
|
|
end
|
|
|
|
if (i >= 0) then
|
|
tab[1] = ffiC.nextspritesect[i]
|
|
return i
|
|
end
|
|
end
|
|
|
|
function spritesofsect(sect, maydelete)
|
|
check_sector_idx(sect)
|
|
|
|
if (maydelete) then
|
|
return iter_spritesofsect_safe, { -1 }, -sect
|
|
else
|
|
return iter_spritesofsect, sect, -1
|
|
end
|
|
end
|
|
|
|
local function iter_spritesofstat(stat, i)
|
|
if (i < 0) then
|
|
i = ffiC.headspritestat[stat]
|
|
else
|
|
i = ffiC.nextspritestat[i]
|
|
end
|
|
|
|
if (i >= 0) then return i end
|
|
end
|
|
|
|
-- sprites of statnum iterator that allows deleting the iterated sprite
|
|
local function iter_spritesofstat_safe(tab, i)
|
|
if (i < 0) then
|
|
i = ffiC.headspritestat[-i]
|
|
else
|
|
i = tab[1]
|
|
end
|
|
|
|
if (i >= 0) then
|
|
tab[1] = ffiC.nextspritestat[i]
|
|
return i
|
|
end
|
|
end
|
|
|
|
function spritesofstat(stat, maydelete)
|
|
if (not (stat >= 0 and stat < ffiC.MAXSTATUS)) then
|
|
error("passed invalid statnum to spritesofstat iterator", 2)
|
|
end
|
|
|
|
if (maydelete) then
|
|
return iter_spritesofstat_safe, { -1 }, -stat
|
|
else
|
|
return iter_spritesofstat, stat, -1
|
|
end
|
|
end
|
|
|
|
--== TROR iterators ==--
|
|
local function iter_sectorsofbunch(cf, i)
|
|
if (i < 0) then
|
|
i = ffiC.headsectbunch[cf][-i-1];
|
|
else
|
|
i = ffiC.nextsectbunch[cf][i];
|
|
end
|
|
|
|
if (i >= 0) then return i end
|
|
end
|
|
|
|
function sectorsofbunch(bunchnum, cf)
|
|
if (not (bunchnum >= 0 and bunchnum < ffiC.numyaxbunches)) then
|
|
error("passed invalid bunchnum to sectorsofbunch iterator", 2)
|
|
end
|
|
if (not (cf == 0 or cf == 1)) then
|
|
error("passed invalid 'cf' to sectorsofbunch iterator, must be 0 or 1", 2)
|
|
end
|
|
|
|
return iter_sectorsofbunch, cf, -bunchnum-1
|
|
end
|
|
|
|
|
|
---=== Engine functions, wrapped for Lua convenience ===---
|
|
|
|
-- returns a hitdata_ct
|
|
-- TODO: make v[xyz] be passed as one aggregate, too?
|
|
-- TODO: make cliptype optional? What should be the default?
|
|
function hitscan(pos, sectnum, vx,vy,vz, cliptype)
|
|
check_sector_idx(sectnum)
|
|
local vec = vec3_ct(pos.x, pos.y, pos.z)
|
|
local hitdata = hitdata_ct()
|
|
|
|
ffiC.hitscan(vec, sectnum, vx,vy,vz, hitdata, cliptype)
|
|
return hitdata
|
|
end
|
|
|
|
function cansee(pos1,sect1, pos2,sect2)
|
|
check_sector_idx(sect1)
|
|
check_sector_idx(sect2)
|
|
|
|
local ret = ffiC.cansee(pos1.x,pos1.y,pos1.z, sect1,
|
|
pos2.x,pos2.y,pos2.z, sect2)
|
|
return (ret~=0)
|
|
end
|
|
|
|
local neartag_ret_ct = ffi.typeof[[
|
|
const struct {
|
|
int32_t sector, wall, sprite;
|
|
int32_t dist;
|
|
}
|
|
]]
|
|
|
|
local function newar() return ffi.new("int16_t [1]") end
|
|
-- NOTE: <tagsearch> flags are in sector.NEARTAG_FLAGS
|
|
function neartag(pos, sectnum, ang, range, tagsearch)
|
|
check_sector_idx(sectnum)
|
|
local a, b, c, d = newar(), newar(), newar(), ffi.new("int32_t [1]")
|
|
ffiC.neartag(pos.x, pos.y, pos.z, sectnum, ang, a, b, c, d, range, tagsearch, nil)
|
|
return neartag_ret_ct(a[0], b[0], c[0], d[0])
|
|
end
|
|
|
|
function inside(pos, sectnum)
|
|
check_sector_idx(sectnum)
|
|
return (ffiC.inside(pos.x, pos.y, sectnum)==1)
|
|
end
|
|
|
|
local us_retsect = ffi.new("int16_t [1]")
|
|
local USF = sector.UPDATE_FLAGS
|
|
|
|
function updatesector(pos, sectnum, flags)
|
|
if (sectnum ~= -1) then
|
|
check_sector_idx(sectnum)
|
|
end
|
|
|
|
us_retsect[0] = sectnum
|
|
|
|
if (flags==nil or flags==0) then
|
|
ffiC.updatesector(pos.x, pos.y, us_retsect)
|
|
elseif (flags==USF.BREADTH) then
|
|
ffiC.updatesectorbreadth(pos.x, pos.y, us_retsect)
|
|
elseif (flags==USF.Z) then
|
|
-- Same as updatesectorz, but useful if we are called from
|
|
-- e.g. sprite.updatesect().
|
|
ffiC.updatesectorz(pos.x, pos.y, pos.z, us_retsect)
|
|
else
|
|
error("invalid argument #3 (flags)", 2)
|
|
end
|
|
|
|
return us_retsect[0]
|
|
end
|
|
|
|
l_updatesector = updatesector
|
|
|
|
function updatesectorz(pos, sectnum, flags)
|
|
if (sectnum ~= -1) then
|
|
check_sector_idx(sectnum)
|
|
end
|
|
if (flags ~= nil) then
|
|
error("invalid argument #3 (flags)", 2)
|
|
end
|
|
|
|
us_retsect[0] = sectnum
|
|
ffiC.updatesectorz(pos.x, pos.y, pos.z, us_retsect)
|
|
return us_retsect[0]
|
|
end
|
|
|
|
function printf(fmt, ...)
|
|
print(string.format(fmt, ...))
|
|
end
|
|
|
|
|
|
-- This is supposed to be run from the file that 'require's this module to take
|
|
-- over the non-local variables from here into its global environment.
|
|
function create_globals(_G_their)
|
|
local _G_our = getfenv(1)
|
|
vars_to_ignore["create_globals"] = true
|
|
|
|
for varname,obj in pairs(_G_our) do
|
|
if (not vars_to_ignore[varname]) then
|
|
if (ffiC._DEBUG_LUNATIC ~= 0) then
|
|
print("EXPORT "..varname)
|
|
end
|
|
_G_their[varname] = obj
|
|
end
|
|
end
|
|
end
|