/
umud/DOC/
umud/DOC/examples/
umud/DOC/internals/
umud/DOC/wizard/
umud/MISC/
umud/MISC/dbchk/
umud/RWHO/rwhod/
/*
	Copyright (C) 1991, Marcus J. Ranum. All rights reserved.
*/

#ifndef	lint
static	char	RCSid[] = "$Header: /usr/users/mjr/hacks/umud/RCS/match.c,v 1.4 91/09/19 12:56:26 mjr Exp $";
#endif

/* configure all options BEFORE including system stuff. */
#include	"config.h"


#include	<ctype.h>

#include	"mud.h"
#include	"vars.h"
#include	"match.h"



/*
perform string-to-string matching. called from within main matcher.
if "sem" is set, then semicolons in "s1" are the logical equivalent
of word breaks, AND reset matching exactitude flag.

this code isn't *deliberately* hellish, honest.
*/
int
matchstr(s1,s2,sem)
char	*s1;
char	*s2;
int	sem;
{
	char	inex = '\0';
	char	c1;
	char	c2;
	char	*p1;
	char	*p2;

	/* preskip */
	while(isspace(*s1) || (sem && *s1 == ';'))
		s1++;
	while(isspace(*s2))
		s2++;

	while(*s1 != '\0') {
		p2 = s2;

		c1 = isupper(*s1) ? tolower(*s1) : *s1;	
		c2 = isupper(*p2) ? tolower(*p2) : *p2;	

		/* first chars of words don't match ?  - skip word. */
		if(c1 != c2 && !(isspace(c1) && isspace(c2)))
			goto skipword;

		p1 = s1 + 1;
		p2++;

		/* first chars *DID* match, now see how well */
		while(*p2 != '\0') {
			c1 = isupper(*p1) ? tolower(*p1) : *p1;	
			c2 = isupper(*p2) ? tolower(*p2) : *p2;	

			if(c1 != c2 && !isspace(c2))
				goto skipword;

			/* skipout spaces if BOTH are spaces */
			if(isspace(c1) && isspace(c2)) {
				while(isspace(*p1))
					p1++;
				while(isspace(*p2))
					p2++;
				goto check_done;
			}


			/* if the smaller string is space, check prefixes */
			if(isspace(c2)) {
				while(isspace(*p2))
					p2++;

				inex = '1';
				while(((!isspace(*p1) && !sem) || (sem && *p1 != ';')) && *p1 != '\0')
					p1++;
				if(sem && *p1 == ';') {
					inex = '\0';
					p1++;
				}
				while(isspace(*p1) && *p1 != '\0')
					p1++;
				goto check_done;
			}


			/* default - chars matched */
			p1++;
			p2++;
check_done:

			/* out of name string, match string leftover. fail */
			if(*p1 == '\0' && *p2 != '\0')
				return(0);
		}

		if( sem && *p2 == '\0' && *p1 != '\0' && *p1 != ';' )
		    goto skipword;

		/* if we hit here, we have run out of match string bytes */
		if((*p1 == '\0' || (sem && *p1 == ';')) && inex == '\0')
			return(2);
		return(1);

skipword:
		inex = '1';
		while(((!isspace(*s1) && !sem) || (sem && *s1 != ';')) && *s1 != '\0')
			s1++;

		if(sem && *s1 == ';') {
			inex = '\0';
			s1++;
		}

		while(isspace(*s1) && *s1 != '\0')
			s1++;
	}
	return(0);
}

