/*
 *  acm : an aerial combat simulator for X
 *  Copyright (C) 1991-1998  Riley Rainey
 *
 *  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; version 2 dated June, 1991.
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program;  if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave., Cambridge, MA 02139, USA.
 */

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>

#include "../util/error.h"
#include "../util/memory.h"
#include "../util/reader.h"
#include "../util/units.h"
#include "init.h"
#include "planes.h"
#include "pm.h"
#include "weapon.h"

#define inventory_IMPORT
#include "inventory.h"

/**
 * The entity_object_map is used to map DIS entity types to a
 * displayable object.
 */
typedef struct _entity_object_map {
	dis_entity_type entity_type;
	dis_entity_type entity_mask;
	char *          object_name;
	VObject         *obj;
} entity_object_map;

/**
 * Currently active craft types. This double-linked list includes:
 * - Aircraft types loaded from the inventory file.
 * - Missiles created in the respective modules.
 * - Drop bombs created in the ccip module.
 * - Cannon shells created by the m61a1 module.
 * - Runways, the name being the name of the airport and the polygons being
 *   one or several runways of that airport.
 * - Features.
 */
static craftType *craft_types;

// Recycled craft types.
static craftType *craft_types_pool;


craftType * inventory_craftTypeNew(zone_Type *zone)
{
	craftType *c;
	if( craft_types_pool == NULL ){
		c = memory_allocate(sizeof(craftType), NULL);
		memset(c, 0, sizeof(craftType));
	} else {
		c = craft_types_pool;
		craft_types_pool = c->next;
	}
	c->next = craft_types;
	c->prev = NULL;
	c->zone = zone;
	if( craft_types != NULL )
		craft_types->prev = c;
	craft_types = c;
	return c;
}


/**
 * Removes a craft type and put into the recycle pool.
 * @param c
 */
void inventory_craftTypeRelease(craftType *c)
{
	if( c->prev == NULL ){
		if( c->next != NULL )
			c->next->prev = NULL;
		craft_types = c->next;
	} else {
		c->prev->next = c->next;
		if( c->next != NULL )
			c->next->prev = c->prev;
	}
	
	memory_dispose(c->name);
	if( c->object != NULL )
		VDestroyObject (c->object);
	memory_dispose(c->objname);
	memory_dispose(c->description);
	memory_dispose(c->modelname);
	memory_dispose(c->CLift);
	memory_dispose(c->CDb);
	memory_dispose(c->CnBeta);
	memory_dispose(c->ClBeta);
	memory_dispose(c->Thrust);
	memory_dispose(c->ABThrust);
	int i;
	for (i=0; i<manifest_STATIONS; ++i) {
		memory_dispose(c->station[i].type);
	}
	memset(c, 0, sizeof(craftType));
	c->next = craft_types_pool;
	craft_types_pool = c;
}



struct lex_record;

typedef enum {
	RESERVED_WORD,
	vtDOUBLE,
	vtLONG,
	vtANGLE,
	vtNMILES,
	vtKNOTS,
	vtTABLE,
	vtPOINT,
	vtSTRING,
	vtSTATION,
	vtENTITY
} value_type;

typedef enum {
	Nil,
	EndOfFile,

/*
 *  Reserved words must be added to this section
 */

	TOKEN_NUMBER,
	RW_PUNCTUATION,
	TOKEN_STRING,
	TOKEN_LEFT_BRACE,
	TOKEN_RIGHT_BRACE,
	TOKEN_COMMA,
	TOKEN_COLON,
	TOKEN_SEMICOLON,
	RW_INCLUDE,
	RW_USE,
	RW_AIRCRAFT,
	RW_DESCRIPTION,
	RW_ENGINE_TYPE,
	RW_HARDPOINT,
	RW_KINETIC,
	RW_EXPLOSIVE,

/*
 *  Fields in the craftType structure must be added in this section
 */

	Object,
	InternalModel,
	AspectRatio,
	BetaStall,
	CLift,
	CDb,
	CnBeta,
	ClBeta,
	CDBOrigin,
	CDBFactor,
	CDBPhase,
	CYbeta,
	Clda,
	Cldr,
	Clp,
	Cmq,
	Cnr,
	maxAileron,
	maxRudder,
	effElevator,
	effRudder,
	Ixx,
	Iyy,
	Izz,
	CmAlpha,
	MaxFlap,
	CFlap,
	FlapDrag,
	FlapRate,
	CGearDrag,
	GearRate,
	MaxSpeedBrake,
	CSpeedBrake,
	SpeedBrakeRate,
	SpeedBrakeIncr,
	WingArea,
	WingSpan,
	WingChord,
	WingHeight,
	EmptyWeight,
	MaxLoadZPositive,
	MaxLoadZNegative,
	MaxFuel,
	EngineType,
	MaxThrust,
	MaxABThrust,
	Thrust,
	ABThrust,
	HasThrustReverser,
	EngineLag,
	SpFuelConsump,
	SpABFuelConsump,
	GroundingPoint,
	ViewPoint,
	MuStatic,
	MuKinetic,
	MuBStatic,
	MuBKinetic,
	MaxNWDef,
	Rm,
	Rn,
	Dm,
	Dn,
	Km,
	Kn,
	Gm,
	Gn,
	CmMax,
	CnMax,
	TailExtent,
	MTOW,
	Vs0,
	Vs1,
	Vfe,
	Vno,
	Vne,
	StructurePts,
	RadarOutput,
	RadarTRange,
	RadarDRange,
	TEWSThreshold,
	HardPoint0,
	HardPoint1,
	HardPoint2,
	HardPoint3,
	HardPoint4,
	HardPoint5,
	HardPoint6,
	HardPoint7,
	HardPoint8,
	WeaponStation,
	WeaponCount,
	DISEntityType,
	Description
} field_id;

struct keyword_info {
	char     *word;
	value_type type;
	field_id  id;
	char     *ptr;
};


static craftType xxx;

#define A(x)	(char *) x

#define B(x) ParseEngineType(&c.engineType)

