raze/polymer/eduke32/source/lunatic/defs.ilua
helixhorned 7f2175fcec Lunatic: retire per-sprite tsprite access for per-tile animation callback reg.
Don't yet make this official API though, since there are unresolved issues
with newly created tsprites potentially being fed back to the animation loop.
Add xmath.angvec(), xmath.bangvec(), tspr:set_sectnum(), tspr:setpos().

git-svn-id: https://svn.eduke32.com/eduke32@3937 1a8010ca-5511-0410-912e-c29ae57300e0
2013-07-07 20:59:10 +00:00

2474 lines
76 KiB
Text

-- INTERNAL
-- definitions of BUILD and game types for the Lunatic Interpreter
local require = require
local ffi = require("ffi")
local ffiC = ffi.C
-- Lua C API functions.
local CF = CF
local bit = bit
local coroutine = coroutine
local string = string
local table = table
local math = math
local assert = assert
local error = error
local getfenv = getfenv
local getmetatable = getmetatable
local ipairs = ipairs
local loadstring = loadstring
local pairs = pairs
local rawget = rawget
local rawset = rawset
local select = select
local setmetatable = setmetatable
local setfenv = setfenv
local tonumber = tonumber
local tostring = tostring
local type = type
-- Create a new module for passing stuff to other modules.
local lprivate = {}
require("package").loaded.lprivate = lprivate
require("jit.opt").start("maxmcode=10240") -- in KiB
-- The "gv" global will provide access to C global *scalars* and safe functions.
-- NOTE: This exposes C library functions from e.g. the global C namespace, but
-- without their declarations, they will be sitting there like a stone.
local gv_ = {}
-- [key]=<boolean> forbids, [key]=<non-boolean (e.g. table, function)> overrides
local gv_access = {}
-- This is for declarations of arrays or pointers which should not be
-- accessible through the "gv" global. The "defs_common" module will
-- use this function.
--
-- Notes: do not declare multiple scalars on one line (this is bad:
-- "int32_t a, b"). Do not name array arguments (or add a space
-- between the identifier and the '[' instead).
function decl(str, ...)
-- NOTE that the regexp also catches non-array/non-function identifiers
-- like "user_defs ud;"
for varname in string.gmatch(str, "([%a_][%w_]*)[[(;]") do
if (ffiC._DEBUG_LUNATIC ~= 0) then
print("FORBID "..varname)
end
gv_access[varname] = true
end
ffi.cdef(str, ...)
end
-- Load the definitions common to the game's and editor's Lua interface.
local defs_c = require("defs_common")
local cansee = defs_c.cansee
local strip_const = defs_c.strip_const
local setmtonce = defs_c.setmtonce
-- Must be after loading "defs_common" which redefines "print" to use
-- OSD_Printf()
local print, printf = print, defs_c.printf
---=== EDuke32 game definitions ===---
local INV_NAMES = {
"STEROIDS",
"SHIELD",
"SCUBA",
"HOLODUKE",
"JETPACK",
"DUMMY1",
"ACCESS",
"HEATS",
"DUMMY2",
"FIRSTAID",
"BOOTS",
}
local WEAPON_NAMES = {
"KNEE",
"PISTOL",
"SHOTGUN",
"CHAINGUN",
"RPG",
"HANDBOMB",
"SHRINKER",
"DEVISTATOR",
"TRIPBOMB",
"FREEZE",
"HANDREMOTE",
"GROW",
}
---- game structs ----
ffi.cdef([[
enum dukeinv_t {
]].. "GET_"..table.concat(INV_NAMES, ",GET_") ..[[,
GET_MAX
};
enum dukeweapon_t {
]].. table.concat(WEAPON_NAMES, "_WEAPON,").."_WEAPON," ..[[
MAX_WEAPONS
};
enum {
MAXPLAYERS = 16,
GTICSPERSEC = 30, // The real number of movement updates per second
};
]])
ffi.cdef[[
struct action {
int16_t startframe, numframes;
int16_t viewtype, incval, delay;
};
struct move {
int16_t hvel, vvel;
};
#pragma pack(push,1)
typedef struct { int32_t id; struct move mv; } con_move_t;
typedef struct { int32_t id; struct action ac; } con_action_t;
#pragma pack(pop)
typedef struct {
int32_t id;
con_action_t act;
con_move_t mov;
int32_t movflags;
} con_ai_t;
]]
defs_c.bitint_new_struct_type("int16_t", "SBit16")
defs_c.bitint_new_struct_type("int32_t", "SBit32")
defs_c.bitint_new_struct_type("uint32_t", "UBit32")
-- Struct template for actor_t. It already has 'const' fields (TODO: might need
-- to make more 'const'), but still has array members exposed, so is unsuited
-- for external exposure.
local ACTOR_STRUCT = [[
__attribute__((packed)) struct {
const int32_t t_data[10];
const struct move mv;
const struct action ac;
const uint16_t actiontics;
]]..defs_c.bitint_member("SBit32", "flags")..[[
vec3_t bpos; //12b
int32_t floorz,ceilingz,lastvx,lastvy; //16b
int32_t lasttransport; //4b
const int16_t picnum;
int16_t ang, extra;
const int16_t owner;
// NOTE: not to be confused with .movflags:
]]..defs_c.bitint_member("SBit16", "_movflag")..[[
int16_t tempang, timetosleep;
int16_t stayputsect;
const int16_t dispicnum;
// Movement flags, sprite[i].hitag in C-CON.
// XXX: more research where it was used in EDuke32's C code? (also .lotag <-> actiontics)
// XXX: what if CON code knew of the above implementation detail?
]]..defs_c.bitint_member("UBit16", "movflags")..[[
int16_t cgg;
const int16_t lightId, lightcount, lightmaxrange;
// NOTE: on 32-bit, C's lightptr+filler <=> this dummy:
const union { intptr_t ptr; uint64_t dummy; } _light;
}
]]
local bcarray = require("bcarray")
local bcheck = require("bcheck")
local check_sector_idx, check_tile_idx = bcheck.sector_idx, bcheck.tile_idx
local check_sprite_idx = bcheck.sprite_idx
local check_weapon_idx, check_inventory_idx = bcheck.weapon_idx, bcheck.inventory_idx
local check_sound_idx = bcheck.sound_idx
local check_number = bcheck.number
bcarray.new("int16_t", 64, "loogie", "int16_x_64") -- TODO: randomize member names
bcarray.new("int16_t", ffiC.MAX_WEAPONS, "weapon", "int16_x_MAX_WEAPONS", WEAPON_NAMES)
bcarray.new("int16_t", ffiC.GET_MAX, "inventory", "int16_x_GET_MAX", INV_NAMES)
-- NOTE: writing e.g. "ps.jetpack_on" in Lua when "ps.jetpack_on~=0" was meant
-- is probably one of the most commonly committed errors, so we make it a bool
-- type instead of uint8_t. The only issue is that if CON coders used these
-- fields to store more than just one bit, we're in trouble.
-- This will need to be documented and frozen for release.
local DUKEPLAYER_STRUCT = [[
__attribute__((packed)) 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;
int32_t autostep, autostep_sbw;
uint32_t interface_toggle_flag;
int32_t pipebombControl, pipebombLifetime, pipebombLifetimeVar;
int32_t tripbombControl, tripbombLifetime, tripbombLifetimeVar;
int32_t zrange;
int16_t angrange, autoaimang;
uint16_t max_actors_killed, actors_killed;
uint16_t gotweapon, zoom;
int16_x_64 loogiex;
int16_x_64 loogiey;
int16_t sbs, sound_pitch;
int16_t ang, oang, angvel;
const<S> int16_t cursectnum;
int16_t look_ang, last_extra, subweapon;
int16_x_MAX_WEAPONS max_ammo_amount;
int16_x_MAX_WEAPONS ammo_amount;
int16_x_GET_MAX inv_amount;
const<I-> int16_t wackedbyactor;
int16_t pyoff, opyoff;
int16_t horiz, horizoff, ohoriz, ohorizoff;
const int16_t newowner;
int16_t jumping_counter, airleft;
int16_t fta;
const<Q> int16_t ftq;
const int16_t access_wallnum, access_spritenum;
int16_t got_access, weapon_ang, visibility;
int16_t somethingonplayer, on_crane;
const int16_t i;
const int16_t one_parallax_sectnum;
int16_t random_club_frame, one_eighty_count;
const<I-> int16_t dummyplayersprite;
int16_t extra_extra8;
int16_t actorsqu, timebeforeexit;
const<X-> int16_t customexitsound;
int16_t last_pissed_time;
int16_x_MAX_WEAPONS weaprecs;
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;
bool reloading;
const uint8_t weapreccnt;
uint8_t aim_mode, auto_aim, weaponswitch, movement_lock, team;
uint8_t tipincs, hbomb_hold_delay;
const<P> uint8_t frag_ps;
uint8_t kickback_pic;
uint8_t gm;
bool on_warping_sector;
uint8_t footprintcount, hurt_delay;
bool hbomb_on, jumping_toggle, rapid_fire_hold, on_ground;
// NOTE: there's array indexing with inven_icon, but always after a
// bound check:
uint8_t inven_icon, buttonpalette;
bool over_shoulder_on;
uint8_t show_empty_weapon;
bool jetpack_on, spritebridge;
uint8_t lastrandomspot; // unused
bool scuba_on;
uint8_t footprintpal;
bool heat_on;
uint8_t invdisptime;
bool holster_weapon;
uint8_t falling_counter, footprintshade;
uint8_t refresh_inventory;
const<W> uint8_t last_full_weapon;
bool toggle_key_flag;
uint8_t knuckle_incs, knee_incs, access_incs;
uint8_t walking_snd_toggle, palookup, hard_landing, fist_incs;
int8_t numloogs, loogcnt, scream_voice;
const<W-> int8_t last_weapon;
int8_t cheat_phase, weapon_pos;
const<W-> int8_t wantweaponfire;
const<W> int8_t curr_weapon;
uint8_t palette;
palette_t _pals;
int8_t _palsfadespeed, _palsfadenext, _palsfadeprio, _padding2;
// NOTE: In C, the struct type has no name. We only have it here to define
// a metatype later.
const weaponaccess_t weapon;
const int8_t _padding;
}
]]
local PROJECTILE_STRUCT = [[
struct {
int32_t workslike, cstat;
int32_t hitradius, range, flashcolor;
const int16_t spawns;
const int16_t sound, isound;
int16_t vel;
const int16_t decal, trail;
int16_t tnum, drop;
int16_t offset, bounces;
const int16_t bsound;
int16_t toffset;
int16_t extra, extra_rand;
int8_t sxrepeat, syrepeat, txrepeat, tyrepeat;
int8_t shade, xrepeat, yrepeat, pal;
int8_t movecnt;
uint8_t clipdist;
int8_t filler[2];
int32_t userdata;
}
]]
-- KEEPINSYNC weapondata_mt below.
local WEAPONDATA_STRUCT = "struct {"..table.concat(con_lang.wdata_members, ';').."; }"
local randgen = require("randgen")
local ma_rand = randgen.new(true) -- initialize to "random" (time-based) seed
local ma_count = nil
local function ma_replace_array(typestr, neltstr)
local nelts = tonumber(neltstr)
if (nelts==nil) then
nelts = ffiC[neltstr]
assert(type(nelts)=="number")
end
local strtab = { "const ", typestr.." " }
for i=1,nelts do
local ch1 = 97 + (ma_rand:getu32() % 25) -- 'a'..'z'
strtab[i+2] = string.format("_%c%x%s", ch1, ma_count, (i<nelts) and "," or ";")
ma_count = ma_count+1
end
return table.concat(strtab)
end
---=== Protection of scalars in (currently only) DukePlayer_t struct. ===---
-- This is more convenient than writing dozens of set-member methods.
local prot_scalar_chkfunc = {
S = check_sector_idx,
I = check_sprite_idx,
P = bcheck.player_idx,
W = check_weapon_idx,
X = check_sound_idx,
Q = bcheck.quote_idx,
}
local DukePlayer_prot_allowneg = {} -- [<member name>] = true if setting <0 allowed
local DukePlayer_prot_chkfunc = {} -- [<member name>] = <checking function>
local function ma_replace_scalar(what, typestr, membname)
DukePlayer_prot_chkfunc[membname] = assert(prot_scalar_chkfunc[what:sub(1,1)])
DukePlayer_prot_allowneg[membname] = (what:sub(2)=="-")
return ma_replace_array(typestr, 1)
end
-- Converts a template struct definition to an external one, in which arrays
-- have been substituted by randomly named scalar fields.
-- <also_scalars>: also handle protected scalars like "const<W-> ..." etc.
local function mangle_arrays(structstr, also_scalars)
ma_count = 0
-- NOTE: regexp only works for non-nested arrays and for one array per line.
structstr = structstr:gsub("const%s+([%w_]+)[^\n]+%[([%w_]+)%];", ma_replace_array)
if (also_scalars) then
-- One protected scalar per line, too.
structstr = structstr:gsub("const<(.-)>%s+([%w_]-)%s+([%w_]-);", ma_replace_scalar)
end
return structstr
end
--print(mangle_arrays(DUKEPLAYER_STRUCT, true))
--- default defines etc.
local con_lang = require("con_lang")
ffi.cdef([[
typedef struct { int32_t _p; } weaponaccess_t;
typedef struct {
]]..defs_c.bitint_member("UBit32", "bits")..[[
int16_t fvel, svel; // 4b
int8_t avel, horz; // 2b
int8_t extbits, filler; // 2b
} input_t;
typedef
]].. mangle_arrays(ACTOR_STRUCT) ..[[
actor_t;
typedef
]].. mangle_arrays(DUKEPLAYER_STRUCT, true) ..[[
DukePlayer_t;
typedef __attribute__((packed)) 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, ready;
char user_name[32];
uint32_t revision;
} playerdata_t;
typedef struct {
int32_t cur, count;
int32_t gunposx, lookhalfang;
int32_t gunposy, lookhoriz;
int32_t shade;
} hudweapon_t;
typedef
]].. WEAPONDATA_STRUCT ..[[
weapondata_t;
typedef
]].. PROJECTILE_STRUCT ..[[
projectile_t;
typedef struct {
uint32_t flags; // XXX: do we want to have this accessible at game time?
const int32_t _cacherange;
const projectile_t _defproj;
} tiledata_t;
typedef struct {
vec3_t pos;
int32_t dist, clock;
int16_t ang, horiz;
int16_t sect; // NOTE: protected in camera_mt's __newindex
} camera_t;
enum
{
MAXMOUSEBUTTONS = 10,
MAXMOUSEAXES = 2,
MAXJOYBUTTONS = (32+4),
MAXJOYAXES = 8,
NUMGAMEFUNCTIONS = 56,
};
typedef struct {
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 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;
const 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;
typedef struct {
int32_t partime, designertime;
char *name, *filename, *musicfn, *alt_musicfn;
void *savedstate;
} map_t;
]])
bcarray.new("weapondata_t", ffiC.MAX_WEAPONS, "weapon", "weapondata_x_MAX_WEAPONS", WEAPON_NAMES)
-- EXTERNALLY EXPOSED GAME VARIABLES
ffi.cdef[[
const int32_t screenpeek;
hudweapon_t hudweap;
int32_t g_logoFlags;
int32_t g_RETURN; // deprecated from Lua
]]
-- INTERNAL VARIABLES/FUNCTIONS
decl("map_t MapInfo[$*$];", con_lang.MAXVOLUMES+1, con_lang.MAXLEVELS)
decl("char EpisodeNames[$][33];", con_lang.MAXVOLUMES)
decl[[
int32_t g_elCONSize;
char *g_elCON;
void El_SetCON(const char *conluacode);
char *g_elSavecode;
void El_FreeSaveCode(void);
const char *(*El_SerializeGamevars)(int32_t *slenptr);
const char *s_buildRev;
const char *g_sizes_of_what[];
int32_t g_sizes_of[];
int32_t g_elCallDepth;
int32_t block_deletesprite;
const char **g_argv;
const char **g_elModules;
char g_modDir[];
actor_t actor[MAXSPRITES];
camera_t g_camera;
user_defs ud;
playerdata_t g_player[MAXPLAYERS];
DukePlayer_t *g_player_ps[MAXPLAYERS];
weapondata_x_MAX_WEAPONS g_playerWeapon[MAXPLAYERS];
weapondata_t g_weaponOverridden[MAX_WEAPONS];
tiledata_t g_tile[MAXTILES];
projectile_t ProjectileData[MAXTILES];
projectile_t SpriteProjectile[MAXSPRITES];
int32_t g_noResetVars;
void (*A_ResetVars)(int32_t iActor);
// Used from lunacon.lua for dynamic {tile,sound} remapping:
struct
{
const char *str;
int32_t *dynvalptr;
const int16_t staticval;
} g_dynTileList[], g_dynSoundList[];
char *ScriptQuotes[];
const int32_t playerswhenstarted;
int32_t lastvisinc;
int16_t g_spriteDeleteQueueSize;
int16_t BlimpSpawnSprites[15];
int32_t g_scriptVersion;
const int32_t g_currentFrameRate;
const int32_t g_currentMenu;
uint16_t g_earthquakeTime;
uint32_t g_moveThingsCount;
char CheatKeys[2];
// Must not have functions here that may call events directly or
// indirectly. See lunatic_game.c.
int32_t A_IncurDamage(int32_t sn); // not bound-checked!
int32_t G_CheckActivatorMotion(int32_t lotag);
int32_t A_Dodge(spritetype *s);
int32_t A_MoveSprite(int32_t spritenum, const vec3_t *change, uint32_t cliptype);
void P_DoQuote(int32_t q, DukePlayer_t *p);
void P_SetGamePalette(DukePlayer_t *player, uint8_t palid, int32_t set);
void G_AddUserQuote(const char *daquote);
void G_ClearCameraView(DukePlayer_t *ps);
void G_DrawTileGeneric(int32_t x, int32_t y, int32_t zoom, int32_t tilenum,
int32_t shade, int32_t orientation, int32_t p);
void G_InitTimer(int32_t ticspersec);
void G_GetTimeDate(int32_t *vals);
int32_t G_ToggleWallInterpolation(int32_t w, int32_t doset);
int32_t G_StartTrack(int32_t level);
int32_t VM_CheckSquished2(int32_t i, int32_t snum);
void M_ChangeMenu(int32_t cm);
const char *KB_ScanCodeToString(uint8_t scancode);
int32_t A_CheckAnySoundPlaying(int32_t i);
int32_t S_CheckSoundPlaying(int32_t i, int32_t num);
void S_StopEnvSound(int32_t num, int32_t i);
int32_t FX_StopAllSounds(void);
void S_ChangeSoundPitch(int32_t num, int32_t i, int32_t pitchoffset);
int32_t minitext_(int32_t x,int32_t y,const char *t,int32_t s,int32_t p,int32_t sb);
void G_DrawTXDigiNumZ(int32_t starttile, int32_t x,int32_t y,int32_t n,int32_t s,int32_t pal,
int32_t cs,int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t z);
int32_t G_PrintGameText(int32_t f, int32_t tile, int32_t x, int32_t y, const char *t,
int32_t s, int32_t p, int32_t o,
int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t z);
typedef struct {
int32_t x, y;
} vec2_t;
vec2_t G_ScreenText(const int32_t font,
int32_t x, int32_t y, const int32_t z, const int32_t blockangle, const int32_t charangle,
const char *str, const int32_t shade, int32_t pal, int32_t o, const int32_t alpha,
int32_t xspace, int32_t yline, int32_t xbetween, int32_t ybetween, const int32_t f,
const int32_t x1, const int32_t y1, const int32_t x2, const int32_t y2);
vec2_t G_ScreenTextSize(const int32_t font,
int32_t x, int32_t y, const int32_t z, const int32_t blockangle,
const char *str, const int32_t o,
int32_t xspace, int32_t yline, int32_t xbetween, int32_t ybetween,
const int32_t f,
int32_t x1, int32_t y1, int32_t x2, int32_t y2);
void G_ShowView(int32_t x, int32_t y, int32_t z, int32_t a, int32_t horiz, int32_t sect,
int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t unbiasedp);
void G_SaveMapState(void);
void G_RestoreMapState(void);
]]
decl[[
int32_t kopen4loadfrommod(const char *filename, char searchfirst);
char **g_scriptModules;
int32_t g_scriptModulesNum;
const char *G_ConFile(void);
void G_DoGameStartup(const int32_t *params);
int32_t C_DefineSound(int32_t sndidx, const char *fn, int32_t args [5]);
void C_DefineMusic(int32_t vol, int32_t lev, const char *fn);
void C_DefineQuote(int32_t qnum, const char *qstr);
void C_DefineVolumeName(int32_t vol, const char *name);
void C_DefineSkillName(int32_t skill, const char *name);
void C_DefineLevelName(int32_t vol, int32_t lev, const char *fn,
int32_t partime, int32_t designertime,
const char *levelname);
void C_DefineProjectile(int32_t j, int32_t what, int32_t val);
void C_DefineGameFuncName(int32_t idx, const char *name);
void C_DefineGameType(int32_t idx, int32_t flags, const char *name);
int32_t C_SetDefName(const char *name);
int32_t SCRIPT_GetNumber(int32_t scripthandle, const char *sectionname, const char *entryname, int32_t *number);
void SCRIPT_PutNumber(int32_t scripthandle, const char *sectionname, const char *entryname, int32_t number,
int32_t hexadecimal, int32_t defaultvalue);
]]
-- 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
-- sanity-check struct type sizes
local good = true
for i=0,10 do
local what = ffi.string(ffiC.g_sizes_of_what[i])
local fsz = ffi.sizeof(what)
local csz = ffiC.g_sizes_of[i]
if (ffiC._DEBUG_LUNATIC ~= 0) then
print(i..": "..what..": C sizeof = "..tostring(csz)..", FFI sizeof = "..tostring(fsz))
end
if (fsz ~= csz) then
good = false;
end
end
if (not good) then
error("Some sizes don't match between C and LuaJIT/FFI.")
end
--== "player" global, needed by the "control" module ==--
local player_static_members = {}
player_static_members._INPUT_BITS = defs_c.conststruct
{
JUMP = 1,
CROUCH = 2,
FIRE = 4,
AIM_UP = 8,
AIM_DOWN = 16,
RUNNING = 32,
LOOK_LEFT = 64,
LOOK_RIGHT = 128,
-- weapons...
STEROIDS = 4096,
LOOK_UP = 8192,
LOOK_DOWN = 16384,
NIGHTVISION = 32768,
MEDKIT = 65536,
RESERVED = 131072,
CENTER_VIEW = 262144,
HOLSTER_WEAPON = 524288,
INVENTORY_LEFT = 1048576,
PAUSE = 2097152,
QUICK_KICK = 4194304,
AIM_MODE = 8388608,
HOLODUKE = 16777216,
JETPACK = 33554432,
QUIT = 67108864,
INVENTORY_RIGHT = 134217728,
TURN_AROUND = 268435456,
OPEN = 536870912,
INVENTORY = 1073741824,
ESC = 2147483648,
}
player_static_members._INPUT_EXT_BITS = defs_c.conststruct
{
MOVE_FORWARD = 1,
MOVE_BACKWARD = 2,
STRAFE_LEFT = 4,
STRAFE_RIGHT = 8,
TURN_LEFT = 16,
TURN_RIGHT = 32,
}
local band = bit.band
local lsh = bit.lshift
do
-- player.all() iterator
local function iter_allplayers(_nil, pli)
if (pli+1 < ffiC.playerswhenstarted) then
return pli+1
end
end
function player_static_members.all()
return iter_allplayers, nil, -1
end
-- player.holdskey(pli, keyname)
local KEYS = { -- SK_CROUCH etc. -- "sync keys"
CROUCH = lsh(1,1),
RUN = lsh(1,5),
OPEN = lsh(1,29),
}
function player_static_members.holdskey(pli, keyname)
bcheck.player_idx(pli)
if (KEYS[keyname] == nil) then
error("invalid key name: "..tostring(keyname), 2)
end
return ffiC.g_player[pli].sync.bitsbits:test(KEYS[keyname])
end
end
local player_holdskey = player_static_members.holdskey
-- Actor flags
local actor_static_members = {}
do
local our_SFLAG = {}
local ext_SFLAG = con_lang.labels[4] -- external actor flags only
local USER_MASK = 0
for name, flag in pairs(ext_SFLAG) do
our_SFLAG[name:sub(7)] = flag -- strip "SFLAG_"
USER_MASK = bit.bor(USER_MASK, flag)
end
-- Add a couple of convenience defines.
our_SFLAG.enemy = con_lang.SFLAG.SFLAG_BADGUY
our_SFLAG.enemystayput = con_lang.SFLAG.SFLAG_BADGUY + con_lang.SFLAG.SFLAG_BADGUYSTAYPUT
our_SFLAG.rotfixed = con_lang.SFLAG.SFLAG_ROTFIXED
-- Callback function chaining control flags.
our_SFLAG.replace_hard = 0x08000000 -- Replace actor code and flags
our_SFLAG.replace_soft = 0x10000000 -- Replace actor code, bitwise OR flags
our_SFLAG.replace = 0x08000000 -- Should only be used for gameevent
our_SFLAG.chain_beg = 0x20000000
our_SFLAG.chain_end = 0x40000000
our_SFLAG._CHAIN_MASK_ACTOR = 0x78000000
our_SFLAG._CHAIN_MASK_EVENT = 0x68000000
-- XXX: CON doesn't export BADGUYSTAYPUT or ROTFIXED SFLAGs, but they are considered
-- external for Lunatic.
our_SFLAG.USER_MASK = bit.bor(USER_MASK, our_SFLAG.enemystayput, our_SFLAG.rotfixed)
actor_static_members.FLAGS = defs_c.conststruct(our_SFLAG)
-- Sprite status numbers. Kept in 'actor', because it's more of a game-side
-- concept (event though status lists are implemented in the engine), and
-- to prevent confusion with sprite.CSTAT.
local our_STAT = {}
for name, statnum in pairs(con_lang.STAT) do
-- Strip 'STAT_'.
our_STAT[name:sub(6)] = statnum
end
actor_static_members.STAT = defs_c.conststruct(our_STAT)
actor_static_members.MOVFLAGS = defs_c.conststruct
{
-- NOTE: no underscores, like in DEFS.CON.
faceplayer = 1,
geth = 2,
getv = 4,
randomangle = 8,
faceplayerslow = 16,
spin = 32,
faceplayersmart = 64,
fleeenemy = 128,
jumptoplayer = 257,
seekplayer = 512,
furthestdir = 1024,
dodgebullet = 4096,
}
end
function actor_static_members.fall(i)
check_sprite_idx(i)
CF.VM_FallSprite(i)
end
-- Delete sprite with index <i>.
function actor_static_members.delete(i)
check_sprite_idx(i)
if (ffiC.sprite[i].statnum == ffiC.MAXSTATUS) then
error("Attempt to delete a sprite already not in the game world", 2)
end
if (ffiC.block_deletesprite ~= 0) then
error("Attempt to delete sprite in EVENT_EGS", 2)
end
-- TODO_MP
if (ffiC.g_player_ps[0].i == i) then
error("Attempt to delete player 0's APLAYER sprite", 2)
end
CF.A_DeleteSprite(i)
end
local tile_static_members = {}
do
tile_static_members.sizx = defs_c.creategtab(ffiC.tilesizx, ffiC.MAXTILES, "tilesizx[]")
tile_static_members.sizy = defs_c.creategtab(ffiC.tilesizy, ffiC.MAXTILES, "tilesizy[]")
end
-- XXX: error message will say "g_player_ps"
player = setmtonce({}, defs_c.GenStructMetatable("g_player_ps", "playerswhenstarted", player_static_members))
-- needed by "control"
actor = setmtonce({}, defs_c.GenStructMetatable("actor", "MAXSPRITES", actor_static_members))
-- Some bitwise NOTs of various actor flag masks.
local BNOT = {
USER_MASK = bit.bnot(actor.FLAGS.USER_MASK),
CHAIN_MASK_ACTOR = bit.bnot(actor.FLAGS._CHAIN_MASK_ACTOR),
CHAIN_MASK_EVENT = bit.bnot(actor.FLAGS._CHAIN_MASK_EVENT),
}
local projectile = defs_c.creategtab(ffiC.ProjectileData, ffiC.MAXTILES, "projectile[]")
local g_tile = setmtonce({}, defs_c.GenStructMetatable("g_tile", "MAXTILES", tile_static_members))
--== Custom operations for BUILD data structures ==--
-- Among other things, declares struct action and struct move, and their
-- ID-wrapped types con_action_t and con_move_t
local con = require("control")
local isenemytile = con.isenemytile
-- Add game-side metamethods to "spritetype" and register it with "metatype"
local spr_mt_index_add = {
isenemy = function(s)
return isenemytile(s.picnum)
end,
}
defs_c.finish_spritetype(spr_mt_index_add)
-- Check a literal numeric action or move value.
local function check_literal_am(am, typename)
if (type(am) ~= "number") then
error("bad argument: expected number or "..typename, 3)
end
-- Negative values are generated as con.action/con.move IDs.
if (not (am >= 0 and am <= 32767)) then
error("bad argument: expected number in [0 .. 32767]", 3)
end
end
-- An unrestricted actor_t pointer, for internal use:
local actor_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(ACTOR_STRUCT)))
local player_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(DUKEPLAYER_STRUCT)))
local projectile_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(PROJECTILE_STRUCT)))
local weapondata_ptr_ct = ffi.typeof("$ *", ffi.typeof((strip_const(WEAPONDATA_STRUCT):gsub(" _"," "))))
local con_action_ct = ffi.typeof("const con_action_t")
local con_move_ct = ffi.typeof("const con_move_t")
local con_ai_ct = ffi.typeof("const con_ai_t")
-- All-zero bare action and move.
local nullac, nullmv = ffi.new("const struct action"), ffi.new("const struct move")
-- All-zero action and move with IDs. Mostly for CON support.
local literal_act = { [0]=con_action_ct(0), [1]=con_action_ct(1) }
local literal_mov = { [0]=con_move_ct(0), [1]=con_move_ct(1) }
local function get_actor_idx(a)
local i = ffi.cast(actor_ptr_ct, a)-ffi.cast(actor_ptr_ct, ffiC.actor)
assert(not (i >= ffiC.MAXSPRITES+0ULL))
return i
end
local actor_mt = {
__index = {
-- action
set_action = function(a, act)
a = ffi.cast(actor_ptr_ct, a)
if (ffi.istype(con_action_ct, act)) then
a.t_data[4] = act.id
a.ac = act.ac
else
check_literal_am(act, "action")
a.t_data[4] = act
a.ac = nullac
end
a.t_data[2] = 0
a.t_data[3] = 0
end,
has_action = function(a, act)
a = ffi.cast(actor_ptr_ct, a)
if (ffi.istype(con_action_ct, act)) then
return (a.t_data[4]==act.id)
else
check_literal_am(act, "action")
return (a.t_data[4]==act)
end
end,
-- count
set_count = function(a, count)
ffi.cast(actor_ptr_ct, a).t_data[0] = count
end,
get_count = function(a)
return ffi.cast(actor_ptr_ct, a).t_data[0]
end,
-- action count
reset_acount = function(a)
ffi.cast(actor_ptr_ct, a).t_data[2] = 0
end,
get_acount = function(a)
return ffi.cast(actor_ptr_ct, a).t_data[2]
end,
-- Override action delay. The action ID is kept.
set_action_delay = function(a, delay)
ffi.cast(actor_ptr_ct, a).ac.delay = delay
end,
-- move
set_move = function(a, mov, movflags)
a = ffi.cast(actor_ptr_ct, a)
if (ffi.istype(con_move_ct, mov)) then
a.t_data[1] = mov.id
a.mv = mov.mv
else
check_literal_am(mov, "move")
a.t_data[1] = mov
a.mv = nullmv
end
a.t_data[0] = 0
a.movflags = movflags or 0
local spr = ffiC.sprite[get_actor_idx(a)]
if (not spr:isenemy() or spr.extra > 0) then
if (bit.band(a.movflags, 8) ~= 0) then -- random_angle
spr.ang = bit.band(ffiC.krand(), 2047)
end
end
end,
has_move = function(a, mov)
a = ffi.cast(actor_ptr_ct, a)
if (ffi.istype(con_move_ct, mov)) then
return (a.t_data[1]==mov.id)
else
check_literal_am(mov, "move")
return (a.t_data[1]==mov)
end
end,
-- Override velocity, keeping move ID.
set_hvel = function(a, hvel)
ffi.cast(actor_ptr_ct, a).mv.hvel = hvel
end,
set_vvel = function(a, vvel)
ffi.cast(actor_ptr_ct, a).mv.vvel = vvel
end,
-- ai
set_ai = function(a, ai)
local oa = a
a = ffi.cast(actor_ptr_ct, a)
-- TODO: literal number AIs?
if (not ffi.istype(con_ai_ct, ai)) then
error("bad argument: expected ai", 2)
end
-- NOTE: compare with gameexec.c, "CON_AI:"
a.t_data[5] = ai.id
oa:set_action(ai.act)
oa:set_move(ai.mov, ai.movflags)
-- Already reset with set_move():
-- a.t_data[0] = 0
end,
has_ai = function(a, ai)
a = ffi.cast(actor_ptr_ct, a)
if (ffi.istype(con_ai_ct, ai)) then
return (a.t_data[5]==ai.id)
else
check_literal_am(ai, "ai")
return (a.t_data[5]==ai)
end
end,
-- Getters/setters.
_get_t_data = function(a, idx)
if (idx >= 10ULL) then
error("invalid t_data index "..idx, 2)
end
return ffi.cast(actor_ptr_ct, a).t_data[idx]
end,
_set_t_data = function(a, idx, val)
if (idx >= 10ULL) then
error("invalid t_data index "..idx, 2)
end
ffi.cast(actor_ptr_ct, a).t_data[idx] = val
end,
set_picnum = function(a, picnum)
if (not (picnum < 0)) then
check_tile_idx(picnum)
end
ffi.cast(actor_ptr_ct, a).picnum = picnum
end,
set_owner = function(a, owner)
-- XXX: is it permissible to set to -1?
check_sprite_idx(owner)
ffi.cast(actor_ptr_ct, a).owner = owner
end,
--- Custom methods ---
-- Checkers for whether the movement update made the actor hit
-- something.
checkhit = function(a)
-- Check whether we hit *anything*, including ceiling/floor.
return a._movflagbits:test(49152)
end,
checkbump = function(a)
-- Check whether we bumped into a wall or sprite.
return (a._movflagbits:mask(49152) >= 32768)
end,
hitwall = function(a)
if (a._movflagbits:mask(49152) == 32768) then
return a._movflagbits:mask(32767)
end
end,
hitsprite = function(a)
if (a._movflagbits:mask(49152) == 49152) then
return a._movflagbits:mask(32767)
end
end,
-- NOTE: no 'hitsector' or 'hitceiling' / 'hitfloor' for now because
-- more research is needed as to what the best way of checking c/f is.
},
}
local function actor_index_index(a, key)
-- actor[].proj gets an actor's projectile ([gs]etthisprojectile in CON)
if (key=="proj") then
return ffiC.SpriteProjectile[get_actor_idx(a)]
end
end
setmtonce(actor_mt.__index, { __index = actor_index_index })
ffi.metatype("actor_t", actor_mt)
--- PER-PLAYER WEAPON SETTINGS
local weapondata_mt = {
__index = function(wd, member)
-- Handle protected members that are renamed (e.g. shoots/_shoots).
return ffi.cast(weapondata_ptr_ct, wd)[0][member]
end,
__newindex = function(wd, member, val)
-- Set to 'true' if we set a tile or sound member.
local didit = false
check_number(val)
if (string.match(member, "sound")) then
if (val < 0) then
val = 0 -- Set to 0 if oob (e.g. CrackDown).
else
check_sound_idx(val)
end
didit = true
elseif (member=="workslike") then
check_weapon_idx(val)
elseif (member=="spawn" or member=="shoots") then
if (val < 0) then
-- Set to 0 if oob (e.g. CrackDown). This is a bit problematic
-- for .shoots because it's used unconditionally except in one
-- case (see player.c).
val = 0
else
check_tile_idx(val)
end
didit = true
end
ffi.cast(weapondata_ptr_ct, wd)[0][member] = val
if (didit and ffiC.g_elCallDepth==0) then
-- Signal that we overrode this member at CON translation time.
-- Get weapon index as pointer difference first. PLAYER_0.
local wi = ffi.cast(weapondata_ptr_ct, wd)
- ffi.cast(weapondata_ptr_ct, ffiC.g_playerWeapon)
assert(not (wi >= ffiC.MAX_WEAPONS+0ULL))
-- Set g_weaponOverridden[wi][member], but without invoking
-- weapondata_t's __newindex metamethod (i.e. us)!
ffi.cast(weapondata_ptr_ct, ffiC.g_weaponOverridden[wi])[member] = 1
end
end,
}
ffi.metatype("weapondata_t", weapondata_mt)
local weaponaccess_mt = {
-- Syntax like "player[0].weapon.KNEE.shoots" possible because
-- g_playerWeapon[] is declared as an array of corresponding bcarray types
-- for us.
__index = function(wa, key)
if (type(key)~="number" and type(key)~="string") then
error("must access weapon either by number or by name")
end
return ffiC.g_playerWeapon[wa._p][key]
end,
}
ffi.metatype("weaponaccess_t", weaponaccess_mt)
local function clamp(num, min, max)
return num < min and min
or num > max and max
or num
end
local function clamp0to1(num)
check_number(num, 4)
return clamp(num, 0, 1)
end
local player_mt = {
__index = {
-- CON-like addammo/addweapon, but without the non-local control flow
-- (returns true if weapon's ammo was at the max. instead).
addammo = con._addammo,
addweapon = con._addweapon,
stomp = con._pstomp,
holdskey = function(p, keyname)
-- XXX: on invalid <keyname>, error will point to this next line:
return player_holdskey(p.weapon._p, keyname)
end,
has_weapon = function(p, weap)
return (band(p.gotweapon, lsh(1, weap)) ~= 0)
end,
give_weapon = function(p, weap)
p.gotweapon = bit.bor(p.gotweapon, lsh(1, weap))
end,
take_weapon = function(p, weap)
p.gotweapon = band(p.gotweapon, bit.bnot(lsh(1, weap)))
end,
-- Give or take weapon, for implementation of CON's .gotweapon access.
_gt_weapon = function(p, weap, give_p)
if (give_p ~= 0) then
p:give_weapon(weap)
else
p:take_weapon(weap)
end
end,
whack = function(p, no_return_to_center)
p.horiz = p.horiz + 64
if (not no_return_to_center) then
p.return_to_center = 9
end
local n = bit.arshift(128-band(ffiC.krand(),255), 1)
p.rotscrnang = n
p.look_ang = n
end,
-- External, improved 'palfrom'.
-- <speed>: possibly fractional speed of tint fading, in pals.f decrements per gametic.
-- XXX: exposes internals.
-- <prio>: a value from -128 to 127, higher ones trump lower or equal ones
fadecol = function(p, fadefrac, r, g, b, speed, prio)
-- Validate inargs: clamp f,r,g,b to [0 .. 1] first and multiply by
-- 63 for the internal handling.
fadefrac = clamp0to1(fadefrac)*63
-- NOTE: a fadefrac of now <1 is allowed, e.g. for clearing the tint.
r = clamp0to1(r)*63
g = clamp0to1(g)*63
b = clamp0to1(b)*63
if (speed ~= nil) then
check_number(speed)
-- Clamp to sensible values; the speed is stored in an int8_t
-- (see below).
speed = clamp(speed, 1/128, 127)
else
speed = 1
end
if (prio ~= nil) then
check_number(prio)
if (not (prio >= -128 and prio < 127)) then
error("invalid argument #6 (priority): must be in [-128 .. 127]", 2)
end
else
prio = 0
end
-- Check if a currently active tint has higher priority.
if (p._pals.f > 0 and p._palsfadeprio > prio) then
return
end
-- The passed tint can be applied now.
p:_palfrom(fadefrac, r, g, b)
p._palsfadeprio = prio
-- Calculate .palsfade{speed,next}
if (speed >= 1) then
-- Will round to the nearest integer ("banker's
-- rounding"). NOTE: This is not correct for all numbers, see
-- http://blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
p._palsfadespeed = speed + 0.5
p._palsfadenext = 0
else
-- NOTE: Values that round to 0 have are equivalent behavior to
-- passing a <speed> of 1.
local negspeedrecip = -((1/speed) + 0.5) -- [-128.5 .. 1/127+0.5]
p._palsfadespeed = negspeedrecip
p._palsfadenext = negspeedrecip
end
end,
-- INTERNAL and CON-only.
_palfrom = function(p, f, r,g,b)
local pals = p._pals
-- Assume that CON's palfrom starts with prio 0 and speed 0.
if (pals.f == 0 or p._palsfadeprio <= 0) then
pals.f = f
pals.r, pals.g, pals.b = r, g, b
p._palsfadespeed, p._palsfadenext = 0, 0
end
end,
},
__newindex = function(p, key, val)
-- Write access to protected player members.
local allowneg = DukePlayer_prot_allowneg[key]
assert(type(allowneg)=="boolean")
if (allowneg==false or not (val < 0)) then
DukePlayer_prot_chkfunc[key](val)
end
ffi.cast(player_ptr_ct, p)[key] = val
end,
}
local function player_index_index(p, key)
if (key=="_input") then
return ffiC.g_player[p.weapon._p].sync[0]
end
-- Read access to protected player members.
return ffi.cast(player_ptr_ct, p)[0][key]
end
setmtonce(player_mt.__index, { __index = player_index_index })
ffi.metatype("DukePlayer_t", player_mt)
local function GenProjectileSetFunc(Member, checkfunc)
return function(self, idx)
if (not (idx == -1)) then
checkfunc(idx)
end
ffi.cast(projectile_ptr_ct, self)[Member] = idx
end
end
local projectile_mt = {
__index = {
set_spawns = GenProjectileSetFunc("spawns", check_tile_idx),
set_decal = GenProjectileSetFunc("decal", check_tile_idx),
set_trail = GenProjectileSetFunc("trail", check_tile_idx),
set_sound = GenProjectileSetFunc("sound", check_sound_idx),
set_isound = GenProjectileSetFunc("isound", check_sound_idx),
set_bsound = GenProjectileSetFunc("bsound", check_sound_idx),
},
}
ffi.metatype("projectile_t", projectile_mt)
local user_defs_mt = {
__index = {
set_screen_size = function(ud, screen_size)
-- TODO: modify .statusbarmode accordingly
ud.screen_size = screen_size
end,
set_volume_number = function(ud, volume_number)
-- NOTE: allow volume_number==MAXVOLUMES.
if (not (volume_number==con_lang.MAXVOLUMES)) then
bcheck.volume_idx(volume_number)
end
ud.volume_number = volume_number
end,
set_level_number = function(ud, level_number)
bcheck.level_idx(level_number)
ud.level_number = level_number
end,
},
}
ffi.metatype("user_defs", user_defs_mt)
--- CUSTOM "gv" VARIABLES
local camera_mt = {
-- TODO: "set position" method, which also updates the sectnum
__index = ffiC.g_camera,
__newindex = function(_, key, val)
if (key=="sect") then
check_sector_idx(val)
end
ffiC.g_camera[key] = val
end,
}
gv_access.cam = setmtonce({}, camera_mt)
gv_access._ud = ffiC.ud
-- Support for some CON global system gamevars. RETURN handled separately.
gv_access._csv = ffi.new "struct { int32_t LOTAG, HITAG, TEXTURE; }"
gv_access.REND = defs_c.conststruct
{
CLASSIC = 0,
POLYMOST = 3,
POLYMER = 4,
}
function gv_access._get_yxaspect()
return ffiC.yxaspect
end
function gv_access._get_viewingrange()
return ffiC.viewingrange
end
function gv_access._currentFramerate()
return ffiC.g_currentFrameRate
end
function gv_access._currentMenu()
return ffiC.g_currentMenu
end
function gv_access._changeMenu(cm)
ffiC.M_ChangeMenu(cm)
end
function gv_access._set_guniqhudid(id)
local MAXUNIQHUDID = 256 -- KEEPINSYNC build.h
if (id >= MAXUNIQHUDID+0ULL) then
error("invalid unique HUD ID "..id)
end
ffiC.guniqhudid = id
end
-- TODO: make return 1-based index?
function gv_access.currentEpisode()
return ffiC.ud.volume_number
end
function gv_access.currentLevel()
return ffiC.ud.level_number
end
function gv_access.doQuake(gametics, snd)
ffiC.g_earthquakeTime = gametics
if (snd ~= nil) then
con._globalsound(ffiC.screenpeek, snd)
end
end
-- Declare all con_lang.labels constants in the global FFI namespace.
for i=1,#con_lang.labels do
if (getmetatable(con_lang.labels[i]) ~= "noffiC") then
local strbuf = {"enum {"}
for label, val in pairs(con_lang.labels[i]) do
strbuf[#strbuf+1] = string.format("%s = %d,", label, val)
end
strbuf[#strbuf+1] = "};"
ffi.cdef(table.concat(strbuf))
end
end
---=== Set up restricted global environment ===---
local allowed_modules = {
coroutine=coroutine, bit=bit, table=table, math=math, string=string,
os = {
clock = function() return gv_.gethiticks()*0.001 end,
},
randgen = randgen,
stat = require("stat"),
bitar = require("bitar"),
xmath = require("xmath"),
con = con,
}
do
local ctype_cansave = {}
-- Register as "serializeable" the type of cdata object <v>.
local function reg_serializable_cv(v)
assert(type(v)=="cdata")
assert(type(v._serialize)=="function")
-- NOTE: tonumber() on a ctype cdata object gets its LuaJIT-internal
-- ID, the one that would be shown with tostring(), e.g.
-- ctype<struct 95>
ctype_cansave[tonumber(ffi.typeof(v))] = true
end
function lprivate.cansave_cdata(v)
return type(v)=="cdata" and ctype_cansave[tonumber(ffi.typeof(v))]
end
reg_serializable_cv(allowed_modules.xmath.vec3())
reg_serializable_cv(allowed_modules.xmath.ivec3())
end
-- Protect base modules.
local function basemod_newidx()
error("modifying base module table forbidden", 2)
end
for modname, themodule in pairs(allowed_modules) do
local mt = {
__index = themodule,
__newindex = basemod_newidx,
}
allowed_modules[modname] = setmtonce({}, mt)
end
---=== Module stuff ===---
local package_loaded = {} -- [<modname>] = false/true/table
local modname_stack = {} -- [<depth>]=string
local module_gamevars = {} -- [<modname>] = { <gvname1>, <gvname2>, ... }
local module_gvlocali = {} -- [<modname>] = { <localidx_beg>, <localidx_end> }
local module_thread = {} -- [<modname>] = <module_thread>
local function getcurmodname(thisfuncname)
if (#modname_stack == 0) then
error("'"..thisfuncname.."' must be called at the top level of a require'd file", 3)
-- ... as opposed to "at runtime".
end
return modname_stack[#modname_stack]
end
local function errorf(level, fmt, ...)
local errmsg = string.format(fmt, ...)
error(errmsg, level+1)
end
local function readintostr_mod(fn)
-- TODO: g_loadFromGroupOnly?
local fd = ffiC.kopen4loadfrommod(fn, 0)
if (fd < 0) then
return nil
end
return defs_c.readintostr(fd)
end
local debug = require("debug")
-- Get the number of active locals in the function that calls the function
-- calling this one.
local function getnumlocals(l)
-- 200 is the max. number of locals at one level
for i=1,200 do
-- level:
-- 0 is getlocal() itself.
-- 1 is this function (getnumlocals).
-- 2 is the function calling getnumlocals()
-- 3 is the function calling that one.
if (debug.getlocal(3, i) == nil) then
return i-1
end
end
end
local function error_on_nil_read(_, varname)
error("attempt to read nil variable '"..varname.."'", 2)
end
local required_module_mt = {
__index = error_on_nil_read,
__newindex = function()
error("modifying module table forbidden", 2)
end,
__metatable = true,
}
-- Will contain a function to restore gamevars when running from savegame
-- restoration. See SAVEFUNC_ARGS for its arguments.
local g_restorefunc = nil
-- Local gamevar restoration function run from
-- our_require('end_gamevars') <- [user module].
local function restore_local(li, lval)
-- level:
-- 0 is getlocal() itself.
-- 1 is this function (restore_local).
-- 2 is the function calling restore_local(), the savecode.
-- 3 is the function calling the savecode, our_require.
-- 4 is the function calling our_require, the module function.
if (ffiC._DEBUG_LUNATIC ~= 0) then
printf("Restoring index #%d (%s) with value %s",
li, debug.getlocal(4, li), tostring(lval))
end
assert(debug.setlocal(4, li, lval))
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.
-- * allows passing varargs beyond the name to the module.
local function our_require(modname, ...)
local ERRLEV = 5
-- Check module name is valid first.
-- TODO: restrict valid names?
if (type(modname) ~= "string" or #modname==0) then
error("module name must be a nonempty string", 2)
end
-- Handle the section between module() and require("end_gamevars").
if (modname == "end_gamevars") then
local thismodname = getcurmodname("require")
if (module_gamevars[thismodname] ~= nil) then
error("\"require 'end_gamevars'\" must be called at most once per require'd file", 2)
end
local gvnames = {}
for name in pairs(getfenv(2)) do
gvnames[#gvnames+1] = name
if (ffiC._DEBUG_LUNATIC ~= 0) then
printf("MODULE %s GAMEVAR %s", thismodname, name)
end
end
module_gamevars[thismodname] = gvnames
local gvmodi = module_gvlocali[thismodname]
gvmodi[2] = getnumlocals()
if (ffiC._DEBUG_LUNATIC ~= 0) then
local numlocals = gvmodi[2]-gvmodi[1]+1
if (numlocals > 0) then
printf("Module '%s' has %d locals, index %d to %d",
thismodname, numlocals, gvmodi[1], gvmodi[2])
end
end
-- Potentially restore gamevars.
if (g_restorefunc) then
local modtab = package_loaded[thismodname]
assert(type(modtab)=="table")
-- SAVEFUNC_ARGS.
g_restorefunc(thismodname, modtab, restore_local)
end
-- Return whether we're NOT running from a savegame restore in the
-- second outarg. (Lunatic-private!)
return nil, (g_restorefunc==nil)
end
-- See whether it's a base module name.
if (allowed_modules[modname] ~= nil) then
return allowed_modules[modname]
end
--- Search user modules...
if (modname:find("[/\\]")) then
error("Module name must not contain directory separators", ERRLEV-1)
end
-- Instead, dots are translated to directory separators. For EDuke32's
-- virtual file system, this is always a forward slash. Keep the original
-- module name for passing to the module function.
local omodname = modname
modname = modname:gsub("%.", "/")
local omod = package_loaded[modname]
if (omod ~= nil) then
if (omod==false) then
error("Loop while loading modules", ERRLEV-1)
end
-- already loaded
assert(omod==true or type(omod)=="table")
return omod
end
local modfn = modname .. ".lua"
local str = readintostr_mod(modfn)
if (str == nil) then
errorf(ERRLEV-1, "Couldn't open file \"%s\"", modfn)
end
-- Implant code that yields the module thread just before it would return
-- otherwise.
str = str.."\nrequire('coroutine').yield()"
local modfunc, errmsg = loadstring(str, modfn)
if (modfunc == nil) then
errorf(ERRLEV-1, "Couldn't load \"%s\": %s", modname, errmsg)
end
package_loaded[modname] = false -- 'not yet loaded'
table.insert(modname_stack, modname)
-- Run the module code in a separate Lua thread!
local modthread = coroutine.create(modfunc)
local ok, retval = coroutine.resume(modthread, omodname, ...)
if (not ok) then
errorf(ERRLEV-1, "Failed running \"%s\": %s\n%s", modname,
retval, debug.traceback(modthread))
end
table.remove(modname_stack)
local modtab = package_loaded[modname]
if (type(modtab) ~= "table") then
-- The module didn't call our 'module'. Check if it returned a table.
-- In that case, the coroutine has finished its main function and has
-- not reached our implanted 'yield'.
if (coroutine.status(modthread)=="dead" and type(retval)=="table") then
modtab = retval
package_loaded[modname] = modtab
else
package_loaded[modname] = true
end
end
if (type(modtab) == "table") then
-- Protect module table in any case (i.e. either if the module used our
-- 'module' or if it returned a table).
setmetatable(modtab, required_module_mt)
end
local gvmodi = module_gvlocali[modname]
if (gvmodi and gvmodi[2]>=gvmodi[1]) then
if (coroutine.status(modthread)=="suspended") then
-- Save off the suspended thread so that we may get its locals later on.
-- It is never resumed, but only ever used for debug.getlocal().
module_thread[modname] = modthread
if (ffiC._DEBUG_LUNATIC ~= 0) then
printf("Keeping coroutine for module \"%s\"", modname)
end
end
end
return modtab
end
local module_mt = {
__index = error_on_nil_read,
}
-- Our 'module' replacement doesn't get the module name from the function args
-- since a malicious user could remove other loaded modules this way.
-- Also, our 'module' takes no varargs ("option functions" in Lua).
-- TODO: make transactional?
local function our_module()
if (#modname_stack == 0) then
error("'module' must be called at the top level of a require'd file", 2)
-- ... as opposed to "at runtime".
end
local modname = getcurmodname("module")
if (package_loaded[modname]) then
error("'module' must be called at most once per require'd file", 2)
end
local M = setmetatable({}, module_mt)
package_loaded[modname] = M
-- change the environment of the function which called us:
setfenv(2, M)
module_gvlocali[modname] = { getnumlocals()+1 }
end
-- overridden 'error' that always passes a string to the base 'error'
local function our_error(errmsg, level)
if (type(errmsg) ~= "string") then
error("error using 'error': error message must be a string", 2)
end
if (level) then
if (type(level) ~= "number") then
error("error using 'error': error level must be a number", 2)
end
error(errmsg, level==0 and 0 or level+1)
end
error(errmsg, 2)
end
-- _G tweaks -- pull in only 'safe' stuff
local G_ = {} -- our soon-to-be global environment
G_.assert = assert
G_.error = our_error
G_.ipairs = ipairs
G_.pairs = pairs
G_.pcall = pcall
G_.print = print
G_.module = our_module
G_.next = next
G_.require = our_require
G_.select = select
G_.tostring = tostring
G_.tonumber = tonumber
G_.type = type
G_.unpack = unpack
G_.xpcall = xpcall
G_._VERSION = _VERSION
-- Available through our 'require':
-- bit, coroutine, math, string, table
-- Not available:
-- collectgarbage, debug, dofile, gcinfo (DEPRECATED), getfenv, getmetatable,
-- jit, load, loadfile, loadstring, newproxy (NOT STD?), package, rawequal,
-- rawget, rawset, setfenv, setmetatable
G_._G = G_
-- Chain together two functions taking 3 input args.
local function chain_func3(func1, func2)
if (func1==nil or func2==nil) then
return assert(func1 or func2)
end
-- Return a function that runs <func1> first and then tail-calls <func2>.
return function(aci, pli, dist)
func1(aci, pli, dist)
return func2(aci, pli, dist)
end
end
-- Determines the last numeric index of a table using *pairs*, so that in
-- arg-lists with "holes" (e.g. {1, 2, nil, function() end}) are handled
-- properly.
local function ourmaxn(tab)
local maxi = 0
for i in pairs(tab) do
if (type(i)=="number") then
maxi = math.max(i, maxi)
end
end
return maxi
end
-- Running for the very first time?
local g_firstRun = (ffiC.g_elCONSize == 0)
-- Actor functions, saved for actor chaining
local actor_funcs = {}
-- Event functions, saved for event chaining
local event_funcs = {}
-- Per-actor sprite animation callbacks
local animsprite_funcs = {}
local gameactor_internal = gameactor_internal -- included in lunatic.c
local gameevent_internal = gameevent_internal -- included in lunatic.c
local function animate_all_sprites()
for i=0,ffiC.spritesortcnt-1 do
local tspr = ffiC.tsprite[i]
if (tspr.owner < ffiC.MAXSPRITES+0ULL) then
local spr = tspr:getspr()
local animfunc = animsprite_funcs[spr.picnum]
if (animfunc) then
animfunc(tspr)
end
end
end
end
-- gameactor{tilenum [, flags [, strength [, action [, move [, movflags]]]]], func}
-- Every arg may be positional OR key=val (with the name indicated above as key),
-- but not both.
local function our_gameactor(args)
bcheck.top_level("gameactor")
if (type(args)~="table") then
error("invalid gameactor call: must be passed a table")
end
local tilenum = args[1]
if (type(tilenum) ~= "number") then
error("invalid argument #1 to gameactor: must be a number", 2)
end
if (tilenum >= ffiC.MAXTILES+0ULL) then
error("invalid argument #1 to gameactor: must be a tile number [0..gv.MAXTILES-1]", 2)
end
local lastargi = ourmaxn(args)
local func = args[lastargi]
if (type(func) ~= "function") then
func = args.func
lastargi = 1/0
end
if (type(func) ~= "function") then
error("invalid gameactor call: must provide a function with last numeric arg or .func", 2)
end
local flags = (lastargi > 2 and args[2]) or args.flags or 0
if (type(flags) ~= "number") then
error("invalid 'flags' argument (#2) to gameactor: must be a number", 2)
end
local AF = actor.FLAGS
local chainflags = band(flags, AF._CHAIN_MASK_ACTOR)
flags = band(flags, BNOT.CHAIN_MASK_ACTOR)
-- Default chaining behavior: don't, replace the old actor instead, but
-- unlike CON, also replace its flags. (CON ORs them instead.)
if (chainflags == 0) then
chainflags = AF.replace_hard
elseif (band(chainflags, chainflags-1) ~= 0) then
error("invalid chaining control flags to gameactor", 2)
end
local replacep = (chainflags==AF.replace_soft or chainflags==AF.replace_hard)
if (not replacep and not actor_funcs[tilenum]) then
error("attempt to chain code to nonexistent actor tile "..tilenum, 2)
end
local flags_rbits = band(flags, BNOT.USER_MASK)
if (flags_rbits ~= 0) then
error("invalid 'flags' argument (#2) to gameactor: must not set reserved bits (0x"
..(bit.tohex(flags_rbits))..")", 2)
end
local strength = ((lastargi > 3 and args[3]) or args.strength) or (replacep and 0 or nil)
if (replacep or strength~=nil) then
if (type(strength) ~= "number") then
error("invalid 'strength' argument (#3) to gameactor: must be a number", 2)
end
end
local act = ((lastargi > 4 and args[4]) or args.action) or (replacep and literal_act[0] or nil)
if (replacep or act ~= nil) then
if (type(act)=="number" and (act==0 or act==1)) then
act = literal_act[act]
elseif (not ffi.istype(con_action_ct, act)) then
error("invalid 'action' argument (#4) to gameactor: must be an action", 2)
end
end
local mov = ((lastargi > 5 and args[5]) or args.move) or (replacep and literal_mov[0] or nil)
if (replacep or mov ~= nil) then
if (type(mov)=="number" and (mov==0 or mov==1)) then
mov = literal_mov[mov]
elseif (not ffi.istype(con_move_ct, mov)) then
error("invalid 'move' argument (#5) to gameactor: must be a move", 2)
end
end
local movflags = ((lastargi > 6 and args[6]) or args.movflags) or (replacep and 0 or nil)
if (replacep or movflags ~= nil) then
if (type(movflags) ~= "number") then
error("invalid 'movflags' argument (#6) to gameactor: must be a number", 2)
end
end
-- Register a potentially passed drawn sprite animation callback function.
-- TODO: allow registering without main actor execution callback.
local animfunc = args.animate
if (animfunc ~= nil) then
if (type(animfunc) ~= "function") then
error("invalid 'animate' argument to gameactor: must be a function", 2)
end
animsprite_funcs[tilenum] = replacep and func
or (chainflags==AF.chain_beg) and chain_func3(animfunc, animsprite_funcs[tilenum])
or (chainflags==AF.chain_end) and chain_func3(animsprite_funcs[tilenum], animfunc)
or assert(false)
-- Register our EVENT_ANIMATEALLSPRITES only now so that it is not
-- called if there are no 'animate' definitions.
gameevent_internal(95, animate_all_sprites) -- EVENT_ANIMATEALLSPRITES
end
-- All good, set the tile bits in initial run and register the actor!
-- XXX: Need to see if some other state living on the C side is clobbered
-- by Lua state recreation.
if (g_firstRun) then
-- NOTE: when chaining, this allows passing different flags which are
-- silently ORed. This may or may not be what the user intends, but it
-- allows e.g. adding a stayput bit to an already defined enemy.
-- Modifying existing behavior is the whole point of chaining after all.
-- When soft-replacing, bitwise OR too, emulating CON behavior.
local tile = ffiC.g_tile[tilenum]
tile.flags = (chainflags==AF.replace_hard) and flags or bit.bor(tile.flags, flags)
end
local newfunc = replacep and func
or (chainflags==AF.chain_beg) and chain_func3(func, actor_funcs[tilenum])
or (chainflags==AF.chain_end) and chain_func3(actor_funcs[tilenum], func)
or assert(false)
gameactor_internal(tilenum, strength, act, mov, movflags, newfunc)
actor_funcs[tilenum] = newfunc
end
-- gameevent{<event idx or string> [, flags], <event function>}
local function our_gameevent(args)
bcheck.top_level("gameevent")
if (type(args)~="table") then
error("invalid gameevent call: must be passed a table")
end
local event = args[1]
if (type(event) == "string") then
if (event:sub(1,6) ~= "EVENT_") then
event = "EVENT_"..event
end
local eventidx = con_lang.EVENT[event]
if (eventidx == nil) then
errorf(2, "gameevent: invalid event label %q", event)
end
event = eventidx
end
if (type(event) ~= "number") then
error("invalid argument #1 to gameevent: must be a number or event label", 2)
end
if (event >= con_lang.MAXEVENTS+0ULL) then
error("invalid argument #1 to gameevent: must be an event number (0 .. MAXEVENTS-1)", 2)
end
local AF = actor.FLAGS
-- Kind of CODEDUP from our_gameactor.
local lastargi = ourmaxn(args)
local func = args[lastargi]
if (type(func) ~= "function") then
func = args.func
lastargi = 1/0
end
if (type(func) ~= "function") then
error("invalid gameevent call: must provide a function with last numeric arg or .func", 2)
end
-- Event chaining: in Lunatic, chaining at the *end* is the default.
local flags = (lastargi > 2 and args[2]) or args.flags or AF.chain_end
if (type(flags) ~= "number") then
error("invalid 'flags' argument (#2) to gameevent: must be a number", 2)
end
if (band(flags, BNOT.CHAIN_MASK_EVENT) ~= 0) then
error("invalid 'flags' argument to gameevent: must not set reserved bits", 2)
end
local newfunc = (flags==AF.replace) and func
or (flags==AF.chain_beg) and chain_func3(func, event_funcs[event])
or (flags==AF.chain_end) and chain_func3(event_funcs[event], func)
or assert(false)
gameevent_internal(event, newfunc)
event_funcs[event] = newfunc
end
--- non-default data and functions
G_.gameevent = our_gameevent
G_.gameactor = our_gameactor
-- These come from above:
G_.player = player
G_.actor = actor
G_.projectile = projectile
G_.g_tile = g_tile
---=== Lunatic translator setup ===---
read_into_string = readintostr_mod -- for lunacon
local lunacon = require("lunacon")
local concode, lineinfo
--- Get Lua code for CON (+ mutator) code.
if (g_firstRun) then
-- Compiling CON for the first time.
local confn = { ffi.string(ffiC.G_ConFile()) }
local nummods = ffiC.g_scriptModulesNum
if (nummods > 0) then
assert(ffiC.g_scriptModules ~= nil)
for i=1,nummods do
confn[i+1] = ffi.string(ffiC.g_scriptModules[i-1])
end
end
concode, lineinfo = lunacon.compile(confn)
if (concode == nil) then
error("Failure compiling CON code, exiting.", 0)
end
assert(lineinfo)
-- Back up the translated code on the C side.
assert(type(concode)=="string")
ffiC.El_SetCON(concode)
else
-- CON was already compiled.
concode = ffi.string(ffiC.g_elCON, ffiC.g_elCONSize)
lineinfo = lunacon.get_lineinfo(concode)
end
if (ffiC._DEBUG_LUNATIC ~= 0) then
-- XXX: lineinfo of 2nd up time has one line less.
printf("CON line info has %d Lua lines", #lineinfo.llines)
end
do
-- Translate one Lua line number to a CON file name + line number
local function transline(lnum)
return string.format("%s:%d", lineinfo:getfline(tonumber(lnum)))
end
-- Register the function that tweaks an error message, looking out for
-- errors from CON and translating the line numbers.
local function tweak_traceback_msg(errmsg)
return errmsg:gsub('%[string "CON"%]:([0-9]+)', transline)
end
set_tweak_traceback_internal(tweak_traceback_msg)
end
-- XXX: May still be require'd from user code, we don't want that (at least not
-- under this name).
local CON_MODULE_NAME = "_CON\0"
-- Set up Lunatic gamevar serialization.
do
local savegame = require("savegame")
-- Callback for: const char *(int32_t *slenptr);
ffiC.El_SerializeGamevars = function(slenptr)
local sb = savegame.savebuffer()
-- Module name, module table, restore_local. See SAVEFUNC_ARGS.
sb:addraw("local N,M,F=...")
-- A local to temporarily hold module locals.
sb:addraw("local L")
-- XXX: System gamevars? Most of them ought to be saved with C data.
for modname, modvars in pairs(module_gamevars) do
sb:startmod(modname)
-- Handle global gamevars first.
for i=1,#modvars do
local varname = modvars[i]
-- Serialize gamevar named 'varname' from module named 'modname'.
-- XXX: May error. This will terminate EDuke32 since this callback
-- is run unprotected.
if (sb:add("M."..varname, package_loaded[modname][varname])) then
-- We couldn't serialize that gamevar.
slenptr[0] = -1
-- Signal which gamevar that was.
return (modname==CON_MODULE_NAME and "<CON>" or modname).."."..varname
end
end
local modthread = module_thread[modname]
if (modthread) then
-- Handle local gamevars.
local gvmodi = module_gvlocali[modname]
for li=gvmodi[1],gvmodi[2] do
-- Serialize local with index <li>. Get its value first.
local lname, lval = debug.getlocal(modthread, 1, li)
if (sb:add("L", lval)) then
-- We couldn't serialize that gamevar.
slenptr[0] = -1
return "local "..modname.."."..lname
end
-- Call restore_local.
sb:addrawf("F(%d,L)", li)
end
end
sb:endmod()
end
-- Get the whole code as a string.
local savecode = sb:getcode()
if (ffiC._DEBUG_LUNATIC ~= 0) then
-- Dump the code if Lunatic debugging is enabled and there is a
-- LUNATIC_SAVECODE_FN variable in the environment.
local os = require("os")
local fn = os.getenv("LUNATIC_SAVECODE_FN")
if (fn ~= nil) then
local io = require("io")
local f = io.open(fn, "w")
if (f ~= nil) then
f:write(savecode)
f:close()
printf("Wrote Lunatic gamevar restoration code to \"%s\".", fn)
end
end
end
-- Set the size of the code and return the code to C.
slenptr[0] = #savecode
return savecode
end
end
-- 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_)
-- Print keys and values of a table.
local function printkv(label, table)
print("========== Keys and values of "..label.." ("..tostring(table)..")")
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 ===---
-- error(..., 2) is to blame the caller and get its line numbers
-- Map of 'gv' variable names to C ones.
local varnameMap = {
gametic = "g_moveThingsCount",
}
gv_access.gametic = true
local tmpmt = {
__index = function(_, key)
if (gv_access[key] == nil) then
-- Read access allowed.
return ffiC[key]
elseif (type(gv_access[key])~="boolean") then
-- Overridden 'gv' pseudo-member...
return gv_access[key]
elseif (varnameMap[key]) then
-- Variable known under a different name on the C side.
return ffiC[varnameMap[key]]
end
error("access forbidden", 2)
end,
__newindex = function(_, key, val)
if (gv_access[key] == nil) then
-- Read-only vars handled by LuaJIT.
ffiC[key] = val
else
error("write access forbidden", 2)
end
end,
}
gv = setmtonce(gv_, tmpmt)
-- This will create 'sprite', 'wall', etc. HERE, i.e. in the environment of this chunk
defs_c.create_globals(_G)
-- REMOVE this for release
DBG_ = {}
DBG_.debug = require("debug")
DBG_.printkv = printkv
DBG_.loadstring = loadstring
DBG_.oom = function()
local s = "1"
for i=1,math.huge do
s = s..s
end
end
-- Test reading from all struct members
function DBG_.testmembread()
for _1, sac in pairs { con_lang.StructAccessCode, con_lang.StructAccessCode2 } do
for what, labels in pairs(sac) do
if (what~="tspr") then
for _3, membaccode in pairs(labels) do
if (type(membaccode)=="table") then
membaccode = membaccode[1]
end
if (membaccode) then
local codestr = [[
do
local _bit,_math=require'bit',require'math'
local _band,_gv=_bit.band,gv
local tmp=]]..
membaccode:gsub("%%s","0").." end"
local code = assert(loadstring(codestr))
code()
end
end
end
end
end
end
---=== 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!
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,
__metatable = true,
})
-- 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 globals defined after setting this chunk's
-- environment earlier.
setfenv(0, _G)
do
-- If we're running from a savegame restoration, create the restoration
-- function. Must be here, after the above setfenv(), because it must be
-- created in this protected ('user') context!
local cstr = ffiC.g_elSavecode
if (cstr~=nil) then
local restorecode = ffi.string(cstr)
ffiC.El_FreeSaveCode()
g_restorefunc = assert(loadstring(restorecode))
end
end
-- Run the CON code translated into Lua.
if (concode) then
local confunc, conerrmsg = loadstring(concode, "CON")
if (confunc == nil) then
error("Failure loading translated CON code: "..conerrmsg, 0)
end
-- Emulate our 'require' for the CON module when running it, for
-- our_module() which is called from the generated Lua code.
table.insert(modname_stack, CON_MODULE_NAME)
local conlabels, conaction, conmove, conai = confunc()
table.remove(modname_stack)
local function protect_con_table(tab)
return setmtonce({}, { __index=tab, __newindex=basemod_newidx })
end
-- Set up CON.* modules, providing access to diffenrent kinds of labels
-- defined in CON from Lua. See CONCODE_RETURN in lunacon.lua.
allowed_modules["CON.DEFS"] = protect_con_table(conlabels)
allowed_modules["CON.ACTION"] = protect_con_table(conaction)
allowed_modules["CON.MOVE"] = protect_con_table(conmove)
allowed_modules["CON.AI"] = protect_con_table(conai)
-- Propagate potentially remapped defines to the control module.
con._setuplabels(conlabels)
end
-- When starting a map, load Lua modules given on the command line.
if (not g_firstRun) then
local i=0
while (ffiC.g_elModules[i] ~= nil) do
-- Get the module name and strip the trailing extension.
local modname = ffi.string(ffiC.g_elModules[i]):gsub("%.lua$","")
if (modname:find("%.")) then
-- Because they will be replaced by dirseps in our_require().
error("Dots are not permitted in module names", 0)
end
-- Allow forward slashes in module names from the cmdline.
our_require((modname:gsub("%.lua$",""):gsub("/",".")))
i = i+1
end
end