pennmush/game/
pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
/* warnings.c - check to make sure rooms and exits are the way they
 * should be. */

#include "config.h"

#include <stdio.h>
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif

#include "copyrite.h"
#include "conf.h"
#include "mushdb.h"
#include "intrface.h"
#include "externs.h"
#include "match.h"
#include "warnings.h"
#include "confmagic.h"

#ifdef USE_WARNINGS

#define Warnings(x)	db[(x)].warnings

#define W_UNLOCKED	0x1
#define W_LOCKED	0x2

#define W_EXIT_ONEWAY	0x1
#define W_EXIT_MULTIPLE	0x2
#define W_EXIT_MSGS	0x4
#define W_EXIT_DESC	0x8
#define W_EXIT_UNLINKED	0x10
/* Space for more exit stuff */
#define W_THING_MSGS	0x100
#define W_THING_DESC	0x200
/* Space for more thing stuff */
#define W_ROOM_DESC	0x1000
/* Space for more room stuff */
#define W_PLAYER_DESC	0x10000

/* Groups of warnings */
#define W_NONE		0
#define W_SERIOUS	(W_EXIT_UNLINKED|W_THING_DESC|W_ROOM_DESC|W_PLAYER_DESC)
#define W_NORMAL	(W_SERIOUS|W_EXIT_ONEWAY|W_EXIT_MULTIPLE|W_EXIT_MSGS)
#define W_EXTRA		(W_NORMAL|W_THING_MSGS)
#define W_ALL		(W_EXTRA|W_EXIT_DESC)

typedef struct a_tcheck tcheck;
struct a_tcheck {
  const char *name;
  warn_type flag;
};


static int warning_lock_type _((dbref i, const struct boolexp * l));
static void complain _((dbref player, dbref i, const char *name, const char *desc));
static void ct_room _((dbref player, dbref i, warn_type flags));
static void ct_exit _((dbref player, dbref i, warn_type flags));
static void ct_player _((dbref player, dbref i, warn_type flags));
static void ct_thing _((dbref player, dbref i, warn_type flags));
void set_initial_warnings _((dbref player));
void do_warnings _((dbref player, char *name, char *warns));
const char *unparse_warnings _((dbref thing));
static void check_topology_on _((dbref player, dbref i));
void run_topology _((void));
void do_wcheck_all _((dbref player));
void do_wcheck _((dbref player, char *name));


static tcheck checklist[] =
{
  {"none", W_NONE},		/* MUST BE FIRST! */
  {"exit-unlinked", W_EXIT_UNLINKED},
  {"thing-desc", W_THING_DESC},
  {"room-desc", W_ROOM_DESC},
  {"my-desc", W_PLAYER_DESC},
  {"exit-oneway", W_EXIT_ONEWAY},
  {"exit-multiple", W_EXIT_MULTIPLE},
  {"exit-msgs", W_EXIT_MSGS},
  {"thing-msgs", W_THING_MSGS},
  {"exit-desc", W_EXIT_DESC},

  /* These should stay at the end */
  {"serious", W_SERIOUS},
  {"normal", W_NORMAL},
  {"extra", W_EXTRA},
  {"all", W_ALL},
  {NULL, 0}
};

/* This is really simple-minded for efficiency. Basically, if it's
 * unlocked, it's unlocked. If it's locked to something starting with
 * a specific db#, it's locked. Anything else, and we don't know.
 */
static int
warning_lock_type(i, l)
    dbref i;
    const struct boolexp *l;	/* 0== unlocked. 1== locked, 2== sometimes */
{
  if (l == TRUE_BOOLEXP)
    return W_UNLOCKED;
  if (l->type == BOOLEXP_CONST ||
      l->type == BOOLEXP_CARRY ||
      l->type == BOOLEXP_IS ||
      l->type == BOOLEXP_OWNER)
    return W_LOCKED;
  return (W_LOCKED | W_UNLOCKED);
}

static void
complain(player, i, name, desc)
    dbref player;
    dbref i;
    const char *name;
    const char *desc;
{
  notify(player, tprintf("Warning '%s' for %s:\r\n     %s",
			 name, real_unparse(player, i, 0), desc));
}

static void
ct_room(player, i, flags)
    dbref player;
    dbref i;
    warn_type flags;
{
  if ((flags & W_ROOM_DESC) && !atr_get(i, "DESCRIBE"))
    complain(player, i, "room-desc", "room has no description");
}

