object_descriptions[] is now objects[].
[open-adventure.git] / misc.c
diff --git a/misc.c b/misc.c
index 29846e23f6a5aef4aef9320ea180e8a0797a5800..e7a03984b0fb96739797499247b797e49e984166 100644 (file)
--- a/misc.c
+++ b/misc.c
@@ -2,6 +2,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+#include <stdarg.h>
 #include <sys/time.h>
 #include <ctype.h>
 
@@ -20,16 +21,6 @@ void* xmalloc(size_t size)
     return (ptr);
 }
 
-char* xstrdup(const char* s)
-{
-    char* ptr = strdup(s);
-    if (ptr == NULL) {
-        fprintf(stderr, "Out of memory!\n");
-        exit(EXIT_FAILURE);
-    }
-    return (ptr);
-}
-
 void packed_to_token(long packed, char token[6])
 {
     // Unpack and map back to ASCII.
@@ -79,9 +70,9 @@ void wordclear(token_t *v)
     *v = 0;
 }
 
-/*  I/O routines (SPEAK, PSPEAK, RSPEAK, SETPRM, GETIN, YES) */
+/*  I/O routines (speak, pspeak, rspeak, GETIN, YES) */
 
-void speak(const char* msg)
+void vspeak(const char* msg, va_list ap)
 {
     // Do nothing if we got a null pointer.
     if (msg == NULL)
@@ -95,97 +86,114 @@ void speak(const char* msg)
     if (game.blklin == true)
         printf("\n");
 
-    // Create a copy of our string, so we can edit it.
-    char* copy = xstrdup(msg);
+    int msglen = strlen(msg);
 
-    // Staging area for stringified parameters.
-    char parameters[5][100]; // FIXME: to be replaced with dynamic allocation
+    // Rendered string
+    ssize_t size = 2000; /* msglen > 50 ? msglen*2 : 100; */
+    char* rendered = xmalloc(size);
+    char* renderp = rendered;
 
     // Handle format specifiers (including the custom %C, %L, %S) by adjusting the parameter accordingly, and replacing the specifier with %s.
-    int pi = 0; // parameter index
-    for (int i = 0; i < (int)strlen(msg); ++i) {
-        if (msg[i] == '%') {
-            ++pi;
-
+    long previous_arg = 0;
+    for (int i = 0; i < msglen; i++) {
+        if (msg[i] != '%') {
+            *renderp++ = msg[i];
+            size--;
+        } else {
+            long arg = va_arg(ap, long);
+           if (arg == -1)
+             arg = 0;
+            i++;
             // Integer specifier. In order to accommodate the fact that PARMS can have both legitimate integers *and* packed tokens, stringify everything. Future work may eliminate the need for this.
-            if (msg[i + 1] == 'd') {
-                copy[i + 1] = 's';
-                sprintf(parameters[pi], "%ld", PARMS[pi]);
+            if (msg[i] == 'd') {
+                int ret = snprintf(renderp, size, "%ld", arg);
+                if (ret < size) {
+                    renderp += ret;
+                    size -= ret;
+                }
             }
 
             // Unmodified string specifier.
-            if (msg[i + 1] == 's') {
-                packed_to_token(PARMS[pi], parameters[pi]);
+            if (msg[i] == 's') {
+                packed_to_token(arg, renderp); /* unpack directly to destination */
+                size_t len = strlen(renderp);
+                renderp += len;
+                size -= len;
             }
 
             // Singular/plural specifier.
-            if (msg[i + 1] == 'S') {
-                copy[i + 1] = 's';
-                if (PARMS[pi - 1] > 1) { // look at the *previous* parameter (which by necessity must be numeric)
-                    sprintf(parameters[pi], "%s", "s");
-                } else {
-                    sprintf(parameters[pi], "%s", "");
+            if (msg[i] == 'S') {
+                if (previous_arg > 1) { // look at the *previous* parameter (which by necessity must be numeric)
+                    *renderp++ = 's';
+                    size--;
                 }
             }
 
             // All-lowercase specifier.
-            if (msg[i + 1] == 'L') {
-                copy[i + 1] = 's';
-                packed_to_token(PARMS[pi], parameters[pi]);
-                for (int j = 0; j < (int)strlen(parameters[pi]); ++j) {
-                    parameters[pi][j] = tolower(parameters[pi][j]);
+            if (msg[i] == 'L' || msg[i] == 'C') {
+                packed_to_token(arg, renderp); /* unpack directly to destination */
+                int len = strlen(renderp);
+                for (int j = 0; j < len; ++j) {
+                    renderp[j] = tolower(renderp[j]);
                 }
+                if (msg[i] == 'C') // First char uppercase, rest lowercase.
+                    renderp[0] = toupper(renderp[0]);
+                renderp += len;
+                size -= len;
             }
 
-            // First char uppercase, rest lowercase.
-            if (msg[i + 1] == 'C') {
-                copy[i + 1] = 's';
-                packed_to_token(PARMS[pi], parameters[pi]);
-                for (int j = 0; j < (int)strlen(parameters[pi]); ++j) {
-                    parameters[pi][j] = tolower(parameters[pi][j]);
-                }
-                parameters[pi][0] = toupper(parameters[pi][0]);
-            }
+            previous_arg = arg;
         }
     }
-
-    // Render the final string.
-    char rendered[2000]; // FIXME: to be replaced with dynamic allocation
-    sprintf(rendered, copy, parameters[1], parameters[2], parameters[3], parameters[4]); // FIXME: to be replaced with vsprintf()
+    *renderp = 0;
 
     // Print the message.
     printf("%s\n", rendered);
 
-    free(copy);
-}
-
-void PSPEAK(vocab_t msg, int skip)
-/*  Find the skip+1st message from msg and print it.  msg should be
- *  the index of the inventory message for object.  (INVEN+N+1 message
- *  is game.prop=N message). */
-{
-    if (skip >= 0)
-        speak(object_descriptions[msg].longs[skip]);
-    else
-        speak(object_descriptions[msg].inventory);
+    free(rendered);
+}
+
+void speak(const char* msg, ...)
+{
+    va_list ap;
+    va_start(ap, msg);
+    vspeak(msg, ap);
+    va_end(ap);
+}
+
+void pspeak(vocab_t msg, enum speaktype mode, int skip, ...)
+/* Find the skip+1st message from msg and print it.  Modes are:
+ * feel = for inventory, what you can touch
+ * look = the long description for the state the object is in
+ * listen = the sound for the state the object is in
+ * study = text on the object. */
+{
+    va_list ap;
+    va_start(ap, skip);
+    switch (mode) {
+    case touch:
+        vspeak(objects[msg].inventory, ap);
+       break;
+    case look: 
+        vspeak(objects[msg].longs[skip], ap);
+       break;
+    case hear:
+        vspeak(objects[msg].sounds[skip], ap);
+       break;
+    case study:
+        vspeak(objects[msg].texts[skip], ap);
+       break;
+    }
+    va_end(ap);
 }
 
-void RSPEAK(vocab_t i)
+void rspeak(vocab_t i, ...)
 /* Print the i-th "random" message (section 6 of database). */
 {
-    speak(arbitrary_messages[i]);
-}
-
-void SETPRM(long first, long p1, long p2)
-/*  Stores parameters into the PRMCOM parms array for use by speak.  P1 and P2
- *  are stored into PARMS(first) and PARMS(first+1). */
-{
-    if (first >= MAXPARMS)
-        BUG(TOO_MANY_PARAMETERS_GIVEN_TO_SETPRM);
-    else {
-        PARMS[first] = p1;
-        PARMS[first + 1] = p2;
-    }
+    va_list ap;
+    va_start(ap, i);
+    vspeak(arbitrary_messages[i], ap);
+    va_end(ap);
 }
 
 bool GETIN(FILE *input,
@@ -220,7 +228,7 @@ bool GETIN(FILE *input,
         (junk > 0);
         if (GETTXT(true, true, true) <= 0)
             return true;
-        RSPEAK(TWO_WORDS);
+        rspeak(TWO_WORDS);
     }
 }
 
@@ -257,8 +265,8 @@ char* get_input()
             IGNORE(getline(&input, &n, stdin));
         }
 
-        if (input == NULL) // Got EOF; quit.
-            exit(EXIT_SUCCESS);
+        if (input == NULL) // Got EOF; return with it.
+           return(input);
         else if (input[0] == '#') // Ignore comments.
             continue;
         else // We have a 'normal' line; leave the loop.
@@ -279,7 +287,7 @@ char* get_input()
     return (input);
 }
 
-bool YES(const char* question, const char* yes_response, const char* no_response)
+bool yes(const char* question, const char* yes_response, const char* no_response)
 /*  Print message X, wait for yes/no answer.  If yes, print Y and return true;
  *  if no, print Z and return false. */
 {
@@ -290,8 +298,12 @@ bool YES(const char* question, const char* yes_response, const char* no_response
         speak(question);
 
         reply = get_input();
+       if (reply == NULL) {
+         linenoiseFree(reply);
+         exit(EXIT_SUCCESS);
+       }
 
-        char* firstword = (char*) xmalloc(strlen(reply));
+        char* firstword = (char*) xmalloc(strlen(reply)+1);
         sscanf(reply, "%s", firstword);
 
         for (int i = 0; i < (int)strlen(firstword); ++i)
@@ -313,7 +325,7 @@ bool YES(const char* question, const char* yes_response, const char* no_response
             outcome = false;
             break;
         } else
-            RSPEAK(PLEASE_ANSWER);
+            rspeak(PLEASE_ANSWER);
     }
     linenoiseFree(reply);
     return (outcome);
@@ -393,7 +405,7 @@ token_t MAKEWD(long letters)
 
 /*  Data structure  routines */
 
-long VOCAB(long id, long init)
+long vocab(long id, long init)
 /*  Look up ID in the vocabulary (ATAB) and return its "definition" (KTAB), or
  *  -1 if not found.  If INIT is positive, this is an initialisation call setting
  *  up a keyword variable, and not finding it constitutes a bug.  It also means
@@ -422,7 +434,7 @@ long VOCAB(long id, long init)
     BUG(RAN_OFF_END_OF_VOCABULARY_TABLE);
 }
 
-void JUGGLE(long object)
+void juggle(long object)
 /*  Juggle an object by picking it up and putting it down again, the purpose
  *  being to get the object to the front of the chain of things at its loc. */
 {
@@ -430,11 +442,11 @@ void JUGGLE(long object)
 
     i = game.place[object];
     j = game.fixed[object];
-    MOVE(object, i);
-    MOVE(object + NOBJECTS, j);
+    move(object, i);
+    move(object + NOBJECTS, j);
 }
 
-void MOVE(long object, long where)
+void move(long object, long where)
 /*  Place any object anywhere by picking it up and dropping it.  May
  *  already be toting, in which case the carry is a no-op.  Mustn't
  *  pick up objects which are not at any loc, since carry wants to
@@ -447,19 +459,19 @@ void MOVE(long object, long where)
     else
         from = game.place[object];
     if (from != LOC_NOWHERE && from != CARRIED && !SPECIAL(from))
-        CARRY(object, from);
-    DROP(object, where);
+        carry(object, from);
+    drop(object, where);
 }
 
-long PUT(long object, long where, long pval)
+long put(long object, long where, long pval)
 /*  PUT is the same as MOVE, except it returns a value used to set up the
  *  negated game.prop values for the repository objects. */
 {
-    MOVE(object, where);
+    move(object, where);
     return (-1) - pval;;
 }
 
-void CARRY(long object, long where)
+void carry(long object, long where)
 /*  Start toting an object, removing it from the list of things at its former
  *  location.  Incr holdng unless it was already being toted.  If object>NOBJECTS
  *  (moving "fixed" second loc), don't change game.place or game.holdng. */
@@ -483,7 +495,7 @@ void CARRY(long object, long where)
     game.link[temp] = game.link[object];
 }
 
-void DROP(long object, long where)
+void drop(long object, long where)
 /*  Place an object at a given loc, prefixing it onto the game.atloc list.  Decr
  *  game.holdng if the object was being toted. */
 {
@@ -500,7 +512,7 @@ void DROP(long object, long where)
     game.atloc[where] = object;
 }
 
-long ATDWRF(long where)
+long atdwrf(long where)
 /*  Return the index of first dwarf at the given location, zero if no dwarf is
  *  there (or if dwarves not active yet), -1 if all dwarves are dead.  Ignore
  *  the pirate (6th dwarf). */
@@ -523,13 +535,13 @@ long ATDWRF(long where)
 /*  Utility routines (SETBIT, TSTBIT, set_seed, get_next_lcg_value,
  *  randrange, RNDVOC) */
 
-long SETBIT(long bit)
+long setbit(long bit)
 /*  Returns 2**bit for use in constructing bit-masks. */
 {
     return (1 << bit);
 }
 
-bool TSTBIT(long mask, int bit)
+bool tstbit(long mask, int bit)
 /*  Returns true if the specified bit is set in the mask. */
 {
     return (mask & (1 << bit)) != 0;
@@ -555,7 +567,7 @@ long randrange(long range)
     return range * get_next_lcg_value() / game.lcg_m;
 }
 
-long RNDVOC(long second, long force)
+long rndvoc(long second, long force)
 /*  Searches the vocabulary ATAB for a word whose second character is
  *  char, and changes that word such that each of the other four
  *  characters is a random letter.  If force is non-zero, it is used
@@ -663,10 +675,10 @@ bool MAPLIN(FILE *fp)
          *  If the data file uses a character other than space (e.g., tab) to
          *  separate numbers, that character should also translate to 0.
          *
-         *  This procedure may use the map1,map2 arrays to maintain static data for
-         *  the mapping.  MAP2(1) is set to 0 when the program starts
-         *  and is not changed thereafter unless the routines on this page choose
-         *  to do so. */
+         *  This procedure may use the map1,map2 arrays to maintain
+         *  static data for he mapping.  MAP2(1) is set to 0 when the
+         *  program starts and is not changed thereafter unless the
+         *  routines in this module choose to do so. */
         LNLENG = 0;
         for (long i = 1; i <= (long)sizeof(INLINE) && INLINE[i] != 0; i++) {
             long val = INLINE[i];
@@ -679,7 +691,7 @@ bool MAPLIN(FILE *fp)
     }
 }
 
-void DATIME(long* d, long* t)
+void datime(long* d, long* t)
 {
     struct timeval tv;
     gettimeofday(&tv, NULL);