static struct keyword_info keywords[] =
{
	{"Description", vtSTRING, Description, A(&xxx.description)},
	{"Object", vtSTRING, Object, A(&xxx.objname)},
	{"InternalModel", vtSTRING, InternalModel, A(&xxx.modelname)},
	{"AspectRatio", vtDOUBLE, AspectRatio, A(&xxx.aspectRatio)},
	{"BetaStall", vtANGLE, BetaStall, A(&xxx.betaStall)},
	{"CLift", vtTABLE, CLift, A(&xxx.CLift)},
	{"CDb", vtTABLE, CDb, A(&xxx.CDb)},
	{"CnBeta", vtTABLE, CnBeta, A(&xxx.CnBeta)},
	{"ClBeta", vtTABLE, ClBeta, A(&xxx.ClBeta)},
	{"CDBOrigin", vtDOUBLE, CDBOrigin, A(&xxx.CDBOrigin)},
	{"CDBFactor", vtDOUBLE, CDBFactor, A(&xxx.CDBFactor)},
	{"CDBPhase", vtANGLE, CDBPhase, A(&xxx.CDBPhase)},
	{"CYBeta", vtDOUBLE, CYbeta, A(&xxx.CYbeta)},
	{"Clda", vtDOUBLE, Clda, A(&xxx.Clda)},
	{"Cldr", vtDOUBLE, Cldr, A(&xxx.Cldr)},
	{"Clp", vtDOUBLE, Clp, A(&xxx.Clp)},
	{"Cmq", vtDOUBLE, Cmq, A(&xxx.Cmq)},
	{"Cnr", vtDOUBLE, Cnr, A(&xxx.Cnr)},
	{"MaxAileron", vtANGLE, maxAileron, A(&xxx.maxAileron)},
	{"MaxRudder", vtANGLE, maxRudder, A(&xxx.maxRudder)},
	{"EffElevator", vtDOUBLE, effElevator, A(&xxx.effElevator)},
	{"EffRudder", vtDOUBLE, effRudder, A(&xxx.effRudder)},
	{"Ixx", vtDOUBLE, Ixx, A(&xxx.I.m[0][0])},
	{"Iyy", vtDOUBLE, Iyy, A(&xxx.I.m[1][1])},
	{"Izz", vtDOUBLE, Izz, A(&xxx.I.m[2][2])},
	{"CmAlpha", vtDOUBLE, CmAlpha, A(&xxx.cmSlope)},
	{"MaxFlap", vtANGLE, MaxFlap, A(&xxx.maxFlap)},
	{"CFlap", vtDOUBLE, CFlap, A(&xxx.cFlap)},
	{"CFlapDrag", vtDOUBLE, FlapDrag, A(&xxx.cFlapDrag)},
	{"FlapRate", vtANGLE, FlapRate, A(&xxx.flapRate)},
	{"CGearDrag", vtDOUBLE, CGearDrag, A(&xxx.cGearDrag)},
	{"GearRate", vtANGLE, GearRate, A(&xxx.gearRate)},
	{"MaxSpeedBrake", vtANGLE, MaxSpeedBrake, A(&xxx.maxSpeedBrake)},
	{"CSpeedBrake", vtDOUBLE, CSpeedBrake, A(&xxx.cSpeedBrake)},
	{"SpeedBrakeRate", vtANGLE, SpeedBrakeRate, A(&xxx.speedBrakeRate)},
	{"SpeedBrakeIncr", vtANGLE, SpeedBrakeIncr, A(&xxx.speedBrakeIncr)},
	{"WingArea", vtDOUBLE, WingArea, A(&xxx.wingS)},
	{"WingHalfSpan", vtDOUBLE, WingSpan, A(&xxx.wings)},
	{"WingHeight", vtDOUBLE, WingHeight, A(&xxx.wingh)},
	{"Chord", vtDOUBLE, WingChord, A(&xxx.c)},
	{"EmptyWeight", vtDOUBLE, EmptyWeight, A(&xxx.emptyWeight)},
	{"MaxLoadZPositive", vtDOUBLE, MaxLoadZPositive, A(&xxx.maxLoadZPositive)},
	{"MaxLoadZNegative", vtDOUBLE, MaxLoadZNegative, A(&xxx.maxLoadZNegative)},
	{"MaxFuel", vtDOUBLE, MaxFuel, A(&xxx.maxFuel)},
	{"MaxThrust", vtDOUBLE, MaxThrust, A(&xxx.maxThrust)},
	{"MaxABThrust", vtDOUBLE, MaxABThrust, A(&xxx.maxABThrust)},
	{"Thrust", vtTABLE, Thrust, A(&xxx.Thrust)},
	{"ABThrust", vtTABLE, ABThrust, A(&xxx.ABThrust)},
	{"HasThrustReverser", vtLONG, HasThrustReverser, A(&xxx.hasThrustReverser)},
	{"EngineLag", vtDOUBLE, EngineLag, A(&xxx.engineLag)},
	{"SpFuelConsump", vtDOUBLE, SpFuelConsump, A(&xxx.spFuelConsump)},
	{"SpABFuelConsump", vtDOUBLE, SpABFuelConsump, A(&xxx.spABFuelConsump)},
	{"ViewPoint", vtPOINT, ViewPoint, A(&xxx.viewPoint)},
	{"MuStatic", vtDOUBLE, MuStatic, A(&xxx.muStatic)},
	{"MuKinetic", vtDOUBLE, MuKinetic, A(&xxx.muKinetic)},
	{"MuBStatic", vtDOUBLE, MuBStatic, A(&xxx.muBStatic)},
	{"MuBKinetic", vtDOUBLE, MuBKinetic, A(&xxx.muBKinetic)},
	{"MaxNWDef", vtANGLE, MaxNWDef, A(&xxx.maxNWDef)},
	{"Rm", vtPOINT, Rm, A(&xxx.rm)},
	{"Rn", vtPOINT, Rn, A(&xxx.rn)},
	{"Dm", vtDOUBLE, Dm, A(&xxx.Dm)},
	{"Dn", vtDOUBLE, Dn, A(&xxx.Dn)},
	{"Km", vtDOUBLE, Km, A(&xxx.Km)},
	{"Kn", vtDOUBLE, Kn, A(&xxx.Kn)},
	{"Gm", vtDOUBLE, Gm, A(&xxx.Gm)},
	{"Gn", vtDOUBLE, Gn, A(&xxx.Gn)},
	{"CmMax", vtDOUBLE, CmMax, A(&xxx.cmMax)},
	{"CnMax", vtDOUBLE, CnMax, A(&xxx.cnMax)},
	{"TailExtent", vtPOINT, TailExtent, A(&xxx.tailExtent)},
	{"MTOW", vtDOUBLE, MTOW, A(&xxx.MTOW)},
	{"Vs0", vtDOUBLE, Vs0, A(&xxx.Vs0)},
	{"Vs1", vtDOUBLE, Vs1, A(&xxx.Vs1)},
	{"Vfe", vtDOUBLE, Vfe, A(&xxx.Vfe)},
	{"Vno", vtDOUBLE, Vno, A(&xxx.Vno)},
	{"Vne", vtDOUBLE, Vne, A(&xxx.Vne)},
	{"StructurePoints", vtLONG, StructurePts, A(&xxx.structurePts)},
	{"RadarOutput", vtDOUBLE, RadarOutput, A(&xxx.radarOutput)},
	{"RadarTRange", vtNMILES, RadarTRange, A(&xxx.radarTRange)},
	{"RadarDRange", vtNMILES, RadarDRange, A(&xxx.radarDRange)},
	{"TEWSThreshold", vtDOUBLE, TEWSThreshold, A(&xxx.TEWSThreshold)},
	{"HardPoint1", vtPOINT, HardPoint1, A(&xxx.wStation[1])},
	{"HardPoint2", vtPOINT, HardPoint2, A(&xxx.wStation[2])},
	{"HardPoint3", vtPOINT, HardPoint3, A(&xxx.wStation[3])},
	{"HardPoint4", vtPOINT, HardPoint4, A(&xxx.wStation[4])},
	{"HardPoint5", vtPOINT, HardPoint5, A(&xxx.wStation[5])},
	{"HardPoint6", vtPOINT, HardPoint6, A(&xxx.wStation[6])},
	{"HardPoint7", vtPOINT, HardPoint7, A(&xxx.wStation[7])},
	{"HardPoint8", vtPOINT, HardPoint8, A(&xxx.wStation[8])},
	{"HardPoint0", vtPOINT, HardPoint0, A(&xxx.wStation[0])},
	{"WeaponCount", vtLONG, WeaponCount, A(&xxx.sCount)},
	{"WeaponStation", vtSTATION, WeaponStation, 0},

