//----------------------------------------------------------------------------
//  EDGE Data Definition File Code (Things - MOBJs)
//----------------------------------------------------------------------------
// 
//  Copyright (c) 1999-2008  The EDGE Team.
// 
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; either version 2
//  of the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//----------------------------------------------------------------------------
//
// Moving Object Setup and Parser Code
//
// -ACB- 1998/08/04 Written.
// -ACB- 1998/09/12 Use DDF_MainGetFixed for fixed number references.
// -ACB- 1998/09/13 Use DDF_MainGetTime for Time count references.
// -KM- 1998/11/25 Translucency is now a fixed_t. Fixed spelling of available.
// -KM- 1998/12/16 No limit on number of ammo types.
//

#include "local.h"

#include "thing.h"

#include "src/p_action.h"


#undef  DF
#define DF  DDF_FIELD

#define DDF_MobjHashFunc(x)  (((x) + LOOKUP_CACHESIZE) % LOOKUP_CACHESIZE)

mobjtype_container_c mobjtypes;

static mobjtype_c * default_mobjtype;

void DDF_MobjGetSpecial(const char *info);
void DDF_MobjGetBenefit(const char *info, void *storage);
void DDF_MobjGetPickupEffect(const char *info, void *storage);
void DDF_MobjGetDLight(const char *info, void *storage);

static void DDF_MobjGetGlowType(const char *info, void *storage);
static void DDF_MobjGetYAlign(const char *info, void *storage);
static void DDF_MobjGetPercentRange(const char *info, void *storage);
static void DDF_MobjGetAngleRange(const char *info, void *storage);

static void AddPickupEffect(pickup_effect_c **list, pickup_effect_c *cur);

static int dlight_radius_warnings = 0;


#undef  DDF_CMD_BASE
#define DDF_CMD_BASE  dummy_dlight
static dlight_info_c dummy_dlight;

const commandlist_t dlight_commands[] =
{
	DF("TYPE",   type,   DDF_MobjGetDLight),
	DF("GRAPHIC",shape,  DDF_MainGetString),
	DF("RADIUS", radius, DDF_MainGetFloat),
	DF("COLOUR", colour, DDF_MainGetRGB),
	DF("HEIGHT", height, DDF_MainGetPercent),
	DF("LEAKY",  leaky,  DDF_MainGetBoolean),

	// backwards compatibility
	DF("INTENSITY", radius, DDF_MainGetFloat),

	DDF_CMD_END
};


#undef  DDF_CMD_BASE
#define DDF_CMD_BASE  dummy_weakness
static weakness_info_c dummy_weakness;

const commandlist_t weakness_commands[] =
{
	DF("CLASS",   classes, DDF_MainGetBitSet),
	DF("HEIGHTS", height,  DDF_MobjGetPercentRange),
	DF("ANGLES",  angle,   DDF_MobjGetAngleRange),
	DF("MULTIPLY", multiply, DDF_MainGetFloat),
	DF("PAINCHANCE", painchance, DDF_MainGetPercent),

	DDF_CMD_END
};


mobjtype_c *dynamic_mobj;

#undef  DDF_CMD_BASE
#define DDF_CMD_BASE  dummy_mobj
static mobjtype_c dummy_mobj;

const commandlist_t thing_commands[] =
{
	// sub-commands
	DDF_SUB_LIST("DLIGHT",  dlight[0], dlight_commands),
	DDF_SUB_LIST("DLIGHT2", dlight[1], dlight_commands),
	DDF_SUB_LIST("WEAKNESS", weak, weakness_commands),
	DDF_SUB_LIST("EXPLODE_DAMAGE", explode_damage, damage_commands),
	DDF_SUB_LIST("CHOKE_DAMAGE",     choke_damage, damage_commands),

	DF("SPAWNHEALTH", spawnhealth, DDF_MainGetFloat),
	DF("RADIUS", radius, DDF_MainGetFloat),
	DF("HEIGHT", height, DDF_MainGetFloat),
	DF("MASS", mass, DDF_MainGetFloat),
	DF("SPEED", speed, DDF_MainGetFloat),
	DF("FAST", fast, DDF_MainGetFloat),
	DF("EXTRA", extendedflags, DDF_MobjGetExtra),
	DF("RESPAWN_TIME", respawntime, DDF_MainGetTime),
	DF("FUSE", fuse, DDF_MainGetTime),
	DF("LIFESPAN", fuse, DDF_MainGetTime),
	DF("PALETTE_REMAP", palremap, DDF_MainGetColourmap),
	DF("TRANSLUCENCY", translucency, DDF_MainGetPercent),

	DF("INITIAL_BENEFIT", initial_benefits, DDF_MobjGetBenefit),
	DF("LOSE_BENEFIT", lose_benefits, DDF_MobjGetBenefit),
	DF("PICKUP_BENEFIT", pickup_benefits, DDF_MobjGetBenefit),
	DF("PICKUP_MESSAGE", pickup_message, DDF_MainGetString),
	DF("PICKUP_EFFECT", pickup_effects, DDF_MobjGetPickupEffect),

	DF("PAINCHANCE", painchance, DDF_MainGetPercent),
	DF("MINATTACK_CHANCE", minatkchance, DDF_MainGetPercent),
	DF("REACTION_TIME", reactiontime, DDF_MainGetTime),
	DF("JUMP_DELAY", jump_delay, DDF_MainGetTime),
	DF("JUMP_HEIGHT", jumpheight, DDF_MainGetFloat),
	DF("CROUCH_HEIGHT", crouchheight, DDF_MainGetFloat),
	DF("VIEW_HEIGHT", viewheight, DDF_MainGetPercent),
	DF("SHOT_HEIGHT", shotheight, DDF_MainGetPercent),
	DF("MAX_FALL", maxfall, DDF_MainGetFloat),
	DF("CASTORDER", castorder, DDF_MainGetNumeric),
	DF("CAST_TITLE", cast_title, DDF_MainGetString),
	DF("PLAYER", playernum, DDF_MobjGetPlayer),
	DF("SIDE", side, DDF_MainGetBitSet),
	DF("CLOSE_ATTACK", closecombat, DDF_MainRefAttack),
	DF("RANGE_ATTACK", rangeattack, DDF_MainRefAttack),
	DF("SPARE_ATTACK", spareattack, DDF_MainRefAttack),
	DF("DROPITEM", dropitem_ref, DDF_MainGetString),
	DF("BLOOD", blood_ref, DDF_MainGetString),
	DF("RESPAWN_EFFECT", respawneffect_ref, DDF_MainGetString),
	DF("SPIT_SPOT", spitspot_ref, DDF_MainGetString),

	DF("PICKUP_SOUND", activesound, DDF_MainLookupSound),
	DF("ACTIVE_SOUND", activesound, DDF_MainLookupSound),
	DF("LAUNCH_SOUND", seesound, DDF_MainLookupSound),
	DF("AMBIENT_SOUND", seesound, DDF_MainLookupSound),
	DF("SIGHTING_SOUND", seesound, DDF_MainLookupSound),
	DF("DEATH_SOUND", deathsound, DDF_MainLookupSound),
	DF("OVERKILL_SOUND", overkill_sound, DDF_MainLookupSound),
	DF("PAIN_SOUND", painsound, DDF_MainLookupSound),
	DF("STARTCOMBAT_SOUND", attacksound, DDF_MainLookupSound),
	DF("WALK_SOUND", walksound, DDF_MainLookupSound),
	DF("JUMP_SOUND", jump_sound, DDF_MainLookupSound),
	DF("NOWAY_SOUND", noway_sound, DDF_MainLookupSound),
	DF("OOF_SOUND", oof_sound, DDF_MainLookupSound),
	DF("GASP_SOUND", gasp_sound, DDF_MainLookupSound),

	DF("FLOAT_SPEED", float_speed, DDF_MainGetFloat),
	DF("STEP_SIZE", step_size, DDF_MainGetFloat),
	DF("SPRITE_SCALE", scale, DDF_MainGetFloat),
	DF("SPRITE_ASPECT", aspect, DDF_MainGetFloat),
	DF("SPRITE_YALIGN", yalign, DDF_MobjGetYAlign),   // -AJA- 2007/08/08
	DF("MODEL_SKIN", model_skin, DDF_MainGetNumeric), // -AJA- 2007/10/16
	DF("MODEL_SCALE", model_scale, DDF_MainGetFloat),
	DF("MODEL_ASPECT", model_aspect, DDF_MainGetFloat),
	DF("MODEL_BIAS", model_bias, DDF_MainGetFloat),
	DF("BOUNCE_SPEED", bounce_speed, DDF_MainGetFloat),
	DF("BOUNCE_UP", bounce_up, DDF_MainGetFloat),
	DF("SIGHT_SLOPE", sight_slope, DDF_MainGetSlope),
	DF("SIGHT_ANGLE", sight_angle, DDF_MainGetAngle),
	DF("RIDE_FRICTION", ride_friction, DDF_MainGetFloat),
	DF("BOBBING", bobbing, DDF_MainGetPercent),
	DF("IMMUNITY_CLASS", immunity, DDF_MainGetBitSet),
	DF("RESISTANCE_CLASS", resistance, DDF_MainGetBitSet),
	DF("RESISTANCE_MULTIPLY", resist_multiply, DDF_MainGetFloat),
	DF("RESISTANCE_PAINCHANCE", resist_painchance, DDF_MainGetPercent),
	DF("GHOST_CLASS", ghost, DDF_MainGetBitSet),   // -AJA- 2005/05/15
	DF("SHADOW_TRANSLUCENCY", shadow_trans, DDF_MainGetPercent),
	DF("LUNG_CAPACITY", lung_capacity, DDF_MainGetTime),
	DF("GASP_START", gasp_start, DDF_MainGetTime),
	DF("EXPLODE_RADIUS", explode_radius, DDF_MainGetFloat),
	DF("RELOAD_SHOTS", reload_shots, DDF_MainGetNumeric),  // -AJA- 2004/11/15
	DF("GLOW_TYPE", glow_type, DDF_MobjGetGlowType), // -AJA- 2007/08/19
	DF("ARMOUR_PROTECTION", armour_protect, DDF_MainGetPercent),  // -AJA- 2007/08/22
	DF("ARMOUR_DEPLETION",  armour_deplete, DDF_MainGetPercentAny),  // -AJA- 2007/08/22
	DF("ARMOUR_CLASS",  armour_class, DDF_MainGetBitSet),  // -AJA- 2007/08/22

	// -AJA- backwards compatibility cruft...
	DF("EXPLOD_DAMAGE", explode_damage.nominal, DDF_MainGetFloat),
	DF("EXPLOSION_DAMAGE", explode_damage.nominal, DDF_MainGetFloat),
	DF("EXPLOD_DAMAGERANGE", explode_damage.nominal, DDF_MainGetFloat),

	DDF_CMD_END
};

