/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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 3 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "glk/agt/agility.h"
#include "glk/agt/interp.h"
#include "glk/agt/exec.h"

namespace Glk {
namespace AGT {

/*                                                                  */
/* This module contains most of the routines that implement the     */
/*   "physics" of the AGT world, including the scope routines.      */


/* ------------------------------------------------------------------- */
/*  Functions for manipulating parse_recs */
/* ------------------------------------------------------------------- */

/* Make artificial parse record for an object */
parse_rec *make_parserec(int obj, parse_rec *rec) {
	if (rec == nullptr) rec = (parse_rec *)rmalloc(sizeof(parse_rec));
	rec->obj = obj;
	rec->info = D_NOUN;
	rec->noun = it_name(obj);
	rec->adj = it_adj(obj);
	rec->num = 0;
	return rec;
}

/* This is used by the parser to initialize a blank parse_rec */
void tmpobj(parse_rec *objrec) {
	objrec->info = D_NOUN;
	objrec->num = 0;
	objrec->noun = objrec->adj = 0;
	objrec->obj = 0;
}

parse_rec *copy_parserec(parse_rec *rec) {
	parse_rec *newrec;
	if (rec == nullptr) return nullptr;
	newrec = (parse_rec *)rmalloc(sizeof(parse_rec));
	memcpy(newrec, rec, sizeof(parse_rec));
	return newrec;
}

void free_all_parserec(void) {
	rfree(actor_rec);
	rfree(dobj_rec);
	rfree(iobj_rec);
}


/* ------------------------------------------------------------------- */
/* Functions for doing basic manipulation of items and extracting      */
/* nformation about these items                                         */
/* (often used to blackbox the difference between nouns and creatures) */



rbool matchclass(int obj, int oclass) {
	int i;
	if (oclass == 0) return 0;
	for (i = obj; i != oclass && i != 0; i = it_class(i));
	return i == oclass;
}


/* Functions for getting bascic information about items */

static const char *it_sdesc(int item) {
	if (tnoun(item)) return noun[item - first_noun].shortdesc;
	if (tcreat(item)) return creature[item - first_creat].shortdesc;
	if (item < 0) return dict[-item];
	return nullptr;
}

rbool it_possess(int item) {
	int l;

	l = it_loc(item);
	return (l == 1 || l == 1000);
}

rbool it_proper(int item) {
	if (tcreat(item))
		return (!PURE_PROPER) || creature[item - first_creat].proper;
	if (tnoun(item))
		return noun[item - first_noun].proper;
	return 0;
}


static rbool invischeck(const char *s) {
	while (rspace(*s)) s++;
	return strncasecmp(s, "INVISIBLE", 9) == 0;
}


static rbool it_invisible(int item, rbool sdesc_flag) {
	if (sdesc_flag)
		return invischeck(it_sdesc(item));
	else {
		char *s;
		rbool tmp;

		if (it_name(item) == 0 && it_adj(item) == 0) return 1;
		s = objname(item); /* Must remember to rfree s before exiting */
		tmp = invischeck(s);
		rfree(s);
		return tmp;
	}
}


static rbool it_appears_empty(int item) {
	int i;
	int sdesc_flag;

	if (item < 0) return 1;
	sdesc_flag = !player_has(item);

	contloop(i, item)
	if (!it_invisible(i, sdesc_flag)) return 0;
	return 1;
}

/* We classify something as a weapon if it kills something. */
rbool it_isweapon(int objnum) {
	int i;

	creatloop(i)
	if (matchclass(objnum, creature[i].weapon)) return 1;
	return 0;
}

/* This used to be a macro like troom, tnoun, and tcreat, but it
   got too complicated and isn't used in time-critical areas,
   anyhow */

rbool it_door(int obj, word nword) {
	if (aver >= AGX00) return 0; /* No doors under Magx */
	if (tdoor(obj)) return 1; /* The basic door */
	if (it_loc(obj) == loc + first_room) return 0;
	return (nword == ext_code[wdoor]);
}


/* ------------------------------------------------------------------- */
/* Routines that manipulate the linked lists representing containment  */
/*  information   */

static void set_contents(int p, int newval) {
	if (troom(p)) room[p - first_room].contents = newval;
	else if (p == 1) player_contents = newval;
	else if (p == 1000) player_worn = newval;
	else if (tnoun(p)) noun[p - first_noun].contents = newval;
	else if (tcreat(p)) creature[p - first_creat].contents = newval;
	else {
		writeln("INT ERR: Invalid object heading chain.");
		return;
	}
}

static void set_next(int p, int newval) {
	if (tnoun(p)) noun[p - first_noun].next = newval;
	else if (tcreat(p)) creature[p - first_creat].next = newval;
	else {
		writeln("INT ERR: Invalid object in chain.");
		return;
	}
}

void add_object(int loc_, int item) {
	int p, q;

	set_next(item, 0);

	if (loc_ == 0) return;
	p = it_contents(loc_);

	if (p == 0 || p > item) {
		set_contents(loc_, item);
		set_next(item, p);
	} else {  /* Figure out where to put the item */
		do {
			q = p;
			p = it_next(p);
		} while (p != 0 && p < item);

		set_next(q, item);
		set_next(item, p);
	}
}


static void set_location(int item, int newloc)
/* This routine assumes item is either a noun or a creature */
{
	int p, q;

	p = it_loc(item);
	if (p != 0) {  /* Fix .next values */
		q = it_contents(p);
		if (q == item) set_contents(p, it_next(item));
		else {
			while (q != item && q != 0) {
				p = q;
				q = it_next(p);
			}
			assert(q != 0); /* This would mean the list structure was corrupted */
			set_next(p, it_next(item));
		}
	}
	/* We've unlinked it from the list at this point. */

	if (tnoun(item))
		noun[item - first_noun].location = newloc;
	else if (tcreat(item))
		creature[item - first_creat].location = newloc;

	add_object(newloc, item);
}


void it_reposition(int item, int newloc, rbool save_pos) {
	integer i;

	if (tnoun(item)) {
		if (player_has(item)) totwt -= noun[item - first_noun].weight;
		if (it_loc(item) == 1) totsize -= noun[item - first_noun].size;

		/* Set position to NULL */
		if (!save_pos) {
			noun[item - first_noun].pos_prep = 0;
			noun[item - first_noun].pos_name = 0;
			noun[item - first_noun].nearby_noun = 0;
			noun[item - first_noun].position = nullptr;
#if 0  /* I think this was wrong, so I'm commenting it out. */
			noun[item - first_noun].initdesc = 0;
#endif
		}

		set_location(item, newloc);

		if (player_has(item)) {
			totwt += noun[item - first_noun].weight;
			if (noun[item - first_noun].win)
				winflag = 1;
		}
		if (it_loc(item) == 1) /* only things you are carrying directly count vs.
			  size limit. */
			totsize += noun[item - first_noun].size;
	} else if (tcreat(item)) {
		if (newloc == 0) {
			creature[item - first_creat].timecounter = 0; /* Reset attack counter */
			creature[item - first_creat].counter = 0;
		}
		set_location(item, newloc);
	}

	nounloop(i)
	if (noun[i].nearby_noun == item) {
		noun[i].nearby_noun = 0;
		noun[i].pos_prep = 0;
		noun[i].pos_name = 0;
		noun[i].position = nullptr;
	}
}



/* ------------------------------------------------------------------- */
/*  Routines to deal with size and weight      */

static long contsize(integer obj) {
	int i;
	long net;

	net = 0;
	contloop(i, obj) {
		if (tnoun(i))
			net += noun[i - first_noun].size;
		if (aver < AGX00) /* Under Magx, size isn't recursive */
			net += contsize(i);
	}
	return net;
}

static long contweight(integer obj) {
	int i;
	long net;

	net = 0;
	contloop(i, obj) {
		if (tnoun(i))
			net += noun[i - first_noun].weight;
		net += contweight(i);
	}
	return net;
}

rbool is_within(integer obj1, integer obj2, rbool stop_if_closed)
/* True if obj1 is contained in obj2 */
{
	int i;
	long cnt;

	for (i = obj1, cnt = 0;
	        i != obj2 && i >= maxroom && i != 1000 && cnt < 40000L;
	        cnt++) {
		i = it_loc(i);
		if (stop_if_closed && !it_open(i)) break;
	}
	if (cnt >= 40000L) {
		/* writeln("GAME ERROR: Loop in object tree.");*/
		return 0;
	}
	if (i == obj2) return 1;
	return 0;
}



int check_fit(int obj1, int obj2)
/* Does obj1 fit inside obj2?  Return one of the FIT_... values
defined in exec.h */
{
	int size, weight;
	long net;

	assert(tnoun(obj1)); /* This should have been checked earlier */
	if (obj2 == 1000) obj2 = 1;

	if (obj2 == 1) size = weight = 100;
	else {
		assert(tnoun(obj2)); /* check_fit shouldn't be called otherwise */
		size = noun[obj2 - first_noun].size;
		weight = noun[obj2 - first_noun].weight;
	}

	/* Weight */
	if (obj2 == 1 || (aver > AGTME15 && aver < AGX00)) {
		/* Pre-1.56 interpreters forgot to check this;
		   Magx deliberately *doesn't* check this */

		net = noun[obj1 - first_noun].weight;
		if (aver >= AGX00) net += contweight(obj1);
		if (net > weight) return FIT_WEIGHT;

		if (obj2 == 1) {
			if (is_within(obj1, 1, 0) || is_within(obj1, 1000, 0)) net = 0;
			net += contweight(1);
			if (aver >= AGX00)
				net += contweight(1000);
			if (!PURE_SIZE) net = 0;
		} else {
			if (is_within(obj1, obj2, 0)) net = 0; /* Avoid double-counting */
			net += contweight(obj2); /* Net size of contents of obj2 */
		}
		if (net > weight) return FIT_NETWEIGHT;
	}

	net = noun[obj1 - first_noun].size;
	if (net > size) return FIT_SIZE;

	if (obj2 == 1 && !PURE_SIZE) return FIT_OK;

	if (obj2 == 1 || aver > AGTME15) {
		/* Pre-ME/1.56 interpreters didn't check this except for the player's
		   inventory */
		if (it_loc(obj1) == obj2
		        || (aver < AGX00 && is_within(obj1, obj2, 0)))
			net = 0; /* Avoid double-counting */
		net += contsize(obj2); /* Net size of contents of obj2 */
		if (net > size) return FIT_NETSIZE;
	}

	return FIT_OK;
}



/* ------------------------------------------------------------------- */
/*  Scope and visibility routines                                      */

integer it_room(int item) {
	int tmploc;
	long cnt;

	cnt = 0;
	while (!troom(item)) {
		tmploc = item;
		if (item == 0) return 0;
		if (item == 1 || item == 1000) item = loc;
		else item = it_loc(item);
		if (item == tmploc || ++cnt >= 40000L) {
			/* writeln("GAME ERROR: Loop in object tree."); */
			return 0;
		}
	}
	return item;
}

rbool player_has(int item) {
	return is_within(item, 1, 0) || is_within(item, 1000, 0);
}


rbool in_scope(int item)
/* strictly speaking, visible actually checks scope; this routine
   determines if an object would be in scope if there were no light
   problems. */
{
	int curloc;
	int tmp;
	long cnt;

	if (it_isglobal(item)) return 1;  /* Global objects always in scope. */

	/* Flag objects in scope if their associated flag is set. */
	tmp = it_flagnum(item);
	if (tmp &&
	        (room[loc].flag_noun_bits & (1L << (tmp - 1)))) return 1;

	curloc = it_loc(item); /* Should work for nouns or creatures */
	cnt = 0;
	while (curloc > maxroom && curloc != 1000 && it_open(curloc)) {
		int tmploc;
		tmploc = it_loc(curloc);
		if (tmploc == curloc || ++cnt >= 40000L) {
			/* writeln("GAME ERROR: Loop in the object tree."); */
			return 0;
		} else curloc = tmploc;
	}
	if (curloc == 1 || curloc == 1000 || curloc == loc + first_room) return 1;
	else return 0;
}


static int good_light(int obj, int roomlight, rbool active)
/* obj is a noun number */
/* If active is false, we don't care if the light is actually turned
on is the valid light */
{
	if (roomlight == 1 && !noun[obj].light)
		return 0; /* obj is not a light source */
	if (roomlight > 1) {
		if (!matchclass(first_noun + obj, roomlight))
			return 0; /* Not the correct light */
		else return 1; /* The correct light _always_ illuminates the room */
	}
	if (!active) return 1;
	/* Now need to determine if obj is actually providing light */
	if (!noun[obj].on)
		return 0;  /* Light source is off or extinguished */
	return 1;
}

int lightcheck(int parent, int roomlight, rbool active)
/* This checks to see if anything contained in parent is a valid
room light */
/* active=1 means that we only want active lights;
active=0 indicates that extinguished light sources are okay. */
{
	int i;

	contloop(i, parent) {
		if (tnoun(i) && good_light(i - first_noun, roomlight, active)) return 1;
		if (it_open(i) && lightcheck(i, roomlight, active))
			return 1; /* Check children */
	}
	return 0;
	/*
	    nounloop(i)
	      if (good_light(i,room[loc].light) && in_scope(i+first_noun))
	    return 1;
	  return 0;*/
}


rbool islit(void) {
	if (room[loc].light == 0) return 1;
	if (lightcheck(loc + first_room, room[loc].light, 1)) return 1;
	if (lightcheck(1, room[loc].light, 1)) return 1; /* Player carried light */
	if (lightcheck(1000, room[loc].light, 1)) return 1; /* Worn light */
	return 0;
}

/* Is item visible to player? */
/* visible only works for "normal" items; if the object could
   be a virtual object (i.e. with negative item number), then use
   gen_visible() below */
rbool visible(int item) {
	assert(item >= 0);
	if (islit())
		return in_scope(item);
	else
		return player_has(item);
}

rbool genvisible(parse_rec *dobj_) {
	int i;

	if (dobj_->obj > 0) return visible(dobj_->obj);

	if (dobj_->info == D_INTERN) {
		if (dobj_->obj != -ext_code[wdoor]) return 1;
		return islit(); /* If item is a is a door */
	}
	if (dobj_->info == D_GLOBAL || dobj_->info == D_NUM) return 1;
	if (dobj_->info == D_FLAG) {
		for (i = 0; i < MAX_FLAG_NOUN; i++) /* Flag nouns */
			if (flag_noun[i] != 0 && dobj_->obj == -flag_noun[i]
			        && (room[loc].flag_noun_bits & (1L << i)) != 0)
				return 1;
		return 0;
	}
	if (dobj_->info == D_PIX) {
		for (i = 0; i < MAX_PIX; i++) /* PIX names */
			if (pix_name[i] != 0 && dobj_->obj == -pix_name[i] &&
			        (room[loc].PIX_bits & (1L << i)) != 0)
				return 1;
		return 0;
	}
	fatal("INTERNAL ERROR: Invalid gen_visible type.");
	return 0;
}




/* Need to find a noun related to w */
/* If there is an object with name w in scope, it returns 0
   (since the object will have already been added to the menu).
   if there are none, it returns the first object with name w. */
static integer find_related(word w) {
	int i;
	int item;

	if (w == 0) return 0;
	item = 0;
	nounloop(i)
	if (noun[i].name == w) {
		if (visible(i + first_noun)) return i + first_noun;
		else if (item == 0) item = i + first_noun;
	}
	creatloop(i)
	if (creature[i].name == w) {
		if (visible(i + first_creat)) return i + first_creat;
		else if (item == 0) item = i + first_creat;
	}
	return item;
}


static void add_to_scope(integer item) {
	integer i;

	if (tnoun(item)) {
		noun[item - first_noun].scope = 1;
		i = find_related(noun[item - first_noun].related_name);
		if (i != 0) {
			if (tnoun(i)) noun[i - first_noun].scope = 1;
			else if (tcreat(i)) creature[i - first_creat].scope = 1;
		}
	} else if (tcreat(item)) creature[item - first_creat].scope = 1;
	if (item == 1 || item == 1000 || troom(item) || it_open(item))
		contloop(i, item)
		add_to_scope(i);
}


void compute_scope(void) {
	int i;
	uint32 rflag;

	nounloop(i) noun[i].scope = 0;
	creatloop(i) creature[i].scope = 0;
	add_to_scope(1);
	add_to_scope(1000);
	add_to_scope(loc + first_room);
	rflag = room[loc].flag_noun_bits;
	nounloop(i)
	if (noun[i].isglobal ||
	        (noun[i].flagnum && (rflag & (1L << (noun[i].flagnum - 1)))))
		add_to_scope(i + first_noun);
	creatloop(i)
	if (creature[i].isglobal ||
	        (creature[i].flagnum && (rflag & (1L << (creature[i].flagnum - 1)))))
		add_to_scope(i + first_creat);
}

void compute_seen(void) {
	int i;

	compute_scope();
	nounloop(i)
	noun[i].seen = noun[i].seen || noun[i].scope;
	creatloop(i)
	creature[i].seen = creature[i].seen || creature[i].scope;
}


/*---------------------------------------------------------------------*/
/*  Routines to compute the score */

void recompute_score(void) {
	int obj;

	tscore -= objscore;
	objscore = 0;
	nounloop(obj)
	if (noun[obj].points && !noun[obj].unused &&
	        (visible(obj + first_noun)
	         || is_within(obj + first_noun, treas_room, 0)))
		objscore += noun[obj].points;
	creatloop(obj)
	if (!creature[obj].unused && creature[obj].points
	        && visible(obj + first_creat))
		objscore += creature[obj].points;
	tscore += objscore;
}



/*---------------------------------------------------------------------*/
/*  Menu Noun section: routines to get a list of 'relevant' nouns for  */
/*    the menuing system. They're here because they belong next to the */
/*    scope routines, above   */

static int *nlist, nleng; /* These are really local variables */

static void add_mnoun(int n) {
	nlist = (int *)rrealloc(nlist, (nleng + 2) * sizeof(int));
	nlist[nleng] = n;
	nlist[++nleng] = 0;
}


/* This adds mitem and everything it contains */
static void add_mitem(int item) {
	integer i;

	if (tnoun(item) || tcreat(item)) add_mnoun(item);
	if (item == 1 || item == 1000 || troom(item) || it_open(item))
		contloop(i, item)
		add_mitem(i);
	/* Need to check related nouns */
	if (tnoun(item)) {
		i = find_related(noun[item - first_noun].related_name);
		if (i != 0) add_mnoun(i);
	}
}


static word getword(int item, int n)
/* Gets nth word associated with item */
{
	if (n == 1) {
		if (item < 0) return -item;
		else if (tnoun(item)) return noun[item - first_noun].adj;
		else if (tcreat(item)) return creature[item - first_creat].adj;
	}
	if (n == 2)
		if (tnoun(item) || tcreat(item)) return it_name(item);
	return 0;
}

static int cmp_nouns(const void *a, const void *b)
/*   *a, *b are object numbers; need alphabetic sort.*/
{
	word wa, wb;
	int cmp;

	wa = getword(*((const int *)a), 1);
	wb = getword(*((const int *)b), 1);
	cmp = strcmp(dict[wa], dict[wb]);
	if (cmp != 0) return cmp;
	wa = getword(*(const int *)a, 2);
	wb = getword(*(const int *)b, 2);
	return strcmp(dict[wa], dict[wb]);
}

int *get_nouns(void)
/* This returns the list of all objects that should show up on the menu */
/* The list should be 0 terminated and needs to be sorted */
{
	int i;
	uint32 rflag;

	nlist = (int *)rmalloc(sizeof(int));
	nlist[0] = 0;
	nleng = 0;

	for (i = 0; i < numglobal; i++)
		add_mnoun(-globalnoun[i]);
	for (i = 0; i < MAX_FLAG_NOUN; i++)
		if (room[loc].flag_noun_bits & (1L << i))
			add_mnoun(-flag_noun[i]);
	add_mitem(1);
	add_mitem(1000);
	add_mitem(loc + first_room);
	rflag = room[loc].flag_noun_bits;
	nounloop(i)
	if (noun[i].isglobal ||
	        (noun[i].flagnum && (rflag & (1L << (noun[i].flagnum - 1)))))
		add_mitem(i + first_noun);
	creatloop(i)
	if (creature[i].isglobal ||
	        (creature[i].flagnum && (rflag & (1L << (creature[i].flagnum - 1)))))
		add_mitem(i + first_creat);
	qsort(nlist, nleng, sizeof(int), cmp_nouns);
	return nlist;
}



/*---------------------------------------------------------------------*/
/* goto_room, the basic primitive used to move the player around       */

void goto_room(int newroom) {
	int i, j;

	/* Move group members in old room to new room */
	safecontloop(i, j, loc + first_room)
	if (it_group(i))
		it_move(i, newroom + first_room);

#if 0  /* -- this has been moved to v_go*/
	if (loc != newroom)
		oldloc = loc; /* Save old location for NO_BLOCK_HOSTILE purposes */
#endif
	loc = newroom;
	if (loc != newroom) oldloc = loc; /* No backtracking unless v_go allows it */
	if (!room[loc].seen) {
		room[loc].seen = 1;
		tscore += room[loc].points;
		first_visit_flag = 1;
		room_firstdesc = 1;
		v_look();
	} else {
		first_visit_flag = 0;
		if (verboseflag)
			v_look(); /* But see v_go() for a special case involving SPECIAL */
		room_firstdesc = 0;
	}
	if (room[loc].end) endflag = 1;
	if (room[loc].win) winflag = 1;
	if (room[loc].killplayer) deadflag = 1;
	do_autoverb = 1;
	set_statline();
}


static void rundesc(int i, descr_ptr dp_[], const char *shortdesc, int msgid) {
	if (dp_[i].size > 0)
		print_descr(dp_[i], 1);
	else if (!invischeck(shortdesc))
		raw_lineout(shortdesc, 1, MSG_DESC, nullptr);
	else sysmsg(msgid, "$You$ see nothing unexpected.");
}

void it_describe(int dobj_) {
	if (troom(dobj_))
		print_descr(room_ptr[dobj_ - first_room], 1);
	else if (tnoun(dobj_))
		rundesc(dobj_ - first_noun, noun_ptr, noun[dobj_ - first_noun].shortdesc, 194);
	else if (tcreat(dobj_))
		rundesc(dobj_ - first_creat, creat_ptr,
		        creature[dobj_ - first_creat].shortdesc, 195);
	else if (dobj_ == -ext_code[wdoor]) { /* i.e. DOOR */
		if (room[loc].locked_door)
			sysmsg(21, "$You$ see a locked door.");
		else sysmsg(22, "$You$ see a perfectly normal doorway.");
	} else sysmsg(194, "$You$ see nothing unexpected.");
	if (tnoun(dobj_) &&
	        (noun[dobj_ - first_noun].open || !noun[dobj_ - first_noun].closable) &&
	        !it_appears_empty(dobj_)) {
		sysmsg(228, "Which contains:");
		print_contents(dobj_, 1);
	}
}



static char *build_position(word prep_, word name)
/* Return the malloc'd string '$prep_$ the $name$' */
{
	int leng;
	char *s;

	leng = strlen(dict[prep_]) + strlen(dict[name]) + 6; /* includes final '\0' */
	s = (char *)rmalloc(leng * sizeof(char));

	Common::strcpy_s(s, leng, dict[prep_]);
	Common::strcat_s(s, leng, " the ");
	Common::strcat_s(s, leng, dict[name]);
	assert((int)strlen(s) + 1 == leng);
	return s;
}






static int print_obj(int obj, int ind_lev)
/* Prints out s on a line of its own if obj isn't INVISIBLE */
/* parent_descr is true if the parent has been described, false
   otherwise (say if the parent is invisible). */
/* ind_lev=indentation level */
/* Return 1 if we actually printed something, 0 if obj is invisible */
{
	int sdesc_flag; /* True if should print out as sdesc rather than
		as adjective-noun */
	int i, retval, parent;
	const char *s;
	char *t, *s0,  *posstr;

	if (tcreat(obj) && creature[obj - first_creat].initdesc != 0)
		return 0; /* Don't print normal description if printing initdesc */

	s0 = nullptr;
	sdesc_flag = !player_has(obj); /* This should be tested. */
	sdesc_flag = sdesc_flag || (ind_lev > 1);  /* It seems that AGT uses the
						sdesc for describing items
						contained in other items */
	/* Some code below relies on this, as well */

	if (sdesc_flag)
		s = it_sdesc(obj);
	else if (it_name(obj) == 0 && it_adj(obj) == 0) /* Invisible */
		return 0;
	else {
		s0 = objname(obj); /* Must remember to rfree s before exiting */
		if (aver >= AGTME10) {
			for (t = s0; isspace(*t); t++); /* Skip over initial whitespace... */
			*t = toupper(*t); /* ...and upcase the first non-space character */
		}
		s = s0;
	}

	retval = 0;
	if (sdesc_flag && tnoun(obj) && noun[obj - first_noun].initdesc != 0) {
		retval = 1;
		msgout(noun[obj - first_noun].initdesc, 1);
		noun[obj - first_noun].initdesc = 0; /* Only show it once */
	} else if (!invischeck(s)) {
		retval = 1; /* We're actually going to print something */
		for (i = 0; i < ind_lev; i++) writestr("   ");
		raw_lineout(s, sdesc_flag, MSG_DESC, nullptr);
		/* Do $word$ formatting if sdesc */
		/* Need to output container */
		parent = it_loc(obj);
		if (tnoun(obj) && noun[obj - first_noun].pos_prep != 0) {
			writestr(" (");
			if (noun[obj - first_noun].pos_prep == -1)
				writestr(noun[obj - first_noun].position);
			else  {
				posstr = build_position(noun[obj - first_noun].pos_prep,
				                        noun[obj - first_noun].pos_name);
				writestr(posstr);
				rfree(posstr);
			}
			writestr(")");
		} else if (parent >= first_noun && it_invisible(parent, sdesc_flag)
		           && (it_name(parent) != 0 || it_adj(parent) != 0)) {
			/* If the parent object *isn't* invisible, we will already have
			printed it out */
			/* This also relies on sdesc_flag being the same for parent
			and child objects */

			if (parent >= first_creat && parent <= maxcreat)
				sysmsg(221, "(Carried by");
			else
				sysmsg(222, " (Inside");
			t = objname(parent);
			writestr(t);
			rfree(t);
			sysmsg(223, ")");
		}
		if (tnoun(obj) && noun[obj - first_noun].light && noun[obj - first_noun].on
		        && PURE_OBJ_DESC)
			sysmsg(220, " (Providing light)");
		writeln("");
	}
	if (!sdesc_flag)
		rfree(s0);
	return retval;
}


int print_contents(int obj, int ind_lev)
/* obj=object to list contents of; ind_lev=indentation level */
/* Returns number of objects contained in obj that were listed */
{
	int i, cnt;

	cnt = 0;

	contloop(i, obj) {
		if (print_obj(i, ind_lev)) cnt++;
		if (it_open(i)) print_contents(i, ind_lev + 1);
	}
	return cnt;
}


/* ------------------------------------------------------------------- */
/*  Routines for directly getting and setting object properties and   */
/*   attributes. */


#define NUM_WPROP 6
#define NUM_WATTR 6


static void *compute_addr(int obj, int prop, const prop_struct *ptable) {
	int ofs;
	void *base;

	if (DIAG)
		rprintf("(Accessing %s->%s)\n", dict[it_name(obj)], ptable[prop].name);
	if (troom(obj)) {
		base = (void *)(&room[obj - first_room]);
		ofs = ptable[prop].room;
	} else if (tnoun(obj)) {
		base = (void *)(&noun[obj - first_noun]);
		ofs = ptable[prop].noun;
	} else if (tcreat(obj)) {
		base = (void *)(&creature[obj - first_creat]);
		ofs = ptable[prop].creature;
	} else return nullptr;

	if (ofs == -1) /* Field doesn't exist in this type of object */
		return nullptr;

	return (void *)(((char *)base) + ofs);
}


long getprop(int obj, int prop) {
	integer *paddr;

	if (prop >= NUM_PROP) return 0;
	paddr = (integer *)compute_addr(obj, prop, proplist);
	if (paddr == nullptr) return 0;
	return *paddr;
}

void setprop(int obj, int prop, long val) {
	integer *paddr;

	if (prop >= NUM_WPROP) {
		writeln("GAME ERROR: Read-only or non-existent property.");
		return;
	}

	paddr = (integer *)compute_addr(obj, prop, proplist);
	if (paddr == nullptr) {
		writeln("GAME ERROR: Property-object mismatch.");
		return;
	}
	*paddr = val;
}

rbool getattr(int obj, int prop) {
	rbool *paddr;

	if (prop >= NUM_ATTR) return 0;
	paddr = (rbool *)compute_addr(obj, prop, attrlist);
	if (paddr == nullptr) return 0;
	return *paddr;
}

void setattr(int obj, int prop, rbool val) {
	rbool *paddr;

	if (prop >= NUM_WATTR && prop != 24) {
		writeln("GAME ERROR: Read-only or non-existent attribute.");
		return;
	}

	paddr = (rbool *)compute_addr(obj, prop, attrlist);
	if (paddr == nullptr) {
		writeln("GAME ERROR: Property-object mismatch.");
		return;
	}
	*paddr = val;
}



/* ------------------------------------------------------------------- */
/*  This sets up the creat_fix[] array, which is used to determine the */
/*  scan ranges for addressed creatures in cases where there is more */
/*  than one creature of the same name */

void init_creat_fix(void) {
	int i, j;

	creat_fix = (integer *)rmalloc(rangefix(maxcreat - first_creat + 1) * sizeof(integer));
	for (i = 0; i < maxcreat - first_creat + 1; i++)
		creat_fix[i] = i + first_creat;
	for (i = 0; i < maxcreat - first_creat + 1; i++)
		if (creat_fix[i] == i + first_creat) /* That is, it hasn't changed. */
			for (j = i + 1; j < maxcreat - first_creat + 1; j++)
				if (creature[i].name == creature[j].name &&
				        creature[i].adj == creature[j].adj)
					creat_fix[j] = i + first_creat; /* That is, j --> i */
}

void free_creat_fix(void) {
	rfree(creat_fix);
}

/* ------------------------------------------------------------------- */

#ifndef IT_MACRO
int it_contents(integer obj) {
	if (tnoun(obj)) return noun[obj - first_noun].contents;
	else if (troom(obj)) return room[obj - first_room].contents;
	else if (tcreat(obj)) return creature[obj - first_creat].contents;
	else if (obj == 1) return player_contents;
	else if (obj == 1000) return player_worn;
	else return 0;
}

rbool it_lockable(integer obj, word nword) {
	if (tnoun(obj)) return noun[obj - first_noun].lockable;
	else if (it_door(obj, nword)) return 1;
	else return 0;
}

rbool it_locked(integer obj, word nword) {
	if (tnoun(obj)) return noun[obj - first_noun].locked;
	else if (it_door(obj, nword) && room[loc].locked_door) return 1;
	else return 0;
}

#endif

} // End of namespace AGT
} // End of namespace Glk
