Skip to content
Snippets Groups Projects
Select Git revision
  • next default protected
  • master
  • acs
  • spriteinfo-refactor
  • 1392-2-2-15-attempting-to-draw-a-hud-graphic-with-the-same-lump-name-as-a-lua-script-crashes-the
  • clipmidtex
  • custom-map-names
  • nogravity-trampolines
  • 2214-pre4
  • 2214-pre3
  • just-in-case
  • fix-opengl-parameter-crash
  • 2214-pre2
  • 2214-pre1
  • delfile2
  • cleanupmusic
  • gametype-refactor-1
  • extra-textures
  • optimize-storewallrange
  • increase-maxconditionsets
  • SRB2_release_2.2.15
  • SRB2_release_2.2.13
  • SRB2_release_2.2.12
  • SRB2_release_2.2.11
  • SRB2_release_2.2.10
  • SRB2_release_2.2.9
  • SRB2_release_2.2.8
  • SRB2_release_2.2.7
  • SRB2_release_2.2.6
  • SRB2_release_2.2.5
  • SRB2_release_2.2.4
  • SRB2_release_2.2.3
  • SRB2_release_2.2.2
  • SRB2_release_2.2.1
  • SRB2_release_2.2.0
  • SRB2_release_2.1.25
  • SRB2_release_2.1.24
  • SRB2_release_2.1.23
  • SRB2_release_2.1.22
  • SRB2_release_2.1.21
40 results

lua_mobjlib.c