const state_starter_t thing_starters[] =
{
	DDF_STATE("SPAWN",     "IDLE",    spawn_state),
	DDF_STATE("IDLE",      "IDLE",    idle_state),
	DDF_STATE("CHASE",     "CHASE",   chase_state),
	DDF_STATE("PAIN",      "IDLE",    pain_state),
	DDF_STATE("MISSILE",   "IDLE",    missile_state),
	DDF_STATE("MELEE",     "IDLE",    melee_state),
	DDF_STATE("DEATH",     "REMOVE",  death_state),
	DDF_STATE("OVERKILL",  "REMOVE",  overkill_state),
	DDF_STATE("RESPAWN",   "IDLE",    raise_state),
	DDF_STATE("RESURRECT", "IDLE",    res_state),
	DDF_STATE("MEANDER",   "MEANDER", meander_state),
	DDF_STATE("BOUNCE",    "IDLE",    bounce_state),
	DDF_STATE("TOUCH",     "IDLE",    touch_state),
	DDF_STATE("RELOAD",    "IDLE",    reload_state),
	DDF_STATE("GIB",       "REMOVE",  gib_state),

	DDF_STATE_END
};

// -KM- 1998/11/25 Added weapon functions.
// -AJA- 1999/08/09: Moved this here from p_action.h, and added an extra
// field `handle_arg' for things like "WEAPON_SHOOT(FIREBALL)".

const actioncode_t thing_actions[] =
{
	{"NOTHING", NULL, NULL},

	{"CLOSEATTEMPTSND",   P_ActMakeCloseAttemptSound, NULL},
	{"COMBOATTACK",       P_ActComboAttack, NULL},
	{"FACETARGET",        P_ActFaceTarget, NULL},
	{"PLAYSOUND",         P_ActPlaySound, DDF_StateGetSound},
	{"KILLSOUND",         P_ActKillSound, NULL},
	{"MAKESOUND",         P_ActMakeAmbientSound, NULL},
	{"MAKEACTIVESOUND",   P_ActMakeActiveSound, NULL},
	{"MAKESOUNDRANDOM",   P_ActMakeAmbientSoundRandom, NULL},
	{"MAKEDEATHSOUND",    P_ActMakeDyingSound, NULL},
	{"MAKEDEAD",          P_ActMakeIntoCorpse, NULL},
	{"MAKEOVERKILLSOUND", P_ActMakeOverKillSound, NULL},
	{"MAKEPAINSOUND",     P_ActMakePainSound, NULL},
	{"PLAYER_SCREAM",     P_ActPlayerScream, NULL},
	{"CLOSE_ATTACK",      P_ActMeleeAttack, DDF_StateGetAttack},
	{"RANGE_ATTACK",      P_ActRangeAttack, DDF_StateGetAttack},
	{"SPARE_ATTACK",      P_ActSpareAttack, DDF_StateGetAttack},

	{"RANGEATTEMPTSND",   P_ActMakeRangeAttemptSound, NULL},
	{"REFIRE_CHECK",      P_ActRefireCheck, NULL},
	{"RELOAD_CHECK",      P_ActReloadCheck, NULL},
	{"RELOAD_RESET",      P_ActReloadReset, NULL},
	{"LOOKOUT",           P_ActStandardLook, NULL},
	{"SUPPORT_LOOKOUT",   P_ActPlayerSupportLook, NULL},
	{"CHASE",             P_ActStandardChase, NULL},
	{"RESCHASE",          P_ActResurrectChase, NULL},
	{"WALKSOUND_CHASE",   P_ActWalkSoundChase, NULL},
	{"MEANDER",           P_ActStandardMeander, NULL},
	{"SUPPORT_MEANDER",   P_ActPlayerSupportMeander, NULL},
	{"EXPLOSIONDAMAGE",   P_ActDamageExplosion, NULL},
	{"THRUST",            P_ActThrust, NULL},
	{"TRACER",            P_ActHomingProjectile, NULL},
	{"RANDOM_TRACER",     P_ActHomingProjectile, NULL},  // same as above
	{"RESET_SPREADER",    P_ActResetSpreadCount, NULL},
	{"SMOKING",           P_ActCreateSmokeTrail, NULL},
	{"TRACKERACTIVE",     P_ActTrackerActive, NULL},
	{"TRACKERFOLLOW",     P_ActTrackerFollow, NULL},
	{"TRACKERSTART",      P_ActTrackerStart, NULL},
	{"EFFECTTRACKER",     P_ActEffectTracker, NULL},
	{"CHECKBLOOD",        P_ActCheckBlood, NULL},
	{"CHECKMOVING",       P_ActCheckMoving, NULL},
	{"CHECK_ACTIVITY",    P_ActCheckActivity, NULL},
	{"JUMP",              P_ActJump, DDF_StateGetJump},
	{"BECOME",            P_ActBecome, DDF_StateGetBecome},
	{"EXPLODE",           P_ActExplode, NULL},
	{"ACTIVATE_LINETYPE", P_ActActivateLineType, DDF_StateGetIntPair},
	{"RTS_ENABLE_TAGGED", P_ActEnableRadTrig,  DDF_StateGetInteger},
	{"RTS_DISABLE_TAGGED",P_ActDisableRadTrig, DDF_StateGetInteger},
	{"TOUCHY_REARM",      P_ActTouchyRearm, NULL},
	{"TOUCHY_DISARM",     P_ActTouchyDisarm, NULL},
	{"BOUNCE_REARM",      P_ActBounceRearm, NULL},
	{"BOUNCE_DISARM",     P_ActBounceDisarm, NULL},
	{"PATH_CHECK",        P_ActPathCheck, NULL},
	{"PATH_FOLLOW",       P_ActPathFollow, NULL},
	{"SET_INVULNERABLE",  P_ActSetInvuln,   NULL},
	{"CLEAR_INVULNERABLE",P_ActClearInvuln, NULL},

	{"DROPITEM",          P_ActDropItem, DDF_StateGetMobj},
	{"SPAWN",             P_ActSpawn,    DDF_StateGetMobj},
	{"TRANS_SET",         P_ActTransSet, DDF_StateGetPercent},
	{"TRANS_FADE",        P_ActTransFade, DDF_StateGetPercent},
	{"TRANS_MORE",        P_ActTransMore, DDF_StateGetPercent},
	{"TRANS_LESS",        P_ActTransLess, DDF_StateGetPercent},
	{"TRANS_ALTERNATE",   P_ActTransAlternate, DDF_StateGetPercent},
	{"DLIGHT_SET",        P_ActDLightSet,  DDF_StateGetInteger},
	{"DLIGHT_FADE",       P_ActDLightFade, DDF_StateGetInteger},
	{"DLIGHT_RANDOM",     P_ActDLightRandom, DDF_StateGetIntPair},
	{"DLIGHT_COLOUR",     P_ActDLightColour, DDF_StateGetRGB},
	{"SET_SKIN",          P_ActSetSkin,   DDF_StateGetInteger},

    {"FACE",              P_ActFaceDir, DDF_StateGetAngle},
    {"TURN",              P_ActTurnDir, DDF_StateGetAngle},
    {"TURN_RANDOM",       P_ActTurnRandom, DDF_StateGetAngle},
	{"MLOOK_FACE",        P_ActMlookFace, DDF_StateGetSlope},
	{"MLOOK_TURN",        P_ActMlookTurn, DDF_StateGetSlope},
	{"MOVE_FWD",          P_ActMoveFwd, DDF_StateGetFloat},
	{"MOVE_RIGHT",        P_ActMoveRight, DDF_StateGetFloat},
	{"MOVE_UP",           P_ActMoveUp, DDF_StateGetFloat},
	{"STOP",              P_ActStopMoving, NULL},

	// Boom/MBF compatibility
	{"DIE",               P_ActDie, NULL},
	{"KEEN_DIE",          P_ActKeenDie, NULL},

	// bossbrain actions
	{"BRAINSPIT", P_ActBrainSpit, NULL},
	{"CUBESPAWN", P_ActCubeSpawn, NULL},
	{"CUBETRACER", P_ActHomeToSpot, NULL},
	{"BRAINSCREAM", P_ActBrainScream, NULL},
	{"BRAINMISSILEEXPLODE", P_ActBrainMissileExplode, NULL},
	{"BRAINDIE", P_ActBrainDie, NULL},

	// -AJA- backwards compatibility cruft...
	{"VARIEDEXPDAMAGE",   P_ActDamageExplosion, NULL},
	{"VARIED_THRUST",     P_ActThrust, NULL},

	{NULL, NULL, NULL}
};


