/*
	DooM/HereTic/HeXen/StriFe INFO.c processors, by Frans P. de Vries.

Derived from:

	DooM MaP StaTistics, by Frans P. de Vries.

	You are allowed to use any parts of this code in another program, as
	long as you give credits to the authors in the documentation and in
	the program itself.  Read the file README for more information.

	This program comes with absolutely no warranty.

	PRINT.C - Common print routines.
*/

#include "print.h"
#include "things.h"

#ifdef DMINFO
#include "../doom/things.h"
#include "p_pspr.h"

#elif D64INFO
#include "../doom64/things.h"
#define S_NULL S_000

#elif HTINFO
#include "../heretic/things.h"

#elif HXINFO
#include "../hexen/things.h"

#elif SFINFO
#include "../strife/things.h"
#include "p_pspr.h"
#endif

#include "maps.h"
#include "stub.h"
#include "wads.h"
#include "levels.h"


/*
	the type definitions
*/
typedef struct monstdata_s {
	int objt;
	int pain;
	int hlth;
} monstdata_t;

typedef struct typedata_s {
	int objt;
	int tcls;
	int ttyp;
	char *name;
} typedata_t;

/*
	the global variables
*/
extern unsigned char rndtable[];

#define MAXFRM 40
int frames[MAXFRM];

monstdata_t **monstdata = NULL;
int nummonst = 0;

typedata_t **typedata = NULL;
int numtypes = 0;
int mobjtypes = 0;

/*
	the local function prototypes
*/
int SpriteCount( void);
char *SpriteFrames( int, Bool);
void AppendType( int, int);
void CheckTypes( void);
void ForgetTypes( void);
int SortMonst( const void *, const void *);
int SortTypes( const void *, const void *);
int SortClasses( const void *, const void *);
void PrintTypesTable( int);
void PrintAppearances( int);


void PrintState( void)
{
	int num;
	char *frm;
	statenum_t state;

	state = State2Num( Stat);
	printf( "Sprite name\t%s\n", sprnames[states[state].sprite]);

	TallyFrames( state, TRUE);
	num = SpriteCount();
	frm = SpriteFrames( num, FALSE);
	printf( "Frames single\t%d (%s)\n", num, frm);

	TallyFrames( state, FALSE);
	num = SpriteCount();
	frm = SpriteFrames( num, FALSE);
	printf( "Frames refire\t%d (%s)\n", num, frm);
}


void PrintSpawns( void)
{
	int i;

	memset(fieldlist, 0, LISTSIZE);
	for (i = 0; i < MAXSPAWN; i++)
		if (spawns[i] != -1)
		{
			if (XWik)
			{
				if (i > 0)
					strcat( fieldlist, "<br>");
				strcat( fieldlist, Obj2Str( spawns[i]));
			}
			else
			{
				if (i == 0)
					printf( "\nSpawns\t");
				else
					printf( "\n\t");
				printf( "\t%s", Obj2Str( spawns[i]));
			}
		}
		else
			break;

	if (XWik)
	{
		tpldata = TMPL_add_var( tpldata, "SPAWNS", fieldlist, NULL);
		if (i > 0)
			tpldata = TMPL_add_var( tpldata, "ATTKTYPE", "[[Projectile]]", NULL);
		else
			tpldata = TMPL_add_var( tpldata, "ATTKTYPE", "[[Hitscan]]", NULL);
		printf( "\n");
	}
	else
		printf( "\n\n");
}


void PrintTypeHealth( mobjinfo_t *MOBJ)
{
	if (XWik)
	{
		tpldata = TMPL_add_var( tpldata, "MOBJNAME", Obj2Str( Objt), NULL);
		tmpl_add_int( tpldata, "MOBJNUM", "%d", Objt);
		tmpl_add_int( tpldata, "TYPEDEC", "%d", MOBJ->doomednum);
		if (MOBJ->doomednum != -1)
		{
			tmpl_add_int( tpldata, "TYPEHEX", "%X", MOBJ->doomednum);
			tpldata = TMPL_add_var( tpldata, "NAME", LookupThing( MOBJ->doomednum, FALSE), NULL);
			AppearsIn( MOBJ->doomednum);
		}
		if ((MOBJ->doomednum != -1 && LookupThingClass( MOBJ->doomednum) == ThClassMnst) ||
		    MOBJ->spawnhealth != 1000)
			tmpl_add_int( tpldata, "HITPNTS", "%d", MOBJ->spawnhealth);

		// enable Melee attack table
		if (MOBJ->meleestate != S_NULL)
			tpldata = TMPL_add_var( tpldata, "MELEE", "1", NULL);
	}
	else
	{
		printf( "\n");
		printf( "Enum\t\t%s (%d)\n", Obj2Str( Objt), Objt);
		if (MOBJ->doomednum != -1)
			printf( "Type\t\t%d\t%X\n", MOBJ->doomednum, MOBJ->doomednum);
		else
			printf( "Type\t\t%d\n", MOBJ->doomednum);
		printf( "Health\t\t%d\n", MOBJ->spawnhealth);
	}
}

#ifdef SFINFO
#define DAMAGERANGE 4
#else
#define DAMAGERANGE 8
#endif

