mirror of
https://github.com/ZDoom/Raze.git
synced 2024-12-15 15:11:41 +00:00
0646e41fa4
Also, fix con._setgamepalette. git-svn-id: https://svn.eduke32.com/eduke32@3541 1a8010ca-5511-0410-912e-c29ae57300e0
1733 lines
49 KiB
Text
1733 lines
49 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 ipairs = ipairs
|
|
local loadstring = loadstring
|
|
local pairs = pairs
|
|
local rawget = rawget
|
|
local rawset = rawset
|
|
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 ===---
|
|
|
|
---- game structs ----
|
|
ffi.cdef[[
|
|
enum dukeinv_t {
|
|
GET_STEROIDS,
|
|
GET_SHIELD,
|
|
GET_SCUBA,
|
|
GET_HOLODUKE,
|
|
GET_JETPACK,
|
|
GET_DUMMY1,
|
|
GET_ACCESS,
|
|
GET_HEATS,
|
|
GET_DUMMY2,
|
|
GET_FIRSTAID,
|
|
GET_BOOTS,
|
|
GET_MAX
|
|
};
|
|
|
|
enum dukeweapon_t {
|
|
KNEE_WEAPON,
|
|
PISTOL_WEAPON,
|
|
SHOTGUN_WEAPON,
|
|
CHAINGUN_WEAPON,
|
|
RPG_WEAPON,
|
|
HANDBOMB_WEAPON,
|
|
SHRINKER_WEAPON,
|
|
DEVISTATOR_WEAPON,
|
|
TRIPBOMB_WEAPON,
|
|
FREEZE_WEAPON,
|
|
HANDREMOTE_WEAPON,
|
|
GROW_WEAPON,
|
|
MAX_WEAPONS
|
|
};
|
|
|
|
enum {
|
|
MAXPLAYERS = 16,
|
|
};
|
|
]]
|
|
|
|
ffi.cdef[[
|
|
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
|
|
|
|
const int16_t lightId, lightcount, lightmaxrange;
|
|
int16_t cgg;
|
|
int16_t actorstayput;
|
|
const int16_t dispicnum;
|
|
int16_t shootzvel;
|
|
const int8_t _do_not_use[8];
|
|
}
|
|
]]
|
|
|
|
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
|
|
|
|
-- TODO: randomize member names
|
|
bcarray.new("int16_t", 64, "loogie", "int16_x_64")
|
|
bcarray.new("int16_t", ffiC.MAX_WEAPONS, "weapon", "int16_x_MAX_WEAPONS")
|
|
bcarray.new("int16_t", ffiC.GET_MAX, "inventory", "int16_x_GET_MAX")
|
|
|
|
-- 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];
|
|
}
|
|
]]
|
|
|
|
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
|
|
]].. PROJECTILE_STRUCT ..[[
|
|
projectile_t;
|
|
|
|
typedef struct {
|
|
uint32_t flags;
|
|
int32_t cacherange;
|
|
projectile_t defproj;
|
|
} tiledata_t;
|
|
|
|
typedef struct {
|
|
vec3_t pos;
|
|
int32_t dist, clock;
|
|
int16_t ang, horiz;
|
|
int16_t sect;
|
|
} camera_t;
|
|
|
|
typedef struct {
|
|
]] .. table.concat(con_lang.wdata_members, ';')..';' .. [[
|
|
} weapondata_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;
|
|
]]
|
|
|
|
-- 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;
|
|
int32_t g_elEventRETURN;
|
|
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];
|
|
|
|
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);
|
|
int32_t kfilelength(int32_t handle);
|
|
void kclose(int32_t handle);
|
|
int32_t kread(int32_t handle, void *buffer, int32_t leng);
|
|
|
|
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
|
|
{
|
|
INPUT_MOVE_FORWARD = 1,
|
|
INPUT_MOVE_BACKWARD = 2,
|
|
INPUT_STRAFE_LEFT = 4,
|
|
INPUT_STRAFE_RIGHT = 8,
|
|
INPUT_TURN_LEFT = 16,
|
|
INPUT_TURN_RIGHT = 32,
|
|
}
|
|
|
|
-- XXX: error message will say "g_player_ps"
|
|
player = setmtonce({}, defs_c.GenStructMetatable("g_player_ps", "playerswhenstarted", player_static_members))
|
|
|
|
-- needed by "control"
|
|
actor = defs_c.creategtab(ffiC.actor, ffiC.MAXSPRITES, "actor[]")
|
|
|
|
local projectile = defs_c.creategtab(ffiC.ProjectileData, ffiC.MAXTILES, "projectile[]")
|
|
|
|
--== 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 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)
|
|
check_tile_idx(picnum)
|
|
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 = {
|
|
__newindex = function(wd, member, val)
|
|
if (string.match(member, "sound")) then
|
|
-- TODO: set to 0 if oob? (e.g. CrackDown)
|
|
check_sound_idx(val)
|
|
elseif (member=="workslike") then
|
|
check_weapon_idx(val)
|
|
elseif (member=="shoots" or member=="spawn") then
|
|
-- TODO: set to 0 if oob? (e.g. AMC TC)
|
|
check_tile_idx(val)
|
|
end
|
|
|
|
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
|
|
|
|
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
|
|
gv_access._csv = ffi.new "struct { int32_t RETURN, 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
|
|
|
|
function gv_access._RETURN(setval)
|
|
if (setval ~= nil) then
|
|
ffiC.g_elEventRETURN = setval
|
|
else
|
|
return ffiC.g_elEventRETURN
|
|
end
|
|
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
|
|
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
|
|
|
|
|
|
---=== 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,
|
|
}
|
|
|
|
for modname, themodule in pairs(allowed_modules) do
|
|
local mt = {
|
|
__index = themodule,
|
|
__newindex = function(tab,idx,val)
|
|
error("modifying base module table forbidden", 2)
|
|
end,
|
|
}
|
|
|
|
-- Comment out to make base modules not protected:
|
|
allowed_modules[modname] = setmtonce({}, mt)
|
|
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_
|
|
|
|
local gameactor_internal = gameactor_internal -- included in lunatic.c
|
|
-- gameactor(tilenum [, strength [, act [, mov [, movflags]]]], actor_func)
|
|
local function our_gameactor(tilenum, ...)
|
|
bcheck.top_level("gameactor")
|
|
|
|
local args = {...}
|
|
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 .. MAXTILES-1)", 2)
|
|
end
|
|
if (#args == 0) then
|
|
error("invalid call to gameactor: must have at least two arguments (tilenum, func)", 2)
|
|
end
|
|
if (#args > 5) then
|
|
error("invalid call to gameactor: must have at most six arguments", 2)
|
|
end
|
|
if (type(args[#args]) ~= "function") then
|
|
error("invalid last argument to gameactor: must be a function", 2)
|
|
end
|
|
|
|
local strength = (#args >= 2) and args[1] or 0
|
|
if (type(strength) ~= "number") then
|
|
error("invalid 'strength' argument to gameactor: must be a number", 2)
|
|
end
|
|
|
|
-- TODO: literal number action other than 0?
|
|
local act = (#args >= 3) and args[2] or "NO"
|
|
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
|
|
|
|
-- TODO: literal number move other than 0?
|
|
local mov = (#args >= 4) and args[3] or "NO"
|
|
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
|
|
|
|
local movflags = (#args >= 5) and args[4] or 0
|
|
if (type(movflags) ~= "number") then
|
|
error("invalid 'movflags' argument to gameactor: must be a number", 2)
|
|
end
|
|
|
|
gameactor_internal(tilenum, strength, act, mov, movflags, args[#args])
|
|
end
|
|
|
|
-- Event functions, saved for event chaining
|
|
local event_funcs = {}
|
|
|
|
local gameevent_internal = gameevent_internal -- included in lunatic.c
|
|
-- gameevent(<event idx or string>, <event function>)
|
|
local function our_gameevent(event, 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", 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
|
|
|
|
if (type(func) ~= "function") then
|
|
error("invalid argument #2 to gameevent: must be a function", 2)
|
|
end
|
|
|
|
local newfunc = func
|
|
local oldfunc = event_funcs[event]
|
|
-- event chaining: events defined later come first
|
|
if (oldfunc ~= nil) then
|
|
newfunc = function(aci, pli, dist)
|
|
func(aci, pli, dist)
|
|
return oldfunc(aci, pli, dist)
|
|
end
|
|
end
|
|
|
|
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
|
|
|
|
|
|
---=== 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)
|
|
assert(lineinfo)
|
|
|
|
if (concode == nil) then
|
|
error("Failure compiling CON code, exiting.", 0)
|
|
end
|
|
|
|
-- 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
|
|
|
|
gv = gv_
|
|
local tmpmt = {
|
|
__index = function(gv, 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() error("write access forbidden", 2) end,
|
|
}
|
|
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)
|
|
end
|
|
confunc()
|
|
end
|