const specflags_t keytype_names[] =
{
	{"BLUECARD",    KF_BlueCard,    0},
	{"YELLOWCARD",  KF_YellowCard,  0},
	{"REDCARD",     KF_RedCard,     0},
	{"GREENCARD",   KF_GreenCard,   0},

	{"BLUESKULL",   KF_BlueSkull,   0},
	{"YELLOWSKULL", KF_YellowSkull, 0},
	{"REDSKULL",    KF_RedSkull,    0},
	{"GREENSKULL",  KF_GreenSkull,  0},

	{"GOLD_KEY",    KF_GoldKey,     0},
	{"SILVER_KEY",  KF_SilverKey,   0},
	{"BRASS_KEY",   KF_BrassKey,    0},
	{"COPPER_KEY",  KF_CopperKey,   0},
	{"STEEL_KEY",   KF_SteelKey,    0},
	{"WOODEN_KEY",  KF_WoodenKey,   0},
	{"FIRE_KEY",    KF_FireKey,     0},
	{"WATER_KEY",   KF_WaterKey,    0},

	// -AJA- compatibility (this way is the easiest)
	{"KEY_BLUECARD",    KF_BlueCard,    0},
	{"KEY_YELLOWCARD",  KF_YellowCard,  0},
	{"KEY_REDCARD",     KF_RedCard,     0},
	{"KEY_GREENCARD",   KF_GreenCard,   0},

	{"KEY_BLUESKULL",   KF_BlueSkull,   0},
	{"KEY_YELLOWSKULL", KF_YellowSkull, 0},
	{"KEY_REDSKULL",    KF_RedSkull,    0},
	{"KEY_GREENSKULL",  KF_GreenSkull,  0},

	{NULL, 0, 0}
};


const specflags_t armourtype_names[] =
{
	{"GREEN_ARMOUR",  ARMOUR_Green,  0},
	{"BLUE_ARMOUR",   ARMOUR_Blue,   0},
	{"PURPLE_ARMOUR", ARMOUR_Purple, 0},
	{"YELLOW_ARMOUR", ARMOUR_Yellow, 0},
	{"RED_ARMOUR",    ARMOUR_Red,    0},
	{NULL, 0, 0}
};


const specflags_t powertype_names[] =
{
	{"POWERUP_INVULNERABLE", PW_Invulnerable, 0},
	{"POWERUP_BARE_BERSERK", PW_Berserk,      0},
	{"POWERUP_BERSERK",      PW_Berserk,      0},
	{"POWERUP_PARTINVIS",    PW_PartInvis,    0},
	{"POWERUP_ACIDSUIT",     PW_AcidSuit,     0},
	{"POWERUP_AUTOMAP",      PW_AllMap,       0},
	{"POWERUP_LIGHTGOGGLES", PW_Infrared,     0},
	{"POWERUP_JETPACK",      PW_Jetpack,      0},
	{"POWERUP_NIGHTVISION",  PW_NightVision,  0},
	{"POWERUP_SCUBA",        PW_Scuba,        0},
	{NULL, 0, 0}
};


const specflags_t simplecond_names[] =
{
	{"JUMPING",     COND_Jumping,    0},
	{"CROUCHING",   COND_Crouching,  0},
	{"SWIMMING",    COND_Swimming,   0},
	{"ATTACKING",   COND_Attacking,  0},
	{"RAMPAGING",   COND_Rampaging,  0},
	{"USING",       COND_Using,      0},
	{"ACTION1",     COND_Action1,    0},
	{"ACTION2",     COND_Action2,    0},
	{"WALKING",     COND_Walking,    0},
	{NULL, 0, 0}
};

//
// DDF_CompareName
//
// Compare two names. This is like stricmp(), except that spaces
// and underscors are ignored for comparison purposes.
//
// -AJA- 1999/09/11: written.
//
int DDF_CompareName(const char *A, const char *B)
{
	for (;;)
	{
		// Note: must skip stuff BEFORE checking for NUL
		while (*A == ' ' || *A == '_') A++;
		while (*B == ' ' || *B == '_') B++;

		if (*A == 0 && *B == 0) return 0;

		if (*A == 0) return -1;
		if (*B == 0) return +1;

		if (toupper(*A) == toupper(*B))
		{
			A++; B++; continue;
		}

		return toupper(*A) - toupper(*B);
	}
}


//
//  DDF PARSE ROUTINES
//

static void ThingStartEntry(const char *buffer, bool extend)
{
	if (!buffer || !buffer[0])
	{
		DDF_WarnError("New thing entry is missing a name!");
		buffer = "THING_WITH_NO_NAME";
	}

	std::string name(buffer);
	int number = 0;

	const char *pos = strchr(buffer, ':');

	if (pos)
	{
		name = std::string(buffer, pos - buffer);

		number = MAX(0, atoi(pos+1));

		if (name.empty())
		{
			DDF_WarnError("New thing entry is missing a name!");
			name = "THING_WITH_NO_NAME";
		}
	}

	dynamic_mobj = NULL;

	int idx = mobjtypes.FindFirst(name.c_str(), 0);

	if (idx >= 0)
	{
		mobjtypes.MoveToEnd(idx);
		dynamic_mobj = mobjtypes[mobjtypes.GetSize()-1];
	}

	if (extend)
	{
		if (! dynamic_mobj)
			DDF_Error("Unknown thing to extend: %s\n", name.c_str());

		if (number > 0)
			dynamic_mobj->number = number;

		DDF_StateBeginRange(dynamic_mobj->state_grp);
		return;
	}

	// replaces an existing entry?
	if (dynamic_mobj)
	{
		dynamic_mobj->Default();
		dynamic_mobj->number = number;
	}
	else
	{
		// not found, create a new one
		dynamic_mobj = new mobjtype_c;
		dynamic_mobj->name = name.c_str();
		dynamic_mobj->number = number;

		mobjtypes.Insert(dynamic_mobj);
	}

	DDF_StateBeginRange(dynamic_mobj->state_grp);
}


static void ThingDoTemplate(const char *contents)
{
	int idx = mobjtypes.FindFirst(contents, 0);
	if (idx < 0)
		DDF_Error("Unknown thing template: '%s'\n", contents);

	mobjtype_c *other = mobjtypes[idx];
	SYS_ASSERT(other);

	if (other == dynamic_mobj)
		DDF_Error("Bad thing template: '%s'\n", contents);

	dynamic_mobj->CopyDetail(*other);

	DDF_StateBeginRange(dynamic_mobj->state_grp);
}


void ThingParseField(const char *field, const char *contents,
					 int index, bool is_last)
{
#if (DEBUG_DDF)  
	I_Debugf("THING_PARSE: %s = %s;\n", field, contents);
#endif

	if (DDF_CompareName(field, "TEMPLATE") == 0)
	{
		ThingDoTemplate(contents);
		return;
	}

	// -AJA- this needs special handling (it touches several fields)
	if (DDF_CompareName(field, "SPECIAL") == 0 ||
	    DDF_CompareName(field, "PROJECTILE_SPECIAL") == 0)
	{
		DDF_MobjGetSpecial(contents);
		return;
	}

	if (DDF_MainParseField(thing_commands, field, contents, (byte *)dynamic_mobj))
		return;

	if (DDF_MainParseState((byte *)dynamic_mobj, dynamic_mobj->state_grp,
	                       field, contents, index, is_last, false /* is_weapon */,
						   thing_starters, thing_actions))
		return;


	DDF_WarnError("Unknown thing/attack command: %s\n", field);
}


static void ThingFinishEntry(void)
{
	DDF_StateFinishRange(dynamic_mobj->state_grp);

	// count-as-kill things are automatically monsters
	if (dynamic_mobj->flags & MF_COUNTKILL)
		dynamic_mobj->extendedflags |= EF_MONSTER;

	// countable items are always pick-up-able
	if (dynamic_mobj->flags & MF_COUNTITEM)
		dynamic_mobj->hyperflags |= HF_FORCEPICKUP;

	// shootable things are always pushable
	if (dynamic_mobj->flags & MF_SHOOTABLE)
		dynamic_mobj->hyperflags |= HF_PUSHABLE;

	// check stuff...

	if (dynamic_mobj->mass < 1)
	{
		DDF_WarnError("Bad MASS value %f in DDF.\n", dynamic_mobj->mass);
		dynamic_mobj->mass = 1;
	}

	// check CAST stuff
	if (dynamic_mobj->castorder > 0)
	{
		if (! dynamic_mobj->chase_state)
			DDF_Error("Cast object must have CHASE states !\n");

		if (! dynamic_mobj->death_state)
			DDF_Error("Cast object must have DEATH states !\n");
	}

	// check DAMAGE stuff
	if (dynamic_mobj->explode_damage.nominal < 0)
	{
		DDF_WarnError("Bad EXPLODE_DAMAGE.VAL value %f in DDF.\n",
			dynamic_mobj->explode_damage.nominal);
	}

	if (dynamic_mobj->explode_radius < 0)
	{
		DDF_Error("Bad EXPLODE_RADIUS value %f in DDF.\n",
			dynamic_mobj->explode_radius);
	}

	if (dynamic_mobj->reload_shots <= 0)
	{
		DDF_Error("Bad RELOAD_SHOTS value %d in DDF.\n",
			dynamic_mobj->reload_shots);
	}

	if (dynamic_mobj->choke_damage.nominal < 0)
	{
		DDF_WarnError("Bad CHOKE_DAMAGE.VAL value %f in DDF.\n",
			dynamic_mobj->choke_damage.nominal);
	}

	if (dynamic_mobj->model_skin < 0 || dynamic_mobj->model_skin > 9)
		DDF_Error("Bad MODEL_SKIN value %d in DDF (must be 0-9).\n",
			dynamic_mobj->model_skin);

    if (dynamic_mobj->dlight[0].radius > 512)
	{
		if (dlight_radius_warnings < 3)
			DDF_Warning("DLIGHT_RADIUS value %1.1f too large (over 512).\n",
				dynamic_mobj->dlight[0].radius);
		else if (dlight_radius_warnings == 3)
			I_Warning("More too large DLIGHT_RADIUS values found....\n");

		dlight_radius_warnings++;
	}

	// FIXME: check more stuff

	// backwards compatibility:
	if (!dynamic_mobj->idle_state && dynamic_mobj->spawn_state)
		dynamic_mobj->idle_state = dynamic_mobj->spawn_state;

	dynamic_mobj->DLightCompatibility();
}


