- // Staging area for stringified parameters.
- char parameters[5][100]; // FIXME: to be replaced with dynamic allocation
-
- // 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;
-
- // 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]);
- }
-
- // Unmodified string specifier.
- if (msg[i + 1] == 's')
- {
- packed_to_token(PARMS[pi], parameters[pi]);
- }
-
- // 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", "");
- }
- }
-
- // 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]);
- }
- }
-
- // 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]);
- }
- }
+void vspeak(const char* msg, va_list ap)
+{
+ // Do nothing if we got a null pointer.
+ if (msg == NULL)
+ return;
+
+ // Do nothing if we got an empty string.
+ if (strlen(msg) == 0)
+ return;
+
+ // Print a newline if the global game.blklin says to.
+ if (game.blklin == true)
+ printf("\n");
+
+ int msglen = strlen(msg);
+
+ // 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.
+ 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] == 'd') {
+ int ret = snprintf(renderp, size, "%ld", arg);
+ if (ret < size) {
+ renderp += ret;
+ size -= ret;
+ }
+ }
+
+ // Unmodified string specifier.
+ 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] == '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] == '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;
+ }
+
+ previous_arg = arg;
+ }