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

/* configure all options BEFORE including system stuff. */
#include    "config.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 (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.
*/
int match (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, sizeof (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
*/
int matchlocal (char *who, char *what, char *where, int flg, char *dest)
{
  Mtch mat[7];

  /* 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;

#ifdef  COMBAT
  /* setup match - weapon */
  mat[5].lp = ut_getatt (who, 1, typ_obj, var_weapon, (char *) 0);
  mat[5].att = var_nam;
  mat[5].flgs = 0;

  /* setup match - armor */
  mat[6].lp = ut_getatt (who, 1, typ_list, var_wearing, (char *) 0);
  mat[6].att = var_nam;
  mat[6].flgs = 0;
#endif

#ifdef  COMBAT
  switch (match (who, what, where, flg, mat, 7, dest)) {
#else
  switch (match (who, what, where, flg, mat, 5, dest)) {
#endif
  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.
*/
int matchinv (char *who, char *what, int matchu, int flg, char *dest)
{
  Mtch mat[5];
  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) {
    mat[1].lp = ut_getatt (who, 1, typ_obj, var_using, (char *) 0);
    mat[1].att = var_nam;
    mat[1].flgs = 0;
    nmatch = 2;
#ifdef  COMBAT
    /* setup match - weapon */
    mat[2].lp = ut_getatt (who, 1, typ_obj, var_weapon, (char *) 0);
    mat[2].att = var_nam;
    mat[2].flgs = 0;
    nmatch = 3;
#endif

  }

  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.
*/
int matchargvexit (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, sizeof (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.
*/
int matchexit (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.
*/
int matchplayers (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.
*/
int matchobjects (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
*/
int matchloggedinplayers (char *what, char *dest)
{
  char *xx;
  char *na;
  int m;
  int ambg = 0;
  int best = 0;

  io_rstnxtwho ();
  while ((xx = io_nxtwho ((time_t *) 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);
}




/*
do a match in the player's clothing. if matchu is nonzero, also match
object in hand.
*/
int matchwear (char *who, char *what, 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_wearing, (char *) 0);
  mat[0].att = var_nam;
  mat[0].flgs = 0;

  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 wearing 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);
}