static void ThingClearAll(void)
{
	I_Warning("Ignoring #CLEARALL in things.ddf\n");
}


bool DDF_ReadThings(void *data, int size)
{
	readinfo_t things;

	things.memfile = (char*)data;
	things.memsize = size;
	things.tag = "THINGS";
	things.entries_per_dot = 6;

	if (things.memfile)
	{
		things.message = NULL;
		things.filename = NULL;
		things.lumpname = "DDFTHING";
	}
	else
	{
		things.message = "DDF_InitThings";
		things.filename = "things.ddf";
		things.lumpname = NULL;
	}

	things.start_entry  = ThingStartEntry;
	things.parse_field  = ThingParseField;
	things.finish_entry = ThingFinishEntry;
	things.clear_all    = ThingClearAll;

	return DDF_MainReadFile(&things);
}

void DDF_MobjInit(void)
{
	mobjtypes.Clear();

	default_mobjtype = new mobjtype_c();
	default_mobjtype->name   = "__DEFAULT_MOBJ";
	default_mobjtype->number = 0;
}

void DDF_MobjCleanUp(void)
{
	epi::array_iterator_c it;
	mobjtype_c *m;

	// lookup references
	for (it = mobjtypes.GetIterator(0); it.IsValid(); it++)
	{
		m = ITERATOR_TO_TYPE(it, mobjtype_c*);

		cur_ddf_entryname = epi::STR_Format("[%s]  (things.ddf)", m->name.c_str());

		m->dropitem = m->dropitem_ref ? mobjtypes.Lookup(m->dropitem_ref) : NULL;
		m->blood = m->blood_ref ? mobjtypes.Lookup(m->blood_ref) : mobjtypes.Lookup("BLOOD");

		m->respawneffect = m->respawneffect_ref ? 
			mobjtypes.Lookup(m->respawneffect_ref) :
			(m->flags & MF_SPECIAL) ? mobjtypes.Lookup("ITEM_RESPAWN") 
				                    : mobjtypes.Lookup("RESPAWN_FLASH");

		m->spitspot = m->spitspot_ref ? mobjtypes.Lookup(m->spitspot_ref) : NULL;

		cur_ddf_entryname.clear();
	}

	mobjtypes.Trim();
}

//
// ParseBenefitString
//
// Parses a string like "HEALTH(20:100)".  Returns the number of
// number parameters (0, 1 or 2).  If the brackets are missing, an
// error occurs.  If the numbers cannot be parsed, then 0 is returned
// and the param buffer contains the stuff in brackets (normally the
// param string will be empty).   FIXME: this interface is fucked.
//
static int ParseBenefitString(const char *info, char *name, char *param,
							  float *value, float *limit)
{
	int len = strlen(info);

	const char *pos = strchr(info, '(');

	param[0] = 0;

	// do we have matched parentheses ?
	if (pos && len >= 4 && info[len - 1] == ')')
	{
		int len2 = (pos - info);

		Z_StrNCpy(name, info, len2);

		len -= len2 + 2;
		Z_StrNCpy(param, pos + 1, len);

		switch (sscanf(param, " %f : %f ", value, limit))
		{
			case 0: return 0;

			case 1: param[0] = 0; return 1;
			case 2: param[0] = 0; return 2;

			default:
				DDF_WarnError("Bad value in benefit string: %s\n", info);
				return -1;
		}
	}
	else if (pos)
	{
		DDF_WarnError("Malformed benefit string: %s\n", info);
		return -1;
	}

	strcpy(name, info);
	return 0;
}

//
//  BENEFIT TESTERS
//
//  These return true if the name matches that particular type of
//  benefit (e.g. "ROCKET" for ammo), and then adjusts the benefit
//  according to how many number values there were.  Otherwise returns
//  false.
//

static bool BenefitTryAmmo(const char *name, benefit_t *be,
								int num_vals)
{
	if (CHKF_Positive != DDF_MainCheckSpecialFlag(name, ammo_types, 
		&be->sub.type, false, false))
	{
		return false;
	}

	be->type = BENEFIT_Ammo;

	if ((ammotype_e)be->sub.type == AM_NoAmmo)
	{
		DDF_WarnError("Illegal ammo benefit: %s\n", name);
		return false;
	}

	if (num_vals < 1)
	{
		DDF_WarnError("Ammo benefit used, but amount is missing.\n");
		return false;
	}

	if (num_vals < 2)
	{
		be->limit = be->amount;
	}

	return true;
}

static bool BenefitTryAmmoLimit(const char *name, benefit_t *be,
									 int num_vals)
{
	char namebuf[200];
	int len = strlen(name);

	// check for ".LIMIT" prefix

	if (len < 7 || DDF_CompareName(name+len-6, ".LIMIT") != 0)
		return false;

	len -= 6;
	Z_StrNCpy(namebuf, name, len);

	if (CHKF_Positive != DDF_MainCheckSpecialFlag(namebuf, ammo_types, 
		&be->sub.type, false, false))
	{
		return false;
	}

	be->type = BENEFIT_AmmoLimit;
	be->limit = 0;

	if (be->sub.type == AM_NoAmmo)
	{
		DDF_WarnError("Illegal ammolimit benefit: %s\n", name);
		return false;
	}

	if (num_vals < 1)
	{
		DDF_WarnError("AmmoLimit benefit used, but amount is missing.\n");
		return false;
	}

	if (num_vals > 1)
	{
		DDF_WarnError("AmmoLimit benefit cannot have a limit value.\n");
		return false;
	}

	return true;
}

static bool BenefitTryWeapon(const char *name, benefit_t *be,
								  int num_vals)
{
	int idx = weapondefs.FindFirst(name, 0);

	if (idx < 0)
		return false;

	be->sub.weap = weapondefs[idx];

	be->type = BENEFIT_Weapon;
	be->limit = 1.0f;

	if (num_vals < 1)
		be->amount = 1.0f;
	else if (be->amount != 0.0f && be->amount != 1.0f)
	{
		DDF_WarnError("Weapon benefit used, bad amount value: %1.1f\n", be->amount);
		return false;
	}

	if (num_vals > 1)
	{
		DDF_WarnError("Weapon benefit cannot have a limit value.\n");
		return false;
	}

	return true;
}

static bool BenefitTryKey(const char *name, benefit_t *be,
							   int num_vals)
{
	if (CHKF_Positive != DDF_MainCheckSpecialFlag(name, keytype_names, 
		&be->sub.type, false, false))
	{
		return false;
	}

	be->type = BENEFIT_Key;
	be->limit = 1.0f;

	if (num_vals < 1)
		be->amount = 1.0f;
	else if (be->amount != 0.0f && be->amount != 1.0f)
	{
		DDF_WarnError("Key benefit used, bad amount value: %1.1f\n", be->amount);
		return false;
	}

	if (num_vals > 1)
	{
		DDF_WarnError("Key benefit cannot have a limit value.\n");
		return false;
	}

	return true;
}

static bool BenefitTryHealth(const char *name, benefit_t *be,
								  int num_vals)
{
	if (DDF_CompareName(name, "HEALTH") != 0)
		return false;

	be->type = BENEFIT_Health;
	be->sub.type = 0;

	if (num_vals < 1)
	{
		DDF_WarnError("Health benefit used, but amount is missing.\n");
		return false;
	}

	if (num_vals < 2)
		be->limit = 100.0f;

	return true;
}

static bool BenefitTryArmour(const char *name, benefit_t *be,
								  int num_vals)
{
	if (CHKF_Positive != DDF_MainCheckSpecialFlag(name, armourtype_names, 
		&be->sub.type, false, false))
	{
		return false;
	}

	be->type = BENEFIT_Armour;

	if (num_vals < 1)
	{
		DDF_WarnError("Armour benefit used, but amount is missing.\n");
		return false;
	}

	if (num_vals < 2)
	{
		switch (be->sub.type)
		{
			case ARMOUR_Green:  be->limit = 100; break;
			case ARMOUR_Blue:   be->limit = 200; break;
			case ARMOUR_Purple: be->limit = 200; break;
			case ARMOUR_Yellow: be->limit = 200; break;
			case ARMOUR_Red:    be->limit = 200; break;
			default: ;
		}
	}

	return true;
}

