/* ************************************************************************
* File: interpreter.c Part of CircleMUD *
* Usage: parse user commands, search for specials, call ACMD functions *
* *
* All rights reserved. See license.doc for complete information. *
* *
* Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
* CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. *
************************************************************************ */
#define __INTERPRETER_C__
#include "conf.h"
#include "sysdep.h"
#include "structs.h"
#include "utils.h"
#include "character.h"
#include "comm.h"
#include "command.h"
#include "interpreter.h"
#include "db.h"
#include "spells.h"
#include "handler.h"
#include "mail.h"
#include "screen.h"
#include "log.h"
#include "item.h"
#include "room.h"
#include "zone.h"
#include "alias.h"
/* external variables */
extern roomData_t *rMortalStart;
extern roomData_t *rImmortalStart;
extern roomData_t *rFrozenStart;
extern roomData_t *rLimbo;
extern const char *class_menu;
extern char *motd;
extern char *imotd;
extern char *background;
extern char *MENU;
extern char *WELC_MESSG;
extern char *START_MESSG;
extern playerIndex_t *player_table;
extern int top_of_p_table;
extern int circle_restrict;
extern int no_specials;
extern int max_bad_pws;
/* external functions */
void echo_on(descriptorData_t *d);
void echo_off(descriptorData_t *d);
void do_start(charData_t *ch);
int parse_class(char arg);
int special(charData_t *ch, const commandData_t *cmd, char *arg);
int isbanned(char *hostname);
int Valid_Name(char *newname);
/* local functions */
int perform_dupe_check(descriptorData_t *d);
int reserved_word(char *argument);
int _parse_name(char *arg, char *name);
const char *fill[] = {
"in",
"from",
"with",
"the",
"on",
"at",
"to",
"\n"
};
const char *reserved[] = {
"a",
"an",
"self",
"me",
"all",
"room",
"someone",
"something",
"\n"
};
/***************************************************************************
* Various other parsing utilities *
**************************************************************************/
/*
* searches an array of strings for a target string. "exact" can be
* 0 or non-0, depending on whether or not the match must be exact for
* it to be returned. Returns -1 if not found; 0..n otherwise. Array
* must be terminated with a '\n' so it knows to stop searching.
*/
ssize_t search_block(const char *arg, const char **list, bool exact)
{
int i, l;
/* We used to have \r as the first character on certain array items to
* prevent the explicit choice of that point. It seems a bit silly to
* dump control characters into arrays to prevent that, so we'll just
* check in here to see if the first character of the argument is '!',
* and if so, just blindly return a '-1' for not found. - ae.
*/
if (*arg == '!')
return (-1);
/* Record the length of the argument. */
l = strlen(arg);
if (exact) {
for (i = 0; **(list + i) != '\n'; i++)
if (!strcasecmp(arg, *(list + i)))
return (i);
} else {
if (!l)
l = 1; /* Avoid "" to match the first available
* string */
for (i = 0; **(list + i) != '\n'; i++)
if (!strncasecmp(arg, *(list + i), l))
return (i);
}
return (-1);
}
/**
* Gets whether a string represents a numeric value.
* @param string the string to be tested
* @return <code>true</code> if the specified string represents an integer or
* floating point numeric value; <code>false</code> otherwise
*/
bool isNumber(const char *string) {
if (string && *string != '\0') {
/* Skip a leading + or - sign. */
if (*string == '+' || *string == '-') {
string++;
}
if (isdigit(*string)) {
/* Declare a variable to point to the end of the source string. */
char *endPointer = NULL;
/* Parse a floating point value from the source string. */
double dummy = strtod(string, &endPointer);
/* Skip any trailing whitespace. */
while (endPointer && *endPointer != '\0' && isspace(*endPointer)) {
endPointer++;
}
/* Test whether we parsed a valid number. */
if (dummy != 0.0 ||
(endPointer != string && *endPointer == '\0')) {
return (TRUE);
}
}
}
return (FALSE);
}
/**
* Reads one token from a source string.
* @param buf the buffer into which the token is to be written
* @param buflen the length of the specified buffer
* @param delims the list of characters delimiting the next token
* @param src the source string from which the token is to be read
* @return a pointer to the character in the source string that bounds the
* token written into the specified buffer, or NULL if some error occurs
*/
const char *onetoken(char *buf, size_t buflen, const char *delims,
const char *src) {
register const char *p = NULL;
if (buf == NULL) {
log("onetoken(): invalid 'buf' string.");
} else if (buflen == 0) {
log("onetoken(): invalid 'buflen' value 0.");
} else if (delims == NULL || *delims == '\0') {
log("onetoken(): invalid 'delims' string.");
} else if (src == NULL) {
log("onetoken(): invalid 'src' string.");
} else {
/* Declare a variable to contain the buffer position. */
register size_t bufpos = 0;
/* Iterate over the source string. */
for (p = src; *p != '\0' && strchr(delims, *p) == NULL; p++) {
/* Accumulate the character. */
if (bufpos < buflen - 1 && *p != '\r') {
buf[bufpos++] = *p;
}
}
/* Terminate the buffer with a NUL character. */
buf[bufpos] = '\0';
}
return (p);
}
/**
* Returns whether a string a valid integer.
* @param str the string to be tested
* @param allowSign whether an initial + or - sign is permitted
* @return TRUE if the string is a valid integer;
* FALSE otherwise
*/
bool isInteger(const char *str, bool allowSign) {
if (str != NULL && *str != '\0') {
/* Skip any leading whitespace. */
while (*str != '\0' && isspace(*str)) {
str++;
}
/* Skip the sign character if its permitted. */
if (allowSign && strchr("+-", *str) != NULL) {
str++;
}
if (isdigit(*str)) {
/* Skip any digit characters. */
while (isdigit(*str)) {
str++;
}
/* Skip any trailing whitespace. */
while (*str != '\0' && isspace(*str)) {
str++;
}
if (*str == '\0') {
return (TRUE);
}
}
}
return (FALSE);
}
/*
* Function to skip over the leading spaces of a string.
*/
void skip_spaces(char **string)
{
for (; **string && isspace(**string); (*string)++);
}
/*
* Given a string, change all instances of double dollar signs ($$) to
* single dollar signs ($). When strings come in, all $'s are changed
* to $$'s to avoid having users be able to crash the system if the
* inputted string is eventually sent to act(). If you are using user
* input to produce screen output AND YOU ARE SURE IT WILL NOT BE SENT
* THROUGH THE act() FUNCTION (i.e., do_gecho, do_title, but NOT do_say),
* you can call delete_doubledollar() to make the output look correct.
*
* Modifies the string in-place.
*/
char *delete_doubledollar(char *string)
{
char *ddread, *ddwrite;
/* If the string has no dollar signs, return immediately */
if ((ddwrite = strchr(string, '$')) == NULL)
return (string);
/* Start from the location of the first dollar sign */
ddread = ddwrite;
while (*ddread) /* Until we reach the end of the string... */
if ((*(ddwrite++) = *(ddread++)) == '$') /* copy one char */
if (*ddread == '$')
ddread++; /* skip if we saw 2 $'s in a row */
*ddwrite = '\0';
return (string);
}
/**
* Trims a string of superfluous whitespace.
* @param str the string to be trimmed
* @return the trimmed string, or NULL
*/
char *trim(char *str) {
if (str == NULL) {
log("trim(): invalid 'str' string.");
} else {
/* Declare a flag to indicate that we're not skipping spaces. */
bool write = FALSE;
/* Declare a iterator variable for reading. */
register char *readp = str;
/* Declare a iterator variable for writing. */
register char *writep = str;
/* Iterate over the string. */
while (*readp != '\0') {
if (write) {
if (isspace(*readp)) {
write = FALSE;
}
if (write) {
*writep++ = *readp;
}
} else {
if (!isspace(*readp)) {
if (writep != str) {
*writep++ = ' ';
}
*writep++ = *readp;
write = TRUE;
}
}
/* Advance the read pointer. */
readp++;
}
/* Terminate the string with a NUL character. */
*writep = '\0';
}
return (str);
}
int fill_word(char *argument)
{
return (search_block(argument, fill, TRUE) >= 0);
}
int reserved_word(char *argument)
{
return (search_block(argument, reserved, TRUE) >= 0);
}
/*
* copy the first non-fill-word, space-delimited argument of 'argument'
* to 'first_arg'; return a pointer to the remainder of the string.
*/
char *one_argument(char *argument, char *first_arg)
{
char *begin = first_arg;
if (!argument) {
log("SYSERR: one_argument received a NULL pointer!");
*first_arg = '\0';
return (NULL);
}
do {
skip_spaces(&argument);
first_arg = begin;
while (*argument && !isspace(*argument)) {
*(first_arg++) = *argument;
argument++;
}
*first_arg = '\0';
} while (fill_word(begin));
return (argument);
}
/**
* Count the number of words in a string
*
* @param string String to count arguments in
* @return number of words in string
*/
int numWords(char *string) {
int num = 0;
if (string) {
char *p = string;
bool onWord = FALSE;
while (p && *p != '\0') {
if (isspace(*p) && onWord == TRUE) {
onWord = FALSE;
}
if (!isspace(*p) && onWord == FALSE) {
onWord = TRUE;
num++;
}
p++;
}
}
return (num);
}
/*
* one_word is like one_argument, except that words in quotes ("") are
* considered one word.
*/
char *one_word(char *argument, char *first_arg)
{
char *begin = first_arg;
do {
skip_spaces(&argument);
first_arg = begin;
if (*argument == '\"') {
argument++;
while (*argument && *argument != '\"') {
*(first_arg++) = *argument;
argument++;
}
argument++;
} else {
while (*argument && !isspace(*argument)) {
*(first_arg++) = *argument;
argument++;
}
}
*first_arg = '\0';
} while (fill_word(begin));
return (argument);
}
/* same as one_argument except that it doesn't ignore fill words */
char *any_one_arg(char *argument, char *first_arg)
{
skip_spaces(&argument);
while (*argument && !isspace(*argument)) {
*(first_arg++) = *argument;
argument++;
}
*first_arg = '\0';
return (argument);
}
/*
* Same as one_argument except that it takes two args and returns the rest;
* ignores fill words
*/
char *two_arguments(char *argument, char *first_arg, char *second_arg)
{
return (one_argument(one_argument(argument, first_arg), second_arg)); /* :-) */
}
/*
* determine if a given string is an abbreviation of another
* (now works symmetrically -- JE 7/25/94)
*
* that was dumb. it shouldn't be symmetrical. JE 5/1/95
*
* returns 1 if arg1 is an abbreviation of arg2
*/
int is_abbrev(const char *arg1, const char *arg2)
{
if (!*arg1)
return (0);
for (; *arg1 && *arg2; arg1++, arg2++)
if (LOWER(*arg1) != LOWER(*arg2))
return (0);
if (!*arg1)
return (1);
else
return (0);
}
/*
* Return first space-delimited token in arg1; remainder of string in arg2.
*
* NOTE: Requires sizeof(arg2) >= sizeof(string)
*/
void half_chop(char *string, char *arg1, char *arg2)
{
char *temp;
temp = any_one_arg(string, arg1);
skip_spaces(&temp);
strcpy(arg2, temp); /* strcpy: OK (documentation) */
}
int special(charData_t *ch, const commandData_t *cmd, char *arg)
{
itemData_t *i;
charData_t *k;
int j;
/* special in room? */
if (GET_ROOM_SPEC(IN_ROOM(ch)) != NULL)
if (GET_ROOM_SPEC(IN_ROOM(ch)) (ch, IN_ROOM(ch), cmd, arg))
return (1);
/* special in equipment list? */
for (j = 0; j < NUM_WEARS; j++)
if (GET_EQ(ch, j) && GET_ITEM_SPEC(GET_EQ(ch, j)) != NULL)
if (GET_ITEM_SPEC(GET_EQ(ch, j)) (ch, GET_EQ(ch, j), cmd, arg))
return (1);
/* special in inventory? */
for (i = ch->carrying; i; i = i->nextContent)
if (GET_ITEM_SPEC(i) != NULL)
if (GET_ITEM_SPEC(i) (ch, i, cmd, arg))
return (1);
/* special in mobile present? */
for (k = ch->room->people; k; k = k->next_in_room)
if (!MOB_FLAGGED(k, MOB_NOTDEADYET))
if (GET_MOB_SPEC(k) && GET_MOB_SPEC(k)(ch, k, cmd, arg))
return (1);
/* special in object present? */
for (i = ch->room->contents; i; i = i->nextContent)
if (GET_ITEM_SPEC(i) != NULL)
if (GET_ITEM_SPEC(i)(ch, i, cmd, arg))
return (1);
return (0);
}
/* *************************************************************************
* Stuff for controlling the non-playing sockets (get name, pwd etc) *
************************************************************************* */
/* This function needs to die. */
int _parse_name(char *arg, char *name)
{
int i;
skip_spaces(&arg);
for (i = 0; (*name = *arg); arg++, i++, name++)
if (!isalpha(*arg))
return (1);
if (!i)
return (1);
return (0);
}
#define RECON 1
#define USURP 2
#define UNSWITCH 3
/* This function seems a bit over-extended. */
int perform_dupe_check(descriptorData_t *d)
{
descriptorData_t *k, *next_k;
charData_t *target = NULL, *ch, *next_ch;
int mode = 0;
int id = GET_IDNUM(d->character);
/*
* Now that this descriptor has successfully logged in, disconnect all
* other descriptors controlling a character with the same ID number.
*/
for (k = descriptor_list; k; k = next_k) {
next_k = k->next;
if (k == d)
continue;
if (k->original && (GET_IDNUM(k->original) == id)) {
/* Original descriptor was switched, booting it and restoring normal body control. */
write_to_output(d, "\r\nMultiple login detected -- disconnecting.\r\n");
STATE(k) = CON_CLOSE;
if (!target) {
target = k->original;
mode = UNSWITCH;
}
if (k->character)
k->character->desc = NULL;
k->character = NULL;
k->original = NULL;
} else if (k->character && GET_IDNUM(k->character) == id && k->original) {
/* Character taking over their own body, while an immortal was switched to it. */
do_return(k->character, NULL, 0);
} else if (k->character && GET_IDNUM(k->character) == id) {
/* Character taking over their own body. */
if (!target && STATE(k) == CON_PLAYING) {
write_to_output(k, "\r\nThis body has been usurped!\r\n");
target = k->character;
mode = USURP;
}
k->character->desc = NULL;
k->character = NULL;
k->original = NULL;
write_to_output(k, "\r\nMultiple login detected -- disconnecting.\r\n");
STATE(k) = CON_CLOSE;
}
}
/*
* now, go through the character list, deleting all characters that
* are not already marked for deletion from the above step (i.e., in the
* CON_HANGUP state), and have not already been selected as a target for
* switching into. In addition, if we haven't already found a target,
* choose one if one is available (while still deleting the other
* duplicates, though theoretically none should be able to exist).
*/
for (ch = character_list; ch; ch = next_ch) {
next_ch = ch->next;
if (IS_NPC(ch))
continue;
if (GET_IDNUM(ch) != id)
continue;
/* ignore chars with descriptors (already handled by above step) */
if (ch->desc)
continue;
/* don't extract the target char we've found one already */
if (ch == target)
continue;
/* we don't already have a target and found a candidate for switching */
if (!target) {
target = ch;
mode = RECON;
continue;
}
/* we've found a duplicate - blow him away, dumping his eq in limbo. */
if (IN_ROOM(ch) != NULL)
char_fromRoom(ch);
char_toRoom(ch, rLimbo);
char_extract(ch);
}
/* no target for switching into was found - allow login to continue */
if (!target)
return (0);
/* Okay, we've found a target. Connect d to target. */
free_char(d->character); /* get rid of the old char */
d->character = target;
d->character->desc = d;
d->original = NULL;
d->character->char_specials.timer = 0;
REMOVE_BIT(PLR_FLAGS(d->character), PLR_MAILING | PLR_WRITING);
REMOVE_BIT(AFF_FLAGS(d->character), AFF_GROUP);
STATE(d) = CON_PLAYING;
switch (mode) {
case RECON:
write_to_output(d, "Reconnecting.\r\n");
act("$n has reconnected.", TRUE, d->character, 0, 0, TO_ROOM);
mudlog(NRM, MAX(AUTH_WIZARD, GET_INVIS_AUTH(d->character)), TRUE, "%s [%s] has reconnected.", GET_NAME(d->character), d->host);
break;
case USURP:
write_to_output(d, "You take over your own body, already in use!\r\n");
act("$n suddenly keels over in pain, surrounded by a white aura...\r\n"
"$n's body has been taken over by a new spirit!",
TRUE, d->character, 0, 0, TO_ROOM);
mudlog(NRM, MAX(AUTH_WIZARD, GET_INVIS_AUTH(d->character)), TRUE,
"%s has re-logged in ... disconnecting old socket.", GET_NAME(d->character));
break;
case UNSWITCH:
write_to_output(d, "Reconnecting to unswitched char.");
mudlog(NRM, MAX(AUTH_WIZARD, GET_INVIS_AUTH(d->character)), TRUE, "%s [%s] has reconnected.", GET_NAME(d->character), d->host);
break;
}
return (1);
}
/* deal with newcomers and other non-playing sockets */
void nanny(descriptorData_t *d, char *arg)
{
int load_result; /* Overloaded variable */
skip_spaces(&arg);
switch (STATE(d)) {
case CON_GET_NAME: /* wait for input of name */
if (d->character == NULL) {
CREATE(d->character, charData_t, 1);
clear_char(d->character);
CREATE(d->character->player_specials, playerSpecials_t, 1);
d->character->desc = d;
}
if (!*arg)
STATE(d) = CON_CLOSE;
else {
char buf[MAX_INPUT_LENGTH], tmp_name[MAX_INPUT_LENGTH];
pfileElement_t tmp_store;
int player_i;
if ((_parse_name(arg, tmp_name)) || strlen(tmp_name) < 2 ||
strlen(tmp_name) > MAX_NAME_LENGTH || !Valid_Name(tmp_name) ||
fill_word(strcpy(buf, tmp_name)) || reserved_word(buf)) { /* strcpy: OK (mutual MAX_INPUT_LENGTH) */
write_to_output(d, "Invalid name, please try another.\r\nName: ");
return;
}
if ((player_i = load_char(tmp_name, &tmp_store)) > -1) {
store_to_char(&tmp_store, d->character);
GET_PFILEPOS(d->character) = player_i;
if (PLR_FLAGGED(d->character, PLR_DELETED)) {
/* We get a false positive from the original deleted character. */
free_char(d->character);
/* Check for multiple creations... */
if (!Valid_Name(tmp_name)) {
write_to_output(d, "Invalid name, please try another.\r\nName: ");
return;
}
CREATE(d->character, charData_t, 1);
clear_char(d->character);
CREATE(d->character->player_specials, playerSpecials_t, 1);
d->character->desc = d;
CREATE(d->character->player.name, char, strlen(tmp_name) + 1);
strcpy(d->character->player.name, CAP(tmp_name)); /* strcpy: OK (size checked above) */
GET_PFILEPOS(d->character) = player_i;
write_to_output(d, "Did I get that right, %s (Y/N)? ", tmp_name);
STATE(d) = CON_NAME_CNFRM;
} else {
/* undo it just in case they are set */
REMOVE_BIT(PLR_FLAGS(d->character),
PLR_WRITING | PLR_MAILING | PLR_CRYO);
REMOVE_BIT(AFF_FLAGS(d->character), AFF_GROUP);
write_to_output(d, "Password: ");
echo_off(d);
d->idle_tics = 0;
STATE(d) = CON_PASSWORD;
}
} else {
/* player unknown -- make new character */
/* Check for multiple creations of a character. */
if (!Valid_Name(tmp_name)) {
write_to_output(d, "Invalid name, please try another.\r\nName: ");
return;
}
CREATE(d->character->player.name, char, strlen(tmp_name) + 1);
strcpy(d->character->player.name, CAP(tmp_name)); /* strcpy: OK (size checked above) */
write_to_output(d, "Did I get that right, %s (Y/N)? ", tmp_name);
STATE(d) = CON_NAME_CNFRM;
}
}
break;
case CON_NAME_CNFRM: /* wait for conf. of new name */
if (UPPER(*arg) == 'Y') {
if (isbanned(d->host) >= BAN_NEW) {
mudlog(NRM, AUTH_WIZARD, TRUE, "Request for new char %s denied from [%s] (siteban)", GET_PC_NAME(d->character), d->host);
write_to_output(d, "Sorry, new characters are not allowed from your site!\r\n");
STATE(d) = CON_CLOSE;
return;
}
if (circle_restrict) {
write_to_output(d, "Sorry, new players can't be created at the moment.\r\n");
mudlog(NRM, AUTH_WIZARD, TRUE, "Request for new char %s denied from [%s] (wizlock)", GET_PC_NAME(d->character), d->host);
STATE(d) = CON_CLOSE;
return;
}
write_to_output(d, "New character.\r\nGive me a password for %s: ", GET_PC_NAME(d->character));
echo_off(d);
STATE(d) = CON_NEWPASSWD;
} else if (*arg == 'n' || *arg == 'N') {
write_to_output(d, "Okay, what IS it, then? ");
free_char(d->character);
STATE(d) = CON_GET_NAME;
} else
write_to_output(d, "Please type Yes or No: ");
break;
case CON_PASSWORD: /* get pwd for known player */
/*
* To really prevent duping correctly, the player's record should
* be reloaded from disk at this point (after the password has been
* typed). However I'm afraid that trying to load a character over
* an already loaded character is going to cause some problem down the
* road that I can't see at the moment. So to compensate, I'm going to
* (1) add a 15 or 20-second time limit for entering a password, and (2)
* re-add the code to cut off duplicates when a player quits. JE 6 Feb 96
*/
echo_on(d); /* turn echo back on */
/* New echo_on() eats the return on telnet. Extra space better than none. */
write_to_output(d, "\r\n");
if (!*arg)
STATE(d) = CON_CLOSE;
else {
if (strncmp(CRYPT(arg, GET_PASSWD(d->character)), GET_PASSWD(d->character), MAX_PWD_LENGTH)) {
mudlog(BRF, AUTH_WIZARD, TRUE, "Bad PW: %s [%s]", GET_NAME(d->character), d->host);
GET_BAD_PWS(d->character)++;
save_char(d->character);
if (++(d->bad_pws) >= max_bad_pws) { /* 3 strikes and you're out. */
write_to_output(d, "Wrong password... disconnecting.\r\n");
STATE(d) = CON_CLOSE;
} else {
write_to_output(d, "Wrong password.\r\nPassword: ");
echo_off(d);
}
return;
}
/* Password was correct. */
load_result = GET_BAD_PWS(d->character);
GET_BAD_PWS(d->character) = 0;
d->bad_pws = 0;
if (isbanned(d->host) == BAN_SELECT &&
!PLR_FLAGGED(d->character, PLR_SITEOK)) {
write_to_output(d, "Sorry, this char has not been cleared for login from your site!\r\n");
STATE(d) = CON_CLOSE;
mudlog(NRM, AUTH_WIZARD, TRUE, "Connection attempt for %s denied from %s", GET_NAME(d->character), d->host);
return;
}
if (GET_LEVEL(d->character) < circle_restrict) {
write_to_output(d, "The game is temporarily restricted.. try again later.\r\n");
STATE(d) = CON_CLOSE;
mudlog(NRM, AUTH_WIZARD, TRUE, "Request for login denied for %s [%s] (wizlock)", GET_NAME(d->character), d->host);
return;
}
/* check and make sure no other copies of this player are logged in */
if (perform_dupe_check(d))
return;
if (GET_AUTH(d->character) >= AUTH_WIZARD)
write_to_output(d, "%s", imotd);
else
write_to_output(d, "%s", motd);
mudlog(BRF, MAX(AUTH_WIZARD, GET_INVIS_AUTH(d->character)), TRUE, "%s [%s] has connected.", GET_NAME(d->character), d->host);
if (load_result) {
write_to_output(d, "\r\n\r\n\007\007\007"
"%s%d LOGIN FAILURE%s SINCE LAST SUCCESSFUL LOGIN.%s\r\n",
CCRED(d->character, C_SPR), load_result,
(load_result > 1) ? "S" : "", CCNRM(d->character, C_SPR));
GET_BAD_PWS(d->character) = 0;
}
write_to_output(d, "\r\n*** PRESS RETURN: ");
STATE(d) = CON_RMOTD;
}
break;
case CON_NEWPASSWD:
case CON_CHPWD_GETNEW:
if (!*arg || strlen(arg) > MAX_PWD_LENGTH || strlen(arg) < 3 ||
!str_cmp(arg, GET_PC_NAME(d->character))) {
write_to_output(d, "\r\nIllegal password.\r\nPassword: ");
return;
}
strncpy(GET_PASSWD(d->character), CRYPT(arg, GET_PC_NAME(d->character)), MAX_PWD_LENGTH); /* strncpy: OK (G_P:MAX_PWD_LENGTH+1) */
*(GET_PASSWD(d->character) + MAX_PWD_LENGTH) = '\0';
write_to_output(d, "\r\nPlease retype password: ");
if (STATE(d) == CON_NEWPASSWD)
STATE(d) = CON_CNFPASSWD;
else
STATE(d) = CON_CHPWD_VRFY;
break;
case CON_CNFPASSWD:
case CON_CHPWD_VRFY:
if (strncmp(CRYPT(arg, GET_PASSWD(d->character)), GET_PASSWD(d->character),
MAX_PWD_LENGTH)) {
write_to_output(d, "\r\nPasswords don't match... start over.\r\nPassword: ");
if (STATE(d) == CON_CNFPASSWD)
STATE(d) = CON_NEWPASSWD;
else
STATE(d) = CON_CHPWD_GETNEW;
return;
}
echo_on(d);
if (STATE(d) == CON_CNFPASSWD) {
write_to_output(d, "\r\nWhat is your sex (M/F)? ");
STATE(d) = CON_QSEX;
} else {
save_char(d->character);
write_to_output(d, "\r\nDone.\r\n%s", MENU);
STATE(d) = CON_MENU;
}
break;
case CON_QSEX: /* query sex of new user */
switch (*arg) {
case 'm':
case 'M':
d->character->player.sex = SEX_MALE;
break;
case 'f':
case 'F':
d->character->player.sex = SEX_FEMALE;
break;
default:
write_to_output(d, "That is not a sex..\r\n"
"What IS your sex? ");
return;
}
write_to_output(d, "%s\r\nClass: ", class_menu);
STATE(d) = CON_QCLASS;
break;
case CON_QCLASS:
load_result = parse_class(*arg);
if (load_result == CLASS_UNDEFINED) {
write_to_output(d, "\r\nThat's not a class.\r\nClass: ");
return;
} else
GET_CLASS(d->character) = load_result;
if (GET_PFILEPOS(d->character) < 0)
GET_PFILEPOS(d->character) = create_entry(GET_PC_NAME(d->character));
/* Now GET_NAME() will work properly. */
init_char(d->character);
save_char(d->character);
write_to_output(d, "%s\r\n*** PRESS RETURN: ", motd);
STATE(d) = CON_RMOTD;
mudlog(NRM, AUTH_WIZARD, TRUE, "%s [%s] new player.", GET_NAME(d->character), d->host);
break;
case CON_RMOTD: /* read CR after printing motd */
write_to_output(d, "%s", MENU);
STATE(d) = CON_MENU;
break;
case CON_MENU: { /* get selection from main menu */
roomData_t *load_room;
switch (*arg) {
case '0':
write_to_output(d, "Goodbye.\r\n");
STATE(d) = CON_CLOSE;
break;
case '1':
reset_char(d->character);
read_aliases(d->character);
if (PLR_FLAGGED(d->character, PLR_INVSTART))
GET_INVIS_AUTH(d->character) = GET_LEVEL(d->character);
/*
* We have to place the character in a room before equipping them
* or char_equipItem() will gripe about the person in NULL.
*/
load_room = GET_LOADROOM(d->character);
/* If char was saved with NULL, or real_room above failed... */
if (load_room == NULL) {
if (GET_AUTH(d->character) >= AUTH_WIZARD)
load_room = rImmortalStart;
else
load_room = rMortalStart;
}
if (PLR_FLAGGED(d->character, PLR_FROZEN))
load_room = rFrozenStart;
send_to_char(d->character, "%s", WELC_MESSG);
d->character->next = character_list;
character_list = d->character;
char_toRoom(d->character, load_room);
load_result = Crash_load(d->character);
/* Clear their load room if it's not persistant. */
if (!PLR_FLAGGED(d->character, PLR_LOADROOM))
GET_LOADROOM(d->character) = NULL;
save_char(d->character);
act("$n has entered the game.", TRUE, d->character, 0, 0, TO_ROOM);
STATE(d) = CON_PLAYING;
if (GET_LEVEL(d->character) == 0) {
do_start(d->character);
send_to_char(d->character, "%s", START_MESSG);
}
look_at_room(d->character, 0);
if (has_mail(GET_IDNUM(d->character)))
send_to_char(d->character, "You have mail waiting.\r\n");
if (load_result == 2) { /* rented items lost */
send_to_char(d->character, "\r\n\007You could not afford your rent!\r\n"
"Your possesions have been donated to the Salvation Army!\r\n");
}
d->has_prompt = 0;
break;
case '2':
if (d->character->player.description) {
write_to_output(d, "Old description:\r\n%s", d->character->player.description);
free(d->character->player.description);
d->character->player.description = NULL;
}
write_to_output(d, "Enter the new text you'd like others to see when they look at you.\r\n"
"Terminate with a '@' on a new line.\r\n");
d->str = &d->character->player.description;
d->max_str = EXDSCR_LENGTH;
STATE(d) = CON_EXDESC;
break;
case '3':
page_string(d, background, 0);
STATE(d) = CON_RMOTD;
break;
case '4':
write_to_output(d, "\r\nEnter your old password: ");
echo_off(d);
STATE(d) = CON_CHPWD_GETOLD;
break;
case '5':
write_to_output(d, "\r\nEnter your password for verification: ");
echo_off(d);
STATE(d) = CON_DELCNF1;
break;
default:
write_to_output(d, "\r\nThat's not a menu choice!\r\n%s", MENU);
break;
}
break;
}
case CON_CHPWD_GETOLD:
if (strncmp(CRYPT(arg, GET_PASSWD(d->character)), GET_PASSWD(d->character), MAX_PWD_LENGTH)) {
echo_on(d);
write_to_output(d, "\r\nIncorrect password.\r\n%s", MENU);
STATE(d) = CON_MENU;
} else {
write_to_output(d, "\r\nEnter a new password: ");
STATE(d) = CON_CHPWD_GETNEW;
}
return;
case CON_DELCNF1:
echo_on(d);
if (strncmp(CRYPT(arg, GET_PASSWD(d->character)), GET_PASSWD(d->character), MAX_PWD_LENGTH)) {
write_to_output(d, "\r\nIncorrect password.\r\n%s", MENU);
STATE(d) = CON_MENU;
} else {
write_to_output(d, "\r\nYOU ARE ABOUT TO DELETE THIS CHARACTER PERMANENTLY.\r\n"
"ARE YOU ABSOLUTELY SURE?\r\n\r\n"
"Please type \"yes\" to confirm: ");
STATE(d) = CON_DELCNF2;
}
break;
case CON_DELCNF2:
if (!strcasecmp(arg, "yes") || !strcasecmp(arg, "YES")) {
if (PLR_FLAGGED(d->character, PLR_FROZEN)) {
write_to_output(d, "You try to kill yourself, but the ice stops you.\r\n"
"Character not deleted.\r\n\r\n");
STATE(d) = CON_CLOSE;
return;
}
if (GET_AUTH(d->character) < AUTH_WIZARD)
SET_BIT(PLR_FLAGS(d->character), PLR_DELETED);
save_char(d->character);
Crash_delete_file(GET_NAME(d->character));
delete_aliases(GET_NAME(d->character));
write_to_output(d, "Character '%s' deleted!\r\n"
"Goodbye.\r\n", GET_NAME(d->character));
mudlog(NRM, AUTH_WIZARD, TRUE, "%s (lev %d) has self-deleted.", GET_NAME(d->character), GET_LEVEL(d->character));
STATE(d) = CON_CLOSE;
return;
} else {
write_to_output(d, "\r\nCharacter not deleted.\r\n%s", MENU);
STATE(d) = CON_MENU;
}
break;
/*
* It's possible, if enough pulses are missed, to kick someone off
* while they are at the password prompt. We'll just defer to let
* the game_loop() axe them.
*/
case CON_CLOSE:
break;
default:
log("SYSERR: Nanny: illegal state of con'ness (%d) for '%s'; closing connection.",
STATE(d), d->character ? GET_NAME(d->character) : "<unknown>");
STATE(d) = CON_DISCONNECT; /* Safest to do. */
break;
}
}