	{"include", RESERVED_WORD, RW_INCLUDE, 0},
	{"use", RESERVED_WORD, RW_USE, 0},
	{"aircraft", RESERVED_WORD, RW_AIRCRAFT, 0},
	{"description", RESERVED_WORD, RW_DESCRIPTION, 0},
	{"EngineType", RESERVED_WORD, RW_ENGINE_TYPE, 0},
	{"hardpoint", RESERVED_WORD, RW_HARDPOINT, 0},
    {"kinetic", RESERVED_WORD, RW_KINETIC, 0},
	{"explosive", RESERVED_WORD, RW_EXPLOSIVE, 0},
	{"blast", RESERVED_WORD, RW_EXPLOSIVE, 0},

	{"DISEntityType", vtENTITY, DISEntityType, A(&xxx.entityType)},
	{"DISAltEntityType", vtENTITY, DISEntityType, A(&xxx.altEntityType)},

	{NULL, RESERVED_WORD, Nil, 0}
};

typedef union {
	struct keyword_info *kw;
	double    double_value;
	interpolate_Table   *table_value;
	char     *string_value;
	long      long_value;
} lex_val;

static lex_val lex_value;

struct lex_record {
	char     *filename;
	FILE     *f;
	int       lineno;
	int       lookahead_valid;
	int       lookahead;
	int       stack_top;
	lex_val   value_stack[16];
};


#define push_value(p, type, val) \
	p->value_stack[p->stack_top++].type = val

#define pop_value(p, type) (p->value_stack[--p->stack_top].type)

#define input(p)	(p->lookahead_valid \
				? (p->lookahead_valid = 0, p->lookahead) \
				: (((p->lookahead = getc(p->f)) == '\n') \
					? (p->lineno++, p->lookahead) \
					: p->lookahead))

#define unput(p, c)	{ p->lookahead = c; p->lookahead_valid = 1; }

static char token[256];
static int token_length = 0;

#define STATE_INITIAL	0
#define STATE_WORD	    1
#define STATE_NUMBER	2
#define STATE_STRING	3
#define STATE_COMMENT	4


static struct lex_record *
OpenSourceFile(char *name)
{
	struct lex_record *p;
	FILE     *f;

	if ((f = fopen(name, "r")) == (FILE *) NULL) {
		return (struct lex_record *) NULL;
	}

	p = (struct lex_record *) memory_allocate(sizeof(struct lex_record), NULL);

	p->filename = memory_strdup(name);
	p->lineno = 1;
	p->lookahead_valid = 0;
	p->stack_top = 0;
	p->f = f;

	return p;
}

static void
ParseError(struct lex_record *p, char *s)
{
	error_external("%s:%): %s", p->filename, p->lineno, s);
}