static bool BenefitTryPowerup(const char *name, benefit_t *be, int num_vals)
{
	if (CHKF_Positive != DDF_MainCheckSpecialFlag(name, powertype_names, 
		&be->sub.type, false, false))
	{
		return false;
	}

	be->type = BENEFIT_Powerup;

	if (num_vals < 1)
		be->amount = 999999.0f;

	if (num_vals < 2)
		be->limit = 999999.0f;

	// -AJA- backwards compatibility (need Fist for Berserk)
	if (be->sub.type == PW_Berserk &&
		DDF_CompareName(name, "POWERUP_BERSERK") == 0)
	{
		int idx = weapondefs.FindFirst("FIST", 0);

		if (idx >= 0)
		{
			AddPickupEffect(&dynamic_mobj->pickup_effects,
				new pickup_effect_c(PUFX_SwitchWeapon, weapondefs[idx], 0, 0));

			AddPickupEffect(&dynamic_mobj->pickup_effects,
				new pickup_effect_c(PUFX_KeepPowerup, PW_Berserk, 0, 0));
		}
	}

	return true;
}

static void BenefitAdd(benefit_t **list, benefit_t *source)
{
	benefit_t *cur, *tail;

	// check if this benefit overrides a previous one
	for (cur=(*list); cur; cur=cur->next)
	{
		if (cur->type == BENEFIT_Weapon)
			continue;

		if (cur->type == source->type && cur->sub.type == source->sub.type)
		{
			cur->amount = source->amount;
			cur->limit  = source->limit;
			return;
		}
	}

	// nope, create a new one and link it onto the _TAIL_
	cur = new benefit_t;

	cur[0] = source[0];
	cur->next = NULL;

	if ((*list) == NULL)
	{
		(*list) = cur;
		return;
	}

	for (tail = (*list); tail && tail->next; tail=tail->next)
	{ }

	tail->next = cur;
}

//
// DDF_MobjGetBenefit
//
// Parse a single benefit and update the benefit list accordingly.  If
// the type/subtype are not in the list, add a new entry, otherwise
// just modify the existing entry.
//
void DDF_MobjGetBenefit(const char *info, void *storage)
{
	char namebuf[200];
	char parambuf[200];
	int num_vals;

	benefit_t temp;

	SYS_ASSERT(storage);

	num_vals = ParseBenefitString(info, namebuf, parambuf, &temp.amount, &temp.limit);

	// an error occurred ?
	if (num_vals < 0)
		return;

	if (BenefitTryAmmo(namebuf, &temp, num_vals) ||
		BenefitTryAmmoLimit(namebuf, &temp, num_vals) ||
		BenefitTryWeapon(namebuf, &temp, num_vals) ||
		BenefitTryKey(namebuf, &temp, num_vals) ||
		BenefitTryHealth(namebuf, &temp, num_vals) ||
		BenefitTryArmour(namebuf, &temp, num_vals) ||
		BenefitTryPowerup(namebuf, &temp, num_vals))
	{
		BenefitAdd((benefit_t **) storage, &temp);
		return;
	}

	DDF_WarnError("Unknown/Malformed benefit type: %s\n", namebuf);
}

pickup_effect_c::pickup_effect_c(pickup_effect_type_e _type,
	int _sub, int _slot, float _time) :
		next(NULL), type(_type), slot(_slot), time(_time)
{
	sub.type = _sub;
}

pickup_effect_c::pickup_effect_c(pickup_effect_type_e _type,
	weapondef_c *_weap, int _slot, float _time) :
		next(NULL), type(_type), slot(_slot), time(_time)
{
	sub.weap = _weap;
}

static void AddPickupEffect(pickup_effect_c **list, pickup_effect_c *cur)
{
	cur->next = NULL;

	if ((*list) == NULL)
	{
		(*list) = cur;
		return;
	}

	pickup_effect_c *tail;

	for (tail = (*list); tail && tail->next; tail=tail->next)
	{ }

	tail->next = cur;
}

void BA_ParsePowerupEffect(pickup_effect_c **list,
	int pnum, float par1, float par2, const char *word_par)
{
	int p_up = (int)par1;
	int slot = (int)par2;

	SYS_ASSERT(0 <= p_up && p_up < NUMPOWERS);

	if (slot < 0 || slot >= NUM_FX_SLOT)
		DDF_Error("POWERUP_EFFECT: bad FX slot #%d\n", p_up);

	AddPickupEffect(list, new pickup_effect_c(PUFX_PowerupEffect, p_up, slot, 0));
}

void BA_ParseScreenEffect(pickup_effect_c **list,
	int pnum, float par1, float par2, const char *word_par)
{
	int slot = (int)par1;

	if (slot < 0 || slot >= NUM_FX_SLOT)
		DDF_Error("SCREEN_EFFECT: bad FX slot #%d\n", slot);

	if (par2 <= 0)
		DDF_Error("SCREEN_EFFECT: bad time value: %1.2f\n", par2);

	AddPickupEffect(list, new pickup_effect_c(PUFX_ScreenEffect, 0, slot, par2));
}

void BA_ParseSwitchWeapon(pickup_effect_c **list,
	int pnum, float par1, float par2, const char *word_par)
{
	if (pnum != -1)
		DDF_Error("SWITCH_WEAPON: missing weapon name !\n");

	SYS_ASSERT(word_par && word_par[0]);

	weapondef_c *weap = weapondefs.Lookup(word_par);

	AddPickupEffect(list, new pickup_effect_c(PUFX_SwitchWeapon, weap, 0, 0));
}

void BA_ParseKeepPowerup(pickup_effect_c **list,
	int pnum, float par1, float par2, const char *word_par)
{
	if (pnum != -1)
		DDF_Error("KEEP_POWERUP: missing powerup name !\n");

	SYS_ASSERT(word_par && word_par[0]);

	if (DDF_CompareName(word_par, "BERSERK") != 0)
		DDF_Error("KEEP_POWERUP: %s is not supported\n", word_par);

	AddPickupEffect(list, new pickup_effect_c(PUFX_KeepPowerup, PW_Berserk, 0, 0));
}

typedef struct pick_fx_parser_s
{
	const char *name;
	int num_pars;  // -1 means a single word
	void (* parser)(pickup_effect_c **list,
			int pnum, float par1, float par2, const char *word_par);
}
pick_fx_parser_t;

static const pick_fx_parser_t pick_fx_parsers[] =
{
	{ "SCREEN_EFFECT",  2, BA_ParseScreenEffect },
	{ "SWITCH_WEAPON", -1, BA_ParseSwitchWeapon },
	{ "KEEP_POWERUP",  -1, BA_ParseKeepPowerup },

	// that's all, folks.
	{ NULL, 0, NULL }
};

//
// DDF_MobjGetPickupEffect
//
// Parse a single effect and add it to the effect list accordingly.
// No merging is done.
//
void DDF_MobjGetPickupEffect(const char *info, void *storage)
{
	char namebuf[200];
	char parambuf[200];
	int num_vals;

	SYS_ASSERT(storage);

	pickup_effect_c **fx_list = (pickup_effect_c **) storage;

	benefit_t temp; // FIXME kludge (write new parser method ?)

	num_vals = ParseBenefitString(info, namebuf, parambuf, &temp.amount, &temp.limit);

	// an error occurred ?
	if (num_vals < 0)
		return;

	if (parambuf[0])
		num_vals = -1;

	for (int i=0; pick_fx_parsers[i].name; i++)
	{
		if (DDF_CompareName(pick_fx_parsers[i].name, namebuf) != 0)
			continue;

		(* pick_fx_parsers[i].parser)(fx_list, num_vals, 
			temp.amount, temp.limit, parambuf);

		return;
	}

	// secondly, try the powerups
	for (int p=0; powertype_names[p].name; p++)
	{
		if (DDF_CompareName(powertype_names[p].name, namebuf) != 0)
			continue;

		BA_ParsePowerupEffect(fx_list, num_vals, p, temp.amount, parambuf);

		return;
	}

	DDF_Error("Unknown/Malformed benefit effect: %s\n", namebuf);
}

// -KM- 1998/11/25 Translucency to fractional.
// -KM- 1998/12/16 Added individual flags for all.
// -AJA- 2000/02/02: Split into two lists.

static const specflags_t normal_specials[] =
{
	{"AMBUSH", MF_AMBUSH, 0},
	{"FUZZY", MF_FUZZY, 0},
	{"SOLID", MF_SOLID, 0},
	{"ON_CEILING", MF_SPAWNCEILING + MF_NOGRAVITY, 0},
	{"FLOATER", MF_FLOAT + MF_NOGRAVITY, 0},
	{"INERT", MF_NOBLOCKMAP, 0},
	{"TELEPORT_TYPE", MF_NOGRAVITY, 0},
	{"LINKS", MF_NOBLOCKMAP + MF_NOSECTOR, 1},
	{"DAMAGESMOKE", MF_NOBLOOD, 0},
	{"SHOOTABLE", MF_SHOOTABLE, 0},
	{"COUNT_AS_KILL", MF_COUNTKILL, 0},
	{"COUNT_AS_ITEM", MF_COUNTITEM, 0},
	{"SKULLFLY", MF_SKULLFLY, 0},
	{"SPECIAL", MF_SPECIAL, 0},
	{"SECTOR", MF_NOSECTOR, 1},
	{"BLOCKMAP", MF_NOBLOCKMAP, 1},
	{"SPAWNCEILING", MF_SPAWNCEILING, 0},
	{"GRAVITY", MF_NOGRAVITY, 1},
	{"DROPOFF", MF_DROPOFF, 0},
	{"PICKUP", MF_PICKUP, 0},
	{"CLIP", MF_NOCLIP, 1},
	{"SLIDER", MF_SLIDE, 0},
	{"FLOAT", MF_FLOAT, 0},
	{"TELEPORT", MF_TELEPORT, 0},
	{"MISSILE", MF_MISSILE, 0},   // has a special check
	{"BARE_MISSILE", MF_MISSILE, 0},
	{"DROPPED", MF_DROPPED, 0},
	{"CORPSE", MF_CORPSE, 0},
	{"STEALTH", MF_STEALTH, 0},
	{"DEATHMATCH", MF_NOTDMATCH, 1},
	{"TOUCHY", MF_TOUCHY, 0},
	{NULL, 0, 0}
};