Blame
  • Forked from STJr / SRB2
    6676 commits behind the upstream repository.
    lua_mobjlib.c 20.93 KiB
    // SONIC ROBO BLAST 2
    //-----------------------------------------------------------------------------
    // Copyright (C) 2012-2016 by John "JTE" Muniz.
    // Copyright (C) 2012-2020 by Sonic Team Junior.
    //
    // This program is free software distributed under the
    // terms of the GNU General Public License, version 2.
    // See the 'LICENSE' file for more details.
    //-----------------------------------------------------------------------------
    /// \file  lua_mobjlib.c
    /// \brief mobj/thing library for Lua scripting
    
    #include "doomdef.h"
    #include "fastcmp.h"
    #include "r_skins.h"
    #include "p_local.h"
    #include "g_game.h"
    #include "p_setup.h"
    
    #include "lua_script.h"
    #include "lua_libs.h"
    #include "lua_hud.h" // hud_running errors
    
    static const char *const array_opt[] ={"iterate",NULL};
    
    enum mobj_e {
    	mobj_valid = 0,
    	mobj_x,
    	mobj_y,
    	mobj_z,
    	mobj_snext,
    	mobj_sprev,
    	mobj_angle,
    	mobj_rollangle,
    	mobj_sprite,
    	mobj_frame,
    	mobj_sprite2,
    	mobj_anim_duration,
    	mobj_touching_sectorlist,
    	mobj_subsector,
    	mobj_floorz,
    	mobj_ceilingz,
    	mobj_floorrover,
    	mobj_ceilingrover,
    	mobj_radius,
    	mobj_height,
    	mobj_momx,
    	mobj_momy,
    	mobj_momz,
    	mobj_pmomz,
    	mobj_tics,
    	mobj_state,
    	mobj_flags,
    	mobj_flags2,
    	mobj_eflags,
    	mobj_skin,
    	mobj_color,
    	mobj_bnext,
    	mobj_bprev,
    	mobj_hnext,
    	mobj_hprev,
    	mobj_type,
    	mobj_info,
    	mobj_health,
    	mobj_movedir,
    	mobj_movecount,
    	mobj_target,
    	mobj_reactiontime,
    	mobj_threshold,
    	mobj_player,
    	mobj_lastlook,
    	mobj_spawnpoint,
    	mobj_tracer,
    	mobj_friction,
    	mobj_movefactor,
    	mobj_fuse,
    	mobj_watertop,
    	mobj_waterbottom,
    	mobj_mobjnum,
    	mobj_scale,
    	mobj_destscale,
    	mobj_scalespeed,
    	mobj_extravalue1,
    	mobj_extravalue2,
    	mobj_cusval,
    	mobj_cvmem,
    	mobj_standingslope,
    	mobj_colorized,
    	mobj_shadowscale
    };
    
    static const char *const mobj_opt[] = {
    	"valid",
    	"x",
    	"y",
    	"z",
    	"snext",
    	"sprev",
    	"angle",
    	"rollangle",
    	"sprite",
    	"frame",
    	"sprite2",
    	"anim_duration",
    	"touching_sectorlist",
    	"subsector",
    	"floorz",
    	"ceilingz",
    	"floorrover",
    	"ceilingrover",
    	"radius",
    	"height",
    	"momx",
    	"momy",
    	"momz",
    	"pmomz",
    	"tics",
    	"state",
    	"flags",
    	"flags2",
    	"eflags",
    	"skin",
    	"color",
    	"bnext",
    	"bprev",
    	"hnext",
    	"hprev",
    	"type",
    	"info",
    	"health",
    	"movedir",
    	"movecount",
    	"target",
    	"reactiontime",
    	"threshold",
    	"player",
    	"lastlook",
    	"spawnpoint",
    	"tracer",
    	"friction",
    	"movefactor",
    	"fuse",
    	"watertop",
    	"waterbottom",
    	"mobjnum",
    	"scale",
    	"destscale",
    	"scalespeed",
    	"extravalue1",
    	"extravalue2",
    	"cusval",
    	"cvmem",
    	"standingslope",
    	"colorized",
    	"shadowscale",
    	NULL};
    
    #define UNIMPLEMENTED luaL_error(L, LUA_QL("mobj_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", mobj_opt[field])
    
    static int mobj_get(lua_State *L)
    {
    	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
    	enum mobj_e field = Lua_optoption(L, 2, NULL, mobj_opt);
    	lua_settop(L, 2);
    
    	INLEVEL
    
    	if (!mo) {
    		if (field == mobj_valid) {
    			lua_pushboolean(L, 0);
    			return 1;
    		}
    		return LUA_ErrInvalid(L, "mobj_t");
    	}
    
    	switch(field)
    	{
    	case mobj_valid:
    		lua_pushboolean(L, 1);
    		break;
    	case mobj_x:
    		lua_pushfixed(L, mo->x);
    		break;
    	case mobj_y:
    		lua_pushfixed(L, mo->y);
    		break;
    	case mobj_z:
    		lua_pushfixed(L, mo->z);
    		break;
    	case mobj_snext:
    		LUA_PushUserdata(L, mo->snext, META_MOBJ);
    		break;
    	case mobj_sprev:
    		// sprev is actually the previous mobj's snext pointer,
    		// or the subsector->sector->thing_list if there is no previous mobj,
    		// i.e. it will always ultimately point to THIS mobj -- so that's actually not useful to Lua and won't be included.
    		return UNIMPLEMENTED;
    	case mobj_angle:
    		lua_pushangle(L, mo->angle);
    		break;
    	case mobj_rollangle:
    		lua_pushangle(L, mo->rollangle);
    		break;
    	case mobj_sprite:
    		lua_pushinteger(L, mo->sprite);
    		break;
    	case mobj_frame:
    		lua_pushinteger(L, mo->frame);
    		break;
    	case mobj_sprite2:
    		lua_pushinteger(L, mo->sprite2);
    		break;
    	case mobj_anim_duration:
    		lua_pushinteger(L, mo->anim_duration);
    		break;
    	case mobj_touching_sectorlist:
    		return UNIMPLEMENTED;
    	case mobj_subsector:
    		LUA_PushUserdata(L, mo->subsector, META_SUBSECTOR);
    		break;
    	case mobj_floorz:
    		lua_pushfixed(L, mo->floorz);
    		break;
    	case mobj_ceilingz:
    		lua_pushfixed(L, mo->ceilingz);
    		break;
    	case mobj_floorrover:
    		LUA_PushUserdata(L, mo->floorrover, META_FFLOOR);
    		break;
    	case mobj_ceilingrover:
    		LUA_PushUserdata(L, mo->ceilingrover, META_FFLOOR);
    		break;
    	case mobj_radius:
    		lua_pushfixed(L, mo->radius);
    		break;
    	case mobj_height:
    		lua_pushfixed(L, mo->height);
    		break;
    	case mobj_momx:
    		lua_pushfixed(L, mo->momx);
    		break;
    	case mobj_momy:
    		lua_pushfixed(L, mo->momy);
    		break;
    	case mobj_momz:
    		lua_pushfixed(L, mo->momz);
    		break;
    	case mobj_pmomz:
    		lua_pushfixed(L, mo->pmomz);
    		break;
    	case mobj_tics:
    		lua_pushinteger(L, mo->tics);
    		break;
    	case mobj_state: // state number, not struct
    		lua_pushinteger(L, mo->state-states);
    		break;
    	case mobj_flags:
    		lua_pushinteger(L, mo->flags);
    		break;
    	case mobj_flags2:
    		lua_pushinteger(L, mo->flags2);
    		break;
    	case mobj_eflags:
    		lua_pushinteger(L, mo->eflags);
    		break;
    	case mobj_skin: // skin name or nil, not struct
    		if (!mo->skin)
    			return 0;
    		lua_pushstring(L, ((skin_t *)mo->skin)->name);
    		break;
    	case mobj_color:
    		lua_pushinteger(L, mo->color);
    		break;
    	case mobj_bnext:
    		LUA_PushUserdata(L, mo->bnext, META_MOBJ);
    		break;
    	case mobj_bprev:
    		// bprev -- same deal as sprev above, but for the blockmap.
    		return UNIMPLEMENTED;
    	case mobj_hnext:
    		if (mo->hnext && P_MobjWasRemoved(mo->hnext))
    		{ // don't put invalid mobj back into Lua.
    			P_SetTarget(&mo->hnext, NULL);
    			return 0;
    		}
    		LUA_PushUserdata(L, mo->hnext, META_MOBJ);
    		break;
    	case mobj_hprev:
    		if (mo->hprev && P_MobjWasRemoved(mo->hprev))
    		{ // don't put invalid mobj back into Lua.
    			P_SetTarget(&mo->hprev, NULL);
    			return 0;
    		}
    		LUA_PushUserdata(L, mo->hprev, META_MOBJ);
    		break;
    	case mobj_type:
    		lua_pushinteger(L, mo->type);
    		break;
    	case mobj_info:
    		LUA_PushUserdata(L, &mobjinfo[mo->type], META_MOBJINFO);
    		break;
    	case mobj_health:
    		lua_pushinteger(L, mo->health);
    		break;
    	case mobj_movedir:
    		lua_pushinteger(L, mo->movedir);
    		break;
    	case mobj_movecount:
    		lua_pushinteger(L, mo->movecount);
    		break;
    	case mobj_target:
    		if (mo->target && P_MobjWasRemoved(mo->target))
    		{ // don't put invalid mobj back into Lua.
    			P_SetTarget(&mo->target, NULL);
    			return 0;
    		}
    		LUA_PushUserdata(L, mo->target, META_MOBJ);
    		break;
    	case mobj_reactiontime:
    		lua_pushinteger(L, mo->reactiontime);
    		break;
    	case mobj_threshold:
    		lua_pushinteger(L, mo->threshold);
    		break;
    	case mobj_player:
    		LUA_PushUserdata(L, mo->player, META_PLAYER);
    		break;
    	case mobj_lastlook:
    		lua_pushinteger(L, mo->lastlook);
    		break;
    	case mobj_spawnpoint:
    		LUA_PushUserdata(L, mo->spawnpoint, META_MAPTHING);
    		break;
    	case mobj_tracer:
    		if (mo->tracer && P_MobjWasRemoved(mo->tracer))
    		{ // don't put invalid mobj back into Lua.
    			P_SetTarget(&mo->tracer, NULL);
    			return 0;
    		}
    		LUA_PushUserdata(L, mo->tracer, META_MOBJ);
    		break;
    	case mobj_friction:
    		lua_pushfixed(L, mo->friction);
    		break;
    	case mobj_movefactor:
    		lua_pushfixed(L, mo->movefactor);
    		break;
    	case mobj_fuse:
    		lua_pushinteger(L, mo->fuse);
    		break;
    	case mobj_watertop:
    		lua_pushfixed(L, mo->watertop);
    		break;
    	case mobj_waterbottom:
    		lua_pushfixed(L, mo->waterbottom);
    		break;
    	case mobj_mobjnum:
    		// mobjnum is a networking thing generated for $$$.sav
    		// and therefore shouldn't be used by Lua.
    		return UNIMPLEMENTED;
    	case mobj_scale:
    		lua_pushfixed(L, mo->scale);
    		break;
    	case mobj_destscale:
    		lua_pushfixed(L, mo->destscale);
    		break;
    	case mobj_scalespeed:
    		lua_pushfixed(L, mo->scalespeed);
    		break;
    	case mobj_extravalue1:
    		lua_pushinteger(L, mo->extravalue1);
    		break;
    	case mobj_extravalue2:
    		lua_pushinteger(L, mo->extravalue2);
    		break;
    	case mobj_cusval:
    		lua_pushinteger(L, mo->cusval);
    		break;
    	case mobj_cvmem:
    		lua_pushinteger(L, mo->cvmem);
    		break;
    	case mobj_standingslope:
    		LUA_PushUserdata(L, mo->standingslope, META_SLOPE);
    		break;
    	case mobj_colorized:
    		lua_pushboolean(L, mo->colorized);
    		break;
    	case mobj_shadowscale:
    		lua_pushfixed(L, mo->shadowscale);
    		break;
    	default: // extra custom variables in Lua memory
    		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
    		I_Assert(lua_istable(L, -1));
    		lua_pushlightuserdata(L, mo);
    		lua_rawget(L, -2);
    		if (!lua_istable(L, -1)) { // no extra values table
    			CONS_Debug(DBG_LUA, M_GetText("'%s' has no extvars table or field named '%s'; returning nil.\n"), "mobj_t", lua_tostring(L, 2));
    			return 0;
    		}
    		lua_pushvalue(L, 2); // field name
    		lua_gettable(L, -2);
    		if (lua_isnil(L, -1)) // no value for this field
    			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "mobj_t", lua_tostring(L, 2));
    		break;
    	}
    	return 1;
    }
    
    #define NOSET luaL_error(L, LUA_QL("mobj_t") " field " LUA_QS " should not be set directly.", mobj_opt[field])
    #define NOSETPOS luaL_error(L, LUA_QL("mobj_t") " field " LUA_QS " should not be set directly. Use " LUA_QL("P_Move") ", " LUA_QL("P_TryMove") ", or " LUA_QL("P_TeleportMove") " instead.", mobj_opt[field])
    static int mobj_set(lua_State *L)
    {
    	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
    	enum mobj_e field = Lua_optoption(L, 2, mobj_opt[0], mobj_opt);
    	lua_settop(L, 3);
    
    	INLEVEL
    
    	if (!mo)
    		return LUA_ErrInvalid(L, "mobj_t");
    
    	if (hud_running)
    		return luaL_error(L, "Do not alter mobj_t in HUD rendering code!");
    
    	switch(field)
    	{
    	case mobj_valid:
    		return NOSET;
    	case mobj_x:
    		return NOSETPOS;
    	case mobj_y:
    		return NOSETPOS;
    	case mobj_z:
    	{
    		// z doesn't cross sector bounds so it's okay.
    		mobj_t *ptmthing = tmthing;
    		mo->z = luaL_checkfixed(L, 3);
    		P_CheckPosition(mo, mo->x, mo->y);
    		mo->floorz = tmfloorz;
    		mo->ceilingz = tmceilingz;
    		mo->floorrover = tmfloorrover;
    		mo->ceilingrover = tmceilingrover;
    		P_SetTarget(&tmthing, ptmthing);
    		break;
    	}
    	case mobj_snext:
    		return NOSETPOS;
    	case mobj_sprev:
    		return UNIMPLEMENTED;
    	case mobj_angle:
    		mo->angle = luaL_checkangle(L, 3);
    		P_SetPlayerAngle(mo->player, mo->angle);
    		break;
    	case mobj_rollangle:
    		mo->rollangle = luaL_checkangle(L, 3);
    		break;
    	case mobj_sprite:
    		mo->sprite = luaL_checkinteger(L, 3);
    		break;
    	case mobj_frame:
    		mo->frame = (UINT32)luaL_checkinteger(L, 3);
    		break;
    	case mobj_sprite2:
    		mo->sprite2 = P_GetSkinSprite2(((skin_t *)mo->skin), (UINT8)luaL_checkinteger(L, 3), mo->player);
    		break;
    	case mobj_anim_duration:
    		mo->anim_duration = (UINT16)luaL_checkinteger(L, 3);
    		break;
    	case mobj_touching_sectorlist:
    		return UNIMPLEMENTED;
    	case mobj_subsector:
    		return NOSETPOS;
    	case mobj_floorz:
    		return NOSETPOS;
    	case mobj_ceilingz:
    		return NOSETPOS;
    	case mobj_floorrover:
    		return NOSET;
    	case mobj_ceilingrover:
    		return NOSET;
    	case mobj_radius:
    	{
    		mobj_t *ptmthing = tmthing;
    		mo->radius = luaL_checkfixed(L, 3);
    		if (mo->radius < 0)
    			mo->radius = 0;
    		P_CheckPosition(mo, mo->x, mo->y);
    		mo->floorz = tmfloorz;
    		mo->ceilingz = tmceilingz;
    		mo->floorrover = tmfloorrover;
    		mo->ceilingrover = tmceilingrover;
    		P_SetTarget(&tmthing, ptmthing);
    		break;
    	}
    	case mobj_height:
    	{
    		mobj_t *ptmthing = tmthing;
    		mo->height = luaL_checkfixed(L, 3);
    		if (mo->height < 0)
    			mo->height = 0;
    		P_CheckPosition(mo, mo->x, mo->y);
    		mo->floorz = tmfloorz;
    		mo->ceilingz = tmceilingz;
    		mo->floorrover = tmfloorrover;
    		mo->ceilingrover = tmceilingrover;
    		P_SetTarget(&tmthing, ptmthing);
    		break;
    	}
    	case mobj_momx:
    		mo->momx = luaL_checkfixed(L, 3);
    		break;
    	case mobj_momy:
    		mo->momy = luaL_checkfixed(L, 3);
    		break;
    	case mobj_momz:
    		mo->momz = luaL_checkfixed(L, 3);
    		break;
    	case mobj_pmomz:
    		mo->pmomz = luaL_checkfixed(L, 3);
    		mo->eflags |= MFE_APPLYPMOMZ;
    		break;
    	case mobj_tics:
    		mo->tics = luaL_checkinteger(L, 3);
    		break;
    	case mobj_state: // set state by enum
    		if (mo->player)
    			P_SetPlayerMobjState(mo, luaL_checkinteger(L, 3));
    		else
    			P_SetMobjState(mo, luaL_checkinteger(L, 3));
    		break;
    	case mobj_flags: // special handling for MF_NOBLOCKMAP and MF_NOSECTOR
    	{
    		UINT32 flags = luaL_checkinteger(L, 3);
    		if ((flags & (MF_NOBLOCKMAP|MF_NOSECTOR)) != (mo->flags & (MF_NOBLOCKMAP|MF_NOSECTOR)))
    		{
    			P_UnsetThingPosition(mo);
    			mo->flags = flags;
    			if (flags & MF_NOSECTOR && sector_list)
    			{
    				P_DelSeclist(sector_list);
    				sector_list = NULL;
    			}
    			mo->snext = NULL, mo->sprev = NULL;
    			mo->bnext = NULL, mo->bprev = NULL;
    			P_SetThingPosition(mo);
    		}
    		else
    			mo->flags = flags;
    		break;
    	}
    	case mobj_flags2:
    		mo->flags2 = (UINT32)luaL_checkinteger(L, 3);
    		break;
    	case mobj_eflags:
    		mo->eflags = (UINT32)luaL_checkinteger(L, 3);
    		break;
    	case mobj_skin: // set skin by name
    	{
    		INT32 i;
    		char skin[SKINNAMESIZE+1]; // all skin names are limited to this length
    		strlcpy(skin, luaL_checkstring(L, 3), sizeof skin);
    		strlwr(skin); // all skin names are lowercase
    		for (i = 0; i < numskins; i++)
    			if (fastcmp(skins[i].name, skin))
    			{
    				if (!mo->player || R_SkinUsable(mo->player-players, i))
    					mo->skin = &skins[i];
    				return 0;
    			}
    		return luaL_error(L, "mobj.skin '%s' not found!", skin);
    	}
    	case mobj_color:
    	{
    		UINT8 newcolor = (UINT8)luaL_checkinteger(L,3);
    		if (newcolor >= MAXTRANSLATIONS)
    			return luaL_error(L, "mobj.color %d out of range (0 - %d).", newcolor, MAXTRANSLATIONS-1);
    		mo->color = newcolor;
    		break;
    	}
    	case mobj_bnext:
    		return NOSETPOS;
    	case mobj_bprev:
    		return UNIMPLEMENTED;
    	case mobj_hnext:
    		if (lua_isnil(L, 3))
    			P_SetTarget(&mo->hnext, NULL);
    		else
    		{
    			mobj_t *hnext = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
    			P_SetTarget(&mo->hnext, hnext);
    		}
    		break;
    	case mobj_hprev:
    		if (lua_isnil(L, 3))
    			P_SetTarget(&mo->hprev, NULL);
    		else
    		{
    			mobj_t *hprev = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
    			P_SetTarget(&mo->hprev, hprev);
    		}
    		break;
    	case mobj_type: // yeah sure, we'll let you change the mobj's type.
    	{
    		mobjtype_t newtype = luaL_checkinteger(L, 3);
    		if (newtype >= NUMMOBJTYPES)
    			return luaL_error(L, "mobj.type %d out of range (0 - %d).", newtype, NUMMOBJTYPES-1);
    		mo->type = newtype;
    		mo->info = &mobjinfo[newtype];
    		P_SetScale(mo, mo->scale);
    		break;
    	}
    	case mobj_info:
    		return NOSET;
    	case mobj_health:
    		mo->health = luaL_checkinteger(L, 3);
    		break;
    	case mobj_movedir:
    		mo->movedir = (angle_t)luaL_checkinteger(L, 3);
    		break;
    	case mobj_movecount:
    		mo->movecount = luaL_checkinteger(L, 3);
    		break;
    	case mobj_target:
    		if (lua_isnil(L, 3))
    			P_SetTarget(&mo->target, NULL);
    		else
    		{
    			mobj_t *target = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
    			P_SetTarget(&mo->target, target);
    		}
    		break;
    	case mobj_reactiontime:
    		mo->reactiontime = luaL_checkinteger(L, 3);
    		break;
    	case mobj_threshold:
    		mo->threshold = luaL_checkinteger(L, 3);
    		break;
    	case mobj_player:
    		return NOSET;
    	case mobj_lastlook:
    		mo->lastlook = luaL_checkinteger(L, 3);
    		break;
    	case mobj_spawnpoint:
    		if (lua_isnil(L, 3))
    			mo->spawnpoint = NULL;
    		else
    		{
    			mapthing_t *spawnpoint = *((mapthing_t **)luaL_checkudata(L, 3, META_MAPTHING));
    			mo->spawnpoint = spawnpoint;
    		}
    		break;
    	case mobj_tracer:
    		if (lua_isnil(L, 3))
    			P_SetTarget(&mo->tracer, NULL);
    		else
    		{
    			mobj_t *tracer = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
    			P_SetTarget(&mo->tracer, tracer);
    		}
    		break;
    	case mobj_friction:
    		mo->friction = luaL_checkfixed(L, 3);
    		break;
    	case mobj_movefactor:
    		mo->movefactor = luaL_checkfixed(L, 3);
    		break;
    	case mobj_fuse:
    		mo->fuse = luaL_checkinteger(L, 3);
    		break;
    	case mobj_watertop:
    		mo->watertop = luaL_checkfixed(L, 3);
    		break;
    	case mobj_waterbottom:
    		mo->waterbottom = luaL_checkfixed(L, 3);
    		break;
    	case mobj_mobjnum:
    		return UNIMPLEMENTED;
    	case mobj_scale:
    	{
    		fixed_t scale = luaL_checkfixed(L, 3);
    		if (scale < FRACUNIT/100)
    			scale = FRACUNIT/100;
    		mo->destscale = scale;
    		P_SetScale(mo, scale);
    		break;
    	}
    	case mobj_destscale:
    	{
    		fixed_t scale = luaL_checkfixed(L, 3);
    		if (scale < FRACUNIT/100)
    			scale = FRACUNIT/100;
    		mo->destscale = scale;
    		break;
    	}
    	case mobj_scalespeed:
    		mo->scalespeed = luaL_checkfixed(L, 3);
    		break;
    	case mobj_extravalue1:
    		mo->extravalue1 = luaL_checkinteger(L, 3);
    		break;
    	case mobj_extravalue2:
    		mo->extravalue2 = luaL_checkinteger(L, 3);
    		break;
    	case mobj_cusval:
    		mo->cusval = luaL_checkinteger(L, 3);
    		break;
    	case mobj_cvmem:
    		mo->cvmem = luaL_checkinteger(L, 3);
    		break;
    	case mobj_standingslope:
    		return NOSET;
    	case mobj_colorized:
    		mo->colorized = luaL_checkboolean(L, 3);
    		break;
    	case mobj_shadowscale:
    		mo->shadowscale = luaL_checkfixed(L, 3);
    		break;
    	default:
    		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
    		I_Assert(lua_istable(L, -1));
    		lua_pushlightuserdata(L, mo);
    		lua_rawget(L, -2);
    		if (lua_isnil(L, -1)) {
    			// This index doesn't have a table for extra values yet, let's make one.
    			lua_pop(L, 1);
    			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; adding it as Lua data.\n"), "mobj_t", lua_tostring(L, 2));
    			lua_newtable(L);
    			lua_pushlightuserdata(L, mo);
    			lua_pushvalue(L, -2); // ext value table
    			lua_rawset(L, -4); // LREG_EXTVARS table
    		}
    		lua_pushvalue(L, 2); // key
    		lua_pushvalue(L, 3); // value to store
    		lua_settable(L, -3);
    		lua_pop(L, 2);
    		break;
    	}
    	return 0;
    }
    
    #undef UNIMPLEMENTED
    #undef NOSET
    #undef NOSETPOS
    #undef NOFIELD
    
    static int mapthing_get(lua_State *L)
    {
    	mapthing_t *mt = *((mapthing_t **)luaL_checkudata(L, 1, META_MAPTHING));
    	const char *field = luaL_checkstring(L, 2);
    	lua_Integer number;
    
    	if (!mt) {
    		if (fastcmp(field,"valid")) {
    			lua_pushboolean(L, false);
    			return 1;
    		}
    		if (devparm)
    			return luaL_error(L, "accessed mapthing_t doesn't exist anymore.");
    		return 0;
    	}
    
    	if (fastcmp(field,"valid")) {
    		lua_pushboolean(L, true);
    		return 1;
    	} else if(fastcmp(field,"x"))
    		number = mt->x;
    	else if(fastcmp(field,"y"))
    		number = mt->y;
    	else if(fastcmp(field,"angle"))
    		number = mt->angle;
    	else if(fastcmp(field,"type"))
    		number = mt->type;
    	else if(fastcmp(field,"options"))
    		number = mt->options;
    	else if(fastcmp(field,"z"))
    		number = mt->z;
    	else if(fastcmp(field,"extrainfo"))
    		number = mt->extrainfo;
    	else if(fastcmp(field,"mobj")) {
    		LUA_PushUserdata(L, mt->mobj, META_MOBJ);
    		return 1;
    	} else if (devparm)
    		return luaL_error(L, LUA_QL("mapthing_t") " has no field named " LUA_QS, field);
    	else
    		return 0;
    
    	lua_pushinteger(L, number);
    	return 1;
    }
    
    static int mapthing_set(lua_State *L)
    {
    	mapthing_t *mt = *((mapthing_t **)luaL_checkudata(L, 1, META_MAPTHING));
    	const char *field = luaL_checkstring(L, 2);
    
    	if (!mt)
    		return luaL_error(L, "accessed mapthing_t doesn't exist anymore.");
    
    	if (hud_running)
    		return luaL_error(L, "Do not alter mapthing_t in HUD rendering code!");
    
    	if(fastcmp(field,"x"))
    		mt->x = (INT16)luaL_checkinteger(L, 3);
    	else if(fastcmp(field,"y"))
    		mt->y = (INT16)luaL_checkinteger(L, 3);
    	else if(fastcmp(field,"angle"))
    		mt->angle = (INT16)luaL_checkinteger(L, 3);
    	else if(fastcmp(field,"type"))
    		mt->type = (UINT16)luaL_checkinteger(L, 3);
    	else if(fastcmp(field,"options"))
    		mt->options = (UINT16)luaL_checkinteger(L, 3);
    	else if(fastcmp(field,"z"))
    		mt->z = (INT16)luaL_checkinteger(L, 3);
    	else if(fastcmp(field,"extrainfo"))
    	{
    		INT32 extrainfo = luaL_checkinteger(L, 3);
    		if (extrainfo & ~15)
    			return luaL_error(L, "mapthing_t extrainfo set %d out of range (%d - %d)", extrainfo, 0, 15);
    		mt->extrainfo = (UINT8)extrainfo;
    	}
    	else if(fastcmp(field,"mobj"))
    		mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
    	else
    		return luaL_error(L, LUA_QL("mapthing_t") " has no field named " LUA_QS, field);
    
    	return 0;
    }
    
    static int lib_iterateMapthings(lua_State *L)
    {
    	size_t i = 0;
    	INLEVEL
    	if (lua_gettop(L) < 2)
    		return luaL_error(L, "Don't call mapthings.iterate() directly, use it as 'for mapthing in mapthings.iterate do <block> end'.");
    	lua_settop(L, 2);
    	lua_remove(L, 1); // state is unused.
    	if (!lua_isnil(L, 1))
    		i = (size_t)(*((mapthing_t **)luaL_checkudata(L, 1, META_MAPTHING)) - mapthings) + 1;
    	if (i < nummapthings)
    	{
    		LUA_PushUserdata(L, &mapthings[i], META_MAPTHING);
    		return 1;
    	}
    	return 0;
    }
    
    static int lib_getMapthing(lua_State *L)
    {
    	int field;
    	INLEVEL
    	lua_settop(L, 2);
    	lua_remove(L, 1); // dummy userdata table is unused.
    	if (lua_isnumber(L, 1))
    	{
    		size_t i = lua_tointeger(L, 1);
    		if (i >= nummapthings)
    			return 0;
    		LUA_PushUserdata(L, &mapthings[i], META_MAPTHING);
    		return 1;
    	}
    	field = luaL_checkoption(L, 1, NULL, array_opt);
    	switch(field)
    	{
    	case 0: // iterate
    		lua_pushcfunction(L, lib_iterateMapthings);
    		return 1;
    	}
    	return 0;
    }
    
    static int lib_nummapthings(lua_State *L)
    {
    	lua_pushinteger(L, nummapthings);
    	return 1;
    }
    
    int LUA_MobjLib(lua_State *L)
    {
    	luaL_newmetatable(L, META_MOBJ);
    		lua_pushcfunction(L, mobj_get);
    		lua_setfield(L, -2, "__index");
    
    		lua_pushcfunction(L, mobj_set);
    		lua_setfield(L, -2, "__newindex");
    	lua_pop(L,1);
    
    	luaL_newmetatable(L, META_MAPTHING);
    		lua_pushcfunction(L, mapthing_get);
    		lua_setfield(L, -2, "__index");
    
    		lua_pushcfunction(L, mapthing_set);
    		lua_setfield(L, -2, "__newindex");
    	lua_pop(L,1);
    
    	lua_newuserdata(L, 0);
    		lua_createtable(L, 0, 2);
    			lua_pushcfunction(L, lib_getMapthing);
    			lua_setfield(L, -2, "__index");
    
    			lua_pushcfunction(L, lib_nummapthings);
    			lua_setfield(L, -2, "__len");
    		lua_setmetatable(L, -2);
    	lua_setglobal(L, "mapthings");
    	return 0;
    }