/* deal with newcomers and other non-playing sockets */
void nanny(struct descriptor_data *d, char *arg)
{
int load_result; /* Overloaded variable */
int player_i;
/* OasisOLC states */
struct {
int state;
void (*func)(struct descriptor_data *, char *);
} olc_functions[] = {
{ CON_OEDIT, oedit_parse },
{ CON_ZEDIT, zedit_parse },
{ CON_SEDIT, sedit_parse },
{ CON_MEDIT, medit_parse },
{ CON_REDIT, redit_parse },
{ CON_CEDIT, cedit_parse },
{ CON_TRIGEDIT, trigedit_parse },
{ CON_AEDIT, aedit_parse },
{ CON_HEDIT, hedit_parse },
{ CON_QEDIT, qedit_parse },
{ CON_PREFEDIT, prefedit_parse },
{ CON_IBTEDIT, ibtedit_parse },
{ CON_MSGEDIT, msgedit_parse },
{ -1, NULL }
};
skip_spaces(&arg);
/* Quick check for the OLC states. */
for (player_i = 0; olc_functions[player_i].state >= 0; player_i++)
if (STATE(d) == olc_functions[player_i].state) {
(*olc_functions[player_i].func)(d, arg);
return;
}
/* Not in OLC. */
switch (STATE(d)) {
case CON_GET_PROTOCOL:
write_to_output(d, "Collecting Protocol Information... Please Wait.\r\n");
return;
break;
case CON_GET_NAME: /* wait for input of name */
if (d->character == NULL) {
CREATE(d->character, struct char_data, 1);
clear_char(d->character);
CREATE(d->character->player_specials, struct player_special_data, 1);
new_mobile_data(d->character);
GET_HOST(d->character) = strdup(d->host);
d->character->desc = d;
}
if (!*arg)
STATE(d) = CON_CLOSE;
else {
char buf[MAX_INPUT_LENGTH], tmp_name[MAX_INPUT_LENGTH];
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, d->character)) > -1) {
GET_PFILEPOS(d->character) = player_i;
if (PLR_FLAGGED(d->character, PLR_DELETED)) {
/* Make sure old files are removed so the new player doesn't get the
* deleted player's equipment. */
if ((player_i = get_ptable_by_name(tmp_name)) >= 0)
remove_player(player_i);
/* 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, struct char_data, 1);
clear_char(d->character);
CREATE(d->character->player_specials, struct player_special_data, 1);
new_mobile_data(d->character);
if (GET_HOST(d->character))
free(GET_HOST(d->character));
GET_HOST(d->character) = strdup(d->host);
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 (\t(Y\t)/\t(N\t))? ", tmp_name);
STATE(d) = CON_NAME_CNFRM;
} else {
/* undo it just in case they are set */
REMOVE_BIT_AR(PLR_FLAGS(d->character), PLR_WRITING);
REMOVE_BIT_AR(PLR_FLAGS(d->character), PLR_MAILING);
REMOVE_BIT_AR(PLR_FLAGS(d->character), PLR_CRYO);
d->character->player.time.logon = time(0);
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 (\t(Y\t)/\t(N\t))? ", 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, LVL_GOD, 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, LVL_GOD, TRUE, "Request for new char %s denied from [%s] (wizlock)", GET_PC_NAME(d->character), d->host);
STATE(d) = CON_CLOSE;
return;
}
perform_new_char_dupe_check(d);
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(d->character->player.name);
d->character->player.name = NULL;
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, LVL_GOD, TRUE, "Bad PW: %s [%s]", GET_NAME(d->character), d->host);
GET_BAD_PWS(d->character)++;
save_char(d->character);
if (++(d->bad_pws) >= CONFIG_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, LVL_GOD, 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, LVL_GOD, 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_LEVEL(d->character) >= LVL_IMMORT)
write_to_output(d, "%s", imotd);
else
write_to_output(d, "%s", motd);
if (GET_INVIS_LEV(d->character))
mudlog(BRF, MAX(LVL_IMMORT, GET_INVIS_LEV(d->character)), TRUE, "%s has connected. (invis %d)", GET_NAME(d->character), GET_INVIS_LEV(d->character));
else
mudlog(BRF, LVL_IMMORT, TRUE, "%s has connected.", GET_NAME(d->character));
/* Add to the list of 'recent' players (since last reboot) */
if (AddRecentPlayer(GET_NAME(d->character), d->host, FALSE, FALSE) == FALSE)
{
mudlog(BRF, MAX(LVL_IMMORT, GET_INVIS_LEV(d->character)), TRUE, "Failure to AddRecentPlayer (returned FALSE).");
}
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 (\t(M\t)/\t(F\t))? ");
STATE(d) = CON_QSEX;
} else {
save_char(d->character);
write_to_output(d, "\r\nDone.\r\n%s", CONFIG_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\nRace: ", race_menu);
STATE(d) = CON_QRACE;
break;
case CON_QRACE:
load_result = parse_race(*arg);
if (load_result == RACE_UNDEFINED) {
write_to_output(d, "\r\nThat's not a race.\r\nRace: ");
return;
} else
GET_RACE(d->character) = load_result;
write_to_output(d, "\r\nSelect a class:\r\n");
int i;
for (i = 0; i < NUM_CLASSES; i++) {
if (classRaceAllowed[(int) GET_RACE(d->character)])
write_to_output(d, "%s", class_menu);
}
write_to_output(d, "\r\nClass: ");
STATE(d) = CON_QCLASS;
break;
case CON_QCLASS:
load_result = parse_class(*arg);
if (load_result == CLASS_UNDEFINED || !classRaceAllowed[GET_RACE(d->character)][load_result]) {
write_to_output(d, "\r\nThat's not a class.\r\nClass: ");
return;
}
GET_CLASS(d->character) = load_result;
write_to_output(d, "\r\nPress enter to roll your stats.");
STATE(d) = CON_QROLLSTATS;
break;
case CON_QROLLSTATS:
switch (*arg) {
case 'y':
case 'Y':
break;
case 'n':
case'N':
default:
roll_real_abils(d->character);
write_to_output(d, "\r\nSTR: [%d/%d] Int: [%d] Wis: [%d] Dex:"
" [%d] Con: [%d] Cha: [%d]",
GET_STR(d->character), GET_ADD(d->character),
GET_INT(d->character), GET_WIS(d->character),
GET_DEX(d->character), GET_CON(d->character),
GET_CHA(d->character));
write_to_output(d, "\r\nKeep these stats? (y/n)");
return;
}
if (d->olc) {
free(d->olc);
d->olc = NULL;
}
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);
save_player_index();
write_to_output(d, "%s\r\n*** PRESS RETURN: ", motd);
STATE(d) = CON_RMOTD;
/* make sure the last log is updated correctly. */
GET_PREF(d->character)= rand_number(1, 128000);
GET_HOST(d->character)= strdup(d->host);
mudlog(NRM, LVL_GOD, TRUE, "%s [%s] new player.", GET_NAME(d->character), d->host);
/* Add to the list of 'recent' players (since last reboot) */
if (AddRecentPlayer(GET_NAME(d->character), d->host, TRUE, FALSE) == FALSE)
{
mudlog(BRF, MAX(LVL_IMMORT, GET_INVIS_LEV(d->character)), TRUE, "Failure to AddRecentPlayer (returned FALSE).");
}
break;
case CON_RMOTD: /* read CR after printing motd */
write_to_output(d, "%s", CONFIG_MENU);
if (IS_HAPPYHOUR > 0){
write_to_output(d, "\r\n");
write_to_output(d, "\tyThere is currently a Happyhour!\tn\r\n");
write_to_output(d, "\r\n");
}
add_llog_entry(d->character, LAST_CONNECT);
STATE(d) = CON_MENU;
break;
case CON_MENU: { /* get selection from main menu */
switch (*arg) {
case '0':
write_to_output(d, "Goodbye.\r\n");
add_llog_entry(d->character, LAST_QUIT);
STATE(d) = CON_CLOSE;
break;
case '1':
load_result = enter_player_game(d);
send_to_char(d->character, "%s", CONFIG_WELC_MESSG);
/* Clear their load room if it's not persistant. */
if (!PLR_FLAGGED(d->character, PLR_LOADROOM))
GET_LOADROOM(d->character) = NOWHERE;
save_char(d->character);
greet_mtrigger(d->character, -1);
greet_memory_mtrigger(d->character);
act("$n has entered the game.", TRUE, d->character, 0, 0, TO_ROOM);
STATE(d) = CON_PLAYING;
MXPSendTag( d, "<VERSION>" );
if (GET_LEVEL(d->character) == 0) {
do_start(d->character);
send_to_char(d->character, "%s", CONFIG_START_MESSG);
do_newbie(d->character); /* newbie eq */
}
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;
/* We've updated to 3.1 - some bits might be set wrongly: */
REMOVE_BIT_AR(PRF_FLAGS(d->character), PRF_BUILDWALK);
break;
case '2':
if (d->character->player.description) {
write_to_output(d, "Current description:\r\n%s", d->character->player.description);
/* Don't free this now... so that the old description gets loaded as the
* current buffer in the editor. Do setup the ABORT buffer here, however. */
d->backstr = strdup(d->character->player.description);
}
write_to_output(d, "Enter the new text you'd like others to see when they look at you.\r\n");
send_editor_help(d);
d->str = &d->character->player.description;
d->max_str = PLR_DESC_LENGTH;
STATE(d) = CON_PLR_DESC;
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", CONFIG_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", CONFIG_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", CONFIG_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 (!strcmp(arg, "yes") || !strcmp(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_LEVEL(d->character) < LVL_GRGOD)
SET_BIT_AR(PLR_FLAGS(d->character), PLR_DELETED);
save_char(d->character);
Crash_delete_file(GET_NAME(d->character));
/* If the selfdelete_fastwipe flag is set (in config.c), remove all the
* player's immediately. */
if (selfdelete_fastwipe)
if ((player_i = get_ptable_by_name(GET_NAME(d->character))) >= 0) {
SET_BIT(player_table[player_i].flags, PINDEX_SELFDELETE);
remove_player(player_i);
}
delete_variables(GET_NAME(d->character));
write_to_output(d, "Character '%s' deleted! Goodbye.\r\n", GET_NAME(d->character));
mudlog(NRM, LVL_GOD, 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", CONFIG_MENU);
STATE(d) = CON_MENU;
}
break;
/* It is possible, if enough pulses are missed, to kick someone off while they
* are at the password prompt. We'll 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;
}
}