static specflags_t extended_specials[] =
{
	{"RESPAWN", EF_NORESPAWN, 1},
	{"RESURRECT", EF_NORESURRECT, 1},
	{"DISLOYAL", EF_DISLOYALTYPE, 0},
	{"TRIGGER_HAPPY", EF_TRIGGERHAPPY, 0},
	{"ATTACK_HURTS", EF_OWNATTACKHURTS, 0},
	{"EXPLODE_IMMUNE", EF_EXPLODEIMMUNE, 0},
	{"ALWAYS_LOUD", EF_ALWAYSLOUD, 0},
	{"BOSSMAN", EF_EXPLODEIMMUNE + EF_ALWAYSLOUD, 0},
	{"NEVERTARGETED", EF_NEVERTARGET, 0},
	{"GRAV_KILL", EF_NOGRAVKILL, 1},
	{"GRUDGE", EF_NOGRUDGE, 1},
	{"BOUNCE", EF_BOUNCE, 0},
	{"EDGEWALKER", EF_EDGEWALKER, 0},
	{"GRAVFALL", EF_GRAVFALL, 0},
	{"CLIMBABLE", EF_CLIMBABLE, 0},
	{"WATERWALKER", EF_WATERWALKER, 0},
	{"MONSTER", EF_MONSTER, 0},
	{"CROSSLINES", EF_CROSSLINES, 0},
	{"FRICTION", EF_NOFRICTION, 1},
	{"USABLE", EF_USABLE, 0},
	{"BLOCK_SHOTS", EF_BLOCKSHOTS, 0},
	{"TUNNEL", EF_TUNNEL, 0},
	{"SIMPLE_ARMOUR", EF_SIMPLEARMOUR, 0},
	{NULL, 0, 0}
};

static specflags_t hyper_specials[] =
{
	{"FORCE_PICKUP", HF_FORCEPICKUP, 0},
	{"SIDE_IMMUNE", HF_SIDEIMMUNE, 0},
	{"SIDE_GHOST", HF_SIDEGHOST, 0},
	{"ULTRA_LOYAL", HF_ULTRALOYAL, 0},
	{"ZBUFFER", HF_NOZBUFFER, 1},
	{"HOVER", HF_HOVER, 0},
	{"PUSHABLE", HF_PUSHABLE, 0},
	{"POINT_FORCE", HF_POINT_FORCE, 0},
	{"PASS_MISSILE", HF_PASSMISSILE, 0},
	{"INVULNERABLE", HF_INVULNERABLE, 0},
	{"VAMPIRE", HF_VAMPIRE, 0},
	{"AUTOAIM", HF_NO_AUTOAIM, 1},
	{"TILT", HF_TILT, 0},
	{NULL, 0, 0}
};

//
// DDF_MobjGetSpecial
//
// Compares info the the entries in special flag lists.
// If found, apply attributes for it to current mobj.
//
void DDF_MobjGetSpecial(const char *info)
{
	int flag_value;

	// handle the "INVISIBLE" tag
	if (DDF_CompareName(info, "INVISIBLE") == 0)
	{
		dynamic_mobj->translucency = PERCENT_MAKE(0);
		return;
	}

	// handle the "NOSHADOW" tag
	if (DDF_CompareName(info, "NOSHADOW") == 0)
	{
		dynamic_mobj->shadow_trans = PERCENT_MAKE(0);
		return;
	}

	// the "MISSILE" tag needs special treatment, since it sets both
	// normal flags & extended flags.
	if (DDF_CompareName(info, "MISSILE") == 0)
	{
		dynamic_mobj->flags |= MF_MISSILE;
		dynamic_mobj->extendedflags |= EF_CROSSLINES | EF_NOFRICTION;
		return;
	}

	int *flag_ptr = &dynamic_mobj->flags; 

	checkflag_result_e res =
		DDF_MainCheckSpecialFlag(info, normal_specials,
			&flag_value, true, false);

	if (res == CHKF_User || res == CHKF_Unknown)
	{
		// wasn't a normal special.  Try the extended ones...
		flag_ptr = &dynamic_mobj->extendedflags;

		res = DDF_MainCheckSpecialFlag(info, extended_specials,
				&flag_value, true, false);
	}

	if (res == CHKF_User || res == CHKF_Unknown)
	{
		// -AJA- 2004/08/25: Try the hyper specials...
		flag_ptr = &dynamic_mobj->hyperflags;

		res = DDF_MainCheckSpecialFlag(info, hyper_specials,
				&flag_value, true, false);
	}

	switch (res)
	{
		case CHKF_Positive:
			*flag_ptr |= flag_value;
			break;

		case CHKF_Negative:
			*flag_ptr &= ~flag_value;
			break;

		case CHKF_User:
		case CHKF_Unknown:
			DDF_WarnError("DDF_MobjGetSpecial: Unknown special '%s'\n", info);
			break;
	}
}

static specflags_t dlight_type_names[] =
{
	{"NONE",      DLITE_None, 0},
	{"MODULATE",  DLITE_Modulate, 0},
	{"ADD",       DLITE_Add, 0},

	// backwards compatibility
	{"LINEAR",    DLITE_Compat_LIN, 0},
	{"QUADRATIC", DLITE_Compat_QUAD, 0},
	{"CONSTANT",  DLITE_Compat_LIN, 0},

	{NULL, 0, 0}
};

//
// DDF_MobjGetDLight
//
void DDF_MobjGetDLight(const char *info, void *storage)
{
	dlight_type_e *dtype = (dlight_type_e *)storage;
	int flag_value;

	SYS_ASSERT(dtype);

	if (CHKF_Positive != DDF_MainCheckSpecialFlag(info, 
		dlight_type_names, &flag_value, false, false))
	{
		DDF_WarnError("Unknown dlight type '%s'\n", info);
		return;
	}

	(*dtype) = (dlight_type_e)flag_value;
}

//
// DDF_MobjGetExtra
//
void DDF_MobjGetExtra(const char *info, void *storage)
{
	int *extendedflags = (int *)storage;

	// If keyword is "NULL", then the mobj is not marked as extra.
	// Otherwise it is.

	if (DDF_CompareName(info, "NULL") == 0)
	{
		*extendedflags &= ~EF_EXTRA;
	}
	else
	{
		*extendedflags |= EF_EXTRA;
	}
}

//
// DDF_MobjGetPlayer
//
// Reads player number and makes sure that maxplayer is large enough.
//
void DDF_MobjGetPlayer(const char *info, void *storage)
{
	int *dest = (int *)storage;

	DDF_MainGetNumeric(info, storage);

	if (*dest > 32)
		DDF_Warning("Player number '%d' will not work.", *dest);
}


static const specflags_t glow_type_names[] =
{
	{"FLOOR",   GLOW_Floor,   0},
	{"CEILING", GLOW_Ceiling, 0},
	{"WALL",    GLOW_Wall,    0},

	{NULL, 0, 0}
};

static void DDF_MobjGetGlowType(const char *info, void *storage)
{
	if (CHKF_Positive != DDF_MainCheckSpecialFlag(info,
		glow_type_names, (int *) storage, false, false))
	{
		DDF_WarnError("DDF_MobjGetGlowType: Unknown glow type: %s\n", info);
	}
}


static const specflags_t sprite_yalign_names[] =
{
	{"BOTTOM", SPYA_BottomUp, 0},
	{"MIDDLE", SPYA_Middle,   0},
	{"TOP",    SPYA_TopDown,  0},

	{NULL, 0, 0}
};

static void DDF_MobjGetYAlign(const char *info, void *storage)
{
	if (CHKF_Positive != DDF_MainCheckSpecialFlag(info,
		sprite_yalign_names, (int *) storage, false, false))
	{
		DDF_WarnError("DDF_MobjGetYAlign: Unknown alignment: %s\n", info);
	}
}

static void DDF_MobjGetPercentRange(const char *info, void *storage)
{
	SYS_ASSERT(info && storage);

	float *dest = (float *)storage;

	if (sscanf(info, "%f%%:%f%%", dest+0, dest+1) != 2)
		DDF_Error("Bad percentage range: %s\n", info);

	dest[0] /= 100.0f;
	dest[1] /= 100.0f;

	if (dest[0] > dest[1])
		DDF_Error("Bad percent range (low > high) : %s\n", info);
}

static void DDF_MobjGetAngleRange(const char *info, void *storage)
{
	SYS_ASSERT(info && storage);

	angle_t *dest = (angle_t *)storage;

	float val1, val2;

	if (sscanf(info, "%f:%f", &val1, &val2) != 2)
		DDF_Error("Bad angle range: %s\n", info);

	if ((int) val1 == 360)
		val1 = 359.5;
	else if (val1 > 360.0f)
		DDF_Error("Angle '%1.1f' too large (must be less than 360)\n", val1);

	if ((int) val2 == 360)
		val2 = 359.5;
	else if (val2 > 360.0f)
		DDF_Error("Angle '%1.1f' too large (must be less than 360)\n", val2);

	dest[0] = FLOAT_2_ANG(val1);
	dest[1] = FLOAT_2_ANG(val2);
}


