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