//*****************************************************************************
//
// save.c
//
// contains all of the functions for the saving of characters and accounts.
// makes sure these things go into the right directories.
//
//*****************************************************************************
#include "mud.h"
#include "utils.h"
#include "socket.h"
#include "character.h"
#include "account.h"
#include "world.h"
#include "handler.h"
#include "body.h"
#include "object.h"
#include "room.h"
#include "storage.h"
#include "save.h"
//*****************************************************************************
// local data structures and variables
//*****************************************************************************
// tables of accounts and players currently referenced
HASHTABLE *account_table = NULL;
HASHTABLE *player_table = NULL;
typedef struct {
int refcnt;
void *data;
} SAVE_REF_DATA;
SAVE_REF_DATA *newSaveRefData(void *data) {
SAVE_REF_DATA *ref_data = malloc(sizeof(SAVE_REF_DATA));
ref_data->refcnt = 1;
ref_data->data = data;
return ref_data;
}
void deleteSaveRefData(SAVE_REF_DATA *ref_data) {
free(ref_data);
}
//*****************************************************************************
// local functions
//*****************************************************************************
//
// Just a general function for finding the filename assocciated with the
// given information about a player.
#define FILETYPE_PFILE 0
#define FILETYPE_OFILE 1
#define FILETYPE_ACCOUNT 2
const char *get_save_filename(const char *name, int filetype) {
static char buf[SMALL_BUFFER];
static char pname[SMALL_BUFFER];
int i, size;
*buf = *pname = '\0';
*pname = toupper(*name);
size = strlen(name);
for (i = 1; i < size; i++)
pname[i] = tolower(*(name+i));
pname[i] = '\0';
switch(filetype) {
case FILETYPE_ACCOUNT:
sprintf(buf, "../lib/accounts/%c/%s.acct", *pname, pname);
break;
case FILETYPE_PFILE:
sprintf(buf, "../lib/players/pfiles/%c/%s.pfile", *pname, pname);
break;
case FILETYPE_OFILE:
sprintf(buf, "../lib/players/objfiles/%c/%s.ofile", *pname, pname);
break;
default:
log_string("ERROR: Tried to write filename for nonexistant char "
"file type, %d", filetype);
break;
}
return buf;
}
bool player_creating(const char *name) {
// a player is being created if it's attached to a socket and does not exist
bool char_found = FALSE;
LIST_ITERATOR *sock_i = newListIterator(socket_list);
SOCKET_DATA *sock = NULL;
ITERATE_LIST(sock, sock_i) {
CHAR_DATA *ch = socketGetChar(sock);
if(ch == NULL) continue;
if(!strcasecmp(charGetName(ch), name)) {
char_found = TRUE;
break;
}
} deleteListIterator(sock_i);
return (char_found && !player_exists(name));
}
bool account_creating(const char *name) {
// a player is being created if it's attached to a socket and does not exist
bool acct_found = FALSE;
LIST_ITERATOR *sock_i = newListIterator(socket_list);
SOCKET_DATA *sock = NULL;
ITERATE_LIST(sock, sock_i) {
ACCOUNT_DATA *acct = socketGetAccount(sock);
if(acct == NULL) continue;
if(!strcasecmp(accountGetName(acct), name)) {
acct_found = TRUE;
break;
}
} deleteListIterator(sock_i);
return (acct_found && !account_exists(name));
}
bool player_exists(const char *name) {
// there's two ways a character can exists. Either someone is already making
// a character with that name, or there is a character with that name in
// storage. We'll check both of these.
const char *fname = get_save_filename(name, FILETYPE_PFILE);
FILE *fl = fopen(fname, "r");
if(fl != NULL) {
fclose(fl);
return TRUE;
}
return FALSE;
}
bool account_exists(const char *name) {
const char *fname = get_save_filename(name, FILETYPE_ACCOUNT);
FILE *fl = fopen(fname, "r");
if(fl != NULL) {
fclose(fl);
return TRUE;
}
return FALSE;
}
void save_pfile(CHAR_DATA *ch) {
STORAGE_SET *set = charStore(ch);
storage_write(set, get_save_filename(charGetName(ch), FILETYPE_PFILE));
storage_close(set);
}
void load_ofile(CHAR_DATA *ch) {
STORAGE_SET *set = storage_read(get_save_filename(charGetName(ch),
FILETYPE_OFILE));
if(set == NULL)
return;
STORAGE_SET *obj_set = NULL;
OBJ_DATA *obj = NULL;
// inventory first
STORAGE_SET_LIST *list = read_list(set, "inventory");
while( (obj_set = storage_list_next(list)) != NULL) {
obj = objRead(obj_set);
obj_to_char(obj, ch);
}
// and then equipped items
list = read_list(set, "equipment");
while( (obj_set = storage_list_next(list)) != NULL) {
if(storage_contains(obj_set, "object")) {
obj = objRead(read_set(obj_set, "object"));
if(!try_equip(ch, obj, read_string(obj_set, "equipped"), NULL))
obj_to_char(obj, ch);
}
}
storage_close(set);
}
void save_objfile(CHAR_DATA *ch) {
STORAGE_SET *set = new_storage_set();
// write all of the inventory
store_list(set, "inventory", gen_store_list(charGetInventory(ch), objStore));
// for equipped items, it's not so easy - we also have to record
// whereabouts on the body the equipment was worn on
STORAGE_SET_LIST *list = new_storage_list();
LIST *eq_list = bodyGetAllEq(charGetBody(ch));
OBJ_DATA *obj = NULL;
while((obj = listPop(eq_list)) != NULL) {
STORAGE_SET *eq_set = new_storage_set();
store_string(eq_set, "equipped", bodyEquippedWhere(charGetBody(ch), obj));
store_set (eq_set, "object", objStore(obj));
storage_list_put(list, eq_set);
}
deleteList(eq_list);
store_list(set, "equipment", list);
storage_write(set, get_save_filename(charGetName(ch), FILETYPE_OFILE));
storage_close(set);
}
CHAR_DATA *load_player(const char *player) {
STORAGE_SET *set = storage_read(get_save_filename(player, FILETYPE_PFILE));
if(set == NULL)
return NULL;
else {
CHAR_DATA *ch = charRead(set);
storage_close(set);
load_ofile(ch);
return ch;
}
}
ACCOUNT_DATA *load_account(const char *account) {
STORAGE_SET *set = storage_read(get_save_filename(account,
FILETYPE_ACCOUNT));
if(set == NULL)
return NULL;
else {
ACCOUNT_DATA *acct = accountRead(set);
storage_close(set);
return acct;
}
}
//*****************************************************************************
// implementation of save.h
//*****************************************************************************
void init_save(void) {
account_table = newHashtable();
player_table = newHashtable();
}
ACCOUNT_DATA *get_account(const char *account) {
SAVE_REF_DATA *ref_data = hashGet(account_table, account);
// account is not loaded in memory... try loading it
if(ref_data == NULL) {
ACCOUNT_DATA *acct = load_account(account);
// it doesn't exist. Return NULL
if(acct == NULL)
return NULL;
else {
hashPut(account_table, account, newSaveRefData(acct));
return acct;
}
}
// up our reference count and return
else {
ref_data->refcnt++;
return ref_data->data;
}
}
CHAR_DATA *get_player(const char *player) {
SAVE_REF_DATA *ref_data = hashGet(player_table, player);
// player is not loaded in memory... try loading it
if(ref_data == NULL) {
CHAR_DATA *ch = load_player(player);
// it doesn't exist. Return NULL
if(ch == NULL)
return NULL;
else {
hashPut(player_table, player, newSaveRefData(ch));
return ch;
}
}
// up our reference count and return
else {
ref_data->refcnt++;
return ref_data->data;
}
}
void unreference_account(ACCOUNT_DATA *account) {
SAVE_REF_DATA *ref_data = hashGet(account_table, accountGetName(account));
if(ref_data == NULL)
log_string("ERROR: Tried unreferencing account '%s' with no references!",
accountGetName(account));
else {
ref_data->refcnt--;
// are we at 0 references?
if(ref_data->refcnt == 0) {
hashRemove(account_table, accountGetName(account));
deleteAccount(account);
deleteSaveRefData(ref_data);
}
}
}
void unreference_player(CHAR_DATA *ch) {
SAVE_REF_DATA *ref_data = hashGet(player_table, charGetName(ch));
if(ref_data == NULL)
log_string("ERROR: Tried unreferencing player '%s' with no references!",
charGetName(ch));
else {
ref_data->refcnt--;
// are we at 0 references?
if(ref_data->refcnt == 0) {
hashRemove(player_table, charGetName(ch));
deleteChar(ch);
deleteSaveRefData(ref_data);
}
}
}
void reference_account(ACCOUNT_DATA *account) {
SAVE_REF_DATA *ref_data = hashGet(account_table, accountGetName(account));
if(ref_data == NULL)
log_string("ERROR: Tried referencing account '%s' that is not loaded!",
accountGetName(account));
else
ref_data->refcnt++;
}
void reference_player(CHAR_DATA *ch) {
SAVE_REF_DATA *ref_data = hashGet(player_table, charGetName(ch));
if(ref_data == NULL)
log_string("ERROR: Tried referencing player '%s' that is not loaded!",
charGetName(ch));
else
ref_data->refcnt++;
}
void register_account(ACCOUNT_DATA *account) {
if(account_exists(accountGetName(account)))
log_string("ERROR: Tried to register already-registered account, '%s'",
accountGetName(account));
else {
hashPut(account_table, accountGetName(account), newSaveRefData(account));
save_account(account);
}
}
void register_player(CHAR_DATA *ch) {
if(player_exists(charGetName(ch)))
log_string("ERROR: Tried to register already-registered player, '%s'",
charGetName(ch));
else {
hashPut(player_table, charGetName(ch), newSaveRefData(ch));
save_player(ch);
}
}
void save_account(ACCOUNT_DATA *account) {
if(!account) return;
STORAGE_SET *set = accountStore(account);
storage_write(set, get_save_filename(accountGetName(account),
FILETYPE_ACCOUNT));
storage_close(set);
}
void save_player(CHAR_DATA *ch) {
if (ch == NULL) return;
// make sure we have a UID for the character before we start saving
if(charGetUID(ch) == NOBODY) {
log_string("ERROR: %s has invalid UID (%d)", charGetName(ch), NOBODY);
send_to_char(ch, "You have an invalid ID. Please inform a god.\r\n");
return;
}
// make sure we'll load back into the same room we were saved in
charSetLoadroom(ch, (charGetRoom(ch) ? roomGetClass(charGetRoom(ch)) :
START_ROOM));
save_objfile(ch); // save the player's objects
save_pfile(ch); // saves the actual player data
}