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