void PrintDimsTimesFlags( mobjinfo_t *MOBJ)
{
	if (XWik)
	{
		tmpl_add_int( tpldata, "RADIUS", "%d", MOBJ->radius / FRACUNIT);
		tmpl_add_int( tpldata, "HEIGHT", "%d", MOBJ->height / FRACUNIT);
		tmpl_add_int( tpldata, "MASS", "%d", MOBJ->mass);

		tmpl_add_int( tpldata, "DIRHIT", "%d", MOBJ->damage);
		if (MOBJ->flags & MF_MISSILE) // missile formula
			tmpl_add_int( tpldata, "DIRRNG", "%d", MOBJ->damage * DAMAGERANGE);

		if (!(MOBJ->flags & MF_SPECIAL) || // no pick-up item
		    LookupThingClass( MOBJ->doomednum) == ThClassMnst
#ifdef SFINFO
		    || MOBJ->spawnhealth == 990 // MT_SUBENTITY
#endif
		   )
		{
			tmpl_add_int( tpldata, "REACTTM", "%d", MOBJ->reactiontime);
			tmpl_add_int( tpldata, "PAINCHC", "%d", MOBJ->painchance);
			tmpl_add_float( tpldata, "PAINPRC", "%.2f", PainPerc( MOBJ->painchance));
			tmpl_add_int( tpldata, "PAINTM", "%d", PainTics( MOBJ->painstate));
		}

		tmpl_add_int( tpldata, "FLAGSHEX", "%08X", MOBJ->flags);
		tmpl_add_int( tpldata, "FLAGSDEC", "%u", MOBJ->flags);

		memset(fieldlist, 0, LISTSIZE);
		if (LookupThingClass( MOBJ->doomednum) == ThClassHlAr ||
		    LookupThingClass( MOBJ->doomednum) == ThClassItem)
			strcat( fieldlist, "[[Item]]");
		else if (LookupThingClass( MOBJ->doomednum) == ThClassWeap)
			strcat( fieldlist, "[[Weapon]]");
		else if (LookupThingClass( MOBJ->doomednum) == ThClassAmmo)
			strcat( fieldlist, "[[Ammo]]");
		else if (LookupThingClass( MOBJ->doomednum) == ThClassKeys)
			strcat( fieldlist, "Key");
		else
			strcat( fieldlist, "Obstacle");
		if (MOBJ->flags & MF_SPECIAL && // pick-up item
		    LookupThingClass( MOBJ->doomednum) != ThClassMnst
#ifdef SFINFO
		    && MOBJ->spawnhealth != 990 // MT_SUBENTITY
#endif
		   )
			strcat( fieldlist, "<br>Pickup");
		tpldata = TMPL_add_var( tpldata, "CLASS", fieldlist, NULL);
	}
	else
	{
		printf( "Radius\t\t%d\n", MOBJ->radius / FRACUNIT);
		printf( "Height\t\t%d\n", MOBJ->height / FRACUNIT);
		printf( "Mass\t\t%d\n", MOBJ->mass);

		if (MOBJ->flags & MF_MISSILE) // missile formula
			printf( "Damage\t\t%d-%d\n", MOBJ->damage, MOBJ->damage * DAMAGERANGE);
		else
			printf( "Damage\t\t%d\n", MOBJ->damage);

		if (!(MOBJ->flags & MF_SPECIAL) || // no pick-up item|
		    LookupThingClass( MOBJ->doomednum) == ThClassMnst
#ifdef SFINFO
		    || MOBJ->spawnhealth == 990 // MT_SUBENTITY
#endif
		   )
		{
			printf( "Reaction time\t%d tics\n", MOBJ->reactiontime);
			printf( "Pain chance\t%d ; %.2f%%\n", MOBJ->painchance, PainPerc( MOBJ->painchance));
			printf( "Pain time\t%d tics\n", PainTics( MOBJ->painstate));
		}

		printf( "Flags\t\t%08x  %d\n", MOBJ->flags, MOBJ->flags);
	}

	PrintFlags( MOBJ->flags);
}

void PrintSpeed( mobjinfo_t *MOBJ)
{
	int tics;

	if (MOBJ->flags & MF_MISSILE)
	{
		if (XWik)
		{
#ifdef D64INFO
			tmpl_add_int( tpldata, "SPDTIC", "%d", MOBJ->speed);
			tmpl_add_int( tpldata, "SPDSEC", "%d", MOBJ->speed * TICRATE);
#else
			tmpl_add_int( tpldata, "SPDTIC", "%d", MOBJ->speed / FRACUNIT);
			tmpl_add_int( tpldata, "SPDSEC", "%d", MOBJ->speed / FRACUNIT * TICRATE);
#endif
		}
		else
			printf( "Speed\t\t%d / tic ; %d / sec\n",
#ifdef D64INFO
			        MOBJ->speed, MOBJ->speed * TICRATE);
#else
			        MOBJ->speed / FRACUNIT, MOBJ->speed / FRACUNIT * TICRATE);
#endif
	}
	else
	{
		tics = FrameTics( MOBJ->seestate);
		if (tics != -1)
		{
			if (XWik)
			{
				tmpl_add_int( tpldata, "SPDTIC", "%d", MOBJ->speed);
				tmpl_add_float( tpldata, "SPDSEC", "%.1f", MOBJ->speed * (float)TICRATE / tics);
			}
			else
				printf( "Speed\t\t%d / frame ; %.1f / sec\n",
				        MOBJ->speed, MOBJ->speed * (float)TICRATE / tics);
		}
		else
		{
			if (XWik)
				tmpl_add_int( tpldata, "SPDTIC", "%d", MOBJ->speed);
			else
				printf( "Speed\t\t%d\n", MOBJ->speed);
		}
	}
}


void PrintSprite( statenum_t state, statestr_t type, Bool singlefire)
{
	int num;
	char *frm, *type_str;

	if (state == S_NULL)
		return;
	switch (type)
	{
		case ST_NONE: break;
		case ST_PICKUP: type_str = (XWik ? "Before pickup" : "pickup"); break;
		case ST_WIELD: type_str = (XWik ? "Wielded" : "wield"); break;
		case ST_FIRE: type_str = (XWik ? "Firing" : "fire"); break;
		case ST_REFIRE: type_str = (XWik ? "Refiring" : "refire"); break;
		case ST_FLASH: type_str = (XWik ? "Flashing" : "flash"); break;
		case ST_WIELD_P: type_str = (XWik ? "Wielded (powered)" : "p wld"); break;
		case ST_FIRE_P: type_str = (XWik ? "Firing (powered)" : "p fire"); break;
		case ST_REFIRE_P: type_str = (XWik ? "Refiring (powered)" : "p ref"); break;
		case ST_SPAWN: type_str = (XWik ? "Idling" : "idle"); break;
		case ST_SEE: type_str = (XWik ? "Chasing" : "see"); break;
		case ST_MELEE: type_str = (XWik ? "Attacking (melee)" : "melee"); break;
		case ST_MISSL: type_str = (XWik ? "Attacking (missile)" : "missl"); break;
		case ST_PAIN: type_str = (XWik ? "[[Pain state|Hurting]]" : "pain"); break;
		case ST_DEATH: type_str = (XWik ? "[[Types of death|Dying]]" : "death"); break;
		case ST_XDEATH: type_str = (XWik ? "[[Gibs|Gibbing]]" : "xdeath"); break;
		case ST_CRASH: type_str = (XWik ? "Crashing" : "crash"); break;
		case ST_RAISE: type_str = (XWik ? "Resurrecting" : "raise"); break;
	}

	if (XWik)
	{
		if (type == ST_NONE)
			strcat( fieldlist, sprnames[states[state].sprite]);
	}
	else
		printf( "Sprite name\t%s\n", sprnames[states[state].sprite]);

#if defined(DMINFO) || defined(D64INFO)
	/* skip Arachnotron sight state */
	if (type == ST_SEE &&
#ifdef DMINFO
	    !states[state].action.acp1
#else
	    !states[state].action
#endif
	   )
		state = states[state].nextstate;
#endif

	TallyFrames( state, singlefire);
	num = SpriteCount();
	frm = SpriteFrames( num, FALSE);
	if (XWik)
	{
		if (type >= ST_SPAWN) // monsters
		{
			sprintf( fieldentry, "| %s||%d [%s]\n", type_str, num, frm);
			strcat( fieldlist, "|-\n");
			strcat( fieldlist, fieldentry);
		}
		else if (type != ST_NONE) // weapons
		{
			sprintf( fieldentry, "| %s||%s||%d [%s]\n", type_str,
			                     sprnames[states[state].sprite], num, frm);
			strcat( fieldlist, "|-\n");
			strcat( fieldlist, fieldentry);
		}
		else // items
		{
			sprintf( fieldentry, "%d [%s]", num, frm);
			strcat( field2list, fieldentry);
		}
	}
	else
		printf( "Frames\t\t%d (%s)\n", num, frm);
	FreeMemory( frm);
}