static field_id
NextTokenx(struct lex_record *p)
{
	register int c, state = STATE_INITIAL;
	register struct keyword_info *q;

	token_length = 0;

	while ((c = input(p)) != EOF) {

		switch (state) {

		case STATE_INITIAL:

			if (isalpha(c)) {
				token[token_length++] = c;
				state = STATE_WORD;
			}
			else if (isspace(c)) {
				continue;
			}
			else if (isdigit(c) || c == '-' || c == '+' || c == '.') {
				token[token_length++] = c;
				state = STATE_NUMBER;
			}
			else if (c == '"') {
				state = STATE_STRING;
			}
			else if (c == '#') {
				state = STATE_COMMENT;
			}
			else {
				token[0] = c;
				token[1] = '\0';
				switch (c) {
				case ',':
					return TOKEN_COMMA;
				case ':':
					return TOKEN_COLON;
				case ';':
					return TOKEN_SEMICOLON;
				case '{':
					return TOKEN_LEFT_BRACE;
				case '}':
					return TOKEN_RIGHT_BRACE;
				default:
					ParseError(p, "invalid character");
					state = STATE_INITIAL;
				}
			}
			break;

		case STATE_WORD:
		case STATE_NUMBER:
			if (isspace(c) || c == ':' || c == ',' || c == '{' || c == '}') {
				token[token_length] = '\0';
				unput(p, c);
				if (state == STATE_WORD) {
					for (q = keywords; q->word; ++q) {
						if (strcmp(q->word, token) == 0) {
							lex_value.kw = q;
							return q->id;
						}
					}
					return Nil;
				}
				else {
					errno = 0;
					char *end;
					lex_value.double_value = strtod( token, &end );
					if( end == token || *end != 0 )
						error_external("%s:%d: invalid syntax for number: %s", p->filename, p->lineno, token);
					if (errno == ERANGE)
						error_external("%s:%d: floating point number out of the range: %s", p->filename, p->lineno, token);
					return TOKEN_NUMBER;
				}
			}
			else {
				token[token_length++] = c;
			}
			break;

		case STATE_STRING:

			switch (c) {

			case '"':
				token[token_length] = '\0';
				return TOKEN_STRING;

			case '\n':
				ParseError(p, "strings cannot span a line");
				unput(p, c);
				state = STATE_INITIAL;
				break;

			case '\\':

				switch (c = input(p)) {

				case EOF:
					ParseError(p, "Premature End-of-file");
					break;

				case 'n':
					token[token_length++] = '\n';
					break;

				case 't':
					token[token_length++] = '\t';
					break;

				default:
					token[token_length++] = c;
					break;
				}

			default:
				token[token_length++] = c;
				break;
			}
			break;

		case STATE_COMMENT:
			while (c != EOF) {
				if (c == '\n')
					break;
				c = input(p);
			}
			state = STATE_INITIAL;
			break;

		}
	}

	return EndOfFile;
}

static field_id
NextToken(struct lex_record * p)
{
	field_id  t;

	t = NextTokenx(p);
	return t;
}

/*
 *  Skip to the specified token, if token is Nil, then skip to the end of the
 *  current line.
 */

static void
Resync(struct lex_record *p, field_id token)
{
	field_id  t;
	int       c;

	if (token == Nil) {
		while ((c = input(p)) != EOF) {
			if (c == '\n')
				break;
		}
	}
	else {
		while ((t = NextToken(p)) != EndOfFile) {
			if (t == token)
				break;
		}
	}

}

/*
 *  Parse syntax:  '{' number ',' number ',' number '}'
 */

static int
ParsePoint(struct lex_record *p)
{

	if (NextToken(p) != TOKEN_LEFT_BRACE) {
		Resync(p, TOKEN_RIGHT_BRACE);
		return -1;
	}

	if (NextToken(p) != TOKEN_NUMBER) {
		Resync(p, TOKEN_RIGHT_BRACE);
		return -1;
	}

	push_value(p, double_value, lex_value.double_value);

	if (NextToken(p) != TOKEN_COMMA) {
		Resync(p, TOKEN_RIGHT_BRACE);
		return -1;
	}

	if (NextToken(p) != TOKEN_NUMBER) {
		Resync(p, TOKEN_RIGHT_BRACE);
		return -1;
	}

	push_value(p, double_value, lex_value.double_value);

	if (NextToken(p) != TOKEN_COMMA) {
		Resync(p, TOKEN_RIGHT_BRACE);
		return -1;
	}

	if (NextToken(p) != TOKEN_NUMBER) {
		Resync(p, TOKEN_RIGHT_BRACE);
		return -1;
	}

	push_value(p, double_value, lex_value.double_value);

	if (NextToken(p) != TOKEN_RIGHT_BRACE) {
		Resync(p, TOKEN_RIGHT_BRACE);
		return -1;
	}

	return 0;
}

static int
ParseValue(struct lex_record *p)
{
	if (NextToken(p) != TOKEN_NUMBER) {
		Resync(p, Nil);
		return -1;
	}
	push_value(p, double_value, lex_value.double_value);
	return 0;
}

/*
 *  Parse syntax:  n.n.n.n.n.n.n
 */

static int
ParseDISEntityType(struct lex_record *p)
{

	long      i, av;
	field_id  t;

	for (i = 0; i < 7; ++i) {

	        t = NextToken(p);

		if (t != TOKEN_NUMBER) {
		  if (t == EndOfFile) {
		    return -2;
		  }
		  Resync(p, Nil);
		  return -1;
		}

		if (lex_value.double_value > 0) {
		  av = (long) (lex_value.double_value + 0.5);
		}
		else {
		   av = (long) (lex_value.double_value - 0.5);
		}
		push_value(p, long_value, av);

		if (i < 6 && NextToken(p) != TOKEN_COLON) {
			Resync(p, Nil);
			return -1;
		}
	}

	return 0;
}


static int
ParseEntityMapEntry(struct lex_record *p, entity_object_map *po)
{
	long ival, result;

	memset (po, 0, sizeof(entity_object_map));

	result = ParseDISEntityType(p);

	/* end-of-file */
	if (result == -2) {
		return -1;
	}

	/* other error */
	if (result != 0) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.extra = 0;
	}
	else {
		po->entity_mask.extra = 1;
		po->entity_type.extra = (unsigned char) ival;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.specific = 0;
	}
	else {
		po->entity_mask.specific = 1;
		po->entity_type.specific = (unsigned char) ival;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.subcategory = 0;
	}
	else {
		po->entity_mask.subcategory = 1;
		po->entity_type.subcategory = (unsigned char) ival;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.category = 0;
	}
	else {
		po->entity_mask.category = 1;
		po->entity_type.category = (unsigned char) ival;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.country = 0;
	}
	else {
		po->entity_mask.country = 1;
		po->entity_type.country = (unsigned short) ival;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.domain = 0;
	}
	else {
		po->entity_mask.domain = 1;
		po->entity_type.domain = (unsigned char) ival;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.kind = 0;
	}
	else {
		po->entity_mask.kind = 1;
		po->entity_type.kind = (unsigned char) ival;
	}

	if (NextToken(p) != TOKEN_COMMA) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	if (NextToken(p) != TOKEN_STRING) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	po->object_name = memory_strdup( token );

	if (NextToken(p) != TOKEN_SEMICOLON) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	return 0;
}


