Lunatic: knee-deep in code.

- more codegen
- make more members const, some char unsigned
- fix some "geom" metamethods
- '^' operator

git-svn-id: https://svn.eduke32.com/eduke32@3253 1a8010ca-5511-0410-912e-c29ae57300e0
This commit is contained in:
helixhorned 2012-12-03 18:24:25 +00:00
parent 2bcf8aa8ec
commit b062d5f572
9 changed files with 305 additions and 80 deletions

View file

@ -2584,7 +2584,7 @@ typedef struct {
*
* This means that
* 1) tint application is independent of their order.
* 2) going from n+1 to n tints is continuous.
* 2) going from n+1 to n tints is continuous when the leaving one has faded.
*
* But note that for more than one tint, the composite tint will in general
* change its hue as the ratio of the weights of the individual ones changes.

View file

@ -5499,3 +5499,20 @@ void G_RestoreMapState(mapstate_t *save)
G_ResetTimers();
}
}
#ifdef LUNATIC
void VM_FallSprite(int32_t i)
{
vm.g_i = i;
vm.g_sp = &sprite[i];
VM_Fall();
}
int32_t VM_ResetPlayer2(int32_t snum)
{
vm.g_p = snum;
vm.g_flags &= ~VM_NOEXECUTE;
VM_ResetPlayer();
return (vm.g_flags&VM_NOEXECUTE);
}
#endif

View file

@ -11,7 +11,8 @@ local setmetatable = setmetatable
local error = error
local type = type
local player = player
local player = assert(player)
local cansee = require("defs_common").cansee
module(...)
@ -111,7 +112,7 @@ function ai(name, action, move, flags)
end
--== RUNTIME CON FUNCTIONS ==--
---=== RUNTIME CON FUNCTIONS ===---
function rotatesprite(x, y, zoom, ang, tilenum, shade, pal, orientation,
cx1, cy1, cx2, cy2)
@ -128,10 +129,10 @@ function rnd(x)
end
---=== Weapon stuff
---=== Weapon stuff ===---
--- Helper functions (might be exported later)
--- Helper functions (might be exported later) ---
local function have_weapon(ps, weap)
return (bit.band(ps.gotweapon, bit.lshift(1, weap)) ~= 0)
@ -157,26 +158,117 @@ local function P_AddWeaponAmmoCommon(ps, weap, amount)
end
--- Exported functions
--- Functions that must be exported because they are used by LunaCON generated code,
--- but which are off limits to users. (That is, we need to think about how to
--- expose the functionality in a better fashion than merely giving access to
--- the C functions.)
-- Non-local control flow. These ones call the original error(), not our
-- redefinition in defs.ilua.
function longjmp()
error(false)
local function check_sprite_idx(i)
if (i >= ffiC.MAXSPRITES+0ULL) then
error("invalid argument: must be a valid sprite index", 3)
end
end
function killit()
-- TODO: guard against deletion of player sprite?
error(true)
local function check_tile_idx(tilenum)
if (tilenum >= ffiC.MAXTILES+0ULL) then
error("invalid argument: must be a valid tile number", 3)
end
end
function _A_Shoot(i, atwith)
check_sprite_idx(i)
check_tile_idx(atwith)
return ffiC.A_Shoot(i, atwith)
end
function _A_IncurDamage(sn)
check_sprite_idx(sn)
return ffiC.A_IncurDamage(sn)
end
function _VM_FallSprite(i)
check_sprite_idx(i)
ffiC.VM_FallSprite(i)
end
function _sizeto(i, xr, yr)
local spr = sprite[i]
local dr = (xr-spr.xrepeat)
-- NOTE: could "overflow" (e.g. goal repeat is 256, gets converted to 0)
spr.xrepeat = spr.xrepeat + ((dr == 0) and 0 or (dr < 0 and -1 or 1))
-- TODO: y stretching is conditional
dr = (yr-spr.yrepeat)
spr.yrepeat = spr.yrepeat + ((dr == 0) and 0 or (dr < 0 and -1 or 1))
end
-- NOTE: function args have overloaded meaning
function _A_Spawn(j, pn)
local bound_check = sector[sprite[j].sectnum] -- two in one whack
check_tile_idx(pn)
return ffiC.A_Spawn(j, pn)
end
function _pstomp(ps, i)
if (ps.knee_incs == 0 and sprite[ps.i].xrepeat >= 40) then
local spr = sprite[i]
if (cansee(spr^(4*256), spr.sectnum, ps.pos^(-16*256), sprite[ps.i].sectnum)) then
for j=ffiC.playerswhenstarted-1,0 do
if (player[j].actorsqu == i) then
return
end
end
ps.actorsqu = i
ps.knee_incs = -1
if (ps.weapon_pos == 0) then
ps.weapon_pos = -1
end
end
end
end
function _VM_ResetPlayer2(snum)
local bound_check = player[snum]
return (ffiC.VM_ResetPlayer2(snum)~=0)
end
function _addinventory(p, inv, amount, pal)
if (inv == ffiC.GET_ACCESS) then
local PALBITS = { [0]=1, [21]=2, [23]=4 }
if (PALBITS[pal]) then
ps.got_access = bit.bor(ps.got_access, PALBITS[pal])
end
else
local ICONS = {
[ffiC.GET_FIRSTAID] = 1, -- ICON_FIRSTAID
[ffiC.GET_STEROIDS] = 2,
[ffiC.GET_HOLODUKE] = 3,
[ffiC.GET_JETPACK] = 4,
[ffiC.GET_HEATS] = 5,
[ffiC.GET_SCUBA] = 6,
[ffiC.GET_BOOTS] = 7,
}
if (ICONS[inv]) then
ps.inven_icon = ICONS[inv]
end
if (inv == ffiC.GET_SHIELD) then
amount = math.min(ps.max_shield_amount, amount)
end
-- NOTE: this is more permissive than CON, e.g. allows
-- GET_DUMMY1 too.
ps:set_inv_amount(inv, amount)
end
end
-- The return value is true iff the ammo was at the weapon's max.
-- In that case, no action is taken.
function addammo(ps, weapon, amount)
function _addammo(ps, weapon, amount)
return have_ammo_at_max(ps, weap) or P_AddWeaponAmmoCommon(ps, weap, amount)
end
function addweapon(ps, weapon, amount)
function _addweapon(ps, weapon, amount)
if (weap >= ffiC.MAX_WEAPONS+0ULL) then
error("Invalid weapon ID "..weap, 2)
end
@ -189,3 +281,17 @@ function addweapon(ps, weapon, amount)
P_AddWeaponAmmoCommon(ps, weap, amount)
end
--- Exported functions ---
-- Non-local control flow. These ones call the original error(), not our
-- redefinition in defs.ilua.
function longjmp()
error(false)
end
function killit()
-- TODO: guard against deletion of player sprite?
error(true)
end