void TallyFrames( statenum_t state, Bool singlefire)
{
	statenum_t st = state;
	int f, num = 0;
	char sprite[5];
	Bool refired = FALSE;

	/* reset seen frames */
	for (f = 0; f < MAXFRM; f++)
		frames[f] = -1;
	/* ignore null state */
	if (state == S_NULL)
		return;
	/* remember initial sprite */
	strcpy( sprite, sprnames[states[state].sprite]);

	for (;;)
	{
		/* tally this frame */
		f = states[st].frame & FF_FRAMEMASK;
		if (f >= MAXFRM)
			ProgError( "unable to count frames for state %d", state);
		frames[num++] = f;
		if (refired)
			break;

		/* done if no next state, or same state, or looped around to earlier one in same flow */
		if (states[st].nextstate == S_NULL ||
		    (states[st].nextstate <= st && states[st].nextstate >= state))
			break;
		/* warn about sprite change */
		if (strcmp( sprite, sprnames[states[st].sprite]) != 0)
		{
			printf( "State %d changed sprite to %s\n", st, sprnames[states[st].sprite]);
			strcpy( sprite, sprnames[states[st].sprite]);
		}
		st = states[st].nextstate;
		/* warn about switching to earlier state flow */
		if (st > 1 && st < state && XWik)
			printf( "Next state %d before start %d\n", st, state);
		/* done if looped around to start state */
		if (st == state)
			break;

#if defined(DMINFO) || defined(HTINFO) || defined(HXINFO)
		/* cease flashing */
		if (st == S_LIGHTDONE)
			break;
#endif

#ifdef DMINFO
		if (states[st].action.acp1)
			states[st].action.acp1(NULL);
#elif D64INFO
		if (states[st].action)
			states[st].action(NULL);
#elif HTINFO
		if (states[st].action)
			states[st].action(NULL);
#elif HXINFO
		if (states[st].action)
			states[st].action(NULL);
#elif SFINFO
		if (states[st].action.acp1)
			states[st].action.acp1(NULL);
#endif
		else
			funcname[0] = '\0';

		/* cease firing */
#ifdef DMINFO
		if (strcmp( funcname, "A_ReFire") == 0 ||
		    // exiting pain states
		    st == S_POSS_RUN1  || // Zombieman
		    st == S_SPOS_RUN1  || // Shotgun guy
		    st == S_CPOS_RUN1  || // Heavy weapon dude
		    st == S_SKULL_RUN1 || // Lost soul
		    st == S_TROO_RUN1  || // Imp
		    st == S_SARG_RUN1  || // Demon
		    st == S_SKEL_RUN1  || // Revenant
		    st == S_HEAD_RUN1  || // Cacodemon
		    st == S_PAIN_RUN1  || // Pain elemental
		    st == S_BOS2_RUN1  || // Hell knight
		    st == S_BOSS_RUN1  || // Baron of Hell
		    st == S_BSPI_RUN1  || // Arachnotron
		    st == S_FATT_RUN1  || // Mancubus
		    st == S_VILE_RUN1  || // Arch-vile
		    st == S_SPID_RUN1  || // Spiderdemon
		    st == S_CYBER_RUN1 || // Cyberdemon
		    st == S_SSWV_RUN1  || // Wolfenstein SS
		    st == S_KEENSTND)     // Commander Keen

#elif D64INFO
		if (strcmp( funcname, "A_ReFire") == 0 ||
		    // exiting pain states
		    st == S_100 || // Zombieman
		    st == S_129 || // Shotgun guy
		    st == S_257 || // Lost soul
		    st == S_158 || // Imp
		    st == S_046 || // Demon
		    st == S_191 || // Cacodemon
		    st == S_327 || // Pain elemental
		    st == S_235 || // Hell knight
		    st == S_214 || // Baron of Hell
		    st == S_279 || // Arachnotron
		    st == S_071 || // Mancubus
		    st == S_299 || // Cyberdemon
		    st == S_358 || // Mother Demon
		    st == S_346 || // Mother Demon
		    st == S_026)   // Marine bot

#elif HTINFO
		if (strcmp( funcname, "A_ReFire") == 0 ||
		    strcmp( funcname, "A_VolcanoSet") == 0 ||     // S_VOLCANO9 loops to S_VOLCANO2, not 1
		    strcmp( funcname, "A_GenWizard") == 0 ||      // S_SOR2FX2_3 loops to S_SOR2FX2_2, not 1
		    strcmp( funcname, "A_WhirlwindSeek") == 0 ||  // S_HEADFX4_7 loops to S_HEADFX4_5, not 1
		    strcmp( funcname, "A_BeakReady") == 0 ||      // S_BEAKREADY loops to S_BEAKREADY
		    st == S_POD_WAIT1    || // Pod
		    // exiting pain states
		    st == S_IMP_FLY1     || // Gargoyle
		    st == S_MUMMY_WALK1  || // Golem
		    st == S_CLINK_WALK1  || // Sabreclaw
		    st == S_WIZARD_WALK1 || // Disciple
		    st == S_KNIGHT_WALK1 || // Undead warrior
		    st == S_BEAST_WALK1  || // Weredragon
		    st == S_SNAKE_WALK1  || // Ophidian
		    st == S_HEAD_FLOAT   || // Iron lich
		    st == S_MNTR_WALK1   || // Maulotaur
		    st == S_SRCR1_WALK1  || // D'Sparil on
		    st == S_SOR2_WALK1   || // D'Sparil sans
		    st == S_CHICKEN_WALK1)  // Chicken

#elif HXINFO
		if (strcmp( funcname, "A_ReFire") == 0 ||
		    st == S_FHAMMERREADY    || // Hammer of Retribution
		    st == S_FSWORDREADY     || // Quietus
		    st == S_CSTAFFREADY2    || // Serpent Staff
		    st == S_CFLAMEREADY1    || // Firestorm
		    st == S_CHOLYREADY      || // Wraithverge
		    st == S_MLIGHTNINGREADY || // Arc of Death
		    st == S_MSTAFFREADY     || // Bloodscrouge
		    // exiting pain states
		    st == S_FIRED_WALK1     || // Afrit
		    st == S_SERPENT_DIVE1   || // Stalker
		    st == S_ICEGUY_WALK1    || // Wendigo
		    st == S_BISHOP_WALK1    || // Dark bishop
		    st == S_WRAITH_CHASE1   || // Reiver
		    st == S_ETTIN_CHASE1    || // Ettin
		    st == S_CENTAUR_WALK1   || // Centaur
		    st == S_DEMN_CHASE1     || // Green chaos serpent
		    st == S_DEMN2_CHASE1    || // Brown chaos serpent
		    st == S_DRAGON_WALK1    || // Death wyvern
		    st == S_SORC_WALK1      || // Heresiarch
		    st == S_MAGE_RUN1       || // Menelkir
		    st == S_CLERIC_RUN1     || // Traductus
		    st == S_FIGHTER_RUN1    || // Zedek
		    st == S_KORAX_CHASE2    || // Korax
		    st == S_MNTR_WALK1      || // Maulotaur
		    st == S_PIG_WALK1)         // Pig

#elif SFINFO
		if (strcmp( funcname, "A_ReFire") == 0 ||
		    strcmp( funcname, "A_WeaponReady") == 0 ||
		    strcmp( funcname, "A_FloatWeave") == 0 ||
		    // exiting pain states
		    st == S_AGRD_12 || // Acolyte
		    st == S_AGRD_13 || // Acolyte
		    st == S_SPID_03 || // Stalker
		    st == S_SEWR_05 || // Sentinel
		    st == S_ROB1_02 || // Reaver
		    st == S_PGRD_04 || // Templar
		    st == S_ROB2_01 || // Crusader
		    st == S_MLDR_01 || // Bishop
		    st == S_LEAD_04 || // Macil
		    st == S_ROB3_02 || // Inquisitor
		    st == S_MRST_00 || // Merchant
		    st == S_HMN1_11 || // Rebel
		    st == S_PEAS_00 || // Peasant
		    st == S_PEAS_01 || // Peasant
		    st == S_PEAS_09 || // Peasant
		    st == S_BEGR_07 || // Beggar
		    st == S_NEAL_00)   // Kneeling guy
#endif
			if (singlefire)
				refired = TRUE;
			else
				break;
	}
}