entity_object_map *eo_map;
int eo_map_count;

static int inventory_compileMapEntities(char *name, int *count, entity_object_map **pmap)
{
	struct lex_record *p;
	int       code = 0;
	entity_object_map po;
	int n = 0;

	*pmap = NULL;
	*count = 0;

	if ((p = OpenSourceFile(name)) == NULL) {
		fprintf(stderr, "unable to open entity map file\n");
		return -1;
	}

	while (ParseEntityMapEntry(p, &po) == 0 && code == 0) {
		*pmap = memory_realloc(*pmap, sizeof(entity_object_map) * (n+1) );
		(*pmap)[n] = po;
		n++;
	}

	*count = n;

	fclose(p->f);
	memory_dispose(p->filename);
	memory_dispose((char *) p);
	return code;
}

/*
 *  munition_entity_type , warhead_type , explosion_diameter , damage_factor ;
 */
munition_map *mun_map;
int mun_map_count;

static int
ParseMunitionMapEntry(struct lex_record *p, munition_map *po)
{
	long ival, result;
	field_id t;

	memset (po, 0, sizeof(munition_map));

	result = ParseDISEntityType(p);

	/* end-of-file */
	if (result == -2) {
		return -1;
	}

	/* other error */
	if (result != 0) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.extra = 0;
	}
	else {
		po->entity_mask.extra = 1;
		po->entity_type.extra = (unsigned char) ival;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.specific = 0;
	}
	else {
		po->entity_mask.specific = 1;
		po->entity_type.specific = (unsigned char) ival;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.subcategory = 0;
	}
	else {
		po->entity_mask.subcategory = 1;
		po->entity_type.subcategory = (unsigned char) ival;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.category = 0;
	}
	else {
		po->entity_mask.category = 1;
		po->entity_type.category = (unsigned char) ival;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.country = 0;
	}
	else {
		po->entity_mask.country = 1;
		po->entity_type.country = (unsigned short) ival;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.domain = 0;
	}
	else {
		po->entity_mask.domain = 1;
		po->entity_type.domain = (unsigned char) ival;
	}

	ival = pop_value(p, long_value);
	if (ival == -1) {
		po->entity_mask.kind = 0;
	}
	else {
		po->entity_mask.kind = 1;
		po->entity_type.kind = (unsigned char) ival;
	}

	if (NextToken(p) != TOKEN_COMMA) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	if (NextToken(p) != TOKEN_NUMBER) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	/* warhead type */

	if (lex_value.double_value >= 0) {
		ival = (int) (lex_value.double_value + 0.5);
	}
	else {
		ival = (long)(lex_value.double_value - 0.5);
	}

	if (ival == -1) {
		po->warhead_mask = 0;
	}
	else {
		po->warhead_mask = 1;
		po->warhead_type = (unsigned short) ival;
	}

	if (NextToken(p) != TOKEN_COMMA) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	/* explosion diameter */

	if (NextToken(p) != TOKEN_NUMBER) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	po->explosion_diameter_meters = units_FEETtoMETERS(lex_value.double_value);

	if (NextToken(p) != TOKEN_COMMA) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	/* damage factor */

	if (NextToken(p) != TOKEN_NUMBER) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	po->damage_factor = lex_value.double_value;

	if (NextToken(p) != TOKEN_COMMA) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	/* warhead class: kinetic or explosive */

	t = NextToken(p);

	if (t != RW_KINETIC && t != RW_EXPLOSIVE) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	po->kinetic_flag = (t == RW_KINETIC) ? 1 : 0;

	if (NextToken(p) != TOKEN_SEMICOLON) {
		Resync(p, TOKEN_SEMICOLON);
		return -1;
	}

	return 0;
}

static int inventory_compileMunitionsMap(char *name, int *count, munition_map **pmap)
{
	struct lex_record *p;
	int       code = 0;
	munition_map po;
	int n = 0;

	*pmap = NULL;
	*count = 0;

	if ((p = OpenSourceFile(name)) == NULL) {
		fprintf(stderr, "unable to open entity map file\n");
		return -1;
	}

	while (ParseMunitionMapEntry(p, &po) == 0 && code == 0) {
		*pmap = memory_realloc(*pmap, sizeof(munition_map) * (n+1) );
		(*pmap)[n] = po;
		n++;
	}

	*count = n;

	fclose(p->f);
	memory_dispose(p->filename);
	memory_dispose((char *) p);
	return code;
}

/*
 *  Parse syntax:  '{' number_list '}'
 *
 *  Where number_list is a collection of zero or more comma separated
 *  numbers.  The list of numbers must be an even count.
 *
 */

