raze/polymer/eduke32/source/lunatic/defs.ilua
helixhorned e63874d011 Lunatic: chaining of actor callback functions.
For events and actors, a flag can be now passed whether to chain the new
function at the beginning or end of an already existing one, or to replace
it entirely.
Also, for the translator, add option -fno-error-nostate, disabled by default.

git-svn-id: https://svn.eduke32.com/eduke32@3629 1a8010ca-5511-0410-912e-c29ae57300e0
2013-03-31 18:58:04 +00:00

1896 lines
55 KiB
Text

-- INTERNAL
-- definitions of BUILD and game types for the Lunatic Interpreter
local require = require
local ffi = require("ffi")
local ffiC = ffi.C
local bit = bit
local string = string
local table = table
local math = math
local assert = assert
local error = error
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
-- 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 = print
---=== 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,
};
]])
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;
]]
-- 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]; // 56b sometimes used to hold offsets to con code
const struct move mv;
const struct action ac;
const int16_t _padding[1];
int32_t flags; //4b
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;
int16_t movflag,tempang,timetosleep; //6b
int16_t actorstayput;
const int16_t dispicnum;
int16_t shootzvel, cgg;
const int16_t lightId, lightcount, lightmaxrange;
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
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 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;
int16_t wackedbyactor, pyoff, opyoff;
int16_t horiz, horizoff, ohoriz, ohorizoff;
const int16_t newowner;
int16_t jumping_counter, airleft;
int16_t fta;
const int16_t ftq, 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 int16_t dummyplayersprite;
int16_t extra_extra8;
int16_t actorsqu, timebeforeexit;
const 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, frag_ps, 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, 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;
int8_t last_weapon, cheat_phase, weapon_pos, wantweaponfire;
const int8_t curr_weapon;
uint8_t palette;
palette_t _pals;
// 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;
int16_t sound, isound, vel;
const int16_t decal, trail;
int16_t tnum, drop;
int16_t offset, bounces, 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[6];
}
]]
-- KEEPINSYNC weapondata_mt below.
local WEAPONDATA_STRUCT = "struct {"..table.concat(con_lang.wdata_members, ';').."; }"
local randgen = require("randgen")
local geom = require("geom")
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
-- Converts a template struct definition to an external one, in which arrays
-- have been substituted by randomly named scalar fields.
local function mangle_arrays(structstr)
ma_count = 0
-- NOTE: regexp only works for non-nested arrays and for one array per line.
return (string.gsub(structstr, "const%s+([%w_]+)[^\n]+%[([%w_]+)%];", ma_replace_array))
end
--print(mangle_arrays(ACTOR_STRUCT))
--- default defines etc.
local con_lang = require("con_lang")
ffi.cdef([[
typedef struct { int32_t _p; } weaponaccess_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
]].. mangle_arrays(ACTOR_STRUCT) ..[[
actor_t;
typedef
]].. mangle_arrays(DUKEPLAYER_STRUCT) ..[[
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;
} 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;
]])
-- 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[[
const char *s_buildRev;
const char *g_sizes_of_what[];
int32_t g_sizes_of[];
int32_t g_elCallDepth;
const char **g_argv;
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_t g_playerWeapon[MAXPLAYERS][MAX_WEAPONS];
tiledata_t g_tile[MAXTILES];
projectile_t ProjectileData[MAXTILES];
projectile_t SpriteProjectile[MAXSPRITES];
// Used from lunacon.lua for dynamic tile remapping:
struct
{
const char *str;
int32_t *dynvalptr;
const int16_t staticval;
} g_dynTileList[];
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;
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);
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);
]]
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);
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,8 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,
}
-- 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.chain_beg = bit.tobit(0x20000000)
our_SFLAG.chain_end = bit.tobit(0x40000000)
our_SFLAG.replace = bit.bor(our_SFLAG.chain_beg, our_SFLAG.chain_end)
-- 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)
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))
local BNOT_SFLAG_USER_MASK = bit.bnot(actor.FLAGS.USER_MASK)
local BNOT_CHAIN_MASK = bit.bnot(actor.FLAGS.replace)
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 MV, AC, AI = con.MV, con.AC, con.AI
-- Add game-side metamethods to "spritetype" and register it with "metatype"
local spr_mt_index_add = {
isenemy = function(s)
return con.isenemytile(s.picnum)
end,
}
defs_c.finish_spritetype(spr_mt_index_add)
-- All-zero action and move
local nullac, nullmv = ffi.new("const struct action"), ffi.new("const struct move")
local function check_literal_am(am, typename)
if (type(am) ~= "number") then
error("bad argument: expected number or "..typename, 3)
end
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("con_action_t")
local con_move_ct = ffi.typeof("con_move_t")
local con_ai_ct = ffi.typeof("con_ai_t")
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 (type(act)=="string") then
act = AC[act];
end
-- TODO: disallow passing the FFI types altogether, in favor of
-- strings (also move, ai)?
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 (type(act)=="string") then
act = AC[act];
end
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)
ffi.cast(actor_ptr_ct, a).t_data[0] = 0
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,
-- move
set_move = function(a, mov, movflags)
a = ffi.cast(actor_ptr_ct, a)
if (type(mov)=="string") then
mov = MV[mov];
end
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
local i = get_actor_idx(a)
ffiC.sprite[i].hitag = movflags or 0
-- TODO: random angle moveflag
end,
has_move = function(a, mov)
a = ffi.cast(actor_ptr_ct, a)
if (type(mov)=="string") then
mov = MV[mov];
end
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,
-- ai
set_ai = function(a, ai)
local oa = a
a = ffi.cast(actor_ptr_ct, a)
if (type(ai)=="string") then
ai = AI[ai];
end
-- 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
a.t_data[5] = ai.id
oa:set_action(ai.act)
oa:set_move(ai.mov, ai.movflags)
a.t_data[0] = 0
end,
has_ai = function(a, ai)
a = ffi.cast(actor_ptr_ct, a)
if (type(ai)=="string") then
ai = AI[ai];
end
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.
-- TODO: make a bcarray instead.
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 (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,
},
}
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)[member]
end,
__newindex = function(wd, member, 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
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
end
ffi.cast(weapondata_ptr_ct, wd)[member] = val
end,
}
ffi.metatype("weapondata_t", weapondata_mt)
local weaponaccess_mt = {
-- Make syntax like "player[0].weapon.KNEE.shoots" possible
__index = function(wa, key)
if (type(key)=="number") then
check_weapon_idx(key)
return ffiC.g_playerWeapon[wa._p][key]
end
-- TODO: use bcarray?
return ffiC.g_playerWeapon[wa._p][ffiC[key.."_WEAPON"]]
end,
}
ffi.metatype("weaponaccess_t", weaponaccess_mt)
local player_mt = {
__index = {
--- Getters/setters
get_ammo_amount = function(p, weap)
return p.ammo_amount[weap]
end,
set_ammo_amount = function(p, weap, amount)
p.ammo_amount[weap] = amount
end,
get_max_ammo_amount = function(p, weap)
return p.max_ammo_amount[weap]
end,
set_max_ammo_amount = function(p, weap, amount)
p.max_ammo_amount[weap] = amount
end,
set_curr_weapon = function(p, weap)
check_weapon_idx(weap)
ffi.cast(player_ptr_ct, p).curr_weapon = weap
end,
get_inv_amount = function(p, inv)
return p.inv_amount[inv]
end,
set_inv_amount = function(p, inv, amount)
p.inv_amount[inv] = amount
end,
set_ftq = function(p, ftq)
bcheck.quote_idx(ftq)
ffi.cast(player_ptr_ct, p).ftq = ftq
end,
set_cursectnum = function(p, sectnum)
check_sector_idx(sectnum)
ffi.cast(player_ptr_ct, p).cursectnum = sectnum
end,
set_customexitsound = function(p, soundnum)
check_sound_idx(soundnum)
ffi.cast(player_ptr_ct, p).customexitsound = soundnum
end,
-- 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,
have_weapon = function(p, weap)
return (bit.band(p.gotweapon, bit.lshift(1, weap)) ~= 0)
end,
give_weapon = function(p, weap)
p.gotweapon = bit.bor(p.gotweapon, bit.lshift(1, weap))
end,
take_weapon = function(p, weap)
p.gotweapon = bit.band(p.gotweapon, bit.bnot(bit.lshift(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,
-- XXX: is the correct spelling "whack"?
wack = 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-bit.band(ffiC.krand(),255), 1)
p.rotscrnang = n
p.look_ang = n
end,
--- Not fully specified, off-limits to users. Might disappear, change
--- signature, etc...
-- TODO: need to think about external API: 0-255 based? 0-1 based?
_palfrom = function(p, f, r,g,b)
local pals = p._pals
pals.f = f
pals.r, pals.g, pals.b = r, g, b
end,
},
}
local function player_index_index(p, key)
if (key=="_input") then
return ffiC.g_player[p.weapon._p].sync[0]
end
end
setmtonce(player_mt.__index, { __index = player_index_index })
ffi.metatype("DukePlayer_t", player_mt)
local function GenProjectileSetFunc(Member)
return function(self, picnum)
if (picnum >= 0) then
check_tile_idx(picnum)
end
ffi.cast(projectile_ptr_ct, self)[Member] = picnum
end
end
local projectile_mt = {
__index = {
set_spawns = GenProjectileSetFunc "spawns",
set_decal = GenProjectileSetFunc "decal",
set_trail = GenProjectileSetFunc "trail",
},
}
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)
-- XXX: should volume_number==MAXVOLUMES be allowed too?
if (volume_number >= con_lang.MAXVOLUMES+0ULL) then
error("invalid volume number "..volume_number, 2)
end
ud.volume_number = volume_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; }"
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._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.currentRenderMode()
-- TODO: USE_OPENGL=0 build
return ffiC.rendmode
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_.gethitickms()*0.001 end,
},
randgen = randgen,
geom = geom,
stat = require("stat"),
bitar = require("bitar"),
xmath = require("xmath"),
con = con,
}
-- Protect base modules.
do
local function basemodule_newindex()
error("modifying base module table forbidden", 2)
end
for modname, themodule in pairs(allowed_modules) do
local mt = {
__index = themodule,
__newindex = basemodule_newindex,
}
allowed_modules[modname] = setmtonce({}, mt)
end
end
local function check_valid_modname(modname, errlev)
if (type(modname) ~= "string") then
error("module name must be a string", errlev+1)
end
-- TODO: restrict valid names?
end
local function errorf(level, fmt, ...)
local errmsg = string.format(fmt, ...)
error(errmsg, level+1)
end
local package_loaded = {}
local modname_stack = {}
local ERRLEV = 5
local function readintostr(fn)
-- XXX: this is pretty much the same as the code in L_RunOnce()
local fd = ffiC.kopen4loadfrommod(fn, 0) -- TODO: g_loadFromGroupOnly
if (fd < 0) then
return nil
end
local sz = ffiC.kfilelength(fd)
if (sz == 0) then
ffiC.kclose(fd)
return ""
end
if (sz < 0) then
ffi.kclose(fd)
error("INTERNAL ERROR: kfilelength() returned negative length", ERRLEV)
end
local str = ffi.new("char [?]", sz)
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.
-- * allows passing varargs beyond the name to the module.
local function our_require(modname, ...)
check_valid_modname(modname, 2)
-- see whether it's a base module name first
if (allowed_modules[modname] ~= nil) then
return allowed_modules[modname]
end
--- search user modules
local omod = package_loaded[modname]
if (omod ~= nil) then
if (omod==true) then
error("Loop while loading modules", ERRLEV-1)
end
-- already loaded
return omod
end
local modfn = modname .. ".lua"
local str = readintostr(modfn)
if (str == nil) then
errorf(ERRLEV-1, "Couldn't open file \"%s\"", modfn)
end
local modfunc, errmsg = loadstring(str)
if (modfunc == nil) then
errorf(ERRLEV-1, "Couldn't load \"%s\": %s", modname, errmsg)
end
package_loaded[modname] = true
table.insert(modname_stack, modname)
-- Run the module code!
modfunc(modname, ...) -- TODO: call protected and report errors here later
table.remove(modname_stack)
local modtab = package_loaded[modname]
if (type(modtab) == "table") then
-- Protect module table if there is one...
local mt = {
__index = modtab,
__newindex = function(tab,idx,val)
error("modifying module table forbidden", 2)
end,
}
setmetatable(modtab, mt)
end
return modtab
end
local module_mt = {
__index = function (_, n)
error("attempt to read undeclared variable '"..n.."'", 2)
end,
}
-- 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' has no varargs ("option functions" in Lua).
-- TODO: make transactional?
local function our_module()
local modname = modname_stack[#modname_stack]
if (type(modname) ~= "string") then
error("'module' must be called at the top level of a 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)
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
-- Actor functions, saved for actor chaining
local actor_funcs = {}
local gameactor_internal = gameactor_internal -- included in lunatic.c
-- gameactor(tilenum [, flags [, strength [, act [, mov [, movflags]]]]], actor_func)
local function our_gameactor(tilenum, ...)
bcheck.top_level("gameactor")
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 args = {...}
-- Number of varargs; args may have nil's in the middle!
local numargs = select('#', ...)
if (numargs == 0) then
error("invalid call to gameactor: must have at least two arguments (tilenum, func)", 2)
end
if (numargs >= 7) then -- sic, because tilenum isn't counted!
error("invalid call to gameactor: must have at most 7 arguments", 2)
end
if (type(args[numargs]) ~= "function") then
error("invalid last argument to gameactor: must be a function", 2)
end
local flags = (numargs > 1) and args[1] or 0
if (type(flags) ~= "number") then
error("invalid 'flags' argument to gameactor: must be a number", 2)
end
local AF = actor.FLAGS
local chainflags = bit.band(flags, AF.replace)
flags = bit.band(flags, BNOT_CHAIN_MASK)
-- Default chaining behavior: don't, replace the old actor instead (like CON).
if (chainflags == 0) then
chainflags = AF.replace
end
local replacep = (chainflags==AF.replace)
if (not replacep and not actor_funcs[tilenum]) then
error("attempt to chain code to nonexistent actor tile "..tilenum, 2)
end
local flags_rbits = bit.band(flags, BNOT_SFLAG_USER_MASK)
if (flags_rbits ~= 0) then
error("invalid 'flags' argument to gameactor: must not set reserved bits (0x"
..(bit.tohex(flags_rbits))..")", 2)
end
local strength = (numargs > 2) and args[2] or (replacep and 0 or nil)
if (replacep or strength~=nil) then
if (type(strength) ~= "number") then
error("invalid 'strength' argument to gameactor: must be a number", 2)
end
end
-- TODO: literal number action other than 0?
local act = (numargs > 3) and args[3] or (replacep and "NO" or nil)
if (replacep or act ~= nil) then
if (type(act)=="string") then
act = AC[act]
end
if (not ffi.istype(con_action_ct, act)) then
error("invalid 'act' argument to gameactor: must be a string or action", 2)
end
end
-- TODO: literal number move other than 0?
local mov = (numargs > 4) and args[4] or (replacep and "NO" or nil)
if (replacep or mov ~= nil) then
if (type(mov)=="string") then
mov = MV[mov]
end
if (not ffi.istype(con_move_ct, mov)) then
error("invalid 'mov' argument to gameactor: must be a string or move", 2)
end
end
local movflags = (numargs > 5) and args[5] or (replacep and 0 or nil)
if (replacep or movflags ~= nil) then
if (type(movflags) ~= "number") then
error("invalid 'movflags' argument to gameactor: must be a number", 2)
end
end
-- All good, set the tile bits and register the actor!
local tile = ffiC.g_tile[tilenum]
local func = args[numargs]
-- 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.
tile.flags = replacep and flags or bit.bor(tile.flags, flags)
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
-- Event functions, saved for event chaining
local event_funcs = {}
local gameevent_internal = gameevent_internal -- included in lunatic.c
-- gameevent(<event idx or string> [, flags], <event function>)
local function our_gameevent(event, flags, func)
if (ffiC.g_elCallDepth > 0) then
error("Invalid use of gameevent: must be called from top level", 2)
end
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
-- Event chaining: in Lunatic, chaining at the *end* is the default.
if (func==nil) then
func = flags
flags = AF.chain_end
else
if (type(flags) ~= "number") then
error("invalid 'flags' argument to gameevent: must be a number", 2)
end
if (bit.band(flags, BNOT_CHAIN_MASK) ~= 0) then
error("invalid 'flags' argument to gameevent: must not set reserved bits", 2)
end
if (flags == 0) then
flags = AF.chain_end
end
end
if (type(func) ~= "function") then
error("invalid last argument to gameevent: must be a function", 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 interpreter setup ===---
local concode
--- Compile CONs
do
read_into_string = readintostr -- for lunacon
local lunacon = require("lunacon")
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
local lineinfo
concode, lineinfo = lunacon.compile(confn)
if (concode == nil) then
error("Failure compiling CON code, exiting.", 0)
end
assert(lineinfo)
-- 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
-- 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
local tmpmt = {
__index = function(_, key)
if (gv_access[key] == nil) then
return ffiC[key]
end
if (type(gv_access[key])~="boolean") then
-- overridden...
return gv_access[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)
---=== 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 (rawget(G_, name) ~= nil) then
error(string.format("Identifier name '%s' is already used in the global environment", name), 2)
end
gamevarNames[name] = true
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
--]=]
assert(loadstring(string))()
end
-- REMOVE this for release
DBG_ = {}
DBG_.debug = require("debug")
DBG_.printkv = printkv
DBG_.loadstring = loadstring
DBG_.serializeGamevars = serializeGamevars
DBG_.loadGamevarsString = loadGamevarsString
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 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,
})
-- 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)
-- Finally, 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
confunc()
end