-- 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; #pragma pack(pop) ]] assert(ffi.sizeof('sectortype')==40) assert(ffi.sizeof('walltype')==32) assert(ffi.sizeof('spritetype')==44) ---- engine data and functions ---- 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'); ffi.cdef[[ sectortype sector[]; walltype wall[]; spritetype sprite[]; spriteext_t spriteext[]; ]] else -- print('main arrays are pointers'); ffi.cdef[[ 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, }; ]] ffi.cdef[[ const int16_t numsectors, numwalls; const int32_t numyaxbunches; 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); ]] ---- game structs ---- ffi.cdef[[ #pragma pack(push,1) // XXX: might still need to make some fields read-only typedef struct { 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 const int8_t _do_not_use[8]; } actor_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; // KEEPINSYNC2 enum GameEvent_t { EVENT_INIT, EVENT_ENTERLEVEL, EVENT_RESETWEAPONS, EVENT_RESETINVENTORY, EVENT_HOLSTER, EVENT_LOOKLEFT, EVENT_LOOKRIGHT, EVENT_SOARUP, EVENT_SOARDOWN, EVENT_CROUCH, EVENT_JUMP, EVENT_RETURNTOCENTER, EVENT_LOOKUP, EVENT_LOOKDOWN, EVENT_AIMUP, EVENT_FIRE, EVENT_CHANGEWEAPON, EVENT_GETSHOTRANGE, EVENT_GETAUTOAIMANGLE, EVENT_GETLOADTILE, EVENT_CHEATGETSTEROIDS, EVENT_CHEATGETHEAT, EVENT_CHEATGETBOOT, EVENT_CHEATGETSHIELD, EVENT_CHEATGETSCUBA, EVENT_CHEATGETHOLODUKE, EVENT_CHEATGETJETPACK, EVENT_CHEATGETFIRSTAID, EVENT_QUICKKICK, EVENT_INVENTORY, EVENT_USENIGHTVISION, EVENT_USESTEROIDS, EVENT_INVENTORYLEFT, EVENT_INVENTORYRIGHT, EVENT_HOLODUKEON, EVENT_HOLODUKEOFF, EVENT_USEMEDKIT, EVENT_USEJETPACK, EVENT_TURNAROUND, EVENT_DISPLAYWEAPON, EVENT_FIREWEAPON, EVENT_SELECTWEAPON, EVENT_MOVEFORWARD, EVENT_MOVEBACKWARD, EVENT_TURNLEFT, EVENT_TURNRIGHT, EVENT_STRAFELEFT, EVENT_STRAFERIGHT, EVENT_WEAPKEY1, EVENT_WEAPKEY2, EVENT_WEAPKEY3, EVENT_WEAPKEY4, EVENT_WEAPKEY5, EVENT_WEAPKEY6, EVENT_WEAPKEY7, EVENT_WEAPKEY8, EVENT_WEAPKEY9, EVENT_WEAPKEY10, EVENT_DRAWWEAPON, EVENT_DISPLAYCROSSHAIR, EVENT_DISPLAYREST, EVENT_DISPLAYSBAR, EVENT_RESETPLAYER, EVENT_INCURDAMAGE, EVENT_AIMDOWN, EVENT_GAME, EVENT_PREVIOUSWEAPON, EVENT_NEXTWEAPON, EVENT_SWIMUP, EVENT_SWIMDOWN, EVENT_GETMENUTILE, EVENT_SPAWN, EVENT_LOGO, EVENT_EGS, EVENT_DOFIRE, EVENT_PRESSEDFIRE, EVENT_USE, EVENT_PROCESSINPUT, EVENT_FAKEDOMOVETHINGS, EVENT_DISPLAYROOMS, EVENT_KILLIT, EVENT_LOADACTOR, EVENT_DISPLAYBONUSSCREEN, EVENT_DISPLAYMENU, EVENT_DISPLAYMENUREST, EVENT_DISPLAYLOADINGSCREEN, EVENT_ANIMATESPRITES, EVENT_NEWGAME, EVENT_SOUND, EVENT_CHECKTOUCHDAMAGE, EVENT_CHECKFLOORDAMAGE, EVENT_LOADGAME, EVENT_SAVEGAME, MAXEVENTS }; ]] ffi.cdef[[ actor_t actor[MAXSPRITES]; user_defs ud; ]] ---=== Set up restricted global environment ===--- -- _G tweaks -- pull in only 'safe' stuff local G_ = {} -- our soon-to-be global environment local oG = _G -- old genv, to access thrown-out functions later in this chunk G_.coroutine = coroutine G_.assert = assert G_.tostring = tostring G_.tonumber = tonumber --rawget G_.xpcall = xpcall G_.ipairs = ipairs G_.print = print G_.pcall = pcall --gcinfo --DEPRECATED --module --setfenv --require --rawset --jit G_.bit = bit --package G_.error = 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_ -- 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 --- non-default data and functions G_._EDUKE32_LUNATIC = _EDUKE32_LUNATIC G_.setevent = setevent -- included in lunatic.c ---=== Lunatic interpreter setup ===--- local lunacon = require("lunacon") -- change the environment of this chunk to the table G_ setfenv(1, G_) -- print keys and values of a table local function printkv(label, table) print('========== Keys and values of '..label) for k,v in pairs(table) do print(k .. ': ' .. tostring(v)) end print('----------') end --printkv('_G AFTER SETFENV', _G) ---=== Restricted access to C variables from Lunatic ===--- local ffiC = ffi.C -- error(..., 2) is to blame the caller and get its line numbers local det = {} -- dummy empty table 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(det, tmpmt) -- XXX: this is incorrect -- ffi.C also contains C library functions, etc. -- GLOBAL gv: provides access to C global *scalars* gv = { -- all non-scalars need to be explicitly listed -- and access to them is redirected to the dummy -- empty table... this is somewhat ugly sector = det, wall = det, sprite = det, spriteext = det, headspritesect = det, headspritestat = det, prevspritesect = det, prevspritestat = det, nextspritesect = det, nextspritestat = det, actor = det, ud = det, luaJIT_BC_con_lang = det, luaJIT_BC_lunacon = det, } 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 a 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('out-of-bounds sector[] read access', 2) end if (cf ~= 0 and cf ~= 1) then error("passed invalid 'cf' to getbunch, must be 0 or 1", 2) end return yax_getbunch(sectnum, cf) 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 (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) else -- must be a string if (type(o) ~= "string") then error("cannot save a " .. type(o) .. " as key of a table", 3); end 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 .. "=" if (type(value) == "number" or type(value) == "string") then strings[#strings+1] = basicSerialize(value) .. "\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 fieldname = string.format("%s[%s]", name, basicSerialize(k)) 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) 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 (_, n) 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)