-- 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, }, __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 { 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 { const uint32_t mdanimtims; const int16_t mdanimcur; int16_t angoff, pitch, roll; // TODO: make into an ivec3_t int32_t xoff, yoff, zoff; ]]..bitint_member("UBit8", "flags")..[[ uint8_t xpanning, ypanning; const uint8_t filler; float alpha; union { const intptr_t _tspr; struct { const int32_t _dummy0, _dummy1; }; }; } spriteext_t; typedef struct { int32_t x, y, z; } vec3_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 geom.lua: if (not _LUNATIC_AUX) then require("geom") 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 \""..fn.."\" 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 decl[[ int32_t yxaspect; int32_t viewingrange; int32_t spritesortcnt; int32_t guniqhudid; const int32_t rendmode; 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 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 gethitickms(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(not (i >= ffiC.numsectors+0ULL)) 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 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, } } ffi.metatype("walltype", walltype_mt) local spriteext_mt = { __index = { -- Enable EVENT_ANIMATESPRITES for this sprite. 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 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, }, } 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) local function get_sprite_idx(spr) local i = ffi.cast(spritetype_ptr_ct, spr)-ffi.cast(spritetype_ptr_ct, ffiC.sprite) assert(not (i >= ffiC.MAXSPRITES+0ULL)) return i end -- Methods that are specific to sprites function spritetype_mt.__index.setpos(spr, pos) -- setsprite() clone spr.x, spr.y, spr.z = pos.x, pos.y, pos.z local newsect = updatesector(spr, spr.sectnum) if (newsect < 0) then return -1 end if (spr.sectnum ~= newsect) then ffiC.changespritesect(get_sprite_idx(spr), newsect) end return newsect end -- Methods that are specific to tsprites 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) return 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.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 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 function static_members.sprite.changestat(spritenum, statnum, noerr) check_sprite_idx(spritenum) if (statnum >= ffiC.MAXSTATUS+0ULL) 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 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) 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 (stat >= ffiC.MAXSTATUS+0ULL) 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 (bunchnum >= ffiC.numyaxbunches+0ULL) 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? -- Additionally, permit different coordinates? (ang&horiz, ...) 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 -- TODO: make tagsearch something more convenient 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 -- TODO: should these rather be one function, and the specific kind of updating -- controlled by an argument? local us_retsect = ffi.new("int16_t [1]") function updatesector(pos, sectnum) us_retsect[0] = sectnum ffiC.updatesector(pos.x, pos.y, us_retsect) return us_retsect[0] end function updatesectorbreadth(pos, sectnum) us_retsect[0] = sectnum ffiC.updatesectorbreadth(pos.x, pos.y, us_retsect) return us_retsect[0] end function updatesectorz(pos, sectnum) 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