//
//  CONDITION TESTERS
//
//  These return true if the name matches that particular type of
//  condition (e.g. "ROCKET" for ammo), and adjusts the condition
//  accodingly.  Otherwise returns false.
//

static bool ConditionTryAmmo(const char *name, const char *sub,
								  condition_check_t *cond)
{
	if (CHKF_Positive != DDF_MainCheckSpecialFlag(name, ammo_types, 
		&cond->sub.type, false, false))
	{
		return false;
	}

	if ((ammotype_e)cond->sub.type == AM_NoAmmo)
	{
		DDF_WarnError("Illegal ammo in condition: %s\n", name);
		return false;
	}

	if (sub[0])
		sscanf(sub, " %f ", &cond->amount);

	cond->cond_type = COND_Ammo;
	return true;
}

static bool ConditionTryWeapon(const char *name, const char *sub,
									condition_check_t *cond)
{
	int idx = weapondefs.FindFirst(name, 0);

	if (idx < 0)
		return false;

	cond->sub.weap = weapondefs[idx];

	cond->cond_type = COND_Weapon;
	return true;
}

static bool ConditionTryKey(const char *name, const char *sub,
								 condition_check_t *cond)
{
	if (CHKF_Positive != DDF_MainCheckSpecialFlag(name, keytype_names, 
		&cond->sub.type, false, false))
	{
		return false;
	}

	cond->cond_type = COND_Key;
	return true;
}

static bool ConditionTryHealth(const char *name, const char *sub,
									condition_check_t *cond)
{
	if (DDF_CompareName(name, "HEALTH") != 0)
		return false;

	if (sub[0])
		sscanf(sub, " %f ", &cond->amount);

	cond->cond_type = COND_Health;
	return true;
}

static bool ConditionTryArmour(const char *name, const char *sub,
									condition_check_t *cond)
{
	if (DDF_CompareName(name, "ARMOUR") == 0)
	{
		cond->sub.type = ARMOUR_Total;
	}
	else if (CHKF_Positive != DDF_MainCheckSpecialFlag(name, armourtype_names, 
		&cond->sub.type, false, false))
	{
		return false;
	}

	if (sub[0])
		sscanf(sub, " %f ", &cond->amount);

	cond->cond_type = COND_Armour;
	return true;
}

static bool ConditionTryPowerup(const char *name, const char *sub,
									 condition_check_t *cond)
{
	if (CHKF_Positive != DDF_MainCheckSpecialFlag(name, powertype_names, 
		&cond->sub.type, false, false))
	{
		return false;
	}

	if (sub[0])
	{
		sscanf(sub, " %f ", &cond->amount);

		cond->amount *= (float)TICRATE;
	}

	cond->cond_type = COND_Powerup;
	return true;
}

static bool ConditionTryPlayerState(const char *name, const char *sub,
										 condition_check_t *cond)
{
	return (CHKF_Positive == DDF_MainCheckSpecialFlag(name, 
		simplecond_names, (int *)&cond->cond_type, false, false));
}

//
// DDF_MainParseCondition
//
// Returns `false' if parsing failed.
//
bool DDF_MainParseCondition(const char *info, condition_check_t *cond)
{
	char typebuf[100];
	char sub_buf[100];

	int len = strlen(info);
	int t_off;
	const char *pos;

	cond->negate = false;
	cond->cond_type = COND_NONE;
	cond->amount = 1;

	memset(&cond->sub, 0, sizeof(cond->sub));

	pos = strchr(info, '(');

	// do we have matched parentheses ?
	if (pos && pos > info && len >= 4 && info[len-1] == ')')
	{
		int len2 = (pos - info);

		Z_StrNCpy(typebuf, info, len2);

		len -= len2 + 2;
		Z_StrNCpy(sub_buf, pos + 1, len);
	}
	else if (pos || strchr(info, ')'))
	{
		DDF_WarnError("Malformed condition string: %s\n", info);
		return false;
	}
	else
	{
		strcpy(typebuf, info);
		sub_buf[0] = 0;
	}

	// check for negation
	t_off = 0;
	if (strnicmp(typebuf, "NOT_", 4) == 0)
	{
		cond->negate = true;
		t_off = 4;
	}

	if (ConditionTryAmmo(typebuf + t_off, sub_buf, cond) ||
		ConditionTryWeapon(typebuf + t_off, sub_buf, cond) ||
		ConditionTryKey(typebuf + t_off, sub_buf, cond) ||
		ConditionTryHealth(typebuf + t_off, sub_buf, cond) ||
		ConditionTryArmour(typebuf + t_off, sub_buf, cond) ||
		ConditionTryPowerup(typebuf + t_off, sub_buf, cond) ||
		ConditionTryPlayerState(typebuf + t_off, sub_buf, cond))
	{
		return true;
	}

	DDF_WarnError("Unknown/Malformed condition type: %s\n", typebuf);
	return false;
}


// ---> mobjdef class

mobjtype_c::mobjtype_c() : name(), state_grp()
{
	Default();
}

mobjtype_c::~mobjtype_c()
{
}


void mobjtype_c::CopyDetail(mobjtype_c &src)
{
	state_grp.clear();

	for (unsigned int i = 0; i < src.state_grp.size(); i++)
		state_grp.push_back(src.state_grp[i]);

    spawn_state = src.spawn_state; 
    idle_state = src.idle_state; 
    chase_state = src.chase_state; 
    pain_state = src.pain_state; 
    missile_state = src.missile_state; 
    melee_state = src.melee_state; 
    death_state = src.death_state; 
    overkill_state = src.overkill_state; 
    raise_state = src.raise_state; 
    res_state = src.res_state; 
    meander_state = src.meander_state; 
    bounce_state = src.bounce_state; 
    touch_state = src.touch_state; 
    reload_state = src.reload_state; 
    gib_state = src.gib_state; 

    reactiontime = src.reactiontime; 
	painchance = src.painchance; 
	spawnhealth = src.spawnhealth; 
    speed = src.speed; 
	float_speed = src.float_speed; 
    radius = src.radius; 
    height = src.height; 
	step_size = src.step_size; 
	mass = src.mass; 

    flags = src.flags; 
    extendedflags = src.extendedflags; 
    hyperflags = src.hyperflags; 

	explode_damage = src.explode_damage;	
	explode_radius = src.explode_radius;

	lose_benefits = src.lose_benefits; 
	pickup_benefits = src.pickup_benefits; 
	pickup_effects = src.pickup_effects; 
	pickup_message = src.pickup_message; 
	initial_benefits = src.initial_benefits; 

    castorder = src.castorder; 
	cast_title = src.cast_title; 
	respawntime = src.respawntime; 
	translucency = src.translucency; 
	minatkchance = src.minatkchance; 
	palremap = src.palremap; 

	jump_delay = src.jump_delay; 
    jumpheight = src.jumpheight; 
    crouchheight = src.crouchheight; 
	viewheight = src.viewheight; 
	shotheight = src.shotheight; 
    maxfall = src.maxfall; 
	fast = src.fast; 
	
	scale = src.scale; 
	aspect = src.aspect; 
	yalign = src.yalign;

	model_skin = src.model_skin;
	model_scale = src.model_scale;
	model_aspect = src.model_aspect;
	model_bias = src.model_bias;

	bounce_speed = src.bounce_speed; 
	bounce_up = src.bounce_up; 
	sight_slope = src.sight_slope; 
	sight_angle = src.sight_angle; 
	ride_friction = src.ride_friction; 
	shadow_trans = src.shadow_trans; 
	glow_type = src.glow_type; 

	seesound = src.seesound; 
	attacksound = src.attacksound; 
	painsound = src.painsound; 
	deathsound = src.deathsound; 
	overkill_sound = src.overkill_sound; 
	activesound = src.activesound; 
	walksound = src.walksound; 
	jump_sound = src.jump_sound; 
	noway_sound = src.noway_sound; 
	oof_sound = src.oof_sound; 
	gasp_sound = src.gasp_sound; 

    fuse = src.fuse; 
	reload_shots = src.reload_shots;
	armour_protect = src.armour_protect;
	armour_deplete = src.armour_deplete;
	armour_class = src.armour_class;

	side = src.side; 
    playernum = src.playernum; 
	lung_capacity = src.lung_capacity; 
	gasp_start = src.gasp_start; 

	// choke_damage
	choke_damage = src.choke_damage;	

	bobbing = src.bobbing; 
	immunity = src.immunity; 
	resistance = src.resistance; 
	resist_multiply = src.resist_multiply; 
	resist_painchance = src.resist_painchance; 
	ghost = src.ghost; 

	closecombat = src.closecombat; 
	rangeattack = src.rangeattack; 
	spareattack = src.spareattack; 

	// dynamic light info
	dlight[0] = src.dlight[0];
	dlight[1] = src.dlight[1];

	weak = src.weak;

	dropitem = src.dropitem; 
	dropitem_ref = src.dropitem_ref; 
	blood = src.blood; 
	blood_ref = src.blood_ref; 
	respawneffect = src.respawneffect; 
	respawneffect_ref = src.respawneffect_ref; 
	spitspot = src.spitspot; 
	spitspot_ref = src.spitspot_ref; 
}