static void
ct_exit(player, i, flags)
    dbref player;
    dbref i;
    warn_type flags;
{
  dbref j, src, dst;
  int count = 0;
  int lt;

  /* i must be an exit, must be in a valid room, and must lead to a
   * different room
   * Remember, for exit i, db[i].exits = source room
   * and db[i].location = destination room
   */

  dst = Location(i);
  if ((flags & W_EXIT_UNLINKED) && (dst == NOTHING))
    complain(player, i, "exit-unlinked", "exit is unlinked; anyone can steal it");

  if (!Dark(i)) {
    if (flags & W_EXIT_MSGS) {
      lt = warning_lock_type(i, Key(i));
      if ((lt & W_UNLOCKED) &&
	  (!atr_get(i, "OSUCCESS") || !atr_get(i, "ODROP") ||
	   !atr_get(i, "SUCCESS")))
	complain(player, i, "exit-msgs", "possibly unlocked exit missing succ/osucc/odrop");
      if ((lt & W_LOCKED) && !atr_get(i, "FAIL"))
	complain(player, i, "exit-msgs", "possibly locked exit missing fail");
    }
    if (flags & W_EXIT_DESC) {
      if (!atr_get(i, "DESCRIBE"))
	complain(player, i, "exit-desc", "exit is missing description");
    }
  }
  src = Exits(i);
  if (!GoodObject(src) || (Typeof(src) != TYPE_ROOM))
    return;
  if (src == dst)
    return;
  /* Don't complain about exits linked to HOME or variable exits. */
  if (!GoodObject(dst))
    return;

  for (j = Exits(dst); GoodObject(j); j = Next(j))
    if (Location(j) == src) {
      if (!(flags & W_EXIT_MULTIPLE))
	return;
      else
	count++;
    }
  if ((count == 0) && (flags & W_EXIT_ONEWAY))
    complain(player, i, "exit-oneway", "exit has no return exit");
  else if ((count > 1) && (flags & W_EXIT_MULTIPLE)) {
    char buff[BUFFER_LEN];
    strncpy(buff, tprintf("exit has multiple (%d) return exits", count), BUFFER_LEN);
    buff[BUFFER_LEN - 1] = '\0';
    complain(player, i, "exit-multiple", buff);
  }
}



static void
ct_player(player, i, flags)
    dbref player;
    dbref i;
    warn_type flags;
{
  if ((flags & W_PLAYER_DESC) && !atr_get(i, "DESCRIBE"))
    complain(player, i, "my-desc", "player is missing description");
}



static void
ct_thing(player, i, flags)
    dbref player;
    dbref i;
    warn_type flags;
{
  int lt;

  /* Ignore carried objects */
  if (Location(i) == player)
    return;
  if ((flags & W_THING_DESC) && !atr_get(i, "DESCRIBE"))
    complain(player, i, "thing-desc", "thing is missing description");

  if (flags & W_THING_MSGS) {
    lt = warning_lock_type(i, Key(i));
    if ((lt & W_UNLOCKED) &&
	(!atr_get(i, "OSUCCESS") || !atr_get(i, "ODROP") ||
	 !atr_get(i, "SUCCESS") || !atr_get(i, "DROP")))
      complain(player, i, "thing-msgs", "possibly unlocked thing missing succ/osucc/drop/odrop");
    if ((lt & W_LOCKED) && !atr_get(i, "FAIL"))
      complain(player, i, "thing-msgs", "possibly locked thing missing fail");
  }
}

void
set_initial_warnings(player)
    dbref player;
{
  db[player].warnings = W_NORMAL;
  return;
}

