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