/*
match. the interface is not really intended to be used by "normal"
applications (see "matchlocal()" below) - match takes a vector of
things to scan, and the attribute name to scan against, as well as
a set of flags tied to the vector. this is somewhat more complex
than is necessary, but the intent is to provide as general purpose
and extensible an interface to matching as possible.
*/
match(who,what,where,fl,ml,mc,dest)
char	*who;
char	*what;
char	*where;
int	fl;
Mtch	*ml;
int	mc;
char	*dest;
{
	int	mp;
	int	best = 0;
	int	tmp1;
	char	nbuf[MAXOID];
	int	cflg;
	int	wformd;
	int	ambg = 0;
	char	*sp;
	Mtch	*m;
#ifdef FLAT_PROBABILITIES
	int	cnt = 1;
#endif

	/* if flagged to accept "me" and "here" */
	if(fl & MTCH_MEOK) {
		if(who != (char *)0 && !strcmp(what,"me")) {
			strcpy(dest,who);
			if(fl & MTCH_WHICH)
				say(who,dest," (",ut_name(dest),")\n",(char *)0);
			return(MTCHRET_OK);
		}
		if(where != (char *)0 && !strcmp(what,"here")) {
			strcpy(dest,where);
			if(fl & MTCH_WHICH)
				say(who,dest," (",ut_name(dest),")\n",(char *)0);
			return(MTCHRET_OK);
		}
	}

	/* is the object name actually a potential object-id? */
	wformd = ut_isgoodid(what);

	for(mp = 0; mp < mc; mp++) {
		m = &ml[mp];

		/* simple type-checking */
		if(m->lp == (char *)0 || m->att == (char *)0)
			continue;

		if(!attistype(m->lp,typ_list) && !attistype(m->lp,typ_obj))
			continue;

		/* if exit-type ';' matching is requested */
		cflg = (m->flgs & MTCHFLG_EXIT) ? 1 : 0;

		/*
		traverse list. this relies on the fact that lstnext
		works correctly on an objid as well as a list
		*/
		m->lp = attdata(m->lp);

		/* search for match on object ID */
		if(wformd && lstlook(m->lp,what)) {
			if(fl & MTCH_WHICH)
				say(who,what," - object IDs match\n",(char *)0);
			(void)strcpy(dest,what);
			best = 3;
		}

		while((m->lp = lstnext(m->lp,nbuf)) != (char *)0) {
			sp = ut_getatt(nbuf,0,typ_str,m->att,(char *)0);
			if(sp != (char *)0) {
				tmp1 = matchstr(sp,what,cflg);

				/* hardcopy requested? */
				if(tmp1 != 0 && (fl & MTCH_WHICH))
					say(who,nbuf," (",sp,")\n",(char *)0);

				if(tmp1 > 0 && tmp1 > best) {
					/* exact match is > 1 */
					if((fl & MTCH_EXACT) && tmp1 < 2)
						continue;

					(void)strcpy(dest,nbuf);
					best = tmp1;
					ambg = 0;

					if(fl & MTCH_FRST)
						return(MTCHRET_OK);
					continue;
				}


				/* ambiguous hit (either exact or partial) */
				if(tmp1 > 0 && tmp1 == best) {
					/* flip a coin */
					if(fl & MTCH_RAND) {
#ifdef FLAT_PROBABILITIES
						if(get_random(++cnt * 17) < 17) {
#else
						if(get_random(100) < 50) {
#endif
							(void)strcpy(dest,nbuf);
						}
					}
					ambg = 1;
					continue;
				}
			}
		}
	}
	if(best == 0){

		/* if flagged to accept "object-id" */
		if((fl & MTCH_NONLOC) && wformd && cache_check(what)) {
			strcpy(dest,what);
			if(fl & MTCH_WHICH)
				say(who,what," - object IDs match\n",(char *)0);
			return(MTCHRET_OK);
		}
		return(MTCHRET_NONE);
	}

	if(ambg && (fl & MTCH_UNIQ))
		return(MTCHRET_AMBIG);
	return(MTCHRET_OK);
}



/*
do a match in the local player/room combination - "standard" matching
*/
matchlocal(who,what,where,flg,dest)
char	*who;
char	*what;
char	*where;
int	flg;
char	*dest;
{
	Mtch	mat[5];

	/* setup match - contents in room */
	mat[0].lp = ut_getatt(where,1,typ_list,var_cont,(char *)0);
	mat[0].att = var_nam;
	mat[0].flgs = 0;

	/* setup match - players in room */
	mat[1].lp = ut_getatt(where,1,typ_list,var_ply,(char *)0);
	mat[1].att = var_nam;
	mat[1].flgs = 0;

	/* setup match - exits in room */
	mat[2].lp = ut_getatt(where,1,typ_list,var_xit,(char *)0);
	mat[2].att = var_nam;
	mat[2].flgs = MTCHFLG_EXIT;

	/* setup match - things in inventory */
	mat[3].lp = ut_getatt(who,1,typ_list,var_cont,(char *)0);
	mat[3].att = var_nam;
	mat[3].flgs = 0;

	/* setup match - item in use */
	mat[4].lp = ut_getatt(who,1,typ_obj,var_using,(char *)0);
	mat[4].att = var_nam;
	mat[4].flgs = 0;

	switch(match(who,what,where,flg,mat,5,dest)) {
	case MTCHRET_OK:
		return(0);
	case MTCHRET_NONE:
		if(!(flg & MTCH_QUIET))
		say(who,"I see no \"",what,"\" here.\n",(char *)0);
		return(1);
	case MTCHRET_AMBIG:
		if(!(flg & MTCH_QUIET))
		say(who,"\"",what,"\" is ambiguous.\n",(char *)0);
		return(1);
	default:
		fatal("Yo-Nakna!\n",(char *)0);
	}
	return(0);
}




/*
do a match in the player's inventory. if matchu is nonzero, also match
object in hand.
*/
matchinv(who,what,matchu,flg,dest)
char	*who;
char	*what;
int	matchu;
int	flg;
char	*dest;
{
	Mtch	mat[2];
	int	nmatch = 1;

	/* setup match - contents of player */
	mat[0].lp = ut_getatt(who,1,typ_list,var_cont,(char *)0);
	mat[0].att = var_nam;
	mat[0].flgs = 0;

	if(matchu) {
		/* setup match - contents of player */
		mat[1].lp = ut_getatt(who,1,typ_obj,var_using,(char *)0);
		mat[1].att = var_nam;
		mat[1].flgs = 0;
		nmatch = 2;
	}

	switch(match(who,what,(char *)0,flg,mat,nmatch,dest)) {
	case MTCHRET_OK:
		return(0);
	case MTCHRET_NONE:
		if(!(flg & MTCH_QUIET))
		say(who,"You are carrying no \"",what,"\".\n",(char *)0);
		return(1);
	case MTCHRET_AMBIG:
		if(!(flg & MTCH_QUIET))
		say(who,"\"",what,"\" is ambiguous.\n",(char *)0);
		return(1);
	default:
		fatal("Yo-Nakna!\n",(char *)0);
	}
	return(0);
}

/*
do an exit match on the room, based on an argument vector, NOT a string.
*/
matchargvexit(where,av,ac,dest)
char	*where;
char	*av[];
int	ac;
char	*dest;
{
	char	*xits;
	char	*nam;
	char	*p;
	int	i;
	int	matched;
	int	hit = 0;
	char	c1,c2;
#ifdef FLAT_PROBABILITIES
	int	cnt = 1;
#endif
	char	nbuf[MAXOID];

	xits = ut_getatt(where,0,typ_list,var_xit,(char *)0);
	if(xits == (char *)0)
		return(0);

	/* Loop across exits */

	while((xits = lstnext(xits,nbuf)) != (char *)0){
		if((nam = ut_name(nbuf)) == (char *)0)
			continue;
		matched = 1;

		while(isspace(*nam) && *nam)
			nam++;

		/* Loop across tokens */
		i = 0;
		while(i < ac){
			p = av[i];
			if(*nam == '\0')
				break;

			/* Match token */

			while(1){
				c1 = islower(*nam) ? toupper(*nam) : *nam;
				c2 = islower(*p) ? toupper(*p) : *p;

				/* Ended token ok? */
				if((isspace(c1) || c1 == ';' || c1 == '\0') && c2 == '\0')
					break;

				/* No match? */
				if(c1 != c2){
					matched = 0;
					break;
				}
				nam++;
				p++;
			}
			while(isspace(*nam) && *nam)
				nam++;
			if(!matched || (i == ac-1 && *nam != '\0' && *nam != ';')){
				while(*nam != ';' && *nam)
					nam++;
				if(*nam){
					nam++;
					while(isspace(*nam) && *nam)
						nam++;
				}
				else
					break;
				matched = 1;
				i = 0;
			} else if(i == ac-1){
				/* Matched all tokens. Flip coin to see if this is it */
				hit = 1;
				strcpy(dest,nbuf);
#ifdef FLAT_PROBABILITIES
				if(get_random(++cnt * 17) < 17){
#else
				if(get_random(100) < 50){
#endif
					return(1);
				}
			} else {
				i++;
			}
		}
	}
	return(hit);
}


/*
do a match on exits in the current room.
*/
matchexit(who,what,where,flg,dest)
char	*who;
char	*what;
char	*where;
int	flg;
char	*dest;
{
	Mtch	mat;

	/* setup match - room exit list */
	mat.lp = ut_getatt(where,1,typ_list,var_xit,(char *)0);
	mat.att = var_nam;
	mat.flgs = MTCHFLG_EXIT;

	switch(match(who,what,(char *)0,flg,&mat,1,dest)) {
	case MTCHRET_OK:
		return(0);
	case MTCHRET_NONE:
		if(!(flg & MTCH_QUIET))
		say(who,"There is no exit \"",what,"\".\n",(char *)0);
		return(1);
	case MTCHRET_AMBIG:
		if(!(flg & MTCH_QUIET))
		say(who,"\"",what,"\" is ambiguous.\n",(char *)0);
		return(1);
	default:
		fatal("Yo-Nakna!\n",(char *)0);
	}
	return(0);
}




/*
do a match on players in the current room.
*/
matchplayers(who,what,where,flg,dest)
char	*who;
char	*what;
char	*where;
int	flg;
char	*dest;
{
	Mtch	mat;

	mat.lp = ut_getatt(where,1,typ_list,var_ply,(char *)0);
	mat.att = var_nam;
	mat.flgs = 0;

	switch(match(who,what,(char *)0,flg,&mat,1,dest)) {
	case MTCHRET_OK:
		return(0);
	case MTCHRET_NONE:
		if(!(flg & MTCH_QUIET))
		say(who,"There is nobody named \"",what,"\" here.\n",(char *)0);
		return(1);
	case MTCHRET_AMBIG:
		if(!(flg & MTCH_QUIET))
		say(who,"\"",what,"\" is ambiguous.\n",(char *)0);
		return(1);
	default:
		fatal("Yo-Nakna!\n",(char *)0);
	}
	return(0);
}




/*
do a match on objects in the current room.
*/
matchobjects(who,what,where,flg,dest)
char	*who;
char	*what;
char	*where;
int	flg;
char	*dest;
{
	Mtch	mat;

	mat.lp = ut_getatt(where,1,typ_list,var_cont,(char *)0);
	mat.att = var_nam;
	mat.flgs = 0;

	switch(match(who,what,(char *)0,flg,&mat,1,dest)) {
	case MTCHRET_OK:
		return(0);
	case MTCHRET_NONE:
		if(!(flg & MTCH_QUIET))
		say(who,"There is no object \"",what,"\" here.\n",(char *)0);
		return(1);
	case MTCHRET_AMBIG:
		if(!(flg & MTCH_QUIET))
		say(who,"\"",what,"\" is ambiguous.\n",(char *)0);
		return(1);
	default:
		fatal("Yo-Nakna!\n",(char *)0);
	}
	return(0);
}


/*
do a match on logged-in players - calls the net layer
*/
matchloggedinplayers(what,dest)
char	*what;
char	*dest;
{
	char	*xx;
	char	*na;
	int	m;
	int	ambg = 0;
	int	best = 0;

	io_rstnxtwho();
	while((xx = io_nxtwho((long)0)) != (char *)0) {
		if(!strcmp(what,xx)){
			(void)strcpy(dest,xx);
			return(0);
		}

		na = ut_name(xx);
		if(na == (char *)0)
			continue;

		m = matchstr(na,what,0);
		if(m != 0 && m > best) {
			(void)strcpy(dest,xx);
			best = m;
			ambg = 0;
			continue;
		} 

		if(m != 0 && m == best) {
			if(!strcmp(dest,xx))
				continue;
			ambg = 1;
			continue;
		}
	}

	if(best == 0)
		return(MTCHRET_NONE);
	if(ambg)
		return(MTCHRET_AMBIG);
	return(MTCHRET_OK);
}