void
do_warnings(player, name, warns)
    dbref player;
    char *name;
    char *warns;
{
  dbref thing;
  int found = 0;
  warn_type flags, negate_flags;
  char tbuf1[BUFFER_LEN];
  char *w, *s;
  tcheck *c;

  switch (thing = match_result(player, name, NOTYPE, MAT_EVERYTHING)) {
  case NOTHING:
    notify(player, "I don't see that object.");
    return;
  case AMBIGUOUS:
    notify(player, "I don't know which one you mean.");
    return;
  default:
    if (!controls(player, thing)) {
      notify(player, "Permission denied.");
      return;
    }
    if (Destroyed(thing)) {
      notify(player, "Why would you want to be warned about garbage?");
      return;
    }
    break;
  }

  flags = W_NONE;
  negate_flags = W_NONE;
  if (warns && *warns) {
    strcpy(tbuf1, warns);
    /* Loop through whatever's listed and add on those warnings */
    s = trim_space_sep(tbuf1, ' ');
    w = split_token(&s, ' ');
    while (w && *w) {
      found = 0;
      if (*w == '!') {
	/* Found a negated warning */
	w++;
	for (c = checklist; c->name; c++) {
	  if (!strcasecmp(w, c->name)) {
	    negate_flags |= c->flag;
	    found++;
	  }
	}
      } else {
	for (c = checklist; c->name; c++) {
	  if (!strcasecmp(w, c->name)) {
	    flags |= c->flag;
	    found++;
	  }
	}
      }
      /* At this point, we haven't matched any warnings. */
      if (!found) {
	notify(player, tprintf("Unknown warning: %s", w));
      }
      w = split_token(&s, ' ');
    }
    if (flags || !negate_flags) {
      db[thing].warnings = (flags & ~negate_flags);
    } else {
      db[thing].warnings &= ~negate_flags;
    }
    notify(player, tprintf("@warnings set to %s", unparse_warnings(thing)));
    return;
  }
}


/* Given an object, return a string of warnings on it */
const char *
unparse_warnings(thing)
    dbref thing;
{
  static char tbuf1[BUFFER_LEN];
  int warns, listsize, index;
  warn_type the_flag;

  warns = Warnings(thing);

  if (!warns)
    return "none";

  strcpy(tbuf1, "");

  /* Get the # of elements in checklist */
  listsize = sizeof(checklist) / sizeof(tcheck);

  /* Point c at last non-null in checklist, and go backwards */
  for (index = listsize - 2; warns && (index >= 0); index--) {
    the_flag = checklist[index].flag;
    if (!(the_flag & ~warns)) {
      /* Which is to say:
       * if the bits set on the_flag is a subset of the bits set on warns
       */
      strcat(tbuf1, checklist[index].name);
      strcat(tbuf1, " ");
      /* If we've got a flag which subsumes smaller ones, don't
       * list the smaller ones
       */
      warns &= ~the_flag;
    }
  }
  return tbuf1;
}

static void
check_topology_on(player, i)
    dbref player;
    dbref i;
{
  warn_type flags;

  /* Skip it if it's NOWARN or the player checking is the owner and
   * is NOWARN 
   */
  if (Flags(i) & NOWARN)
    return;

  /* If the owner is checking, use the flags on the object, and fall back
   * on the owner's flags as default. If it's not the owner checking
   * (therefore, an admin), ignore the object flags, use the admin's flags
   */
  if (Owner(player) == Owner(i)) {
    if (!(flags = Warnings(i)))
      flags = Warnings(player);
  } else
    flags = Warnings(player);

  switch (Typeof(i)) {
  case TYPE_ROOM:
    ct_room(player, i, flags);
    break;
  case TYPE_THING:
    ct_thing(player, i, flags);
    break;
  case TYPE_EXIT:
    ct_exit(player, i, flags);
    break;
  case TYPE_PLAYER:
    ct_player(player, i, flags);
    break;
  }
  return;
}


/* Loop through all objects and check their topology */
void
run_topology()
{
  int ndone;
  for (ndone = 0; ndone < db_top; ndone++) {
    if (!(Destroyed(ndone)) && Connected(Owner(ndone)) &&
	!(Flags(Owner(ndone)) & NOWARN)) {
      check_topology_on(Owner(ndone), ndone);
    }
  }
}

void
do_wcheck_all(player)
    dbref player;
{
  if (!Wizard(player)) {
    notify(player, "You'd better check your wizbit first.");
    return;
  }
  notify(player, "Running database topology warning checks");
  run_topology();
  notify(player, "Warning checks complete.");
}

/* Called when a player wants to do a check on something. We check for
 * ownership or hasprivs before allowing it
 */
void
do_wcheck(player, name)
    dbref player;
    char *name;
{
  dbref thing;

  switch (thing = match_result(player, name, NOTYPE, MAT_EVERYTHING)) {
  case NOTHING:
    notify(player, "I don't see that object.");
    return;
  case AMBIGUOUS:
    notify(player, "I don't know which one you mean.");
    return;
  default:
    if (!(See_All(player) || (Owner(player) == Owner(thing)))) {
      notify(player, "Permission denied.");
      return;
    }
    if (Destroyed(thing)) {
      notify(player, "Why would you want to be warned about garbage?");
      return;
    }
    break;
  }

  check_topology_on(player, thing);
  notify(player, "@wcheck complete.");
  return;
}


#endif				/* USE_WARNINGS */