static int
ParseTable(struct lex_record *p)
{
	field_id  t;
	double    x[64], y[64];
	int       count = 0, i;
	interpolate_Table   *table;
	interpolate_Entry   *entry;

	if (NextToken(p) != TOKEN_LEFT_BRACE) {
		Resync(p, TOKEN_RIGHT_BRACE);
		return -1;
	}

	while ((t = NextToken(p)) != TOKEN_RIGHT_BRACE) {

		if (t == EndOfFile)
			return -1;

		if (t == TOKEN_NUMBER) {

			if (count == 64) {
				ParseError(p, "too many table entries");
				return -1;
			}

			x[count] = lex_value.double_value;

			if (NextToken(p) != TOKEN_COMMA) {
				Resync(p, TOKEN_RIGHT_BRACE);
				return -1;
			}

			if (NextToken(p) != TOKEN_NUMBER) {
				Resync(p, TOKEN_RIGHT_BRACE);
				return -1;
			}
			y[count++] = lex_value.double_value;

			t = NextToken(p);

			if (t == TOKEN_RIGHT_BRACE)
				goto done;
			else if (t != TOKEN_COMMA) {
				Resync(p, TOKEN_RIGHT_BRACE);
				return -1;
			}
		}
		else {
			Resync(p, TOKEN_RIGHT_BRACE);
			return -1;
		}
	}

  done:

/*
 *  Build an interpolation table
 */

	table = interpolate_new(count - 1);
	entry = table->entry;
	table->minX = x[0];
	table->count = count - 1;
	table->entry = entry;

	for (i = 1; i < count; ++i) {
		entry[i - 1].x = x[i];
		entry[i - 1].m = ((y[i] - y[i - 1]) / (x[i] - x[i - 1]));
		entry[i - 1].b = (y[i] - (x[i] * entry[i - 1].m));
	}

	push_value(p, table_value, table);

	return 0;
}


/*
 *  Parse syntax:  number weapon-type number number number
 */

static int
ParseStation(struct lex_record *p)
{

	long      i, a1, b1, c1;
	char     *ptr;

	if (NextToken(p) != TOKEN_NUMBER) {
		Resync(p, Nil);
		return -1;
	}

	i = (long) (lex_value.double_value + 0.5);

	if (NextToken(p) != TOKEN_STRING) {
		Resync(p, Nil);
		return -1;
	}

	ptr = memory_strdup(token);

	if (NextToken(p) != TOKEN_NUMBER) {
		Resync(p, Nil);
		return -1;
	}

	a1 = (long) (lex_value.double_value + 0.5);

	if (NextToken(p) != TOKEN_NUMBER) {
		Resync(p, Nil);
		return -1;
	}

	b1 = (long) (lex_value.double_value + 0.5);

	if (NextToken(p) != TOKEN_NUMBER) {
		Resync(p, Nil);
		return -1;
	}

	c1 = (long) (lex_value.double_value + 0.5);

	push_value(p, long_value, c1);
	push_value(p, long_value, b1);
	push_value(p, long_value, a1);
	push_value(p, string_value, ptr);
	push_value(p, long_value, i);

	return 0;
}

/**
 * Reads next aircraft model.
 * The current token must be "aircraft" (RW_AIRCRAFT).
 */
static int
ParseAircraft(struct lex_record *p, craftType *c)
{
	field_id  t;
	long      n=0, i;
	double    d;
	VPoint    pt;
	interpolate_Table   *table;
	dis_entity_type entity;
	char      s[256];
	struct keyword_info *kw;
	craftType *used;

	// Parse all inside global "xxx" and, if we succeeded, copy into *c.
	memset(&xxx, 0, sizeof(xxx));
	xxx.engineType = inventory_NoEngine;

	if (NextToken(p) != TOKEN_STRING) {
		return -1;
	}

	xxx.name = memory_strdup(token);

	if (NextToken(p) != TOKEN_LEFT_BRACE) {
		return -1;
	}

	while ((t = NextToken(p)) != EndOfFile) {

		if (t >= Object || t == RW_USE || t == RW_ENGINE_TYPE ) {

			kw = lex_value.kw;

			switch (kw->type) {

			case RESERVED_WORD:

				/*
				 *  use "aircraft-type-name"
				 *
				 *  uses a previously defined aircraft as the starting point for defining another
				 */

				if (kw->id == RW_USE) {
					if ( NextToken(p) == TOKEN_STRING ) {
						char * ptmp = xxx.name;

						n = 1;
						used = inventory_craftTypeSearchByZoneAndName(NULL, token);
						if (used) {
							xxx = *used;
							xxx.CLift    = interpolate_clone (used->CLift   );
							xxx.CDb      = interpolate_clone (used->CDb     );
							xxx.CnBeta   = interpolate_clone (used->CnBeta  );
							xxx.ClBeta   = interpolate_clone (used->ClBeta  );
							xxx.Thrust   = interpolate_clone (used->Thrust  );
							xxx.ABThrust = interpolate_clone (used->ABThrust);
							//if (used->name) {
							//	xxx.name = memory_strdup( used->name );
							//}
							xxx.name = ptmp;
							if (used->description) {
								xxx.description = memory_strdup( used->description );
							}
							if (used->modelname) {
								xxx.modelname = memory_strdup( used->modelname );
							}
							if (used->objname) {
								xxx.objname = memory_strdup( used->objname );
							}
							for (i=0; i<manifest_STATIONS; ++i) {
								if ( used->station[i].type ) {
									xxx.station[i].type = memory_strdup( used->station[i].type) ;
								}
							}


							n = 0;

						}
						else {
							sprintf(s, "\"%s\" is not a valid aircraft type", token);
							ParseError(p, s);
						}
					}
					
				} else if (kw->id == RW_ENGINE_TYPE) {
					if ( NextToken(p) == TOKEN_STRING ) {
						if( strcmp(token, "NoEngine") == 0 )
							xxx.engineType = inventory_NoEngine;
						else if( strcmp(token, "GenericPistonEngine") == 0 )
							xxx.engineType = inventory_GenericPistonEngine;
						else if( strcmp(token, "GenericJetEngine") == 0 )
							xxx.engineType = inventory_GenericJetEngine;
						else if( strcmp(token, "GenericRocketEngine") == 0 )
							xxx.engineType = inventory_GenericRocketEngine;
						else {
							sprintf(s, "unknown engine type: \"%s\"", token);
							ParseError(p, s);
						}
							
					} else {
						sprintf(s, "unexpected token: \"%s\"", token);
						ParseError(p, s);
					}
					
				} else {
					sprintf(s, "unexpected token: \"%s\"", token);
					ParseError(p, s);
				}
				break;

			case vtSTRING:
				if (NextToken(p) == TOKEN_STRING) {
					n = 0;
					*((char **) kw->ptr) = memory_strdup(token);
				}
				else
					n = -1;
				break;

			case vtDOUBLE:
			case vtNMILES:
			case vtKNOTS:
				if ((n = ParseValue(p)) == 0) {
					d = pop_value(p, double_value);
					if (kw->type == vtNMILES)
						d *= units_NmToFeetFactor;
					else if (kw->type == vtKNOTS)
						d *= units_NmToFeetFactor / 3600;
					*((double *) kw->ptr) = d;
				}
				break;

			case vtANGLE:
				if ((n = ParseValue(p)) == 0) {
					d = units_DEGtoRAD(pop_value(p, double_value));
					*((double *) kw->ptr) = d;
				}
				break;

			case vtLONG:
				if ((n = ParseValue(p)) == 0) {
					d = pop_value(p, double_value);
					*((long *) kw->ptr) = (long) (d + 0.5);
				}
				break;

			case vtTABLE:
				if ((n = ParseTable(p)) == 0) {
					table = pop_value(p, table_value);
					*((interpolate_Table **) kw->ptr) = table;
				}
				break;

			case vtPOINT:
				if ((n = ParsePoint(p)) == 0) {
					pt.z = pop_value(p, double_value);
					pt.y = pop_value(p, double_value);
					pt.x = pop_value(p, double_value);
					*(VPoint *) kw->ptr = pt;
				}
				break;

			case vtSTATION:
				if ((n = ParseStation(p)) == 0) {
					i = pop_value(p, long_value);

					char * wname = pop_value(p, string_value);
					int wid = weapon_nameToId(wname);

					if( wid < 0 )
						error_external("unknown weapon `%s'", wname);

					xxx.station[i].id = wid;
					xxx.station[i].type = wname;

					xxx.station[i].info =
						pop_value(p, long_value);
					xxx.station[i].info2 =
						pop_value(p, long_value);
					xxx.station[i].info3 =
						pop_value(p, long_value);
				}
				break;

			case vtENTITY:
				if ((n = ParseDISEntityType(p)) == 0) {
					entity.extra = (unsigned char)
						pop_value(p, long_value);
					entity.specific = (unsigned char)
						pop_value(p, long_value);
					entity.subcategory = (unsigned char)
						pop_value(p, long_value);
					entity.category = (unsigned char)
						pop_value(p, long_value);
					entity.country = (unsigned char)
						pop_value(p, long_value);
					entity.domain = (unsigned char)
						pop_value(p, long_value);
					entity.kind = (unsigned char)
						pop_value(p, long_value);
					*((dis_entity_type *) kw->ptr) = entity;
				}
				break;
			}

			if (n != 0) {
				sprintf(s, "invalid syntax for %s\
 parameter", kw->word);
				ParseError(p, s);
			}
		}
		else if (t == TOKEN_RIGHT_BRACE) {
			if( xxx.objname != NULL ){
				char *path = reader_resolveRelativePath(p->filename, xxx.objname);
				memory_dispose(xxx.objname);
				xxx.objname = path;
			}
			
			// Success!
			xxx.zone = c->zone;
			xxx.prev = c->prev;
			xxx.next = c->next;
			*c = xxx;
			return 0;
		}
		else {
			sprintf(s, "\"%s\" was found where another token was\
 expected", token);
			ParseError(p, s);
			return -1;
		}
	}

	return -1;
}