View file

@ -9,6 +9,7 @@ local ffiC = ffi.C
local bit = bit
local string = string
local table = table
local math = math
local print = print
--== First, load the definitions common to the game's and editor's Lua interface.
@ -40,9 +41,11 @@ function decl(str)
ffi.cdef(str)
end
-- load them!
-- load the common definitions!
local defs_c = require("defs_common")
local strip_const = defs_c.strip_const
local setmtonce = defs_c.setmtonce
---=== EDuke32 game definitions ===---
@ -134,7 +137,9 @@ local ACTOR_STRUCT = [[
const struct action ac;
const int16_t _padding[1];
int16_t picnum,ang,extra,owner; //8b
const int16_t picnum;
int16_t ang, extra;
const int16_t owner;
int16_t movflag,tempang,timetosleep; //6b
int32_t flags, bposx,bposy,bposz; //16b
@ -142,7 +147,9 @@ local ACTOR_STRUCT = [[
int32_t lasttransport; //4b
const int16_t lightId, lightcount, lightmaxrange, cgg; //8b
int16_t actorstayput, dispicnum, shootzvel; // 6b
int16_t actorstayput;
const int16_t dispicnum;
int16_t shootzvel;
const int8_t _do_not_use[8];
}
]]
@ -167,19 +174,22 @@ local DUKEPLAYER_STRUCT = [[
const int16_t loogiey[64];
int16_t sbs, sound_pitch;
int16_t ang, oang, angvel, cursectnum, look_ang, last_extra, subweapon;
int16_t ang, oang, angvel;
const int16_t cursectnum;
int16_t look_ang, last_extra, subweapon;
const int16_t max_ammo_amount[MAX_WEAPONS];
const int16_t ammo_amount[MAX_WEAPONS];
const int16_t inv_amount[GET_MAX];
int16_t wackedbyactor, pyoff, opyoff;
int16_t horiz, horizoff, ohoriz, ohorizoff;
int16_t newowner, jumping_counter, airleft;
const int16_t newowner;
int16_t jumping_counter, airleft;
int16_t fta, ftq, access_wallnum, access_spritenum;
int16_t got_access, weapon_ang, visibility;
int16_t somethingonplayer, on_crane;
const int16_t i;
int16_t one_parallax_sectnum;
const int16_t one_parallax_sectnum;
int16_t random_club_frame, one_eighty_count;
int16_t dummyplayersprite, extra_extra8;
int16_t actorsqu, timebeforeexit, customexitsound, last_pissed_time;
@ -417,6 +427,11 @@ const int32_t playerswhenstarted;
int32_t A_IncurDamage(int32_t sn); // not bound-checked!
void P_AddWeaponMaybeSwitch(DukePlayer_t *ps, int32_t weap);
int32_t A_Shoot(int32_t i, int32_t atwith);
int32_t A_IncurDamage(int32_t sn);
int32_t A_Spawn(int32_t j, int32_t pn);
void VM_FallSprite(int32_t i);
int32_t VM_ResetPlayer2(int32_t snum);
]]
-- functions
@ -461,6 +476,18 @@ if (not good) then
error("Some sizes don't match between C and LuaJIT/FFI.")
end
-- "player" global, needed by the "control" module
local tmpmt = {
__index = function(tab, key)
if (key >= 0 and key < ffiC.playerswhenstarted) then
return ffiC.g_player[key].ps[0]
end
error('out-of-bounds player[] read access', 2)
end,
__newindex = function(tab, key, val) error('cannot write directly to player[] struct', 2) end,
}
player = setmtonce({}, tmpmt)
--== Custom operations for BUILD data structures ==--
-- declares struct action and struct move, and their ID-wrapped types
@ -625,6 +652,12 @@ local function check_weapon_idx(weap)
end
end
local function check_inventory_idx(inv)
if (inv >= ffiC.GET_MAX+0ULL) then
error("Invalid inventory ID "..inv, 3)
end
end
local player_mt = {
__index = {
--- Getters/setters
@ -653,13 +686,34 @@ local player_mt = {
ffi.cast(player_ptr_ct, p).curr_weapon = weap
end,
get_inv_amount = function(p, inv)
check_inventory_idx(inv)
return ffi.cast(player_ptr_ct, p).inv_amount[inv]
end,
set_inv_amount = function(p, inv, amount)
check_inventory_idx(inv)
ffi.cast(player_ptr_ct, p).inv_amount[inv] = amount
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,
addammo = con._addammo,
addweapon = con._addweapon,
addinventory = con._addinventory,
--- Convenient wrappers around engine functions
cansee = function(p, otherpos, othersect)
pstomp = con._pstomp,
--- Not fully specified, off-limits to users. Might disappear, change
--- signature, etc...
_palfrom = function(p, f, r,g,b)
local pals = p.pals
pals.f = f
pals.r, pals.g, pals.b = r, g, b
end,
-- XXX: is this so useful without z offset?
_cansee = function(p, otherpos, othersect)
return cansee(p.pos, sprite[p.i].sectnum, otherpos, othersect)
end,
},
@ -884,6 +938,7 @@ G_._G = G_
G_._EDUKE32_LUNATIC = _EDUKE32_LUNATIC
G_.gameevent = gameevent -- included in lunatic.c
G_.gameactor = gameactor -- included in lunatic.c
G_.player = player -- from above
---=== Lunatic interpreter setup ===---
@ -912,8 +967,6 @@ end
---=== Restricted access to C variables from Lunatic ===---
local setmtonce = defs_c.setmtonce
-- error(..., 2) is to blame the caller and get its line numbers
local tmpmt = {
@ -929,22 +982,10 @@ local tmpmt = {
}
setmtonce(gv, tmpmt)
---- indirect C array access ----
local tmpmt = {
__index = function(tab, key)
if (key >= 0 and key < ffiC.playerswhenstarted) then
return ffiC.g_player[key].ps[0]
end
error('out-of-bounds player[] read access', 2)
end,
__newindex = function(tab, key, val) error('cannot write directly to player[] struct', 2) end,
}
player = setmtonce({}, tmpmt)
-- This will create 'sprite', 'wall', etc. HERE, i.e. in the environment of this chunk
defs_c.create_globals(_G)
---- indirect C array access ----
actor = defs_c.creategtab(ffiC.actor, ffiC.MAXSPRITES, 'actor[]')
function TEMP_getvollev() -- REMOVE

View file

@ -32,7 +32,10 @@ local SPRITE_STRUCT = [[
uint8_t xrepeat, yrepeat;
int8_t xoffset, yoffset;
const int16_t sectnum, statnum;
int16_t ang, owner, xvel, yvel, zvel;
int16_t ang;
// NOTE: yvel is often used as player index in game code. Make xvel/zvel
// "const" for consistency, too.
const int16_t owner, xvel, yvel, zvel;
int16_t lotag, hitag, extra;
}
]]
@ -42,6 +45,10 @@ function strip_const(structstr)
return string.gsub(structstr, "const ", "");
end
-- NOTE for FFI definitions: we're compiling EDuke32 with -funsigned-char, so
-- we need to take care to declare chars as unsigned whenever it matters, for
-- example if it represents a palette index. (I think it's harmless for stuff
-- like passing a function argument, but it should be done there for clarity.)
-- TODO: provide getters for unsigned {hi,lo}tag?
ffi.cdef([[
@ -105,7 +112,7 @@ typedef struct {
} hitdata_t;
typedef struct {
char r,g,b,f;
unsigned char r,g,b,f;
} palette_t;
#pragma pack(pop)
]])
@ -203,7 +210,7 @@ void updatesectorbreadth(int32_t x, int32_t y, int16_t *sectnum);
void updatesectorz(int32_t x, int32_t y, int32_t z, int16_t *sectnum);
void rotatesprite(int32_t sx, int32_t sy, int32_t z, int16_t a, int16_t picnum,
int8_t dashade, char dapalnum, int32_t dastat,
int8_t dashade, unsigned char dapalnum, int32_t dastat,
int32_t cx1, int32_t cy1, int32_t cx2, int32_t cy2);
]]
@ -215,10 +222,22 @@ int32_t krand(void);
int32_t ksqrt(uint32_t num);
]]
local ivec3_
local ivec3_mt = {
-- '^' is the "translate upwards" operator
__pow = function(v, zofs)
return ivec3_(v.x, v.y, v.z-zofs)
end,
}
ivec3_ = ffi.metatype("vec3_t", ivec3_mt)
local spritetype_ptr_ct = ffi.typeof("spritetype_u_t *")
local spritetype_mt = {
__pow = function(s, zofs)
return ivec3_(s.x, s.y, s.z-zofs)
end,
__index = {
set_picnum = function(s, tilenum)
if (tilenum >= ffiC.MAXTILES+0ULL) then

View file

@ -72,4 +72,9 @@ md4once;
A_IncurDamage;
P_AddWeaponMaybeSwitch;
A_Shoot;
A_IncurDamage;
A_Spawn;
VM_FallSprite;
VM_ResetPlayer2;
};

View file

@ -4,7 +4,7 @@ local ffi = require("ffi")
local math = require("math")
local type = type
local assert = assert
local error = error
module(...)
@ -19,19 +19,23 @@ local vec2_
local vec2_mt = {
__add = function(a, b) return vec2_(a.x+b.x, a.y+b.y) end,
__sub = function(a, b) return vec2_(a.x-b.x, a.y-b.y) end,
__unm = function(a) return vec2_(-a.x, -a.x) end,
__unm = function(a) return vec2_(-a.x, -a.y) end,
__mul = function(a,b)
if (type(a)=="number") then
return vec2_(a*b.x, a*b.y)
end
assert(type(b)=="number")
if (type(b)~="number") then
error("number expected in vec2 multiplication", 2)
end
return vec2_(a.x*b, a.y*b)
end,
__div = function(a,b)
assert(type(b)=="number")
if (type(b)~="number") then
error("number expected in vec2 division", 2)
end
return vec2_(a.x/b, a.y/b)
end,
@ -52,19 +56,23 @@ local vec3_
local vec3_mt = {
__add = function(a, b) return vec3_(a.x+b.x, a.y+b.y, a.z+b.z) end,
__sub = function(a, b) return vec3_(a.x-b.x, a.y-b.y, a.z-b.z) end,
__unm = function(a) return vec3_(-a.x, -a.x, -a.z) end,
__unm = function(a) return vec3_(-a.x, -a.y, -a.z) end,
__mul = function(a,b)
if (type(a)=="number") then
return vec3_(a*b.x, a*b.y, a*b.z)
end
assert(type(b)=="number")
if (type(b)~="number") then
error("number expected in vec3 multiplication", 2)
end
return vec2_(a.x*b, a.y*b, a.z*b)
end,
__div = function(a,b)
assert(type(b)=="number")
if (type(b)~="number") then
error("number expected in vec3 division", 2)
end
return vec2_(a.x/b, a.y/b, a.z/b)
end,

View file

@ -662,6 +662,12 @@ local function ACS(s) return "actor[_aci]"..s end
local function SPS(s) return "sprite[_aci]"..s end
local function PLS(s) return "player[_pli]"..s end
local function handle_palfrom(...)
local v = {...}
return format(PLS":_palfrom(%d,%d,%d,%d)",
v[1] or 0, v[2] or 0, v[3] or 0, v[4] or 0)
end
-- NOTE about prefixes: most is handled by all_alt_pattern(), however commands
-- that have no arguments and that are prefixes of other commands MUST be
-- suffixed with a "* #sp1" pattern.
@ -670,7 +676,7 @@ local Ci = {
-- these can appear anywhere in the script
["break"] = cmd()
/ "do return end", -- TODO: more exact semantics
["return"] = cmd()
["return"] = cmd() -- NLCF
/ "_con.longjmp()", -- TODO: test with code from Wiki "return" entry
state = cmd(I)
@ -756,10 +762,13 @@ local Ci = {
/ SPS":set_picnum(%1)",
count = cmd(D)
/ ACS":set_count(%1)",
cstator = cmd(D),
cstat = cmd(D),
cstator = cmd(D)
/ (SPS".cstat=_bit.bor(%1,"..SPS".cstat)"),
cstat = cmd(D)
/ SPS".cstat=%1",
clipdist = cmd(D),
sizeto = cmd(D,D),
sizeto = cmd(D,D)
/ "_con._sizeto(_aci)", -- TODO: see control.lua:_sizeto
sizeat = cmd(D,D)
/ (SPS".xrepeat,"..SPS".yrepeat=%1,%2"),
strength = cmd(D)
@ -804,23 +813,29 @@ local Ci = {
setgamepalette = cmd(R),
-- some commands taking defines
addammo = cmd(D,D), -- exec SPECIAL HANDLING!
addweapon = cmd(D,D), -- exec SPECIAL HANDLING!
debris = cmd(D,D),
addinventory = cmd(D,D),
addammo = cmd(D,D) -- NLCF
/ format("if (%s) then _con.longjmp() end", PLS":addammo(%1,%2)"),
addweapon = cmd(D,D) -- NLCF
/ format("if (%s) then _con.longjmp() end", PLS":addweapon(%1,%2)"),
debris = cmd(D,D)
/ "", -- TODO
addinventory = cmd(D,D)
/ PLS":addinventory(%1,%2)",
guts = cmd(D,D),
-- cont'd
addkills = cmd(D)
/ (PLS".actors_killed="..PLS".actors_killed+%1;"..ACS".actorstayput=-1"),
addphealth = cmd(D),
addphealth = cmd(D)
/ "", -- TODO
angoff = cmd(D),
debug = cmd(D)
/ "", -- TODO?
endofgame = cmd(D),
eqspawn = cmd(D),
espawn = cmd(D),
globalsound = cmd(D),
globalsound = cmd(D)
/ "",
lotsofglass = cmd(D),
mail = cmd(D),
money = cmd(D),
@ -831,32 +846,38 @@ local Ci = {
save = cmd(D),
sleeptime = cmd(D),
soundonce = cmd(D),
sound = cmd(D),
sound = cmd(D)
/ "", -- TODO: all things audio...
spawn = cmd(D),
stopsound = cmd(D),
stopsound = cmd(D)
/ "",
eshoot = cmd(D),
ezshoot = cmd(R,D),
ezshootvar = cmd(R,R),
shoot = cmd(D),
shoot = cmd(D)
/ "_con._A_Shoot(_aci, %1)",
zshoot = cmd(R,D),
zshootvar = cmd(R,R),
fall = cmd(),
fall = cmd()
/ "_con._VM_FallSprite(_aci)",
flash = cmd(),
getlastpal = cmd(),
insertspriteq = cmd(),
killit = cmd()
/ "_con.killit()", -- exec SPECIAL HANDLING!
killit = cmd() -- NLCF
/ "_con.killit()",
mikesnd = cmd(),
nullop = cmd(),
pkick = cmd(),
pstomp = cmd(),
pstomp = cmd()
/ PLS":pstomp(_aci)",
resetactioncount = cmd()
/ ACS":reset_acount()",
resetcount = cmd()
/ ACS":set_count(0)",
resetplayer = cmd(), -- exec SPECIAL HANDLING!
resetplayer = cmd() -- NLCF
/ "if (_con._VM_ResetPlayer2(_pli,_aci)) then _con.longjmp() end",
respawnhitag = cmd(),
tip = cmd()
/ PLS".tipincs=26",
@ -925,7 +946,8 @@ local Ci = {
neartag = cmd(R,R,R,R,R,W,W,W,W,R,R),
operateactivators = cmd(R,R),
operatesectors = cmd(R,R),
palfrom = (sp1 * t_define)^-4,
palfrom = (sp1 * t_define)^-4
/ handle_palfrom,
operate = cmd() * #sp1,
@ -1002,9 +1024,11 @@ local Cif = {
/ ACS":has_move(%1)",
ifrnd = cmd(D)
/ "_gv.rnd(%1)",
ifpdistl = cmd(D),
ifpdistg = cmd(D),
/ "_con.rnd(%1)",
ifpdistl = cmd(D)
/ "_dist<%1", -- TODO: maybe set actor[].timetosleep afterwards
ifpdistg = cmd(D)
/ "_dist>%1", -- TODO: maybe set actor[].timetosleep afterwards
ifactioncount = cmd(D)
/ ACS":get_acount()==%1",
ifcount = cmd(D)
@ -1029,7 +1053,8 @@ local Cif = {
/ SPS".pal==%1",
ifgotweaponce = cmd(D),
ifangdiffl = cmd(D),
ifsound = cmd(D),
ifsound = cmd(D)
/ "",
ifpinventory = cmd(D,D),
ifvarl = cmd(R,D),
@ -1053,12 +1078,15 @@ local Cif = {
ifactorsound = cmd(R,R),
ifp = (sp1 * t_define)^1,
ifsquished = cmd(),
ifsquished = cmd()
/ "false", -- TODO
ifserver = cmd(),
ifrespawn = cmd(),
ifoutside = cmd()
/ format("_bit.band(sector[%s].ceilingstat,1)~=0", SPS".sectnum"),
ifonwater = cmd(),
ifonwater = cmd()
/ format("sectnum[%s].lotag==1 and _math.abs(%s-sector[%s].floorz)<32*256",
SPS".sectnum", SPS".z", SPS".sectnum"),
ifnotmoving = cmd(),
ifnosounds = cmd(),
ifmultiplayer = cmd()
@ -1068,12 +1096,13 @@ local Cif = {
ifinspace = cmd(),
ifinouterspace = cmd(),
ifhitweapon = cmd()
/ "TODO",
/ "_con._A_IncurDamage(_aci)",
ifhitspace = cmd(),
ifdead = cmd()
/ SPS".extra<=0",
ifclient = cmd(),
ifcanshoottarget = cmd(),
ifcanshoottarget = cmd()
/ "false", -- TODO
ifcanseetarget = cmd(),
ifcansee = cmd() * #sp1,
ifbulletnear = cmd(),
@ -1466,7 +1495,7 @@ end
---=== EXPORTED FUNCTIONS ===---
local function new_initial_perfile_codetab()
return { "local _con=require'con'; local _gv=gv; local _bit=require'bit'" }
return { "local _con=require'con'; local _bit=require'bit'" }
end
function parse(contents) -- local

View file

@ -56,12 +56,12 @@ enum dukeinv_t {
// these are not in the same order as the above, and it can't be changed for compat reasons. lame!
enum dukeinvicon_t {
ICON_NONE,
ICON_NONE, // 0
ICON_FIRSTAID,
ICON_STEROIDS,
ICON_HOLODUKE,
ICON_JETPACK,
ICON_HEATS,
ICON_HEATS, // 5
ICON_SCUBA,
ICON_BOOTS,
ICON_MAX