1 /* Interface-independent natural language handling for Xconq.
2    Copyright (C) 1987-1989, 1991-2000 Stanley T. Shebs.
3    Copyright (C) 2004-2005 Eric A. McDonald.
4 
5 Xconq is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version.  See the file COPYING.  */
9 
10 /* This file should be entirely replaced for non-English Xconq. */
11 /* (One way to do this would be to call this file "nlang-en.c", then
12    symlink this or nlang-fr.c, etc to nlang.c when configuring;
13    similarly for help.c.) */
14 /* (A better way to would be to run all these through a dispatch
15    vector, then each player could get messages and such in a
16    different language.) */
17 /* (Or just use the 'msgcat' and other GNU NLS stuff.) */
18 
19 #include "conq.h"
20 #include "kernel.h"
21 
22 #include "aiscore.h"
23 #include "aiunit.h"
24 #include "aiunit2.h"
25 #include "aitact.h"
26 #include "aioprt.h"
27 
28 static void notify_combat(Unit *unit, Unit *atker, char *str);
29 static int pattern_matches_combat(Obj *pattern, Unit *unit, Unit *unit2);
30 static void combat_desc_from_list(Side *side, Obj *lis, Unit *unit,
31 				  Unit *unit2, char *str, char *buf);
32 static void init_calendar(void);
33 static void parse_date_step_range(Obj *form);
34 static void maybe_mention_date(Side *side);
35 static int gain_count(Side *side, int u, int r);
36 static int loss_count(Side *side, int u, int r);
37 static int atkstats(Side *side, int a, int d);
38 static int hitstats(Side *side, int a, int d);
39 static void pad_out(char *buf, int n);
40 static char *past_unit_handle(Side *side, PastUnit *past_unit);
41 static char *short_side_title_with_adjective(Side *side, char *adjective);
42 
43 static char *tmpnbuf;
44 
45 static char *tmpdbuf;
46 
47 static char *pluralbuf;
48 
49 /* Short names of directions. */
50 
51 char *dirnames[] = DIRNAMES;
52 
53 char *unitbuf = NULL;
54 
55 char *past_unitbuf = NULL;
56 
57 static char *side_short_title = NULL;
58 
59 static char *gain_reason_names[] = { "Ini", "Bld", "Cap", "Oth" };
60 
61 static char *loss_reason_names[] = { "Cbt", "Cap", "Stv", "Acc", "Dis", "Oth" };
62 
63 /* Calendar handling. */
64 
65 typedef enum {
66     cal_unknown,
67     cal_number,
68     cal_usual
69 } CalendarType;
70 
71 static CalendarType calendar_type = cal_unknown;
72 
73 typedef enum {
74     ds_second,
75     ds_minute,
76     ds_hour,
77     ds_day,
78     ds_week,
79     ds_month,
80     ds_season,
81     ds_year
82 } UsualDateStepType;
83 
84 typedef struct a_usualdate {
85     int second;
86     int minute;
87     int hour;
88     int day;
89     int month;
90     int year;
91 } UsualDate;
92 
93 typedef struct a_usualdatesteprange {
94     int turn_start;
95     int turn_end;
96     UsualDateStepType step_type;
97     int step_size;
98 } UsualDateStepRange;
99 
100 static char *usual_date_string(int date);
101 static void parse_usual_date(char *datestr, int range, UsualDate *udate);
102 
103 static int turn_initial;
104 
105 static UsualDateStepRange date_step_ranges[20];
106 
107 static int num_date_step_ranges;
108 
109 static char *months[] = {
110   "Jan", "Feb", "Mar", "Apr", "May", "Jun",
111   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???" };
112 
113 static short monthdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0 };
114 
115 static char *seasons[] = { "Win", "Spr", "Sum", "Aut" };
116 
117 static UsualDate *usual_initial;
118 
119 char *datebuf;
120 
121 char *turn_name;
122 
123 char *featurebuf;
124 
125 /* This is the number of types to mention by name; any others will
126    just be included in the count of missing images. */
127 
128 #define NUMTOLIST 5
129 
130 static char *missinglist;
131 
132 /* This array allows for counting up to 4 classes of missing images. */
133 
134 static int missing[4];
135 
136 static int totlisted = 0;
137 
138 /* Initialize things.  Note that this happens before any game is loaded, so
139    can't do game-specific init here. */
140 
141 void
init_nlang(void)142 init_nlang(void)
143 {
144     if (tmpnbuf == NULL)
145       tmpnbuf = (char *)xmalloc(BUFSIZE);
146     if (tmpdbuf == NULL)
147       tmpdbuf = (char *)xmalloc(BUFSIZE);
148     if (pluralbuf == NULL)
149       pluralbuf = (char *)xmalloc(BUFSIZE);
150     if (datebuf == NULL)
151       datebuf = (char *)xmalloc(BUFSIZE);
152 }
153 
154 /* Send a message to everybody who's got a screen. */
155 
156 void
notify_all(char * fmt,...)157 notify_all(char *fmt, ...)
158 {
159     va_list ap;
160     Side *side;
161 
162     for_all_sides(side) {
163 	if (active_display(side)) {
164 	    maybe_mention_date(side);
165 	    va_start(ap, fmt);
166 	    vsprintf(tmpnbuf, fmt, ap);
167 	    va_end(ap);
168 	    /* Always capitalize first char of notice. */
169 	    capitalize(tmpnbuf);
170 	    low_notify(side, tmpnbuf);
171 	}
172     }
173 }
174 
175 void
notify(Side * side,char * fmt,...)176 notify(Side *side, char *fmt, ...)
177 {
178     va_list ap;
179 
180     if (!active_display(side))
181       return;
182     maybe_mention_date(side);
183     va_start(ap, fmt);
184     vsprintf(tmpnbuf, fmt, ap);
185     va_end(ap);
186     /* Always capitalize first char of notice. */
187     capitalize(tmpnbuf);
188     low_notify(side, tmpnbuf);
189 }
190 
191 void
vnotify(Side * side,char * fmt,va_list ap)192 vnotify(Side *side, char *fmt, va_list ap)
193 {
194     if (!active_display(side))
195       return;
196     maybe_mention_date(side);
197     vsprintf(tmpnbuf, fmt, ap);
198     /* Always capitalize first char of notice. */
199     capitalize(tmpnbuf);
200     low_notify(side, tmpnbuf);
201 }
202 
203 static void
maybe_mention_date(Side * side)204 maybe_mention_date(Side *side)
205 {
206     /* Note that last_notice_date defaults to 0, so this means that
207        any turn 0 notices will not have a date prepended (which is good). */
208     if (g_turn() != side->last_notice_date) {
209 	sprintf(tmpdbuf, "%s:", absolute_date_string(g_turn()));
210 	low_notify(side, tmpdbuf);
211 	side->last_notice_date = g_turn();
212     }
213 }
214 
215 /* Pad a given buffer with blanks out to the given position, and cut
216    off any additional content. */
217 
218 void
pad_out(char * buf,int n)219 pad_out(char *buf, int n)
220 {
221     int i, len = strlen(buf);
222 
223     if (n < 1)
224       return;
225     for (i = len; i < n; ++i) {
226 	buf[i] = ' ';
227     }
228     buf[n - 1] = '\0';
229 }
230 
231 /* Get a char string naming the side.  Doesn't have to be pretty. */
232 
233 /* (should synth complete name/adjective from other parts of speech) */
234 
235 char *
side_name(Side * side)236 side_name(Side *side)
237 {
238     return (side->name ? side->name :
239 	    (side->adjective ? side->adjective :
240 	     (side->pluralnoun ? side->pluralnoun :
241 	      (side->noun ? side->noun :
242 	       (char *)""))));
243 }
244 
245 char *
side_adjective(Side * side)246 side_adjective(Side *side)
247 {
248     return (side->adjective ? side->adjective :
249 	    (side->noun ? side->noun :
250 	     (side->pluralnoun ? side->pluralnoun :
251 	      (side->name ? side->name :
252 	       (char *)""))));
253 }
254 
255 char *
short_side_title(Side * side)256 short_side_title(Side *side)
257 {
258     if (side_short_title == NULL)
259       side_short_title = (char *)xmalloc(BUFSIZE);
260     if (side->name) {
261 	return side->name;
262     } else if (side->pluralnoun) {
263 	sprintf(side_short_title, "the %s", side->pluralnoun);
264     } else if (side->noun) {
265 	sprintf(side_short_title, "the %s", plural_form(side->noun));
266     } else if (side->adjective) {
267 	sprintf(side_short_title, "the %s side", side->adjective);
268     } else {
269 	return " - ";
270     }
271     return side_short_title;
272 }
273 
274 char *
short_side_title_with_adjective(Side * side,char * adjective)275 short_side_title_with_adjective(Side *side, char *adjective)
276 {
277     if (side_short_title == NULL)
278       side_short_title = (char *)xmalloc(BUFSIZE);
279     side_short_title[0] = '\0';
280     if (side->name) {
281 	if (empty_string(adjective))
282 	  return side->name;
283 	else {
284 	    strcat(side_short_title, adjective);
285 	    strcat(side_short_title, " ");
286 	    strcat(side_short_title, side->name);
287 	}
288     } else if (side->pluralnoun) {
289 	strcat(side_short_title, "the ");
290 	if (!empty_string(adjective)) {
291 	    strcat(side_short_title, adjective);
292 	    strcat(side_short_title, " ");
293 	}
294 	strcat(side_short_title, side->pluralnoun);
295     } else if (side->noun) {
296 	strcat(side_short_title, "the ");
297 	if (!empty_string(adjective)) {
298 	    strcat(side_short_title, adjective);
299 	    strcat(side_short_title, " ");
300 	}
301 	strcat(side_short_title, side->noun);
302     } else if (side->adjective) {
303 	strcat(side_short_title, "the ");
304 	if (!empty_string(adjective)) {
305 	    strcat(side_short_title, adjective);
306 	    strcat(side_short_title, " ");
307 	}
308 	strcat(side_short_title, side->adjective);
309 	strcat(side_short_title, " side");
310     } else {
311 	return " - ";
312     }
313     return side_short_title;
314 }
315 
316 /* This indicates whether the above routine returns a singular or plural form
317    of title. */
318 
319 int
short_side_title_plural_p(Side * side)320 short_side_title_plural_p(Side *side)
321 {
322     if (side->name) {
323 	return FALSE;
324     } else if (side->pluralnoun) {
325 	return TRUE;
326     } else if (side->noun) {
327 	return TRUE;
328     } else if (side->adjective) {
329 	sprintf(side_short_title, "the %s side", side->adjective);
330 	return FALSE;
331     } else {
332 	return FALSE;
333     }
334 }
335 
336 char *
shortest_side_title(Side * side2,char * buf)337 shortest_side_title(Side *side2, char *buf)
338 {
339     if (side2->name) {
340 	return side2->name;
341     } else if (side2->adjective) {
342 	return side2->adjective;
343     } else if (side2->noun) {
344 	return side2->noun;
345     } else if (side2->pluralnoun) {
346 	return side2->pluralnoun;
347     } else {
348 	sprintf(buf, "(#%d)", side_number(side2));
349     }
350     return buf;
351 }
352 
353 char *
sidemask_desc(char * buf,SideMask sidemask)354 sidemask_desc(char *buf, SideMask sidemask)
355 {
356     int first = TRUE;
357     Side *side2;
358 
359     if (sidemask < 0 || sidemask >= ((1 << (numsides + 1)) - 2))
360       return "all";
361     buf[0] = '\0';
362     for_all_sides(side2) {
363 	if (side_in_set(side2, sidemask)) {
364 	    if (first)
365 	      first = FALSE;
366 	    else
367 	      strcat(buf, ", ");
368 	    strcat(buf, short_side_title(side2));
369 	}
370     }
371     return buf;
372 }
373 
374 char *
side_score_desc(char * buf,Side * side,Scorekeeper * sk)375 side_score_desc(char *buf, Side *side, Scorekeeper *sk)
376 {
377     if (!sk->keepscore) {
378 	buf[0] = 0;
379 	return buf;
380     }
381     if (!empty_string(sk->title)) {
382     	strcpy(buf, sk->title);
383     } else {
384     	sprintf(buf, "Score");
385     	if (numscorekeepers > 1) {
386     		tprintf(buf, " %d", sk->id);
387     	}
388     }
389     tprintf(buf, ": ");
390     if (symbolp(sk->body)
391         && match_keyword(sk->body, K_LAST_SIDE_WINS)) {
392 	tprintf(buf, "%d", side_point_value(side));
393     } else if (symbolp(sk->body)
394         && match_keyword(sk->body, K_LAST_ALLIANCE_WINS)) {
395 	if (has_allies(side)) {
396 		tprintf(buf, "%d", alliance_point_value(side));
397 		tprintf(buf, " (%d alone)", side_point_value(side));
398     	} else {
399 		tprintf(buf, "%d", side_point_value(side));
400        	}
401     } else {
402 	/* Compose the generic scorekeeper status display. */
403 	if (sk->scorenum >= 0) {
404 	    tprintf(buf, "%d", side->scores[sk->scorenum]);
405 	}
406     }
407     return buf;
408 }
409 
410 char *
long_player_title(char * buf,Player * player,char * thisdisplayname)411 long_player_title(char *buf, Player *player, char *thisdisplayname)
412 {
413     buf[0] = '\0';
414     if (player == NULL) {
415 	/* Do nothing */
416     } else if (player->displayname != NULL) {
417 	if (player->name != NULL) {
418 	    strcat(buf, player->name);
419 	    strcat(buf, "@");
420 	}
421 	if (thisdisplayname != NULL
422 	    && strcmp(player->displayname, thisdisplayname) == 0
423 	    && player->rid == my_rid) {
424 	    strcat(buf, "You");
425 	} else {
426 	    strcat(buf, player->displayname);
427 	}
428 	if (player->aitypename != NULL) {
429 	    strcat(buf, "(& AI ");
430 	    strcat(buf, player->aitypename);
431 	    strcat(buf, ")");
432 	}
433     } else if (player->aitypename != NULL) {
434 	strcat(buf, "AI ");
435 	strcat(buf, player->aitypename);
436     } else {
437 	strcat(buf, "-");
438     }
439     return buf;
440 }
441 
442 char *
short_player_title(char * buf,Player * player,char * thisdisplayname)443 short_player_title(char *buf, Player *player, char *thisdisplayname)
444 {
445     buf[0] = '\0';
446     if (player == NULL)
447       return buf;
448     if (player->name != NULL) {
449 	strcat(buf, player->name);
450     }
451     if (player->aitypename != NULL) {
452 	strcat(buf, ",");
453 	strcat(buf, player->aitypename);
454     }
455     if ((player->name != NULL || player->aitypename != NULL)
456 	&& player->displayname != NULL) {
457 	strcat(buf, "@");
458     }
459     if (thisdisplayname != NULL
460 	&& player->displayname != NULL
461 	&& strcmp(player->displayname, thisdisplayname) == 0) {
462 	strcat(buf, "You");
463     } else if (player->displayname != NULL) {
464 	strcat(buf, player->displayname);
465     }
466     if (strlen(buf) == 0) {
467 	strcat(buf, "-");
468     }
469     return buf;
470 }
471 
472 /* If we are not using the X11 interface, we don't need to pass thisdisplayname
473 in order to figure out if we are "You". This make things a lot simpler since the. */
474 
475 char *
simple_player_title(char * buf,Player * player)476 simple_player_title(char *buf, Player *player)
477 {
478     buf[0] = '\0';
479     if (player == NULL)
480       return buf;
481     /* First handle human players. */
482     if (player->displayname) {
483     	/* If we are "You" we are done. */
484     	if (player->rid == my_rid) {
485     		strcat(buf, "You");
486     	/* If we have a "name" use it. */
487     	} else if (player->name) {
488     		strcat(buf, player->name);
489 	/* Else use the "displayname". This is where we usually end up, since
490 	whatever is passed in default_player_spec by default is used to set the
491 	"displayname" and not the "name". */
492 	} else {
493     		strcat(buf, player->displayname);
494 	}
495 	/* Add the ai name if it exists. */
496 	if (player->aitypename) {
497 		strcat(buf, " + ");
498 		strcat(buf, player->aitypename);
499 	}
500     /* Then handle ai players. */
501     } else if (player->aitypename) {
502 	strcat(buf, player->aitypename);
503     }
504     if (strlen(buf) == 0) {
505 	strcat(buf, "-");
506     }
507     return buf;
508 }
509 
510 /* Used in player setup. Returns "You" or name for human players
511 and "Computer" or "Nobody" for non-human players. */
512 
513 char *
simple_player_name(char * buf,Player * player)514 simple_player_name(char *buf, Player *player)
515 {
516     buf[0] = '\0';
517     if (player == NULL)
518       return buf;
519     /* First handle human players. */
520     if (player->displayname) {
521     	/* If we are "You" we are done. */
522     	if (player->rid == my_rid) {
523     		strcat(buf, "You");
524     	/* If we have a "name" use it. */
525     	} else if (player->name) {
526     		strcat(buf, player->name);
527 	/* Else use the "displayname". This is where we usually end up, since
528 	whatever is passed in default_player_spec by default is used to set the
529 	"displayname" and not the "name". */
530 	} else {
531     		strcat(buf, player->displayname);
532 	}
533     /* Then handle ai players. */
534     } else if (player->aitypename) {
535 	strcat(buf, "Computer");
536     }
537     if (strlen(buf) == 0) {
538 	strcat(buf, "Nobody");
539     }
540     return buf;
541 }
542 
543 #if 0 	/* Unused. */
544 
545 void
546 side_and_type_name(char *buf, Side *side, int u, Side *side2)
547 {
548     /* Decide how to identify the side. */
549     if (side2 == NULL) {
550 	sprintf(buf, "independent ");
551     } else if (side == side2) {
552 	sprintf(buf, "your ");
553     } else {
554 	sprintf(buf, "%s ", side_adjective(side2));
555     }
556     /* Glue the pieces together and return it. */
557     strcat(buf, u_type_name(u));
558 }
559 
560 #endif
561 
562 /* Build a short phrase describing a given unit to a given side,
563    basically consisting of indication of unit's side, then of unit
564    itself. */
565 
566 char *
unit_handle(Side * side,Unit * unit)567 unit_handle(Side *side, Unit *unit)
568 {
569     Side *side2 = NULL;
570 
571     if (unit != NULL)
572       side2 = unit->side;
573     return apparent_unit_handle(side, unit, side2);
574 }
575 
576 /* This version allows the caller to supply a side other than the
577    unit's actual side, such as when describing an out-of-date image of
578    a unit that may have been captured. */
579 
580 char *
apparent_unit_handle(Side * side,Unit * unit,Side * side2)581 apparent_unit_handle(Side *side, Unit *unit, Side *side2)
582 {
583     char *utypename, *fmtstr, smallbuf[40], sidebuf[100];
584     Side *side3;
585     Obj *frest, *fmt1;
586 
587     /* This should be impossible, be really obvious if it happens. */
588     if (side2 == NULL)
589       return "null side2 in apparent_unit_handle?";
590     /* Make sure our working space exists. */
591     if (unitbuf == NULL)
592       unitbuf = (char *)xmalloc(BUFSIZE);
593     /* Handle various weird situations. */
594     if (unit == NULL)
595       return "???";
596     if (!alive(unit)) {
597     	sprintf(unitbuf, "dead #%d", unit->id);
598         return unitbuf;
599     }
600     unitbuf[0] = '\0';
601     /* Decide how to identify the side.  If the unit's original side
602        is not its current side, list both of them. */
603     side3 = NULL;
604     if (unit->origside != NULL && side2 != unit->origside)
605       side3 = unit->origside;
606     sidebuf[0] = '\0';
607     if (side2 == side) {
608 	strcat(sidebuf, "your");
609     } else {
610 	/* If the side adjective is a genitive (ends in 's, s' or z')
611 	   we should skip the definite article. */
612 	int len = strlen(side_adjective(side2));
613 	char *end = side_adjective(side2) + len - 2;
614 
615 	if (strcmp(end, "'s") != 0
616 	    && strcmp(end, "s'") != 0
617 	    && strcmp(end, "z'") != 0)
618 	  strcat(sidebuf, "the ");
619 	strcat(sidebuf, side_adjective(side2));
620     }
621 
622 	/* Generates a lot of extra text of limited interest to the player. */
623 #if 0
624     if (side3 != NULL) {
625 	if (side3 == side) {
626 	    strcat(sidebuf, " (formerly your)");
627 	} else if (side3 == indepside) {
628 	    /* Don't add anything for captured independents.  While
629 	       technically there's no reason not to do this, in
630 	       practice a side will often have many captured indeps,
631 	       and this keeps the text from getting too cluttered. */
632 	} else {
633 	    strcat(sidebuf, " (formerly ");
634 	    strcat(sidebuf, side_adjective(side3));
635 	    strcat(sidebuf, ")");
636 	}
637     }
638 #endif
639 
640     /* Now add the unit's unique description. */
641     utypename = u_type_name(unit->type);
642     /* If we have special formatting info, interpret it. */
643     if (u_desc_format(unit->type) != lispnil) {
644 	for_all_list(u_desc_format(unit->type), frest) {
645 	    fmt1 = car(frest);
646 	    if (stringp(fmt1)) {
647 		/* Append strings verbatim. */
648 		strcat(unitbuf, c_string(fmt1));
649 	    } else if (symbolp(fmt1)) {
650 		/* Symbols indicate the types of data to format and
651                    output. */
652 		fmtstr = c_string(fmt1);
653 		if (strcmp(fmtstr, "name") == 0) {
654 		    strcat(unitbuf, (unit->name ? unit->name : "anon"));
655 		} else if (strcmp(fmtstr, "position") == 0) {
656 		    sprintf(smallbuf, "%d,%d", unit->x, unit->y);
657 		    strcat(unitbuf, smallbuf);
658 		} else if (strcmp(fmtstr, "side") == 0) {
659 		    strcat(unitbuf, sidebuf);
660 		} else if (strcmp(fmtstr, "type") == 0) {
661 		    strcat(unitbuf, utypename);
662 		} else if (strcmp(fmtstr, "side-name") == 0) {
663 		    strcat(unitbuf, side_name(unit->side));
664 		} else if (strcmp(fmtstr, "side-adjective") == 0) {
665 		    strcat(unitbuf, side_adjective(unit->side));
666 		} else {
667 		    strcat(unitbuf, "??description-format??");
668 		}
669 	    } else {
670 		strcat(unitbuf, "??description-format??");
671 	    }
672 	}
673 	return unitbuf;
674     } else {
675 	strcat(unitbuf, sidebuf);
676 	strcat(unitbuf, " ");
677     }
678     /* If this unit is a self unit, say so. */
679     if (unit->side != NULL && unit == unit->side->self_unit) {
680     	if (!mobile(unit->type) || u_advanced(unit->type)) {
681 	  	strcat(unitbuf, "capital ");
682 	} else {
683 		strcat(unitbuf, "leader ");
684     	}
685 	if (unit->name) {
686 		tprintf(unitbuf, "%s", unit->name);
687 	} else if (unit->number > 0) {
688 		tprintf(unitbuf, "%d%s %s",
689 			unit->number, ordinal_suffix(unit->number), utypename);
690 	} else {
691 		strcat(unitbuf, utypename);
692 	}
693     /* Default formats for units. */
694     } else {
695 	    if (unit->name) {
696 		tprintf(unitbuf, "%s %s", utypename, unit->name);
697 	    } else if (unit->number > 0) {
698 		tprintf(unitbuf, "%d%s %s",
699 			unit->number, ordinal_suffix(unit->number), utypename);
700 	    } else {
701 		strcat(unitbuf, utypename);
702 	    }
703     }
704     return unitbuf;
705 }
706 
707 /* Shorter unit description omits side name, but uses same buffer.
708    This is mainly useful for describing the transport of a unit and
709    suchlike, where the player will likely already know the side of the
710    unit. */
711 
712 char *
short_unit_handle(Unit * unit)713 short_unit_handle(Unit *unit)
714 {
715     int u;
716 
717     if (unitbuf == NULL)
718       unitbuf = (char *)xmalloc(BUFSIZE);
719     if (unit == NULL)
720       return "???";
721     if (!alive(unit)) {
722     	sprintf(unitbuf, "dead #%d", unit->id);
723         return unitbuf;
724     }
725     u = unit->type;
726     /* Use the name alone if the unit is named, or else use optional
727        ordinal and the shortest type name available. */
728     if (!empty_string(unit->name)) {
729 	strcpy(unitbuf, unit->name);
730     } else {
731 	unitbuf[0] = '\0';
732 	if (unit->number > 0) {
733 	    sprintf(unitbuf, "%d%s ",
734 		    unit->number, ordinal_suffix(unit->number));
735 	}
736 	if (!empty_string(u_short_name(u)))
737 	  strcat(unitbuf, u_short_name(u));
738 	else
739 	  strcat(unitbuf, u_type_name(u));
740     }
741     return unitbuf;
742 }
743 
744 /* This version lists the side but skips original side etc. */
745 
746 char *
medium_long_unit_handle(Unit * unit)747 medium_long_unit_handle(Unit *unit)
748 {
749     if (unitbuf == NULL)
750       unitbuf = (char *)xmalloc(BUFSIZE);
751     if (unit == NULL)
752       return "???";
753     if (!alive(unit)) {
754     	sprintf(unitbuf, "dead #%d", unit->id);
755         return unitbuf;
756     }
757     strcpy(unitbuf, side_adjective(unit->side));
758     strcat(unitbuf, " ");
759     /* If this unit is a self unit, say so. */
760     if (unit->side != NULL && unit == unit->side->self_unit) {
761 	if (!mobile(unit->type) || u_advanced(unit->type)) {
762 		strcat(unitbuf, "capital ");
763 	} else {
764 		strcat(unitbuf, "leader ");
765 	}
766 	/* If the unit has a name, write its name. */
767 	if (!empty_string(unit->name)) {
768 		strcat(unitbuf, unit->name);
769 		/* If the unit has a number, write it followed by the type. */
770 	} else if (unit->number > 0) {
771 		tprintf(unitbuf, "%d%s %s",
772 			unit->number, ordinal_suffix(unit->number),
773 			u_type_name(unit->type));
774 		/* Else just write the unit type. */
775 	} else {
776 		strcat(unitbuf, u_type_name(unit->type));
777 	}
778     } else {
779 	/* If the unit has a name, write its type followed by the name. */
780 	if (!empty_string(unit->name)) {
781 		strcat(unitbuf, u_type_name(unit->type));
782 		strcat(unitbuf, " ");
783 		strcat(unitbuf, unit->name);
784 		/* If the unit has a number, write it followed by the type. */
785 	} else if (unit->number > 0) {
786 		tprintf(unitbuf, "%d%s %s",
787 			unit->number, ordinal_suffix(unit->number),
788 			u_type_name(unit->type));
789 		/* Else just write the unit type. */
790 	} else {
791 		strcat(unitbuf, u_type_name(unit->type));
792 	}
793     }
794     return unitbuf;
795 }
796 
797 /* Put either the unit's name or its number into the given buffer. */
798 
799 void
name_or_number(Unit * unit,char * buf)800 name_or_number(Unit *unit, char *buf)
801 {
802     if (unit->name) {
803 	strcpy(buf, unit->name);
804     } else if (unit->number > 0) {
805 	sprintf(buf, "%d%s", unit->number, ordinal_suffix(unit->number));
806     } else {
807 	buf[0] = '\0';
808     }
809 }
810 
811 /* Build a short phrase describing a given past unit to a given side,
812    basically consisting of indication of unit's side, then of unit
813    itself. */
814 
815 char *
past_unit_handle(Side * side,PastUnit * past_unit)816 past_unit_handle(Side *side, PastUnit *past_unit)
817 {
818     char *utypename;
819     Side *side2;
820 
821     if (past_unitbuf == NULL)
822       past_unitbuf = (char *)xmalloc(BUFSIZE);
823     /* Handle various weird situations. */
824     if (past_unit == NULL)
825       return "???";
826     /* Decide how to identify the side. */
827     side2 = past_unit->side;
828     if (side2 == NULL) {
829 	sprintf(past_unitbuf, "the ");
830     } else if (side2 == side) {
831 	sprintf(past_unitbuf, "your ");
832     } else {
833 	/* If the side adjective is a genitive (ends in 's, s' or z')
834 	   we should skip the definite article. */
835 	int len = strlen(side_adjective(side2));
836 	char *end = side_adjective(side2) + len - 2;
837 
838 	if (strcmp(end, "'s") != 0
839 	    && strcmp(end, "s'") != 0
840 	    && strcmp(end, "z'") != 0)
841 	  sprintf(past_unitbuf, "the ");
842 	sprintf(past_unitbuf, "%s", side_adjective(side2));
843 	strcat(past_unitbuf, " ");
844     }
845     /* Now add the past_unit's unique description. */
846     utypename = u_type_name(past_unit->type);
847     if (past_unit->name) {
848 	tprintf(past_unitbuf, "%s %s", utypename, past_unit->name);
849     } else if (past_unit->number > 0) {
850 	tprintf(past_unitbuf, "%d%s %s",
851 		past_unit->number, ordinal_suffix(past_unit->number),
852 		utypename);
853     } else {
854 	strcat(past_unitbuf, utypename);
855     }
856     return past_unitbuf;
857 }
858 
859 /* Given a unit and optional type u, summarize construction status
860    and timing. */
861 
862 void
construction_desc(char * buf,Unit * unit,int u)863 construction_desc(char *buf, Unit *unit, int u)
864 {
865     int est, u2;
866     char ubuf[10], tmpbuf[100];
867     Task *task;
868     Unit *unit2;
869 
870     if (u != NONUTYPE) {
871 	est = est_completion_time(unit, u);
872 	if (est >= 0) {
873 	    sprintf(ubuf, "[%2d] ", est);
874 	} else {
875 	    strcpy(ubuf, " --  ");
876 	}
877     } else {
878 	ubuf[0] = '\0';
879     }
880     name_or_number(unit, tmpbuf);
881     sprintf(buf, "%s%s %s", ubuf, u_type_name(unit->type), tmpbuf);
882     pad_out(buf, 25);
883     if (unit->plan
884 	&& unit->plan->tasks) {
885 	task = unit->plan->tasks;
886 	if (task->type == TASK_BUILD) {
887 	    u2 = task->args[0];
888 	    tprintf(buf, " %s ", (is_unit_type(u2) ? u_type_name(u2) : "?"));
889 	    unit2 = find_unit(task->args[1]);
890 	    if (in_play(unit2) && unit2->type == u2) {
891 		tprintf(buf, "%d/%d done ", unit2->cp, u_cp(unit2->type));
892 	    }
893 	    tprintf(buf, "(%d of %d)", task->args[2] + 1, task->args[3]);
894 	} else if (task->type == TASK_DEVELOP) {
895 	    u2 = task->args[0];
896 	    if (is_unit_type(u2)) {
897 		tprintf(buf, " %s tech %d/%d",
898 			u_type_name(u2), unit->side->tech[u2], task->args[1]);
899 	    }
900 	}
901     }
902 }
903 
904 /* Given a unit and optional advance a, summarize research status
905    and timing. */
906 
907 void
research_desc(char * buf,Unit * unit,int a)908 research_desc(char *buf, Unit *unit, int a)
909 {
910     char abuf[10], tmpbuf[100];
911 
912     if (a != NONATYPE) {
913 	if (u_can_research(unit->type)) {
914 	    sprintf(abuf, "[%2d] ", a_rp(a));
915 	} else {
916 	    strcpy(abuf, " --  ");
917 	}
918     } else {
919 	abuf[0] = '\0';
920     }
921     name_or_number(unit, tmpbuf);
922     sprintf(buf, "%s%s %s", abuf, u_type_name(unit->type), tmpbuf);
923     pad_out(buf, 25);
924 }
925 
926 void
researchible_desc(char * buf,Unit * unit,int a)927 researchible_desc(char *buf, Unit *unit, int a)
928 {
929     char abuf[10];
930 
931     if (a != NONATYPE && unit != NULL) {
932 	if (u_advanced(unit->type)) {
933 	    sprintf(abuf, "[%2d] ", a_rp(a));
934 	} else {
935 	    strcpy(abuf, " --  ");
936 	}
937     } else {
938 	abuf[0] = '\0';
939     }
940     sprintf(buf, "%s%s", abuf, a_type_name(a));
941     pad_out(buf, 25);
942 }
943 
944 /* This generates a textual description of a type's construction info,
945    including estimated time for the given unit to build one, tooling &
946    tech, plus number of that type in existence already. */
947 
948 void
constructible_desc(char * buf,Side * side,int u,Unit * unit)949 constructible_desc(char *buf, Side *side, int u, Unit *unit)
950 {
951     char estbuf[20];
952     char techbuf[50];
953     char typenamebuf[50];
954     int est, tp, num;
955 
956     if (unit != NULL) {
957 	est = est_completion_time(unit, u);
958     	if (est >= 0) {
959 	    sprintf(estbuf, "[%2d] ", est);
960 	    if (uu_tp_to_build(unit->type, u) > 0) {
961 		tp = (unit->tooling ? unit->tooling[u] : 0);
962 		tprintf(estbuf, "(%2d) ", tp);
963 	    }
964     	} else {
965 	    strcpy(estbuf, " --  ");
966     	}
967     } else {
968 	estbuf[0] = '\0';
969     }
970     if (u_tech_max(u) > 0) {
971     	sprintf(techbuf, "[Tech %d/%d/%d] ",
972 		side->tech[u], u_tech_to_build(u), u_tech_max(u));
973     } else {
974 	techbuf[0] = '\0';
975     }
976     strcpy(typenamebuf, u_type_name(u));
977     /* If the single char for the type is different from the first character
978        of its type name, mention the char. */
979     if (!empty_string(u_uchar(u)) && (u_uchar(u))[0] != typenamebuf[0]) {
980 	tprintf(typenamebuf, "(%c)", (u_uchar(u))[0]);
981     }
982     sprintf(buf, "%s%s%-16.16s", estbuf, techbuf, typenamebuf);
983     num = num_units_in_play(side, u);
984     if (num > 0) {
985 	tprintf(buf, "  %3d", num);
986     } else {
987 	strcat(buf, "     ");
988     }
989     num = num_units_incomplete(side, u);
990     if (num > 0) {
991 	tprintf(buf, "(%d)", num);
992     }
993 }
994 
995 #if 0		/* Unused. */
996 
997 void
998 historical_event_date_desc(HistEvent *hevt, char *buf)
999 {
1000     sprintf(buf, "%d: ", hevt->startdate);
1001 }
1002 
1003 #endif
1004 
1005 int
find_event_type(Obj * sym)1006 find_event_type(Obj *sym)
1007 {
1008     int i;
1009 
1010     for (i = 0; hevtdefns[i].name != NULL; ++i) {
1011 	if (strcmp(c_string(sym), hevtdefns[i].name) == 0)
1012 	  return i;
1013     }
1014     return -1;
1015 }
1016 
1017 /* (should abstract out evt arg -> unit/pastunit description code) */
1018 
1019 void
historical_event_desc(Side * side,HistEvent * hevt,char * buf)1020 historical_event_desc(Side *side, HistEvent *hevt, char *buf)
1021 {
1022     int data0 = hevt->data[0];
1023     int data1 = hevt->data[1];
1024     int data2 = hevt->data[2];
1025     Obj *rest, *head, *pattern, *text;
1026     Unit *unit;
1027     PastUnit *pastunit, *pastunit2;
1028     Side *side2;
1029 
1030     for_all_list(g_event_narratives(), rest) {
1031 	head = car(rest);
1032 	if (consp(head)) {
1033 	    pattern = car(head);
1034 	    if (symbolp(pattern)
1035 		&& find_event_type(pattern) == hevt->type) {
1036 		text = cadr(head);
1037 		if (stringp(text)) {
1038 		    sprintf(buf, "%s", c_string(text));
1039 		} else {
1040 		    sprintlisp(buf, text, 50);
1041 		}
1042 		return;
1043 	    } else if (consp(pattern)
1044 		       && symbolp(car(pattern))
1045 		       && pattern_matches_event(pattern, hevt)
1046 		       ) {
1047 		text = cadr(head);
1048 		if (stringp(text)) {
1049 		    sprintf(buf, "%s", c_string(text));
1050 		} else {
1051 		    event_desc_from_list(side, text, hevt, buf);
1052 		}
1053 		return;
1054 	    }
1055 	}
1056     }
1057     /* Generate a default description of the event. */
1058     switch (hevt->type) {
1059       case H_LOG_STARTED:
1060 	sprintf(buf, "we started recording events");
1061 	break;
1062       case H_LOG_ENDED:
1063 	sprintf(buf, "we stopped recording events");
1064 	break;
1065       case H_GAME_STARTED:
1066 	sprintf(buf, "we started the game");
1067 	break;
1068       case H_GAME_SAVED:
1069 	sprintf(buf, "we saved the game");
1070 	break;
1071       case H_GAME_RESTARTED:
1072 	sprintf(buf, "we restarted the game");
1073 	break;
1074       case H_GAME_ENDED:
1075 	sprintf(buf, "we ended the game");
1076 	break;
1077       case H_SIDE_JOINED:
1078       	side2 = side_n(data0);
1079 	sprintf(buf, "%s joined the game",
1080 		(side == side2 ? "you" : side_name(side2)));
1081 	break;
1082       case H_SIDE_LOST:
1083       	side2 = side_n(data0);
1084 	sprintf(buf, "%s lost!", (side == side2 ? "you" : side_name(side2)));
1085 	/* Include an explanation of the cause, if there is one. */
1086 	if (data1 == -1) {
1087 	    tprintf(buf, " (resigned)");
1088 	} else if (data1 == -2) {
1089 	    tprintf(buf, " (self-unit died)");
1090 	} else if (data1 > 0) {
1091 	    tprintf(buf, " (scorekeeper %d)", data1);
1092 	} else {
1093 	    tprintf(buf, " (don't know why)");
1094 	}
1095 	break;
1096       case H_SIDE_WITHDREW:
1097       	side2 = side_n(data0);
1098 	sprintf(buf, "%s withdrew!", (side == side2 ? "you" : side_name(side2)));
1099 	break;
1100       case H_SIDE_WON:
1101       	side2 = side_n(data0);
1102 	sprintf(buf, "%s won!", (side == side2 ? "you" : side_name(side2)));
1103 	/* Include an explanation of the cause, if there is one. */
1104 	if (data1 > 0) {
1105 	    tprintf(buf, " (scorekeeper %d)", data1);
1106 	} else {
1107 	    tprintf(buf, " (don't know why)");
1108 	}
1109 	break;
1110       case H_UNIT_CREATED:
1111       	side2 = side_n(data0);
1112 	sprintf(buf, "%s created ",
1113 		(side == side2 ? "you" : side_name(side2)));
1114 	unit = find_unit(data1);
1115 	if (unit != NULL) {
1116 	    strcat(buf, unit_handle(side, unit));
1117 	} else {
1118 	    pastunit = find_past_unit(data1);
1119 	    if (pastunit != NULL) {
1120 		strcat(buf, past_unit_handle(side, pastunit));
1121 	    } else {
1122 		tprintf(buf, "%d??", data1);
1123 	    }
1124 	}
1125 	break;
1126       case H_UNIT_COMPLETED:
1127       	side2 = side_n(data0);
1128 	sprintf(buf, "%s completed ",
1129 		(side == side2 ? "you" : side_name(side2)));
1130 	unit = find_unit(data1);
1131 	if (unit != NULL) {
1132 	    strcat(buf, unit_handle(side, unit));
1133 	} else {
1134 	    pastunit = find_past_unit(data1);
1135 	    if (pastunit != NULL) {
1136 		strcat(buf, past_unit_handle(side, pastunit));
1137 	    } else {
1138 		tprintf(buf, "%d??", data1);
1139 	    }
1140 	}
1141 	break;
1142       case H_UNIT_DAMAGED:
1143 	unit = find_unit_dead_or_alive(data0);
1144 	if (unit != NULL) {
1145 	    strcpy(buf, unit_handle(side, unit));
1146 	} else {
1147  	    pastunit = find_past_unit(data0);
1148 	    if (pastunit != NULL) {
1149 		strcpy(buf, past_unit_handle(side, pastunit));
1150 	    } else {
1151 		sprintf(buf, "%d??", data0);
1152 	    }
1153 	}
1154 	tprintf(buf, " damaged (%d -> %d hp)", data1, hevt->data[2]);
1155 	break;
1156       case H_UNIT_CAPTURED:
1157 	buf[0] = '\0';
1158 	/* Note that the second optional value, if present, is the id
1159 	   of the unit that did the capturing. */
1160 	unit = find_unit_dead_or_alive(data1);
1161 	if (unit != NULL) {
1162 	    strcat(buf, unit_handle(side, unit));
1163 	} else {
1164  	    pastunit = find_past_unit(data1);
1165 	    if (pastunit != NULL) {
1166 		strcat(buf, past_unit_handle(side, pastunit));
1167 	    } else if (data1 == 0) {
1168 		tprintf(buf, "somebody");
1169 	    } else {
1170 		tprintf(buf, "%d??", data1);
1171 	    }
1172 	}
1173  	tprintf(buf, " captured ");
1174  	/* Describe the unit that was captured. */
1175 	unit = find_unit_dead_or_alive(data0);
1176 	if (unit != NULL) {
1177 	    strcat(buf, unit_handle(side, unit));
1178 	} else {
1179  	    pastunit = find_past_unit(data0);
1180 	    if (pastunit != NULL) {
1181 		strcat(buf, past_unit_handle(side, pastunit));
1182 	    } else {
1183 		tprintf(buf, "%d??", data0);
1184 	    }
1185 	}
1186 	break;
1187       case H_UNIT_SURRENDERED:
1188 	buf[0] = '\0';
1189  	/* Describe the unit that surrendered. */
1190 	unit = find_unit_dead_or_alive(data0);
1191 	if (unit != NULL) {
1192 	    strcat(buf, unit_handle(side, unit));
1193 	} else {
1194  	    pastunit = find_past_unit(data0);
1195 	    if (pastunit != NULL) {
1196 		strcat(buf, past_unit_handle(side, pastunit));
1197 	    } else {
1198 		tprintf(buf, "%d??", data0);
1199 	    }
1200 	}
1201  	tprintf(buf, " surrendered to ");
1202 	/* The second optional value, if present, is the id of the
1203 	   unit that accepted the surrender. */
1204 	unit = find_unit_dead_or_alive(data1);
1205 	if (unit != NULL) {
1206 	    strcat(buf, unit_handle(side, unit));
1207 	} else {
1208  	    pastunit = find_past_unit(data1);
1209 	    if (pastunit != NULL) {
1210 		strcat(buf, past_unit_handle(side, pastunit));
1211 	    } else if (data1 == 0) {
1212 		tprintf(buf, "somebody");
1213 	    } else {
1214 		tprintf(buf, "%d??", data1);
1215 	    }
1216 	}
1217 	break;
1218       case H_UNIT_ACQUIRED:
1219 	buf[0] = '\0';
1220 	if (data1 >= 0) {
1221 	    side2 = side_n(data1);
1222 	    strcat(buf, (side == side2 ? "you" : side_name(side2)));
1223 	} else {
1224 	    tprintf(buf, "somebody");
1225 	}
1226  	tprintf(buf, " acquired ");
1227  	/* Describe the unit that was acquired. */
1228 	unit = find_unit_dead_or_alive(data0);
1229 	if (unit != NULL) {
1230 	    strcat(buf, unit_handle(side, unit));
1231 	} else {
1232  	    pastunit = find_past_unit(data0);
1233 	    if (pastunit != NULL) {
1234 		strcat(buf, past_unit_handle(side, pastunit));
1235 	    } else {
1236 		tprintf(buf, "%d??", data0);
1237 	    }
1238 	}
1239 	break;
1240       case H_UNIT_REVOLTED:
1241 	buf[0] = '\0';
1242  	/* Describe the unit that revolted. */
1243 	unit = find_unit_dead_or_alive(data0);
1244 	if (unit != NULL) {
1245 	    strcat(buf, unit_handle(side, unit));
1246 	} else {
1247  	    pastunit = find_past_unit(data0);
1248 	    if (pastunit != NULL) {
1249 		strcat(buf, past_unit_handle(side, pastunit));
1250 	    } else {
1251 		tprintf(buf, "%d??", data0);
1252 	    }
1253 	}
1254  	tprintf(buf, " revolted");
1255  	if (data1 >= 0) {
1256 	    side2 = side_n(data1);
1257 	    tprintf(buf, ", went over to %s", (side == side2 ? "you" : side_name(side2)));
1258 	}
1259 	break;
1260       case H_UNIT_KILLED:
1261       case H_UNIT_DIED_IN_ACCIDENT:
1262       case H_UNIT_DIED_FROM_TEMPERATURE:
1263       case H_UNIT_DIED_FROM_ATTRITION:
1264 	/* Obviously, the unit mentioned here can only be a past unit. */
1265 	pastunit = find_past_unit(data0);
1266 	if (pastunit != NULL) {
1267 	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
1268 	} else {
1269 	    sprintf(buf, "%d??", data0);
1270 	}
1271 	if (hevt->type == H_UNIT_KILLED)
1272 	  tprintf(buf, " was destroyed");
1273 	else if (hevt->type == H_UNIT_DIED_IN_ACCIDENT)
1274 	  tprintf(buf, " died in an accident");
1275 	else if (hevt->type == H_UNIT_DIED_FROM_ATTRITION)
1276 	  tprintf(buf, " died in damaging terrain");
1277 	else
1278 	  tprintf(buf, " died from excessive temperature");
1279 	break;
1280       case H_UNIT_WRECKED:
1281       case H_UNIT_WRECKED_IN_ACCIDENT:
1282       case H_UNIT_WRECKED_FROM_ATTRITION:
1283 	pastunit = find_past_unit(data0);
1284 	if (pastunit != NULL) {
1285 	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
1286 	} else {
1287 	    sprintf(buf, "%d??", data0);
1288 	}
1289 	if (hevt->type == H_UNIT_WRECKED)
1290 	  tprintf(buf, " was wrecked");
1291 	else if (hevt->type == H_UNIT_WRECKED_FROM_ATTRITION)
1292 	  tprintf(buf, " was wrecked in damaging terrain");
1293 	else
1294 	  tprintf(buf, " was wrecked in an accident");
1295 	break;
1296       case H_UNIT_VANISHED:
1297 	pastunit = find_past_unit(data0);
1298 	if (pastunit != NULL) {
1299 	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
1300 	} else {
1301 	    sprintf(buf, "%d??", data0);
1302 	}
1303 	tprintf(buf, " vanished");
1304 	break;
1305       case H_UNIT_DISBANDED:
1306 	pastunit = find_past_unit(data0);
1307 	if (pastunit != NULL) {
1308 	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
1309 	} else {
1310 	    sprintf(buf, "%d??", data0);
1311 	}
1312 	tprintf(buf, " was disbanded");
1313 	break;
1314       case H_UNIT_GARRISONED:
1315 	pastunit = find_past_unit(data0);
1316 	if (pastunit != NULL) {
1317 	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
1318 	} else {
1319 	    sprintf(buf, "%d??", data0);
1320 	}
1321 	tprintf(buf, " was used to garrison ");
1322 	unit = find_unit(data1);
1323 	if (unit != NULL) {
1324 	    strcat(buf, unit_handle(side, unit));
1325 	} else {
1326 	    pastunit2 = find_past_unit(data1);
1327 	    if (pastunit2 != NULL) {
1328 		strcat(buf, past_unit_handle(side, pastunit2));
1329 	    } else {
1330 		/* Should never happen, but don't choke if it does. */
1331 		tprintf(buf, "?????");
1332 	    }
1333 	}
1334 	break;
1335       case H_UNIT_STARVED:
1336 	pastunit = find_past_unit(data0);
1337 	if (pastunit != NULL) {
1338 	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
1339 	} else {
1340 	    sprintf(buf, "%d??", data0);
1341 	}
1342 	tprintf(buf, " starved to death");
1343 	break;
1344       case H_UNIT_MERGED:
1345 	pastunit = find_past_unit(data0);
1346 	if (pastunit != NULL) {
1347 	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
1348 	} else {
1349 	    sprintf(buf, "%d??", data0);
1350 	}
1351 	tprintf(buf, " merged into another");
1352 	break;
1353       case H_UNIT_LEFT_WORLD:
1354 	pastunit = find_past_unit(data0);
1355 	if (pastunit != NULL) {
1356 	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
1357 	} else {
1358 	    sprintf(buf, "%d??", data0);
1359 	}
1360 	tprintf(buf, " left the world");
1361 	break;
1362       case H_UNIT_NAME_CHANGED:
1363 	pastunit = find_past_unit(data0);
1364 	if (pastunit != NULL) {
1365 	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
1366 	} else {
1367 	    sprintf(buf, "%d??", data0);
1368 	}
1369 	unit = find_unit(data1);
1370 	if (unit != NULL) {
1371 	    if (unit->name != NULL)
1372 	      tprintf(buf, " changed name to \"%s\"", unit->name);
1373 	    else
1374 	      tprintf(buf, " became anonymous");
1375 	} else {
1376 	    pastunit2 = find_past_unit(data1);
1377 	    if (pastunit2 != NULL) {
1378 		if (pastunit2->name != NULL)
1379 		  tprintf(buf, " changed name to \"%s\"", pastunit2->name);
1380 		else
1381 		  tprintf(buf, " became anonymous");
1382 	    } else
1383 	      tprintf(buf, " no name change???");
1384 	}
1385 	break;
1386       case H_UNIT_TYPE_CHANGED:
1387 	pastunit = find_past_unit(data0);
1388 	unit = find_unit(data1);
1389 	if (pastunit != NULL) {
1390 	    sprintf(buf, "%s", past_unit_handle(side, pastunit));
1391 	} else if (unit != NULL) {
1392 	    sprintf(buf, "%s", unit_handle(side, unit));
1393 	}
1394 	tprintf(buf, " changed into a %s", u_type_name(data2));
1395 	break;
1396       default:
1397 	/* Don't warn, will cause serious problems for windows that
1398 	   display lists of events, but make sure the non-understood event
1399 	   is obvious. */
1400 	sprintf(buf, "?????????? \"%s\" ??????????",
1401 		hevtdefns[hevt->type].name);
1402 	break;
1403     }
1404 }
1405 
1406 int
pattern_matches_event(Obj * pattern,HistEvent * hevt)1407 pattern_matches_event(Obj *pattern, HistEvent *hevt)
1408 {
1409     int data0, u;
1410     Obj *rest, *subpat;
1411     PastUnit *pastunit;
1412 
1413     if (find_event_type(car(pattern)) != hevt->type)
1414       return FALSE;
1415     data0 = hevt->data[0];
1416     for_all_list(cdr(pattern), rest) {
1417 	subpat = car(rest);
1418 	if (symbolp(subpat)) {
1419 	    switch (hevt->type) {
1420 	      case H_UNIT_STARVED:
1421 	      case H_UNIT_VANISHED:
1422 		u = utype_from_name(c_string(subpat));
1423 		pastunit = find_past_unit(data0);
1424 		if (pastunit != NULL && pastunit->type == u)
1425 		  return TRUE;
1426 		else
1427 		  return FALSE;
1428 		break;
1429 	      default:
1430 		return FALSE;
1431 	    }
1432 	} else {
1433 	    /* (should warn of bad pattern syntax?) */
1434 	    return FALSE;
1435 	}
1436     }
1437     return TRUE;
1438 }
1439 
1440 void
event_desc_from_list(Side * side,Obj * lis,HistEvent * hevt,char * buf)1441 event_desc_from_list(Side *side, Obj *lis, HistEvent *hevt, char *buf)
1442 {
1443     int n;
1444     Obj *rest, *item;
1445     PastUnit *pastunit;
1446 
1447     buf[0] = '\0';
1448     for_all_list(lis, rest) {
1449 	item = car(rest);
1450 	if (stringp(item)) {
1451 	    strcat(buf, c_string(item));
1452 	} else if (numberp(item)) {
1453 	    n = c_number(item);
1454 	    if (between(0, n, 3)) {
1455 		switch (hevt->type) {
1456 		  case H_UNIT_STARVED:
1457 		  case H_UNIT_VANISHED:
1458 		    pastunit = find_past_unit(hevt->data[0]);
1459 		    if (pastunit != NULL) {
1460 			strcat(buf, past_unit_handle(side, pastunit));
1461 		    } else {
1462 			tprintf(buf, "%d?", hevt->data[0]);
1463 		    }
1464 		    break;
1465 		  /* (should add other event types) */
1466 		  default:
1467 		    break;
1468 		}
1469 	    } else {
1470 		tprintf(buf, " ??%d?? ", n);
1471 	    }
1472 	} else {
1473 	    strcat(buf, " ????? ");
1474 	}
1475     }
1476 }
1477 
1478 /* Return a string describing the action's result. */
1479 
1480 char *
action_result_desc(int rslt)1481 action_result_desc(int rslt)
1482 {
1483     char *str;
1484 
1485     switch (rslt) {
1486       case A_ANY_OK:
1487 	str = "OK";
1488 	break;
1489       case A_ANY_DONE:
1490 	str = "done";
1491 	break;
1492       case A_ANY_CANNOT_DO:
1493 	str = "can never do";
1494 	break;
1495       case A_ANY_OCC_CANNOT_DO:
1496 	str = "occupant cannot fight";
1497 	break;
1498       case A_ANY_NO_ACP:
1499 	str = "insufficient acp";
1500 	break;
1501       case A_ANY_NO_MATERIAL:
1502 	str = "insufficient material";
1503 	break;
1504       case A_ANY_NO_AMMO:
1505 	str = "insufficient ammo";
1506 	break;
1507       case A_ANY_TOO_FAR:
1508 	str = "too far";
1509 	break;
1510       case A_ANY_TOO_NEAR:
1511 	str = "too near";
1512 	break;
1513       case A_MOVE_NO_MP:
1514 	str = "insufficient mp";
1515 	break;
1516       case A_MOVE_BLOCKING_ZOC:
1517 	str = "blocking ZOC present";
1518 	break;
1519       case A_MOVE_CANNOT_LEAVE_WORLD:
1520 	str = "cannot leave world";
1521 	break;
1522       case A_MOVE_DEST_FULL:
1523 	str = "destination full";
1524 	break;
1525       case A_CONSTRUCT_NO_TOOLING:
1526 	str = "insufficient tooling to construct";
1527 	break;
1528       case A_ATTACK_CANNOT_HIT:
1529 	str = "attack cannot hit or damage target";
1530 	break;
1531       case A_OVERRUN_CANNOT_HIT:
1532 	str = "overrun cannot work";
1533 	break;
1534       case A_OVERRUN_FAILED:
1535 	str = "overrun failed";
1536 	break;
1537       case A_OVERRUN_SUCCEEDED:
1538 	str = "overrun succeeded";
1539 	break;
1540       case A_FIRE_BLOCKED:
1541 	str = "fire blocked by terrain";
1542 	break;
1543       case A_FIRE_CANNOT_HIT:
1544 	str = "fire cannot hit or damage target";
1545 	break;
1546       case A_FIRE_INTO_OUTSIDE_WORLD:
1547 	str = "cannot fire outside world";
1548 	break;
1549       case A_CAPTURE_FAILED:
1550 	str = "capture failed";
1551 	break;
1552       case A_CAPTURE_SUCCEEDED:
1553 	str = "capture succeeded";
1554 	break;
1555       case A_ANY_ERROR:
1556 	str = "misc error";
1557 	break;
1558       default:
1559 	if (between(0, rslt, NUMHEVTTYPES - 1))
1560 	  str = hevtdefns[rslt].name;
1561 	else
1562 	  /* This should never happen. */
1563 	  str = "???";
1564 	break;
1565     }
1566     return str;
1567 }
1568 
1569 /* Explain the reason for failure to move into a cell. */
1570 
1571 void
advance_failure_desc(char * buf,Unit * unit,HistEventType reason)1572 advance_failure_desc(char *buf, Unit *unit, HistEventType reason)
1573 {
1574     /* impl_move_to has already returned a more detailed error
1575     message, so we want to suppresss this one. */
1576     if (reason == A_MOVE_NO_PATH) {
1577 	strcpy(buf, "");
1578 	return;
1579     }
1580     strcpy(buf, unit_handle(unit->side, unit));
1581     if (reason == A_ANY_CANNOT_DO)
1582       strcat(buf, " can never do this!");
1583     else if (reason == A_ANY_NO_ACP)
1584       strcat(buf, " does not have enough ACP!");
1585     else if (reason == A_ANY_NO_MATERIAL)
1586       strcat(buf, " is out of some material!");
1587     else if (reason == A_ANY_NO_AMMO)
1588       strcat(buf, " is out of ammo!");
1589     else if (reason == A_MOVE_NO_MP)
1590       /* Player doesn't see mp calcs, so explain as ACP */
1591       strcat(buf, " does not have enough ACP!");
1592     else if (reason == A_MOVE_CANNOT_LEAVE_WORLD)
1593       strcat(buf, " can never leave the world!");
1594     else if (reason == A_MOVE_BLOCKING_ZOC)
1595       strcat(buf, " cannot enter a blocking ZOC!");
1596     else if (reason == A_MOVE_DEST_FULL)
1597       strcat(buf, " cannot fit into the destination!");
1598     else if (reason != H_UNDEFINED)
1599       sprintf(buf, " cannot act, reason is %s", action_result_desc(reason));
1600     else
1601       strcat(buf, " is unable to act, don't know why");
1602 }
1603 
1604 /* Generate a description of the borders and connections in and around
1605    a location. */
1606 
1607 void
linear_desc(char * buf,int x,int y)1608 linear_desc(char *buf, int x, int y)
1609 {
1610     int t, first = TRUE;
1611 
1612     buf[0] = '\0';
1613     if (any_aux_terrain_defined()) {
1614 	for_all_terrain_types(t) {
1615 	    if (t_is_border(t)
1616 		&& aux_terrain_defined(t)
1617 		&& any_borders_at(x, y, t)) {
1618 
1619 #if 0 /* Confusing. */
1620 		if (first) {
1621 		    strcat(buf, " at ");
1622 		    first = FALSE;
1623 		} else {
1624 		    strcat(buf, ",");
1625 		}
1626 		strcat(buf, t_type_name(t));
1627 #endif
1628 
1629 #if 0 /* takes up more space, but not very useful */
1630 		for_all_directions(dir) {
1631 		    if (border_at(x, y, dir, t)) {
1632 			tprintf(buf, "/%s", dirnames[dir]);
1633 		    }
1634 		}
1635 #endif
1636 
1637 	    } else if (t_is_connection(t)
1638 		       && aux_terrain_defined(t)
1639 		       && any_connections_at(x, y, t)) {
1640 		if (first) {
1641 		    strcat(buf, " + ");
1642 		    first = FALSE;
1643 		} else {
1644 		    strcat(buf, ",");
1645 		}
1646 		strcat(buf, t_type_name(t));
1647 #if 0
1648 		for_all_directions(dir) {
1649 		    if (connection_at(x, y, dir, t)) {
1650 			tprintf(buf, "/%s", dirnames[dir]);
1651 		    }
1652 		}
1653 #endif
1654 	    }
1655 	}
1656     }
1657 }
1658 
1659 void
elevation_desc(char * buf,int x,int y)1660 elevation_desc(char *buf, int x, int y)
1661 {
1662     if (elevations_defined()) {
1663 	sprintf(buf, "(Elev %d)", elev_at(x, y));
1664     }
1665 }
1666 
1667 char *
feature_desc(Feature * feature,char * buf)1668 feature_desc(Feature *feature, char *buf)
1669 {
1670     int i, caps = FALSE;
1671     char *str;
1672 
1673     if (feature == NULL)
1674       return NULL;
1675     if (feature->name) {
1676 	/* Does the name need any substitutions done? */
1677 	if (strchr(feature->name, '%')) {
1678 	    i = 0;
1679 	    for (str = feature->name; *str != '\0'; ++str) {
1680 	    	if (*str == '%') {
1681 		    /* Interpret substitution directives. */
1682 		    switch (*(str + 1)) {
1683 		      case 'T':
1684 			caps = TRUE;
1685 		      case 't':
1686 			if (feature->feattype) {
1687 			    buf[i] = '\0';
1688 			    strcat(buf, feature->feattype);
1689 			    if (caps)
1690 			      capitalize(buf);
1691 			    i = strlen(buf);
1692 			}
1693 			++str;
1694 			break;
1695 		      default:
1696 			break;
1697 		    }
1698 	    	} else {
1699 		    buf[i++] = *str;
1700 	    	}
1701 	    }
1702 	    /* Close off the string. */
1703 	    buf[i] = '\0';
1704 	    return buf;
1705 	} else {
1706 	    /* Return the name alone. */
1707 	    return feature->name;
1708 	}
1709     } else {
1710 	if (feature->feattype) {
1711 	    strcpy(buf, "unnamed ");
1712 	    strcat(buf, feature->feattype);
1713 	    return buf;
1714 	}
1715     }
1716     /* No description of the location is available. */
1717     return "anonymous feature";
1718 }
1719 
1720 /* Generate a string describing what is at the given location. */
1721 
1722 char *
feature_name_at(int x,int y)1723 feature_name_at(int x, int y)
1724 {
1725     int fid = (features_defined() ? raw_feature_at(x, y) : 0);
1726     Feature *feature;
1727 
1728     if (fid == 0)
1729       return NULL;
1730     feature = find_feature(fid);
1731     if (feature != NULL) {
1732 	if (featurebuf == NULL)
1733 	  featurebuf = (char *)xmalloc(BUFSIZE);
1734 	return feature_desc(feature, featurebuf);
1735     }
1736     /* No description of the location is available. */
1737     return NULL;
1738 }
1739 
1740 void
temperature_desc(char * buf,int x,int y)1741 temperature_desc(char *buf, int x, int y)
1742 {
1743     if (temperatures_defined()) {
1744 	sprintf(buf, "(Temp %d)", temperature_at(x, y));
1745     }
1746 }
1747 
1748 #if 0
1749     int age, u;
1750     short view, prevview;
1751     Side *side2;
1752 
1753     /* Compose and display view history of this cell. */
1754     Dprintf("Drawing previous view info\n");
1755     age = side_view_age(side, curx, cury);
1756     prevview = side_prevview(side, curx, cury);
1757     if (age == 0) {
1758 	if (prevview != view) {
1759 	    if (prevview == EMPTY) {
1760 		/* misleading if prevview was set during init. */
1761 		sprintf(tmpbuf, "Up to date; had been empty.");
1762 	    } else if (prevview == UNSEEN) {
1763 		sprintf(tmpbuf, "Up to date; had been unexplored.");
1764 	    } else {
1765 		side2 = side_n(vside(prevview));
1766 		u = vtype(prevview);
1767 		if (side2 != side) {
1768 		    sprintf(tmpbuf, "Up to date; had seen %s %s.",
1769 			    (side2 == NULL ? "independent" :
1770 			     side_name(side2)),
1771 			    u_type_name(u));
1772 		} else {
1773 		    sprintf(tmpbuf,
1774 			    "Up to date; had been occupied by your %s.",
1775 			    u_type_name(u));
1776 		}
1777 	    }
1778 	} else {
1779 	    sprintf(tmpbuf, "Up to date.");
1780 	}
1781     } else {
1782 	if (prevview == EMPTY) {
1783 	    sprintf(tmpbuf, "Was empty %d turns ago.", age);
1784 	} else if (prevview == UNSEEN) {
1785 	    sprintf(tmpbuf, "Terrain first seen %d turns ago.", age);
1786 	} else {
1787 	    side2 = side_n(vside(prevview));
1788 	    u = vtype(prevview);
1789 	    if (side2 != side) {
1790 		sprintf(tmpbuf, "Saw %s %s, %d turns ago.",
1791 			(side2 == NULL ? "independent" :
1792 			 side_name(side2)),
1793 			u_type_name(u), age);
1794 	    } else {
1795 		sprintf(tmpbuf, "Was occupied by your %s %d turns ago.",
1796 			u_type_name(u), age);
1797 	    }
1798 	}
1799     }
1800 #endif
1801 
1802 void
size_desc(char * buf,Unit * unit,int label)1803 size_desc(char *buf, Unit *unit, int label)
1804 {
1805     buf[0] = '\0';
1806     if (!u_advanced(unit->type))
1807       return;
1808     tprintf(buf, "(%d)", unit->size);
1809 }
1810 
1811 void
hp_desc(char * buf,Unit * unit,int label)1812 hp_desc(char *buf, Unit *unit, int label)
1813 {
1814     if (label) {
1815 	sprintf(buf, "HP ");
1816     } else {
1817 	buf[0] = '\0';
1818     }
1819     /* (print '-' or some such for zero hp case?) */
1820     if (unit->hp == u_hp(unit->type)) {
1821 	tprintf(buf, "%d", unit->hp);
1822     } else {
1823 	tprintf(buf, "%d/%d", unit->hp, u_hp(unit->type));
1824     }
1825 }
1826 
1827 void
acp_desc(char * buf,Unit * unit,int label)1828 acp_desc(char *buf, Unit *unit, int label)
1829 {
1830     int u = unit->type;
1831 
1832     if (!completed(unit)) {
1833 	sprintf(buf, "%d/%d done", unit->cp, u_cp(u));
1834     } else if (unit->act && new_acp_for_turn(unit) > 0) {
1835     	if (label) {
1836     	    strcpy(buf, "ACP ");
1837     	} else {
1838 	    buf[0] = '\0';
1839     	}
1840 	if (unit->act->acp == unit->act->initacp) {
1841 	    tprintf(buf, "%d", unit->act->acp);
1842 	} else {
1843 	    tprintf(buf, "%d/%d", unit->act->acp, unit->act->initacp);
1844 	}
1845     } else {
1846 	buf[0] = '\0';
1847     }
1848 }
1849 
1850 /* Describe a unit's current combat experience, if applicable. */
1851 
1852 void
cxp_desc(char * buf,Unit * unit,int label)1853 cxp_desc(char *buf, Unit *unit, int label)
1854 {
1855     int cxpmax = u_cxp_max(unit->type);
1856 
1857     buf[0] = '\0';
1858     if (cxpmax == 0)
1859       return;
1860     if (label)
1861       strcat(buf, "  cXP ");
1862     if (unit->cxp == cxpmax) {
1863 	tprintf(buf, "%d", unit->cxp);
1864     } else {
1865 	tprintf(buf, "%d/%d", unit->cxp, cxpmax);
1866     }
1867 }
1868 
1869 /* Describe a unit's current morale, if applicable. */
1870 
1871 void
morale_desc(char * buf,Unit * unit,int label)1872 morale_desc(char *buf, Unit *unit, int label)
1873 {
1874     int moralemax = u_morale_max(unit->type);
1875 
1876     buf[0] = '\0';
1877     if (moralemax == 0)
1878       return;
1879     if (label)
1880       strcat(buf, "  Mor ");
1881     if (unit->morale == moralemax) {
1882 	tprintf(buf, "%d", unit->morale);
1883     } else {
1884 	tprintf(buf, "%d/%d", unit->morale, moralemax);
1885     }
1886 }
1887 
1888 /* Describe a unit's individual point value if it has one. */
1889 
1890 void
point_value_desc(char * buf,Unit * unit,int label)1891 point_value_desc(char *buf, Unit *unit, int label)
1892 {
1893     buf[0] = '\0';
1894     if (unit_point_value(unit) < 0)
1895       return;
1896     if (label)
1897       strcat(buf, "  Value ");
1898     tprintf(buf, "%d", unit_point_value(unit));
1899 }
1900 
1901 /* Describe one "row" (group of 3) of a unit's supply status. */
1902 
1903 int
supply_desc(char * buf,Unit * unit,int mrow)1904 supply_desc(char *buf, Unit *unit, int mrow)
1905 {
1906     int u = unit->type, m, mm, tmprow;
1907 
1908     tmprow = 0;
1909     buf[0] = '\0';
1910     mm = 0;
1911     for_all_material_types(m) {
1912 	if (um_storage_x(u, m) > 0) {
1913 	    if (mm > 0 && mm % 3 == 0)
1914 	      ++tmprow;
1915 	    if (tmprow == mrow) {
1916 		/* Don't print out storage space if it is set to arbitrarily large
1917 		(9999, 999 or 99) as in many games. */
1918 	    	if (um_storage_x(u, m) == 9999
1919 	    	    || um_storage_x(u, m) == 999
1920 	    	    || um_storage_x(u, m) == 99) {
1921 			tprintf(buf, "%s %d  ",
1922 				m_type_name(m), unit->supply[m]);
1923 		} else {
1924 			tprintf(buf, "%s %d/%d  ",
1925 				m_type_name(m), unit->supply[m], um_storage_x(u, m));
1926 	    	}
1927 	    }
1928 	    ++mm;
1929 	}
1930     }
1931     return (strlen(buf) > 0);
1932 }
1933 
1934 /* Describe a builder's current tooling. */
1935 
1936 int
tooling_desc(char * buf,Unit * unit)1937 tooling_desc(char *buf, Unit *unit)
1938 {
1939     int u2, num;
1940 
1941     buf[0] = '\0';
1942     if (unit->tooling == NULL)
1943       return FALSE;
1944     /* Use the number of nonzero tooling to govern the use of
1945        abbreviations. */
1946     num = 0;
1947     for_all_unit_types(u2)
1948       if (unit->tooling[u2] > 0)
1949 	++num;
1950     if (num == 0) {
1951 	strcat(buf, "No tooling");
1952 	return TRUE;
1953     }
1954     strcat(buf, "Tooling: ");
1955     for_all_unit_types(u2) {
1956 	if (unit->tooling[u2] > 0) {
1957 	    tprintf(buf, "%s %d/%d  ",
1958 		    (num > 2 ? shortest_unique_name(u2) : u_type_name(u2)),
1959 		    unit->tooling[u2], uu_tp_to_build(unit->type, u2));
1960 	}
1961     }
1962     return TRUE;
1963 }
1964 
1965 void
location_desc(char * buf,Side * side,Unit * unit,int u,int x,int y)1966 location_desc(char *buf, Side *side, Unit *unit, int u, int x, int y)
1967 {
1968     int t = terrain_at(x, y);
1969     char *featurename;
1970 
1971     if (unit != NULL && unit->transport != NULL) {
1972 	sprintf(buf, "In %s", short_unit_handle(unit->transport));
1973     } else if (unit != NULL || u != NONUTYPE) {
1974 	sprintf(buf, "In %s", t_type_name(t));
1975 	linear_desc(buf + strlen(buf), x, y);
1976     } else if (terrain_visible(side, x, y)) {
1977 	sprintf(buf, "Empty %s", t_type_name(t));
1978 	linear_desc(buf + strlen(buf), x, y);
1979     } else {
1980 	sprintf(buf, "Unknown");
1981     }
1982     if (terrain_visible(side, x, y)) {
1983 	featurename = feature_name_at(x, y);
1984 	if (!empty_string(featurename))
1985 	  tprintf(buf, " (%s)", featurename);
1986 	if (elevations_defined())
1987 	  tprintf(buf, " (El %d)", elev_at(x, y));
1988 	if (temperatures_defined())
1989 	  tprintf(buf, " (T %d)", temperature_at(x, y));
1990 	if (winds_defined()) {
1991 	    int wforce = wind_force_at(x, y);
1992 
1993 	    if (wforce == 0)
1994 	      tprintf(buf, " (W calm)");
1995 	    else
1996 	      tprintf(buf, " (W f%d %s)", wforce, dirnames[wind_dir_at(x, y)]);
1997 	}
1998 	/* (should optionally list other local weather also) */
1999     }
2000     tprintf(buf, " at %d,%d", x, y);
2001 }
2002 
2003 /* Given a cell, describe where it is. */
2004 
2005 void
destination_desc(char * buf,Side * side,Unit * unit,int x,int y,int z)2006 destination_desc(char *buf, Side *side, Unit *unit, int x, int y, int z)
2007 {
2008     int dir, x1, y1;
2009     Unit *unit2;
2010 
2011     if (!in_area(x, y)) {
2012 	sprintf(buf, "?%d,%d?", x, y);
2013 	return;
2014     }
2015     unit2 = unit_at(x, y);
2016     if (unit2 != NULL && unit2->side == side) {
2017 	if (unit2 == unit) {
2018 	    sprintf(buf, "self (%d,%d)", x, y);
2019 	} else if (!empty_string(unit2->name)) {
2020 	    sprintf(buf, "%s (%d,%d)", unit2->name, x, y);
2021 	} else {
2022 	    sprintf(buf, "%s (%d,%d)", u_type_name(unit2->type), x, y);
2023 	}
2024 	return;
2025     }
2026     for_all_directions(dir) {
2027 	if (interior_point_in_dir(x, y, dir, &x1, &y1)) {
2028 	    unit2 = unit_at(x1, y1);
2029 	    /* (should look at stack) */
2030 	    if (unit2 != NULL && unit2->side == side) {
2031 		if (unit2 == unit) {
2032 		    sprintf(buf, "%s (%d,%d)",
2033 			    dirnames[opposite_dir(dir)], x, y);
2034 		} else if (!empty_string(unit2->name)) {
2035 		    sprintf(buf, "%s of %s (%d,%d)",
2036 			    dirnames[opposite_dir(dir)], unit2->name, x, y);
2037 		} else {
2038 		    sprintf(buf, "%s of %s (%d,%d)",
2039 			    dirnames[opposite_dir(dir)],
2040 			    u_type_name(unit2->type), x, y);
2041 		}
2042 		return;
2043 	    }
2044 	}
2045     }
2046     /* This is the old reliable case. */
2047     sprintf(buf, "%d,%d", x, y);
2048     if (z != 0)
2049       tprintf(buf, ",%d", z);
2050 }
2051 
2052 void
latlong_desc(char * buf,int x,int y,int xf,int yf,int which)2053 latlong_desc(char *buf, int x, int y, int xf, int yf, int which)
2054 {
2055     char minbuf[10];
2056     int rawlat, latdeg, latmin, rawlon, londeg, lonmin;
2057 
2058     buf[0] = '\0';
2059     if (world.circumference <= 0)
2060       return;
2061     xy_to_latlong(x, y, xf, yf, &rawlat, &rawlon);
2062     if (which & 2) {
2063 	latdeg = abs(rawlat) / 60;
2064 	latmin = abs(rawlat) % 60;
2065 	minbuf[0] = '\0';
2066 	if (latmin != 0)
2067 	  sprintf(minbuf, "%dm", latmin);
2068 	sprintf(buf, "%dd%s %c",
2069 		latdeg, minbuf, (rawlat >= 0 ? 'N' : 'S'));
2070     }
2071     if (which & 1) {
2072 	londeg = abs(rawlon) / 60;
2073 	lonmin = abs(rawlon) % 60;
2074 	minbuf[0] = '\0';
2075 	if (lonmin != 0)
2076 	  sprintf(minbuf, "%dm", lonmin);
2077 	if (!empty_string(buf))
2078 	  strcat(buf, " ");
2079 	sprintf(buf + strlen(buf), "%dd%s %c",
2080 		londeg, minbuf, (rawlon >= 0 ? 'E' : 'W'));
2081     }
2082 }
2083 
2084 static int *ohd_nums;
2085 static int *ohd_incomplete;
2086 
2087 void
others_here_desc(char * buf,Unit * unit)2088 others_here_desc(char *buf, Unit *unit)
2089 {
2090     int u2, first = TRUE;
2091     Unit *unit2, *top;
2092 
2093     if (ohd_nums == NULL)
2094       ohd_nums = (int *) xmalloc(numutypes * sizeof(int));
2095     if (ohd_incomplete == NULL)
2096       ohd_incomplete = (int *) xmalloc(numutypes * sizeof(int));
2097     buf[0] = '\0';
2098     top = unit_at(unit->x, unit->y);
2099     if (top != NULL && top->nexthere != NULL) {
2100 	for_all_unit_types(u2)
2101 	  ohd_nums[u2] = ohd_incomplete[u2] = 0;
2102 	for_all_stack(unit->x, unit->y, unit2)
2103 	  if (completed(unit2))
2104 	    ++ohd_nums[unit2->type];
2105 	  else
2106 	    ++ohd_incomplete[unit2->type];
2107 	/* Don't count ourselves. */
2108 	if (completed(unit))
2109 	  --ohd_nums[unit->type];
2110 	else
2111 	  --ohd_incomplete[unit->type];
2112 	for_all_unit_types(u2) {
2113 	    if (ohd_nums[u2] > 0 || ohd_incomplete[u2] > 0) {
2114 		if (first)
2115 		  first = FALSE;
2116 		else
2117 		  strcat(buf, " ");
2118 		if (ohd_nums[u2] > 0)
2119 		  tprintf(buf, "%d", ohd_nums[u2]);
2120 		if (ohd_incomplete[u2] > 0)
2121 		  tprintf(buf, "(%d)", ohd_incomplete[u2]);
2122 		strcat(buf, " ");
2123 		strcat(buf, shortest_generic_name(u2));
2124 	    }
2125 	}
2126 	strcat(buf, " here also");
2127     }
2128 }
2129 
2130 void
occupants_desc(char * buf,Unit * unit)2131 occupants_desc(char *buf, Unit *unit)
2132 {
2133     int u2, first = TRUE;
2134     Unit *occ;
2135 
2136     if (ohd_nums == NULL)
2137       ohd_nums = (int *) xmalloc(numutypes * sizeof(int));
2138     if (ohd_incomplete == NULL)
2139       ohd_incomplete = (int *) xmalloc(numutypes * sizeof(int));
2140     buf[0] = '\0';
2141     if (unit->occupant != NULL) {
2142 	strcat(buf, "Occs ");
2143 	for_all_unit_types(u2)
2144 	  ohd_nums[u2] = ohd_incomplete[u2] = 0;
2145 	for_all_occupants(unit, occ)
2146 	  if (completed(occ))
2147 	    ++ohd_nums[occ->type];
2148 	  else
2149 	    ++ohd_incomplete[occ->type];
2150 	for_all_unit_types(u2) {
2151 	    if (ohd_nums[u2] > 0 || ohd_incomplete[u2] > 0) {
2152 		if (first)
2153 		  first = FALSE;
2154 		else
2155 		  strcat(buf, " ");
2156 		if (ohd_nums[u2] > 0)
2157 		  tprintf(buf, "%d", ohd_nums[u2]);
2158 		if (ohd_incomplete[u2] > 0)
2159 		  tprintf(buf, "(%d)", ohd_incomplete[u2]);
2160 		strcat(buf, " ");
2161 		strcat(buf, shortest_generic_name(u2));
2162 	    }
2163 	}
2164     }
2165 }
2166 
2167 void
oprole_desc(char * buf,Xconq::AI::OpRole * oprole)2168 oprole_desc(char *buf, Xconq::AI::OpRole *oprole)
2169 {
2170     using namespace Xconq::AI;
2171 
2172     // Return, if no oprole.
2173     if (!oprole || (OR_NONE == oprole->type)) {
2174 	buf[0] = 0;
2175 	return;
2176     }
2177     // Mention that this is an oprole.
2178     strncat(buf, "Operational Role: ", BUFSIZE);
2179     // Mention which type of oprole.
2180     switch (oprole->type) {
2181 	case OR_CONSTRUCTOR:
2182 	    strncat(buf, "Constructor/Repairer", BUFSIZE - strlen(buf));
2183 	    break;
2184 	case OR_SHUTTLE:
2185 	    strncat(buf, "Shuttle", BUFSIZE - strlen(buf));
2186 	    break;
2187 	default:
2188 	    strncat(
2189 		buf, "Unknown! (Weird, report this please.)",
2190 		BUFSIZE - strlen(buf));
2191 	    break;
2192     }
2193 }
2194 
2195 /* Fill the given buffer with a verbal description of the unit's
2196    current plan. */
2197 
2198 void
plan_desc(char * buf,Unit * unit)2199 plan_desc(char *buf, Unit *unit)
2200 {
2201     using namespace Xconq::AI;
2202 
2203     char goalbuf[BUFSIZE];
2204     int i;
2205     Plan *plan = unit->plan;
2206     Task *task = NULL;
2207     OpRole *oprole = NULL;
2208 
2209     if (plan == NULL) {
2210 	buf[0] = '\0';
2211     	return;
2212     }
2213     oprole = find_oprole(unit->side, unit->id);
2214     if (oprole && (PLAN_PASSIVE == plan->type))
2215 	oprole_desc(buf, oprole);
2216     else
2217 	sprintf(buf, "Plan: %s", plantypenames[plan->type]);
2218     if (plan->waitingfortasks)
2219       strcat(buf, " Waiting");
2220     if (plan->asleep)
2221       strcat(buf, " Asleep");
2222     if (plan->reserve)
2223       strcat(buf, " Reserve");
2224     if (plan->delayed)
2225       strcat(buf, " Delay");
2226     /* The more usual case is to allow AI control of units, so we only
2227        mention it when it's off. */
2228     if (!plan->aicontrol)
2229       strcat(buf, " NoAI");
2230     if (plan->supply_is_low)
2231       strcat(buf, " SupplyLow");
2232     if (plan->waitingfortransport)
2233       strcat(buf, " WaitingForTransport");
2234     if (plan->maingoal) {
2235 	strcat(buf, ". Goal: ");
2236 	strcat(buf, goal_desc(goalbuf, plan->maingoal));
2237     }
2238     if (plan->formation) {
2239 	strcat(buf, ". Formation: ");
2240 	strcat(buf, goal_desc(goalbuf, plan->formation));
2241     }
2242     /* For tasks, just put out a count. */
2243     if (plan->tasks) {
2244 	i = 0;
2245 	for_all_tasks(plan, task)
2246 	  ++i;
2247 	tprintf(buf, ". %d task%s", i, (i == 1 ? "" : "s"));
2248     }
2249     strcat(buf, ".");
2250 }
2251 
2252 /* Describe a task in a brief but intelligible way.  Note that the
2253    given side and/or unit may be NULL, such as when describing a task
2254    not yet assigned to a unit (standing orders for instance). */
2255 
2256 void
task_desc(char * buf,Side * side,Unit * unit,Task * task)2257 task_desc(char *buf, Side *side, Unit *unit, Task *task)
2258 {
2259     int i, slen, arg0, arg1, arg2, arg3, arg4, tp;
2260     char *argtypes, *sidedesc, *utypedesc;
2261     Unit *unit2;
2262 
2263     if (task == NULL) {
2264 	buf[0] = '\0';
2265 	return;
2266     }
2267     arg0 = task->args[0];
2268     arg1 = task->args[1];
2269     arg2 = task->args[2];
2270     arg3 = task->args[3];
2271     arg4 = task->args[4];
2272     sprintf(buf, "%s ", taskdefns[task->type].display_name);
2273     switch (task->type) {
2274       case TASK_BUILD:
2275 	if (arg0 != 0) {
2276 	    unit2 = find_unit(arg0);
2277 	    if (unit2 != NULL) {
2278 		tprintf(
2279 		    buf, "%s: %d/%d done", medium_long_unit_handle(unit2),
2280 		    unit2->cp, u_cp(unit2->type));
2281 	    }
2282 	}
2283 #if (0)
2284 	else if (is_unit_type(arg0)) {
2285 	    tp = (unit->tooling ? unit->tooling[arg0] : 0);
2286 	    if (tp < uu_tp_to_build(unit->type, arg0)) {
2287 		tprintf(buf, " (tooling up: %d/%d)", tp,
2288 			uu_tp_to_build(unit->type, arg0));
2289 	    }
2290 	}
2291 	if (arg3 > 1) {
2292 	    tprintf(buf, " (%d%s of %d)", arg2 + 1, ordinal_suffix(arg2 + 1),
2293 		    arg3);
2294 	}
2295 #endif
2296 	break;
2297       case TASK_CAPTURE:
2298 	sidedesc = (arg3 == ALLSIDES ? (char *)"any"
2299 				     : side_adjective(side_n(arg3)));
2300 	utypedesc = (arg2 == NONUTYPE ? (char *)"unit" : u_type_name(arg2));
2301 	tprintf(buf, "%s %s ", sidedesc, utypedesc);
2302 	unit2 = unit_at(arg0, arg1);
2303 	/* (should scan stack, only list a visible unit) */
2304 	if (unit2 != NULL
2305 	    && unit2->side == side_n(arg3)
2306 	    && !empty_string(unit2->name)) {
2307 	    tprintf(buf, "%s ", unit2->name);
2308 	    tprintf(buf, "(%d,%d)", arg0, arg1);
2309 	} else {
2310 	    tprintf(buf, "at %d,%d", arg0, arg1);
2311 	}
2312 	break;
2313       case TASK_COLLECT:
2314 	tprintf(buf, "%s around ", m_type_name(arg0));
2315 	tprintf(buf, "%d,%d", arg1, arg2);
2316 	break;
2317       case TASK_CONSTRUCT:
2318 	tprintf(buf, "%d %s ", arg1, u_type_name(arg0));
2319 	unit2 = find_unit(arg2);
2320 	if (in_play(unit2))
2321 	    tprintf(buf, "in %s",
2322 		    unit == unit2 ? "self" : medium_long_unit_handle(unit2));
2323 	else
2324 	    tprintf(buf, "at %d,%d", arg3, arg4);
2325 	break;
2326       case TASK_DEVELOP:
2327 	tprintf(buf, "tech for %s: %d/%d", u_type_name(arg0),
2328 		(side ? side->tech[arg0] : 0), arg1);
2329 	break;
2330       case TASK_HIT_POSITION:
2331 	tprintf(buf, "at %d,%d", arg0, arg1);
2332 	break;
2333       case TASK_HIT_UNIT:
2334 	sidedesc = (arg3 == ALLSIDES ? (char *)"any"
2335 				     : side_adjective(side_n(arg3)));
2336 	utypedesc = (arg2 == NONUTYPE ? (char *)"unit" : u_type_name(arg2));
2337 	tprintf(buf, "%s %s ", sidedesc, utypedesc);
2338 	unit2 = unit_at(arg0, arg1);
2339 	if (unit2 != NULL
2340 	    && unit2->side == side_n(arg3)
2341 	    && unit2->type == arg2
2342 	    && !empty_string(unit2->name)) {
2343 	    tprintf(buf, "%s ", unit2->name);
2344 	    tprintf(buf, "(%d,%d)", arg0, arg1);
2345 	} else {
2346 	    tprintf(buf, "at %d,%d", arg0, arg1);
2347 	}
2348 	break;
2349       case TASK_MOVE_DIR:
2350 	tprintf(buf, "%s, %d times", dirnames[arg0], arg1);
2351 	break;
2352       case TASK_MOVE_TO:
2353 	if (unit != NULL && unit->x == arg0 && unit->y == arg1) {
2354 	    tprintf(buf, "here");
2355 	} else {
2356 	    if (arg3 == 0) {
2357 		/* do nothing */
2358 	    } else if (arg3 == 1) {
2359 		tprintf(buf, "adj ");
2360 	    } else {
2361 		tprintf(buf, "within %d of ", arg3);
2362 	    }
2363 	    tprintf(buf, "%d,%d", arg0, arg1);
2364 	}
2365         break;
2366       case TASK_OCCUPY:
2367 	unit2 = find_unit(arg0);
2368 	if (unit2 != NULL) {
2369 	    tprintf(buf, "%s ", medium_long_unit_handle(unit2));
2370 	    if (!empty_string(unit2->name)) {
2371 		tprintf(buf, "(%d,%d)", unit2->x, unit2->y);
2372 	    } else {
2373 	   	tprintf(buf, "at %d,%d", unit2->x, unit2->y);
2374 	    }
2375 	} else {
2376 	    tprintf(buf, "unknown unit #%d", arg0);
2377 	}
2378 	break;
2379       case TASK_PICKUP:
2380 	unit2 = find_unit(arg0);
2381 	if (unit2 != NULL) {
2382 	    tprintf(buf, "%s", medium_long_unit_handle(unit2));
2383 	} else {
2384 	    tprintf(buf, "unknown unit #%d", arg0);
2385 	}
2386 	break;
2387       case TASK_REPAIR:
2388 	unit2 = find_unit(arg0);
2389 	if (!unit2)
2390 	    tprintf(buf, "unknown unit #%d", arg0);
2391 	else if (unit2->id == unit->id)
2392 	    tprintf(buf, "self");
2393 	else
2394 	    tprintf(buf, "%s", medium_long_unit_handle(unit2));
2395 	break;
2396       case TASK_RESUPPLY:
2397 	if (arg0 != NONMTYPE)
2398 	  tprintf(buf, "%s", m_type_name(arg0));
2399 	else
2400 	  tprintf(buf, "all");
2401 	if (arg1 != 0) {
2402 	    unit2 = find_unit(arg1);
2403 	    if (unit2 != NULL) {
2404 		tprintf(buf, " at %s", short_unit_handle(unit2));
2405 	    }
2406 	}
2407 	break;
2408       case TASK_SENTRY:
2409 	/* (should display as calendar time) */
2410 	tprintf(buf, "for %d turns", arg0);
2411 	break;
2412       case TASK_PRODUCE:
2413 	tprintf(buf, "%s (%d/%d)", mtypes[task->args[0]].name, task->args[2],
2414 		task->args[1]);
2415 	break;
2416       default:
2417 	/* Default is just to dump out the raw data about the task. */
2418 	tprintf(buf, "raw");
2419 	argtypes = taskdefns[task->type].argtypes;
2420 	slen = strlen(argtypes);
2421 	for (i = 0; i < slen; ++i) {
2422 	    tprintf(buf, "%c%d", (i == 0 ? ' ' : ','), task->args[i]);
2423 	}
2424 	break;
2425     }
2426     if (Debug) {
2427 	/* Include the number of executions and retries. */
2428 	tprintf(buf, " x %d", task->execnum);
2429 	if (task->retrynum > 0) {
2430 	    tprintf(buf, " fail %d", task->retrynum);
2431 	}
2432     }
2433 }
2434 
2435 /* Describe an action in a brief but intelligible way. */
2436 
2437 void
action_desc(char * buf,Action * action,Unit * unit)2438 action_desc(char *buf, Action *action, Unit *unit)
2439 {
2440     Unit *unit2 = NULL;
2441     int *args = NULL;
2442     int tv = UNSEEN;
2443 
2444     assert_error(buf, "Tried to write to a NULL buffer");
2445     assert_error(unit, "Tried to access a NULL unit");
2446     if ((action == NULL) || (action->type == ACTION_NONE)) {
2447 	buf[0] = '\0';
2448 	return;
2449     }
2450     args = action->args;
2451     /* Print the name of the action. */
2452     sprintf(buf, "%s ", actiondefns[(int) action->type].name);
2453     /* Print things specific to the type of action. */
2454     switch (action->type) {
2455       case ACTION_MOVE:
2456 	if (in_area(args[0], args[1])) {
2457 	    tv = terrain_view(unit->side, args[0], args[1]);
2458 	    tprintf(buf, "to %s at (%d,%d)",
2459 		    (tv == UNSEEN) ? "unknown cell"
2460 				   : t_type_name(vterrain(tv)),
2461 		    args[0], args[1]);
2462 	    /* (TODO: Consider elevation.) */
2463 	}
2464 	else
2465 	  tprintf(buf, "%s", "out of playing area");
2466 	break;
2467       case ACTION_ENTER:
2468 	/* (TODO: This should be rewritten to handle unit views instead of
2469 	    raw units.) */
2470 	unit2 = find_unit(args[0]);
2471 	if (!unit2)
2472 	  tprintf(buf, "%s", "missing or out-of-play transport");
2473 	else
2474 	  tprintf(buf, "%s", unit_desig(unit2));
2475 	break;
2476       case ACTION_ATTACK: case ACTION_FIRE_AT: case ACTION_CAPTURE:
2477 	/* (TODO: This should be rewritten to handle unit views instead of
2478 	    raw units.) */
2479 	unit2 = find_unit(args[0]);
2480 	if (!unit2)
2481 	  tprintf(buf, "%s", "missing or out-of-play defender");
2482 	else
2483 	  tprintf(buf, "%s %s at (%d,%d)", side_desig(unit2->side),
2484 		  u_type_name(unit2->type), unit2->x, unit2->y);
2485 	break;
2486       case ACTION_OVERRUN: case ACTION_FIRE_INTO:
2487 	if (in_area(args[0], args[1])) {
2488 	    tv = terrain_view(unit->side, args[0], args[1]);
2489 	    tprintf(buf, "%s at (%d,%d)",
2490 		    (tv == UNSEEN) ? "unknown cell"
2491 				   : t_type_name(vterrain(tv)),
2492 		    args[0], args[1]);
2493 	    /* (TODO: Consider elevation?) */
2494 	}
2495 	else
2496 	  tprintf(buf, "%s", "a cell out of playing area");
2497 	break;
2498       case ACTION_DETONATE:
2499 	if (in_area(args[0], args[1])) {
2500 	    tv = terrain_view(unit->side, args[0], args[1]);
2501 	    tprintf(buf, "in %s at (%d,%d)",
2502 		    (tv == UNSEEN) ? "unknown cell"
2503 				   : t_type_name(vterrain(tv)),
2504 		    args[0], args[1]);
2505 	    /* (TODO: Consider elevation?) */
2506 	}
2507 	else
2508 	  tprintf(buf, "%s", "in a cell out of playing area");
2509 	break;
2510       case ACTION_PRODUCE:
2511 	tprintf(buf, "%d %s", args[1], m_type_name(args[0]));
2512 	break;
2513       case ACTION_EXTRACT:
2514 	if (in_area(args[0], args[1])) {
2515 	    tv = terrain_view(unit->side, args[0], args[1]);
2516 	    tprintf(buf, "%d %s from %s at (%d,%d)", args[3],
2517 		    m_type_name(args[2]),
2518 		    (tv == UNSEEN) ? "unknown cell"
2519 				   : t_type_name(vterrain(tv)),
2520 		    args[0], args[1]);
2521 	    /* (TODO: Consider elevation?) */
2522 	}
2523 	else
2524 	  tprintf(buf, "%d %s from a cell out of playing area",
2525 		  args[3], m_type_name(args[2]));
2526 	break;
2527       case ACTION_TRANSFER:
2528 	unit2 = find_unit(args[2]);
2529 	if (!unit2)
2530 	  tprintf(buf, "%s", "missing or out-of-play recipient");
2531 	else
2532 	  tprintf(buf, "%d %s to %s %s at (%d,%d)",
2533 		  args[1], m_type_name(args[0]),
2534 		  side_desig(unit2->side),
2535 		  u_type_name(unit2->type), unit2->x, unit2->y);
2536 	break;
2537       case ACTION_DEVELOP:
2538 	tprintf(buf, "%s", u_type_name(args[0]));
2539 	break;
2540       case ACTION_TOOL_UP:
2541 	tprintf(buf, "for constructing %s", u_type_name(args[0]));
2542 	break;
2543       case ACTION_CREATE_IN:
2544 	/* (TODO: This should be rewritten to handle unit views instead of
2545 	    raw units.) */
2546 	unit2 = find_unit(args[1]);
2547 	if (!unit2)
2548 	  tprintf(buf, "%s", "missing or out-of-play womb unit");
2549 	else
2550 	  tprintf(buf, "%s %s at (%d,%d): %s", side_desig(unit2->side),
2551 		  u_type_name(unit2->type), unit2->x, unit2->y,
2552 		  u_type_name(args[0]));
2553 	break;
2554       case ACTION_CREATE_AT:
2555 	if (in_area(args[1], args[2])) {
2556 	    tv = terrain_view(unit->side, args[1], args[2]);
2557 	    tprintf(buf, "%s at (%d,%d): %s",
2558 		    (tv == UNSEEN) ? "unknown cell"
2559 				   : t_type_name(vterrain(tv)),
2560 		    args[1], args[2], u_type_name(args[0]));
2561 	    /* (TODO: Consider elevation?) */
2562 	}
2563 	else
2564 	  tprintf(buf, "%s", "a cell out of playing area");
2565 	break;
2566       case ACTION_BUILD: case ACTION_REPAIR:
2567 	unit2 = find_unit(args[0]);
2568 	if (!unit2)
2569 	  tprintf(buf, "%s", "missing or out-of-play unit");
2570 	else
2571 	  tprintf(buf, "%s %s at (%d,%d)", side_desig(unit2->side),
2572 		  u_type_name(unit2->type), unit2->x, unit2->y);
2573 	break;
2574       default: break;
2575     }
2576 }
2577 
2578 
2579 /* Describe a goal in a human-intelligible way. */
2580 
2581 char *
goal_desc(char * buf,Goal * goal)2582 goal_desc(char *buf, Goal *goal)
2583 {
2584     int numargs, i, arg;
2585     char *argtypes;
2586     Unit *unit;
2587 
2588     if (goal == NULL)
2589       return "null goal";
2590     switch (goal->type) {
2591       case GOAL_KEEP_FORMATION:
2592 	sprintf(buf, " %d,%d from %s (var %d)",
2593 		goal->args[1], goal->args[2],
2594 		unit_handle(NULL, find_unit(goal->args[0])),
2595 		goal->args[3]);
2596 	break;
2597       case GOAL_VICINITY_HELD:
2598 	sprintf(buf, "Hold %dx%d area around %d,%d",
2599 		goal->args[2], goal->args[3], goal->args[0], goal->args[1]);
2600 	break;
2601       case GOAL_UNIT_OCCUPIED:
2602 	unit = find_unit(goal->args[0]);
2603 	sprintf(buf, "Hold %s ", medium_long_unit_handle(unit));
2604 	    if (!empty_string(unit->name)) {
2605 		tprintf(buf, "(%d,%d)", unit->x, unit->y);
2606 	    } else {
2607 	   	tprintf(buf, "at %d,%d", unit->x, unit->y);
2608 	    }
2609 	break;
2610 	/* (should add more cases specific to common types of goals) */
2611       default:
2612 	sprintf(buf, "%s%s", (goal->tf ? "" : "not "),
2613 		goaldefns[goal->type].display_name);
2614 	argtypes = goaldefns[goal->type].argtypes;
2615 	numargs = strlen(argtypes);
2616 	for (i = 0; i < numargs; ++i) {
2617 	    arg = goal->args[i];
2618 	    switch (argtypes[i]) {
2619 	      case 'h':
2620 		tprintf(buf, "%d", arg);
2621 		break;
2622 	      case 'm':
2623 		if (is_material_type(arg))
2624 		  tprintf(buf, " %s", m_type_name(arg));
2625 		else
2626 		  tprintf(buf, " m%d?", arg);
2627 		break;
2628 	      case 'S':
2629 		tprintf(buf, " %s", short_side_title(side_n(arg)));
2630 		break;
2631 	      case 'u':
2632 		if (is_unit_type(arg))
2633 		  tprintf(buf, " %s", u_type_name(arg));
2634 		else
2635 		  tprintf(buf, " u%d?", arg);
2636 		break;
2637 	      case 'U':
2638 		tprintf(buf, " %s", unit_handle(NULL, find_unit(arg)));
2639 		break;
2640 	      case 'w':
2641 		tprintf(buf, " %dx", arg);
2642 		break;
2643 	      case 'x':
2644 		tprintf(buf, " %d,", arg);
2645 		break;
2646 	      case 'y':
2647 		tprintf(buf, "%d", arg);
2648 		break;
2649 	      default:
2650 		tprintf(buf, " %d", arg);
2651 		break;
2652 	    }
2653 	}
2654 	break;
2655     }
2656     return buf;
2657 }
2658 
2659 /* Format a (real, not game) clock time into a standard form.  This
2660    routine will omit the hours part if it will be uninteresting. */
2661 
2662 void
time_desc(char * buf,int seconds,int maxtime)2663 time_desc(char *buf, int seconds, int maxtime)
2664 {
2665     int hour, minute, second;
2666 
2667     if (seconds >= 0) {
2668 	hour = seconds / 3600;
2669 	minute = (seconds / 60) % 60;
2670 	second = seconds % 60;
2671     	if (between(1, maxtime, 3600) && hour == 0) {
2672 	    sprintf(buf, "%.2d:%.2d", minute, second);
2673 	} else {
2674 	    sprintf(buf, "%.2d:%.2d:%.2d", hour, minute, second);
2675 	}
2676     } else {
2677     	sprintf(buf, "??:??:??");
2678     }
2679 }
2680 
2681 #if 0		/* Unused. */
2682 
2683 /* General-purpose routine to take an array of anonymous unit types and
2684    summarize what's in it, using numbers and unit chars. */
2685 
2686 char *
2687 summarize_units(char *buf, int *ucnts)
2688 {
2689     char tmp[BUFSIZE];  /* should be bigger? */
2690     int u;
2691 
2692     buf[0] = '\0';
2693     for_all_unit_types(u) {
2694 	if (ucnts[u] > 0) {
2695 	    sprintf(tmp, " %d %s", ucnts[u], utype_name_n(u, 3));
2696 	    strcat(buf, tmp);
2697 	}
2698     }
2699     return buf;
2700 }
2701 
2702 #endif
2703 
2704 static void notify_doctrine_1(Side *side, Doctrine *doctrine);
2705 
2706 void
notify_doctrine(Side * side,char * spec)2707 notify_doctrine(Side *side, char *spec)
2708 {
2709     int u;
2710     char *arg, *rest, substr[BUFSIZE], outbuf[BUFSIZE];
2711     Doctrine *doctrine;
2712 
2713     if (!empty_string(spec))
2714       rest = get_next_arg(spec, substr, &arg);
2715     else
2716       arg = "";
2717     if ((doctrine = find_doctrine_by_name(arg)) != NULL) {
2718 	/* Found a specific named doctrine. */
2719 	/* (should say which of our own unit types use it) */
2720     } else if ((u = utype_from_name(arg)) != NONUTYPE) {
2721 	doctrine = side->udoctrine[u];
2722     } else if (strcmp(arg, "default") == 0) {
2723 	doctrine = side->default_doctrine;
2724 	/* (should say which unit types use it) */
2725     } else {
2726 	if (!empty_string(arg))
2727 	  notify(side, "\"%s\" not recognized as doctrine name or unit type.",
2728 		 arg);
2729 	/* Note that although we list all doctrines, including those
2730 	   belonging to other sides, because we don't know if the
2731 	   other side is actually *using* any particular doctrine.
2732 	   While this may seem like an information leak, if a side has
2733 	   its own unique doctrine, curiously enough this corresponds
2734 	   well to the real world; for instance, present-day nations'
2735 	   militaries are all familiar with each other's doctrines. */
2736 	outbuf[0] = '\0';
2737 	for_all_doctrines(doctrine) {
2738 	    if (!empty_string(doctrine->name))
2739 	      tprintf(outbuf, " %s", doctrine->name);
2740 	    else
2741 	      tprintf(outbuf, " #%d", doctrine->id);
2742 	    if (doctrine->next != NULL)
2743 	      tprintf(outbuf, ",");
2744 	}
2745 	notify(side, "Doctrines available:%s", outbuf);
2746 	for_all_doctrines(doctrine) {
2747 	    notify_doctrine_1(side, doctrine);
2748 	}
2749 	return;
2750     }
2751     /* We now have a doctrine to display. */
2752     notify_doctrine_1(side, doctrine);
2753 }
2754 
2755 static void
notify_doctrine_1(Side * side,Doctrine * doctrine)2756 notify_doctrine_1(Side *side, Doctrine *doctrine)
2757 {
2758     int u, i, num, dflt;
2759     char abuf[BUFSIZE];
2760 
2761     if (!empty_string(doctrine->name))
2762       notify(side, "Doctrine '%s' (%s):",
2763 	     doctrine->name, (doctrine->locked ? "fixed" : "adjustable"));
2764     else
2765       notify(side, "Doctrine #%d (%s):",
2766 	     doctrine->id, (doctrine->locked ? "fixed" : "adjustable"));
2767     notify(side, "  Resupply at %d%% of storage", doctrine->resupply_percent);
2768     notify(side, "  Rearm at %d%% of storage", doctrine->rearm_percent);
2769     notify(side, "  Repair at %d%% of hp", doctrine->repair_percent);
2770     abuf[0] = '\0';
2771     i = 0;
2772     num = 0;
2773     dflt = doctrine->construction_run[0];
2774 #if 0 /* should use histo code in write_table */
2775     for_all_unit_types(u) {
2776 	if (dflt == doctrine->construction_run[u]) {
2777 	    ++num;
2778 	}
2779     }
2780 #endif
2781     for_all_unit_types(u) {
2782 	tprintf(abuf, "  %s %d",
2783 		u_type_name(u), doctrine->construction_run[u]);
2784 	if (i > 0 && (i++) % 4 == 0 && !empty_string(abuf)) {
2785 	    notify(side, "  Construction run:%s", abuf);
2786 	    abuf[0] = '\0';
2787 	}
2788     }
2789     if (!empty_string(abuf))
2790       notify(side, "  Construction run:%s", abuf);
2791 }
2792 
2793 static int report_combat_special(Unit *unit1, Unit *unit2, char *str);
2794 int type_matches_symbol(Obj *sym, int u);
2795 
2796 void
report_combat(Unit * atker,Unit * other,char * str)2797 report_combat(Unit *atker, Unit *other, char *str)
2798 {
2799     int rslt;
2800 
2801     if (g_action_notices() != lispnil) {
2802 	rslt = report_combat_special(atker, other, str);
2803 	if (rslt)
2804 	  return;
2805     }
2806     /* Default messages for each type of report. */
2807     if (strcmp(str, "destroy") == 0) {
2808 	notify_combat(atker, other, (char *) "%s destroys %s!");
2809     } else if (strcmp(str, "destroy-occupant") == 0) {
2810 	notify_combat(other, atker, (char *) " - and destroys occupant %s!");
2811     } else if (strcmp(str, "hit") == 0) {
2812 	notify_combat(atker, other, (char *) "%s hits %s!");
2813     } else if (strcmp(str, "hit-occupant") == 0) {
2814 	notify_combat(other, atker, (char *) " - and hits occupant %s!");
2815     } else if (strcmp(str, "miss-fire") == 0) {
2816 	notify_combat(atker, other, (char *) "%s fires at %s.");
2817     } else if (strcmp(str, "miss-attack") == 0) {
2818 	notify_combat(atker, other, (char *) "%s attacks %s.");
2819     } else if (strcmp(str, "miss-occupant") == 0) {
2820 	/* (this case is too uninteresting to mention) */
2821     } else if (strcmp(str, "resist") == 0) {
2822 	notify_combat(other, atker, (char *) "%s resists capture by %s!");
2823     } else if (strcmp(str, "resist/slaughter") == 0) {
2824 	notify_combat(other, atker, (char *) "%s resists capture; %s slaughtered!");
2825     } else if (strcmp(str, "capture") == 0) {
2826 	notify_combat(atker, other, (char *) "%s captures %s!");
2827     } else if (strcmp(str, "liberate") == 0) {
2828 	notify_combat(atker, other, (char *) "%s liberates %s!");
2829     } else if (strcmp(str, "escape") == 0) {
2830 	notify_combat(other, atker, (char *) "%s escapes!");
2831     } else if (strcmp(str, "retreat") == 0) {
2832 	notify_combat(other, atker, (char *) "%s retreats!");
2833     } else if (strcmp(str, "detonate") == 0) {
2834 	notify_combat(atker, other, (char *) "%s detonates!");
2835     } else {
2836 	notify_combat(atker, other, str);
2837     }
2838 }
2839 
2840 static void
notify_combat(Unit * unit1,Unit * unit2,char * str)2841 notify_combat(Unit *unit1, Unit *unit2, char *str)
2842 {
2843     char buf1[BUFSIZE], buf2[BUFSIZE];
2844     Side *side3;
2845 
2846     for_all_sides(side3) {
2847 	if (active_display(side3)
2848 	    && (side3->see_all
2849 		|| (unit1 != NULL && side3 == unit1->side)
2850 		|| (unit2 != NULL && side3 == unit2->side))) {
2851 	    strcpy(buf1, unit_handle(side3, unit1));
2852 	    strcpy(buf2, unit_handle(side3, unit2));
2853 	    notify(side3, str, buf1, buf2);
2854 	}
2855     }
2856 }
2857 
2858 static int
report_combat_special(Unit * unit1,Unit * unit2,char * str)2859 report_combat_special(Unit *unit1, Unit *unit2, char *str)
2860 {
2861     int found = FALSE;
2862     char abuf[BUFSIZE];
2863     Obj *rest, *head, *pat, *msgdesc;
2864     Side *side3;
2865 
2866     for_all_list(g_action_notices(), rest) {
2867 	head = car(rest);
2868 	if (!consp(head)) {
2869 	    run_warning("Non-list in action-notices");
2870 	    continue;
2871 	}
2872 	pat = car(head);
2873 	if (symbolp(pat) && strcmp(c_string(pat), str) == 0) {
2874 	    found = TRUE;
2875 	    break;
2876 	}
2877 	if (consp(pat)
2878 	    && symbolp(car(pat))
2879 	    && strcmp(c_string(car(pat)), str) == 0
2880 	    && pattern_matches_combat(cdr(pat), unit1, unit2)) {
2881 	    found = TRUE;
2882 	    break;
2883 	}
2884     }
2885     /* If we have a match, do something with it. */
2886     if (found) {
2887 	msgdesc = cadr(head);
2888 	if (stringp(msgdesc)) {
2889 	    strcpy(abuf, c_string(msgdesc));
2890 	} else {
2891 	    /* Notify all sides that could have seen the combat. */
2892 	    for_all_sides(side3) {
2893 		if (active_display(side3)
2894 		    && (side3->see_all
2895 			|| (unit1 != NULL && side3 == unit1->side)
2896 			|| (unit2 != NULL && side3 == unit2->side))) {
2897 		    combat_desc_from_list(side3, msgdesc, unit1, unit2, str,
2898 					  abuf);
2899 		    notify(side3, abuf);
2900 		}
2901 	    }
2902 	}
2903     }
2904     return found;
2905 }
2906 
2907 static int
pattern_matches_combat(Obj * parms,Unit * unit,Unit * unit2)2908 pattern_matches_combat(Obj *parms, Unit *unit, Unit *unit2)
2909 {
2910     Obj *head;
2911 
2912     head = car(parms);
2913     if (!type_matches_symbol(head, unit->type))
2914       return FALSE;
2915     parms = cdr(parms);
2916     head = car(parms);
2917     if (!type_matches_symbol(head, unit2->type))
2918       return FALSE;
2919     return TRUE;
2920 }
2921 
2922 int
type_matches_symbol(Obj * sym,int u)2923 type_matches_symbol(Obj *sym, int u)
2924 {
2925     char *uname;
2926     Obj *val, *rest, *head;
2927 
2928     if (!symbolp(sym))
2929       return FALSE;
2930     uname = u_type_name(u);
2931     if (strcmp(c_string(sym), uname) == 0)
2932       return TRUE;
2933     if (match_keyword(sym, K_USTAR))
2934       return TRUE;
2935     if (boundp(sym)) {
2936 	val = eval(sym);
2937 	if (symbolp(val) && strcmp(c_string(val), uname) == 0)
2938 	  return TRUE;
2939 	if (utypep(val) && c_number(val) == u)
2940 	  return TRUE;
2941 	if (consp(val)) {
2942 	    for_all_list(val, rest) {
2943 		head = car(rest);
2944 		if (symbolp(head) && strcmp(c_string(head), uname) == 0)
2945 		  return TRUE;
2946 		if (utypep(head) && c_number(head) == u)
2947 		  return TRUE;
2948 	    }
2949 	}
2950     }
2951     return FALSE;
2952 }
2953 
2954 static void
combat_desc_from_list(Side * side,Obj * lis,Unit * unit,Unit * unit2,char * str,char * buf)2955 combat_desc_from_list(Side *side, Obj *lis, Unit *unit, Unit *unit2,
2956 		      char *str, char *buf)
2957 {
2958     int n;
2959     char *symname;
2960     Obj *rest, *item;
2961 
2962     buf[0] = '\0';
2963     for_all_list(lis, rest) {
2964 	item = car(rest);
2965 	if (stringp(item)) {
2966 	    strcat(buf, c_string(item));
2967 	} else if (symbolp(item)) {
2968 	    symname = c_string(item);
2969 	    if (strcmp(symname, "actor") == 0) {
2970 		strcat(buf, unit_handle(side, unit));
2971 	    } else if (strcmp(symname, "actee") == 0) {
2972 		strcat(buf, unit_handle(side, unit2));
2973 	    } else {
2974 		tprintf(buf, " ??%s?? ", symname);
2975 	    }
2976 	} else if (numberp(item)) {
2977 	    n = c_number(item);
2978 	    if (0 /* special processing */) {
2979 	    } else {
2980 		tprintf(buf, "%d", n);
2981 	    }
2982 	} else {
2983 	    strcat(buf, " ????? ");
2984 	}
2985     }
2986 }
2987 
2988 void
report_give(Side * side,Unit * unit,Unit * unit2,short * rslts)2989 report_give(Side *side, Unit *unit, Unit *unit2, short *rslts)
2990 {
2991     char buf[BUFSIZE];
2992     int m, something = FALSE;
2993 
2994     sprintf(buf, "%s gave", unit_handle(side, unit));
2995     for_all_material_types(m) {
2996 	if (rslts[m] > 0) {
2997 	    tprintf(buf, " %d %s", rslts[m], m_type_name(m));
2998 	    something = TRUE;
2999 	}
3000     }
3001     if (!something) {
3002 	strcat(buf, " nothing");
3003     }
3004     if (unit2 != NULL) {
3005 	tprintf(buf, " to %s", unit_handle(side, unit2));
3006     }
3007     notify(side, "%s.", buf);
3008 }
3009 
3010 void
report_take(Side * side,Unit * unit,int needed,short * rslts)3011 report_take(Side *side, Unit *unit, int needed, short *rslts)
3012 {
3013     char buf[BUFSIZE];
3014     int m, something = FALSE;
3015 
3016     if (!needed) {
3017 	notify(side, "%s needed nothing.", unit_handle(side, unit));
3018 	return;
3019     }
3020     buf[0] = '\0';
3021     for_all_material_types(m) {
3022 	if (rslts[m] > 0) {
3023 	    tprintf(buf, " %d %s", rslts[m], m_type_name(m));
3024 	    something = TRUE;
3025 	}
3026     }
3027     if (something) {
3028 	notify(side, "%s got%s.", unit_handle(side, unit), buf);
3029     } else {
3030 	notify(side, "%s got nothing.", unit_handle(side, unit));
3031     }
3032 }
3033 
3034 void
notify_all_of_resignation(Side * side,Side * side2)3035 notify_all_of_resignation(Side *side, Side *side2)
3036 {
3037     Side *side3;
3038 
3039     for_all_sides(side3) {
3040 	if (side3 != side) {
3041 	    notify(side3,
3042 		   "%s %s giving up!",
3043 		   short_side_title_with_adjective(side,
3044 #ifdef RUDE
3045 		   (char *)(flip_coin() ? "cowardly" : "wimpy")
3046 #else
3047 		   NULL
3048 #endif /* RUDE */
3049 		   ),
3050 		   (char *)(short_side_title_plural_p(side) ? "are" : "is"));
3051 	    if (side2 != NULL) {
3052 		notify(side3, "... and donating everything to %s!",
3053 		       short_side_title(side2));
3054 	    }
3055 	}
3056     }
3057 }
3058 
3059 /* Given a number, figure out what suffix should go with it. */
3060 
3061 char *
ordinal_suffix(int n)3062 ordinal_suffix(int n)
3063 {
3064     if (n % 100 == 11 || n % 100 == 12 || n % 100 == 13) {
3065 	return "th";
3066     } else {
3067 	switch (n % 10) {
3068 	  case 1:   return "st";
3069 	  case 2:   return "nd";
3070 	  case 3:   return "rd";
3071 	  default:  return "th";
3072 	}
3073     }
3074 }
3075 
3076 /* Pluralize a word, attempting to be smart about various
3077    possibilities that don't have a different plural form (such as
3078    "Chinese" and "Swiss"). */
3079 
3080 /* There should probably be a test for when to add "es" instead of "s". */
3081 
3082 char *
plural_form(char * word)3083 plural_form(char *word)
3084 {
3085     char endch = ' ', nextend = ' ';
3086     int len;
3087 
3088     if (word == NULL) {
3089 	run_warning("plural_form given NULL string");
3090 	pluralbuf[0] = '\0';
3091 	return pluralbuf;
3092     }
3093     len = strlen(word);
3094     if (len > 0)
3095       endch = word[len - 1];
3096     if (len > 1)
3097       nextend = word[len - 2];
3098     if (endch == 'h' || endch == 's' || (endch == 'e' && nextend == 's')) {
3099 	sprintf(pluralbuf, "%s", word);
3100     } else {
3101 	sprintf(pluralbuf, "%ss", word);
3102     }
3103     return pluralbuf;
3104 }
3105 
3106 /* Make the first letter of the buffer upper-case. */
3107 
3108 char *
capitalize(char * buf)3109 capitalize(char *buf)
3110 {
3111     if (islower(buf[0]))
3112       buf[0] = toupper(buf[0]);
3113     return buf;
3114 }
3115 
3116 /* Make all letters in the buffer upper-case. */
3117 
3118 char *
all_capitals(char * buf)3119 all_capitals(char *buf)
3120 {
3121     int i = 0;
3122 
3123     while (buf[i] != '\0') {
3124     	if (islower(buf[i]))
3125 	      buf[i] = toupper(buf[i]);
3126     	i++;
3127     }
3128     return buf;
3129 }
3130 
3131 /* Compose a readable form of the given date. */
3132 
3133 char *
absolute_date_string(int date)3134 absolute_date_string(int date)
3135 {
3136     /* The first time we ask for a date, interpret the calendar. */
3137     if (calendar_type == cal_unknown)
3138       init_calendar();
3139     switch (calendar_type) {
3140       case cal_number:
3141 	sprintf(datebuf, "%s%4d", turn_name, date);
3142 	return datebuf;
3143       case cal_usual:
3144 	return usual_date_string(date);
3145       default:
3146 	case_panic("calendar type", calendar_type);
3147     }
3148     return "!?!";
3149 }
3150 
3151 /* Interpret the calendar definition. */
3152 
3153 static void
init_calendar(void)3154 init_calendar(void)
3155 {
3156     Obj *cal, *caltype, *stepname, *steprest;
3157 
3158     cal = g_calendar();
3159     turn_name = "Turn";
3160     if (cal == lispnil) {
3161 	/* The default is to have numbered turns named "Turn". */
3162 	calendar_type = cal_number;
3163     } else if (consp(cal)) {
3164 	caltype = car(cal);
3165 	if (match_keyword(caltype, K_NUMBER)) {
3166 	    calendar_type = cal_number;
3167 	    if (stringp(cadr(cal)))
3168 	      turn_name = c_string(cadr(cal));
3169 	} else if (match_keyword(caltype, K_USUAL)) {
3170 	    calendar_type = cal_usual;
3171 	    stepname = cadr(cal);
3172 	    if (symbolp(stepname)) {
3173 		parse_date_step_range(cdr(cal));
3174 	    } else if (consp(stepname)) {
3175 		for_all_list(cdr(cal), steprest) {
3176 		    parse_date_step_range(car(steprest));
3177 		}
3178 	    } else {
3179 		init_warning("No name for date step type, substituting `day'");
3180 		date_step_ranges[0].step_type = ds_day;
3181 		date_step_ranges[0].step_size = 1;
3182 		num_date_step_ranges = 1;
3183 	    }
3184 	}
3185     }
3186     if (calendar_type == cal_unknown)
3187       init_error("Bad calendar type");
3188     if (!empty_string(g_initial_date()))
3189       set_initial_date(g_initial_date());
3190 }
3191 
3192 static void
parse_date_step_range(Obj * form)3193 parse_date_step_range(Obj *form)
3194 {
3195     int n;
3196     char *stepnamestr;
3197     UsualDateStepType steptype;
3198     Obj *step;
3199 
3200     n = num_date_step_ranges;
3201     /* Peel off a turn range if supplied. */
3202     if (numberp(car(form))) {
3203 	date_step_ranges[n].turn_start = c_number(car(form));
3204 	form = cdr(form);
3205     }
3206     if (numberp(car(form))) {
3207 	date_step_ranges[n].turn_end = c_number(car(form));
3208 	form = cdr(form);
3209     }
3210     if (symbolp(car(form))) {
3211 	stepnamestr = c_string(car(form));
3212 	if (strcmp(stepnamestr, "second") == 0) {
3213 	    steptype = ds_second;
3214 	} else if (strcmp(stepnamestr, "minute") == 0) {
3215 	    steptype = ds_minute;
3216 	} else if (strcmp(stepnamestr, "hour") == 0) {
3217 	    steptype = ds_hour;
3218 	} else if (strcmp(stepnamestr, "day") == 0) {
3219 	    steptype = ds_day;
3220 	} else if (strcmp(stepnamestr, "week") == 0) {
3221 	    steptype = ds_week;
3222 	} else if (strcmp(stepnamestr, "month") == 0) {
3223 	    steptype = ds_month;
3224 	} else if (strcmp(stepnamestr, "season") == 0) {
3225 	    steptype = ds_season;
3226 	} else if (strcmp(stepnamestr, "year") == 0) {
3227 	    steptype = ds_year;
3228 	} else {
3229 	    init_warning("\"%s\" not a known date step name", stepnamestr);
3230 	    steptype = ds_day;
3231 	}
3232     } else {
3233 	/* (should warn) */
3234 	steptype = ds_day;
3235     }
3236     date_step_ranges[n].step_type = steptype;
3237     /* Collect an optional multiple. */
3238     step = cadr(form);
3239     date_step_ranges[n].step_size = (numberp(step) ? c_number(step) : 1);
3240     ++num_date_step_ranges;
3241 }
3242 
3243 /* Given two dates, figure out how many turns encompassed by them. */
3244 
3245 int
turns_between(char * datestr1,char * datestr2)3246 turns_between(char *datestr1, char *datestr2)
3247 {
3248     int rslt, turn1, turn2;
3249     UsualDate date1, date2;
3250 
3251     if (calendar_type == cal_unknown)
3252       init_calendar();
3253     switch (calendar_type) {
3254       case cal_number:
3255 	sscanf("%d", datestr1, &turn1);
3256 	sscanf("%d", datestr2, &turn2);
3257 	return (turn2 - turn1);
3258       case cal_usual:
3259 	parse_usual_date(datestr1, 0, &date1);
3260 	parse_usual_date(datestr2, 0, &date2);
3261 	rslt = date2.year - date1.year;
3262 	if (num_date_step_ranges == 1) {
3263 	    if (date_step_ranges[0].step_type == ds_year)
3264 	      return (rslt / date_step_ranges[0].step_size);
3265 	    if (date_step_ranges[0].step_type < ds_year) {
3266 		rslt = (12 * rslt) - (date2.month - date1.month);
3267 	    }
3268 	    if (date_step_ranges[0].step_type == ds_month)
3269 	      return (rslt / date_step_ranges[0].step_size);
3270 	    if (date_step_ranges[0].step_type < ds_month) {
3271 		rslt = (30 * rslt) - (date2.day - date1.day);
3272 	    }
3273 	    if (date_step_ranges[0].step_type == ds_week)
3274 	      return (((rslt + 6) / 7) / date_step_ranges[0].step_size);
3275 	    if (date_step_ranges[0].step_type == ds_day)
3276 	      return (rslt / date_step_ranges[0].step_size);
3277 	    if (date_step_ranges[0].step_type < ds_day) {
3278 		rslt = (24 * rslt) - (date2.hour - date1.hour);
3279 	    }
3280 	    if (date_step_ranges[0].step_type == ds_hour)
3281 	      return (rslt / date_step_ranges[0].step_size);
3282 	    return (rslt / date_step_ranges[0].step_size); /* semi-bogus */
3283 	} else {
3284 	    /* Too complicated for now, give up. */
3285 	    return 1;
3286 	}
3287       default:
3288 	case_panic("calendar type", calendar_type);
3289 	break;
3290     }
3291     return 1;  /* appease the compiler */
3292 }
3293 
3294 /* Given a date string, make it be the date of the first turn. */
3295 
3296 void
set_initial_date(char * str)3297 set_initial_date(char *str)
3298 {
3299     if (calendar_type == cal_unknown)
3300       init_calendar();
3301     switch (calendar_type) {
3302       case cal_number:
3303 	sscanf("%d", str, &turn_initial);
3304 	break;
3305       case cal_usual:
3306 	if (usual_initial == NULL)
3307 	  usual_initial = (UsualDate *) xmalloc(sizeof(UsualDate));
3308 	parse_usual_date(str, 0, usual_initial);
3309 	break;
3310       default:
3311 	case_panic("calendar type", calendar_type);
3312 	break;
3313     }
3314 }
3315 
3316 /* Pick a date out of the given string. Note that this implementation
3317    does not detect extra garbage in the string, should fix someday. */
3318 
3319 /* (should use strtol etc instead of sscanf) */
3320 
3321 static void
parse_usual_date(char * datestr,int range,UsualDate * udate)3322 parse_usual_date(char *datestr, int range, UsualDate *udate)
3323 {
3324     char aname[BUFSIZE];
3325     int i, cnt;
3326 
3327     udate->second = udate->minute = udate->hour = 0;
3328     udate->day = udate->month = udate->year = 0;
3329     aname[0] = '\0';
3330     if (!empty_string(datestr)) {
3331 	/* Assume it's in a standard date format. */
3332 	switch (date_step_ranges[0].step_type) {
3333 	  case ds_second:
3334 	    cnt = sscanf(datestr, "%d:%d:%d %d %s %d",
3335 			 &(udate->second), &(udate->minute), &(udate->hour),
3336 			 &(udate->day), aname, &(udate->year));
3337 	    if (cnt != 6) {
3338 		cnt = sscanf(datestr, "%d:%d:%d",
3339 			     &(udate->second), &(udate->minute), &(udate->hour));
3340 		if (cnt != 3)
3341 		  goto bad_format;
3342 		return;
3343 	    }
3344 	    --(udate->day);
3345 	    break;
3346 	  case ds_minute:
3347 	    cnt = sscanf(datestr, "%d:%d %d %s %d",
3348 			 &(udate->minute), &(udate->hour),
3349 			 &(udate->day), aname, &(udate->year));
3350 	    if (cnt != 5) {
3351 		cnt = sscanf(datestr, "%d:%d",
3352 			     &(udate->minute), &(udate->hour));
3353 		if (cnt != 2)
3354 		  goto bad_format;
3355 		return;
3356 	    }
3357 	    --(udate->day);
3358 	    break;
3359 	  case ds_hour:
3360 	    cnt = sscanf(datestr, "%d:00 %d %s %d",
3361 			 &(udate->hour),
3362 			 &(udate->day), aname, &(udate->year));
3363 	    if (cnt != 4)
3364 	      cnt = sscanf(datestr, "%d %d %s %d",
3365 			   &(udate->hour),
3366 			   &(udate->day), aname, &(udate->year));
3367 	    if (cnt != 4)
3368 	      goto bad_format;
3369 	    --(udate->day);
3370 	    break;
3371 	  case ds_day:
3372 	  case ds_week:
3373 	    cnt = sscanf(datestr, "%d %s %d",
3374 			 &(udate->day), aname, &(udate->year));
3375 	    if (cnt != 3)
3376 	      goto bad_format;
3377 	    --(udate->day);
3378 	    break;
3379 	  case ds_month:
3380 	    cnt = sscanf(datestr, "%s %d", aname, &(udate->year));
3381 	    if (cnt != 2)
3382 	      goto bad_format;
3383 	    break;
3384 	  case ds_season:
3385 	    cnt = sscanf(datestr, "%s %d", aname, &(udate->year));
3386 	    if (cnt != 2)
3387 	      goto bad_format;
3388 	    for (i = 0; i < 4; ++i) {
3389 		if (strcmp(aname, seasons[i]) == 0) {
3390 		    udate->month = i * 3;
3391 		    return;
3392 		}
3393 	    }
3394 	    init_warning("\"%s\" not a recognized season name", aname);
3395 	    return;
3396 	  case ds_year:
3397 	    cnt = sscanf(datestr, "%d", &(udate->year));
3398 	    if (cnt != 1)
3399 	      goto bad_format;
3400 	    return;
3401 	  default:
3402 	    init_warning("%d not an allowed date step type",
3403 			 date_step_ranges[0].step_type);
3404 	    break;
3405 	}
3406 	for (i = 0; i < 12; ++i) {
3407 	    /* (should make case-insensitive) */
3408 	    if (strcmp(aname, months[i]) == 0) {
3409 		udate->month = i;
3410 		return;
3411 	    }
3412 	}
3413 	init_warning("\"%s\" not a recognized month name", aname);
3414     }
3415     return;
3416   bad_format:
3417     init_warning("\"%s\" is a badly formatted date", datestr);
3418 }
3419 
3420 /* Given a numeric date, convert it into something understandable. */
3421 
3422 static int ever_mentioned_bc;
3423 
3424 static char *
usual_date_string(int date)3425 usual_date_string(int date)
3426 {
3427     int year = 0, season = 0, month = 0, day = 0;
3428     int hour = 0, second = 0, minute = 0;
3429     int i, r, range;
3430 
3431     /* The date, which is a turn number, should be 1 or more, but this
3432        routine may be called before the game really starts, so return
3433        something that will be distinctive if it's ever displayed. */
3434     if (date <= 0)
3435       return "pregame";
3436     /* First displayed date is normally turn 1; be zero-based for
3437        the benefit of calculation. */
3438     --date;
3439     /* Find the date step type and size. */
3440     for (range = 0; range < num_date_step_ranges; ++range) {
3441 	if (between(date_step_ranges[range].turn_start,
3442 		    date,
3443 		    date_step_ranges[range].turn_end))
3444 	  break;
3445     }
3446     /* If no matches, just use the last range in the array. */
3447     if (range >= num_date_step_ranges)
3448       range = num_date_step_ranges - 1;
3449     /* If multiples of basic step, convert to basic step by multiplying. */
3450     date *= date_step_ranges[range].step_size;
3451     if (usual_initial == NULL) {
3452 	usual_initial = (UsualDate *) xmalloc(sizeof(UsualDate));
3453 	if (!empty_string(g_initial_date()))
3454 	  parse_usual_date(g_initial_date(), 0, usual_initial);
3455     }
3456     switch (date_step_ranges[range].step_type) {
3457       case ds_second:
3458 	second = date % 60;
3459 	minute = date / 60;
3460 	sprintf(datebuf, "%d:%d", minute, second);
3461 	/* (should add day/month/year if available?) */
3462 	break;
3463       case ds_minute:
3464 	minute = date % 60;
3465 	hour = date / 60 + usual_initial->hour;
3466 	sprintf(datebuf, "%d:%d", hour, minute);
3467 	/* (should add day/month/year if available?) */
3468 	break;
3469       case ds_hour:
3470 	date += usual_initial->hour;
3471  	hour = date % 24;
3472  	date /= 24;
3473 	date += usual_initial->day;
3474 	for (i = 0; i < usual_initial->month; ++i)
3475 	  date += monthdays[i];
3476 	day = date % 365;
3477 	month = 0;
3478 	for (i = 0; i < 12; ++i) {
3479 	    if (day < monthdays[i])
3480 	      break;
3481 	    day -= monthdays[i];
3482 	    ++month;
3483 	}
3484 	++day;
3485 	year = date / 365 + usual_initial->year;
3486 	sprintf(datebuf, "%d:00 %2d %s %d",
3487 		hour, day, months[month], ABS(year));
3488 	break;
3489       case ds_week:
3490 	/* Convert to days, then proceed as for days. */
3491 	date *= 7;
3492 	/* Fall through. */
3493       case ds_day:
3494 	date += usual_initial->day;
3495 	for (i = 0; i < usual_initial->month; ++i)
3496 	  date += monthdays[i];
3497 	day = date % 365;
3498 	month = 0;
3499 	for (i = 0; i < 12; ++i) {
3500 	    if (day < monthdays[i])
3501 	      break;
3502 	    day -= monthdays[i];
3503 	    ++month;
3504 	}
3505 	++day;
3506 	year = date / 365 + usual_initial->year;
3507 	sprintf(datebuf, "%2d %s %d", day, months[month], ABS(year));
3508 	break;
3509       case ds_month:
3510 	date += usual_initial->month;
3511     	month = date % 12;
3512 	year = date / 12 + usual_initial->year;
3513 	sprintf(datebuf, "%s %d", months[month], ABS(year));
3514 	break;
3515       case ds_season:
3516      	season = date % 4;
3517 	year = date / 4 + usual_initial->year;
3518 	sprintf(datebuf, "%s %d", seasons[season], ABS(year));
3519 	break;
3520       case ds_year:
3521 	year = usual_initial->year;
3522 	/* (should do something similar for other step types) */
3523 	for (r = 0; r < range; ++r)
3524 	  year += ((date_step_ranges[r].turn_end
3525 		    - date_step_ranges[r].turn_start)
3526 		   * date_step_ranges[r].step_size);
3527 	year += (date - (date_step_ranges[range].turn_start
3528 			 * date_step_ranges[range].step_size));
3529 	sprintf(datebuf, "%d", ABS(year));
3530 	break;
3531       default:
3532 	sprintf(datebuf, "%d is unknown date step type",
3533 		date_step_ranges[0].step_type);
3534 	break;
3535     }
3536     if (year < 0) {
3537 	strcat(datebuf, " BC");
3538 	ever_mentioned_bc = TRUE;
3539     } else if (ever_mentioned_bc) {
3540 	/* If any date was displayed as "BC", use "AD" with all positive
3541 	   year numbers. */
3542 	strcat(datebuf, " AD");
3543     }
3544     return datebuf;
3545 }
3546 
3547 /* Show some overall numbers on performance of a side. */
3548 
3549 void
write_side_results(FILE * fp,Side * side)3550 write_side_results(FILE *fp, Side *side)
3551 {
3552     int i;
3553 
3554     if (side == NULL) {
3555 	fprintf(fp, "Results for game as a whole:\n\n");
3556     } else {
3557 	fprintf(fp, "Results for %s%s",
3558 		short_side_title(side),
3559 		(side_won(side) ? " (WINNER)" :
3560 		 (side_lost(side) ? " (LOSER)" :
3561 		  "")));
3562 	for (i = 0; i < numscores; ++i) {
3563 	    fprintf(fp, " %d", side->scores[i]);
3564 	}
3565 	fprintf(fp, ", played by %s:\n\n",
3566 		long_player_title(spbuf, side->player, NULL));
3567     }
3568 }
3569 
3570 /* Display what is essentially a double-column bookkeeping of unit gains
3571    and losses. */
3572 
3573 void
write_unit_record(FILE * fp,Side * side)3574 write_unit_record(FILE *fp, Side *side)
3575 {
3576     int u, gainreason, lossreason, totgain, totloss, val;
3577 
3578     fprintf(fp, "Unit Record (gains and losses by cause and unit type)\n");
3579     fprintf(fp, " Unit Type ");
3580     for (gainreason = 0; gainreason < num_gain_reasons; ++gainreason) {
3581 	fprintf(fp, " %3s", gain_reason_names[gainreason]);
3582     }
3583     fprintf(fp, " Gain |");
3584     for (lossreason = 0; lossreason < num_loss_reasons; ++lossreason) {
3585 	fprintf(fp, " %3s", loss_reason_names[lossreason]);
3586     }
3587     fprintf(fp, " Loss |");
3588     fprintf(fp, " Total\n");
3589     for_all_unit_types(u) {
3590 	if (u_possible[u]) {
3591 	    totgain = 0;
3592 	    fprintf(fp, " %9s ", utype_name_n(u, 9));
3593 	    for (gainreason = 0; gainreason < num_gain_reasons; ++gainreason) {
3594 		val = gain_count(side, u, gainreason);
3595 		if (val > 0) {
3596 		    fprintf(fp, " %3d", val);
3597 		    totgain += val;
3598 		} else {
3599 		    fprintf(fp, "    ");
3600 		}
3601 	    }
3602 	    fprintf(fp, "  %3d |", totgain);
3603 	    totloss = 0;
3604 	    for (lossreason = 0; lossreason < num_loss_reasons; ++lossreason) {
3605 		val = loss_count(side, u, lossreason);
3606 		if (val > 0) {
3607 		    fprintf(fp, " %3d", val);
3608 		    totloss += val;
3609 		} else {
3610 		    fprintf(fp, "    ");
3611 		}
3612 	    }
3613 	    fprintf(fp, "  %3d |", totloss);
3614 	    fprintf(fp, "  %3d\n", totgain - totloss);
3615 	}
3616     }
3617     fprintf(fp, "\n");
3618 }
3619 
3620 static int
gain_count(Side * side,int u,int r)3621 gain_count(Side *side, int u, int r)
3622 {
3623     int sum;
3624 
3625     if (side != NULL)
3626       return side_gain_count(side, u, r);
3627     sum = 0;
3628     for_all_sides(side) {
3629 	sum += side_gain_count(side, u, r);
3630     }
3631     return sum;
3632 }
3633 
3634 static int
loss_count(Side * side,int u,int r)3635 loss_count(Side *side, int u, int r)
3636 {
3637     int sum;
3638 
3639     if (side != NULL)
3640       return side_loss_count(side, u, r);
3641     sum = 0;
3642     for_all_sides(side) {
3643 	sum += side_loss_count(side, u, r);
3644     }
3645     return sum;
3646 }
3647 
3648 /* Nearly-raw combat statistics; hard to interpret, but they provide
3649    a useful check against subjective evaluation of performance. */
3650 
3651 void
write_combat_results(FILE * fp,Side * side)3652 write_combat_results(FILE *fp, Side *side)
3653 {
3654     int a, d, atk;
3655 
3656     fprintf(fp,
3657 	    "Unit Combat Results (average damage over # attacks against enemy, by type)\n");
3658     fprintf(fp, " A  D->");
3659     for_all_unit_types(d) {
3660 	if (u_possible[d]) {
3661 	    fprintf(fp, " %4s ", utype_name_n(d, 4));
3662 	}
3663     }
3664     fprintf(fp, "\n");
3665     for_all_unit_types(a) {
3666 	if (u_possible[a]) {
3667 	    fprintf(fp, " %4s ", utype_name_n(a, 4));
3668 	    for_all_unit_types(d) {
3669 		if (u_possible[d]) {
3670 		    atk = atkstats(side, a, d);
3671 		    if (atk > 0) {
3672 			fprintf(fp, " %5.2f",
3673 				((float) hitstats(side, a, d)) / atk);
3674 		    } else {
3675 			fprintf(fp, "      ");
3676 		    }
3677 		}
3678 	    }
3679 	    fprintf(fp, "\n     ");
3680 	    for_all_unit_types(d) {
3681 		if (u_possible[d]) {
3682 		    atk = atkstats(side, a, d);
3683 		    if (atk > 0) {
3684 			fprintf(fp, " %4d ", atk);
3685 		    } else {
3686 			fprintf(fp, "      ");
3687 		    }
3688 		}
3689 	    }
3690 	    fprintf(fp, "\n");
3691 	}
3692     }
3693     fprintf(fp, "\n");
3694 }
3695 
3696 static int
atkstats(Side * side,int a,int d)3697 atkstats(Side *side, int a, int d)
3698 {
3699     int sum;
3700 
3701     if (side != NULL)
3702       return side_atkstats(side, a, d);
3703     sum = 0;
3704     for_all_sides(side) {
3705 	sum += side_atkstats(side, a, d);
3706     }
3707     return sum;
3708 }
3709 
3710 static int
hitstats(Side * side,int a,int d)3711 hitstats(Side *side, int a, int d)
3712 {
3713     int sum;
3714 
3715     if (side != NULL)
3716       return side_hitstats(side, a, d);
3717     sum = 0;
3718     for_all_sides(side) {
3719 	sum += side_hitstats(side, a, d);
3720     }
3721     return sum;
3722 }
3723 
3724 void
dice_desc(char * buf,int dice)3725 dice_desc(char *buf, int dice)
3726 {
3727     int numdice, die, offset;
3728 
3729     if ((dice < 0) || dice >> 14 == 0 || dice >> 14 == 3) {
3730 	sprintf(buf, "%d", dice);
3731     } else {
3732     	numdice = (dice >> 11) & 0x07;
3733     	die = (dice >> 7) & 0x0f;
3734     	offset = dice & 0x7f;
3735     	if (offset == 0) {
3736 	    sprintf(buf, "%dd%d", numdice, die);
3737     	} else {
3738 	    sprintf(buf, "%dd%d+%d", numdice, die, offset);
3739     	}
3740     }
3741 }
3742 
3743 /* The following code formats a list of types that are missing images. */
3744 
3745 void
record_missing_image(int typtyp,char * str)3746 record_missing_image(int typtyp, char *str)
3747 {
3748     if (missinglist == NULL) {
3749 	missinglist = (char *)xmalloc(BUFSIZE);
3750 	missinglist[0] = '\0';
3751     }
3752     ++missing[typtyp];
3753     /* Add the name of the image-less type, but only if one of
3754        the first few. */
3755     if (between(1, totlisted, NUMTOLIST))
3756       strcat(missinglist, ",");
3757     if (totlisted < NUMTOLIST) {
3758 	strcat(missinglist, str);
3759     } else if (totlisted == NUMTOLIST) {
3760 	strcat(missinglist, "...");
3761     }
3762     ++totlisted;
3763 }
3764 
3765 /* Return true if any images could not be found, and provide some
3766    helpful info into the supplied buffer. */
3767 
3768 int
missing_images(char * buf)3769 missing_images(char *buf)
3770 {
3771     if (missinglist == NULL)
3772       return FALSE;
3773     buf[0] = '\0';
3774     if (missing[UTYP] > 0)
3775       tprintf(buf, " %d unit images", missing[UTYP]);
3776     if (missing[TTYP] > 0)
3777       tprintf(buf, " %d terrain images", missing[TTYP]);
3778     if (missing[3] > 0)
3779       tprintf(buf, " %d emblems", missing[3]);
3780     tprintf(buf, " - %s", missinglist);
3781     return TRUE;
3782 }
3783 
3784 /* format a number up to 32 bits with 3 digits of precision. */
3785 
3786 char
format_number(char * buf,int value)3787 *format_number(char *buf, int value)
3788 {
3789     const int thousand = 1000;
3790     const int million = thousand * thousand;
3791     char *spbuf = buf;
3792 
3793     if(value < 0) {
3794 	value = -value;
3795 	*spbuf++ = '-';
3796     }
3797     *spbuf = 0;
3798     if (value < 10 * thousand) {
3799     	sprintf(spbuf, "%d", value);
3800     } else if (value < 100 * thousand) {
3801     	sprintf(spbuf,"%4.1fk", ((double) value) / thousand);
3802     } else if (value < million) {
3803     	sprintf(spbuf,"%dK", value / thousand);
3804     } else if (value < 10 * million) {
3805     	sprintf(spbuf,"%4.2fM", ((double) value) / million);
3806     } else if(value < 100 * million) {
3807     	sprintf(spbuf,"%4.1fM", ((double) value) / million);
3808     } else if(value < 1000 * million) {
3809     	sprintf(spbuf,"%dM", value / million);
3810     } else {
3811     	sprintf(spbuf,"%4.2G",((double) value) / (1000 * million));
3812     }
3813     return buf;
3814 }
3815