void mobjtype_c::Default()
{
	state_grp.clear();

    spawn_state = 0;
    idle_state = 0;
    chase_state = 0;
    pain_state = 0;
    missile_state = 0;
    melee_state = 0;
    death_state = 0;
    overkill_state = 0;
    raise_state = 0;
    res_state = 0;
    meander_state = 0;
    bounce_state = 0;
    touch_state = 0;
    reload_state = 0;
    gib_state = 0;

    reactiontime = 0;
	painchance = PERCENT_MAKE(0);
	spawnhealth = 1000.0f;
    speed = 0;
	float_speed = 2.0f;
    radius = 0;
    height = 0;
	step_size = 24.0f;
	mass = 100.0f;

    flags = 0;
    extendedflags = 0;
	hyperflags = 0;

	explode_damage.Default(damage_c::DEFAULT_Mobj);
	explode_radius = 0;

	lose_benefits = NULL;
	pickup_benefits = NULL;
	pickup_effects = NULL;
	pickup_message = NULL;
	initial_benefits = NULL;

    castorder = 0;
	cast_title.clear();
	respawntime = 30 * TICRATE;
	translucency = PERCENT_MAKE(100);
	minatkchance = PERCENT_MAKE(0);
	palremap = NULL;

	jump_delay = 1 * TICRATE;
    jumpheight = 0;
    crouchheight = 0;
	viewheight = PERCENT_MAKE(75);
	shotheight = PERCENT_MAKE(64);
    maxfall = 0;
	fast = 1.0f;
	scale = 1.0f;
	aspect = 1.0f;
	yalign = SPYA_BottomUp;

	model_skin = 1;
	model_scale = 1.0f;
	model_aspect = 1.0f;
	model_bias = 0.0f;

	bounce_speed = 0.5f;
	bounce_up = 0.5f;
	sight_slope = 16.0f;
	sight_angle = ANG90;
	ride_friction = RIDE_FRICTION;
	shadow_trans = PERCENT_MAKE(50);
	glow_type = GLOW_None;

	seesound = sfx_None;
	attacksound = sfx_None;
	painsound = sfx_None;
	deathsound = sfx_None;
	overkill_sound = sfx_None;
	activesound = sfx_None;
	walksound = sfx_None;
	jump_sound = sfx_None;
	noway_sound = sfx_None;
	oof_sound = sfx_None;
	gasp_sound = sfx_None;

    fuse = 0;
	reload_shots = 5;
	armour_protect = -1.0;  // disabled!
	armour_deplete = PERCENT_MAKE(100);
	armour_class = BITSET_FULL;

	side = BITSET_EMPTY;
    playernum = 0;
	lung_capacity = 20 * TICRATE;
	gasp_start = 2  * TICRATE;

	choke_damage.Default(damage_c::DEFAULT_MobjChoke);

	bobbing = PERCENT_MAKE(100);
	immunity = BITSET_EMPTY;
	resistance = BITSET_EMPTY;
	resist_multiply = 0.4;
	resist_painchance = -1; // disabled
	ghost = BITSET_EMPTY;

	closecombat = NULL;
	rangeattack = NULL;
	spareattack = NULL;

	// dynamic light info
	dlight[0].Default();
	dlight[1].Default();

	weak.Default();

	dropitem = NULL;
	dropitem_ref.clear();
	blood = NULL;
	blood_ref.clear();
	respawneffect = NULL;
	respawneffect_ref.clear();
	spitspot = NULL;
	spitspot_ref.clear();
}

void mobjtype_c::DLightCompatibility(void)
{
	for (int DL = 0; DL < 2; DL++)
	{
		int r = RGB_RED(dlight[DL].colour);
		int g = RGB_GRN(dlight[DL].colour);
		int b = RGB_BLU(dlight[DL].colour);

		// dim the colour
		r = int(r * DLIT_COMPAT_ITY);
		g = int(g * DLIT_COMPAT_ITY);
		b = int(b * DLIT_COMPAT_ITY);

		switch (dlight[DL].type)
		{
			case DLITE_Compat_QUAD:
				dlight[DL].type = DLITE_Modulate;
				dlight[DL].radius = DLIT_COMPAT_RAD(dlight[DL].radius);
				dlight[DL].colour = RGB_MAKE(r, g, b);

				hyperflags |= HF_QUADRATIC_COMPAT;
				break;

			case DLITE_Compat_LIN:
				dlight[DL].type = DLITE_Modulate;
				dlight[DL].radius *= 1.3;
				dlight[DL].colour = RGB_MAKE(r, g, b);
				break;

			default: // nothing to do
				break;
		}

//??	if (dlight[DL].radius > 500)
//??		dlight[DL].radius = 500;
	}
}


// --> mobjtype_container_c class

mobjtype_container_c::mobjtype_container_c() : epi::array_c(sizeof(mobjtype_c*))
{
	memset(lookup_cache, 0, sizeof(mobjtype_c*) * LOOKUP_CACHESIZE);
}


mobjtype_container_c::~mobjtype_container_c()
{
	Clear();					// <-- Destroy self before exiting
}


void mobjtype_container_c::CleanupObject(void *obj)
{
	mobjtype_c *m = *(mobjtype_c**)obj;

	if (m)
		delete m;

	return;
}


int mobjtype_container_c::FindFirst(const char *name, int startpos)
{
	epi::array_iterator_c it;
	mobjtype_c *m;

	if (startpos>0)
		it = GetIterator(startpos);
	else
		it = GetBaseIterator();

	while (it.IsValid())
	{
		m = ITERATOR_TO_TYPE(it, mobjtype_c*);
		if (DDF_CompareName(m->name.c_str(), name) == 0)
		{
			return it.GetPos();
		}

		it++;
	}

	return -1;
}


int mobjtype_container_c::FindLast(const char *name, int startpos)
{
	epi::array_iterator_c it;
	mobjtype_c *m;

	if (startpos>=0 && startpos<array_entries)
		it = GetIterator(startpos);
	else
		it = GetTailIterator();

	while (it.IsValid())
	{
		m = ITERATOR_TO_TYPE(it, mobjtype_c*);
		if (DDF_CompareName(m->name.c_str(), name) == 0)
		{
			return it.GetPos();
		}

		it--;
	}

	return -1;
}


bool mobjtype_container_c::MoveToEnd(int idx)
{
	// Moves an entry from its current position to end of the list.

	mobjtype_c* m;

	if (idx < 0 || idx >= array_entries)
		return false;

	if (idx == (array_entries - 1))
		return true;					// Already at the end

	// Get a copy of the pointer 
	m = (*this)[idx];

	memmove(&array[idx*array_block_objsize], 
		&array[(idx+1)*array_block_objsize], 
		(array_entries-(idx+1))*array_objsize);

	memcpy(&array[(array_entries-1)*array_block_objsize], (void*)&m, sizeof(mobjtype_c*));
	return true;
}


const mobjtype_c *mobjtype_container_c::Lookup(const char *refname)
{
	// Looks an mobjdef by name.
	// Fatal error if it does not exist.

	int idx = FindLast(refname);

	if (idx >= 0)
		return (*this)[idx];

	if (lax_errors)
		return default_mobjtype;

	DDF_Error("Unknown thing type: %s\n", refname);
	return NULL; /* NOT REACHED */
}


const mobjtype_c *mobjtype_container_c::Lookup(int id)
{
	if (id == 0)
		return default_mobjtype;

	// Looks an mobjdef by number.
	// Fatal error if it does not exist.

	int slot = DDF_MobjHashFunc(id);

	// check the cache
	if (lookup_cache[slot] &&
		lookup_cache[slot]->number == id)
	{
		return lookup_cache[slot];
	}

	epi::array_iterator_c it;

	for (it = GetTailIterator(); it.IsValid(); it--)
	{
		mobjtype_c *m = ITERATOR_TO_TYPE(it, mobjtype_c*);

		if (m->number == id)
		{
			// update the cache
			lookup_cache[slot] = m;
			return m;
		}
	}

	return NULL;
}


const mobjtype_c *mobjtype_container_c::LookupCastMember(int castpos)
{
	// Lookup the cast member of the one with the nearest match
	// to the position given.

	epi::array_iterator_c it;
	mobjtype_c* best;
	mobjtype_c* m;

	best = NULL;
	for (it = GetTailIterator(); it.IsValid(); it--)
	{
		m = ITERATOR_TO_TYPE(it, mobjtype_c*);
		if (m->castorder > 0)
		{
			if (m->castorder == castpos)	// Exact match
				return m;

			if (best)
			{
				if (m->castorder > castpos)
				{
					if (best->castorder > castpos)
					{
						int of1 = m->castorder - castpos;
						int of2 = best->castorder - castpos;

						if (of2 > of1)
							best = m;
					}
					else
					{
						// Our previous was before the requested
						// entry in the cast order, this is later and
						// as such always better.
						best = m;
					}
				}
				else
				{
					// We only care about updating this if the
					// best match was also prior to current 
					// entry. In this case we are looking for
					// the first entry to wrap around to.
					if (best->castorder < castpos)
					{
						int of1 = castpos - m->castorder;
						int of2 = castpos - best->castorder;

						if (of1 > of2)
							best = m;
					}
				}
			}
			else
			{
				// We don't have a best item, so this has to be our best current match
				best = m;
			}
		}
	}

	return best;
}


const mobjtype_c* mobjtype_container_c::LookupPlayer(int playernum)
{
	// Find a player thing (needed by deathmatch code).

	epi::array_iterator_c it;

	for (it = GetTailIterator(); it.IsValid(); it--)
	{
		mobjtype_c *m = ITERATOR_TO_TYPE(it, mobjtype_c*);

		if (m->playernum == playernum)
			return m;
	}

	I_Error("Missing DDF entry for player number %d\n", playernum);
	return NULL; /* NOT REACHED */
}

//--- editor settings ---
// vi:ts=4:sw=4:noexpandtab