int SpriteCount( void)
{
	int f, count = 0;

	for (f = 0; f < MAXFRM && frames[f] != -1; f++)
		count++;

	return count;
}

char *SpriteFrames( int count, Bool add)
{
	char *ret, frm[MAXFRM];
	int f;

	memset(frm, 0, MAXFRM);
	/* transpose char to actually used frame */
	for (f = 0; f < MAXFRM && frames[f] != -1; f++)
		frm[f] = 'A' + frames[f];

	/* append symbol for additional frames */
	if (add)
		if (f < MAXFRM)
			frm[f] = '+';
		else
			ProgError( "unable to append '+' after %d frames", count);

	if (strlen( frm) == 0)
		return strdup( "-");
	else
		return strdup( frm);
}


float PainPerc( int chance)
{
	int i, count = 0;

	for (i = 0; i < 256; i++)
		if (rndtable[i] < chance)
			count++;

	/* ensure x.xx50 is rounded up to x.xx+0.01 */
	return count * 100.0f / 256.0f + 0.0001f;
}

void PainChances( void)
{
	int i;

	printf( "\n");
	for (i = 0; i <= 256; i++)
		printf( "%3d\t%.2f%%\n", i, PainPerc( i));
}

int PainTics( statenum_t state)
{
	statenum_t st = state;
	int tics = 0;

	if (st != S_NULL)
		for (;;)
		{
			if (states[st].tics > 0)
				tics += states[st].tics;

#ifdef DMINFO
			if (states[st].action.acp1)
				states[st].action.acp1(NULL);
#elif D64INFO
			if (states[st].action)
				states[st].action(NULL);
#elif HTINFO
			if (states[st].action)
				states[st].action(NULL);
#elif HXINFO
			if (states[st].action)
				states[st].action(NULL);
#elif SFINFO
			if (states[st].action.acp1)
				states[st].action.acp1(NULL);
#endif
			else
				funcname[0] = '\0';

#ifdef DMINFO
			if (strcmp( funcname, "A_Pain") == 0 ||
			    strcmp( funcname, "A_BrainPain") == 0)

#elif D64INFO
			if (strcmp( funcname, "A_Pain") == 0)

#elif HTINFO
			if (strcmp( funcname, "A_Pain") == 0 ||
			    strcmp( funcname, "A_PodPain") == 0 ||
			    strcmp( funcname, "A_ChicPain") == 0 ||
			    strcmp( funcname, "A_Sor1Pain") == 0)

#elif HXINFO
			if (strcmp( funcname, "A_Pain") == 0 ||
			    strcmp( funcname, "A_PigPain") == 0 ||
			    strcmp( funcname, "A_DragonPain") == 0 ||
			    strcmp( funcname, "A_BishopPainBlur") == 0 ||
			    strcmp( funcname, "A_BounceCheck") == 0)

#elif SFINFO
			if (strcmp( funcname, "A_Pain") == 0 ||
			    strcmp( funcname, "A_MerchantPain") == 0 ||
			    strcmp( funcname, "A_ZombieInSpecialSector") == 0 ||
			    strcmp( funcname, "A_HideZombie") == 0 ||
			    strcmp( funcname, "A_CheckTargetVisible") == 0)
#endif
				break;

			st = states[st].nextstate;
			if (tics > 30)
				ProgError( "unable to count tics for painstate %d", state);
		}

	return tics;
}