static void
maxCLift(craftType *c, double *max_clift, double *stall_alpha)
/*
	Return the max CLift and the corresponding stall AoA.
*/
{
	double m = 0.0, a = 0.0, n;
	int i;

	for( i = 1; i < c->CLift->count; i++ ){

		/* Retrieve the original "y" value from the table: */
		n = c->CLift->entry[i-1].b
			+ c->CLift->entry[i-1].x * c->CLift->entry[i-1].m;

		if( i == 0 || n > m ){
			m = n;
			a = c->CLift->entry[i-1].x;
		}
	}

	*max_clift = m;
	*stall_alpha = a;
}


/* Standard air density at sea level: */
#define RHO 0.07648 /* lb/ft^3 */

/* Max vertical pos/neg gust speed: */
#define MAX_GUST 30.0 /* ft/s */


static void
compileSpeedLimits(craftType *c)
{
	double max_clift, alpha_stall;

	maxCLift(c, &max_clift, &alpha_stall);

	c->alpha_stall = alpha_stall;

	/*
		Calculate Vs0, stall speed at MTOW with full flaps:
	*/
	
	if( c->Vs0 == 0.0 && c->MTOW > 0.0 ){

		double clift = max_clift + c->cFlap * sin(c->maxFlap);

		c->Vs0 = units_FPStoKT( sqrt( 2.0*c->MTOW * units_earth_g
			/ (RHO * c->wingS * clift) ) );
	}

	/*
		Calculate Vs1, stall speed at MTOW without flaps:
	*/

	if( c->Vs1 == 0.0 && c->MTOW > 0.0 ){

		c->Vs1 = units_FPStoKT( sqrt( 2.0*c->MTOW * units_earth_g
			/ (RHO * c->wingS * max_clift) ) );
	}

	/*
		Calculate Vno:
	*/

	if( c->Vno == 0.0 ){

		double b = 6.302;

		c->Vno = units_FPStoKT( 2.0 * (c->maxLoadZNegative + c->emptyWeight) * units_earth_g
			/ (RHO * c->wingS * b * MAX_GUST) );
	}
}


/**
 * Parse "aircraft" record.
 * @param p
 * @return True on success.
 */
static int parseAircraft(struct lex_record *p)
{
	craftType *c = inventory_craftTypeNew(NULL);
	if ( ParseAircraft(p, c) != 0 ){
		inventory_craftTypeRelease(c);
		return 0;
	}

/*
 *  Initialize some other interesting values
 */

	if( c->objname != NULL ){
		FILE *f = fopen(c->objname, "r");
		if( f == NULL ){
			error_external("%s: file %s not found", p->filename, c->objname);
			return 0;
		}
		c->object = VReadDepthCueuedObject(f, 1);
		fclose(f);
	} else {
		c->object = NULL;
	}
	c->placeProc = NULL;
	c->damageBits = 0;
	c->thrust = planes_getThrustCalculator(c);
	c->resupply = planes_genericResupply;

/*
 *  Some older values are now derived from more precise information
 */

	c->CLOrigin = interpolate_value(c->CLift, 0.0);
	c->CLSlope = (interpolate_value(c->CLift, units_DEGtoRAD(10.0))
				   - c->CLOrigin) / units_DEGtoRAD(10.0);
	compileSpeedLimits(c);
	return 1;
}


