pennmush/game/
pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
/* match.c */
/* Routines for parsing arguments */

/***********************************************************************
 * These are the PennMUSH name-matching routines, rewritten by
 * Javelin to be fully re-entrant instead of using global variables,
 * which fixes some bugs. [pl12]
 *
 * Calling these routines no longer requires using init_match,
 * a bunch of match_<foo> calls, and then getting match_result().
 * Instead, call one of these functions:
 *  match_result(who,name,type,flags) - return match, AMBIGUOUS, or NOTHING
 *  noisy_match_result(who,name,type,flags) - return match or NOTHING,
 *	and notify player on failures
 *  last_match_result(who,name,type,flags) - return match or NOTHING,
 *	and return the last match found in ambiguous situations
 *
 * who = dbref of player to match for
 * name = string to match on
 * type = preferred type of match (TYPE_THING, etc.) or NOTYPE
 * flags = a set of bits indicating what kind of matching to do
 *
 * flags are defined in match.h, but here they are for reference:
 * MAT_CHECK_KEYS	- check locks when matching
 * MAT_GLOBAL		- match in master room
 * MAT_REMOTES		- match things not nearby
 * MAT_NEAR		- match things nearby
 * MAT_CONTROL		- do a control check after matching
 * MAT_ME		- match "me"
 * MAT_HERE		- match "here"
 * MAT_ABSOLUTE		- match "#<dbref>"
 * MAT_PLAYER		- match a player's name
 * MAT_NEIGHBOR		- match something in the same room
 * MAT_POSSESSION	- match something I'm carrying
 * MAT_EXIT		- match an exit
 * MAT_CARRIED_EXIT	- match a carried exit (rare)
 * MAT_CONTAINER	- match a container I'm in
 * MAT_REMOTE_CONTENTS	- match the contents of a remote location
 * MAT_EVERYTHING	- me,here,absolute,player,neighbor,possession,exit
 * MAT_NEARBY		- everything near
 * MAT_OBJECTS		- me,absolute,player,neigbor,possession
 * MAT_NEAR_THINGS	- objects near
 * MAT_REMOTE		- absolute,player,remote_contents,exit,remotes
 * MAT_LIMITED		- absolute,player,neighbor
 */

#include "copyrite.h"
#include "config.h"
#include <ctype.h>
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif
#ifdef I_STDLIB
#include <stdlib.h>
#endif
#include "conf.h"
#include "mushdb.h"
#include "externs.h"
#include "globals.h"
#include "match.h"
#include "confmagic.h"

extern dbref short_page _((const char *match));		/* in bsd.c */


dbref match_result _((const dbref who, const char *name, const int type, const long int flags));
dbref noisy_match_result _((const dbref who, const char *name, const int type, const long int flags));
dbref last_match_result _((const dbref who, const char *name, const int type, const long int flags));
dbref match_controlled _((dbref player, const char *name));
static dbref match_result_internal _((const dbref who, const char *name, const int type, const long int flags));
static dbref match_me _((const dbref who, const char *name));
static dbref match_here _((const dbref who, const char *name));
static dbref match_absolute _((const char *name));
static dbref match_player _((const char *match_name));
static dbref match_list _((const dbref match_who, const char *match_name, const int type, const long int flags, dbref first, dbref *last_match, int *match_count));
static dbref match_neighbor _((const dbref who, const char *name, const int type, const long int flags, dbref *last_match, int *match_count));
static dbref match_possession _((const dbref who, const char *name, const int type, const long int flags, dbref *last_match, int *match_count));
static dbref match_exit _((const dbref who, const char *name, const int type, const long int flags));
static dbref match_exit_internal _((const dbref match_who, const char *match_name, const int type, const long int flags, const dbref loc));
static dbref match_remote_contents _((const dbref who, const char *name, const int type, const long int flags, dbref *last_match, int *match_count));
static dbref match_container _((const dbref who, const char *name, const int type, const long int flags, dbref *last_match, int *match_count));
static dbref choose_thing _((const dbref match_who, const int preferred_type, long int flags, dbref thing1, dbref thing2));


char const *nomatch_message = "I can't see that here.";
char const *ambiguous_message = "I don't know which one you mean!";

/* A wrapper for returning a match, AMBIGUOUS, or NOTHING
 */
dbref
match_result(who, name, type, flags)
    const dbref who;
    const char *name;
    const int type;
    const long int flags;
{
  return match_result_internal(who, name, type, flags);
}

/* A wrapper for returning a match or NOTHING
 * Ambiguous matches return NOTHING
 * It will also notify the player of non-matches or ambiguous matches
 */
dbref
noisy_match_result(who, name, type, flags)
    const dbref who;
    const char *name;
    const int type;
    const long int flags;
{
  return match_result_internal(who, name, type, flags | MAT_NOISY);
}

/* A wrapper for returning a match or NOTHING
 * Ambiguous matches return the last matched thing
 */