void MonstersPcHp( void)
{
	mobjinfo_t *MOBJ;
	monstdata_t *mnst;
	int obj;

	for (obj = 0; obj < NUMMOBJTYPES; obj++)
		/* skip player */
#ifdef DMINFO
		if (obj != MT_PLAYER) {
#elif D64INFO
		if (obj != MT_PLAYER) {
#elif HTINFO
		if (obj != MT_PLAYER && obj != MT_CHICPLAYER) {
#elif HXINFO
		if (obj != MT_PLAYER_FIGHTER && obj != MT_PLAYER_CLERIC &&
		    obj != MT_PLAYER_MAGE && obj != MT_PIGPLAYER) {
#elif SFINFO
		if (obj != MT_PLAYER) {
#endif
			MOBJ = &mobjinfo[obj];
			if (MOBJ->painchance != 0

#ifdef DMINFO
			    /* add Explosive barrel */
			    || obj == MT_BARREL

#elif D64INFO
			    /* add Explosive barrel */
			    || obj == MT_PROP_BARREL

#elif HTINFO
			    /* add Pod */
			    || obj == MT_POD

#elif HXINFO
			    /* add Destructible tree, Evergreen tree, Sitting corpse, Shrubs,
			       Pots, Suit of armor, Mushroom */
			    || obj == MT_TREEDESTRUCTIBLE || obj == MT_ZXMAS_TREE
			    || obj == MT_MISC78 || obj == MT_ZSHRUB1 || obj == MT_ZSHRUB2
			    || obj == MT_POTTERY1 || obj == MT_POTTERY2 || obj == MT_POTTERY3
			    || obj == MT_ZSUITOFARMOR || obj == MT_ZPOISONSHROOM

#elif SFINFO
			    /* add Ceiling turret, Exploding barrel, Wooden barrel */
			    || obj == MT_TURRET || obj == MT_MISC_06 || obj == MT_MISC_05
#endif
			   )
			{
				mnst = (monstdata_t *) GetMemory( sizeof(monstdata_t));
				mnst->objt = obj;
				mnst->pain = MOBJ->painchance;
				mnst->hlth = MOBJ->spawnhealth;
				monstdata = ResizeMemory( monstdata, (nummonst+1) * sizeof(mnst));
				monstdata[nummonst++] = mnst;
			}
		}

	qsort( monstdata, nummonst, sizeof(monstdata_t *), SortMonst);

	printf( "\n");
	for (obj = 0; obj < nummonst; obj++)
		printf( "%-20s\t%d (%.2f%%)\t%d\n",
		        Monst2Str( monstdata[obj]->objt),
		        monstdata[obj]->pain, PainPerc( monstdata[obj]->pain),
		        monstdata[obj]->hlth);

	for (obj = 0; obj < nummonst; obj++)
		if (monstdata[obj] != NULL)
			FreeMemory( monstdata[obj]);
	FreeMemory( monstdata);
	monstdata = NULL;
}

/* sort by decreasing pain chaince, decreasing hit points, alphabetic name */
int SortMonst( const void *p1, const void *p2)
{
	monstdata_t *m1 = * (monstdata_t * const *) p1,
	            *m2 = * (monstdata_t * const *) p2;

	if (m1->pain < m2->pain)
		return 1;
	else if (m1->pain > m2->pain)
		return -1;

	if (m1->hlth < m2->hlth)
		return 1;
	else if (m1->hlth > m2->hlth)
		return -1;

	return strcmp( Monst2Str( m1->objt),
	               Monst2Str( m2->objt));
}


void InitTypes( void)
{
	mobjinfo_t *MOBJ;
	typedata_t *type;
	int obj;

#ifdef DMINFO
	InitDoomThings();
#elif D64INFO
	InitDoom64Things();
#elif HTINFO
	InitHereticThings();
#elif HXINFO
	InitHexenThings();
#elif SFINFO
	InitStrifeThings();
#endif

	/* collect all mobjs with a Thing type */
	for (obj = 0; obj < NUMMOBJTYPES; obj++)
	{
		MOBJ = &mobjinfo[obj];
		if (MOBJ->doomednum != -1)
			AppendType( obj, MOBJ->doomednum);
	}
	/* remember mobj count with doomednum */
	mobjtypes = numtypes;

	/* add various starting spots which are not in mobjinfo array */
#ifdef HXINFO
	AppendType( MT_PLAYER_FIGHTER, THING_PLAYER1);
	AppendType( MT_PLAYER_FIGHTER, THING_PLAYER2);
	AppendType( MT_PLAYER_FIGHTER, THING_PLAYER3);
	AppendType( MT_PLAYER_FIGHTER, THING_PLAYER4);
	AppendType( MT_PLAYER_FIGHTER, THING_PLAYER5X);
	AppendType( MT_PLAYER_FIGHTER, THING_PLAYER6X);
	AppendType( MT_PLAYER_FIGHTER, THING_PLAYER7X);
	AppendType( MT_PLAYER_FIGHTER, THING_PLAYER8X);
#else
	AppendType( MT_PLAYER, THING_PLAYER1);
	AppendType( MT_PLAYER, THING_PLAYER2);
	AppendType( MT_PLAYER, THING_PLAYER3);
	AppendType( MT_PLAYER, THING_PLAYER4);
#endif

#ifdef D64INFO
	AppendType( MT_DEST_TELEPORT, THING_DEATHMATCH);
#else
	AppendType( MT_TELEPORTMAN, THING_DEATHMATCH);
#endif

#ifdef HTINFO
	/* add teleport spot */
	AppendType( MT_TELEPORTMAN, THING_DSPARILSPOT);

	/* add non-mobj sound things (besides Wind & Waterfall) */
	AppendType( MT_SOUNDWIND, THING_SNDBELSS);
	AppendType( MT_SOUNDWIND, THING_SNDDROPS);
	AppendType( MT_SOUNDWIND, THING_SNDFASTFOOT);
	AppendType( MT_SOUNDWIND, THING_SNDGROWL);
	AppendType( MT_SOUNDWIND, THING_SNDHEARTBEAT);
	AppendType( MT_SOUNDWIND, THING_SNDLAUGHTER);
	AppendType( MT_SOUNDWIND, THING_SNDMAGIC);
	AppendType( MT_SOUNDWIND, THING_SNDSCREAM);
	AppendType( MT_SOUNDWIND, THING_SNDSLOWFOOT);
	AppendType( MT_SOUNDWIND, THING_SNDSQUISH);
#endif

#ifdef HXINFO
	/* add polyobjects */
	AppendType( MT_MAPSPOT, THING_POLYANCHOR);
	AppendType( MT_MAPSPOT, THING_POLYSPAWN);
	AppendType( MT_MAPSPOT, THING_POLYSPAWNCR);

	/* add non-mobj sound things (besides Wind) */
	AppendType( MT_SOUNDWIND, THING_SNDCREAK);
	AppendType( MT_SOUNDWIND, THING_SNDEARTH);
	AppendType( MT_SOUNDWIND, THING_SNDHEAVY);
	AppendType( MT_SOUNDWIND, THING_SNDICE);
	AppendType( MT_SOUNDWIND, THING_SNDLAVA);
	AppendType( MT_SOUNDWIND, THING_SNDMETAL);
	AppendType( MT_SOUNDWIND, THING_SNDMETAL2);
	AppendType( MT_SOUNDWIND, THING_SNDSILENCE);
	AppendType( MT_SOUNDWIND, THING_SNDSTONE);
	AppendType( MT_SOUNDWIND, THING_SNDWATER);
#endif

#if defined(SFINFO) && defined(MOBJTRANSLATION)
	/* add Veteran Ed. spots */
	AppendType( MT_PLAYER, THING_TEAMBLUE);
	AppendType( MT_PLAYER, THING_TEAMRED);
#endif

	CheckTypes();
}

void AppendType( int objt, int dedn)
{
	typedata_t *type;

	type = (typedata_t *) GetMemory( sizeof(typedata_t));
	type->objt = objt;
	type->ttyp = dedn;
	type->tcls = LookupThingClass( dedn);
	type->name = LookupThing( dedn, FALSE);
	typedata = ResizeMemory( typedata, (numtypes+1) * sizeof(type));
	typedata[numtypes++] = type;
}

void CheckTypes( void)
{
	int i, j;
	Bool found;

	/* check that all Thing items have been included in mobj types list */
	for (i = 0; i < NumThItems; i++)
	{
		found = FALSE;
		for (j = 0; j < numtypes; j++)
			if (ThItems[i]->type == typedata[j]->ttyp)
			{
				found = TRUE;
				break;
			}
		if (!found)
			printf( "\tNo mobj found for Thing type: %d\n", ThItems[i]->type);
	}
}

void ForgetTypes( void)
{
	int obj;

	for (obj = 0; obj < numtypes; obj++)
		if (typedata[obj] != NULL)
			FreeMemory( typedata[obj]);
	FreeMemory( typedata);
	typedata = NULL;
}


/* allocates the returned string which the caller must free */
char *LookupFlags( int flags, char *name)
{
	char flagstr[6];
	int flagidx = 0;

	memset(flagstr, 0, sizeof(flagstr));

	/* check for player/team starts, which have no flags */
	if ((strncmp(name, "Player ", strlen("Player ")) != 0
	     && strncmp(name, "Team ", strlen("Team ")) != 0) ||
	    strstr(name, " start") == NULL)
	{
		if (IsWeapon( name))
			flagstr[flagidx++] = 'W';

#if defined(DMINFO) || defined(HTINFO) 
		if (flags & MF_COUNTITEM)
			flagstr[flagidx++] = 'A';
#endif

		if (flags & MF_SPECIAL
#ifdef SFINFO
		    /* enemies with special flag cannot be picked up */
		    && strcmp(name, "Entity") != 0
		    && strncmp(name, "Spectre", strlen( "Spectre")) != 0
		    /* VE flag spots with special flag cannot be picked up */
		    && strstr(name, "flag spot") == NULL
#endif
		   )
			flagstr[flagidx++] = 'P';

		if (flags & MF_COUNTKILL
#ifdef DMINFO
		    /* no count kill flag but still a monster */
		    || strcmp(name, "Lost soul") == 0
#endif
		   )
			flagstr[flagidx++] = 'M';

		if (flags & MF_SOLID)
			flagstr[flagidx++] = 'O';

		if (flags & MF_SHOOTABLE)
			flagstr[flagidx++] = '*';

		if ((flags & MF_SPAWNCEILING) || (flags & MF_FLOAT))
			flagstr[flagidx++] = '^';
	}

	return strdup( flagstr);
}


void ThingTypes( void)
{
	qsort( typedata, numtypes, sizeof(typedata_t *), SortTypes);

	PrintTypesTable( -1);

#if defined(SFINFO) && defined(MOBJTRANSLATION)
	/* add Veteran Ed. table */
	printf("===%s===\n", LookupClassName( ThClassSnds));
	PrintTypesTable( ThClassSnds);
#endif

	printf("Totals: '''%d''' editable things, out of %d mobjs and %d non-mobjs.\n",
	       numtypes, NUMMOBJTYPES, numtypes - mobjtypes);
	ForgetTypes();
	ForgetThings();
}

/* sort by increasing Thing type */
int SortTypes( const void *p1, const void *p2)
{
	typedata_t *t1 = * (typedata_t * const *) p1,
	           *t2 = * (typedata_t * const *) p2;

	if (t1->ttyp > t2->ttyp)
		return 1;
	else if (t1->ttyp < t2->ttyp)
		return -1;
	else // typ1 == typ2
		return 0; // this shouldn't happen anyway
}

void ClassTypes( void)
{
	int class;

	qsort( typedata, numtypes, sizeof(typedata_t *), SortClasses);

	/* process all classes */
	printf( "\n");
	for (class = 0; class < ThClass_End; class++)
#if defined(SFINFO) && defined(MOBJTRANSLATION)
		if (class != ThClassSnds) // skip Veteran Ed.
#endif
		{
			printf("===%s===\n", LookupClassName( class));
			if (XWik)
				PrintAppearances( class);
			else
				PrintTypesTable( class);
		}

#if defined(SFINFO) && defined(MOBJTRANSLATION)
	/* add Veteran Ed. table */
	printf("===%s===\n", LookupClassName( ThClassSnds));
	if (XWik)
		PrintAppearances( ThClassSnds);
	else
		PrintTypesTable( ThClassSnds);
#endif

	ForgetTypes();
	ForgetThings();
}

/* sort by Thing name */
int SortClasses( const void *p1, const void *p2)
{
	typedata_t *t1 = * (typedata_t * const *) p1,
	           *t2 = * (typedata_t * const *) p2;

	if (t1->tcls > t2->tcls)
		return 1;
	else if (t1->tcls < t2->tcls)
		return -1;
	else // cls1 == cls2
		return strcmp(t1->name, t2->name);
}


void PrintTypesTable( int class)
{
	typedata_t *type;
	statenum_t state;
	WadPtr curw;
	int num, lmp, obj, wad, sprwad;
	char *frm, *flgs, *vers;
	Bool foundspr, foundlvl;

	/* output table header */
	printf( "\n{| {{prettySortable}}\n"
	        "! Decimal\n"
	        "! Hex\n"
	        "! Version\n"
	        "! Radius\n"
	        "! Height\n"
	        "! Sprite\n"
	        "! Sequence\n"
	        "! Class\n"
	        "! Description\n");

	/* output table with 8 columns */
	for (obj = 0; obj < numtypes; obj++)
	{
		type = typedata[obj];
		if (class != -1 && class != type->tcls)
			continue;

		state = mobjinfo[type->objt].spawnstate;

#ifdef HXINFO
		/* in Hexen's info.c, the Maulotaur mobjinfo entry specifies doomednum 9,
		   but the Maulotaur is only summonable and 9 is also used for a mossy rock */
		if (type->ttyp == THING_MOSSYROCKMED &&
		    strcmp("MNTR", sprnames[states[state].sprite]) == 0)
			continue;
		/* also, the Waterfall sound mobjinfo entry specifies doomednum 41, but this
		   is a Heretic remnant not used in Hexen, and 41 is also used for a mushroom */
		if (type->ttyp == THING_MEDIUMMUSHROOM &&
		    strcmp("TLGL", sprnames[states[state].sprite]) == 0)
			continue;
#endif

#ifdef SFINFO
		/* in Strife's info.c, a bloodied corpse mobjinfo entry specifies doomednum 54,
		   but that object is not placeable and 54 is also used for the Aztec pillar */
		if (type->ttyp == THING_AZTECPILLAR &&
		    strcmp("DEAD", sprnames[states[state].sprite]) == 0)
			continue;

#ifdef MOBJTRANSLATION
		/* skip Veteran Ed. if printing all types by number */
		if (class == -1 && type->tcls == ThClassSnds)
			continue;
#endif
#endif

		flgs = LookupFlags( mobjinfo[type->objt].flags, type->name);

		/* check for things not using a sprite at all */
		if (type->ttyp == THING_DEATHMATCH || type->ttyp == THING_TELEPORT

#ifdef DMINFO
		    || type->ttyp == THING_SPAWNSPOT || type->ttyp == THING_BOSSSHOOT

#elif HTINFO
		    || type->ttyp == THING_DSPARILSPOT || type->ttyp == THING_PODGENERATOR
		    || type->tcls == ThClassSnds

#elif HXINFO
		    || type->ttyp == THING_BATSPAWNER || type->ttyp == THING_FOGSPAWNER
		    || type->ttyp == THING_LEAFSPAWNER || type->ttyp == THING_MAPSPOT
		    || type->ttyp == THING_MAPSPOTGRAV || type->ttyp == THING_POLYANCHOR
		    || type->ttyp == THING_POLYSPAWN || type->ttyp == THING_POLYSPAWNCR
		    || type->tcls == ThClassSnds

#elif SFINFO
		    || type->ttyp == THING_ORESPAWNER || type->ttyp == THING_ZOMBIESPAWN
#endif
		   )
		{
			frm = NULL;
		}
		else
		{
			TallyFrames( state, FALSE);
			num = SpriteCount();
			frm = SpriteFrames( num, FALSE);
		}

		/* count sprite prefix in each master directory (until found) and
		   thing in levels of each IWAD */
		wad = 0;
		sprwad = -1;
		foundspr = FALSE;
		curw = WadFileList;
		while (curw)
		{
			/* imply sprite-less thing is found */
			if (!frm)
			{
				foundspr = TRUE;
				sprwad = wad;
			}
			/* if sprite was already found in a more limited IWAD, it'll be
			   in the full IWAD too */
			else if (!foundspr)
			{
				/* search this IWAD's sprites section */
				lmp = CountSpriteLumps( curw->masterdir, sprnames[states[state].sprite]);
				if (lmp != 0)
				{
					foundspr = TRUE;
					sprwad = wad;
					if (Verb)
						fprintf( stderr, "%s:\t%5d\tsprite %s\n",
						         curw->filename, type->ttyp, sprnames[states[state].sprite]);

					/* check for additional sprites beyond default pattern */
					if (lmp > num
					    /* decorations are not interactive, but often use one out of a series */
					    && type->tcls != ThClassDeco

#ifdef HXINFO
					    /* these lit/unlit objects use one/multiple out of a series */
					    && type->ttyp != THING_CAULDRONLIT && type->ttyp != THING_CAULDRONULT
					    && type->ttyp != THING_MINOTSTATLIT && type->ttyp != THING_MINOTSTATULT
					    && type->ttyp != THING_TWINEDTORCHLIT && type->ttyp != THING_TWINEDTORCHULT

#elif SFINFO
					    /* plenty obstacles use one out of a series, but shootable barrels are animated */
					    && (type->tcls != ThClassObst ||
					        type->ttyp == THING_BARRELSTRIFE || type->ttyp == THING_WOODENBARREL)
					    /* (broken) power coupling use 1-2 out of 3 */
					    && type->ttyp != THING_POWERCOUPL && type->ttyp != THING_BRKNPWRCOUPL
					    /* some weapons use one/multiple out of a series */
					    && type->tcls != ThClassWeap
#endif
					   )
					{
						FreeMemory( frm);
						/* mark sequence as interactive with a '+' */
						frm = SpriteFrames( num, TRUE);
					}
				}
			}

			/* search this IWAD's levels */
			foundlvl = SearchLevels( curw,
				(curw->gamevers == 0x02 || curw->gamevers >= 0x20) ? "MAP##" : "E#M#",
				type->ttyp);

			/* done if sprite and thing are found in IWAD */
			if (foundspr && foundlvl)	
				break;

			wad++;
			curw = curw->next;
		}

		/* set IWAD version */
		switch (wad)
		{
#ifdef DMINFO
			case 0: vers = "S"; break; // shareware
			case 1: vers = "R"; break; // registered
			case 2: vers = "2"; break; // II

#elif HTINFO
			case 0: vers = "S"; break; // shareware
			case 1: vers = "R"; break; // registered
			case 2: vers = "X"; break; // SSR eXpansion: registered + more maps

#elif HXINFO
			case 0: vers = "D"; break; // demo
			case 1: vers = "C"; break; // commercial
			case 2: vers = "X"; break; // DDC eXpansion: no sprites, only maps

#elif SFINFO
			case 0: vers = "D"; break; // demo
			case 1: vers = "C"; break; // commercial
#ifdef MOBJTRANSLATION
			case 2: vers = "V"; break; // Veteran Ed: has only sprites not in commercial
#endif
#endif

			default:
				vers = "-";

#ifdef HXINFO
				/* present in demo but only in coop mode */
				if (type->ttyp == THING_MYSTAMBINC)
				{
					vers = "D<sup>#</sup>";
				}
				/* all sounds present in demo */
				if (type->tcls == ThClassSnds)
				{
					vers = "D<sup>#</sup>";
				}
				else
				{
					/* if sprite was found but thing not in levels, use sprite IWAD */
					if (foundspr && !foundlvl)
						switch (sprwad)
						{
							case 0: vers = "D<sup>#</sup>"; break;
							case 1: vers = "C<sup>#</sup>"; break;
							default: vers = "-";
						}
				}

#elif SFINFO
				/* all player, acolyte, peasant & misc. sprites present in demo */
				if (strcmp(sprnames[states[state].sprite], "PLAY") == 0 ||
				    strcmp(sprnames[states[state].sprite], "AGRD") == 0 ||
				    strcmp(sprnames[states[state].sprite], "PEAS") == 0 ||
				    type->ttyp == THING_ROCK1 || type->ttyp == THING_STALAGMSML ||
				    type->ttyp == THING_ROCK2 || type->ttyp == THING_BARRCOLUMN ||
				    type->ttyp == THING_ROCK4 || type->ttyp == THING_OUTSIDELAMP ||
				    type->ttyp == THING_RUBBLE6 || type->ttyp == THING_RUBBLE8 ||
				    type->ttyp == THING_TRAY)
				{
					vers = "D<sup>#</sup>";
				}
				/* enemies, weapons & keys are spawned if not present in levels */
				else if (type->tcls == ThClassMnst || type->tcls == ThClassWeap ||
				         type->tcls == ThClassKeys)
				{
					vers = "C";
				}
				/* obstacle & decoration sprites present in commercial */
				else
				{
					vers = "C<sup>#</sup>";
				}
#endif
		}

		printf( "|-\n"
		        "| align=\"right\" | %d\n"
		        "| align=\"right\" | %X\n"
		        "| align=\"center\" | %s\n"
		        "| align=\"right\" | %d\n"
		        "| align=\"right\" | %d\n"
		        "| %s\n"
		        "| %s\n"
		        "| %s\n"
		        "| %s\n",

			type->ttyp, type->ttyp, vers,
			mobjinfo[type->objt].radius / FRACUNIT,
			mobjinfo[type->objt].height / FRACUNIT,
			(!frm ? "''none''" : sprnames[states[state].sprite]),
			(!frm ? "-" : frm),
			flgs, LookupThing( type->ttyp, TRUE));

		if (frm)
			FreeMemory( frm);
		FreeMemory( flgs);
	}

	/* output table footer */
	printf( "|}\n\n");
}

void PrintAppearances( int class)
{
	typedata_t *type;
	WadPtr curw;
	int obj;
	char appears[30];
	Bool first;

	printf("<pre>\n");
	/* output list with 3 columns */
	for (obj = 0; obj < numtypes; obj++)
	{
		type = typedata[obj];
		if (class != -1 && class != type->tcls)
			continue;

		memset(appears, 0, 30);
		first = TRUE;
		curw = WadFileList;
		while (curw)
		{
			if (first)
				first = FALSE;
			else
				strcat( appears, " ");

			/* search this IWAD's levels */
			if (SearchLevels( curw,
			    (((curw->gamevers & 0xF0) == 0x00 && (curw->gamevers & 0x02) == 0x02) ||
			     curw->gamevers >= 0x20) ? "MAP##" : "E#M#",
			    type->ttyp))
				switch (curw->gamevers)
				{
					case 0x00: strcat( appears, "S"); break;
					case 0x01: strcat( appears, "R"); break;
					case 0x04: strcat( appears, "U"); break;
					case 0x02: strcat( appears, "2"); break;
					case 0x06: strcat( appears, "T"); break;
					case 0x07: strcat( appears, "P"); break;
					case 0x82: strcat( appears, "6"); break;
					case 0x88: strcat( appears, "L"); break;
					case 0x10: strcat( appears, "S"); break;
					case 0x11: strcat( appears, "R"); break;
					case 0x14: strcat( appears, "X"); break;
					case 0x40: strcat( appears, "D"); break;
					case 0x42: strcat( appears, "C"); break;
					case 0x48: strcat( appears, "X"); break;
					case 0x20: strcat( appears, "D"); break;
					case 0x22: strcat( appears, "C"); break;
					case 0x28: strcat( appears, "V"); break;
					default: strcat( appears, "?"); break;
				}
			else
				strcat( appears, " ");

			curw = curw->next;
		}

		printf( "%5d  %-18s %s\n", type->ttyp, appears, LookupThing( type->ttyp, FALSE));
	}
	printf("</pre>\n");
}

void AppearsIn( int dedn)
{
	WadPtr curw;
	int wad;
	Bool first = TRUE;

	memset( fieldlist, 0, LISTSIZE);
	curw = WadFileList;
	while (curw)
	{
		/* search this IWAD's levels */
		if (SearchLevels( curw,
		    (((curw->gamevers & 0xF0) == 0x00 && (curw->gamevers & 0x02) == 0x02) || curw->gamevers >= 0x20) ? "MAP##" : "E#M#",
		    dedn))
		{
			if (first)
				first = FALSE;
			else
				strcat( fieldlist, "<br>");

			switch (curw->gamevers)
			{
				case 0x00: strcat( fieldlist, "[[Shareware]] Doom"); break;
				case 0x01: strcat( fieldlist, "[[Doom]]/[[Ultimate Doom]]"); break;
				//case 0x01: strcat( fieldlist, "[[Doom|Registered Doom]]"); break;
				//case 0x04: strcat( fieldlist, "[[Ultimate Doom]] [[Thy Flesh Consumed|E4]]"); break;
				case 0x02: strcat( fieldlist, "[[Doom II]]/[[Final Doom]]"); break;
				//case 0x02: strcat( fieldlist, "[[Doom II]]"); break;
				//case 0x06: strcat( fieldlist, "[[TNT: Evilution]]"); break;
				//case 0x07: strcat( fieldlist, "[[Plutonia Experiment]]"); break;
				case 0x82: strcat( fieldlist, "[[Doom 64]]/[[The Lost Levels|Lost Levels]]"); break;
				//case 0x82: strcat( fieldlist, "[[Doom 64]]"); break;
				//case 0x88: strcat( fieldlist, "[[The Lost Levels]]"); break;
				case 0x10: strcat( fieldlist, "[[Shareware]] Heretic"); break;
				case 0x11: strcat( fieldlist, "[[Heretic]]/[[Shadow of the Serpent Riders|SSR expansion]]"); break;
				//case 0x11: strcat( fieldlist, "[[Heretic|Registered Heretic]]"); break;
				//case 0x14: strcat( fieldlist, "Heretic [[Shadow of the Serpent Riders|SSR expansion]]"); break;
				case 0x40: strcat( fieldlist, "Hexen demo"); break;
				case 0x42: strcat( fieldlist, "[[Hexen]]/[[Deathkings of the Dark Citadel|DDC expansion]]"); break;
				//case 0x42: strcat( fieldlist, "[[Hexen]] full version"); break;
				//case 0x48: strcat( fieldlist, "Hexen [[Deathkings of the Dark Citadel|DDC expansion]]"); break;
				case 0x20: strcat( fieldlist, "Strife demo"); break;
				case 0x22: strcat( fieldlist, "[[Strife]] full version"); break;
				case 0x28: strcat( fieldlist, "Strife [[Strife: Veteran Edition|Veteran Ed.]]"); break;
				default: break;
			}
		}

		wad++;
		curw = curw->next;
	}

	tpldata = TMPL_add_var( tpldata, "APPEARS", fieldlist, NULL);
}

/* add formatted integer as string to template variables */
TMPL_varlist *tmpl_add_int( TMPL_varlist *tpl, char *name, char *fmt, BCLNG value)
{
	char strbuf[12];

	memset( strbuf, 0, sizeof(strbuf));
	snprintf( strbuf, sizeof(strbuf), fmt, value);

	return TMPL_add_var( tpl, name, strbuf, NULL);
}

/* add formatted float as string to template variables */
TMPL_varlist *tmpl_add_float( TMPL_varlist *tpl, char *name, char *fmt, float value)
{
	char strbuf[12];

	memset( strbuf, 0, sizeof(strbuf));
	snprintf( strbuf, sizeof(strbuf), fmt, value);

	return TMPL_add_var( tpl, name, strbuf, NULL);
}

/* vim:set noexpandtab tabstop=2 softtabstop=2 shiftwidth=2: */
