extending_nakedmud.tex 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477
  1. \documentclass[12pt]{article}
  2. \usepackage{geometry}
  3. \geometry{letterpaper}
  4. \usepackage[parfill]{parskip} % Activate to begin paragraphs with an empty line
  5. \usepackage{graphicx}
  6. \usepackage{amssymb}
  7. \usepackage{epstopdf}
  8. \DeclareGraphicsRule{.tif}{png}{.png}{`convert #1 `dirname #1`/`basename #1 .tif`.png}
  9. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  10. % set all of our non-document information
  11. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  12. \title{Extending NakedMud:\\
  13. An Introduction to Modules, Storage Sets, and Auxiliary Data}
  14. \author{Geoff Hollis\\
  15. hollisgf@email.uc.edu}
  16. \begin{document}
  17. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  18. % set up all of our stylistic issues
  19. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  20. \pagenumbering{arabic}
  21. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  22. % generate the title and table of contents
  23. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  24. \maketitle
  25. \newpage
  26. \tableofcontents
  27. \newpage
  28. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  29. % Introduction
  30. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  31. \section{Introduction}
  32. There are three aspects of NakedMud's code that must be understood to efficiently build a mud using the codebase: modules, auxiliary data, and storage sets. Modules and auxiliary data allow programmers to organize their work by concept (e.g. combat-related functions and variables, magic-related stuff, etc...) rather than by data structure (e.g. all character variables, all room variables, etc...). I believe such conceptual organization make the processes of debugging, maintenance, and distribution much easier than they would otherwise be. The third aspect - storage sets - is an attempt to provide a general format for saving and loading data from files, and eliminate much of the legwork that comes with reading and writing to files. It is also (more importantly) an attempt to ensure the addition of new information to data structures in the MUD never results in formatting conflicts within files. This manual is mainly a tutorial for how to use modules, auxiliary data, and storage sets. There are three sections to this manual - one for each topic. The three sections build on top of eachother, but each one can be read and used independently with the help of code supplied in the appendices.
  33. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  34. % Your first Module
  35. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  36. \newpage \section{Modules}
  37. Almost all new extensions to NakedMud are expected to be added through modules. In its most basic form, a module is a directory that contains src files that are all united in some high-level, conceptual manner. So for instance, you might have a module that contains all of the mechanics for combat, or another module for all the magic mechanics, or maybe a module that adds commands with names that give your MUD the look\&feel of a famous codebase like Circle, or ROM. The main point is that modules organize the source code of your mud by concept.
  38. It is good practice to organize your code by concept rather than lumping all your additions into the main src directory. When you need to go back and debug systems within your MUD, things will be easier to find if everything is organized by system or concept. On the same token, code is much easier to maintain and extend if everything you need relating to some concept you are changing is spatially localized. And as an added benefit, if you would like to distribute new systems you have written, you can simply package up the directory containing your module. No more work is involved.
  39. Modules are very easy to set up. Adding a module is basically like you would normally add code, except you have to make a new directory for everything that will be included in your module, and let the MUD know you are adding a new module. Here, we will walk through the creation of a module that allows players to send and receive mail. In later sections, it will be built on to demonstrate how storage sets and auxiliary data work.
  40. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  41. % Subsection: Preparing to Program
  42. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  43. \subsection{Preparing to Program}
  44. Before we can begin programming the mail module, we have to make a directory for it. We will also have to change a few things in the makefile and gameloop to ensure the module will be compiled and loaded properly.
  45. Enter your src directory, and make a new folder for the mail module. If you are in a terminal window, you can make the new directory with {\it mkdir mail.} You will now have to let your Makefile know that the new module exists. Open up Makefile in your src directory, and look for the line where optional modules are added to the variable, MODULES. The line you are searching for will look something like:
  46. % code snippet for adding the new module to Makefile
  47. {\bf \begin{verbatim}
  48. # optional modules go on this line
  49. MODULES += time socials alias help
  50. \end{verbatim}}
  51. To this list, add the name of the new module you just created. Now, when the Makefile compiles your MUD, it will know that you have installed a new module called mail, and it will go into that directory and compile all the files within it. Well, almost. What actually happens is the Makefile goes into the module directory and looks for {\it another} makefile that lists off all the source files that need to be compiled for that module, along with all the libraries and compiler flags that are required for the new code to work. So, let's make a new makefile in the module directory to let the main makefile know which source files we will be editing. We do not have to worry about adding libraries or compiler flags for this module, as it is going to be very simple. In your module directory, create a file called {\it module.mk} and edit it. We will only be working with one source file in this directory, and it will be called {\it mail.c}. To let the main makefile know that this source file will be made, add the following lines of code to your {\it module.mk} file:
  52. % code snippet for mail/module.mk
  53. {\bf \begin{verbatim}
  54. # include all of the source files contained in this module
  55. SRC += mail/mail.c
  56. \end{verbatim}}
  57. In general, the path relative to the main src directory for all source files in your module should be added to the SRC variable.
  58. Now that your MUD knows that your module exists, you will have to take some steps to initialize all of the new features your module will add to the MUD. This is traditionally done by adding an init\_xxx() function to your module, and calling it when the MUD first boots up. Let us create an init function and fill it with a nonsense message until we actually have code to initialize. In your new module directory, create and edit a file called {\it mail.c}. To it, add the following bit of code:
  59. % code snippet for the first edit of mail/mail.c
  60. {\bf \begin{verbatim}
  61. // include all the header files we will need from the MUD core
  62. #include "../mud.h"
  63. #include "../utils.h" // for get_time()
  64. #include "../character.h" // for handling characters sending mail
  65. #include "../save.h" // for char_exists()
  66. #include "../object.h" // for creating mail objects
  67. #include "../handler.h" // for giving mail to characters
  68. // include headers from other modules that we require
  69. #include "../editor/editor.h" // for access to sockets' notepads
  70. // include the headers for this module
  71. #include "mail.h"
  72. // boot up the mail module
  73. void init_mail(void) {
  74. printf("Nothing in the mail module yet!");
  75. }
  76. \end{verbatim}}
  77. You will notice that we include a header called {\it mail.h}, which has not yet been created. Let's create the header and add all of the functions that source code outside of the mail module should have access to. In your new module directory, create and edit a file called {\it mail.h}. To it, add the following bit of code:
  78. % code snippet for mail/mail.h
  79. {\bf \begin{verbatim}
  80. #ifndef MAIL_H
  81. #define MAIL_H
  82. // this function should be called when the MUD first boots up.
  83. // calling it will initialize the mail module for use.
  84. void init_mail(void);
  85. #endif // MAIL_H
  86. \end{verbatim}}
  87. Then, let us call the init function where all the other modules' init functions are called. This is a two-step process. We first have to make a define in {\it mud.h} that informs the rest of the MUD code that the module is installed. So, edit {\it mud.h} in the main src directory. Near the very start of the file, you will see lists of defined of the form MODULE\_XXX. With the rest of your optional modules, add the line:
  88. % code snippet for define in mud.h
  89. {\bf \begin{verbatim}
  90. #define MODULE_MAIL
  91. \end{verbatim}}
  92. We then need to go into {\it gameloop.c} and call the init function. Edit {\it gameloop.c} in your main src directory. At the end of the header files, you will see headers for optional modules. Add another entry for your mail module:
  93. % code snippet for new include in gameloop.c
  94. {\bf \begin{verbatim}
  95. #ifdef MODULE_MAIL
  96. #include "mail/mail.h"
  97. #endif
  98. \end{verbatim}}
  99. Now, go down further to where all of the modules are initialized. This will be in the main() function, right before the gameworld is created. Add your init function to the list of other init functions:
  100. % code snippet for calling the module init function
  101. {\bf \begin{verbatim}
  102. #ifdef MODULE_MAIL
  103. log_string("Initializing mail system.");
  104. init_mail();
  105. #endif
  106. \end{verbatim}}
  107. Notice how both the include for our mail.h header, and the call to our init function for the mail module are wrapped around \#ifdef and \#endif statements? This is to allow us to easily pull out the mail module if we ever want to. If we ever want to turn off the module, all we will have to do is go into mud.h and comment out the line, \#define MODULE\_MAIL. For all intents and purposes within the code, the mail module no longer exists when this line is commented out. We have now completed all of the prep work needed before we can start writing the mail module.
  108. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  109. % Subsection: Programming a Mail Module
  110. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  111. \subsection{Programming a Mail Module}
  112. To start, we will aim for something simple. The first incarnation of our mail module will allow players to send written messages to one another. Sent mail will not be persistent (it will not save over reboots or crashes). That feature will be postponed until section 3: Storage Sets.
  113. To start, we will have to create a new structure to represent a piece of sent mail. Add this to {\it mail.c}:
  114. {\bf \begin{verbatim}
  115. typedef struct {
  116. char *sender; // name of the char who sent this mail
  117. char *time; // the time it was sent at
  118. BUFFER *mssg; // the accompanying message
  119. } MAIL_DATA;
  120. \end{verbatim}}
  121. Now, let's write some functions for a couple procedures we will be needing down the line - the creation and deletion of mail:
  122. {\bf \begin{verbatim}
  123. // create a new piece of mail.
  124. MAIL_DATA *newMail(CHAR_DATA *sender, const char *mssg) {
  125. MAIL_DATA *mail = malloc(sizeof(MAIL_DATA));
  126. mail->sender = strdup(charGetName(sender));
  127. mail->time = strdup(get_time());
  128. mail->mssg = newBuffer(strlen(mssg));
  129. bufferCat(mail->mssg, mssg);
  130. return mail;
  131. }
  132. // free all of the memory that was allocated to make this piece of mail
  133. void deleteMail(MAIL_DATA *mail) {
  134. if(mail->sender) free(mail->sender);
  135. if(mail->time) free(mail->time);
  136. if(mail->mssg) deleteBuffer(mail->mssg);
  137. free(mail);
  138. }
  139. \end{verbatim}}
  140. Now that the basic steps for creating and deleting mail is complete, we can
  141. set up some commands to allow players to send mail:
  142. {\bf \begin{verbatim}
  143. // mails a message to the specified person. This command must take the
  144. // following form:
  145. // mail <person>
  146. //
  147. // The contents of the character's notepad will be used as the body of the
  148. // message. The character's notepad must not be empty.
  149. COMMAND(cmd_mail) {
  150. // make sure the character exists
  151. if(!char_exists(arg))
  152. send_to_char(ch, "Noone named %s is registered on %s.\r\n",
  153. arg, "<insert mud name here>");
  154. // make sure we have a socket - we'll need access to its notepad
  155. else if(!charGetSocket(ch))
  156. send_to_char(ch, "Only characters with sockets can send mail!\r\n");
  157. // make sure our notepad is not empty
  158. else if(!*bufferString(socketGetNotepad(charGetSocket(ch))))
  159. send_to_char(ch, "Your notepad is empty. "
  160. "First, try writing something with {cwrite{n.\r\n");
  161. // the character exists. Let's parse the items and send the mail
  162. else {
  163. MAIL_DATA *mail = newMail(ch, bufferString(socketGetNotepad(charGetSocket(ch))));
  164. // if we had some way of storing mail, we'd now do that. But since we
  165. // don't, let's just delete the mail.
  166. //***********
  167. // FINISH ME
  168. //***********
  169. deleteMail(mail);
  170. // let the character know we've sent the mail
  171. send_to_char(ch, "You send a message to %s.\r\n", arg);
  172. }
  173. }
  174. \end{verbatim}}
  175. We will also want to add this new command to a list of all the commands in the mud. So, let's go down to init\_mail() and do that:
  176. {\bf \begin{verbatim}
  177. // boot up the mail module
  178. void init_mail(void) {
  179. // add all of the commands that come with this module
  180. add_cmd("mail", NULL, cmd_mail, POS_STANDING, POS_FLYING,
  181. "player", FALSE, TRUE);
  182. }
  183. \end{verbatim}}
  184. The first incarnation of our module is just about complete! The only thing we
  185. need now is a way to store mail that has been sent. To do this, we will need some way of mapping characters to their received mail, and a command for them to access that mail. Let us create a hashtable to map mail recipients to their mail. We will do this at the top of mail.c, just before we create the MAIL\_DATA structure:
  186. {\bf \begin{verbatim}
  187. // maps charName to a list of mail they have received
  188. HASHTABLE *mail_table = NULL;
  189. \end{verbatim}}
  190. We will also need to create this hashtable in our init\_mail() function, so go down to that and perform the initialization for a hashtable:
  191. {\bf \begin{verbatim}
  192. // boot up the mail module
  193. void init_mail(void) {
  194. // initialize our mail table
  195. mail_table = newHashtable();
  196. // add all of the commands that come with this module
  197. add_cmd("mail", NULL, cmd_mail, POS_STANDING, POS_FLYING,
  198. "player", FALSE, TRUE);
  199. }
  200. \end{verbatim}}
  201. Earlier, we left cmd\_mail unfinished, because we had no way to store mail. Now that we have a hashtable for performing this function, let's go back to cmd\_mail and fill in the unfinished part:
  202. {\bf \begin{verbatim}
  203. // the character exists. Let's parse the items and send the mail
  204. else {
  205. MAIL_DATA *mail = newMail(ch, bufferString(socketGetNotepad(charGetSocket(ch))));
  206. // see if the receiver already has a mail list
  207. LIST *mssgs = hashGet(mail_table, arg);
  208. // if he doesn't, create one and add it to the hashtable
  209. if(mssgs == NULL) {
  210. mssgs = newList();
  211. hashPut(mail_table, arg, mssgs);
  212. }
  213. // add the new mail to our mail list
  214. listPut(mssgs, mail);
  215. // let the character know we've sent the mail
  216. send_to_char(ch, "You send a message to %s.\r\n", arg);
  217. }
  218. \end{verbatim}}
  219. It's all well and good being able to send mail, but we still need a way for people to receive their mail. Let's write the command for performing that function now:
  220. {\bf \begin{verbatim}
  221. // checks to see if the character has any mail. If he does, convert each piece
  222. // of mail into an object, and transfer them all into the character's inventory.
  223. COMMAND(cmd_receive) {
  224. // Remove the character's mail list from our mail table
  225. LIST *mail_list = hashRemove(mail_table, charGetName(ch));
  226. // make sure the list exists
  227. if(mail_list == NULL || listSize(mail_list) == 0)
  228. send_to_char(ch, "You have no new mail.\r\n");
  229. // hand over all of the mail
  230. else {
  231. // go through each piece of mail, make an object for it,
  232. // and transfer the new object to us
  233. LIST_ITERATOR *mail_i = newListIterator(mail_list);
  234. MAIL_DATA *mail = NULL;
  235. ITERATE_LIST(mail, mail_i) {
  236. OBJ_DATA *obj = newObj();
  237. objSetName (obj, "a letter");
  238. objSetKeywords (obj, "letter, mail");
  239. objSetRdesc (obj, "A letter is here.");
  240. objSetMultiName (obj, "A stack of %d letters");
  241. objSetMultiRdesc(obj, "A stack of %d letters are here.");
  242. bprintf(objGetDescBuffer(obj),
  243. "Sender : %s\r\n"
  244. "Date sent: %s\r\n"
  245. "%s", mail->sender, mail->time, bufferString(mail->mssg));
  246. // give the object to the character
  247. obj_to_game(obj);
  248. obj_to_char(obj, ch);
  249. } deleteListIterator(mail_i);
  250. // let the character know how much mail he received
  251. send_to_char(ch, "You receive %d letter%s.\r\n",
  252. listSize(mail_list), (listSize(mail_list) == 1 ? "" : "s"));
  253. }
  254. // delete the mail list, and all of its contents
  255. if(mail_list != NULL) deleteListWith(mail_list, deleteMail);
  256. }
  257. \end{verbatim}}
  258. Like with cmd\_mail, we will also have to add this new command to the list of all commands in the mud. Go down to init\_mail() and add it in below our other command:
  259. {\bf \begin{verbatim}
  260. // add all of the commands that come with this module
  261. add_cmd("mail", NULL, cmd_mail, POS_STANDING, POS_FLYING,
  262. "player", FALSE, TRUE);
  263. add_cmd("receive", NULL, cmd_receive, POS_STANDING, POS_FLYING,
  264. "player", FALSE, TRUE);
  265. \end{verbatim}}
  266. There, we're all done! You now have a great, basic mail module. We could probably add lots to this module, like parcels of items, fees for sending mail, checks to make sure mail is only sent at mailboxes, etc... but for our purposes of demonstrating how modules work, this will suffice. The rest of the features will be left to you as an exercise.
  267. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  268. % Subsection: Summary
  269. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  270. \subsection{Summary}
  271. You have been provided with a hands on demonstration on how new modules are created. This topic covered lots of ground. You are not expected to immediately remember everything that has been covered so far, but going back through the entire tutorial to reread details about the module creation process would definitely be an onerous task. As such, a short reference for the module creation process has been outlined below:
  272. \begin{itemize}
  273. \item Create a directory for your module
  274. \item Create your directory's module.mk file
  275. \item Add an entry for the module to the main Makefile module list
  276. \item Add a define for your module in mud.h
  277. \item Call your module's init function in gameloop.c
  278. \item Write the code for your module
  279. \end{itemize}
  280. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  281. % Storage Sets
  282. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  283. \newpage \section{Storage Sets}
  284. Storage sets are a big part of NakedMud. They serve a few important purposes: They simplify the process of saving data from files by eliminating your need to come up with formatting schemes for your flatfiles. They also eliminate your need to write file parsers to extract data from files; the process of retrieving information from a file is reduced to querying for the value of some key. As we will learn later, they also play an integral role in the process of saving and loading auxiliary data.
  285. In this section, we will learn the ropes of storage sets. We'll see how to store and read lists and strings. The other data types storage sets can deal with (ints, bools, doubles, longs) are handled in the exact same way as strings, except with different function names. After this tutorial, you should be able to extrapolate how these other data types interact with storage sets. In the next section on auxiliary data, we will examine how storage sets work in conjunction with auxiliary data.
  286. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  287. % Subsection: Getting Up To Speed
  288. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  289. \subsection{Getting Up To Speed}
  290. If you have not already performed the tutorial on modules, you will need to do a couple things before you can go through the storage set tutorial. If you have already performed the tutorial on modules, disregard this section and move onto the next one.
  291. \begin{itemize}
  292. \item In your src directory, make a new directory called {\it mail}
  293. \item Examine appendix A. For each of the 3 files you see, add it and its contents to the {\it mail} directory you just created
  294. \item Edit the {\it Makefile} in your src directory. To the line listing off your optional modules, add {\it mail}
  295. \item Edit {\it mud.h} and add {\it \#define MODULE\_MAIL} to the list of other module defines you have
  296. \item Edit {\it gameloop.c} and add {\it \#include "mail/mail.h"} to the list of optional module headers in the same fashion it is added for the other module headers
  297. \item Still in {\it gameloop.c}, search for the init functions of your other modules and add your {\it init\_mail()} function in the same way the init() functions for your other modules is added
  298. \end{itemize}
  299. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  300. % Subsection: Storage Set Basics
  301. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  302. \subsection{Storage Set Basics}
  303. In the section on modules, we designed a 'proof of concept' for a mail system. One feature we did not add was the ability for unreceived mail to be persistent. If the MUD ever crashed or rebooted, all mail not yet received would be lost. This, of course, is highly undesirable. We will now build on our mail module, and demonstrate how make mail persistent with the aid of storage sets.
  304. The first thing we will need to do is add the header for interacting with storage sets. Let's do this where we include all the other headers we need for interacting with core features of the MUD. We'll add the new header right after we add the {\it handler.h} header:
  305. {\bf \begin{verbatim}
  306. #include "../handler.h" // for giving mail to characters
  307. #include "../storage.h" // for saving/loading mail
  308. \end{verbatim}}
  309. Next, we will need to define a file where mail will be stored when the mud is down. The MUD's lib directory seems like an ideal candidate directory. Why don't we define where mail will be saved at the top of our {\it mail.c} file, where we define the hashtable for holding mail:
  310. % define the file we will be saving mail to
  311. {\bf \begin{verbatim}
  312. // this is the file we will save all unreceived mail in, when the mud is down
  313. #define MAIL_FILE "../lib/misc/mail"
  314. // maps charName to a list of mail they have received
  315. HASHTABLE *mail_table = NULL;
  316. \end{verbatim}}
  317. Two things we will need are functions for converting both ways between MAIL\_DATA and STORAGE\_SETS. By convention, these functions are called xxxStore and xxxRead, where xxx is what we are trying to convert to and from a STORAGE\_SET. Let's get those functions set up next, just below the {\it newMail} and {\it deleteMail} functions:
  318. % make our read and store functions
  319. {\bf \begin{verbatim}
  320. // parse a piece of mail from a storage set
  321. MAIL_DATA *mailRead(STORAGE_SET *set) {
  322. // allocate some memory for the mail
  323. MAIL_DATA *mail = malloc(sizeof(MAIL_DATA));
  324. mail->mssg = newBuffer(1);
  325. // read in all of our values
  326. mail->sender = strdup(read_string(set, "sender"));
  327. mail->time = strdup(read_string(set, "time"));
  328. bufferCat(mail->mssg, read_string(set, "mssg"));
  329. return mail;
  330. }
  331. // represent a piece of mail as a storage set
  332. STORAGE_SET *mailStore(MAIL_DATA *mail) {
  333. // create a new storage set
  334. STORAGE_SET *set = new_storage_set();
  335. store_string(set, "sender", mail->sender);
  336. store_string(set, "time", mail->time);
  337. store_string(set, "mssg", bufferString(mail->mssg));
  338. return set;
  339. }
  340. \end{verbatim}}
  341. Now that we have the ability to store and read mail, let's create two functions for actually doing the storing and reading:
  342. {\bf \begin{verbatim}
  343. // saves all of our unreceived mail to disk
  344. void save_mail(void) {
  345. // make a storage set to hold all our mail
  346. STORAGE_SET *set = new_storage_set();
  347. // make a list of name:mail pairs, and store it in the set
  348. STORAGE_SET_LIST *list = new_storage_list();
  349. // iterate across all of the people who have not received mail, and
  350. // store their names in the storage list, along with their mail
  351. HASH_ITERATOR *mail_i = newHashIterator(mail_table);
  352. const char *name = NULL;
  353. LIST *mail = NULL;
  354. ITERATE_HASH(name, mail, mail_i) {
  355. // create a new storage set that holds each name:mail pair,
  356. // and add it to our list of all name:mail pairs
  357. STORAGE_SET *one_pair = new_storage_set();
  358. store_string (one_pair, "name", name);
  359. store_list (one_pair, "mail", gen_store_list(mail, mailStore));
  360. storage_list_put(list, one_pair);
  361. } deleteHashIterator(mail_i);
  362. // make sure we add the list of name:mail pairs we want to save
  363. store_list(set, "list", list);
  364. // now, store our set in the mail file, and clean up our mess
  365. storage_write(set, MAIL_FILE);
  366. storage_close(set);
  367. }
  368. // loads all of our unreceived mail from disk
  369. void load_mail(void) {
  370. // parse our storage set
  371. STORAGE_SET *set = storage_read(MAIL_FILE);
  372. // make sure the file existed and wasn't empty
  373. if(set == NULL) return;
  374. // get the list of all name:mail pairs, and parse each one
  375. STORAGE_SET_LIST *list = read_list(set, "list");
  376. STORAGE_SET *one_pair = NULL;
  377. while( (one_pair = storage_list_next(list)) != NULL) {
  378. const char *name = read_string(one_pair, "name");
  379. LIST *mail = gen_read_list(read_list(one_pair, "mail"), mailRead);
  380. hashPut(mail_table, name, mail);
  381. }
  382. // Everything is parsed! Now it's time to clean up our mess
  383. storage_close(set);
  384. }
  385. \end{verbatim}}
  386. This code may be a bit ugly to the untrained eye. However, there are some very useful nuggets of knowledge buried within it. If you are having troubles understanding what is going on, it is highly suggested that you take a few minutes to trace through these two functions and figure out what is going on. Once we are completely done this section, it may also help to write a couple mails to yourself and examine what the mail file looks like. The file's structure might help elucidate many of the things that are going on in these two functions.
  387. Ok, we are on the home stretch. Now that our save and load functions are written, we have to make sure they are called appropriately. We will want to load up unread mail when the mail module initialized, and we will want to make sure we update the contents of the mail file whenever mail is sent or received. Why don't we add those bits of code.
  388. At the end of cmd\_mail, make sure mail is saved:
  389. {\bf \begin{verbatim}
  390. // let the character know we've sent the mail
  391. send_to_char(ch, "You send a message to %s.\r\n", arg);
  392. // save all unread mail
  393. save_mail();
  394. \end{verbatim}}
  395. At the end of cmd\_receive, make sure mail is saved:
  396. {\bf \begin{verbatim}
  397. // let the character know how much mail he received
  398. send_to_char(ch, "You receive %d letter%s.\r\n",
  399. listSize(mail_list), (listSize(mail_list) == 1 ? "" : "s"));
  400. // update the unread mail in our mail file
  401. save_mail();
  402. \end{verbatim}}
  403. Finally, ensure we load up all unread mail when we initialize the module:
  404. {\bf \begin{verbatim}
  405. // boot up the mail module
  406. void init_mail(void) {
  407. // initialize our mail table
  408. mail_table = newHashtable();
  409. // parse any unread mail
  410. load_mail();
  411. \end{verbatim}}
  412. That's it! Unreceived mail will now be persistent across reboots and crashes. You may have noticed that saving of mail is rather inefficient; every time someone receives a mail or sends a mail, we have to re-save all unreceived mails. Ideally, we would like to change it so we have to re-save as little information as possible. That is the problem we will tackle in the section on auxiliary data.
  413. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  414. % Subsection: Summary
  415. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  416. \subsection{Summary}
  417. In this section, we have learned the basics of storage sets. Storage sets can be a bit tricky to understand at first. If you are having troubles completely understanding the code presented in this section, it is strongly suggested you spend some time reading over it and understanding how it works. It may also help looking at the files that storage sets generate after one is written to disk. If you plan on adding anything new to NakedMud, it is almost mandatory that you understand how storage sets work. However, Once you figure out how they work, you will be glad you did. They are very helpful data structures.
  418. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  419. % Auxiliary Data
  420. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  421. \newpage \section{Auxiliary Data}
  422. Auxiliary Data is, perhaps, the most important part of NakedMud's design. Auxiliary Data allows you to add new variables to the various datatypes NakedMud handles within the game (objects, rooms, mobiles, accounts, sockets) without even touching the files that house those data structures. The biggest gain from this is the ability to modularize your code by what it is intended to do; all of the code related to combat - including new variables that must be created - can stay in one module. As was mentioned in the introduction the modules section, this will undoubtedly help with your ability to debug, maintain, and distribute pieces of your code in the future. Designing new auxiliary data is very simple, but it does require a bit of effort if you have not done it in the past. This tutorial will walk you through the steps of writing and installing new auxiliary data.
  423. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  424. % Subsection: Getting Up To Speed
  425. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  426. \subsection{Getting Up To Speed}
  427. If you have already completed the tutorial on modules, and that is the only tutorial in this guide you have performed, you can skip this subsection, as it does not apply to you.
  428. If you have completed the tutorial on storage sets, you will have to make some modifications to your code. As we discussed at the end of the storage sets section, the way saving is performed is rather inefficient, and we will attempt to address that problem within this section. {\it You will need to replace your mail.c file with the mail.c found in appendix A}.
  429. If you have not yet completed any of the tutorials in this guide, you will need to do a couple things before you can go through the auxiliary data tutorial. All of the steps you must take are outlined in section 3.1 - the {\it Getting Up To Speed} section for storage sets.
  430. Once you have figured out which set of changes apply to you, carry on with the next subsection in the auxiliary data tutorial.
  431. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  432. % Subsection: Auxiliary Data Basics
  433. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  434. \subsection{Auxiliary Data Basics}
  435. In the section on modules, we designed a 'proof of concept' for a mail system. One feature we did not add was the ability for unreceived mail to be persistent. If the MUD ever crashed or rebooted, all mail not yet received would be lost. This inconvenience was addressed in the section on storage sets. However, we ran into another problem with the saving procedure being inefficient: to save any change to someone's unreceived mail status, we effectively had to re-save everyone's unreceived mail. This section will address the problem by attaching a character's unread mail to that character's actual data structure. Now, whenever we want to the unread mail for a character, we will only have to save that character, and not all unread mail in existence.
  436. The first thing we will need to do is add the headers for interacting with auxiliary data and storage sets. Let's do this where we include all the other headers we need for interacting with core features of the MUD. We'll add the new headers right after we add the {\it handler.h} header:
  437. {\bf \begin{verbatim}
  438. #include "../handler.h" // for giving mail to characters
  439. #include "../storage.h" // for saving/loading auxiliary data
  440. #include "../auxiliary.h" // for creating new auxiliary data
  441. #include "../world.h" // for loading offline chars receiving mail
  442. \end{verbatim}}
  443. Because we will save unread mail as auxiliary data within character data, we will no longer need a hashtable for keeping everything stored. Therefore, let us search and destroy all references to the mail\_table:
  444. Right after we finish including all of our headers, delete:
  445. {\bf \begin{verbatim}
  446. // maps charName to a list of mail they have received
  447. HASHTABLE *mail_table = NULL;
  448. \end{verbatim}}
  449. In cmd\_mail, delete the entire section within the last else statement:
  450. {\bf \begin{verbatim}
  451. MAIL_DATA *mail = newMail(ch, bufferString(socketGetNotepad(charGetSocket(ch))));
  452. // see if the receiver already has a mail list
  453. LIST *mssgs = hashGet(mail_table, arg);
  454. // if he doesn't, create one and add it to the hashtable
  455. if(mssgs == NULL) {
  456. mssgs = newList();
  457. hashPut(mail_table, arg, mssgs);
  458. }
  459. // add the new mail to our mail list
  460. listPut(mssgs, mail);
  461. // let the character know we've sent the mail
  462. send_to_char(ch, "You send a message to %s.\r\n", arg);
  463. \end{verbatim}}
  464. At the very start of cmd\_receive, remove the reference to hashRemove, and for the time being, set mail\_list to NULL:
  465. {\bf \begin{verbatim}
  466. // Remove the character's mail list from our mail table
  467. LIST *mail_list = hashRemove(mail_table, charGetName(ch));
  468. \end{verbatim}}
  469. Finally, remove our creation of the mail\_table in init\_mail:
  470. {\bf \begin{verbatim}
  471. // initialize our mail table
  472. mail_table = newHashtable();
  473. \end{verbatim}}
  474. Before we start writing our auxiliary data, we are going to have to provide a couple functions for handling the saving, reading, and copying of mail. Right after newMail and deleteMail, add these 3 new functions:
  475. {\bf \begin{verbatim}
  476. // parse a piece of mail from a storage set
  477. MAIL_DATA *mailRead(STORAGE_SET *set) {
  478. // allocate some memory for the mail
  479. MAIL_DATA *mail = malloc(sizeof(MAIL_DATA));
  480. mail->mssg = newBuffer(1);
  481. // read in all of our values
  482. mail->sender = strdup(read_string(set, "sender"));
  483. mail->time = strdup(read_string(set, "time"));
  484. bufferCat(mail->mssg, read_string(set, "mssg"));
  485. return mail;
  486. }
  487. // represent a piece of mail as a storage set
  488. STORAGE_SET *mailStore(MAIL_DATA *mail) {
  489. // create a new storage set
  490. STORAGE_SET *set = new_storage_set();
  491. store_string(set, "sender", mail->sender);
  492. store_string(set, "time", mail->time);
  493. store_string(set, "mssg", bufferString(mail->mssg));
  494. return set;
  495. }
  496. // copy a piece of mail. This will be needed by our auxiliary
  497. // data copy functions
  498. MAIL_DATA *mailCopy(MAIL_DATA *mail) {
  499. MAIL_DATA *newmail = malloc(sizeof(MAIL_DATA));
  500. newmail->sender = strdup(mail->sender);
  501. newmail->time = strdup(mail->time);
  502. newmail->mssg = bufferCopy(mail->mssg);
  503. return newmail;
  504. }
  505. \end{verbatim}}
  506. Now we're ready to start writing our auxiliary data. Auxiliary Data require 7 things: a new structure that is the auxiliary data, a function that constructs the auxiliary data, a function that deletes the auxiliary data, a function that copies the auxiliary data, a function that copies the auxiliary data to another instance of the same auxiliary data, a function that reads the auxiliary data from a storage set, and a function that writes the auxiliary data to a storage set. Below, we lay out all of those things. There is quite a bit of code but, as you will notice, it is all very simple (perhaps with the exception of the read and write functions). Right below the the mailCopy function, add the following bit of code:
  507. {\bf \begin{verbatim}
  508. // our mail auxiliary data.
  509. // Holds a list of all the unreceived mail a person has
  510. typedef struct {
  511. LIST *mail; // our list of unread mail
  512. } MAIL_AUX_DATA;
  513. // create a new instance of mail aux data, for us to put onto a character
  514. MAIL_AUX_DATA *newMailAuxData(void) {
  515. MAIL_AUX_DATA *data = malloc(sizeof(MAIL_AUX_DATA));
  516. data->mail = newList();
  517. return data;
  518. }
  519. // delete a character's mail aux data
  520. void deleteMailAuxData(MAIL_AUX_DATA *data) {
  521. if(data->mail) deleteListWith(data->mail, deleteMail);
  522. free(data);
  523. }
  524. // copy one mail aux data to another
  525. void mailAuxDataCopyTo(MAIL_AUX_DATA *from, MAIL_AUX_DATA *to) {
  526. if(to->mail) deleteListWith(to->mail, deleteMail);
  527. if(from->mail) to->mail = listCopyWith(from->mail, mailCopy);
  528. else to->mail = newList();
  529. }
  530. // return a copy of a mail aux data
  531. MAIL_AUX_DATA *mailAuxDataCopy(MAIL_AUX_DATA *data) {
  532. MAIL_AUX_DATA *newdata = newMailAuxData();
  533. mailAuxDataCopyTo(data, newdata);
  534. return newdata;
  535. }
  536. // parse a mail aux data from a storage set
  537. MAIL_AUX_DATA *mailAuxDataRead(STORAGE_SET *set) {
  538. MAIL_AUX_DATA *data = malloc(sizeof(MAIL_AUX_DATA));
  539. data->mail = gen_read_list(read_list(set, "mail"), mailRead);
  540. return data;
  541. }
  542. // represent a mail aux data as a storage set
  543. STORAGE_SET *mailAuxDataStore(MAIL_AUX_DATA *data) {
  544. STORAGE_SET *set = new_storage_set();
  545. store_list(set, "mail", gen_store_list(data->mail, mailStore));
  546. return set;
  547. }
  548. \end{verbatim}}
  549. Finally, we will want to ensure this new auxiliary data exists on characters. When our mail module boots up, we will want to install this new bit of auxiliary data:
  550. {\bf \begin{verbatim}
  551. // boot up the mail module
  552. void init_mail(void) {
  553. // install our auxiliary data
  554. auxiliariesInstall("mail_aux_data",
  555. newAuxiliaryFuncs(AUXILIARY_TYPE_CHAR,
  556. newMailAuxData, deleteMailAuxData,
  557. mailAuxDataCopyTo, mailAuxDataCopy,
  558. mailAuxDataStore, mailAuxDataRead));
  559. // add all of the commands that come with this module
  560. add_cmd("mail", NULL, cmd_mail, POS_STANDING, POS_FLYING,
  561. "player", FALSE, TRUE);
  562. add_cmd("receive", NULL, cmd_receive, POS_STANDING, POS_FLYING,
  563. "player", FALSE, TRUE);
  564. }
  565. \end{verbatim}}
  566. Our mail auxiliary data is installed and completely functional! Now, we will want to go back to cmd\_mail and cmd\_receive to ensure they use the auxiliary data in replacement of the hashtable we used before. Let's start with the new code for cmd\_mail. In the last else block, add the following bit of code:
  567. {\bf \begin{verbatim}
  568. // create the new piece of mail
  569. MAIL_DATA *mail = newMail(ch, bufferString(socketGetNotepad(charGetSocket(ch))));
  570. // get a copy of the player, send mail, and save
  571. CHAR_DATA *recv = get_player(arg);
  572. send_to_char(recv, "You have new mail.\r\n");
  573. // let's pull out the character's mail aux data, and add the new piece
  574. MAIL_AUX_DATA *maux = charGetAuxiliaryData(recv, "mail_aux_data");
  575. listPut(maux->mail, mail);
  576. save_player(recv);
  577. // get rid of our reference, and extract from game if need be
  578. unreference_player(recv);
  579. // let the character know we've sent the mail
  580. send_to_char(ch, "You send a message to %s.\r\n", arg);
  581. \end{verbatim}}
  582. Now, we will want to make it so players can receive their unread mail. At the very start of cmd\_receive, add the following bit of code:
  583. {\bf \begin{verbatim}
  584. COMMAND(cmd_receive) {
  585. // Remove the character's mail list from our mail table
  586. MAIL_AUX_DATA *maux = charGetAuxiliaryData(ch, "mail_aux_data");
  587. LIST *mail_list = maux->mail;
  588. // replace our old list with a new one. Our old one will be deleted soon
  589. maux->mail = newList();
  590. \end{verbatim}}
  591. There, we're done! Players can now send and receive mail.
  592. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  593. % Subsection: Summary
  594. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  595. \subsection{Summary}
  596. In this section, we have learned the basics of using auxiliary data. In our tutorial on storage sets, we were able to make unreceived mail persistent, but loading and saving was very inefficient. In this tutorial, we addressed the efficiency problem by saving unread mail on the character the mail belongs to. Auxiliary Data does require a bit of programming, but once you get the hang of it, you will realize it is very routine programming - the bulk of which you can probably copy from a template like the one provided in this tutorial or any other module that employs the use of auxiliary data.
  597. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  598. % Conclusion
  599. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  600. \newpage \section{Conclusion}
  601. Throughout this manual, we have learned the basics of modules, storage sets, and auxiliary data - three fundamental aspects of NakedMud. With a bit more practical experience, you will know all there is to know about these three topics. Hopefully this tutorial has helped you understand enough of the basics so that you will not have too much trouble extending NakedMud with new game content.
  602. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  603. % Switching to Appendices
  604. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  605. \appendix
  606. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  607. % Appendix A: Mail Module Code, First Draft
  608. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  609. \newpage \section{Mail Module Code, First Draft}
  610. {\bf \begin{verbatim}
  611. ################################################################################
  612. # module.mk
  613. ################################################################################
  614. # include all of the source files contained in this module
  615. SRC += mail/mail.c
  616. //*****************************************************************************
  617. // mail.h
  618. //*****************************************************************************
  619. #ifndef MAIL_H
  620. #define MAIL_H
  621. // this function should be called when the MUD first boots up.
  622. // calling it will initialize the mail module for use.
  623. void init_mail(void);
  624. #endif // MAIL_H
  625. //*****************************************************************************
  626. // mail.c
  627. //*****************************************************************************
  628. // include all the header files we will need from the MUD core
  629. #include "../mud.h"
  630. #include "../utils.h" // for get_time()
  631. #include "../character.h" // for handling characters sending mail
  632. #include "../save.h" // for char_exists()
  633. #include "../object.h" // for creating mail objects
  634. #include "../handler.h" // for giving mail to characters
  635. // include headers from other modules that we require
  636. #include "../editor/editor.h" // for access to sockets' notepads
  637. // include the headers for this module
  638. #include "mail.h"
  639. // maps charName to a list of mail they have received
  640. HASHTABLE *mail_table = NULL;
  641. typedef struct {
  642. char *sender; // name of the char who sent this mail
  643. char *time; // the time it was sent at
  644. BUFFER *mssg; // the accompanying message
  645. } MAIL_DATA;
  646. // create a new piece of mail.
  647. MAIL_DATA *newMail(CHAR_DATA *sender, const char *mssg) {
  648. MAIL_DATA *mail = malloc(sizeof(MAIL_DATA));
  649. mail->sender = strdup(charGetName(sender));
  650. mail->time = strdup(get_time());
  651. mail->mssg = newBuffer(strlen(mssg));
  652. bufferCat(mail->mssg, mssg);
  653. return mail;
  654. }
  655. // free all of the memory that was allocated to make this piece of mail
  656. void deleteMail(MAIL_DATA *mail) {
  657. if(mail->sender) free(mail->sender);
  658. if(mail->time) free(mail->time);
  659. if(mail->mssg) deleteBuffer(mail->mssg);
  660. free(mail);
  661. }
  662. // mails a message to the specified person. This command must take the
  663. // following form:
  664. // mail <person>
  665. //
  666. // The contents of the character's notepad will be used as the body of the
  667. // message. The character's notepad must not be empty.
  668. COMMAND(cmd_mail) {
  669. // make sure the character exists
  670. if(!char_exists(arg))
  671. send_to_char(ch, "Noone named %s is registered on %s.\r\n",
  672. arg, "<insert mud name here>");
  673. // make sure we have a socket - we'll need access to its notepad
  674. else if(!charGetSocket(ch))
  675. send_to_char(ch, "Only characters with sockets can send mail!\r\n");
  676. // make sure our notepad is not empty
  677. else if(!*bufferString(socketGetNotepad(charGetSocket(ch))))
  678. send_to_char(ch, "Your notepad is empty. "
  679. "First, try writing something with {cwrite{n.\r\n");
  680. // the character exists. Let's parse the items and send the mail
  681. else {
  682. MAIL_DATA *mail = newMail(ch, bufferString(socketGetNotepad(charGetSocket(ch))));
  683. // see if the receiver already has a mail list
  684. LIST *mssgs = hashGet(mail_table, arg);
  685. // if he doesn't, create one and add it to the hashtable
  686. if(mssgs == NULL) {
  687. mssgs = newList();
  688. hashPut(mail_table, arg, mssgs);
  689. }
  690. // add the new mail to our mail list
  691. listPut(mssgs, mail);
  692. // let the character know we've sent the mail
  693. send_to_char(ch, "You send a message to %s.\r\n", arg);
  694. }
  695. }
  696. // checks to see if the character has any mail. If he does, convert each piece
  697. // of mail into an object, and transfer them all into the character's inventory.
  698. COMMAND(cmd_receive) {
  699. // Remove the character's mail list from our mail table
  700. LIST *mail_list = hashRemove(mail_table, charGetName(ch));
  701. // make sure the list exists
  702. if(mail_list == NULL || listSize(mail_list) == 0)
  703. send_to_char(ch, "You have no new mail.\r\n");
  704. // hand over all of the mail
  705. else {
  706. // go through each piece of mail, make an object for it,
  707. // and transfer the new object to us
  708. LIST_ITERATOR *mail_i = newListIterator(mail_list);
  709. MAIL_DATA *mail = NULL;
  710. ITERATE_LIST(mail, mail_i) {
  711. OBJ_DATA *obj = newObj();
  712. objSetName (obj, "a letter");
  713. objSetKeywords (obj, "letter, mail");
  714. objSetRdesc (obj, "A letter is here.");
  715. objSetMultiName (obj, "A stack of %d letters");
  716. objSetMultiRdesc(obj, "A stack of %d letters are here.");
  717. bprintf(objGetDescBuffer(obj),
  718. "Sender : %s\r\n"
  719. "Date sent: %s\r\n"
  720. "%s", mail->sender, mail->time, bufferString(mail->mssg));
  721. // give the object to the character
  722. obj_to_game(obj);
  723. obj_to_char(obj, ch);
  724. } deleteListIterator(mail_i);
  725. // let the character know how much mail he received
  726. send_to_char(ch, "You receive %d letter%s.\r\n",
  727. listSize(mail_list), (listSize(mail_list) == 1 ? "" : "s"));
  728. }
  729. // delete the mail list, and all of its contents
  730. if(mail_list != NULL) deleteListWith(mail_list, deleteMail);
  731. }
  732. // boot up the mail module
  733. void init_mail(void) {
  734. // initialize our mail table
  735. mail_table = newHashtable();
  736. // add all of the commands that come with this module
  737. add_cmd("mail", NULL, cmd_mail, POS_STANDING, POS_FLYING,
  738. "player", FALSE, TRUE);
  739. add_cmd("receive", NULL, cmd_receive, POS_STANDING, POS_FLYING,
  740. "player", FALSE, TRUE);
  741. }
  742. \end{verbatim}}
  743. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  744. % Appendix B: Mail Module Code, Second Draft
  745. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  746. \newpage \section{Mail Module Code, Second Draft}
  747. {\bf \begin{verbatim}
  748. ################################################################################
  749. # module.mk
  750. ################################################################################
  751. # include all of the source files contained in this module
  752. SRC += mail/mail.c
  753. //*****************************************************************************
  754. // mail.h
  755. //*****************************************************************************
  756. #ifndef MAIL_H
  757. #define MAIL_H
  758. // this function should be called when the MUD first boots up.
  759. // calling it will initialize the mail module for use.
  760. void init_mail(void);
  761. #endif // MAIL_H
  762. //*****************************************************************************
  763. // mail.c
  764. //*****************************************************************************
  765. // include all the header files we will need from the MUD core
  766. #include "../mud.h"
  767. #include "../utils.h" // for get_time()
  768. #include "../character.h" // for handling characters sending mail
  769. #include "../save.h" // for char_exists()
  770. #include "../object.h" // for creating mail objects
  771. #include "../handler.h" // for giving mail to characters
  772. #include "../storage.h" // for saving/loading mail
  773. // include headers from other modules that we require
  774. #include "../editor/editor.h" // for access to sockets' notepads
  775. // include the headers for this module
  776. #include "mail.h"
  777. // this is the file we will save all unreceived mail in, when the mud is down
  778. #define MAIL_FILE "../lib/misc/mail"
  779. // maps charName to a list of mail they have received
  780. HASHTABLE *mail_table = NULL;
  781. typedef struct {
  782. char *sender; // name of the char who sent this mail
  783. char *time; // the time it was sent at
  784. BUFFER *mssg; // the accompanying message
  785. } MAIL_DATA;
  786. // create a new piece of mail.
  787. MAIL_DATA *newMail(CHAR_DATA *sender, const char *mssg) {
  788. MAIL_DATA *mail = malloc(sizeof(MAIL_DATA));
  789. mail->sender = strdup(charGetName(sender));
  790. mail->time = strdup(get_time());
  791. mail->mssg = newBuffer(strlen(mssg));
  792. bufferCat(mail->mssg, mssg);
  793. return mail;
  794. }
  795. // free all of the memory that was allocated to make this piece of mail
  796. void deleteMail(MAIL_DATA *mail) {
  797. if(mail->sender) free(mail->sender);
  798. if(mail->time) free(mail->time);
  799. if(mail->mssg) deleteBuffer(mail->mssg);
  800. free(mail);
  801. }
  802. // parse a piece of mail from a storage set
  803. MAIL_DATA *mailRead(STORAGE_SET *set) {
  804. // allocate some memory for the mail
  805. MAIL_DATA *mail = malloc(sizeof(MAIL_DATA));
  806. mail->mssg = newBuffer(1);
  807. // read in all of our values
  808. mail->sender = strdup(read_string(set, "sender"));
  809. mail->time = strdup(read_string(set, "time"));
  810. bufferCat(mail->mssg, read_string(set, "mssg"));
  811. return mail;
  812. }
  813. // represent a piece of mail as a storage set
  814. STORAGE_SET *mailStore(MAIL_DATA *mail) {
  815. // create a new storage set
  816. STORAGE_SET *set = new_storage_set();
  817. store_string(set, "sender", mail->sender);
  818. store_string(set, "time", mail->time);
  819. store_string(set, "mssg", bufferString(mail->mssg));
  820. return set;
  821. }
  822. // saves all of our unreceived mail to disk
  823. void save_mail(void) {
  824. // make a storage set to hold all our mail
  825. STORAGE_SET *set = new_storage_set();
  826. // make a list of name:mail pairs, and store it in the set
  827. STORAGE_SET_LIST *list = new_storage_list();
  828. // iterate across all of the people who have not received mail, and
  829. // store their names in the storage list, along with their mail
  830. HASH_ITERATOR *mail_i = newHashIterator(mail_table);
  831. const char *name = NULL;
  832. LIST *mail = NULL;
  833. ITERATE_HASH(name, mail, mail_i) {
  834. // create a new storage set that holds each name:mail pair,
  835. // and add it to our list of all name:mail pairs
  836. STORAGE_SET *one_pair = new_storage_set();
  837. store_string (one_pair, "name", name);
  838. store_list (one_pair, "mail", gen_store_list(mail, mailStore));
  839. storage_list_put(list, one_pair);
  840. } deleteHashIterator(mail_i);
  841. // make sure we add the list of name:mail pairs we want to save
  842. store_list(set, "list", list);
  843. // now, store our set in the mail file, and clean up our mess
  844. storage_write(set, MAIL_FILE);
  845. storage_close(set);
  846. }
  847. // loads all of our unreceived mail from disk
  848. void load_mail(void) {
  849. // parse our storage set
  850. STORAGE_SET *set = storage_read(MAIL_FILE);
  851. // make sure the file existed and wasn't empty
  852. if(set == NULL) return;
  853. // get the list of all name:mail pairs, and parse each one
  854. STORAGE_SET_LIST *list = read_list(set, "list");
  855. STORAGE_SET *one_pair = NULL;
  856. while( (one_pair = storage_list_next(list)) != NULL) {
  857. const char *name = read_string(one_pair, "name");
  858. LIST *mail = gen_read_list(read_list(one_pair, "mail"), mailRead);
  859. hashPut(mail_table, name, mail);
  860. }
  861. // Everything is parsed! Now it's time to clean up our mess
  862. storage_close(set);
  863. }
  864. // mails a message to the specified person. This command must take the
  865. // following form:
  866. // mail <person>
  867. //
  868. // The contents of the character's notepad will be used as the body of the
  869. // message. The character's notepad must not be empty.
  870. COMMAND(cmd_mail) {
  871. // make sure the character exists
  872. if(!char_exists(arg))
  873. send_to_char(ch, "Noone named %s is registered on %s.\r\n",
  874. arg, "<insert mud name here>");
  875. // make sure we have a socket - we'll need access to its notepad
  876. else if(!charGetSocket(ch))
  877. send_to_char(ch, "Only characters with sockets can send mail!\r\n");
  878. // make sure our notepad is not empty
  879. else if(!*bufferString(socketGetNotepad(charGetSocket(ch))))
  880. send_to_char(ch, "Your notepad is empty. "
  881. "First, try writing something with {cwrite{n.\r\n");
  882. // the character exists. Let's parse the items and send the mail
  883. else {
  884. MAIL_DATA *mail = newMail(ch, bufferString(socketGetNotepad(charGetSocket(ch))));
  885. // see if the receiver already has a mail list
  886. LIST *mssgs = hashGet(mail_table, arg);
  887. // if he doesn't, create one and add it to the hashtable
  888. if(mssgs == NULL) {
  889. mssgs = newList();
  890. hashPut(mail_table, arg, mssgs);
  891. }
  892. // add the new mail to our mail list
  893. listPut(mssgs, mail);
  894. // let the character know we've sent the mail
  895. send_to_char(ch, "You send a message to %s.\r\n", arg);
  896. // save all unread mail
  897. save_mail();
  898. }
  899. }
  900. // checks to see if the character has any mail. If he does, convert each piece
  901. // of mail into an object, and transfer them all into the character's inventory.
  902. COMMAND(cmd_receive) {
  903. // Remove the character's mail list from our mail table
  904. LIST *mail_list = hashRemove(mail_table, charGetName(ch));
  905. // make sure the list exists
  906. if(mail_list == NULL || listSize(mail_list) == 0)
  907. send_to_char(ch, "You have no new mail.\r\n");
  908. // hand over all of the mail
  909. else {
  910. // go through each piece of mail, make an object for it,
  911. // and transfer the new object to us
  912. LIST_ITERATOR *mail_i = newListIterator(mail_list);
  913. MAIL_DATA *mail = NULL;
  914. ITERATE_LIST(mail, mail_i) {
  915. OBJ_DATA *obj = newObj();
  916. objSetName (obj, "a letter");
  917. objSetKeywords (obj, "letter, mail");
  918. objSetRdesc (obj, "A letter is here.");
  919. objSetMultiName (obj, "A stack of %d letters");
  920. objSetMultiRdesc(obj, "A stack of %d letters are here.");
  921. bprintf(objGetDescBuffer(obj),
  922. "Sender : %s\r\n"
  923. "Date sent: %s\r\n"
  924. "%s", mail->sender, mail->time, bufferString(mail->mssg));
  925. // give the object to the character
  926. obj_to_game(obj);
  927. obj_to_char(obj, ch);
  928. } deleteListIterator(mail_i);
  929. // let the character know how much mail he received
  930. send_to_char(ch, "You receive %d letter%s.\r\n",
  931. listSize(mail_list), (listSize(mail_list) == 1 ? "" : "s"));
  932. // update the unread mail in our mail file
  933. save_mail();
  934. }
  935. // delete the mail list, and all of its contents
  936. if(mail_list != NULL) deleteListWith(mail_list, deleteMail);
  937. }
  938. // boot up the mail module
  939. void init_mail(void) {
  940. // initialize our mail table
  941. mail_table = newHashtable();
  942. // parse any unread mail
  943. load_mail();
  944. // add all of the commands that come with this module
  945. add_cmd("mail", NULL, cmd_mail, POS_STANDING, POS_FLYING,
  946. "player", FALSE, TRUE);
  947. add_cmd("receive", NULL, cmd_receive, POS_STANDING, POS_FLYING,
  948. "player", FALSE, TRUE);
  949. }
  950. \end{verbatim}}
  951. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  952. % Appendix C: Mail Module Code, Third Draft
  953. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  954. \newpage \section{Mail Module Code, Third Draft}
  955. {\bf \begin{verbatim}
  956. ################################################################################
  957. # module.mk
  958. ################################################################################
  959. # include all of the source files contained in this module
  960. SRC += mail/mail.c
  961. //*****************************************************************************
  962. // mail.h
  963. //*****************************************************************************
  964. #ifndef MAIL_H
  965. #define MAIL_H
  966. // this function should be called when the MUD first boots up.
  967. // calling it will initialize the mail module for use.
  968. void init_mail(void);
  969. #endif // MAIL_H
  970. //*****************************************************************************
  971. // mail.c
  972. //*****************************************************************************
  973. // include all the header files we will need from the MUD core
  974. #include "../mud.h"
  975. #include "../utils.h" // for get_time()
  976. #include "../character.h" // for handling characters sending mail
  977. #include "../save.h" // for char_exists()
  978. #include "../object.h" // for creating mail objects
  979. #include "../handler.h" // for giving mail to characters
  980. #include "../storage.h" // for saving/loading auxiliary data
  981. #include "../auxiliary.h" // for creating new auxiliary data
  982. #include "../world.h" // for loading offline chars receiving mail
  983. // include headers from other modules that we require
  984. #include "../editor/editor.h" // for access to sockets' notepads
  985. // include the headers for this module
  986. #include "mail.h"
  987. // maps charName to a list of mail they have received
  988. HASHTABLE *mail_table = NULL;
  989. typedef struct {
  990. char *sender; // name of the char who sent this mail
  991. char *time; // the time it was sent at
  992. BUFFER *mssg; // the accompanying message
  993. } MAIL_DATA;
  994. // create a new piece of mail.
  995. MAIL_DATA *newMail(CHAR_DATA *sender, const char *mssg) {
  996. MAIL_DATA *mail = malloc(sizeof(MAIL_DATA));
  997. mail->sender = strdup(charGetName(sender));
  998. mail->time = strdup(get_time());
  999. mail->mssg = newBuffer(strlen(mssg));
  1000. bufferCat(mail->mssg, mssg);
  1001. return mail;
  1002. }
  1003. // free all of the memory that was allocated to make this piece of mail
  1004. void deleteMail(MAIL_DATA *mail) {
  1005. if(mail->sender) free(mail->sender);
  1006. if(mail->time) free(mail->time);
  1007. if(mail->mssg) deleteBuffer(mail->mssg);
  1008. free(mail);
  1009. }
  1010. // parse a piece of mail from a storage set
  1011. MAIL_DATA *mailRead(STORAGE_SET *set) {
  1012. // allocate some memory for the mail
  1013. MAIL_DATA *mail = malloc(sizeof(MAIL_DATA));
  1014. mail->mssg = newBuffer(1);
  1015. // read in all of our values
  1016. mail->sender = strdup(read_string(set, "sender"));
  1017. mail->time = strdup(read_string(set, "time"));
  1018. bufferCat(mail->mssg, read_string(set, "mssg"));
  1019. return mail;
  1020. }
  1021. // represent a piece of mail as a storage set
  1022. STORAGE_SET *mailStore(MAIL_DATA *mail) {
  1023. // create a new storage set
  1024. STORAGE_SET *set = new_storage_set();
  1025. store_string(set, "sender", mail->sender);
  1026. store_string(set, "time", mail->time);
  1027. store_string(set, "mssg", bufferString(mail->mssg));
  1028. return set;
  1029. }
  1030. // copy a piece of mail. This will be needed by our auxiliary
  1031. // data copy functions
  1032. MAIL_DATA *mailCopy(MAIL_DATA *mail) {
  1033. MAIL_DATA *newmail = malloc(sizeof(MAIL_DATA));
  1034. newmail->sender = strdup(mail->sender);
  1035. newmail->time = strdup(mail->time);
  1036. newmail->mssg = bufferCopy(mail->mssg);
  1037. return newmail;
  1038. }
  1039. // our mail auxiliary data.
  1040. // Holds a list of all the unreceived mail a person has
  1041. typedef struct {
  1042. LIST *mail; // our list of unread mail
  1043. } MAIL_AUX_DATA;
  1044. // create a new instance of mail aux data, for us to put onto a character
  1045. MAIL_AUX_DATA *newMailAuxData(void) {
  1046. MAIL_AUX_DATA *data = malloc(sizeof(MAIL_AUX_DATA));
  1047. data->mail = newList();
  1048. return data;
  1049. }
  1050. // delete a character's mail aux data
  1051. void deleteMailAuxData(MAIL_AUX_DATA *data) {
  1052. if(data->mail) deleteListWith(data->mail, deleteMail);
  1053. free(data);
  1054. }
  1055. // copy one mail aux data to another
  1056. void mailAuxDataCopyTo(MAIL_AUX_DATA *from, MAIL_AUX_DATA *to) {
  1057. if(to->mail) deleteListWith(to->mail, deleteMail);
  1058. if(from->mail) to->mail = listCopyWith(from->mail, mailCopy);
  1059. else to->mail = newList();
  1060. }
  1061. // return a copy of a mail aux data
  1062. MAIL_AUX_DATA *mailAuxDataCopy(MAIL_AUX_DATA *data) {
  1063. MAIL_AUX_DATA *newdata = newMailAuxData();
  1064. mailAuxDataCopyTo(data, newdata);
  1065. return newdata;
  1066. }
  1067. // parse a mail aux data from a storage set
  1068. MAIL_AUX_DATA *mailAuxDataRead(STORAGE_SET *set) {
  1069. MAIL_AUX_DATA *data = malloc(sizeof(MAIL_AUX_DATA));
  1070. data->mail = gen_read_list(read_list(set, "mail"), mailRead);
  1071. return data;
  1072. }
  1073. // represent a mail aux data as a storage set
  1074. STORAGE_SET *mailAuxDataStore(MAIL_AUX_DATA *data) {
  1075. STORAGE_SET *set = new_storage_set();
  1076. store_list(set, "mail", gen_store_list(data->mail, mailStore));
  1077. return set;
  1078. }
  1079. // mails a message to the specified person. This command must take the
  1080. // following form:
  1081. // mail <person>
  1082. //
  1083. // The contents of the character's notepad will be used as the body of the
  1084. // message. The character's notepad must not be empty.
  1085. COMMAND(cmd_mail) {
  1086. // make sure the character exists
  1087. if(!char_exists(arg))
  1088. send_to_char(ch, "Noone named %s is registered on %s.\r\n",
  1089. arg, "<insert mud name here>");
  1090. // make sure we have a socket - we'll need access to its notepad
  1091. else if(!charGetSocket(ch))
  1092. send_to_char(ch, "Only characters with sockets can send mail!\r\n");
  1093. // make sure our notepad is not empty
  1094. else if(!*bufferString(socketGetNotepad(charGetSocket(ch))))
  1095. send_to_char(ch, "Your notepad is empty. "
  1096. "First, try writing something with {cwrite{n.\r\n");
  1097. // the character exists. Let's parse the items and send the mail
  1098. else {
  1099. // create the new piece of mail
  1100. MAIL_DATA *mail = newMail(ch, bufferString(socketGetNotepad(charGetSocket(ch))));
  1101. // get a copy of the player, send mail, and save
  1102. CHAR_DATA *recv = get_player(arg);
  1103. send_to_char(recv, "You have new mail.\r\n");
  1104. // let's pull out the character's mail aux data, and add the new piece
  1105. MAIL_AUX_DATA *maux = charGetAuxiliaryData(recv, "mail_aux_data");
  1106. listPut(maux->mail, mail);
  1107. save_player(recv);
  1108. // get rid of our reference, and extract from game if need be
  1109. unreference_player(recv);
  1110. // let the character know we've sent the mail
  1111. send_to_char(ch, "You send a message to %s.\r\n", arg);
  1112. }
  1113. }
  1114. // checks to see if the character has any mail. If he does, convert each piece
  1115. // of mail into an object, and transfer them all into the character's inventory.
  1116. COMMAND(cmd_receive) {
  1117. // Remove the character's mail list from our mail table
  1118. MAIL_AUX_DATA *maux = charGetAuxiliaryData(ch, "mail_aux_data");
  1119. LIST *mail_list = maux->mail;
  1120. // replace our old list with a new one. Our old one will be deleted soon
  1121. maux->mail = newList();
  1122. // make sure the list exists
  1123. if(mail_list == NULL || listSize(mail_list) == 0)
  1124. send_to_char(ch, "You have no new mail.\r\n");
  1125. // hand over all of the mail
  1126. else {
  1127. // go through each piece of mail, make an object for it,
  1128. // and transfer the new object to us
  1129. LIST_ITERATOR *mail_i = newListIterator(mail_list);
  1130. MAIL_DATA *mail = NULL;
  1131. ITERATE_LIST(mail, mail_i) {
  1132. OBJ_DATA *obj = newObj();
  1133. BUFFER *desc = newBuffer(1);
  1134. objSetName (obj, "a letter");
  1135. objSetKeywords (obj, "letter, mail");
  1136. objSetRdesc (obj, "A letter is here.");
  1137. objSetMultiName (obj, "A stack of %d letters");
  1138. objSetMultiRdesc(obj, "A stack of %d letters are here.");
  1139. // make our description
  1140. bprintf(desc,
  1141. "Sender : %s\r\n"
  1142. "Date sent: %s\r\n"
  1143. "%s", mail->sender, mail->time, bufferString(mail->mssg));
  1144. objSetDesc(obj, bufferString(desc));
  1145. // clean up our mess and give the object to the character
  1146. deleteBuffer(desc);
  1147. obj_to_game(obj);
  1148. obj_to_char(obj, ch);
  1149. } deleteListIterator(mail_i);
  1150. // let the character know how much mail he received
  1151. send_to_char(ch, "You receive %d letter%s.\r\n",
  1152. listSize(mail_list), (listSize(mail_list) == 1 ? "" : "s"));
  1153. }
  1154. // delete the mail list, and all of its contents
  1155. if(mail_list != NULL) deleteListWith(mail_list, deleteMail);
  1156. }
  1157. // boot up the mail module
  1158. void init_mail(void) {
  1159. // install our auxiliary data
  1160. auxiliariesInstall("mail_aux_data",
  1161. newAuxiliaryFuncs(AUXILIARY_TYPE_CHAR,
  1162. newMailAuxData, deleteMailAuxData,
  1163. mailAuxDataCopyTo, mailAuxDataCopy,
  1164. mailAuxDataStore, mailAuxDataRead));
  1165. // add all of the commands that come with this module
  1166. add_cmd("mail", NULL, cmd_mail, POS_STANDING, POS_FLYING,
  1167. "player", FALSE, TRUE);
  1168. add_cmd("receive", NULL, cmd_receive, POS_STANDING, POS_FLYING,
  1169. "player", FALSE, TRUE);
  1170. }
  1171. \end{verbatim}}
  1172. \end{document}