dbref
last_match_result(who, name, type, flags)
    const dbref who;
    const char *name;
    const int type;
    const long int flags;
{
  return match_result_internal(who, name, type, flags | MAT_LAST);
}

/* Wrapper for a noisy match with control checks */
dbref
match_controlled(player, name)
    dbref player;
    const char *name;
{
  dbref match;
  match = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING);
  if (GoodObject(match) && !controls(player, match)) {
    notify(player, "Permission denied.");
    return NOTHING;
  } else {
    return match;
  }
}

/* The real work. */
static dbref
match_result_internal(who, name, type, flags)
    const dbref who;
    const char *name;
    const int type;
    const long int flags;
{
  dbref match = NOTHING, last_match = NOTHING;
  int match_count = 0;
  if (flags & MAT_ME) {
    match = match_me(who, name);
    if (GoodObject(match))
      return match;
  }
  if (flags & MAT_HERE) {
    match = match_here(who, name);
    if (GoodObject(match))
      return match;
  }
  if (!(flags & MAT_NEAR) || Long_Fingers(who)) {
    if (flags & MAT_ABSOLUTE) {
      match = match_absolute(name);
      if (GoodObject(match)) {
	if (flags & MAT_CONTROL) {
	  /* Check for control */
	  if (controls(who, match) || nearby(who, match))
	    return match;
	} else {
	  return match;
	}
      }
    }
    if (flags & MAT_PLAYER) {
      match = match_player(name);
      if (GoodObject(match))
	return match;
    }
  } else {
    /* We're doing a nearby match and the player doesn't have
     * long_fingers, so it's a controlled absolute
     */
    match = match_absolute(name);
    if (GoodObject(match) &&
	(controls(who, match) || nearby(who, match)))
      return match;
  }

  /* These return a match if the match is exact, and otherwise
   * store last thing matched in last_match and the number of matches
   * in match_count. Remote_contents and Neighbor are exclusive.
   */
  if (DO_GLOBALS && (flags & MAT_REMOTE_CONTENTS)) {
    match = match_remote_contents(who, name, type, flags, &last_match, &match_count);
  }
  if (flags & MAT_NEIGHBOR) {
    match = match_neighbor(who, name, type, flags, &last_match, &match_count);
  }
  if (flags & MAT_POSSESSION) {
    match = choose_thing(who, type, flags, match,
    match_possession(who, name, type, flags, &last_match, &match_count));
  }
  if (GoodObject(match))
    return match;
  if (flags & MAT_EXIT) {
    match = match_exit(who, name, type, flags);
    if (GoodObject(match))
      return match;
  }
  /* Miscellaneous ones */
  if (flags & MAT_CONTAINER) {
    match = match_container(who, name, type, flags, &last_match, &match_count);
  }
  if (flags & MAT_CARRIED_EXIT) {
    match = match_exit_internal(who, name, type, flags, who);
  }
  /* If we've got an exact match, we'll return it */
  if (GoodObject(match))
    return match;

  /* If it's a last_match_result, just return last_match */
  if (flags & MAT_LAST)
    return last_match;

  /* Set up the default match_result behavior */
  switch (match_count) {
  case 0:
    match = NOTHING;
    break;
  case 1:
    match = last_match;
    break;
  default:
    match = AMBIGUOUS;
    break;
  }

  /* Handle noisy_match_result */
  if (flags & MAT_NOISY) {
    switch (match) {
    case NOTHING:
      notify(who, nomatch_message);
      return NOTHING;
    case AMBIGUOUS:
      notify(who, ambiguous_message);
      return NOTHING;
    default:
      return match;
    }
  }
  return match;
}

static dbref
match_me(who, name)
    const dbref who;
    const char *name;
{
  if (!strcasecmp(name, "me"))
    return who;
  return NOTHING;
}

static dbref
match_here(who, name)
    const dbref who;
    const char *name;
{
  if (!strcasecmp(name, "here") && GoodObject(Location(who)))
    return Location(who);
  return NOTHING;
}

static dbref
match_absolute(name)
    const char *name;
{
  return parse_dbref(name);
}

static dbref
match_player(match_name)
    const char *match_name;
{
  dbref match;
  const char *p;


  if (*match_name == LOOKUP_TOKEN) {
    for (p = match_name + 1; isspace(*p); p++) ;
    if ((match = lookup_player(p)) != NOTHING) {
      return match;
    } else {
      /* try a partial match on a connected player, 2.0 style */
      /* Can return a match, NOTHING, or AMBIGUOUS */
      return short_page(p);
    }
  }
  return NOTHING;
}

static dbref
match_list(match_who, match_name, type, flags, first, last_match, match_count)
    const dbref match_who;
    const char *match_name;
    const int type;
    const long int flags;
    dbref first;
    dbref *last_match;
    int *match_count;
{
  dbref absolute;
  dbref the_match = NOTHING;
  absolute = match_absolute(match_name);
  if (!controls(match_who, absolute))
    absolute = NOTHING;

  DOLIST(first, first) {
    Access(first);
    if (first == absolute) {
      return first;
    } else if (!strcasecmp(Name(first), match_name)) {
      /* if there are multiple exact matches, randomly choose one */
      the_match = choose_thing(match_who, type, flags, the_match, first);
    } else if (string_match(Name(first), match_name)) {
      *last_match = first;
      *match_count += 1;
    }
  }
  return the_match;
}

