-- INTERNAL -- definitions of BUILD and game types for the Lunatic Interpreter _EDUKE32_LUNATIC = true local ffi = require("ffi") ---=== Duke3D engine and game definitions ===--- ffi.cdef[[ #pragma pack(push,1) typedef struct { const int16_t wallptr, wallnum; int32_t ceilingz, floorz; int16_t ceilingstat, floorstat; int16_t ceilingpicnum, ceilingheinum; int8_t ceilingshade; uint8_t ceilingpal, ceilingxpanning, ceilingypanning; int16_t floorpicnum, floorheinum; int8_t floorshade; uint8_t floorpal, floorxpanning, floorypanning; uint8_t visibility, filler; int16_t lotag, hitag, extra; } sectortype; typedef struct { int32_t x, y; const int16_t point2, nextwall, nextsector; int16_t cstat; int16_t picnum, overpicnum; int8_t shade; uint8_t pal, xrepeat, yrepeat, xpanning, ypanning; int16_t lotag, hitag, extra; } walltype; typedef struct { int32_t x, y, z; int16_t cstat, 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, owner, xvel, yvel, zvel; int16_t lotag, hitag, extra; } spritetype; typedef struct { const uint32_t mdanimtims; const int16_t mdanimcur; int16_t angoff, pitch, roll; int32_t xoff, yoff, zoff; uint8_t flags; uint8_t xpanning, ypanning; const uint8_t filler; float alpha; const int32_t _do_not_use1; const int32_t _do_not_use2; } spriteext_t; typedef struct { int32_t x, y, z; } vec3_t; typedef struct { vec3_t pos; int16_t hitsprite, hitwall, hitsect; } hitdata_t; typedef struct { char r,g,b,f; } palette_t; #pragma pack(pop) ]] local vec3_ct = ffi.typeof("vec3_t") local hitdata_ct = ffi.typeof("hitdata_t") ---- engine data and functions ---- -- GLOBAL gv: provides access to C global *scalars* and safe functions -- XXX: still exposes C library functions etc. contained in ffi.C. -- Is this a problem? local gv_ = { -- all non-scalars need to be explicitly listed here and access to them is -- redirected to the dummy empty table... } local dummy_empty_table = {} -- This is for declarations of arrays or pointers which should not be -- accessible through the gv global. -- NOTE: don't declare multiple pointers on one line (like int32_t *a, *b;)! local function decl(str) for varname in string.gmatch(str, "([%a_][%w_]*)[[(;]") do -- print("protect "..varname) gv_[varname] = dummy_empty_table end ffi.cdef(str) end ffi.cdef[[int32_t engine_main_arrays_are_static, engine_v8;]] -- NOTE TO SELF: This is not C, never EVER write -- if (x) -- when checking a C variable x for 'thuthiness' if (ffi.C.engine_main_arrays_are_static ~= 0) then -- print('main arrays are static'); decl[[ sectortype sector[]; walltype wall[]; spritetype sprite[]; spriteext_t spriteext[]; ]] else -- print('main arrays are pointers'); decl[[ sectortype *sector; walltype *wall; spritetype *sprite; spriteext_t *spriteext; ]] end if (ffi.C.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, MAXBUNCHES = 256, CEILING = 0, FLOOR = 1, CLIPMASK0 = (1<<16)+1, // blocking CLIPMASK1 = (256<<16)+64, // hittable }; ]] ffi.cdef[[ const int16_t numsectors, numwalls; const int32_t numyaxbunches; ]] decl[[ 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 headsectbunch[2][MAXBUNCHES], nextsectbunch[2][MAXSECTORS]; int16_t yax_getbunch(int16_t i, int16_t cf); 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); ]] -- array -> element flattening inside structs, -- e.g. int32_t a[4] --> int32_t _a0, _a1, _a2, _a3; local function repeat_n_elts(type, namebase, num) local strbuf = { "const "..type.." " } for i=1,num do local str = namebase..tostring(i-1) if (i < num) then str = str.."," end strbuf[#strbuf+1] = str end strbuf[#strbuf+1] = ";" return table.concat(strbuf) end ---- game structs ---- ffi.cdef[[ enum dukeinv_t { GET_STEROIDS, GET_SHIELD, GET_SCUBA, GET_HOLODUKE, GET_JETPACK, GET_DUMMY1, GET_ACCESS, GET_HEATS, GET_DUMMY2, GET_FIRSTAID, GET_BOOTS, GET_MAX }; enum dukeweapon_t { KNEE_WEAPON, PISTOL_WEAPON, SHOTGUN_WEAPON, CHAINGUN_WEAPON, RPG_WEAPON, HANDBOMB_WEAPON, SHRINKER_WEAPON, DEVISTATOR_WEAPON, TRIPBOMB_WEAPON, FREEZE_WEAPON, HANDREMOTE_WEAPON, GROW_WEAPON, MAX_WEAPONS }; enum { MAXPLAYERS = 16, }; ]] ffi.cdef([[ #pragma pack(push,1) // TODO: still need to make some fields read-only // NOTE: must not expose arrays in structs!!! typedef struct { ]] ..repeat_n_elts("int32_t", "_t", 14).. [[ // const int32_t t_data[14]; // 56b sometimes used to hold offsets to con code int16_t picnum,ang,extra,owner; //8b int16_t movflag,tempang,timetosleep; //6b int32_t flags, bposx,bposy,bposz; //16b int32_t floorz,ceilingz,lastvx,lastvy; //16b int32_t lasttransport; //4b const int16_t lightId, lightcount, lightmaxrange, cgg; //8b int16_t actorstayput, dispicnum, shootzvel; // 6b ]] ..repeat_n_elts("int8_t", "_d", 8).. [[ // const int8_t _do_not_use[8]; } actor_t; typedef struct { vec3_t pos, opos, vel, npos; int32_t bobposx, bobposy; int32_t truefz, truecz, player_par; int32_t randomflamex, exitx, exity; int32_t runspeed, max_player_health, max_shield_amount; uint32_t interface_toggle_flag; uint8_t palette; uint16_t max_actors_killed, actors_killed; uint16_t gotweapon, zoom; ]] ..repeat_n_elts("int16_t", "_lx", 64)..repeat_n_elts("int16_t", "_ly", 64).. [[ // int16_t loogiex[64], loogiey[64]; int16_t sbs, sound_pitch; int16_t ang, oang, angvel, cursectnum, look_ang, last_extra, subweapon; ]] ..repeat_n_elts("int16_t", "_ma", ffi.C.MAX_WEAPONS) ..repeat_n_elts("int16_t", "_aa", ffi.C.MAX_WEAPONS) ..repeat_n_elts("int16_t", "_ia", ffi.C.GET_MAX).. [[ // int16_t max_ammo_amount[MAX_WEAPONS], ammo_amount[MAX_WEAPONS], inv_amount[GET_MAX]; int16_t wackedbyactor, pyoff, opyoff; int16_t horiz, horizoff, ohoriz, ohorizoff; int16_t newowner, jumping_counter, airleft; int16_t fta, ftq, access_wallnum, access_spritenum; int16_t got_access, weapon_ang, visibility; int16_t somethingonplayer, on_crane, i, one_parallax_sectnum; int16_t random_club_frame, one_eighty_count; int16_t dummyplayersprite, extra_extra8; int16_t actorsqu, timebeforeexit, customexitsound, last_pissed_time; ]] ..repeat_n_elts("int16_t", "_w", ffi.C.MAX_WEAPONS).. [[ // int16_t weaprecs[MAX_WEAPONS]; int16_t weapon_sway, crack_time, bobcounter; int16_t orotscrnang, rotscrnang, dead_flag; // JBF 20031220: added orotscrnang int16_t holoduke_on, pycount; int16_t transporter_hold; uint8_t max_secret_rooms, secret_rooms; uint8_t frag, fraggedself, quick_kick, last_quick_kick; uint8_t return_to_center, reloading, weapreccnt; uint8_t aim_mode, auto_aim, weaponswitch, movement_lock, team; uint8_t tipincs, hbomb_hold_delay, frag_ps, kickback_pic; uint8_t gm, on_warping_sector, footprintcount, hurt_delay; uint8_t hbomb_on, jumping_toggle, rapid_fire_hold, on_ground; uint8_t inven_icon, buttonpalette, over_shoulder_on, show_empty_weapon; uint8_t jetpack_on, spritebridge, lastrandomspot; uint8_t scuba_on, footprintpal, heat_on, invdisptime; uint8_t holster_weapon, falling_counter, footprintshade; uint8_t refresh_inventory, last_full_weapon; uint8_t toggle_key_flag, knuckle_incs, knee_incs, access_incs; uint8_t walking_snd_toggle, palookup, hard_landing, fist_incs; int8_t numloogs, loogcnt, scream_voice; int8_t last_weapon, cheat_phase, weapon_pos, wantweaponfire, curr_weapon; palette_t pals; ]] ..repeat_n_elts("char", "_n", 32).. [[ // char name[32]; int8_t padding_; } DukePlayer_t; typedef struct { uint32_t bits; // 4b int16_t fvel, svel; // 4b int8_t avel, horz; // 2b int8_t extbits, filler; // 2b } input_t; typedef struct { DukePlayer_t *ps; input_t *sync; int32_t netsynctime; int16_t ping, filler; int32_t pcolor, pteam; uint8_t frags[MAXPLAYERS], wchoice[MAX_WEAPONS]; char vote, gotvote, pingcnt, playerquitflag; char user_name[32]; uint32_t revision; } playerdata_t; #pragma pack(pop) enum { MAXMOUSEBUTTONS = 10, MAXMOUSEAXES = 2, MAXJOYBUTTONS = (32+4), MAXJOYAXES = 8, NUMGAMEFUNCTIONS = 56, }; typedef struct { vec3_t camera; int32_t const_visibility,uw_framerate; int32_t camera_time,folfvel,folavel,folx,foly,fola; int32_t reccnt,crosshairscale; int32_t runkey_mode,statusbarscale,mouseaiming,weaponswitch,drawweapon; // JBF 20031125 int32_t democams,color,msgdisptime,statusbarmode; int32_t m_noexits,noexits,autovote,automsg,idplayers; int32_t team, viewbob, weaponsway, althud, weaponscale, textscale; int32_t entered_name,screen_tilting,shadows,fta_on,executions,auto_run; int32_t coords,tickrate,levelstats,m_coop,coop,screen_size,lockout,crosshair; int32_t playerai,angleinterpolation,obituaries; int32_t respawn_monsters,respawn_items,respawn_inventory,recstat,monsters_off,brightness; int32_t m_respawn_items,m_respawn_monsters,m_respawn_inventory,m_recstat,m_monsters_off,detail; int32_t m_ffire,ffire,m_player_skill,m_level_number,m_volume_number,multimode; int32_t player_skill,level_number,volume_number,m_marker,marker,mouseflip; int32_t configversion; int16_t cameraang, camerasect, camerahoriz; int16_t pause_on,from_bonus; int16_t camerasprite,last_camsprite; int16_t last_level,secretlevel, bgstretch; struct { int32_t UseJoystick; int32_t UseMouse; int32_t AutoAim; int32_t ShowOpponentWeapons; int32_t MouseDeadZone,MouseBias; int32_t SmoothInput; // JBF 20031211: Store the input settings because // (currently) jmact can't regurgitate them int32_t MouseFunctions[MAXMOUSEBUTTONS][2]; int32_t MouseDigitalFunctions[MAXMOUSEAXES][2]; int32_t MouseAnalogueAxes[MAXMOUSEAXES]; int32_t MouseAnalogueScale[MAXMOUSEAXES]; int32_t JoystickFunctions[MAXJOYBUTTONS][2]; int32_t JoystickDigitalFunctions[MAXJOYAXES][2]; int32_t JoystickAnalogueAxes[MAXJOYAXES]; int32_t JoystickAnalogueScale[MAXJOYAXES]; int32_t JoystickAnalogueDead[MAXJOYAXES]; int32_t JoystickAnalogueSaturate[MAXJOYAXES]; uint8_t KeyboardKeys[NUMGAMEFUNCTIONS][2]; // // Sound variables // int32_t FXDevice; int32_t MusicDevice; int32_t FXVolume; int32_t MusicVolume; int32_t SoundToggle; int32_t MusicToggle; int32_t VoiceToggle; int32_t AmbienceToggle; int32_t NumVoices; int32_t NumChannels; int32_t NumBits; int32_t MixRate; int32_t ReverseStereo; // // Screen variables // int32_t ScreenMode; int32_t ScreenWidth; int32_t ScreenHeight; int32_t ScreenBPP; int32_t ForceSetup; int32_t NoAutoLoad; int32_t scripthandle; int32_t setupread; int32_t CheckForUpdates; int32_t LastUpdateCheck; int32_t useprecache; } config; char overhead_on,last_overhead,showweapons; char god,warp_on,cashman,eog,showallmap; char show_help,scrollmode,noclip; char ridecule[10][40]; char savegame[10][22]; char pwlockout[128],rtsname[128]; char display_bonus_screen; char show_level_text; } user_defs; ]]) decl[[ const char *g_sizes_of_what[]; int32_t g_sizes_of[]; actor_t actor[MAXSPRITES]; user_defs ud; playerdata_t g_player[MAXPLAYERS]; ]] -- functions decl[[ int32_t kopen4loadfrommod(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); ]] -- JKISS PRNG state struct and functions, see randgen module ffi.cdef[[ typedef struct { uint32_t x, y, z, c; } rng_jkiss_t; ]] decl[[ uint32_t rand_jkiss_u32(rng_jkiss_t *s); double rand_jkiss_dbl(rng_jkiss_t *s); void md4once(const unsigned char *block, unsigned int len, unsigned char digest[16]); ]] ffi.cdef[[ int32_t ksqrt(uint32_t num); ]] ffi.cdef "double gethitickms(void);" local ffiC = ffi.C -- sanity-check struct type sizes for i=0,6 do assert(ffi.sizeof(ffi.typeof(ffi.string(ffiC.g_sizes_of_what[i]))) == ffiC.g_sizes_of[i]) end --- default defines local con = require("con_lang") for i=1,#con.labels do local strbuf = {"enum {"} for label, val in pairs(con.labels[i]) do strbuf[#strbuf+1] = string.format("%s = %d,", label, val) end strbuf[#strbuf+1] = "};" ffi.cdef(table.concat(strbuf)) end ---=== Set up restricted global environment ===--- -- These are for this file... local string = string local table = table -- http://lua-users.org/wiki/SandBoxes says "potentially unsafe" -- as it allows to see implementations of functions. local string_dump = string.dump string.dump = nil gv_tmp = gv_ -- required by randgen local allowed_modules = { coroutine=coroutine, bit=bit, table=table, math=math, string=string, os = { clock = function() return gv_.gethitickms()/1000 end, }, randgen = require("randgen"), geom = require("geom"), stat = require("stat"), } local package_loaded = {} for modname, themodule in pairs(allowed_modules) do local mt = { __index = themodule, __newindex = function(tab,idx,val) error("modifying base module table forbidden") end, __metatable = true, } -- Comment out to make base modules not protected: allowed_modules[modname] = setmetatable({}, mt) end local function check_valid_modname(modname) if (type(modname) ~= "string") then error("module name must be a string", 5) end -- TODO: restrict valid names? end local function errorf(level, fmt, ...) local errmsg = string.format(fmt, ...) error(errmsg, level+1) end local ERRLEV = 5 local function readintostr(fn) -- XXX: this is pretty much the same as the code in El_RunOnce() local fd = ffiC.kopen4loadfrommod(fn, 0) -- TODO: g_loadFromGroupOnly if (fd < 0) then errorf(ERRLEV, "Couldn't open file \"%s\"", fn) end local sz = ffiC.kfilelength(fd) if (sz == 0) then ffiC.kclose(fd) errorf(ERRLEV, "Didn't load module \"%s\": zero-length file", fn) end if (sz < 0) then ffi.kclose(fd) error("INTERNAL ERROR: kfilelength() returned negative length", 5) end local str = ffi.new("char [?]", sz) -- XXX: what does it do on out of mem? local readlen = ffiC.kread(fd, str, sz) ffiC.kclose(fd); fd=-1 if (readlen ~= sz) then errorf(ERRLEV, "INTERNAL ERROR: couldn't read \"%s\" wholly", fn) end return ffi.string(str, sz) end -- The "require" function accessible to Lunatic code. -- Base modules in allowed_modules are wrapped so that they cannot be -- modified, user modules are searched in the EDuke32 search -- path. Also, our require never messes with the global environment, -- it only returns the module. local function our_require(modname) check_valid_modname(modname) -- see whether it's a base module name first if (allowed_modules[modname] ~= nil) then return allowed_modules[modname] end --- search user modules if (package_loaded[modname] ~= nil) then -- already loaded return package_loaded[modname] end -- TODO: better pattern-matching (permit "", ".lua", ".elua" ?) local str = readintostr(modname .. ".lua") local modfunc, errmsg = loadstring(str) if (modfunc == nil) then errorf(ERRLEV-1, "Couldn't load \"%s\": %s", modname, errmsg) end local modtab = modfunc(modname) if (modtab ~= nil) then if (type(modtab) ~= "table") then errorf(ERRLEV-1, "Didn't load module \"%s\": expected table as return value", modname) end package_loaded[modname] = modtab else modtab = package_loaded[modname] if (type(modtab) ~= "table") then errorf(ERRLEV-1, "Didn't load module \"%s\": expected module() to be called", modname) end end -- Protect module table... local mt = { __index = modtab, __newindex = function(tab,idx,val) error("modifying module table forbidden", 2) end, } -- ..here: setmetatable(modtab, mt) return modtab end -- _G tweaks -- pull in only 'safe' stuff local G_ = {} -- our soon-to-be global environment -- Old global environment, to access thrown-out functions later in this chunk. -- Also, inside this file, we must refer to functions like 'pairs' by using -- this table, since user code could later do pairs=nil. local oG = _G local module_mt = { __index = function (_, n) error("attempt to read undeclared variable '"..n.."'", 2) end, } -- replacement for "module" local function our_module(modname) check_valid_modname(modname) local M = oG.setmetatable({}, module_mt) package_loaded[modname] = M -- change the environment of the function which called us: oG.setfenv(2, M) end -- overridden 'error' that always passes a string to the base 'error' local function our_error(errmsg, level) if (type(errmsg) ~= "string") then oG.error("error using 'error': error message must be a string", 2) end if (level) then if (type(level) ~= "number") then oG.error("error using 'error': error level must be a number", 2) end oG.error(errmsg, level==0 and 0 or level+1) end oG.error(errmsg, 2) end G_.require = our_require G_.module = our_module ---G_.coroutine = coroutine G_.assert = assert G_.tostring = tostring G_.tonumber = tonumber --rawget G_.xpcall = xpcall G_.ipairs = ipairs G_.print = print -- TODO: --> initprintf or OSD_Printf; why not needed on linux? G_.pcall = pcall --gcinfo --DEPRECATED --module --setfenv --require --rawset --jit ---G_.bit = bit --package G_.error = our_error --debug --loadfile --rawequal --load G_.unpack = unpack G_.pairs = pairs ---G_.table = table G_._VERSION = _VERSION --newproxy --NOT STD? --collectgarbage --dofile G_.next = next ---G_.math = math --loadstring --_G G_.select = select ---G_.string = string G_.type = type --getmetatable --getfenv --setmetatable G_._G = G_ --- non-default data and functions G_._EDUKE32_LUNATIC = _EDUKE32_LUNATIC G_.gameevent = gameevent -- included in lunatic.c G_.gameactor = gameactor -- included in lunatic.c ---=== Lunatic interpreter setup ===--- local lunacon = require("lunacon") -- change the environment of this chunk to the table G_ -- NOTE: all references to global variables from this point on -- (also in functions created after this point) refer to G_ ! setfenv(1, G_) -- We MUST remember to call base lib functions through oG here. -- 'tostring' is a special case: it is used in 'print', i.e. the global 'tostring', -- which is looked up in the current environment! Thus, we must REMOVE it and -- 'print' for release, or write our own, atomic 'print'. local error = oG.error local type = oG.type local pairs = oG.pairs -- print keys and values of a table local function printkv(label, table) oG.print('========== Keys and values of '..label) for k,v in oG.pairs(table) do oG.print(k .. ': ' .. tostring(v)) end oG.print('----------') end --printkv('_G AFTER SETFENV', _G) ---=== Restricted access to C variables from Lunatic ===--- -- error(..., 2) is to blame the caller and get its line numbers local tmpmt = { __index = function() error('dummy variable: read access forbidden', 2) end, __newindex = function() error('dummy variable: write access forbidden', 2) end, __metatable = true, -- forbid setting the metatable } oG.setmetatable(dummy_empty_table, tmpmt) gv = gv_ local tmpmt = { __index = ffiC, __newindex = function() error("cannot create new or write into existing fields of 'gv'", 2) end, __metatable = true, } oG.setmetatable(gv, tmpmt) ---- indirect C array access ---- sector = {} local tmpmt = { __index = function(tab, key) if (key >= 0 and key < ffiC.numsectors) then return ffiC.sector[key] end error('out-of-bounds sector[] read access', 2) end, __newindex = function(tab, key, val) error('cannot write directly to sector[] struct', 2) end, __metatable = true, } oG.setmetatable(sector, tmpmt) wall = {} local tmpmt = { __index = function(tab, key) if (key >= 0 and key < ffiC.numwalls) then return ffiC.wall[key] end error('out-of-bounds wall[] read access', 2) end, __newindex = function(tab, key, val) error('cannot write directly to wall[] struct', 2) end, __metatable = true, } oG.setmetatable(wall, tmpmt) -- create a safe indirection for an ffi.C array local 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(tab, key, val) error('cannot write directly to '..name, 2) end, __metatable = true, } oG.setmetatable(tab, tmpmt) return tab end sprite = creategtab(ffiC.sprite, ffiC.MAXSPRITES, 'sprite[] struct') spriteext = creategtab(ffiC.spriteext, ffiC.MAXSPRITES, 'spriteext[] struct') headspritesect = creategtab(ffiC.headspritesect, ffiC.MAXSECTORS, 'headspritesect[]') -- TODO: allow sprite freelist access via the status list for CON compatibility? headspritestat = creategtab(ffiC.headspritestat, ffiC.MAXSTATUS, 'headspritestat[]') nextspritesect = creategtab(ffiC.nextspritesect, ffiC.MAXSPRITES, 'nextspritesect[]') nextspritestat = creategtab(ffiC.nextspritestat, ffiC.MAXSPRITES, 'nextspritestat[]') prevspritesect = creategtab(ffiC.prevspritesect, ffiC.MAXSPRITES, 'prevspritesect[]') prevspritestat = creategtab(ffiC.prevspritestat, ffiC.MAXSPRITES, 'prevspritestat[]') actor = creategtab(ffiC.actor, ffiC.MAXSPRITES, 'actor[]') function TEMP_getvollev() -- REMOVE return ffiC.ud.volume_number+1, ffiC.ud.level_number+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 function spritesofsect(sect) if (sect < 0 or sect >= ffiC.numsectors) then error("passed invalid sectnum to spritesofsect iterator", 2) end return iter_spritesofsect, sect, -1 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 function spritesofstat(stat) if (stat < 0 or stat >= ffiC.MAXSTATUS) then error("passed invalid statnum to spritesofstat iterator", 2) end return iter_spritesofstat, stat, -1 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 < 0 or bunchnum >= ffiC.numyaxbunches) then error("passed invalid bunchnum to sectorsofbunch iterator", 2) end if (cf ~= 0 and cf ~= 1) then error("passed invalid 'cf' to sectorsofbunch iterator, must be 0 or 1", 2) end return iter_sectorsofbunch, cf, -bunchnum-1 end function getbunch(sectnum, cf) if (sectnum < 0 or sectnum >= ffiC.numsectors) then error('passed out-of-bounds sector number'..sectnum, 2) end if (cf ~= 0 and cf ~= 1) then error("passed invalid 'cf' to getbunch, must be 0 or 1", 2) end return ffiC.yax_getbunch(sectnum, cf) end ---=== Engine functions, wrapped for Lua convenience ===--- -- returns a hitdata_ct function hitscan(x,y,z, sectnum, vx,vy,vz, cliptype) if (sectnum < 0 or sectnum >= ffiC.numsectors) then error('passed out-of-bounds sector number'..sectnum, 2) end local vec = vec3_ct(x,y,z) local hitdata = hitdata_ct() ffiC.hitscan(vec, sectnum, vx,vy,vz, hitdata, cliptype) return hitdata end ---=== Game variables ===--- -- gamevarNames[name] is true if that gamevar was declared local gamevarNames = {} -- code for prohibiting initial assignments to create new variables, -- based on 14.2 of PiL function gamevar(name, initval) -- aka 'declare' if (type(name) ~= "string") then error("First argument to 'gamevar' must be a string", 2) end if (not string.match(name, "^[%a_][%w_]*$")) then error("First argument to 'gamevar' must be a valid identifier", 2) end if (string.match(name, "^_")) then error("Identifier names starting with an underscore are reserved", 2) end if (gamevarNames[name]) then error(string.format("Duplicate declaration of identifier '%s'", name), 2) end if (oG.rawget(G_, name) ~= nil) then error(string.format("Identifier name '%s' is already used in the global environment", name), 2) end gamevarNames[name] = true oG.rawset(G_, name, initval or false) end ---=== Serialization, from PiL with modifications ===--- local function basicSerialize(o) if (type(o) == "number") then if (o ~= o) then return "0/0" end -- nan if (o == 1/0) then return "1/0" end -- inf if (o == -1/0) then return "-1/0" end -- -inf return tostring(o) elseif (type(o) == "boolean") then return tostring(o) elseif (type(o) == "string") then return string.format("%q", o) end end -- will contain all gamevar tables as keys (and true as value) local tmpgvtabs = {} local function save(name, value, saved, strings) saved = saved or {} -- initial value strings[#strings+1] = name .. "=" local str = basicSerialize(value) if (str ~= nil) then strings[#strings+1] = str .. "\n" elseif (type(value) == "table") then if (saved[value]) then -- value already saved? strings[#strings+1] = saved[value] .. "\n" -- use its previous name else saved[value] = name -- save name for next time strings[#strings+1] = "{}\n" -- create a new table for k,v in pairs(value) do -- save its fields local keystr = basicSerialize(k) if (keystr == nil) then error("cannot save a " .. type(k) .. " as key of a table", 2); end local fieldname = string.format("%s[%s]", name, keystr) if (type(v)=="table" and not tmpgvtabs[v]) then error("cannot save \""..name.. "\": gamevar tables may only contain tables that are also gamevars", 2) end save(fieldname, v, saved, strings) end end else error("cannot save \""..name.."\", a " .. type(value), 2) end end local function serializeGamevars() local saved = {} local strings = {} for gvname,_ in pairs(gamevarNames) do if (type(G_[gvname])=="table") then tmpgvtabs[G_[gvname]] = true end end -- TODO: catch errors in save() for gvname,_ in pairs(gamevarNames) do save(gvname, G_[gvname], saved, strings) end strings[#strings+1] = "\n" tmpgvtabs = {} return table.concat(strings) end local function loadGamevarsString(string) --[=[ for gvname,_ in pairs(gamevarNames) do G_[gvname] = nil; end gamevarNames = {}; -- clear gamevars --]=] oG.assert(oG.loadstring(string))() end -- REMOVE this for release DBG_ = {} DBG_.printkv = printkv DBG_.loadstring = oG.loadstring DBG_.serializeGamevars = serializeGamevars DBG_.loadGamevarsString = loadGamevarsString ---=== Finishing environment setup ===--- --printkv('_G AFTER DECLS', _G) -- PiL 14.2 continued -- We need this at the end because we were previously doing just that! -- XXX: but user modules will want to do "function thisfunc() ... " oG.setmetatable( G_, { __newindex = function (_1, n, _2) error("attempt to write to undeclared variable '"..n.."'", 2) end, __index = function (_, n) error("attempt to read undeclared variable '"..n.."'", 2) end, }) -- Change the environment of the running Lua thread so that everything -- what we've set up will be available when this chunk is left. -- In particular, we need the functions defined after setting this chunk's -- environment earlier. oG.setfenv(0, _G)