diff --git a/README.md b/README.md index ebd2b9d..682532d 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -***Files for Dark Sun MUD.*** +***Files for Miranthas MUD.*** -Dark Sun MUD is a continuation of tbaMUD/CircleMUD, which is built on DIKU MUD. +Miranthas MUD is a continuation of tbaMUD/CircleMUD, which is built on DIKU MUD. The code here is freeware to honor that tradition. Due to the sensitive nature of topics found in this setting, all characters and -players are 18+. The game world is based on the D&D campaign setting Dark Sun -(as close as possible). +players are 18+. The game world is derived from several inspirational sources, +most notably the former Armageddon MUD. Roleplay is highly encouraged, but not enforced. -Features in Dark Sun MUD Alpha release: +Features in Miranthas MUD Alpha release: -* The city of Tyr is available for exploration +* The city of Caleran is available for exploration * Experience points and levels are removed in favor of skill based progression -* Initial skills/spells based partly on tbaMUD code and Dark Sun 5e conversion +* Initial skills/spells based partly on tbaMUD code and 5e conversion (to be cleaned up in later release) * Expanded emoting system for roleplay * Permanent character death - aka. hardcore mode * A hybrid "5e-like" system where: @@ -42,11 +42,13 @@ Features in Dark Sun MUD Alpha release: * NPC's can now be assigned a class like a PC and inherit the relevant skills * PC's now use a short description for identification instead of name * Backgrounds are now available for PC's and NPC's + * Account system for tracking players/characters over long periods of time Features to be implemented in the next few releases: -* Race selection based on Dark Sun -* The Merchant Calendar and moon cycles +* Race/species selection +* Subclass selection +* New calendar and moon cycles * Heat based on time of day increases/decreases, changing hunger/thirst levels * Sandstorms * Shaded rooms @@ -55,16 +57,17 @@ Features to be implemented in the next few releases: * Basic crafting system * Continued skill and spell improvements * Apartment rentals for storing your loot -* Account system for tracking players/characters over long periods of time -* Quest system to increase or decrease notoriety -* Additional zones/cities based on Dark Sun world map -* Resources on the world map can be claimed by different city-states +* Enhanced quest system +* Dialogue trees with NPC's +* Additional zones/cities based on Miranthas world map +* Resources on the world map can be claimed by different city-states or independent factions * Claimed resources improve quality of armor/weapons/food/prices available ...and down the road: -* Change to SQL database on the backend -* Python abstraction layer for modern scripting support +* Replace ASCII files in favor of SQL database on the backend +* Replace DG Scripts with a Python abstraction layer for modern scripting support +* Replace Oasis OLC with more modern interface for easing builder duties * Discord server integration for ticketing and community * Full documentation for admins and easy to follow improvement guides -* ...something else I haven't thought of +* ...suggestions from anyone who uses this game diff --git a/lib/acctfiles/A-E/00 b/lib/acctfiles/A-E/00 new file mode 100644 index 0000000..0805cef --- /dev/null +++ b/lib/acctfiles/A-E/00 @@ -0,0 +1 @@ +This is a placeholder file so the directory will be created diff --git a/lib/acctfiles/F-J/00 b/lib/acctfiles/F-J/00 new file mode 100644 index 0000000..0805cef --- /dev/null +++ b/lib/acctfiles/F-J/00 @@ -0,0 +1 @@ +This is a placeholder file so the directory will be created diff --git a/lib/acctfiles/K-O/00 b/lib/acctfiles/K-O/00 new file mode 100644 index 0000000..0805cef --- /dev/null +++ b/lib/acctfiles/K-O/00 @@ -0,0 +1 @@ +This is a placeholder file so the directory will be created diff --git a/lib/acctfiles/P-T/00 b/lib/acctfiles/P-T/00 new file mode 100644 index 0000000..0805cef --- /dev/null +++ b/lib/acctfiles/P-T/00 @@ -0,0 +1 @@ +This is a placeholder file so the directory will be created diff --git a/lib/acctfiles/U-Z/00 b/lib/acctfiles/U-Z/00 new file mode 100644 index 0000000..0805cef --- /dev/null +++ b/lib/acctfiles/U-Z/00 @@ -0,0 +1 @@ +This is a placeholder file so the directory will be created diff --git a/lib/acctfiles/ZZZ/00 b/lib/acctfiles/ZZZ/00 new file mode 100644 index 0000000..0805cef --- /dev/null +++ b/lib/acctfiles/ZZZ/00 @@ -0,0 +1 @@ +This is a placeholder file so the directory will be created diff --git a/src/accounts.c b/src/accounts.c new file mode 100644 index 0000000..a3a48f1 --- /dev/null +++ b/src/accounts.c @@ -0,0 +1,175 @@ +#include "conf.h" +#include "sysdep.h" + +#include "structs.h" +#include "utils.h" +#include "db.h" +#include "accounts.h" + +static void set_account_name(struct account_data *account, const char *name) +{ + char tmp[MAX_INPUT_LENGTH]; + + if (!account || !name || !*name) + return; + + strlcpy(tmp, name, sizeof(tmp)); + CAP(tmp); + + if (account->name) + free(account->name); + account->name = strdup(tmp); +} + +struct account_data *account_create(const char *name) +{ + struct account_data *account; + + CREATE(account, struct account_data, 1); + set_account_name(account, name); + return account; +} + +struct account_data *account_load(const char *name) +{ + struct account_data *account; + FILE *fl; + char filename[PATH_MAX]; + char line[MAX_INPUT_LENGTH + 1]; + char tag[6]; + + if (!name || !*name) + return NULL; + + if (!get_filename(filename, sizeof(filename), ACCT_FILE, name)) + return NULL; + + fl = fopen(filename, "r"); + if (!fl) + return NULL; + + account = account_create(NULL); + + while (get_line(fl, line)) { + tag_argument(line, tag); + + if (!strcmp(tag, "Name")) + set_account_name(account, line); + else if (!strcmp(tag, "Pass")) + strlcpy(account->passwd, line, sizeof(account->passwd)); + else if (!strcmp(tag, "Mail")) { + if (account->email) + free(account->email); + account->email = strdup(line); + } else if (!strcmp(tag, "Char")) { + if (account->pc_name) + free(account->pc_name); + account->pc_name = strdup(line); + } + } + + fclose(fl); + + if (!account->name) + set_account_name(account, name); + + return account; +} + +int account_save(const struct account_data *account) +{ + FILE *fl; + char filename[PATH_MAX]; + + if (!account || !account->name || !*account->name) + return 0; + + if (!get_filename(filename, sizeof(filename), ACCT_FILE, account->name)) + return 0; + + if (!(fl = fopen(filename, "w"))) { + log("SYSERR: Couldn't open account file %s for write.", filename); + return 0; + } + + fprintf(fl, "Name: %s\n", account->name); + fprintf(fl, "Pass: %s\n", account->passwd); + if (account->email && *account->email) + fprintf(fl, "Mail: %s\n", account->email); + if (account->pc_name && *account->pc_name) + fprintf(fl, "Char: %s\n", account->pc_name); + + fclose(fl); + return 1; +} + +void account_free(struct account_data *account) +{ + if (!account) + return; + + if (account->name) + free(account->name); + if (account->email) + free(account->email); + if (account->pc_name) + free(account->pc_name); + + free(account); +} + +int account_has_alive_pc(const struct account_data *account) +{ + int pfilepos; + + if (!account || !account->pc_name || !*account->pc_name) + return 0; + + pfilepos = get_ptable_by_name(account->pc_name); + if (pfilepos < 0) + return 0; + if (IS_SET(player_table[pfilepos].flags, PINDEX_DELETED)) + return 0; + + return 1; +} + +void account_set_pc(struct account_data *account, const char *pc_name) +{ + char tmp[MAX_INPUT_LENGTH]; + + if (!account) + return; + + if (account->pc_name) + free(account->pc_name); + account->pc_name = NULL; + + if (pc_name && *pc_name) { + strlcpy(tmp, pc_name, sizeof(tmp)); + CAP(tmp); + account->pc_name = strdup(tmp); + } +} + +void account_clear_pc(struct account_data *account) +{ + if (!account) + return; + + if (account->pc_name) + free(account->pc_name); + account->pc_name = NULL; +} + +void account_refresh_pc(struct account_data *account) +{ + if (!account || !account->pc_name || !*account->pc_name) + return; + + if (account_has_alive_pc(account)) + return; + + account_clear_pc(account); + account_save(account); +} diff --git a/src/accounts.h b/src/accounts.h new file mode 100644 index 0000000..004e229 --- /dev/null +++ b/src/accounts.h @@ -0,0 +1,15 @@ +#ifndef _ACCOUNTS_H_ +#define _ACCOUNTS_H_ + +struct account_data; + +struct account_data *account_load(const char *name); +struct account_data *account_create(const char *name); +int account_save(const struct account_data *account); +void account_free(struct account_data *account); +int account_has_alive_pc(const struct account_data *account); +void account_set_pc(struct account_data *account, const char *pc_name); +void account_clear_pc(struct account_data *account); +void account_refresh_pc(struct account_data *account); + +#endif diff --git a/src/comm.c b/src/comm.c index bfb3266..95a9558 100644 --- a/src/comm.c +++ b/src/comm.c @@ -65,6 +65,7 @@ #include "interpreter.h" #include "handler.h" #include "db.h" +#include "accounts.h" #include "house.h" #include "oasis.h" #include "genolc.h" @@ -1451,6 +1452,7 @@ static void init_descriptor (struct descriptor_data *newd, int desc) newd->has_prompt = 1; /* prompt is part of greetings */ STATE(newd) = CONFIG_PROTOCOL_NEGOTIATION ? CON_GET_PROTOCOL : CON_GET_CONNECT; CREATE(newd->history, char *, HISTORY_SIZE); + newd->account = NULL; if (++last_desc == 1000) last_desc = 1; newd->desc_num = last_desc; @@ -2126,6 +2128,9 @@ void close_socket(struct descriptor_data *d) break; } + account_free(d->account); + d->account = NULL; + free(d); } @@ -2135,7 +2140,10 @@ static void check_idle_passwords(void) for (d = descriptor_list; d; d = next_d) { next_d = d->next; - if (STATE(d) != CON_PASSWORD && STATE(d) != CON_GET_NAME && STATE(d) != CON_GET_CONNECT) + if (STATE(d) != CON_PASSWORD && STATE(d) != CON_GET_NAME && + STATE(d) != CON_GET_CONNECT && STATE(d) != CON_GET_ACCOUNT && + STATE(d) != CON_ACCOUNT_PASSWORD && STATE(d) != CON_ACCOUNT_NEWPASSWD && + STATE(d) != CON_ACCOUNT_CNFPASSWD) continue; if (!d->idle_tics) { d->idle_tics++; diff --git a/src/db.c b/src/db.c index 15f681f..7cf3153 100644 --- a/src/db.c +++ b/src/db.c @@ -3527,6 +3527,8 @@ void free_char(struct char_data *ch) free(ch->player_specials->poofin); if (ch->player_specials->poofout) free(ch->player_specials->poofout); + if (ch->player_specials->account_name) + free(ch->player_specials->account_name); if (ch->player_specials->saved.completed_quests) free(ch->player_specials->saved.completed_quests); if (GET_HOST(ch)) diff --git a/src/db.h b/src/db.h index b8e4add..29edc95 100644 --- a/src/db.h +++ b/src/db.h @@ -34,6 +34,7 @@ #define LIB_PLROBJS ":plrobjs:" #define LIB_PLRVARS ":plrvars:" #define LIB_PLRFILES ":plrfiles:" +#define LIB_ACCTFILES ":acctfiles:" #define LIB_HOUSE ":house:" #define SLASH ":" #elif defined(CIRCLE_AMIGA) || defined(CIRCLE_UNIX) || defined(CIRCLE_WINDOWS) || defined(CIRCLE_ACORN) || defined(CIRCLE_VMS) @@ -47,6 +48,7 @@ #define LIB_PLRVARS "plrvars/" #define LIB_HOUSE "house/" #define LIB_PLRFILES "plrfiles/" +#define LIB_ACCTFILES "acctfiles/" #define SLASH "/" #else #error "Unknown path components." @@ -56,6 +58,7 @@ #define SUF_TEXT "text" #define SUF_MEM "mem" #define SUF_PLR "plr" +#define SUF_ACCT "acc" #if defined(CIRCLE_AMIGA) #define EXE_FILE "/bin/circle" /* maybe use argv[0] but it's not reliable */ diff --git a/src/handler.c b/src/handler.c index 5e29f6d..d792a79 100644 --- a/src/handler.c +++ b/src/handler.c @@ -992,8 +992,8 @@ void extract_char_final(struct char_data *ch) if (GET_POS(ch) == POS_DEAD) { STATE(ch->desc) = CON_CLOSE; } else { - STATE(ch->desc) = CON_MENU; - write_to_output(ch->desc, "%s", CONFIG_MENU); + STATE(ch->desc) = CON_ACCOUNT_MENU; + send_account_menu(ch->desc); } } } diff --git a/src/interpreter.c b/src/interpreter.c index 9719db9..c128b70 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -37,6 +37,7 @@ #include "ibt.h" #include "mud_event.h" #include "modify.h" /* to ensure page_string is available */ +#include "accounts.h" /* local (file scope) functions */ static int perform_dupe_check(struct descriptor_data *d); @@ -1035,6 +1036,25 @@ static int _parse_name(char *arg, char *name) return (0); } +void send_account_menu(struct descriptor_data *d) +{ + int has_pc; + + if (!d || !d->account) + return; + + account_refresh_pc(d->account); + has_pc = account_has_alive_pc(d->account); + + write_to_output(d, "\r\nAccount: %s\r\n", d->account->name); + if (has_pc) + write_to_output(d, "\t(1\t)) Connect to %s.\r\n", d->account->pc_name); + else + write_to_output(d, "\t(1\t)) Create a new PC.\r\n"); + write_to_output(d, "\t(0\t)) Exit from tbaMUD.\r\n\r\n" + " Make your choice: "); +} + #define RECON 1 #define USURP 2 #define UNSWITCH 3 @@ -1246,6 +1266,9 @@ int enter_player_game (struct descriptor_data *d) if (!d || !d->character) return 0; + if (!d->account && GET_ACCOUNT(d->character) && *GET_ACCOUNT(d->character)) + d->account = account_load(GET_ACCOUNT(d->character)); + /* Cache the pfile's saved load room FIRST. */ saved_vnum = GET_LOADROOM(d->character); @@ -1432,8 +1455,8 @@ void nanny(struct descriptor_data *d, char *arg) } switch (UPPER(*arg)) { case 'C': - write_to_output(d, "By what name do you wish to be known? "); - STATE(d) = CON_GET_NAME; + write_to_output(d, "Account name: "); + STATE(d) = CON_GET_ACCOUNT; return; case 'X': write_to_output(d, "Goodbye.\r\n"); @@ -1445,14 +1468,61 @@ void nanny(struct descriptor_data *d, char *arg) write_to_output(d, GREETINGS, 0); return; } + case CON_GET_ACCOUNT: + if (!*arg) { + STATE(d) = CON_CLOSE; + break; + } 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 account name, please try another.\r\nAccount name: "); + return; + } + + if (d->account) { + account_free(d->account); + d->account = NULL; + } + + d->account = account_load(tmp_name); + if (d->account) { + write_to_output(d, "Password: "); + echo_off(d); + d->idle_tics = 0; + STATE(d) = CON_ACCOUNT_PASSWORD; + return; + } + + if ((player_i = get_ptable_by_name(tmp_name)) >= 0 && + !IS_SET(player_table[player_i].flags, PINDEX_DELETED)) { + d->account = account_create(tmp_name); + account_set_pc(d->account, player_table[player_i].name); + write_to_output(d, "An existing character was found. Create an account to link it (\t(Y\t)/\t(N\t))? "); + STATE(d) = CON_ACCOUNT_CNFRM; + return; + } + + d->account = account_create(tmp_name); + write_to_output(d, "Create a new account %s (\t(Y\t)/\t(N\t))? ", tmp_name); + STATE(d) = CON_ACCOUNT_CNFRM; + return; + } + case CON_GET_NAME: /* wait for input of name */ + if (!d->account) { + STATE(d) = CON_CLOSE; + return; + } 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; } @@ -1462,72 +1532,125 @@ void nanny(struct descriptor_data *d, char *arg) 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; + 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; + if ((player_i = get_ptable_by_name(tmp_name)) >= 0) { + if (IS_SET(player_table[player_i].flags, PINDEX_DELETED)) { + remove_player(player_i); } 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\nBy what name do you wish to be known? "); + write_to_output(d, "That name is already taken.\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; } + + if (GET_NAME(d->character)) + free(GET_NAME(d->character)); + 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_ACCOUNT_CNFRM: + if (UPPER(*arg) == 'Y') { + if (isbanned(d->host) >= BAN_NEW) { + mudlog(NRM, LVL_GOD, TRUE, "Request for new account %s denied from [%s] (siteban)", + d->account ? d->account->name : "", d->host); + write_to_output(d, "Sorry, new accounts are not allowed from your site!\r\n"); + STATE(d) = CON_CLOSE; + return; + } + if (circle_restrict) { + write_to_output(d, "Sorry, new accounts can't be created at the moment.\r\n"); + mudlog(NRM, LVL_GOD, TRUE, "Request for new account %s denied from [%s] (wizlock)", + d->account ? d->account->name : "", d->host); + STATE(d) = CON_CLOSE; + return; + } + write_to_output(d, "New account.\r\nGive me a password: "); + echo_off(d); + STATE(d) = CON_ACCOUNT_NEWPASSWD; + } else if (*arg == 'n' || *arg == 'N') { + account_free(d->account); + d->account = NULL; + write_to_output(d, "Okay, what IS it, then?\r\nAccount name: "); + STATE(d) = CON_GET_ACCOUNT; + } else + write_to_output(d, "Please type Yes or No: "); + break; + + case CON_ACCOUNT_PASSWORD: + echo_on(d); + write_to_output(d, "\r\n"); + + if (!*arg) + STATE(d) = CON_CLOSE; + else if (!d->account) { + STATE(d) = CON_CLOSE; + } else { + if (strncmp(CRYPT(arg, d->account->passwd), d->account->passwd, MAX_PWD_LENGTH)) { + mudlog(BRF, LVL_GOD, TRUE, "Bad account PW: %s [%s]", d->account->name, d->host); + if (++(d->bad_pws) >= CONFIG_MAX_BAD_PWS) { + 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; + } + + d->bad_pws = 0; + send_account_menu(d); + STATE(d) = CON_ACCOUNT_MENU; + } + break; + + case CON_ACCOUNT_NEWPASSWD: + if (!*arg || strlen(arg) > MAX_PWD_LENGTH || strlen(arg) < 3 || + !str_cmp(arg, d->account ? d->account->name : "")) { + write_to_output(d, "\r\nIllegal password.\r\nPassword: "); + return; + } + strncpy(d->account->passwd, CRYPT(arg, d->account->name), MAX_PWD_LENGTH); + d->account->passwd[MAX_PWD_LENGTH] = '\0'; + + write_to_output(d, "\r\nPlease retype password: "); + STATE(d) = CON_ACCOUNT_CNFPASSWD; + break; + + case CON_ACCOUNT_CNFPASSWD: + if (strncmp(CRYPT(arg, d->account->passwd), d->account->passwd, MAX_PWD_LENGTH)) { + write_to_output(d, "\r\nPasswords don't match... start over.\r\nPassword: "); + STATE(d) = CON_ACCOUNT_NEWPASSWD; + return; + } + echo_on(d); + + write_to_output(d, "\r\nEmail address (optional, press Enter to skip): "); + STATE(d) = CON_ACCOUNT_EMAIL; + break; + + case CON_ACCOUNT_EMAIL: + if (d->account) { + if (d->account->email) + free(d->account->email); + d->account->email = NULL; + if (*arg) + d->account->email = strdup(arg); + account_save(d->account); + } + write_to_output(d, "\r\nAccount created.\r\n"); + send_account_menu(d); + STATE(d) = CON_ACCOUNT_MENU; + break; + case CON_NAME_CNFRM: /* wait for conf. of new name */ if (UPPER(*arg) == 'Y') { if (isbanned(d->host) >= BAN_NEW) { @@ -1543,11 +1666,13 @@ void nanny(struct descriptor_data *d, char *arg) 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; + if (GET_ACCOUNT(d->character)) + free(GET_ACCOUNT(d->character)); + GET_ACCOUNT(d->character) = d->account ? strdup(d->account->name) : NULL; + write_to_output(d, "New character.\r\nWhat is your sex (\t(M\t)/\t(F\t))? "); + STATE(d) = CON_QSEX; } else if (*arg == 'n' || *arg == 'N') { - write_to_output(d, "Okay, what IS it, then?\r\nBy what name do you wish to be known? "); + write_to_output(d, "Okay, what IS it, then?\r\nName: "); free(d->character->player.name); d->character->player.name = NULL; STATE(d) = CON_GET_NAME; @@ -1671,8 +1796,9 @@ void nanny(struct descriptor_data *d, char *arg) STATE(d) = CON_QSEX; } else { save_char(d->character); - write_to_output(d, "\r\nDone.\r\n%s", CONFIG_MENU); - STATE(d) = CON_MENU; + write_to_output(d, "\r\nDone.\r\n"); + send_account_menu(d); + STATE(d) = CON_ACCOUNT_MENU; } break; @@ -1715,8 +1841,17 @@ case CON_QCLASS: /* Initialize base stats, starting level, etc. */ init_char(d->character); + if (d->account && d->account->name) { + if (GET_ACCOUNT(d->character)) + free(GET_ACCOUNT(d->character)); + GET_ACCOUNT(d->character) = strdup(d->account->name); + } save_char(d->character); save_player_index(); + if (d->account) { + account_set_pc(d->account, GET_NAME(d->character)); + account_save(d->account); + } /* Log and register early so new players are tracked immediately */ GET_PREF(d->character) = rand_number(1, 128000); @@ -1807,13 +1942,13 @@ case CON_QCLASS: write_to_output(d, "\r\nBefore stepping into Miranthas, share a bit of your character's background.\r\n" "Guideline: aim for at least four lines that hint at where they came from,\r\n" - "who shaped them, and why they now walk the Tyr region. Touch on things like:\r\n" + "who shaped them, and why they now walk the Caleran region. Touch on things like:\r\n" " - The city-state, tribe, or caravan that claimed them.\r\n" " - Mentors, slavers, or patrons who left a mark.\r\n" " - A defining hardship, triumph, oath, or secret.\r\n" " - The goal, vengeance, or hope that drives them back into the wastes.\r\n" "\r\nExample 1:\r\n" - " Raised beneath the ziggurat of Tyr, I learned to barter gossip between\r\n" + " Raised beneath the ziggurat of Caleran, I learned to barter gossip between\r\n" " templars and gladiators just to stay alive. Freedom came when Kalak fell,\r\n" " but the slave-scar on my shoulder still aches. I now search the desert\r\n" " for the relic my clutch mates died protecting, hoping to buy their kin peace.\r\n" @@ -1825,7 +1960,7 @@ case CON_QCLASS: "\r\nExample 3:\r\n" " Born outside Raam, I was tempered by obsidian shards and psionic murmurs.\r\n" " A defiler ruined our oasis, so I swore to hound such spell-scars wherever\r\n" - " they bloom. Rumor of a hidden well near Tyr is the lone hope that guides me.\r\n" + " they bloom. Rumor of a hidden well near Caleran is the lone hope that guides me.\r\n" "\r\nType your background now. Use '/s' on a blank line when you finish.\r\n" "If you'd rather keep it secret, just save immediately and we'll note the mystery.\r\n\r\n"); d->backstr = NULL; @@ -1847,78 +1982,173 @@ case CON_QCLASS: break; case CON_RMOTD: /* read CR after printing motd */ - write_to_output(d, "%s", CONFIG_MENU); + if (!d->character) { + send_account_menu(d); + STATE(d) = CON_ACCOUNT_MENU; + break; + } + if (!d->account && GET_ACCOUNT(d->character) && *GET_ACCOUNT(d->character)) + d->account = account_load(GET_ACCOUNT(d->character)); add_llog_entry(d->character, LAST_CONNECT); - STATE(d) = CON_MENU; + + load_result = enter_player_game(d); + send_to_char(d->character, "%s", CONFIG_WELC_MESSG); + + 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, ""); + + if (GET_LEVEL(d->character) == 0) { + do_start(d->character); + send_to_char(d->character, "%s", CONFIG_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"); + + 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 CON_MENU: { /* get selection from main menu */ + case CON_ACCOUNT_MENU: { + int has_pc; + + if (!d->account) { + STATE(d) = CON_CLOSE; + break; + } + + account_refresh_pc(d->account); + has_pc = account_has_alive_pc(d->account); switch (*arg) { case '0': write_to_output(d, "Goodbye.\r\n"); - add_llog_entry(d->character, LAST_QUIT); + if (d->character) + add_llog_entry(d->character, LAST_QUIT); STATE(d) = CON_CLOSE; break; - case '1': - /* Proceed into the world */ - load_result = enter_player_game(d); - send_to_char(d->character, "%s", CONFIG_WELC_MESSG); + if (has_pc) { + if (d->character) { + free_char(d->character); + 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); - save_char(d->character); + GET_HOST(d->character) = strdup(d->host); + d->character->desc = d; - greet_mtrigger(d->character, -1); - greet_memory_mtrigger(d->character); + if ((player_i = load_char(d->account->pc_name, d->character)) < 0) { + write_to_output(d, "Could not load that character.\r\n"); + send_account_menu(d); + STATE(d) = CON_ACCOUNT_MENU; + break; + } + GET_PFILEPOS(d->character) = player_i; - act("$n has entered the game.", TRUE, d->character, 0, 0, TO_ROOM); + if (GET_ACCOUNT(d->character) && str_cmp(GET_ACCOUNT(d->character), d->account->name)) { + write_to_output(d, "That character does not belong to this account.\r\n"); + free_char(d->character); + d->character = NULL; + send_account_menu(d); + STATE(d) = CON_ACCOUNT_MENU; + break; + } + if (!GET_ACCOUNT(d->character) && d->account->name) { + GET_ACCOUNT(d->character) = strdup(d->account->name); + save_char(d->character); + } - STATE(d) = CON_PLAYING; - MXPSendTag(d, ""); + /* 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); - if (GET_LEVEL(d->character) == 0) { - do_start(d->character); - send_to_char(d->character, "%s", CONFIG_START_MESSG); + 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; + } + + 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)); + + 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)."); + } + + write_to_output(d, "\r\n*** PRESS RETURN: "); + STATE(d) = CON_RMOTD; + } else { + if (d->character) { + free_char(d->character); + d->character = NULL; + } + write_to_output(d, "By what name do you wish to be known? "); + STATE(d) = CON_GET_NAME; } - - look_at_room(d->character, 0); - if (has_mail(GET_IDNUM(d->character))) - send_to_char(d->character, "You have mail waiting.\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': - page_string(d, background, 0); - STATE(d) = CON_RMOTD; - break; - - case '3': - write_to_output(d, "\r\nEnter your old password: "); - echo_off(d); - STATE(d) = CON_CHPWD_GETOLD; - break; - - case '4': - 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); + write_to_output(d, "\r\nThat's not a menu choice!\r\n"); + send_account_menu(d); break; } break; } + case CON_MENU: { /* get selection from main menu */ + if (!d->account) { + write_to_output(d, "No account loaded.\r\n"); + STATE(d) = CON_CLOSE; + break; + } + send_account_menu(d); + STATE(d) = CON_ACCOUNT_MENU; + 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; + write_to_output(d, "\r\nIncorrect password.\r\n"); + send_account_menu(d); + STATE(d) = CON_ACCOUNT_MENU; } else { write_to_output(d, "\r\nEnter a new password: "); STATE(d) = CON_CHPWD_GETNEW; @@ -1928,8 +2158,9 @@ case CON_QCLASS: 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; + write_to_output(d, "\r\nIncorrect password.\r\n"); + send_account_menu(d); + STATE(d) = CON_ACCOUNT_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" @@ -1965,8 +2196,9 @@ case CON_QCLASS: STATE(d) = CON_CLOSE; return; } else { - write_to_output(d, "\r\nCharacter not deleted.\r\n%s", CONFIG_MENU); - STATE(d) = CON_MENU; + write_to_output(d, "\r\nCharacter not deleted.\r\n"); + send_account_menu(d); + STATE(d) = CON_ACCOUNT_MENU; } break; diff --git a/src/interpreter.h b/src/interpreter.h index 0ad8534..14a674b 100644 --- a/src/interpreter.h +++ b/src/interpreter.h @@ -31,6 +31,7 @@ int fill_word(char *argument); int reserved_word(char *argument); void half_chop(char *string, char *arg1, char *arg2); void nanny(struct descriptor_data *d, char *arg); +void send_account_menu(struct descriptor_data *d); int is_abbrev(const char *arg1, const char *arg2); int is_number(const char *str); int find_command(const char *command); diff --git a/src/modify.c b/src/modify.c index aeb0887..c8526e7 100644 --- a/src/modify.c +++ b/src/modify.c @@ -312,13 +312,13 @@ static void exdesc_string_cleanup(struct descriptor_data *d, int action) write_to_output(d, "\r\nBefore stepping into Miranthas, share a bit of your character's background.\r\n" "Guideline: aim for at least four lines that hint at where they came from,\r\n" - "who shaped them, and why they now walk the Tyr region. Touch on things like:\r\n" + "who shaped them, and why they now walk the Caleran region. Touch on things like:\r\n" " - The city-state, tribe, or caravan that claimed them.\r\n" " - Mentors, slavers, or patrons who left a mark.\r\n" " - A defining hardship, triumph, oath, or secret.\r\n" " - The goal, vengeance, or hope that drives them back into the wastes.\r\n" "\r\nExample 1:\r\n" - " Raised beneath the ziggurat of Tyr, I learned to barter gossip between\r\n" + " Raised beneath the ziggurat of Caleran, I learned to barter gossip between\r\n" " templars and gladiators just to stay alive. Freedom came when Kalak fell,\r\n" " but the slave-scar on my shoulder still aches. I now search the desert\r\n" " for the relic my clutch mates died protecting, hoping to buy their kin peace.\r\n" @@ -330,7 +330,7 @@ static void exdesc_string_cleanup(struct descriptor_data *d, int action) "\r\nExample 3:\r\n" " Born outside Raam, I was tempered by obsidian shards and psionic murmurs.\r\n" " A defiler ruined our oasis, so I swore to hound such spell-scars wherever\r\n" - " they bloom. Rumor of a hidden well near Tyr is the lone hope that guides me.\r\n" + " they bloom. Rumor of a hidden well near Caleran is the lone hope that guides me.\r\n" "\r\nType your background now. Use '/s' on a blank line when you finish.\r\n" "If you'd rather keep it secret, just save immediately and we'll note the mystery.\r\n\r\n"); d->backstr = NULL; @@ -343,8 +343,8 @@ static void exdesc_string_cleanup(struct descriptor_data *d, int action) return; } - write_to_output(d, "%s", CONFIG_MENU); - STATE(d) = CON_MENU; + send_account_menu(d); + STATE(d) = CON_ACCOUNT_MENU; } static void background_string_cleanup(struct descriptor_data *d, int action) @@ -369,8 +369,8 @@ static void background_string_cleanup(struct descriptor_data *d, int action) return; } - write_to_output(d, "%s", CONFIG_MENU); - STATE(d) = CON_MENU; + send_account_menu(d); + STATE(d) = CON_ACCOUNT_MENU; } /* By Michael Buselli. Traverse down the string until the begining of the next diff --git a/src/players.c b/src/players.c index 6b08ecc..ce06c33 100644 --- a/src/players.c +++ b/src/players.c @@ -297,6 +297,10 @@ int load_char(const char *name, struct char_data *ch) GET_NUM_QUESTS(ch) = PFDEF_COMPQUESTS; GET_LAST_MOTD(ch) = PFDEF_LASTMOTD; GET_LAST_NEWS(ch) = PFDEF_LASTNEWS; + if (GET_ACCOUNT(ch)) { + free(GET_ACCOUNT(ch)); + GET_ACCOUNT(ch) = NULL; + } for (i = 0; i < AF_ARRAY_MAX; i++) AFF_FLAGS(ch)[i] = PFDEF_AFFFLAGS; @@ -311,6 +315,11 @@ int load_char(const char *name, struct char_data *ch) switch (*tag) { case 'A': if (!strcmp(tag, "Ac ")) GET_AC(ch) = atoi(line); + else if (!strcmp(tag, "Acct")) { + if (GET_ACCOUNT(ch)) + free(GET_ACCOUNT(ch)); + GET_ACCOUNT(ch) = strdup(line); + } else if (!strcmp(tag, "Act ")) { if (sscanf(line, "%s %s %s %s", f1, f2, f3, f4) == 4) { PLR_FLAGS(ch)[0] = asciiflag_conv(f1); @@ -590,6 +599,7 @@ void save_char(struct char_data * ch) if (GET_NAME(ch)) fprintf(fl, "Name: %s\n", GET_NAME(ch)); if (GET_SHORT_DESC(ch) && *GET_SHORT_DESC(ch)) fprintf(fl, "Sdsc: %s\n", GET_SHORT_DESC(ch)); if (GET_PASSWD(ch)) fprintf(fl, "Pass: %s\n", GET_PASSWD(ch)); + if (GET_ACCOUNT(ch) && *GET_ACCOUNT(ch)) fprintf(fl, "Acct: %s\n", GET_ACCOUNT(ch)); if (ch->player.description && *ch->player.description) { strcpy(buf, ch->player.description); strip_cr(buf); diff --git a/src/structs.h b/src/structs.h index 085e25c..55c681b 100644 --- a/src/structs.h +++ b/src/structs.h @@ -341,6 +341,13 @@ #define CON_PLR_BACKGROUND 33 /**< Entering a new character background */ #define CON_GET_PROTOCOL 33 /**< Used at log-in while attempting to get protocols > */ #define CON_GET_CONNECT 34 /**< Login connect/disconnect menu */ +#define CON_GET_ACCOUNT 35 /**< Login with account name */ +#define CON_ACCOUNT_CNFRM 36 /**< New account, confirm name */ +#define CON_ACCOUNT_PASSWORD 37 /**< Login with account password */ +#define CON_ACCOUNT_NEWPASSWD 38 /**< New account, create password */ +#define CON_ACCOUNT_CNFPASSWD 39 /**< New account, confirm password */ +#define CON_ACCOUNT_EMAIL 40 /**< New account, optional email */ +#define CON_ACCOUNT_MENU 41 /**< Account main menu */ /* OLC States range - used by IS_IN_OLC and IS_PLAYING */ #define FIRST_OLC_STATE CON_OEDIT /**< The first CON_ state that is an OLC */ @@ -1022,10 +1029,20 @@ struct player_special_data void *last_olc_targ; /**< ? Currently Unused ? */ int last_olc_mode; /**< ? Currently Unused ? */ char *host; /**< Resolved hostname, or ip, for player. */ + char *account_name; /**< Account name owning this PC. */ int buildwalk_sector; /**< Default sector type for buildwalk */ struct scan_result_data *scan_results; /**< Hidden figures this player has spotted */ }; +/** Account data stored separately from character data. */ +struct account_data +{ + char *name; /**< Account username */ + char passwd[MAX_PWD_LENGTH+1]; /**< Account password (hashed) */ + char *email; /**< Optional email address */ + char *pc_name; /**< Attached PC name, if any */ +}; + /** Special data used by NPCs, not PCs */ struct mob_special_data { @@ -1158,6 +1175,7 @@ struct descriptor_data int bufspace; /**< space left in the output buffer */ struct txt_block *large_outbuf; /**< ptr to large buffer, if we need it */ struct txt_q input; /**< q of unprocessed input */ + struct account_data *account; /**< Active account session data */ struct char_data *character; /**< linked to char */ struct char_data *original; /**< original char if switched */ struct descriptor_data *snooping; /**< Who is this char snooping */ diff --git a/src/utils.c b/src/utils.c index a9928a4..1751511 100644 --- a/src/utils.c +++ b/src/utils.c @@ -787,6 +787,10 @@ int get_filename(char *filename, size_t fbufsize, int mode, const char *orig_nam prefix = LIB_PLRFILES; suffix = SUF_PLR; break; + case ACCT_FILE: + prefix = LIB_ACCTFILES; + suffix = SUF_ACCT; + break; default: return (0); } diff --git a/src/utils.h b/src/utils.h index e58617e..eb91d2d 100644 --- a/src/utils.h +++ b/src/utils.h @@ -211,8 +211,9 @@ void char_from_furniture(struct char_data *ch); #define ETEXT_FILE 1 /**< ???? */ #define SCRIPT_VARS_FILE 2 /**< Reference to a global variable file. */ #define PLR_FILE 3 /**< The standard player file */ +#define ACCT_FILE 4 /**< The standard account file */ -#define MAX_FILES 4 /**< Max number of files types vailable */ +#define MAX_FILES 4 /**< Max number of player-owned file types available */ /* breadth-first searching for graph function (tracking, etc) */ #define BFS_ERROR (-1) /**< Error in the search. */ @@ -543,6 +544,7 @@ do \ #define GET_LEVEL(ch) ((ch)->player.level) /** Password of PC. */ #define GET_PASSWD(ch) ((ch)->player.passwd) +#define GET_ACCOUNT(ch) CHECK_PLAYER_SPECIAL((ch), ((ch)->player_specials->account_name)) /** The player file position of PC. */ #define GET_PFILEPOS(ch)((ch)->pfilepos)