mirror of
https://github.com/ZDoom/raze-gles.git
synced 2024-12-25 03:00:46 +00:00
c7d97d55ec
There's only one RETURN value on the C side (g_RETURN), which is also referenced from Lua. Upon entering an event, its value takes on the per-event default one, and its value when the event code finishes is passed back to the game. Independently of that, its value is always saved and restored across event calls. git-svn-id: https://svn.eduke32.com/eduke32@3601 1a8010ca-5511-0410-912e-c29ae57300e0
1825 lines
52 KiB
Text
1825 lines
52 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
|
|
|
|
-- 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 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)
|
|
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 = {
|
|
__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_
|
|
|
|
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 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 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 = (numargs > 3) and args[3] 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 = (numargs > 4) and args[4] 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 = (numargs > 5) and args[5] or 0
|
|
if (type(movflags) ~= "number") then
|
|
error("invalid 'movflags' argument to gameactor: must be a number", 2)
|
|
end
|
|
|
|
-- All good, set the tile bits and register the actor!
|
|
|
|
local tile = ffiC.g_tile[tilenum]
|
|
tile.flags = bit.bor(tile.flags, flags)
|
|
|
|
gameactor_internal(tilenum, strength, act, mov, movflags, args[numargs])
|
|
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
|
|
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
|