//*****************************************************************************
//
// socials.c
//
// socials are commonly used emotes (e.g. smiling, grinning, laughing). Instead
// making people have to write out an entire emote every time they would like
// to express such an emote, they can simply use one of these simple commands
// to perform a pre-made emote.
//
//*****************************************************************************
#include "../mud.h"
#include "../utils.h"
#include "../storage.h"
#include "../handler.h"
#include "../inform.h"
#include "../character.h"
#include "socedit.h"
#include "socials.h"
//*****************************************************************************
//
// local datastructures, defines, and functions
//
//*****************************************************************************
// the file where all of the socials are stored
#define SOCIALS_FILE "../lib/misc/socials"
// the table we store all of the socials in
HASHTABLE *social_table = NULL;
//
// save all of the socials to disk
//
void save_socials() {
STORAGE_SET *set = new_storage_set();
LIST *soc_list = newList();
// iterate across the social table and save all of the unique socials
HASH_ITERATOR *hash_i = newHashIterator(social_table);
const char *cmd = NULL;
SOCIAL_DATA *data = NULL;
ITERATE_HASH(cmd, data, hash_i)
listPut(soc_list, data);
deleteHashIterator(hash_i);
store_list(set, "socials", gen_store_list(soc_list, socialStore));
deleteList(soc_list);
// write the set
storage_write(set, SOCIALS_FILE);
// close the set
storage_close(set);
}
//*****************************************************************************
//
// Implementation of the social datastructure
//
//*****************************************************************************
struct social_data {
char *cmds; // the list of commands we fire off of
char *to_char_notgt; // the message to us when there's no target
char *to_room_notgt; // the message to the room when there's no target
char *to_char_self; // the message to ourself when tgt == us
char *to_room_self; // the message to the room when tgt == us
char *to_char_tgt; // the message to us when there's a target
char *to_vict_tgt; // the message to the target
char *to_room_tgt; // the message to the room when there's a target
int min_pos; // the minimum position we can perform this from
int max_pos; // and the maximum
};
SOCIAL_DATA *newSocial(const char *cmds,
const char *to_char_notgt,
const char *to_room_notgt,
const char *to_char_self,
const char *to_room_self,
const char *to_char_tgt,
const char *to_vict_tgt,
const char *to_room_tgt,
int min_pos, int max_pos) {
SOCIAL_DATA *data = malloc(sizeof(SOCIAL_DATA));
data->cmds = strdupsafe(cmds);
data->to_char_notgt = strdupsafe(to_char_notgt);
data->to_room_notgt = strdupsafe(to_room_notgt);
data->to_char_self = strdupsafe(to_char_self);
data->to_room_self = strdupsafe(to_room_self);
data->to_char_tgt = strdupsafe(to_char_tgt);
data->to_vict_tgt = strdupsafe(to_vict_tgt);
data->to_room_tgt = strdupsafe(to_room_tgt);
data->min_pos = min_pos;
data->max_pos = max_pos;
return data;
}
void deleteSocial(SOCIAL_DATA *data) {
if(data->cmds) free(data->cmds);
if(data->to_char_notgt) free(data->to_char_notgt);
if(data->to_room_notgt) free(data->to_room_notgt);
if(data->to_char_self) free(data->to_char_self);
if(data->to_room_self) free(data->to_room_self);
if(data->to_char_tgt) free(data->to_char_tgt);
if(data->to_vict_tgt) free(data->to_vict_tgt);
if(data->to_room_tgt) free(data->to_room_tgt);
free(data);
}
STORAGE_SET *socialStore(SOCIAL_DATA *data) {
STORAGE_SET *set = new_storage_set();
store_string(set, "cmds", data->cmds);
store_string(set, "to_char_notgt", data->to_char_notgt);
store_string(set, "to_room_notgt", data->to_room_notgt);
store_string(set, "to_char_self", data->to_char_self);
store_string(set, "to_room_self", data->to_room_self);
store_string(set, "to_char_tgt", data->to_char_tgt);
store_string(set, "to_vict_tgt", data->to_vict_tgt);
store_string(set, "to_room_tgt", data->to_room_tgt);
store_string(set, "min_pos", posGetName(data->min_pos));
store_string(set, "max_pos", posGetName(data->max_pos));
return set;
}
SOCIAL_DATA *socialRead(STORAGE_SET *set) {
return newSocial(read_string(set, "cmds"),
read_string(set, "to_char_notgt"),
read_string(set, "to_room_notgt"),
read_string(set, "to_char_self"),
read_string(set, "to_room_self"),
read_string(set, "to_char_tgt"),
read_string(set, "to_vict_tgt"),
read_string(set, "to_room_tgt"),
posGetNum(read_string(set, "min_pos")),
posGetNum(read_string(set, "max_pos")));
}
void socialCopyTo(SOCIAL_DATA *from, SOCIAL_DATA *to) {
// free all of the strings
if(to->cmds) free(to->cmds);
if(to->to_char_notgt) free(to->to_char_notgt);
if(to->to_room_notgt) free(to->to_room_notgt);
if(to->to_char_self) free(to->to_char_self);
if(to->to_room_self) free(to->to_room_self);
if(to->to_char_tgt) free(to->to_char_tgt);
if(to->to_vict_tgt) free(to->to_vict_tgt);
if(to->to_room_tgt) free(to->to_room_tgt);
// copy over all of the new descs and commands
to->cmds = strdupsafe(from->cmds);
to->to_char_notgt = strdupsafe(from->to_char_notgt);
to->to_room_notgt = strdupsafe(from->to_room_notgt);
to->to_char_self = strdupsafe(from->to_char_self);
to->to_room_self = strdupsafe(from->to_room_self);
to->to_char_tgt = strdupsafe(from->to_char_tgt);
to->to_vict_tgt = strdupsafe(from->to_vict_tgt);
to->to_room_tgt = strdupsafe(from->to_room_tgt);
to->min_pos = from->min_pos;
to->max_pos = from->max_pos;
}
SOCIAL_DATA *socialCopy(SOCIAL_DATA *social) {
SOCIAL_DATA *new_soc = newSocial("", NULL, NULL, NULL, NULL, NULL, NULL, NULL,
POS_STANDING, POS_STANDING);
socialCopyTo(social, new_soc);
return new_soc;
}
const char *socialGetCmds(SOCIAL_DATA *social) {
return social->cmds;
}
const char *socialGetCharNotgt(SOCIAL_DATA *social) {
return social->to_char_notgt;
}
const char *socialGetRoomNotgt(SOCIAL_DATA *social) {
return social->to_room_notgt;
}
const char *socialGetCharSelf (SOCIAL_DATA *social) {
return social->to_char_self;
}
const char *socialGetRoomSelf (SOCIAL_DATA *social) {
return social->to_room_self;
}
const char *socialGetCharTgt(SOCIAL_DATA *social) {
return social->to_char_tgt;
}
const char *socialGetVictTgt(SOCIAL_DATA *social) {
return social->to_vict_tgt;
}
const char *socialGetRoomTgt(SOCIAL_DATA *social) {
return social->to_room_tgt;
}
int socialGetMinPos(SOCIAL_DATA *social) {
return social->min_pos;
}
int socialGetMaxPos(SOCIAL_DATA *social) {
return social->max_pos;
}
void socialSetCharNotgt(SOCIAL_DATA *social, const char *mssg) {
if(social->to_char_notgt) free(social->to_char_notgt);
social->to_char_notgt = strdupsafe(mssg);
}
void socialSetRoomNotgt(SOCIAL_DATA *social, const char *mssg) {
if(social->to_room_notgt) free(social->to_room_notgt);
social->to_room_notgt = strdupsafe(mssg);
}
void socialSetCharSelf(SOCIAL_DATA *social, const char *mssg) {
if(social->to_char_self) free(social->to_char_self);
social->to_char_self = strdupsafe(mssg);
}
void socialSetRoomSelf(SOCIAL_DATA *social, const char *mssg) {
if(social->to_room_self) free(social->to_room_self);
social->to_room_self = strdupsafe(mssg);
}
void socialSetCharTgt(SOCIAL_DATA *social, const char *mssg) {
if(social->to_char_tgt) free(social->to_char_tgt);
social->to_char_tgt = strdupsafe(mssg);
}
void socialSetVictTgt(SOCIAL_DATA *social, const char *mssg) {
if(social->to_vict_tgt) free(social->to_vict_tgt);
social->to_vict_tgt = strdupsafe(mssg);
}
void socialSetRoomTgt(SOCIAL_DATA *social, const char *mssg) {
if(social->to_room_tgt) free(social->to_room_tgt);
social->to_room_tgt = strdupsafe(mssg);
}
void socialSetMinPos(SOCIAL_DATA *social, int pos) {
social->min_pos = pos;
}
void socialSetMaxPos(SOCIAL_DATA *social, int pos) {
social->max_pos = pos;
}
//
// Link one social to another
// usage: soclink [new command] [old command]
//
COMMAND(cmd_soclink) {
char new_soc[SMALL_BUFFER];
if(!arg || !*arg) {
send_to_char(ch, "Link which social to which?\r\n");
return;
}
arg = one_arg(arg, new_soc);
if(!*new_soc || !*arg) {
send_to_char(ch, "You must provide a new command and an old social "
"to link it to.\r\n");
return;
}
// find the social we're trying to link to
SOCIAL_DATA *social = get_social(arg);
if(social == NULL) {
send_to_char(ch, "No social exists for %s.\r\n", arg);
return;
}
// perform the link
link_social(new_soc, arg);
send_to_char(ch, "%s linked to %s.\r\n", new_soc, arg);
}
//
// Unlink a social
// usage: socunlink [command]
//
COMMAND(cmd_socunlink) {
if(!arg || !*arg) {
send_to_char(ch, "Unlink which social?\r\n");
return;
}
// find the social we're trying to link to
SOCIAL_DATA *social = get_social(arg);
if(social == NULL) {
send_to_char(ch, "No social exists for %s.\r\n", arg);
return;
}
// perform the unlinking
unlink_social(arg);
send_to_char(ch, "The %s social was unlinked.\r\n", arg);
}
//
// List all of the socials to the character
//
COMMAND(cmd_socials) {
HASH_ITERATOR *soc_i = newHashIterator(social_table);
const char *soc = NULL;
SOCIAL_DATA *data = NULL;
LIST *soc_list = newList();
int count = 0;
// go through the hashtable and pull out each key
ITERATE_HASH(soc, data, soc_i)
listPut(soc_list, strdup(soc));
deleteHashIterator(soc_i);
// now sort them, and pop them out one at a time
listSortWith(soc_list, strcmp);
// and pop them out one at a time to print
while( (soc = listPop(soc_list)) != NULL) {
count++;
send_to_char(ch, "%-20s%s", soc, (count % 4 == 0 ? "\r\n" : ""));
}
deleteListWith(soc_list, free);
if(count == 0 || count % 4 != 0)
send_to_char(ch, "\r\n");
}
//
// One generic command for handling socials. Does table lookup on all of
// the existing socials and executes the proper one.
//
COMMAND(cmd_social) {
// look up the social
SOCIAL_DATA *data = hashGet(social_table, cmd);
// does the social exist? Do we have a problem? DO WE?
if(data != NULL) {
// find the target
CHAR_DATA *tgt = NULL;
if(*arg) {
int tgt_type = FOUND_NONE;
tgt = generic_find(ch, arg, FIND_TYPE_CHAR, FIND_SCOPE_IMMEDIATE,
FALSE, &tgt_type);
}
// see if we were trying to find a target but couldn't
if(*arg && !tgt)
send_to_char(ch, "Who were you looking for?\r\n");
// no target was supplied
else if(!tgt) {
if(*data->to_char_notgt)
message(ch, NULL, NULL, NULL, TRUE, TO_CHAR, data->to_char_notgt);
if(*data->to_room_notgt)
message(ch, NULL, NULL, NULL, TRUE, TO_ROOM, data->to_room_notgt);
}
// a target was supplied, and it was us
else if(ch == tgt) {
if(*data->to_char_self)
message(ch, NULL, NULL, NULL, TRUE, TO_CHAR, data->to_char_self);
else if(*data->to_char_notgt)
message(ch, NULL, NULL, NULL, TRUE, TO_CHAR, data->to_char_notgt);
if(*data->to_room_self)
message(ch, NULL, NULL, NULL, TRUE, TO_ROOM, data->to_room_self);
else if(*data->to_room_notgt)
message(ch, NULL, NULL, NULL, TRUE, TO_ROOM, data->to_room_notgt);
}
// a target was supplied and it was not us
else {
if(*data->to_char_tgt)
message(ch, tgt, NULL, NULL, TRUE, TO_CHAR, data->to_char_tgt);
if(*data->to_vict_tgt)
message(ch, tgt, NULL, NULL, TRUE, TO_VICT, data->to_vict_tgt);
if(*data->to_room_tgt)
message(ch, tgt, NULL, NULL, TRUE, TO_ROOM, data->to_room_tgt);
}
}
else
log_string("ERROR: %s tried social, %s, but no such social exists!",
charGetName(ch), cmd);
}
//*****************************************************************************
//
// implementation of socials.h
//
//*****************************************************************************
// I feel dirty doing this, but it's small and it saves us lots of work. So,
// well, we'll just have to live with it ;)
//
// init_socials calls add_social to add in all of the socials it loads up.
// But add_social calls save_socials each time that a new social is added.
// Thus, when we are booting up, we're making so so so many writes to disk
// that are un-needed. Here, we just have a little variable that tells whether
// we're booting up or if we're adding a new social in-game. If we're just
// booting up, then add_socials won't save any changes.
bool in_social_init = TRUE;
void init_socials() {
// create the social table
social_table = newHashtable();
// open up the storage set
STORAGE_SET *set = storage_read(SOCIALS_FILE);
STORAGE_SET_LIST *list = read_list(set, "socials");
STORAGE_SET *social = NULL;
// parse all of the socials
while( (social = storage_list_next(list)) != NULL)
add_social(socialRead(social));
// close the storage set
storage_close(set);
// add all of the socials to the command table
HASH_ITERATOR *hash_i = newHashIterator(social_table);
const char *cmd = NULL;
SOCIAL_DATA *data = NULL;
ITERATE_HASH(cmd, data, hash_i)
add_cmd(cmd, NULL, cmd_social, data->min_pos, data->max_pos,
"player", TRUE, FALSE);
deleteHashIterator(hash_i);
// link/unlink commands for the admins
add_cmd("soclink", NULL, cmd_soclink, POS_UNCONSCIOUS, POS_FLYING,
"builder", FALSE, FALSE);
add_cmd("socunlink", NULL, cmd_socunlink, POS_UNCONSCIOUS, POS_FLYING,
"builder", FALSE, FALSE);
add_cmd("socials", NULL, cmd_socials, POS_UNCONSCIOUS, POS_FLYING,
"player", TRUE, FALSE);
// let add_social know it can start saving again
in_social_init = FALSE;
init_socedit();
}
SOCIAL_DATA *get_social(const char *cmd) {
return hashGet(social_table, cmd);
}
void add_social(SOCIAL_DATA *social) {
// for each of our keywords, go through and
// unlink all of the current socials and link the new one
LIST *cmd_list = parse_keywords(social->cmds);
LIST_ITERATOR *cmd_i = newListIterator(cmd_list);
char *cmd = NULL;
ITERATE_LIST(cmd, cmd_i) {
unlink_social(cmd);
hashPut(social_table, cmd, social);
// add the new command to the game
add_cmd(cmd, NULL, cmd_social, social->min_pos, social->max_pos,
"player", TRUE, FALSE);
} deleteListIterator(cmd_i);
// garbage collection
deleteListWith(cmd_list, free);
// save changes
if(!in_social_init)
save_socials();
}
void link_social(const char *new_cmd, const char *old_cmd) {
// check for old_cmd
SOCIAL_DATA *data = hashGet(social_table, old_cmd);
// link new_cmd to old_cmd
if(data != NULL) {
// first, remove the current new_cmd social, if it exists
unlink_social(new_cmd);
// add the new keyword to the social
add_keyword(&(data->cmds), new_cmd);
// add the new command to the social table
hashPut(social_table, new_cmd, data);
// add the new command to the game
add_cmd(new_cmd, NULL, cmd_social, data->min_pos, data->max_pos,
"player", TRUE, FALSE);
}
// save changes
if(!in_social_init)
save_socials();
}
void unlink_social(const char *cmd) {
// remove the command
SOCIAL_DATA *data = hashRemove(social_table, cmd);
// unlink the social
if(data != NULL) {
remove_keyword(data->cmds, cmd);
// remove the command from the command table
remove_cmd(cmd);
// if no links are left, delete the social
if(!*data->cmds)
deleteSocial(data);
// save changes
if(!in_social_init)
save_socials();
}
}