-void SPEAK(vocab_t N) {
-long BLANK, CASE, I, K, L, NEG, NPARMS, PARM, PRMTYP, STATE;
-
-/* Print the message which starts at LINES(N). Precede it with a blank line
- * unless game.blklin is false. */
-
- if(N == 0)return;
- BLANK=game.blklin;
- K=N;
- NPARMS=1;
-L10: L=labs(LINES[K])-1;
- K=K+1;
- LNLENG=0;
- LNPOSN=1;
- STATE=0;
- for (I=K; I<=L; I++) {
- PUTTXT(LINES[I],STATE,2);
- } /* end loop */
- LNPOSN=0;
-L30: LNPOSN=LNPOSN+1;
-L32: if(LNPOSN > LNLENG) goto L40;
- if(INLINE[LNPOSN] != 63) goto L30;
- {long x = LNPOSN+1; PRMTYP=INLINE[x];}
-/* 63 is a "%"; the next character determine the type of parameter: 1 (!) =
- * suppress message completely, 29 (S) = NULL If PARM=1, else 'S' (optional
- * plural ending), 33 (W) = word (two 30-bit values) with trailing spaces
- * suppressed, 22 (L) or 31 (U) = word but map to lower/upper case, 13 (C) =
- * word in lower case with first letter capitalised, 30 (T) = text ending
- * with a word of -1, 65-73 (1-9) = number using that many characters,
- * 12 (B) = variable number of blanks. */
- if(PRMTYP == 1)return;
- if(PRMTYP == 29) goto L320;
- if(PRMTYP == 30) goto L340;
- if(PRMTYP == 12) goto L360;
- if(PRMTYP == 33 || PRMTYP == 22 || PRMTYP == 31 || PRMTYP == 13) goto
- L380;
- PRMTYP=PRMTYP-64;
- if(PRMTYP < 1 || PRMTYP > 9) goto L30;
- SHFTXT(LNPOSN+2,PRMTYP-2);
- LNPOSN=LNPOSN+PRMTYP;
- PARM=labs(PARMS[NPARMS]);
- NEG=0;
- if(PARMS[NPARMS] < 0)NEG=9;
- /* 390 */ for (I=1; I<=PRMTYP; I++) {
- LNPOSN=LNPOSN-1;
- INLINE[LNPOSN]=MOD(PARM,10)+64;
- if(I == 1 || PARM != 0) goto L390;
- INLINE[LNPOSN]=NEG;
- NEG=0;
-L390: PARM=PARM/10;
- } /* end loop */
- LNPOSN=LNPOSN+PRMTYP;
-L395: NPARMS=NPARMS+1;
- goto L32;
-
-L320: SHFTXT(LNPOSN+2,-1);
- INLINE[LNPOSN]=55;
- if(PARMS[NPARMS] == 1)SHFTXT(LNPOSN+1,-1);
- goto L395;
-
-L340: SHFTXT(LNPOSN+2,-2);
- STATE=0;
- CASE=2;
-L345: if(PARMS[NPARMS] < 0) goto L395;
- {long x = NPARMS+1; if(PARMS[x] < 0)CASE=0;}
- PUTTXT(PARMS[NPARMS],STATE,CASE);
- NPARMS=NPARMS+1;
- goto L345;
-
-L360: PRMTYP=PARMS[NPARMS];
- SHFTXT(LNPOSN+2,PRMTYP-2);
- if(PRMTYP == 0) goto L395;
- for (I=1; I<=PRMTYP; I++) {
- INLINE[LNPOSN]=0;
- LNPOSN=LNPOSN+1;
- } /* end loop */
- goto L395;
-
-L380: SHFTXT(LNPOSN+2,-2);
- STATE=0;
- CASE= -1;
- if(PRMTYP == 31)CASE=1;
- if(PRMTYP == 33)CASE=0;
- I=LNPOSN;
- PUTTXT(PARMS[NPARMS],STATE,CASE);
- {long x = NPARMS+1; PUTTXT(PARMS[x],STATE,CASE);}
- if(PRMTYP == 13 && INLINE[I] >= 37 && INLINE[I] <=
- 62)INLINE[I]=INLINE[I]-26;
- NPARMS=NPARMS+2;
- goto L32;
-
-L40: if(BLANK)TYPE0();
- BLANK=false;
- TYPE();
- K=L+1;
- if(LINES[K] >= 0) goto L10;
- return;
+void speak(const char* msg)
+{
+ // 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");
+
+ // Create a copy of our string, so we can edit it.
+ char* copy = xstrdup(msg);
+
+ // 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]);
+ }
+ }
+ }
+
+ // 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()
+
+ // Print the message.
+ printf("%s\n", rendered);
+
+ free(copy);