/**
* \file warnings.c
*
* \brief Check topology and messages on PennMUSH objects and give warnings
*
*
*/
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "copyrite.h"
#include "conf.h"
#include "externs.h"
#include "mushdb.h"
#include "lock.h"
#include "flags.h"
#include "dbdefs.h"
#include "match.h"
#include "attrib.h"
#include "confmagic.h"
/* We might check for both locked and unlocked warnings if we can't
* figure out a lock.
*/
#define W_UNLOCKED 0x1 /**< Check for unlocked-object warnings */
#define W_LOCKED 0x2 /**< Check for locked-object warnings */
#define W_EXIT_ONEWAY 0x1 /**< Find one-way exits */
#define W_EXIT_MULTIPLE 0x2 /**< Find multiple exits to same place */
#define W_EXIT_MSGS 0x4 /**< Find exits without messages */
#define W_EXIT_DESC 0x8 /**< Find exits without descs */
#define W_EXIT_UNLINKED 0x10 /**< Find unlinked exits */
/* Space for more exit stuff */
#define W_THING_MSGS 0x100 /**< Find things without messages */
#define W_THING_DESC 0x200 /**< Find things without descs */
/* Space for more thing stuff */
#define W_ROOM_DESC 0x1000 /**< Find rooms without descs */
/* Space for more room stuff */
#define W_PLAYER_DESC 0x10000 /**< Find players without descs */
#define W_LOCK_PROBS 0x100000 /**< Find bad locks */
/* Groups of warnings */
#define W_NONE 0 /**< No warnings */
/** Serious warnings only */
#define W_SERIOUS (W_EXIT_UNLINKED|W_THING_DESC|W_ROOM_DESC|W_PLAYER_DESC|W_LOCK_PROBS)
/** Standard warnings: serious warnings plus others */
#define W_NORMAL (W_SERIOUS|W_EXIT_ONEWAY|W_EXIT_MULTIPLE|W_EXIT_MSGS)
/** Extra warnings: standard warnings plus others */
#define W_EXTRA (W_NORMAL|W_THING_MSGS)
/** All warnings */
#define W_ALL (W_EXTRA|W_EXIT_DESC)
/** A structure representing a topology warning check. */
typedef struct a_tcheck {
const char *name; /**< Name of warning. */
warn_type flag; /**< Bitmask of warning. */
} tcheck;
extern int warning_lock_type(const boolexp l);
void complain(dbref player, dbref i, const char *name, const char *desc, ...)
__attribute__ ((__format__(__printf__, 4, 5)));
extern void check_lock(dbref player, dbref i, const char *name, boolexp be);
static void ct_generic(dbref player, dbref i, warn_type flags);
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);
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},
{"lock-checks", W_LOCK_PROBS},
/* These should stay at the end */
{"serious", W_SERIOUS},
{"normal", W_NORMAL},
{"extra", W_EXTRA},
{"all", W_ALL},
{NULL, 0}
};
/** Issue a warning about an object.
* \param player player to receive the warning notification.
* \param i object the warning is about.
* \param name name of the warnings.
* \param desc a formatting string for the warning message.
*/
void
complain(dbref player, dbref i, const char *name, const char *desc, ...)
{
#ifdef HAS_VSNPRINTF
char buff[BUFFER_LEN];
#else
char buff[BUFFER_LEN * 3]; /* safety margin */
#endif
va_list args;
va_start(args, desc);
#ifdef HAS_VSNPRINTF
vsnprintf(buff, sizeof buff, desc, args);
#else
vsprintf(buff, desc, args);
#endif
buff[BUFFER_LEN - 1] = '\0';
va_end(args);
notify_format(player, T("Warning '%s' for %s:"),
name, unparse_object(player, i));
notify(player, buff);
}
static void
ct_generic(dbref player, dbref i, warn_type flags)
{
if ((flags & W_LOCK_PROBS)) {
lock_list *ll;
for (ll = Locks(i); ll; ll = L_NEXT(ll)) {
check_lock(player, i, L_TYPE(ll), L_KEY(ll));
}
}
}
static void
ct_room(dbref player, dbref i, warn_type flags)
{
if ((flags & W_ROOM_DESC) && !atr_get(i, "DESCRIBE"))
complain(player, i, "room-desc", T("room has no description"));
}
static void
ct_exit(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, Exits(i) = source room
* and Location(i) = destination room
*/
dst = Destination(i);
if ((flags & W_EXIT_UNLINKED) && (dst == NOTHING))
complain(player, i, "exit-unlinked",
T("exit is unlinked; anyone can steal it"));
if ((flags & W_EXIT_UNLINKED) && dst == AMBIGUOUS) {
ATTR *a;
const char *var = "DESTINATION";
a = atr_get(i, "DESTINATION");
if (!a)
a = atr_get(i, "EXITTO");
if (a)
var = "EXITTO";
if (!a)
complain(player, i, "exit-unlinked",
T("Variable exit has no %s attribute"), var);
else {
const char *x = atr_value(a);
if (!x || !*x)
complain(player, i, "exit-unlinked",
T("Variable exit has empty %s attribute"), var);
}
}
if (!Dark(i)) {
if (flags & W_EXIT_MSGS) {
lt = warning_lock_type(getlock(i, Basic_Lock));
if ((lt & W_UNLOCKED) &&
(!atr_get(i, "OSUCCESS") || !atr_get(i, "ODROP") ||
!atr_get(i, "SUCCESS")))
complain(player, i, "exit-msgs",
T("possibly unlocked exit missing succ/osucc/odrop"));
if ((lt & W_LOCKED) && !atr_get(i, "FAILURE"))
complain(player, i, "exit-msgs",
T("possibly locked exit missing fail"));
}
if (flags & W_EXIT_DESC) {
if (!atr_get(i, "DESCRIBE"))
complain(player, i, "exit-desc", T("exit is missing description"));
}
}
src = Source(i);
if (!GoodObject(src) || !IsRoom(src))
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", T("exit has no return exit"));
else if ((count > 1) && (flags & W_EXIT_MULTIPLE))
complain(player, i, "exit-multiple",
T("exit has multiple (%d) return exits"), count);
}
static void
ct_player(dbref player, dbref i, warn_type flags)
{
if ((flags & W_PLAYER_DESC) && !atr_get(i, "DESCRIBE"))
complain(player, i, "my-desc", T("player is missing description"));
}
static void
ct_thing(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", T("thing is missing description"));
if (flags & W_THING_MSGS) {
lt = warning_lock_type(getlock(i, Basic_Lock));
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",
T("possibly unlocked thing missing succ/osucc/drop/odrop"));
if ((lt & W_LOCKED) && !atr_get(i, "FAILURE"))
complain(player, i, "thing-msgs",
T("possibly locked thing missing fail"));
}
}
/** Set up the default warnings on an object.
* \param player object to set warnings on.
*/
void
set_initial_warnings(dbref player)
{
Warnings(player) = W_NORMAL;
return;
}
/** Set warnings on an object.
* \verbatim
* This implements @warnings obj=warning list
* \endverbatim
* \param player the enactor.
* \param name name of object to set warnings on.
* \param warns list of warnings to set, space-separated.
*/
void
do_warnings(dbref player, const char *name, const char *warns)
{
dbref thing;
warn_type w;
switch (thing = match_result(player, name, NOTYPE, MAT_EVERYTHING)) {
case NOTHING:
notify(player, T("I don't see that object."));
return;
case AMBIGUOUS:
notify(player, T("I don't know which one you mean."));
return;
default:
if (!controls(player, thing)) {
notify(player, T("Permission denied."));
return;
}
if (IsGarbage(thing)) {
notify(player, T("Why would you want to be warned about garbage?"));
return;
}
break;
}
w = parse_warnings(player, warns);
if (w >= 0) {
Warnings(thing) = w;
if (Warnings(thing))
notify_format(player, T("@warnings set to: %s"),
unparse_warnings(Warnings(thing)));
else
notify(player, T("@warnings cleared."));
} else {
notify(player, T("@warnings not changed."));
}
}
/** Given a list of warnings, return the bitmask that represents it.
* \param player dbref to report errors to, or NOTHING.
* \param warnings the string of warning names
* \return a warning bitmask
*/
warn_type
parse_warnings(dbref player, const char *warnings)
{
int found = 0;
warn_type flags, negate_flags;
char tbuf1[BUFFER_LEN];
char *w, *s;
tcheck *c;
flags = W_NONE;
negate_flags = W_NONE;
if (warnings && *warnings) {
strcpy(tbuf1, warnings);
/* 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 && player != NOTHING) {
notify_format(player, T("Unknown warning: %s"), w);
}
w = split_token(&s, ' ');
}
/* If we haven't matched anything, don't change the player's stuff */
if (!found)
return -1;
return flags & ~negate_flags;
} else
return 0;
}
/** Given a warning bitmask, return a string of warnings on it.
* \param warns the warnings.
* \return pointer to statically allocated string listing warnings.
*/
const char *
unparse_warnings(warn_type warns)
{
static char tbuf1[BUFFER_LEN];
int listsize, indexx;
tbuf1[0] = '\0';
/* Get the # of elements in checklist */
listsize = sizeof(checklist) / sizeof(tcheck);
/* Point c at last non-null in checklist, and go backwards */
for (indexx = listsize - 2; warns && (indexx >= 0); indexx--) {
warn_type the_flag = checklist[indexx].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[indexx].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(dbref player, dbref i)
{
warn_type flags;
/* Skip it if it's NOWARN or the player checking is the owner and
* is NOWARN. Also skip GOING objects.
*/
if (Going(i) || NoWarn(i))
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);
ct_generic(player, i, flags);
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(void)
{
int ndone;
for (ndone = 0; ndone < db_top; ndone++) {
if (!IsGarbage(ndone) && Connected(Owner(ndone)) && !NoWarn(Owner(ndone))) {
check_topology_on(Owner(ndone), ndone);
}
}
}
/** Wizard command to check all objects.
* \verbatim
* This implements @wcheck/all.
* \endverbatim
* \param player the enactor.
*/
void
do_wcheck_all(dbref player)
{
if (!Wizard(player)) {
notify(player, T("You'd better check your wizbit first."));
return;
}
notify(player, T("Running database topology warning checks"));
run_topology();
notify(player, T("Warning checks complete."));
}
/** Check warnings on a specific player by themselves.
* \param player player checking warnings on their objects.
*/
void
do_wcheck_me(dbref player)
{
int ndone;
if (!Connected(player))
return;
for (ndone = 0; ndone < db_top; ndone++) {
if ((Owner(ndone) == player) && !IsGarbage(ndone))
check_topology_on(player, ndone);
}
notify(player, T("@wcheck complete."));
return;
}
/** Check warnings on a specific object.
* We check for ownership or hasprivs before allowing this.
* \param player the enactor.
* \param name name of object to check.
*/
void
do_wcheck(dbref player, const char *name)
{
dbref thing;
switch (thing = match_result(player, name, NOTYPE, MAT_EVERYTHING)) {
case NOTHING:
notify(player, T("I don't see that object."));
return;
case AMBIGUOUS:
notify(player, T("I don't know which one you mean."));
return;
default:
if (!(See_All(player) || (Owner(player) == Owner(thing)))) {
notify(player, T("Permission denied."));
return;
}
if (IsGarbage(thing)) {
notify(player, T("Why would you want to be warned about garbage?"));
return;
}
break;
}
check_topology_on(player, thing);
notify(player, T("@wcheck complete."));
return;
}