static int parseInventoryFile(char *filename)
{
	struct lex_record *p;
	field_id t;
	int success = 1;
	char *subfile;
	
	if ((p = OpenSourceFile(filename)) == NULL) {
		error_external("%s: failed opening inventory file", filename);
	}
	
	t = NextToken(p);
	while (1) {
		if( t == EndOfFile ){
			break;
			
		} else if( t == RW_AIRCRAFT ){
			success = parseAircraft(p);
			if( ! success )
				break;
			t = NextToken(p);
			
		} else if( t == RW_INCLUDE ){
			t = NextToken(p);
			if( t != TOKEN_STRING )
				error_external("%s:%d: expected double-quoted string after 'include'", p->filename, p->lineno);
			subfile = reader_resolveRelativePath(filename, token);
			success = parseInventoryFile(subfile);
			memory_dispose(subfile);
			if( ! success ){
				error_external("%s:%d: failed including '%s'",
					p->filename, p->lineno, token);
				break;
			}
			t = NextToken(p);
			
		} else {
			error_external("%s:%d: expected either 'include' or 'aircraft'", p->filename, p->lineno);
		}
	}

	fclose(p->f);
	memory_dispose(p->filename);
	memory_dispose((char *) p);
	
	return success;
}


craftType *
inventory_craftTypeSearchByZoneAndName(zone_Type *zone, char *name)
{
	craftType *c = craft_types;
	while( c != NULL ){
		if( zone == c->zone && strcmp(name, c->name) == 0 )
			return c;
		c = c->next;
	}
	return NULL;
}



/*
 *  Find (or possible generate) the craftType entry associated with a
 *  given DIS entity type.
 */
craftType *
inventory_craftTypeSearchByEntityType( const dis_entity_type * id )
{
	int depthcue = 1;
	entity_object_map *ep;

	
	craftType *c = craft_types;
	while( c != NULL ){
		if (c->entityType.kind == id->kind &&
			c->entityType.domain == id->domain &&
			c->entityType.country == id->country &&
			c->entityType.category == id->category &&
			c->entityType.subcategory == id->subcategory &&
			c->entityType.specific == id->specific &&
			c->entityType.extra == id->extra) {
			return c;
		}
		c = c->next;
	}

	/*
	 * Well, there wasn't a craft type defined that matched the desired
	 * entity type.  So, we'll look for an entry in the patterns contained
	 * in the eo_map.  If we find a match, create a new craftType entry
	 * and return it.
	 */

	ep = eo_map;
	int i;
	for (i = 0; i < eo_map_count; ++ i) {

		FILE *f1;

		if (dis_entityWildcardMatch(id, &ep->entity_type, &ep->entity_mask)) {

			c = inventory_craftTypeNew(NULL);
			char *p = strrchr (ep->object_name, '.');
			f1 = init_fopen (ep->object_name, "r");
			c->name = memory_strdup(ep->object_name);
			c->objname = memory_strdup(ep->object_name);
			c->entityType = *id;
			c->altEntityType = *id;
			c->object = NULL;
			if (p != NULL && (strcmp (p, ".dxf") == 0 || 
							  strcmp (p, ".DXF") == 0)) {
				c->object = VReadDepthCueuedDXFObject (f1, depthcue);
			}
			else {
				c->object = VReadDepthCueuedObject (f1, depthcue);
			}

			if (c->object == NULL)
				error_internal("failed to read object from file: %s", ep->object_name);
			ep->obj = c->object;
			fclose (f1);
			return c;
		}
		++ ep;
	}

	return c;
}


void inventory_purgeZone(zone_Type *zone)
{
	craftType *c = craft_types;
	while( c != NULL ){
		if( c->zone == zone ){
			craftType *q = c;
			c = c->next;
			inventory_craftTypeRelease(q);
		} else {
			c = c->next;
		}
	}
}


static void inventory_cleanup()
{
	/* Releases munitions map: */
	memory_dispose(mun_map);
	mun_map = NULL;
	mun_map_count = 0;
	
	/* Releases aircraft inventory and recycle pool: */
	while( craft_types != NULL ){
		craftType *next = craft_types->next;
		inventory_craftTypeRelease(craft_types);
		craft_types = next;
	}
	while( craft_types_pool != NULL ){
		craftType *next = craft_types_pool->next;
		memory_dispose(craft_types_pool);
		craft_types_pool = next;
	}
	
	/* Releases entity-to-object map: */
	int i;
	for (i = 0; i < eo_map_count; ++ i) {
		memory_dispose(eo_map[i].object_name);
	}
	memory_dispose(eo_map);
	eo_map = NULL;
	eo_map_count = 0;
}


void inventory_init()
{
	char name[999];

	memory_strcpy(name, sizeof(name), init_findFile("aircraft.txt"));
    if ( ! parseInventoryFile(name) )
		error_external("Fatal error compiling aircraft inventory");

	memory_strcpy(name, sizeof(name), init_findFile("object-map.txt"));
    if (inventory_compileMapEntities(name, &eo_map_count, &eo_map) != 0)
		error_external("Fatal error reading object-map.txt");

	memory_strcpy(name, sizeof(name), init_findFile("munition-map.txt"));
    if (inventory_compileMunitionsMap(name, &mun_map_count, &mun_map) != 0)
		error_external("Fatal error reading munition-map.txt");
	
	memory_registerCleanup(inventory_cleanup);
}


munition_map * inventory_getMunition(int i)
{
	if( 0 <= i && i < mun_map_count )
		return &mun_map[i];
	else
		return NULL;
}


void inventory_printValidAircraft()
{
	craftType *c = craft_types;
	while( c != NULL ){
		if( c->CLift != NULL )
			printf("%s\n", c->name);
		c = c->next;
	}
}
