Accounts update 1

This commit is contained in:
kinther 2025-12-26 09:58:06 -08:00
parent c3db46da13
commit 76513050cb
20 changed files with 626 additions and 147 deletions

View file

@ -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

1
lib/acctfiles/A-E/00 Normal file
View file

@ -0,0 +1 @@
This is a placeholder file so the directory will be created

1
lib/acctfiles/F-J/00 Normal file
View file

@ -0,0 +1 @@
This is a placeholder file so the directory will be created

1
lib/acctfiles/K-O/00 Normal file
View file

@ -0,0 +1 @@
This is a placeholder file so the directory will be created

1
lib/acctfiles/P-T/00 Normal file
View file

@ -0,0 +1 @@
This is a placeholder file so the directory will be created

1
lib/acctfiles/U-Z/00 Normal file
View file

@ -0,0 +1 @@
This is a placeholder file so the directory will be created

1
lib/acctfiles/ZZZ/00 Normal file
View file

@ -0,0 +1 @@
This is a placeholder file so the directory will be created

175
src/accounts.c Normal file
View file

@ -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);
}

15
src/accounts.h Normal file
View file

@ -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

View file

@ -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++;

View file

@ -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))

View file

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

View file

@ -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);
}
}
}

View file

@ -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 : "<unknown>", 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 : "<unknown>", 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, "<VERSION>");
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, "<VERSION>");
/* 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;

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

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

View file

@ -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);
}

View file

@ -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)