static dbref
match_neighbor(who, name, type, flags, last_match, match_count)
    const dbref who;
    const char *name;
    const int type;
    const long int flags;
    dbref *last_match;
    int *match_count;
{
  dbref loc;
  loc = Location(who);
  if (GoodObject(loc)) {
    return match_list(who, name, type, flags, Contents(loc), last_match, match_count);
  }
  return NOTHING;
}

static dbref
match_possession(who, name, type, flags, last_match, match_count)
    const dbref who;
    const char *name;
    const int type;
    const long flags;
    dbref *last_match;
    int *match_count;
{
  return match_list(who, name, type, flags, Contents(who), last_match, match_count);
}

static dbref
match_exit(who, name, type, flags)
    const dbref who;
    const char *name;
    const int type;
    const long flags;
{
  dbref loc;
  loc = (Typeof(who) == TYPE_ROOM) ? who : Location(who);
  if (DO_GLOBALS) {
    if (flags & MAT_REMOTES)
      if (GoodObject(loc))
	return match_exit_internal(who, name, type, flags, Zone(loc));
      else
	return NOTHING;
    else if (flags & MAT_GLOBAL)
      return match_exit_internal(who, name, type, flags, MASTER_ROOM);
  }
  return match_exit_internal(who, name, type, flags, loc);
}

static dbref
match_exit_internal(match_who, match_name, type, flags, loc)
    const dbref match_who;
    const char *match_name;
    const int type;
    const long int flags;
    const dbref loc;
{
  dbref exit_tmp;
  dbref absolute;
  const char *match;
  const char *p;
  dbref the_match = NOTHING;

  if (!GoodObject(loc) || !match_name || !*match_name)
    return NOTHING;
  if (Typeof(loc) != TYPE_ROOM)
    return NOTHING;
  absolute = match_absolute(match_name);
  if (!controls(match_who, absolute)) {
    absolute = NOTHING;
  }
  DOLIST(exit_tmp, Exits(loc)) {
    Access(exit_tmp);
    if (exit_tmp == absolute) {
      the_match = exit_tmp;
    } else {
      match = Name(exit_tmp);
      while (*match) {
	/* check out this one */
	for (p = match_name;
	     (*p
	      && DOWNCASE(*p) == DOWNCASE(*match)
	      && *match != EXIT_DELIMITER);
	     p++, match++) ;
	/* did we get it? */
	if (*p == '\0') {
	  /* make sure there's nothing afterwards */
	  while (isspace(*match))
	    match++;
	  if (*match == '\0' || *match == EXIT_DELIMITER) {
	    /* we got it */
	    the_match = choose_thing(match_who, type, flags, the_match, exit_tmp);
	    goto next_exit;
	  }
	}
	/* we didn't get it, find next match */
	while (*match && *match++ != EXIT_DELIMITER) ;
	while (isspace(*match))
	  match++;
      }
    }
  next_exit:
    ;
  }
  return the_match;
}

static dbref
match_remote_contents(who, name, type, flags, last_match, match_count)
    const dbref who;
    const char *name;
    const int type;
    const long int flags;
    dbref *last_match;
    int *match_count;
{
  if (GoodObject(who))
    return match_list(who, name, type, flags, Contents(who), last_match, match_count);
  return NOTHING;
}

static dbref
match_container(who, name, type, flags, last_match, match_count)
    const dbref who;
    const char *name;
    const int type;
    const long int flags;
    dbref *last_match;
    int *match_count;
{
  dbref loc;
  loc = Location(who);
  if (GoodObject(loc))
    return match_list(who, name, type, flags, loc, last_match, match_count);
  return NOTHING;
}


static dbref
choose_thing(match_who, preferred_type, flags, thing1, thing2)
    const dbref match_who;
    const int preferred_type;
    long int flags;
    dbref thing1;
    dbref thing2;
{
  int has1;
  int has2;
  if (thing1 == NOTHING) {
    return thing2;
  } else if (thing2 == NOTHING) {
    return thing1;
  }
  if (preferred_type != NOTYPE) {
    if (Typeof(thing1) == preferred_type) {
      if (Typeof(thing2) != preferred_type) {
	return thing1;
      }
    } else if (Typeof(thing2) == preferred_type) {
      return thing2;
    }
  }
  if (flags & MAT_CHECK_KEYS) {
    has1 = could_doit(match_who, thing1);
    has2 = could_doit(match_who, thing2);

    if (has1 && !has2) {
      return thing1;
    } else if (has2 && !has1) {
      return thing2;
    }
    /* else fall through */
  }
  return (getrandom(2) ? thing1 : thing2);
}