1 /* Online help support 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 is basically support code for interfaces, which handle the
11    actual help interaction themselves. */
12 
13 /* This file must also be translated (mostly) for non-English Xconq. */
14 
15 #include "conq.h"
16 #ifdef __cplusplus
17 extern "C" {
18 #endif
19 #include <fcntl.h>
20 #ifdef __cplusplus
21 }
22 #endif
23 
24 /* Extra functions. */
25 
26 int may_detonate(int u);
27 int any_ut_capacity_x(int u);
28 int any_mp_to_enter_unit(int u);
29 int any_mp_to_leave_unit(int u);
30 int any_enter_indep(int u);
31 
32 /* Help system internal variables. */
33 
34 static int help_output_cc = HELP_OUTPUT_CC_NONE;
35 static HelpOutputMode help_output_mode = HELP_OUTPUT_PLAIN_TEXT;
36 static char *help_output_dir = NULL;
37 static FILE *help_output_tmp_filep = NULL;
38 static FILE *help_output_toc_filep = NULL;
39 
40 static HelpPageDefn hpagedefns [] = {
41     /* HELP_PAGE_NONE */
42     { NULL, NULL, HELP_PAGE_NONE, HELP_PAGE_NONE, HELP_PAGE_NONE },
43     /* HELP_PAGE_MASTER_INDEX */
44     { NULL, "Master Game Index", HELP_PAGE_NONE, HELP_PAGE_NONE,
45       HELP_PAGE_NONE },
46     /* HELP_PAGE_TOC */
47     { "index", "Table of Contents", HELP_PAGE_MASTER_INDEX, HELP_PAGE_NONE,
48       HELP_PAGE_NONE },
49     /* HELP_PAGE_COPYRIGHT */
50     { "copyleft", "Copyright", HELP_PAGE_TOC, HELP_PAGE_CONCEPTS,
51       HELP_PAGE_WARRANTY },
52     /* HELP_PAGE_WARRANTY */
53     { "warranty", "Warranty (or lack thereof)", HELP_PAGE_TOC,
54       HELP_PAGE_COPYRIGHT, HELP_PAGE_NEWS },
55     /* HELP_PAGE_NEWS */
56     { "news", "News (or lack thereof)", HELP_PAGE_TOC, HELP_PAGE_WARRANTY,
57       HELP_PAGE_INSTRUCTIONS },
58     /* HELP_PAGE_INSTRUCTIONS */
59     { "isns", "Instructions", HELP_PAGE_TOC, HELP_PAGE_NEWS,
60       HELP_PAGE_GAME_OVERVIEW },
61     /* HELP_PAGE_GAME_OVERVIEW */
62     { "overview", "Game Overview", HELP_PAGE_TOC, HELP_PAGE_INSTRUCTIONS,
63       HELP_PAGE_SCORING },
64     /* HELP_PAGE_SCORING */
65     { "scoring", "Scoring", HELP_PAGE_TOC, HELP_PAGE_GAME_OVERVIEW,
66       HELP_PAGE_MODULES },
67     /* HELP_PAGE_MODULES */
68     { "modules", "Modules", HELP_PAGE_TOC, HELP_PAGE_SCORING,
69       HELP_PAGE_GAME_SETUP },
70     /* HELP_PAGE_GAME_SETUP */
71     { "config", "Game Setup", HELP_PAGE_TOC, HELP_PAGE_MODULES,
72       HELP_PAGE_WORLD },
73     /* HELP_PAGE_WORLD */
74     { "world", "World", HELP_PAGE_TOC, HELP_PAGE_GAME_SETUP, HELP_PAGE_UTYPE },
75     /* HELP_PAGE_UTYPE */
76     { "utype", "Unit", HELP_PAGE_TOC, HELP_PAGE_WORLD, HELP_PAGE_TTYPE },
77     /* HELP_PAGE_TTYPE */
78     { "ttype", "Terrain", HELP_PAGE_TOC, HELP_PAGE_UTYPE, HELP_PAGE_MTYPE },
79     /* HELP_PAGE_MTYPE */
80     { "mtype", "Material", HELP_PAGE_TOC, HELP_PAGE_TTYPE, HELP_PAGE_ATYPE },
81     /* HELP_PAGE_ATYPE */
82     { "atype", "Advance", HELP_PAGE_TOC, HELP_PAGE_MTYPE, HELP_PAGE_NONE },
83     /* HELP_PAGE_CONCEPTS */
84     { "concepts", "Game Concepts", HELP_PAGE_TOC, HELP_PAGE_ATYPE,
85       HELP_PAGE_COPYRIGHT }
86 };
87 #define HELP_PAGE_LAST	HELP_PAGE_CONCEPTS
88 
89 /* Obstack allocation and deallocation routines.  */
90 
91 #define obstack_chunk_alloc xmalloc
92 #define obstack_chunk_free free
93 
94 /* Help system internal functions. */
95 
96 static void describe_help_system(int arg, char *key, TextBuffer *buf);
97 static void describe_instructions(int arg, char *key, TextBuffer *buf);
98 static void describe_synth_run(TextBuffer *buf, int methkey);
99 static void describe_world(int arg, char *key, TextBuffer *buf);
100 static int histogram_compare(const void *h1, const void *h2);
101 static void describe_news(int arg, char *key, TextBuffer *buf);
102 static void describe_concepts(int arg, char *key, TextBuffer *buf);
103 static void describe_game_design(int arg, char *key, TextBuffer *buf);
104 static void describe_utype(int u, char *key, TextBuffer *buf);
105 static void describe_utype_movement(int u, char *key, TextBuffer *buf);
106 static void describe_utype_actions(int u, char *key, TextBuffer *buf);
107 static void describe_utype_side_attributes(int u, char *key, TextBuffer *buf);
108 static void describe_utype_ai_attributes(int u, char *key, TextBuffer *buf);
109 static void describe_mtype(int m, char *key, TextBuffer *buf);
110 static void describe_ttype(int t, char *key, TextBuffer *buf);
111 static void describe_atype(int t, char *key, TextBuffer *buf);
112 
113 static void describe_scorekeepers(int arg, char *key, TextBuffer *buf);
114 static void describe_setup(int arg, char *key, TextBuffer *buf);
115 static void describe_game_modules(int arg, char *key, TextBuffer *buf);
116 static void describe_game_module_aux(TextBuffer *buf, Module *module,
117 				     int level);
118 static void describe_module_notes(TextBuffer *buf, Module *module);
119 
120 static int u_property_not_default(int (*fn)(int i), int dflt);
121 static int t_property_not_default(int (*fn)(int i), int dflt);
122 static int uu_table_row_not_default(int u, int (*fn)(int i, int j), int dflt);
123 static int ut_table_row_not_default(int u, int (*fn)(int i, int j), int dflt);
124 static int um_table_row_not_default(int u, int (*fn)(int i, int j), int dflt);
125 static int ua_table_row_not_default(int u, int (*fn)(int i, int j), int dflt);
126 static int tt_table_row_not_default(int t, int (*fn)(int i, int j), int dflt);
127 #if 0
128 static int tm_table_row_not_default(int t, int (*fn)(int i, int j), int dflt);
129 #endif
130 static int aa_table_row_not_default(int a1, int (*fn)(int i, int j), int dflt);
131 static int uu_table_column_not_default(int u, int (*fn)(int i, int j),
132 				       int dflt);
133 static int aa_table_column_not_default(int a1, int (*fn)(int i, int j),
134 				       int dflt);
135 static void u_property_desc(TextBuffer *buf, int (*fn)(int),
136 			    void (*formatter)(TextBuffer *, int));
137 static void t_property_desc(TextBuffer *buf, int (*fn)(int),
138 			    void (*formatter)(TextBuffer *, int));
139 static void uu_table_row_desc(TextBuffer *buf, int u, int (*fn)(int, int),
140 			      void (*formatter)(TextBuffer *, int),
141 			      char *connect);
142 static void uu_table_column_desc(TextBuffer *buf, int u, int (*fn)(int, int),
143 				 void (*formatter)(TextBuffer *, int),
144 				 char *connect);
145 static void uu_table_rowcol_desc(TextBuffer *buf, int u, int (*fn)(int, int),
146 				 void (*formatter)(TextBuffer *, int),
147 				 char *connect, int rowcol);
148 static void ut_table_row_desc(TextBuffer *buf, int u, int (*fn)(int, int),
149 			      void (*formatter)(TextBuffer *, int),
150 			      char *connect);
151 static void um_table_row_desc(TextBuffer *buf, int u, int (*fn)(int, int),
152 			      void (*formatter)(TextBuffer *, int));
153 static void ua_table_row_desc(TextBuffer *buf, int u, int (*fn)(int, int),
154 			      void (*formatter)(TextBuffer *, int));
155 static void tt_table_row_desc(TextBuffer *buf, int t1, int (*fn)(int, int),
156 			      void (*formatter)(TextBuffer *, int));
157 #if 0
158 static void tm_table_row_desc(TextBuffer *buf, int t, int (*fn)(int, int),
159 			      void (*formatter)(TextBuffer *, int));
160 #endif
161 static void aa_table_row_desc(TextBuffer *buf, int a1, int (*fn)(int, int),
162 			      void (*formatter)(TextBuffer *, int));
163 static void aa_table_column_desc(TextBuffer *buf, int a1, int (*fn)(int, int),
164 				 void (*formatter)(TextBuffer *, int));
165 static void aa_table_rowcol_desc(TextBuffer *buf, int a1, int (*fn)(int, int),
166 				 void (*formatter)(TextBuffer *, int),
167 				 int rowcol);
168 static void tb_value_desc(TextBuffer *buf, int val);
169 static void tb_fraction_desc(TextBuffer *buf, int val);
170 static void tb_percent_desc(TextBuffer *buf, int val);
171 static void tb_percent_100th_desc(TextBuffer *buf, int val);
172 static void tb_probfraction_desc(TextBuffer *buf, int val);
173 static void tb_dice_desc(TextBuffer *buf, int val);
174 static void tb_mult_desc(TextBuffer *buf, int val);
175 static void tb_bool_desc(TextBuffer *buf, int val);
176 #if 0
177 static void append_number(TextBuffer *buf, int value, int dflt);
178 #endif
179 static void append_help_phrase(TextBuffer *buf, char *phrase);
180 static void append_notes(TextBuffer *buf, Obj *notes);
181 
182 static void init_help_pages(void);
183 static void meta_prep_help_cc(HelpPage hpage, int hpageidx);
184 static void prep_help_cc_toc(void);
185 static void prep_help_cc_any(char *ccfilename, char *sectionname);
186 static void flush_help_cc_tmp(HelpNode *helpnode);
187 static void meta_finish_help_cc(HelpPage hpage, int hpageidx,
188 				HelpNode *helpnode);
189 static void finish_help_cc_toc(void);
190 static void flush_help_cc(FILE *ccfile, HelpNode *helpnode);
191 static void write_help_file_navbar(FILE *ccfilep, HelpPage hpage, int hpageidx);
192 static void write_help_section_header(FILE *ccfile, char *headerdata);
193 static void write_help_section_footer(FILE *ccfilep, char *footerdata);
194 static void write_help_subsection_header(FILE *ccfile, char *headerdata);
195 static void write_help_subsection_footer(FILE *ccfile, char *footerdata);
196 static void write_help_subsection_break(FILE *ccfile);
197 
198 static char *help_newline(void);
199 static char *help_indent(unsigned int spaces);
200 
201 /* The first help node in the chain. */
202 
203 HelpNode *first_help_node;
204 
205 /* The last help node. */
206 
207 HelpNode *last_help_node;
208 
209 /* The help node with help system info. */
210 
211 HelpNode *help_system_node;
212 
213 /* The help node with copying and copyright info. */
214 
215 HelpNode *copying_help_node;
216 
217 /* The help node with (non-)warranty info. */
218 
219 HelpNode *warranty_help_node;
220 
221 HelpNode *default_prev_help_node;
222 
223 /* Set the help output carbon copy. */
224 void
set_help_output_cc(int cctarget)225 set_help_output_cc(int cctarget)
226 {
227     switch(cctarget) {
228       case HELP_OUTPUT_CC_NONE: case HELP_OUTPUT_CC_FILES:
229 	help_output_cc = cctarget;
230 	break;
231       default:
232 	run_error("Attempted to set an invalid help carbon copy target.");
233     }
234 }
235 
236 /* Set the help output mode. */
237 void
set_help_output_mode(HelpOutputMode houtmode)238 set_help_output_mode(HelpOutputMode houtmode)
239 {
240     switch(houtmode) {
241       case HELP_OUTPUT_PLAIN_TEXT: case HELP_OUTPUT_HTML:
242 	help_output_mode = houtmode;
243 	break;
244       default:
245 	run_error("Attempted to set an unsupported help output mode.");
246     }
247 }
248 
249 /* Set the help output directory. */
250 void
set_help_output_dir(char * dir)251 set_help_output_dir(char *dir)
252 {
253 #if (!defined MAC)
254     if (access(dir, F_OK))
255       run_error("Attempted to access a missing directory.");
256 #endif
257     help_output_dir = dir;
258 }
259 
260 /* Set the help TOC file stream pointer. */
261 void
set_help_toc_filep(FILE * htocfilep)262 set_help_toc_filep(FILE *htocfilep)
263 {
264     help_output_toc_filep = htocfilep;
265 }
266 
267 /* Create the initial help node and link it to itself.  Subsequent
268    nodes will be inserted later, after a game has been loaded. */
269 
270 void
init_help(void)271 init_help(void)
272 {
273     HelpNode *node = NULL;
274 
275     init_help_pages();
276     prep_help_cc_toc();
277     /* Note that we can't use add_help_node to set up the first help
278        node. */
279     first_help_node = create_help_node();
280     first_help_node->key = "--- BASIC HELP ---";
281     first_help_node->prev = first_help_node->next = first_help_node;
282     last_help_node = first_help_node;
283     help_system_node =
284       add_help_node("help system", describe_help_system, 0, first_help_node);
285     meta_prep_help_cc(HELP_PAGE_COPYRIGHT, -1);
286     copying_help_node =
287       add_help_node("copyright", describe_copyright, 0, help_system_node);
288     meta_finish_help_cc(HELP_PAGE_COPYRIGHT, -1, copying_help_node);
289     meta_prep_help_cc(HELP_PAGE_WARRANTY, -1);
290     warranty_help_node =
291       add_help_node("warranty", describe_warranty, 0, copying_help_node);
292     meta_finish_help_cc(HELP_PAGE_WARRANTY, -1, warranty_help_node);
293     /* Set the place for new nodes to appear normally. */
294     default_prev_help_node = copying_help_node;
295     meta_prep_help_cc(HELP_PAGE_NEWS, -1);
296     node = add_help_node("news", describe_news, 0, NULL);
297     meta_finish_help_cc(HELP_PAGE_NEWS, -1, node);
298 }
299 
300 /* This function creates the actual set of help nodes for the kernel. */
301 
302 void
create_game_help_nodes(void)303 create_game_help_nodes(void)
304 {
305     int u, m, t, a;
306     char *name, *longname;
307     HelpNode *node;
308 
309     add_help_node("--- MODULE INFO ---", NULL, 0, NULL);
310     meta_prep_help_cc(HELP_PAGE_INSTRUCTIONS, -1);
311     node = add_help_node("instructions", describe_instructions, 0, NULL);
312     meta_finish_help_cc(HELP_PAGE_INSTRUCTIONS, -1, node);
313     meta_prep_help_cc(HELP_PAGE_GAME_OVERVIEW, -1);
314     node = add_help_node("game overview", describe_game_design, 0, NULL);
315     meta_finish_help_cc(HELP_PAGE_GAME_OVERVIEW, -1, node);
316     meta_prep_help_cc(HELP_PAGE_SCORING, -1);
317     node = add_help_node("scoring", describe_scorekeepers, 0, NULL);
318     meta_finish_help_cc(HELP_PAGE_SCORING, -1, node);
319     meta_prep_help_cc(HELP_PAGE_MODULES, -1);
320     node = add_help_node("modules", describe_game_modules, 0, NULL);
321     meta_finish_help_cc(HELP_PAGE_MODULES, -1, node);
322     meta_prep_help_cc(HELP_PAGE_GAME_SETUP, -1);
323     node = add_help_node("game setup", describe_setup, 0, NULL);
324     meta_finish_help_cc(HELP_PAGE_GAME_SETUP, -1, node);
325     meta_prep_help_cc(HELP_PAGE_WORLD, -1);
326     node = add_help_node("world", describe_world, 0, NULL);
327     meta_finish_help_cc(HELP_PAGE_WORLD, -1, node);
328     if (numutypes) {
329         add_help_node("--- UNIT TYPES ---", NULL, 0, NULL);
330 	write_help_toc_entry(NULL, "Unit Types", 0);
331     }
332     for_all_unit_types(u) {
333 	longname = u_long_name(u);
334 	if (!empty_string(longname)) {
335 	    sprintf(spbuf, "%s (%s)", longname, u_type_name(u));
336 	    name = copy_string(spbuf);
337 	} else {
338 	    name = u_type_name(u);
339 	}
340 	meta_prep_help_cc(HELP_PAGE_UTYPE, u);
341 	node = add_help_node(name, describe_utype, u, NULL);
342 	node->nclass = utypenode;
343 	meta_finish_help_cc(HELP_PAGE_UTYPE, u, node);
344     }
345     if (nummtypes) {
346         add_help_node("--- MATERIALS ---", NULL, 0, NULL);
347 	write_help_toc_entry(NULL, "Material Types", 0);
348     }
349     for_all_material_types(m) {
350 	meta_prep_help_cc(HELP_PAGE_MTYPE, m);
351 	node = add_help_node(m_type_name(m), describe_mtype, m, NULL);
352 	node->nclass = mtypenode;
353 	meta_finish_help_cc(HELP_PAGE_MTYPE, m, node);
354     }
355     if (numttypes) {
356         add_help_node("--- TERRAIN TYPES ---", NULL, 0, NULL);
357 	write_help_toc_entry(NULL, "Terrain Types", 0);
358     }
359     for_all_terrain_types(t) {
360 	meta_prep_help_cc(HELP_PAGE_TTYPE, t);
361 	node = add_help_node(t_type_name(t), describe_ttype, t, NULL);
362 	node->nclass = ttypenode;
363 	meta_finish_help_cc(HELP_PAGE_TTYPE, t, node);
364     }
365     if (numatypes) {
366         add_help_node("--- ADVANCES ---", NULL, 0, NULL);
367 	write_help_toc_entry(NULL, "Advances", 0);
368     }
369     for_all_advance_types(a) {
370 	meta_prep_help_cc(HELP_PAGE_ATYPE, a);
371 	node = add_help_node(a_type_name(a), describe_atype, a, NULL);
372 	node->nclass = atypenode;
373 	meta_finish_help_cc(HELP_PAGE_ATYPE, a, node);
374     }
375     add_help_node("--- GENERAL INFO ---", NULL, 0, NULL);
376     meta_prep_help_cc(HELP_PAGE_CONCEPTS, -1);
377     node = add_help_node("general concepts", describe_concepts, 0, NULL);
378     meta_finish_help_cc(HELP_PAGE_CONCEPTS, -1, node);
379     /* Invalidate any existing topics node. */
380     first_help_node->text = NULL;
381     finish_help_cc_toc();
382 }
383 
384 /* Create an empty help node. */
385 
386 HelpNode *
create_help_node(void)387 create_help_node(void)
388 {
389     HelpNode *node = (HelpNode *) xmalloc(sizeof(HelpNode));
390 
391     node->key = NULL;
392     node->fn = NULL;
393     node->nclass = miscnode;
394     node->arg = 0;
395     node->text = NULL;
396     node->prev = node->next = NULL;
397     return node;
398 }
399 
400 /* Add a help node after the given node. */
401 
402 HelpNode *
add_help_node(char * key,void (* fn)(int t,char * key,TextBuffer * buf),int arg,HelpNode * prevnode)403 add_help_node(char *key, void (*fn)(int t, char *key, TextBuffer *buf),
404 	      int arg, HelpNode *prevnode)
405 {
406     HelpNode *node, *nextnode;
407 
408     if (empty_string(key)) {
409 	run_error("empty help key");
410     }
411     node = create_help_node();
412     node->key = key;
413     node->fn = fn;
414     node->arg = arg;
415     if (prevnode == NULL)
416       prevnode = default_prev_help_node->prev;
417     nextnode = prevnode->next;
418     node->prev = prevnode;
419     node->next = nextnode;
420     prevnode->next = node;
421     nextnode->prev = node;
422     /* Might need to fix last help node. */
423     last_help_node = first_help_node->prev;
424     return node;
425 }
426 
427 /* Given a string and node, find the next node whose key matches. */
428 
429 HelpNode *
find_help_node(HelpNode * node,char * str)430 find_help_node(HelpNode *node, char *str)
431 {
432     HelpNode *tmp;
433 
434     /* Note that the search wraps around. */
435     for (tmp = node->next; tmp != node; tmp = tmp->next) {
436     	if (strcmp(tmp->key, str) == 0)
437     	  return tmp;
438     }
439     return NULL;
440 }
441 
442 /* Return the string containing the text of the help node, possibly
443    computing it first. */
444 
445 char *
get_help_text(HelpNode * node)446 get_help_text(HelpNode *node)
447 {
448     TextBuffer tbuf;
449 
450     if (node != NULL) {
451 	/* Maybe calculate the text to display. */
452 	if (node->text == NULL) {
453 	    if (node->fn != NULL) {
454 		/* (should allow for variable-size allocation) */
455 	    	obstack_begin(&(tbuf.ostack), 200);
456 		if (1) {
457 		    node->textend = 0;
458 		    (*(node->fn))(node->arg, node->key, &tbuf);
459 		    obstack_1grow(&(tbuf.ostack), '\0');
460 		    node->text = copy_string((char *)obstack_finish(&(tbuf.ostack)));
461 		    obstack_free(&(tbuf.ostack), 0);
462 		    node->textend = strlen(node->text);
463 		} else {
464 		    /* Ran out of memory... (would never get here though!) */
465 		}
466 	    } else if (strspn(node->key, "-") > 2) {
467 		/* Generate an empty separator. */
468 		sprintf(spbuf, " ");
469 		node->text = copy_string(spbuf);
470 		node->textend = strlen(node->text);
471 	    } else {
472 		/* Generate a default message if nothing to compute help. */
473 		sprintf(spbuf, "%s: No info available.", node->key);
474 		node->text = copy_string(spbuf);
475 		node->textend = strlen(node->text);
476 	    }
477 	    Dprintf("Size of help node \"%s\" text is %d\n", node->key, node->textend);
478 	}
479 	return node->text;
480     } else {
481 	return NULL;
482     }
483 }
484 
485 static void
describe_help_system(int arg,char * key,TextBuffer * buf)486 describe_help_system(int arg, char *key, TextBuffer *buf)
487 {
488     tbcatline(buf, "This is the header node of the Xconq help system.");
489     tbcatline(buf, "Go forward or backward from here to see the online help.");
490 }
491 
492 /* Create a raw list of help topics by just iterating through all the nodes,
493    except for the topics node itself. */
494 
495 void
describe_topics(int arg,char * key,TextBuffer * buf)496 describe_topics(int arg, char *key, TextBuffer *buf)
497 {
498     HelpNode *topics, *tmp;
499 
500     topics = find_help_node(first_help_node, "topics");
501     /* Unlikely that we'll call this without the topics node existing
502        already, but just in case... */
503     if (topics == NULL)
504       return;
505     for (tmp = topics->next; tmp != topics; tmp = tmp->next) {
506 	tbprintf(buf, "%s", tmp->key);
507 	tbcat(buf, "\n");
508     }
509 }
510 
511 /* Get the news file and put it into text buffer. */
512 
513 static void
describe_news(int arg,char * key,TextBuffer * buf)514 describe_news(int arg, char *key, TextBuffer *buf)
515 {
516     FILE *fp;
517 
518     fp = open_file(news_filename(), "r");
519     if (fp != NULL) {
520 	tbcat(buf, "XCONQ NEWS\n\n");
521 	while (fgets(spbuf, BUFSIZE-1, fp) != NULL) {
522 	    tbcat(buf, spbuf);
523 	}
524 	fclose(fp);
525     } else {
526 	tbcat(buf, "(no news)");
527     }
528 }
529 
530 /* Describe general game concepts in a general way.  If a concept does
531    not apply to the game in effect, then just say it's not part of
532    this game (would be confusing if the online doc described things
533    irrelevant to the specific game). */
534 
535 static void
describe_concepts(int arg,char * key,TextBuffer * buf)536 describe_concepts(int arg, char *key, TextBuffer *buf)
537 {
538     tbcat(buf, "Hit points (HP) represent the overall condition of ");
539     tbcatline(buf, "the unit.");
540     tbcat(buf, "Action points (ACP) are what a unit needs to be able ");
541     tbcat(buf, "to do anything at all.  Typically a unit will use 1 ACP ");
542     tbcatline(buf, "to move 1 cell.");
543     tbcat(buf, "Movement points (MP) represent varying costs of movement ");
544     tbcat(buf, "actions, such as a difficult-to-cross border.  The number ");
545     tbcat(buf, "of movement points is added up then divided by unit's speed ");
546     tbcatline(buf, "to get the total number of acp used up by a move.");
547 #if (0)
548     if (0) {
549     } else {
550 	tbcatline(buf, "No combat experience (CXP) in this game.");
551     }
552     if (0) {
553     } else {
554 	tbcatline(buf, "No morale (MO) in this game.");
555     }
556 #endif
557     tbcat(buf, "Each unit that can do anything has a plan, and a list of ");
558     tbcatline(buf, "tasks to perform.");
559     /* (should describe more general concepts) */
560 }
561 
562 static void
describe_instructions(int arg,char * key,TextBuffer * buf)563 describe_instructions(int arg, char *key, TextBuffer *buf)
564 {
565     Obj *instructions = mainmodule->instructions;
566 
567     if (instructions != lispnil) {
568 	append_notes(buf, instructions);
569     } else {
570 	tbcat(buf, "(no instructions supplied)");
571     }
572 }
573 
574 /* Spit out all the general game_design parameters in a readable fashion. */
575 
576 static void
describe_game_design(int arg,char * key,TextBuffer * buf)577 describe_game_design(int arg, char *key, TextBuffer *buf)
578 {
579     int u, m, t, a;
580 
581     /* Replicate title and blurb? (should put title at head of
582        windows, and pages if printed) */
583     tbprintf(buf, "*** %s ***%s",
584 	     (mainmodule->title ? mainmodule->title : mainmodule->name),
585 	     help_newline());
586     tbcatline(buf, "");
587     tbprintf(buf, "This game includes %d unit types and %d terrain types",
588 	     numutypes, numttypes);
589     if (nummtypes > 0) {
590 	tbprintf(buf, ", along with %d material types", nummtypes);
591     }
592     if (numatypes > 0) {
593 	tbprintf(buf, ", and it has %d types of advances", numatypes);
594     }
595     tbcatline(buf, ".");
596     if (g_sides_min() == g_sides_max()) {
597     	tbprintf(buf, "Exactly %d sides may play.%s", g_sides_min(),
598 		 help_newline());
599     } else {
600 	tbprintf(buf,
601 "Number of sides to play may range from %d to %d, defaulting to %d.%s",
602 		 g_sides_min(), g_sides_max(), g_sides_wanted(),
603 		 help_newline());
604     }
605     tbcatline(buf, "");
606     if (g_advantage_min() == g_advantage_max())
607       tbprintf(buf, "Side advantages are fixed.%s", help_newline());
608     else
609       tbprintf(buf,
610 "Side advantages may range from %d to %d, defaulting to %d.%s",
611 	       g_advantage_min(), g_advantage_max(), g_advantage_default(),
612 	       help_newline());
613     tbcatline(buf, "");
614     if (g_see_all()) {
615 	tbcatline(buf, "Everything is always seen by all sides.");
616     } else {
617     	if (g_see_terrain_always()) {
618 	    tbcatline(buf, "Terrain view is always accurate once seen.");
619     	}
620     	/* (should only have if any weather to be seen) */
621     	if ((any_temp_variation || any_wind_variation || any_clouds)
622 	    && g_see_weather_always()) {
623 	    tbcatline(buf,
624 		      "Weather view is always accurate once terrain seen.");
625     	}
626     	if (g_terrain_seen()) {
627 	    tbcatline(buf, "World terrain is already seen by all sides.");
628     	}
629     }
630     tbcatline(buf, "");
631     if (g_last_turn() != gvar_i_default(g_last_turn)) {
632 	tbprintf(buf, "Game can go for up to %d turns", g_last_turn());
633 	if (g_extra_turn() > 0) {
634 	    tbprintf(buf, ", with %d%% chance of additional turn thereafter.",
635 		     g_extra_turn());
636 	}
637 	tbcatline(buf, ".");
638     }
639     if (g_rt_for_game() > 0) {
640 	tbprintf(buf, "Entire game can last up to %d minutes.%s",
641 		 g_rt_for_game() / 60, help_newline());
642     }
643     if (g_rt_per_turn() > 0) {
644 	tbprintf(buf, "Each turn can last up to %d minutes.%s",
645 		g_rt_per_turn() / 60, help_newline());
646     }
647     if (g_rt_per_side() > 0) {
648 	tbprintf(buf, "Each side gets a total %d minutes to act.%s",
649 		g_rt_per_side() / 60, help_newline());
650     }
651     if (g_units_in_game_max() >= 0) {
652 	tbprintf(buf, "Limited to no more than %d units in all.%s",
653 		 g_units_in_game_max(), help_newline());
654     }
655     if (g_units_per_side_max() >= 0) {
656 	tbprintf(buf, "Limited to no more than %d units per side.%s",
657 		 g_units_per_side_max(), help_newline());
658     }
659     if (g_use_side_priority()) {
660 	tbcatline(buf, "Sides move sequentially, in priority order.");
661     } else {
662 	tbcatline(buf, "Sides move simultaneously.");
663     }
664     if (any_temp_variation) {
665 	tbprintf(buf,
666 		 "Lowest possible temperature is %d, at an elevation of %d.%s",
667 		 g_temp_floor(), g_temp_floor_elev(), help_newline());
668 	tbprintf(buf, "Temperatures averaged to range %d.%s",
669 		 g_temp_mod_range(), help_newline());
670     }
671     tbprintf(buf, "%sUnit Types:%s", help_newline(), help_newline());
672     for_all_unit_types(u) {
673 	tbprintf(buf, "%s%s", help_indent(2), u_type_name(u));
674 	if (!empty_string(u_help(u)))
675 	  tbprintf(buf, " (%s)", u_help(u));
676 	tbcatline(buf, "");
677 #ifdef DESIGNERS
678 	/* Show designers a bit more. */
679 	if (numdesigners > 0) {
680 	    tbcat(buf, "    [");
681 	    if (!empty_string(u_uchar(u)))
682 	      tbprintf(buf, "char '%s'", u_uchar(u));
683 	    else
684 	      tbcat(buf, "no char");
685 	    if (!empty_string(u_gchar(u)))
686 	      tbprintf(buf, "generic char '%s'", u_gchar(u));
687 	    else
688 	      tbcat(buf, "no generic char");
689 	    if (!empty_string(get_string(u_image_name(u))))
690 	      tbprintf(buf, ", image \"%s\"", get_string(u_image_name(u)));
691 	    if (!empty_string(u_generic_name(u)))
692 	      tbprintf(buf, ", generic name \"%s\"", u_generic_name(u));
693 	    if (u_desc_format(u) != lispnil) {
694 	        tbcat(buf, ", special format");
695 	    }
696 	    tbcatline(buf, "]");
697 	}
698 #endif /* DESIGNERS */
699     }
700     tbprintf(buf, "%sTerrain Types:%s", help_newline(), help_newline());
701     for_all_terrain_types(t) {
702 	tbprintf(buf, "%s%s", help_indent(2), t_type_name(t));
703 	if (!empty_string(t_help(t)))
704 	  tbprintf(buf, " (%s)", t_help(t));
705 	tbcatline(buf, "");
706 #ifdef DESIGNERS
707 	/* Show designers a bit more. */
708 	if (numdesigners > 0) {
709 	    tbcat(buf, "    [");
710 	    if (!empty_string(t_char(t)))
711 	      tbprintf(buf, "char '%s'", t_char(t));
712 	    else
713 	      tbcat(buf, "no char");
714 	    if (!empty_string(t_image_name(t)))
715 	      tbprintf(buf, ", image \"%s\"", t_image_name(t));
716 	    tbcatline(buf, "]");
717 	}
718 #endif /* DESIGNERS */
719     }
720     if (nummtypes > 0) {
721 	tbprintf(buf, "%sMaterial Types:%s", help_newline(), help_newline());
722 	for_all_material_types(m) {
723 	    tbprintf(buf, "%s%s", help_indent(2), m_type_name(m));
724 	    if (!empty_string(m_help(m)))
725 	      tbprintf(buf, " (%s)", m_help(m));
726 	    tbcatline(buf, "");
727 #ifdef DESIGNERS
728 	    /* Show designers a bit more. */
729 	    if (numdesigners > 0) {
730 		tbcat(buf, "    [");
731 		if (!empty_string(m_char(m)))
732 		  tbprintf(buf, "char '%s'", m_char(m));
733 		else
734 		  tbcat(buf, "no char");
735 		if (!empty_string(m_image_name(m)))
736 		  tbprintf(buf, ", image \"%s\"", m_image_name(m));
737 		tbcatline(buf, "]");
738 	    }
739 #endif /* DESIGNERS */
740 	}
741     }
742     if (numatypes > 0) {
743 	tbprintf(buf, "%sAdvances:%s", help_newline(), help_newline());
744 	for_all_advance_types(a) {
745 	    tbprintf(buf, "%s%s", help_indent(2), a_type_name(a));
746 	    if (!empty_string(a_help(a)))
747 	      tbprintf(buf, " (%s)", a_help(a));
748 	    tbcatline(buf, "");
749 #ifdef DESIGNERS
750 	    /* Show designers a bit more. */
751 	    if (numdesigners > 0) {
752 		tbcat(buf, "    [");
753 		if (!empty_string(a_image_name(a)))
754 		  tbprintf(buf, ", image \"%s\"", a_image_name(a));
755 		tbcatline(buf, "]");
756 	    }
757 #endif /* DESIGNERS */
758 	}
759     }
760 #ifdef DESIGNERS
761     /* Show designers a bit more. */
762     if (numdesigners > 0) {
763 	tbcat(buf, "FOR DESIGNERS:\n");
764 	tbprintf(buf, "Unseen terrain char is \"%s\".%s", g_unseen_char(),
765 		 help_newline());
766 	tbprintf(buf, "Scorefile name is \"%s\".%s", g_scorefile_name(),
767 		 help_newline());
768     }
769 #endif /* DESIGNERS */
770 }
771 
772 /* Display game module info to a side. */
773 
774 static void
describe_game_modules(int arg,char * key,TextBuffer * buf)775 describe_game_modules(int arg, char *key, TextBuffer *buf)
776 {
777     if (mainmodule != NULL) {
778 	/* First put out basic module info. */
779 	describe_game_module_aux(buf, mainmodule, 0);
780 	/* Now do the lengthy module notes (with no indentation). */
781 	describe_module_notes(buf, mainmodule);
782     } else {
783 	tbcat(buf, "(No game module information is available.)");
784     }
785 }
786 
787 /* Recurse down through included modules to display docs on each.
788    Indents each file by inclusion level.  Note that modules cannot
789    be loaded more than once, so each will be described only once here. */
790 
791 static void
describe_game_module_aux(TextBuffer * buf,Module * module,int level)792 describe_game_module_aux(TextBuffer *buf, Module *module, int level)
793 {
794     int i;
795     char indentbuf[BUFSIZE];
796     char dashbuf[BUFSIZE];
797     int indentsz = 0;
798     Module *submodule;
799 
800     dashbuf[0] = '\0';
801     indentbuf[0] = '\0';
802     indentsz = strlen(help_indent(3));
803     for (i = 0; i < level; ++i) {
804 	strcat(dashbuf, "-- ");
805 	strncat(indentbuf, help_indent(3), BUFSIZE - (indentsz * i));
806     }
807     tbprintf(buf, "%s\"%s\"", dashbuf,
808 	    (module->title ? module->title : module->name));
809     /* Display the true name of the module if not the same as the title. */
810     if (module->title != NULL && strcmp(module->title, module->name) != 0) {
811 	tbprintf(buf, " (\"%s\")", module->name);
812     }
813     if (module->version != NULL) {
814 	tbprintf(buf, " (version \"%s\")", module->version);
815     }
816     tbcatline(buf, help_indent(10));
817     if (module->blurb != lispnil) {
818 	tbcat(buf, indentbuf);
819 	append_notes(buf, module->blurb);
820 	tbcatline(buf, "");
821     } else {
822 	tbprintf(buf, "%s(no description)%s", indentbuf, help_newline());
823     }
824     if (module->notes != lispnil) {
825 	tbprintf(buf, "%sNotes to \"%s\":%s", indentbuf, module->name,
826 		 help_newline());
827 	append_notes(buf, module->notes);
828 	tbcatline(buf, "");
829     }
830     tbcatline(buf, "");
831     /* Now describe any included modules. */
832     for_all_includes(module, submodule) {
833 	describe_game_module_aux(buf, submodule, level + 1);
834     }
835 }
836 
837 /* Dump the module designer's notes into the given buffer.  When doing
838    submodules, don't indent. */
839 
840 static void
describe_module_notes(TextBuffer * buf,Module * module)841 describe_module_notes(TextBuffer *buf, Module *module)
842 {
843     Module *submodule;
844 
845 #ifdef DESIGNERS
846     /* Only show design notes if any designers around. */
847     if (numdesigners > 0 && module->designnotes != lispnil) {
848 	tbprintf(buf, "Design Notes to \"%s\":%s", module->name,
849 		 help_newline());
850 	append_notes(buf, module->designnotes);
851 	tbcatline(buf, "");
852     }
853 #endif /* DESIGNERS */
854     for_all_includes(module, submodule) {
855 	describe_module_notes(buf, submodule);
856     }
857 }
858 
859 int
any_ut_capacity_x(int u)860 any_ut_capacity_x(int u)
861 {
862     int t;
863 
864     for_all_terrain_types(t) {
865 	if (ut_capacity_x(u, t) != 0)
866 	  return TRUE;
867     }
868     return FALSE;
869 }
870 
871 int
any_mp_to_enter_unit(int u)872 any_mp_to_enter_unit(int u)
873 {
874     int u2;
875 
876     for_all_unit_types(u2) {
877 	if (uu_mp_to_enter(u, u2) != 0)
878 	  return TRUE;
879     }
880     return FALSE;
881 }
882 
883 int
any_mp_to_leave_unit(int u)884 any_mp_to_leave_unit(int u)
885 {
886     int u2;
887 
888     for_all_unit_types(u2) {
889 	if (uu_mp_to_leave(u, u2) != 0)
890 	  return TRUE;
891     }
892     return FALSE;
893 }
894 
895 int
any_enter_indep(int u)896 any_enter_indep(int u)
897 {
898     int u2;
899 
900     for_all_unit_types(u2) {
901 	if (uu_can_enter_indep(u, u2))
902 	  return TRUE;
903     }
904     return FALSE;
905 }
906 
907 /* Full details on the given type of unit. */
908 
909 static void
describe_utype(int u,char * key,TextBuffer * buf)910 describe_utype(int u, char *key, TextBuffer *buf)
911 {
912     int m, usesm, a;
913 
914     append_help_phrase(buf, u_help(u));
915     /* Display the point value of the unit. */
916     if (u_point_value(u) > 0) {
917 	tbprintf(buf, "Point Value: %d%s%s", u_point_value(u),
918 		 help_newline(), help_newline());
919     }
920     /* Display the designer's notes for this type. */
921     if (u_notes(u) != lispnil) {
922 	tbcatline(buf, "Notes:");
923 	append_notes(buf, u_notes(u));
924 	tbprintf(buf, "%s%s", help_newline(), help_newline());
925     }
926     /* Display side attributes. */
927     describe_utype_side_attributes(u, key, buf);
928     /* Display action attributes. */
929     describe_utype_actions(u, key, buf);
930     /* Display movement attributes. */
931     describe_utype_movement(u, key, buf);
932     tbprintf(buf, "Hit Points (HP): %d.", u_hp_max(u));
933     if (u_parts(u) > 1) {
934 	tbprintf(buf, "  Parts: %d.", u_parts(u));
935     }
936     if (u_hp_recovery(u) != 0) {
937 	tbprintf(buf, "%sRecovers by ", help_indent(2));
938 	tb_fraction_desc(buf, u_hp_recovery(u));
939 	tbprintf(buf, " HP each turn");
940 	if (u_hp_to_recover(u) > 0)
941 	  tbprintf(buf, ", if over %d HP", u_hp_to_recover(u));
942 	tbcat(buf, ".");
943     }
944     tbcatline(buf, "");
945     if (ut_table_row_not_default(u, ut_vanishes_on, 0)) {
946 	tbprintf(buf, "Vanishes if in: ");
947 	ut_table_row_desc(buf, u, ut_vanishes_on, tb_bool_desc, "");
948 	tbcatline(buf, ".");
949     }
950     if (ut_table_row_not_default(u, ut_wrecks_on, 0)) {
951 	tbprintf(buf, "Immediate wreck if in: ");
952 	ut_table_row_desc(buf, u, ut_wrecks_on, tb_bool_desc, "");
953 	tbcatline(buf, ".");
954     }
955     /* Describe unit's transport capabilities. */
956     if (u_capacity(u) > 0
957 	|| uu_table_row_not_default(u, uu_capacity_x, 0)) {
958 	if (u_capacity(u) > 0) {
959 	    tbprintf(buf, "Generic capacity for units is %d.%s",
960 		     u_capacity(u), help_newline());
961 	    if (uu_table_column_not_default(u, uu_size, 1)) {
962 		tbcat(buf, "Relative sizes of occupants: ");
963 		uu_table_column_desc(buf, u, uu_size, NULL, NULL);
964 		tbcatline(buf, ".");
965 	    }
966 	}
967 	if (uu_table_row_not_default(u, uu_capacity_x, 0)) {
968 	    tbcat(buf, "Dedicated space for units: ");
969 	    uu_table_row_desc(buf, u, uu_capacity_x, NULL, NULL);
970 	    tbcatline(buf, ".");
971 	}
972 	/* (should only display if < capacities would allow?) */
973 	if (uu_table_row_not_default(u, uu_occ_max, -1)) {
974 	    tbcat(buf, "Maximum number of occupants: ");
975 	    uu_table_row_desc(buf, u, uu_occ_max, NULL, NULL);
976 	    tbcatline(buf, ".");
977 	}
978 	if (u_occ_total_max(u) >= 0) {
979 	    tbprintf(buf, "Maximum total of %d for all types together.%s",
980 		     u_occ_total_max(u), help_newline());
981 	}
982     }
983     if (any_ut_capacity_x(u)) {
984 	tbcat(buf, "Exclusive terrain capacity: ");
985         ut_table_row_desc(buf, u, ut_capacity_x, NULL, NULL);
986 	tbcatline(buf, ".");
987     }
988     if (uu_table_row_not_default(u, uu_zoc_range, 0)) {
989 	tbcat(buf, "Exerts ZOC out to: ");
990         uu_table_row_desc(buf, u, uu_zoc_range, NULL, NULL);
991 	tbcatline(buf, ".");
992 	if (ut_table_row_not_default(u, ut_zoc_into, 1)) {
993 	    tbcat(buf, "Exerts ZOC into: ");
994 	    ut_table_row_desc(buf, u, ut_zoc_into, tb_bool_desc, "in");
995 	    tbcatline(buf, ".");
996 	}
997 	if (ut_table_row_not_default(u, ut_zoc_from_terrain, 100)) {
998 	    tbcat(buf, "Effect of own terrain: ");
999 	    ut_table_row_desc(buf, u, ut_zoc_from_terrain, tb_mult_desc, "in");
1000 	    tbcatline(buf, ".");
1001 	}
1002 	if (uu_table_row_not_default(u, uu_mp_to_enter_zoc, -1)) {
1003 	    tbcat(buf, "MP to enter ZOC: ");
1004 	    uu_table_row_desc(buf, u, uu_mp_to_enter_zoc, NULL, NULL);
1005 	    tbcatline(buf, ".");
1006 	}
1007 	if (uu_table_row_not_default(u, uu_mp_to_leave_zoc, 0)) {
1008 	    tbcat(buf, "MP to leave ZOC: ");
1009 	    uu_table_row_desc(buf, u, uu_mp_to_leave_zoc, NULL, NULL);
1010 	    tbcatline(buf, ".");
1011 	}
1012 	if (uu_table_row_not_default(u, uu_mp_to_traverse_zoc, 0)) {
1013 	    tbcat(buf, "MP to traverse ZOC: ");
1014 	    uu_table_row_desc(buf, u, uu_mp_to_traverse_zoc, NULL, NULL);
1015 	    tbcatline(buf, ".");
1016 	}
1017     }
1018     if (u_cxp_max(u) != 0) {
1019 	tbprintf(buf, "Combat experience (CXP) maximum: %d.%s", u_cxp_max(u),
1020 		 help_newline());
1021     }
1022     if (u_morale_max(u) != 0) {
1023 	tbprintf(buf, "Morale maximum: %d", u_morale_max(u));
1024 	if (u_morale_recovery(u) != 0)
1025 	  tbprintf(buf, ", recover %s each turn", u_morale_recovery(u));
1026 	tbcatline(buf, ".");
1027     }
1028     if (u_cp(u) != 1) {
1029 	tbprintf(buf, "Construction points (CP): %d.%s", u_cp(u),
1030 		 help_newline());
1031     }
1032     if (u_tech_to_see(u) != 0) {
1033 	tbprintf(buf, "Tech to see: %d.%s", u_tech_to_see(u),
1034 		 help_newline());
1035     }
1036     if (u_tech_to_own(u) != 0) {
1037 	tbprintf(buf, "Tech to own: %d.%s", u_tech_to_own(u),
1038 		 help_newline());
1039     }
1040     if (u_tech_to_use(u) != 0) {
1041 	tbprintf(buf, "Tech to use: %d.%s", u_tech_to_use(u),
1042 		 help_newline());
1043     }
1044     if (u_tech_to_build(u) != 0) {
1045 	tbprintf(buf, "Tech to build: %d.%s", u_tech_to_build(u),
1046 		 help_newline());
1047     }
1048     if (u_tech_to_change_type_to(u)) {
1049 	tbprintf(buf, "Tech to change type to: %d.%s",
1050 		 u_tech_to_change_type_to(u), help_newline());
1051     }
1052     if (u_tech_max(u) != 0) {
1053 	tbprintf(buf, "Tech max: %d.%s", u_tech_max(u), help_newline());
1054     }
1055     if (u_tech_max(u) != 0 && u_tech_per_turn_max(u) != PROPHI) {
1056 	tbprintf(buf, "Tech increase per turn max: %d.%s",
1057 		 u_tech_per_turn_max(u), help_newline());
1058     }
1059     if (u_tech_from_ownership(u) != 0) {
1060 	tbprintf(buf, "Tech guaranteed by ownership: %d.%s",
1061 		 u_tech_from_ownership(u), help_newline());
1062     }
1063     if (u_tech_leakage(u) != 0) {
1064 	tbprintf(buf, "Tech leakage: %d.%s", u_tech_leakage(u),
1065 		 help_newline());
1066     }
1067     if (type_max_acp(u) > 0
1068         && type_can_develop(u) > 0
1069         ) {
1070         tbprintf(buf, "%sDevelop:%s", help_newline(), help_newline());
1071 	tbcat(buf, "ACP to develop: ");
1072         uu_table_row_desc(buf, u, uu_acp_to_develop, NULL, NULL);
1073 	tbcatline(buf, ".");
1074 	tbcat_si(buf, "Tech gained: ");
1075         uu_table_row_desc(buf, u, uu_tech_per_develop, tb_fraction_desc, NULL);
1076 	tbcatline(buf, ".");
1077     }
1078     if (type_max_acp(u) > 0
1079         && (could_create_any(u) || could_build_any(u))) {
1080         tbprintf(buf, "%sConstruction:%s", help_newline(), help_newline());
1081         if (could_create_any(u)) {
1082 	    tbcat(buf, "ACP to create: ");
1083 	    uu_table_row_desc(buf, u, uu_acp_to_create, NULL, NULL);
1084 	    tbcatline(buf, ".");
1085 	    if (uu_table_row_not_default(u, uu_create_range, 0)) {
1086   		tbcat_si(buf, "Creation distance max: ");
1087 		uu_table_row_desc(buf, u, uu_create_range, NULL, NULL);
1088  		tbcatline(buf, ".");
1089  	    }
1090 	    if (uu_table_row_not_default(u, uu_creation_cp, 1)) {
1091   		tbcat_si(buf, "CP upon creation: ");
1092 		uu_table_row_desc(buf, u, uu_creation_cp, NULL, NULL);
1093  		tbcatline(buf, ".");
1094  	    }
1095 	}
1096         if (could_build_any(u)) {
1097 	    tbcat(buf, "ACP to build: ");
1098 	    uu_table_row_desc(buf, u, uu_acp_to_build, NULL, NULL);
1099 	    tbcatline(buf, ".");
1100 	    if (uu_table_row_not_default(u, uu_cp_per_build, 1)) {
1101  		tbcat_si(buf, "CP added per build: ");
1102  		uu_table_row_desc(buf, u, uu_cp_per_build, NULL, NULL);
1103  		tbcatline(buf, ".");
1104  	    }
1105         }
1106         if (u_cp_per_self_build(u) > 0) {
1107 	    tbprintf(buf,
1108 "Can finish building self at %d cp, will add %d cp per action.%s",
1109 		    u_cp_to_self_build(u), u_cp_per_self_build(u),
1110 		    help_newline());
1111         }
1112 	if (uu_table_row_not_default(u, uu_build_range, 0)) {
1113 	    tbcat(buf, "Range at which can build: ");
1114 	    uu_table_row_desc(buf, u, uu_build_range, NULL, NULL);
1115 	    tbcatline(buf, ".");
1116 	} else {
1117 	    tbcatline(buf, "Can build at own location.");
1118 	}
1119         /* Toolup help. */
1120         if (could_toolup_for_any(u)) {
1121 	    tbcat(buf, "ACP to toolup: ");
1122 	    uu_table_row_desc(buf, u, uu_acp_to_toolup, NULL, NULL);
1123 	    tbcatline(buf, ".");
1124 	    tbcat_si(buf, "TP/toolup action: ");
1125 	    uu_table_row_desc(buf, u, uu_tp_per_toolup, NULL, NULL);
1126 	    tbcatline(buf, ".");
1127 	    /* (should put these with type beING built...) */
1128 	    tbcat_si(buf, "TP to build: ");
1129 	    uu_table_row_desc(buf, u, uu_tp_to_build, NULL, NULL);
1130 	    tbcatline(buf, ".");
1131 	    tbcat_si(buf, "TP max: ");
1132 	    uu_table_row_desc(buf, u, uu_tp_max, NULL, NULL);
1133 	    tbcatline(buf, ".");
1134         }
1135     }
1136     if ((type_max_acp(u) > 0
1137 	 && (could_attack_any(u)
1138 	     || could_fire_at_any(u) > 0
1139 	     || could_capture_any(u) > 0
1140 	    ))
1141 	|| may_detonate(u)
1142 	|| uu_table_row_not_default(u, uu_protection, 100)
1143 	|| uu_table_row_not_default(u, uu_retreat_chance, 0)
1144 	|| uu_table_row_not_default(u, uu_acp_retreat, 0)
1145 	|| u_wrecked_type(u) != NONUTYPE
1146 	) {
1147 	tbprintf(buf, "%sCombat:%s", help_newline(), help_newline());
1148 	if (could_attack_any(u)) {
1149 	    tbcat(buf, "Can attack (ACP ");
1150 	    uu_table_row_desc(buf, u, uu_acp_to_attack, NULL, "vs");
1151 	    tbcatline(buf, ").");
1152 	    if (uu_table_row_not_default(u, uu_attack_range, 1)) {
1153 		tbcat(buf, "Attack range is ");
1154 		uu_table_row_desc(buf, u, uu_attack_range, NULL, "vs");
1155 		tbcatline(buf, ".");
1156 		tbcat(buf, "Attack range min is ");
1157 		uu_table_row_desc(buf, u, uu_attack_range_min, NULL, "vs");
1158 		tbcatline(buf, ".");
1159 	    }
1160 	}
1161    	if (u_acp_to_fire(u) > 0) {
1162 	    tbprintf(buf, "Can fire (%d ACP), at ranges", u_acp_to_fire(u));
1163 	    if (u_range_min(u) > 0) {
1164 		tbprintf(buf, " from %d", u_range_min(u));
1165 	    }
1166 	    tbprintf(buf, " up to %d", u_range(u));
1167 	    tbcatline(buf, ".");
1168 	}
1169 	tbcat(buf, "Hit chances are ");
1170 	uu_table_row_desc(buf, u, uu_hit, tb_percent_desc, "vs");
1171 	tbcatline(buf, ".");
1172    	if (u_acp_to_fire(u) > 0) {
1173 	    if (uu_table_row_not_default(u, uu_fire_hit, -1)) {
1174 		tbcat(buf, "Hit chances if firing are ");
1175 		uu_table_row_desc(buf, u, uu_fire_hit, tb_percent_desc, "vs");
1176 		tbcatline(buf, ".");
1177 	    } else {
1178 		tbcatline(buf,
1179 		          "Hit chances if firing same as for regular combat.");
1180 	    }
1181    	}
1182 	if (ut_table_row_not_default(u, ut_attack_terrain_effect, 100)) {
1183 	    tbcat(buf, "Effect of attacker's terrain is ");
1184 	    ut_table_row_desc(buf, u, ut_attack_terrain_effect,
1185 			      tb_mult_desc, "in");
1186 	    tbcatline(buf, ".");
1187 	}
1188 	tbcat(buf, "Damage is ");
1189 	uu_table_row_desc(buf, u, uu_damage, tb_dice_desc, "vs");
1190 	tbcatline(buf, ".");
1191 	if (uu_table_row_not_default(u, uu_tp_damage, 0)) {
1192 	    tbcat(buf, "Tooling damage is ");
1193 	    uu_table_row_desc(buf, u, uu_tp_damage, tb_dice_desc, NULL);
1194 	    tbcatline(buf, ".");
1195 	}
1196    	if (u_acp_to_fire(u) > 0) {
1197 	    if (uu_table_row_not_default(u, uu_fire_damage, -1)) {
1198 		tbcat(buf, "Damage if firing is ");
1199 		uu_table_row_desc(buf, u, uu_fire_damage, tb_dice_desc, "vs");
1200 		tbcatline(buf, ".");
1201 	    } else {
1202 		tbcatline(buf, "Damages if firing same as for regular combat.");
1203 	    }
1204 	}
1205 	if (uu_table_row_not_default(u, uu_acp_to_defend, 1)) {
1206 	    tbcat(buf, "If attacked, ACP to defend is ");
1207 	    uu_table_row_desc(buf, u, uu_acp_to_defend, NULL, "vs");
1208 	    tbcatline(buf, ".");
1209 	}
1210 	if (ut_table_row_not_default(u, ut_defend_terrain_effect, 100)) {
1211 	    tbcat(buf, "If attacked, effect of own terrain is ");
1212 	    ut_table_row_desc(buf, u, ut_defend_terrain_effect,
1213 			      tb_mult_desc, "in");
1214 	    tbcatline(buf, ".");
1215 	}
1216 	if (um_table_row_not_default(u, um_hit_by, 0)) {
1217 	    tbcat(buf, "Ammo needed to hit unit: ");
1218 	    um_table_row_desc(buf, u, um_hit_by, NULL);
1219 	    tbcatline(buf, ".");
1220 	}
1221 	if (could_capture_any(u) > 0) {
1222 	    tbcat(buf, "Can capture (ACP ");
1223 	    uu_table_row_desc(buf, u, uu_acp_to_capture, NULL, "vs");
1224 	    tbcatline(buf, ").");
1225 	    tbcat(buf, "Chance to capture: ");
1226 	    uu_table_row_desc(buf, u, uu_capture, tb_percent_desc, "vs");
1227 	    tbcatline(buf, ".");
1228 	    if (uu_table_row_not_default(u, uu_indep_capture, -1)) {
1229 		tbcat(buf, "Chance to capture indep: ");
1230 		uu_table_row_desc(buf, u, uu_indep_capture,
1231 				  tb_percent_desc, "vs");
1232 		tbcatline(buf, ".");
1233 	    }
1234 	}
1235 	if (may_detonate(u)) {
1236 	    /* Display all the different ways that a unit might detonate. */
1237 	    if (u_acp_to_detonate(u) > 0) {
1238 		tbprintf(buf, "Can detonate self (%d ACP).%s",
1239 			 u_acp_to_detonate(u), help_newline());
1240 	    }
1241 	    if (u_detonate_on_death(u)) {
1242 		tbprintf(buf,
1243 			 "%d%% chance to detonate if destroyed in combat.%s",
1244 			 u_detonate_on_death(u), help_newline());
1245 	    }
1246 	    if (uu_table_row_not_default(u, uu_detonate_on_hit, 0)) {
1247 		tbcat(buf, "Chance to detonate upon being hit: ");
1248 		uu_table_row_desc(buf, u, uu_detonate_on_hit, tb_percent_desc,
1249 				  NULL);
1250 		tbcatline(buf, ".");
1251 	    }
1252 	    if (uu_table_row_not_default(u, uu_detonate_on_capture, 0)) {
1253 		tbcat(buf, "Chance to detonate upon capture: ");
1254 		uu_table_row_desc(buf, u, uu_detonate_on_capture,
1255 				  tb_percent_desc, NULL);
1256 		tbcatline(buf, ".");
1257 	    }
1258 	    if (uu_table_row_not_default(u, uu_detonate_approach_range, -1)) {
1259 		tbcat(buf, "Will detonate upon approach within range: ");
1260 		uu_table_row_desc(buf, u, uu_detonate_approach_range,
1261 				  NULL, NULL);
1262 		tbcatline(buf, ".");
1263 	    }
1264 	    if (ut_table_row_not_default(u, ut_detonation_accident, 0)) {
1265 		tbcat(buf, "Chance of accidental detonation: ");
1266 		ut_table_row_desc(buf, u, ut_detonation_accident,
1267 				  tb_percent_desc, "in");
1268 		tbcatline(buf, ".");
1269 	    }
1270 	    tbcat(buf, "Detonation damage at ground zero is ");
1271 	    uu_table_row_desc(buf, u, uu_detonation_damage_at, tb_dice_desc,
1272 			      NULL);
1273 	    tbcatline(buf, ".");
1274 	    /* (should only display if effect range > 0) */
1275 	    tbcat(buf, "Detonation damage to adjacent units is ");
1276 	    uu_table_row_desc(buf, u, uu_detonation_damage_adj, tb_dice_desc,
1277 			      NULL);
1278 	    tbcatline(buf, ".");
1279 	    tbcat(buf, "Range of detonation effect on units is ");
1280 	    uu_table_row_desc(buf, u, uu_detonation_range, NULL, NULL);
1281 	    tbcatline(buf, ".");
1282 	    /* Damage decreases as inverse square of distance. */
1283 	    if (ut_table_row_not_default(u, ut_detonation_damage, 0)) {
1284 		tbcat(buf, "Chance of detonation damage to terrain is ");
1285 		ut_table_row_desc(buf, u, ut_detonation_damage, NULL, NULL);
1286 		tbcatline(buf, ".");
1287 		tbcat(buf, "Range of detonation effect on terrain is ");
1288 		ut_table_row_desc(buf, u, ut_detonation_range, NULL, NULL);
1289 		tbcatline(buf, ".");
1290 	    }
1291 	    if (u_hp_per_detonation(u) < u_hp_max(u)) {
1292 		tbprintf(buf, "Loses %d HP per detonation.%s",
1293 			 u_hp_per_detonation(u), help_newline());
1294 	    } else {
1295 		tbcatline(buf, "Always destroyed by detonation.");
1296 	    }
1297 	}
1298 	if (uu_table_row_not_default(u, uu_hp_min, 0)) {
1299 	    tbcat(buf, "Combat never reduces defender's HP below ");
1300 	    uu_table_row_desc(buf, u, uu_hp_min, NULL, "vs");
1301 	    tbcatline(buf, ".");
1302 	}
1303 	if (uu_table_row_not_default(u, uu_protection, 100)) {
1304 	    tbcat(buf, "Protection of occupants/transport is ");
1305 	    uu_table_row_desc(buf, u, uu_protection, tb_mult_desc, NULL);
1306 	    tbcatline(buf, ".");
1307 	}
1308 	if (uu_table_row_not_default(u, uu_occ_combat, 100)) {
1309 	    tbcat(buf, "Combat effectiveness as occupant is ");
1310 	    uu_table_row_desc(buf, u, uu_occ_combat, tb_percent_desc, "in");
1311 	    tbcatline(buf, ".");
1312 	}
1313 	if (uu_table_row_not_default(u, uu_retreat_chance, 0)) {
1314 	    tbcat(buf, "Chance to retreat from combat is ");
1315 	    uu_table_row_desc(buf, u, uu_retreat_chance, tb_percent_desc,
1316 			      "vs");
1317 	    tbcatline(buf, ".");
1318 	}
1319 	if (uu_table_row_not_default(u, uu_acp_retreat, 0)) {
1320 	    tbcat(buf, "Extra ACP for retreating is ");
1321 	    uu_table_row_desc(buf, u, uu_acp_retreat, NULL, "vs");
1322 	    tbcatline(buf, ".");
1323 	}
1324 	if (u_wrecked_type(u) != NONUTYPE) {
1325 	    tbprintf(buf, "Becomes %s when destroyed.%s",
1326 		     u_type_name(u_wrecked_type(u)), help_newline());
1327 	}
1328     }
1329     if (type_max_acp(u) > 0
1330         && (u_acp_to_change_side(u) > 0
1331             || u_acp_to_disband(u) > 0
1332             || u_acp_to_transfer_part(u) > 0
1333 	    || uu_table_row_not_default(u, uu_acp_to_repair, 0)
1334 	    || (uu_table_row_not_default(u, uu_change_type_to, 0)
1335 		&& uu_table_row_not_default(u, uu_acp_to_change_type, 0))
1336 	    || ut_table_row_not_default(u, ut_acp_to_add_terrain, 0)
1337 	    || ut_table_row_not_default(u, ut_acp_to_remove_terrain, 0)
1338             )) {
1339 	tbprintf(buf, "%sOther Actions:%s", help_newline(), help_newline());
1340 	if (u_acp_to_change_side(u) > 0) {
1341 	    tbprintf(buf, "Can be given to another side (%d ACP).%s",
1342 		    u_acp_to_change_side(u), help_newline());
1343 	}
1344 	if (u_acp_to_disband(u) > 0) {
1345 	    tbprintf(buf, "Can be disbanded (%d ACP)", u_acp_to_disband(u));
1346 	    if (u_hp_per_disband(u) < u_hp_max(u)) {
1347 	    	tbprintf(buf, ", losing %d HP per action",
1348 			 u_hp_per_disband(u));
1349 	    }
1350 	    tbcatline(buf, ".");
1351 	    if (um_table_row_not_default(u, um_supply_per_disband, 0)) {
1352 		tbprintf(buf, "Supply yield per disband action: ");
1353 		um_table_row_desc(buf, u, um_supply_per_disband, NULL);
1354 		tbcatline(buf, ".");
1355 	    }
1356 	    if (um_table_row_not_default(u, um_recycleable, 0)) {
1357 		tbprintf(buf, "Additional material when unit is gone: ");
1358 		um_table_row_desc(buf, u, um_recycleable, NULL);
1359 		tbcatline(buf, ".");
1360 	    }
1361 	}
1362 	if (u_acp_to_transfer_part(u) > 0) {
1363 	    tbprintf(buf, "Can transfer parts (%d ACP).%s",
1364 		    u_acp_to_transfer_part(u), help_newline());
1365 	}
1366 	if (uu_table_row_not_default(u, uu_acp_to_repair, 0)) {
1367 	    tbcat(buf, "ACP for explicit repair is ");
1368 	    uu_table_row_desc(buf, u, uu_acp_to_repair, NULL, NULL);
1369 	    tbcatline(buf, ".");
1370 	    tbcat(buf, "Explicit repair performance is ");
1371 	    uu_table_row_desc(buf, u, uu_hp_per_repair, NULL, NULL);
1372 	    tbcatline(buf, ".");
1373 	}
1374 	/* (More work still needs to be done, so that we can accomodate
1375 	    'u_auto_upgrade_to' and ACP-less 'change-type' possibilities.) */
1376 	if (uu_table_row_not_default(u, uu_change_type_to, FALSE)) {
1377 	    if (uu_table_row_not_default(u, uu_acp_to_change_type, 0)) {
1378 		tbcat(buf, "ACP to change type is ");
1379 		uu_table_row_desc(buf, u, uu_acp_to_change_type, NULL, NULL);
1380 		tbcatline(buf, ".");
1381 		if (um_table_row_not_default(u, um_to_change_type, 0)) {
1382 		    tbcat(buf, "Material to change type is ");
1383 		    um_table_row_desc(buf, u, um_to_change_type, NULL);
1384 		    tbcatline(buf, ".");
1385 		}
1386 		if (ua_table_row_not_default(u, ua_to_change_type, FALSE)) {
1387 		    tbcat(buf, "Advance to change type is ");
1388 		    ua_table_row_desc(buf, u, ua_to_change_type, tb_bool_desc);
1389 		    tbcatline(buf, ".");
1390 		}
1391 		if (uu_table_row_not_default(u, uu_size_to_change_type, 0)) {
1392 		    tbcat(buf, "Size to change type is ");
1393 		    uu_table_row_desc(buf, u, uu_size_to_change_type, NULL,
1394 				      NULL);
1395 		    tbcatline(buf, ".");
1396 		}
1397 		if (uu_table_row_not_default(u, uu_cxp_to_change_type, 0)) {
1398 		    tbcat(buf, "Combat experience to change type is ");
1399 		    uu_table_row_desc(buf, u, uu_cxp_to_change_type, NULL,
1400 				      NULL);
1401 		    tbcatline(buf, ".");
1402 		}
1403 	    }
1404 	}
1405 	if (ut_table_row_not_default(u, ut_acp_to_add_terrain, 0)) {
1406 	    tbcat(buf, "ACP to add/change terrain is ");
1407 	    ut_table_row_desc(buf, u, ut_acp_to_add_terrain, NULL, NULL);
1408 	    tbcatline(buf, ".");
1409 	    if (ut_table_row_not_default(u, ut_alter_range, 0)) {
1410 		tbcat_si(buf, "Can alter up to range ");
1411 		ut_table_row_desc(buf, u, ut_alter_range, NULL, NULL);
1412 		tbcatline(buf, ".");
1413 	    }
1414 	}
1415 	if (ut_table_row_not_default(u, ut_acp_to_remove_terrain, 0)) {
1416 	    tbcat(buf, "ACP to remove terrain is ");
1417 	    ut_table_row_desc(buf, u, ut_acp_to_remove_terrain, NULL, NULL);
1418 	    tbcatline(buf, ".");
1419 	    if (ut_table_row_not_default(u, ut_alter_range, 0)) {
1420 		tbcat_si(buf, "Can alter up to range ");
1421 		ut_table_row_desc(buf, u, ut_alter_range, NULL, NULL);
1422 		tbcatline(buf, ".");
1423 	    }
1424 	}
1425     }
1426     if (!g_see_all()) {
1427     	tbprintf(buf, "%sVision:%s", help_newline(), help_newline());
1428 	tbprintf(buf, "%d%% chance to be seen at outset of game.%s",
1429 		 u_already_seen(u), help_newline());
1430 	tbprintf(buf,
1431 		 "%d%% chance to be seen at outset of game if independent.%s",
1432 		 u_already_seen_indep(u), help_newline());
1433 	if (u_see_always(u))
1434 	  tbcatline(buf, "Always seen if terrain has been seen.");
1435 	else
1436 	  tbcatline(buf, "Not always seen even if terrain has been seen.");
1437 	/* (should only say if can be an occupant) */
1438 	if (uu_table_row_not_default(u, uu_occ_vision, 100)) {
1439 	    tbcat(buf, "Vision effect when occupying: ");
1440 	    uu_table_row_desc(buf, u, uu_occ_vision, NULL, "in");
1441 	    tbcatline(buf, ".");
1442 	}
1443 	/* (should only say if unit can have occupants) */
1444 	if (u_see_occupants(u))
1445 	  tbcatline(buf, "Occupants seen if unit has been seen.");
1446 	else
1447 	  tbcatline(buf, "Occupants not seen even if unit has been seen.");
1448 	switch (u_vision_range(u)) {
1449 	  case -1:
1450 	    tbcatline(buf, "Can never see other units.");
1451 	    break;
1452 	  case 0:
1453 	    tbcatline(buf, "Can see other units at own location.");
1454 	    break;
1455 	  case 1:
1456 	    /* Default range, no need to say anything. */
1457 	    break;
1458 	  default:
1459 	    tbprintf(buf,
1460 		     "Can see units up to %d cells away.%s", u_vision_range(u),
1461 		     help_newline());
1462 	    break;
1463 	}
1464 	if (u_vision_range(u) > 0 && !u_can_see_behind(u)) {
1465 	    tbcat(buf, "Vision is line-of-sight");
1466 	    if (u_can_see_behind(u) > 0)
1467 	      tbprintf(buf, ", seeing behind obstacles.");
1468 	    tbcatline(buf, ".");
1469 	    if (ut_table_row_not_default(u, ut_eye_height, 0)) {
1470 		tbcat(buf, "Effective eye height is ");
1471 		ut_table_row_desc(buf, u, ut_eye_height, NULL, "in");
1472 		tbcatline(buf, ".");
1473 	    }
1474 	}
1475 	if (ut_table_row_not_default(u, ut_body_height, 0)) {
1476 	    tbcat(buf, "Effective body height is ");
1477 	    ut_table_row_desc(buf, u, ut_body_height, NULL, "in");
1478 	    tbcatline(buf, ".");
1479 	}
1480 	if (ut_table_row_not_default(u, ut_weapon_height, 0)) {
1481 	    tbcat(buf, "Effective weapon height is ");
1482 	    ut_table_row_desc(buf, u, ut_weapon_height, NULL, "in");
1483 	    tbcatline(buf, ".");
1484 	}
1485 	if (u_vision_range(u) >= 0
1486 	    && uu_table_row_not_default(u, uu_see_at, 100)) {
1487 	    tbcat(buf, "Chance to see if in same cell is ");
1488 	    uu_table_row_desc(buf, u, uu_see_at, tb_percent_desc, NULL);
1489 	    tbcatline(buf, ".");
1490 	}
1491 	if (u_vision_range(u) >= 1
1492 	    && uu_table_row_not_default(u, uu_see_adj, 100)) {
1493 	    tbcat(buf, "Chance to see if adjacent is ");
1494 	    uu_table_row_desc(buf, u, uu_see_adj, tb_percent_desc, NULL);
1495 	    tbcatline(buf, ".");
1496 	}
1497 	if (u_vision_range(u) >= 2
1498 	    && uu_table_row_not_default(u, uu_see, 100)) {
1499 	    tbcat(buf, "Chance to see in general is ");
1500 	    uu_table_row_desc(buf, u, uu_see, tb_percent_desc, NULL);
1501 	    tbcatline(buf, ".");
1502 	}
1503 	if (u_see_terrain_captured(u) > 0)
1504 	  tbprintf(buf,
1505 "%d%% chance for enemy to see your terrain view if this type captured.%s",
1506 		   u_see_terrain_captured(u), help_newline());
1507     }
1508     if (ut_table_row_not_default(u, ut_accident_hit, 0)
1509 	|| ut_table_row_not_default(u, ut_accident_vanish, 0)) {
1510 	tbprintf(buf, "%sAccidents:%s", help_newline(), help_newline());
1511 	if (ut_table_row_not_default(u, ut_accident_hit, 0)) {
1512 	    tbcat(buf, "Chance to be damaged in an accident: ");
1513 	    ut_table_row_desc(buf, u, ut_accident_hit, tb_percent_100th_desc,
1514 			      "in");
1515 	    tbcatline(buf, ".");
1516 	    tbcat(buf, "Amount of damage: ");
1517 	    ut_table_row_desc(buf, u, ut_accident_damage, tb_dice_desc, "in");
1518 	    tbcatline(buf, ".");
1519 	}
1520 	if (ut_table_row_not_default(u, ut_accident_vanish, 0)) {
1521 	    tbcat(buf, "Chance to vanish in an accident: ");
1522 	    ut_table_row_desc(buf, u, ut_accident_vanish,
1523 			      tb_percent_100th_desc, "in");
1524 	    tbcatline(buf, ".");
1525 	}
1526     }
1527     if (ut_table_row_not_default(u, ut_attrition, 0)) {
1528 	tbprintf(buf, "%sAttrition in HP:%s", help_newline(), help_newline());
1529 	ut_table_row_desc(buf, u, ut_attrition, tb_probfraction_desc, "in");
1530 	tbcatline(buf, ".");
1531     }
1532     /* Don't bother with this if economy code is not running. */
1533     if (nummtypes > 0 && g_economy()) {
1534 	tbprintf(buf, "%sMaterial Handling:%s", help_newline(), help_newline());
1535 	for_all_material_types(m) {
1536 	    usesm = FALSE;
1537 	    tbprintf(buf, "%s%s", help_indent(2), m_type_name(m));
1538 	    if (um_base_production(u, m) > 0) {
1539 		tbprintf(buf, ", %d basic production",
1540 			 um_base_production(u, m));
1541 		usesm = TRUE;
1542 	    }
1543 	    if (um_occ_production(u, m) >= 0) {
1544 		tbprintf(buf, ", %d basic production if occupant",
1545 			 um_occ_production(u, m));
1546 		usesm = TRUE;
1547 	    }
1548 	    if (um_acp_to_extract(u, m) > 0) {
1549 		tbprintf(buf, ", %d ACP to extract", um_acp_to_extract(u, m));
1550 		usesm = TRUE;
1551 	    }
1552 	    if (um_storage_x(u, m) > 0) {
1553 		tbprintf(buf, ", %d storage", um_storage_x(u, m));
1554 		if (um_initial(u, m) > 0) {
1555 		    tbprintf(buf, " (%d at start of game)",
1556 			     min(um_initial(u, m), um_storage_x(u, m)));
1557 		}
1558 		usesm = TRUE;
1559 	    }
1560 	    if (um_base_consumption(u, m) > 0) {
1561 		tbprintf(buf, ", %d basic consumption",
1562 			 um_base_consumption(u, m));
1563 		if (um_consumption_as_occupant(u, m) != 100) {
1564 		    tbprintf(buf, ", times %d%% if occupant",
1565 			     um_consumption_as_occupant(u, m));
1566 		}
1567 		usesm = TRUE;
1568 	    }
1569 	    if (um_to_act(u, m) != 0) {
1570 		tbprintf(buf, ", needs %d to act at all",
1571 			 um_to_act(u, m));
1572 		usesm = TRUE;
1573 	    }
1574 	    if (um_to_move(u, m) != 0) {
1575 		tbprintf(buf, ", needs %d to move",
1576 			 um_to_move(u, m));
1577 		usesm = TRUE;
1578 	    }
1579 	    if (um_consumption_per_move(u, m) != 0) {
1580 		tbprintf(buf, ", %d consumed per move",
1581 			 um_consumption_per_move(u, m));
1582 		usesm = TRUE;
1583 	    }
1584 	    if (um_to_attack(u, m) != 0) {
1585 		tbprintf(buf, ", needs %d (weapons or equipment) to attack",
1586 			 um_to_attack(u, m));
1587 		usesm = TRUE;
1588 	    }
1589 	    if (um_consumption_per_attack(u, m) != 0) {
1590 		tbprintf(buf, ", %d (ammo) consumed per attack",
1591 			 um_consumption_per_attack(u, m));
1592 		usesm = TRUE;
1593 	    }
1594 	    if (u_acp_to_fire(u) > 0 && um_to_fire(u, m) != 0) {
1595 		tbprintf(buf, ", needs %d (weapons or equipment) to fire",
1596 			 um_to_fire(u, m));
1597 		usesm = TRUE;
1598 	    }
1599 	    if (u_acp_to_fire(u) > 0 && um_consumption_per_fire(u, m) != 0) {
1600 		tbprintf(buf, ", %d (ammo) consumed when firing",
1601 			 um_consumption_per_fire(u, m));
1602 		usesm = TRUE;
1603 	    }
1604 	    if (um_to_create(u, m) != 0) {
1605 		tbprintf(buf, ", builder needs %d to create",
1606 			 um_to_create(u, m));
1607 		usesm = TRUE;
1608 	    }
1609 	    if (um_consumption_on_creation(u, m) != 0) {
1610 		tbprintf(buf, ", %d used in creation",
1611 			 um_consumption_on_creation(u, m));
1612 		usesm = TRUE;
1613 	    }
1614 	    if (um_to_build(u, m) != 0) {
1615 		tbprintf(buf, ", builder needs %d to build anything",
1616 			 um_to_build(u, m));
1617 		usesm = TRUE;
1618 	    }
1619 	    if (um_consumption_per_build(u, m) != 0) {
1620 		tbprintf(buf, ", %d used to build anything",
1621 			 um_consumption_per_build(u, m));
1622 		usesm = TRUE;
1623 	    }
1624 	    if (um_consumption_per_built(u, m) != 0) {
1625 		tbprintf(buf, ", %d used to perform a build upon",
1626 			 um_consumption_per_built(u, m));
1627 		usesm = TRUE;
1628 	    }
1629 	    if (um_consumption_per_cp(u, m) != 0) {
1630 		tbprintf(buf, ", %d used to add 1 CP to",
1631 			 um_consumption_per_cp(u, m));
1632 		usesm = TRUE;
1633 	    }
1634 	    if (um_to_repair(u, m) != 0) {
1635 		tbprintf(buf, ", repairer needs %d to repair",
1636 			 um_to_repair(u, m));
1637 		usesm = TRUE;
1638 	    }
1639 	    if (um_consumption_per_repair(u, m) != 0) {
1640 		tbprintf(buf, ", %d used to restore 1 HP",
1641 			 um_consumption_per_repair(u, m));
1642 		usesm = TRUE;
1643 	    }
1644 	    if (usesm) {
1645 		if (um_inlength(u, m) > 0) {
1646 		    tbprintf(buf, ", receive from up to %d cells away",
1647 			     um_inlength(u, m));
1648 		}
1649 		if (um_outlength(u, m) > 0) {
1650 		    tbprintf(buf, ", send up to %d cells away",
1651 			     um_outlength(u, m));
1652 		}
1653 	    } else {
1654 		tbcat(buf, " (none)");
1655 	    }
1656 	    tbcatline(buf, "");
1657 	}
1658 	/* Productivity adjustment due to terrain applies to all
1659 	   material types equally, but only display if unit
1660 	   produces. */
1661 	if (um_table_row_not_default(u, um_base_production, 0)) {
1662 	    if (ut_table_row_not_default(u, ut_productivity, 100)) {
1663 		tbcat(buf, "Productivity adjustment for terrain is ");
1664 		ut_table_row_desc(buf, u, ut_productivity, tb_mult_desc, "in");
1665 		tbcatline(buf, ".");
1666 	    }
1667 	    if (ut_table_row_not_default(u, ut_productivity_adj, 0)) {
1668 		tbcat(buf, "Productivity adjustment for adjacent terrain is ");
1669 		ut_table_row_desc(buf, u, ut_productivity_adj, tb_mult_desc,
1670 				  "in");
1671 		tbcatline(buf, ".");
1672 	    }
1673 	    if (um_table_row_not_default(u, um_productivity_min, 0)) {
1674 		tbcat(buf, "Minimum net adjustment is ");
1675 		um_table_row_desc(buf, u, um_productivity_min, tb_mult_desc);
1676 		tbcatline(buf, ".");
1677 	    }
1678 	    if (um_table_row_not_default(u, um_productivity_max, TABHI)) {
1679 		tbcat(buf, "Maximum net adjustment is ");
1680 		um_table_row_desc(buf, u, um_productivity_max, tb_mult_desc);
1681 		tbcatline(buf, ".");
1682 	    }
1683 	}
1684     }
1685     if (numatypes > 0) {
1686 	/* Now also prints "None" if no advances are required. */
1687 	int	found = FALSE;
1688 
1689 	tbprintf(buf, "%sRequired advances to build: ", help_newline());
1690 	for_all_advance_types(a) {
1691 	    if (ua_needed_to_build(u, a) > 0) {
1692 		if (found)
1693 			tbprintf(buf, ", ");
1694 		tbprintf(buf, "%s", a_type_name(a));
1695 		found = TRUE;
1696 	    }
1697 	}
1698 	if (found)
1699 	  tbcatline(buf, ".");
1700 	else
1701 	  tbcatline(buf, "None.");
1702     }
1703     tbcatline(buf, "");
1704     /* (should display weather interaction here) */
1705     if (uu_table_row_not_default(u, uu_auto_repair, 0)) {
1706 	tbcat(buf, "Can auto-repair lost HP of other units: ");
1707 	uu_table_row_desc(buf, u, uu_auto_repair, tb_probfraction_desc, NULL);
1708 	tbcatline(buf, ".");
1709 	if (uu_table_row_not_default(u, uu_auto_repair_range, 0)) {
1710 	    tbcat(buf, "Range of auto-repair: ");
1711 	    uu_table_row_desc(buf, u, uu_auto_repair_range, NULL, NULL);
1712 	    tbcatline(buf, ".");
1713 	}
1714     }
1715     if (u_spy_chance(u) > 0 /* and random event in use */) {
1716 	tbprintf(buf, "%d%% chance to spy, on units up to %d away.%s",
1717 		 u_spy_chance(u), u_spy_range(u), help_newline());
1718     }
1719     if (u_revolt(u) > 0 /* and random event in use */) {
1720 	tb_fraction_desc(buf, u_revolt(u));
1721 	tbcatline(buf, "% chance of revolt.");
1722     }
1723     if (u_lost_wreck(u) > 0
1724 	|| u_lost_vanish(u) > 0
1725 	|| u_lost_revolt(u) > 0
1726 	|| uu_table_row_not_default(u, uu_lost_surrender, 0)) {
1727 	tbprintf(buf, "%sFate if side loses:", help_newline());
1728 	if (u_lost_wreck(u) > 0) {
1729 	    tbcat(buf, "  ");
1730 	    tb_percent_100th_desc(buf, u_lost_wreck(u));
1731 	    tbcat(buf, " chance to wreck");
1732 	}
1733 	if (u_lost_wreck(u) < 10000 && u_lost_vanish(u) > 0) {
1734 	    tbcat(buf, "  ");
1735 	    tb_percent_100th_desc(buf, u_lost_vanish(u));
1736 	    tbcat(buf, " chance to vanish");
1737 	}
1738 	if (u_lost_wreck(u) < 10000
1739 	    && u_lost_vanish(u) < 10000
1740 	    && u_lost_revolt(u) > 0) {
1741 	    tbcat(buf, "  ");
1742 	    tb_percent_100th_desc(buf, u_lost_revolt(u));
1743 	    tbcat(buf, " chance to revolt");
1744 	}
1745 	if (u_lost_wreck(u) < 10000
1746 	    && u_lost_vanish(u) < 10000
1747 	    && u_lost_revolt(u) < 10000
1748 	    && uu_table_row_not_default(u, uu_lost_surrender, 0)) {
1749 	    tbcat(buf, " chance to surrender to nearby unit, ");
1750 	    uu_table_row_desc(buf, u, uu_lost_surrender,
1751 			      tb_percent_100th_desc, "to");
1752 	}
1753 	if (u_lost_wreck(u) == 0
1754 	    && u_lost_vanish(u) == 0
1755 	    && u_lost_revolt(u) == 0
1756 	    && !uu_table_row_not_default(u, uu_lost_surrender, 0))
1757 	  tbcat(buf, " survival");
1758 	tbprintf(buf, ".%s%s", help_newline(), help_newline());
1759     }
1760     if (u_type_in_game_max(u) >= 0) {
1761 	tbprintf(buf, "At most %d allowed in a game.%s", u_type_in_game_max(u),
1762 		 help_newline());
1763     }
1764 #ifdef DESIGNERS
1765     if (numdesigners > 0) {
1766 	tbcat(buf, "FOR DESIGNERS:\n");
1767 	tbprintf(buf, "Internal name is \"%s\".%s", u_internal_name(u),
1768 		 help_newline());
1769 	tbprintf(buf, "Short name is \"%s\".%s", u_short_name(u),
1770 		 help_newline());
1771 	if (u_assign_number(u))
1772 	  tbcatline(buf, "New units get assigned a number.");
1773 	tbcatline(buf, "");
1774     }
1775 #endif /* DESIGNERS */
1776     describe_utype_ai_attributes(u, key, buf);
1777 }
1778 
1779 /* Describe the unit type's ability to move. */
1780 
1781 static void
describe_utype_movement(int u,char * key,TextBuffer * buf)1782 describe_utype_movement(int u, char *key, TextBuffer *buf)
1783 {
1784     int speedvaries = FALSE;
1785 
1786     tbcatline(buf,
1787 	      "Movement, Movement Points (MP), and Speeds (MP/ACP Ratios):");
1788     if (type_max_speed(u) <= 0) {
1789 	tbprintf(buf, "%sIs always immobile.%s", help_indent(2),
1790 		 help_newline());
1791     }
1792     else {
1793 	if (u_acp_to_move(u) > 0) {
1794 	    tbprintf(buf, "%sUses a minimum of %d ACP per move.%s",
1795 		     help_indent(2), u_acp_to_move(u), help_newline());
1796 	} else {
1797 	    tbprintf(buf, "%sCannot move by its own action.%s",
1798 		     help_indent(2), help_newline());
1799 	}
1800 	if (u_speed(u) != uprop_i_default(u_speed)) {
1801 	    tbprintf(buf, "%sUnadjusted speed is ", help_indent(2));
1802 	    tb_percent_desc(buf, u_speed(u));
1803 	    tbcatline(buf, ".");
1804 	}
1805 	if (uu_table_row_not_default(u, uu_occ_adds_speed,
1806 				     table_default(uu_occ_adds_speed))) {
1807 	    tbprintf(buf, "%sOccupant adds speed: ", help_indent(2));
1808 	    uu_table_row_desc(buf, u, uu_occ_adds_speed, tb_percent_desc, NULL);
1809 	    tbcatline(buf, ".");
1810 	    speedvaries = TRUE;
1811 	}
1812 	if (uu_table_row_not_default(u, uu_occ_multiplies_speed,
1813 				     table_default(uu_occ_multiplies_speed))) {
1814 	    tbprintf(buf, "%sOccupant multiplies speed: ", help_indent(2));
1815 	    uu_table_row_desc(buf, u, uu_occ_multiplies_speed, tb_mult_desc,
1816 			      NULL);
1817 	    tbcatline(buf, ".");
1818 	    speedvaries = TRUE;
1819 	}
1820 	if (u_speed_wind_effect(u) != uprop_l_default(u_speed_wind_effect)) {
1821 	    /* (should add mech to describe in detail) */
1822 	    tbprintf(buf, "%sWind affects speed.%s", help_indent(2),
1823 		     help_newline());
1824 	    speedvaries = TRUE;
1825 	}
1826 	if (u_speed_damage_effect(u) !=
1827 	    uprop_l_default(u_speed_damage_effect)) {
1828 	    /* (should add mech to describe in detail) */
1829 	    tbprintf(buf, "%sDamage affects speed.%s", help_indent(2),
1830 		     help_newline());
1831 	    speedvaries = TRUE;
1832 	}
1833 	/* (should only list variation limits if actually needed to clip) */
1834 	if (speedvaries) {
1835 	    tbprintf(buf, "%sSpeed variation limited to between ",
1836 		     help_indent(2));
1837 	    tb_percent_desc(buf, u_speed_min(u));
1838 	    tbcat(buf, " and ");
1839 	    tb_percent_desc(buf, u_speed_max(u));
1840 	    tbcatline(buf, ".");
1841 	}
1842 	tbprintf(buf, "%sNeeds MP to enter terrain: ", help_indent(2));
1843 	ut_table_row_desc(buf, u, ut_mp_to_enter, NULL, "into");
1844 	tbcatline(buf, ".");
1845 	if (ut_table_row_not_default(u, ut_mp_to_leave,
1846 				     table_default(ut_mp_to_leave))) {
1847 	    tbprintf(buf, "%sNeeds MP to leave terrain: ", help_indent(2));
1848 	    ut_table_row_desc(buf, u, ut_mp_to_leave, NULL, "in");
1849 	    tbcatline(buf, ".");
1850 	}
1851 	if (ut_table_row_not_default(u, ut_mp_to_traverse,
1852 				     table_default(ut_mp_to_traverse))) {
1853 	    tbprintf(buf, "%sNeeds MP to traverse terrain: ", help_indent(2));
1854 	    ut_table_row_desc(buf, u, ut_mp_to_traverse, NULL, "across");
1855 	    tbcatline(buf, ".");
1856 	}
1857 	if (any_mp_to_enter_unit(u)) {
1858 	    tbprintf(buf, "%sMP to enter unit: ", help_indent(2));
1859 	    uu_table_row_desc(buf, u, uu_mp_to_enter, NULL, "into");
1860 	    tbcatline(buf, ".");
1861 	}
1862 	if (any_mp_to_leave_unit(u)) {
1863 	    tbprintf(buf, "%sMP to leave unit: ", help_indent(2));
1864 	    uu_table_row_desc(buf, u, uu_mp_to_leave, NULL, "in");
1865 	    tbcatline(buf, ".");
1866 	}
1867 	if (any_enter_indep(u)) {
1868 	    tbprintf(buf, "%sCan enter independent units: ", help_indent(2));
1869 	    uu_table_row_desc(buf, u, uu_can_enter_indep, tb_bool_desc, NULL);
1870 	    tbcatline(buf, ".");
1871 	}
1872 	if (u_mp_to_leave_world(u) > uprop_i_default(u_mp_to_leave_world)) {
1873 	    tbprintf(buf, "%sNeeds %d MP to leave the world entirely.%s",
1874 		     help_indent(2), u_mp_to_leave_world(u), help_newline());
1875 	}
1876 	if (u_free_mp(u) > 0) {
1877 	    tbprintf(buf,
1878 		     "%sGets up to %d free MP if needed to finish a move.%s",
1879 		     help_indent(2), u_free_mp(u), help_newline());
1880 	}
1881     }
1882     tbcatline(buf, "");
1883 }
1884 
1885 /* Describe the unit type's ability to act. */
1886 
1887 static void
describe_utype_actions(int u,char * key,TextBuffer * buf)1888 describe_utype_actions(int u, char *key, TextBuffer *buf)
1889 {
1890     tbcatline(buf, "Actions and Action Points (ACP):");
1891     /* (Should enumerate possible actions here.) */
1892     /* ACP independent. */
1893     if (u_acp_independent(u)) {
1894 	tbprintf(buf, "%sActs independently of ACP restrictions.%s",
1895 		 help_indent(2), help_newline());
1896     }
1897     /* Actionless. */
1898     else if (type_max_acp(u) <= 0) {
1899 	tbprintf(buf, "%sDoes not act.%s", help_indent(2), help_newline());
1900     }
1901     /* ACP-dependent actions. */
1902     else {
1903 	/* Limits on total ACP. */
1904 	if (u_acp_min(u) != uprop_i_default(u_acp_min))
1905 	  tbprintf(buf, "%sCan act until %d ACP left.%s", help_indent(2),
1906 		   u_acp_min(u), help_newline());
1907 	if (u_acp_max(u) != uprop_i_default(u_acp_max)) {
1908 	  tbprintf(buf, "%sWill never exceed %d ACP.%s", help_indent(2),
1909 		   u_acp_max(u), help_newline());
1910 	}
1911 	/* Limits on new ACP per turn. */
1912 	if (u_acp_turn_min(u) != uprop_i_default(u_acp_turn_min))
1913 	  tbprintf(buf, "%sGuaranteed to gain at least %d new ACP per turn.%s",
1914 		   help_indent(2), u_acp_turn_min(u), help_newline());
1915 	if (u_acp_turn_max(u) != uprop_i_default(u_acp_turn_max))
1916 	  tbprintf(buf, "%sWill never exceed %d new ACP per turn.%s",
1917 		   help_indent(2), u_acp_turn_max(u), help_newline());
1918 	/* Basic new ACP per turn. */
1919 	if (u_acp(u) != uprop_i_default(u_acp))
1920 	  tbprintf(buf, "%sReceives basic allotment of %d new ACP per turn.%s",
1921 		   help_indent(2), u_acp(u), help_newline());
1922 	/* Possible added ACP per turn. */
1923 	if (uu_table_row_not_default(u, uu_occ_adds_acp,
1924 				     table_default(uu_occ_adds_acp))) {
1925 	    tbprintf(buf, "%sOccupant adds ACP: ", help_indent(2));
1926 	    uu_table_row_desc(buf, u, uu_occ_adds_acp, NULL, NULL);
1927 	    tbcatline(buf, ".");
1928 	}
1929 	if (ut_table_row_not_default(u, ut_night_adds_acp,
1930 				     table_default(ut_night_adds_acp))) {
1931 	    tbprintf(buf, "%sNight adds ACP: ", help_indent(2));
1932 	    ut_table_row_desc(buf, u, ut_night_adds_acp, NULL, "in");
1933 	    tbcatline(buf, ".");
1934 	}
1935 	if (u_acp_damage_effect(u) != uprop_l_default(u_acp_damage_effect)) {
1936 	    /* (Should write out ACP damage interpolation list.) */
1937 	    tbprintf(buf, "%sDamage affects ACP.%s", help_indent(2),
1938 		     help_newline());
1939 	}
1940 	/* Possible ACP multipliers. */
1941 	if (uu_table_row_not_default(u, uu_occ_multiplies_acp,
1942 				     table_default(uu_occ_multiplies_acp))) {
1943 	    tbprintf(buf, "%sOccupant multiplies ACP: ", help_indent(2));
1944 	    uu_table_row_desc(buf, u, uu_occ_multiplies_acp, tb_mult_desc,
1945 			      NULL);
1946 	    tbcatline(buf, ".");
1947 	}
1948 	if (ut_table_row_not_default(u, ut_night_multiplies_acp,
1949 				     table_default(ut_night_multiplies_acp))) {
1950 	    tbprintf(buf, "%sNight multiplies ACP: ", help_indent(2));
1951 	    ut_table_row_desc(buf, u, ut_night_multiplies_acp, tb_mult_desc,
1952 			      "in");
1953 	    tbcatline(buf, ".");
1954 	}
1955 	/* (Should consider other multipliers such as temperature.) */
1956 	/* Free ACP. */
1957 	if (u_free_acp(u) != uprop_i_default(u_free_acp)) {
1958 	    tbprintf(buf, "%sAllowed %d free ACP to complete an action.%s",
1959 		     help_indent(2), u_free_acp(u), help_newline());
1960 	}
1961     }
1962     tbcatline(buf, "");
1963 }
1964 
1965 /* Describe the unit type's relevance to the sides. */
1966 
1967 static void
describe_utype_side_attributes(int u,char * key,TextBuffer * buf)1968 describe_utype_side_attributes(int u, char *key, TextBuffer *buf)
1969 {
1970     int hasattribs = FALSE, first = TRUE;
1971     Side *side = NULL;
1972     char sidetmpbuf[BUFSIZE];
1973 
1974     tbcatline(buf, "Side Attributes:");
1975     /* Possible sides. */
1976     if (u_possible_sides(u) != lispnil) {
1977 	tbprintf(buf, "%sCan belong to the following sides: ", help_indent(2));
1978 	first = TRUE;
1979 	for_all_sides(side) {
1980 	    if (type_allowed_on_side(u, side)) {
1981 		if (first)
1982 		  first = FALSE;
1983 		else
1984 		  tbcat(buf, ", ");
1985 		tbcat(buf, shortest_side_title(side, sidetmpbuf));
1986 	    }
1987     	}
1988     	tbcatline(buf, ".");
1989 	hasattribs = TRUE;
1990     }
1991     /* (Should mention whether the unit can ever be available to the
1992 	side of the player viewing the help.) */
1993     /* Self unit. */
1994     if (u_can_be_self(u)) {
1995     	tbcatline_si(buf, "Can be a side leader/capital (aka. \"self\") unit.");
1996     	if (u_self_changeable(u))
1997 	  tbcatline_si(buf, "Side can opt for a different self unit instead.");
1998     	if (u_self_resurrects(u))
1999 	  tbcatline_si(buf, "Upon death, annoint a new self unit.");
2000 	/* (Should move the following test to side help.) */
2001 	if (g_self_required())
2002 	  tbcatline_si(buf, "(Side must always have a self unit during game.)");
2003 	/* (If an unit of this type is the side's self unit, then should
2004 	    mention its name and location.) */
2005 	hasattribs = TRUE;
2006     }
2007     /* How many per side? */
2008     if (u_type_per_side_max(u) >= 0) {
2009 	tbprintf(buf, "%sAt most %d allowed on each side in a game.%s",
2010 		 help_indent(2), u_type_per_side_max(u), help_newline());
2011 	hasattribs = TRUE;
2012     }
2013     /* (Should tell how many the side of the player currently owns. And
2014 	perhaps how many allied sides own in addition.) */
2015     if (!u_direct_control(u)) {
2016 	tbcatline_si(buf, "Cannot be controlled directly by side.");
2017 	hasattribs = TRUE;
2018     }
2019     if (!hasattribs)
2020       tbcatline_si(buf, "None.");
2021     tbcatline(buf, "");
2022 }
2023 
2024 /* Describe the unit type's AI attributes. */
2025 
2026 static void
describe_utype_ai_attributes(int u,char * key,TextBuffer * buf)2027 describe_utype_ai_attributes(int u, char *key, TextBuffer *buf)
2028 {
2029     int hasattribs = FALSE;
2030 
2031     tbcatline(buf, "AI Attributes:");
2032     if (u_colonizer(u)) {
2033 	tbcatline_si(buf, "Can build advanced units.");
2034 	hasattribs = TRUE;
2035     }
2036     if (u_facility(u)) {
2037 	tbcatline_si(buf, "Considered to be an immobile facility.");
2038 	hasattribs = TRUE;
2039     }
2040     if (u_ground_mobile(u)) {
2041 	tbcatline_si(buf, "Considered to be a mobile ground unit.");
2042 	hasattribs = TRUE;
2043     }
2044     if (u_naval_mobile(u)) {
2045 	tbcatline_si(buf, "Considered to be a mobile naval unit.");
2046 	hasattribs = TRUE;
2047     }
2048     if (u_air_mobile(u)) {
2049 	tbcatline_si(buf, "Considered to be a mobile air unit.");
2050 	hasattribs = TRUE;
2051     }
2052     if (u_minimal_sea_for_docks(u) < PROPHI) {
2053 	tbprintf(buf,
2054 "%sRequires >= %d accessible, liquid cells to build naval units.%s",
2055 		 help_indent(2), u_minimal_sea_for_docks(u), help_newline());
2056 	hasattribs = TRUE;
2057     }
2058     if (u_ai_peace_garrison(u) > 0) {
2059 	tbprintf(buf, "%sRequests a total peacetime garrison of %d units.%s",
2060 		 help_indent(2), u_ai_peace_garrison(u), help_newline());
2061 	hasattribs = TRUE;
2062     }
2063     if (u_ai_war_garrison(u) > 0) {
2064 	tbprintf(buf, "%sRequests a total wartime garrison of %d units.%s",
2065 		 help_indent(2), u_ai_war_garrison(u), help_newline());
2066 	hasattribs = TRUE;
2067     }
2068     if (u_ai_enemy_alert_range(u) > 0) {
2069 	tbprintf(buf,
2070 		 "%sRange within which enemies will cause an alert is %d.%s",
2071 		 help_indent(2), u_ai_enemy_alert_range(u), help_newline());
2072 	hasattribs = TRUE;
2073     }
2074     if (u_ai_tactical_range(u) > 0) {
2075 	tbprintf(buf, "%sBasic tactical computation range is %d.%s",
2076 		 help_indent(2), u_ai_tactical_range(u), help_newline());
2077 	hasattribs = TRUE;
2078     }
2079     if (!hasattribs)
2080       tbcatline_si(buf, "None.");
2081     tbcatline(buf, "");
2082 }
2083 
2084 int
may_detonate(int u)2085 may_detonate(int u)
2086 {
2087     return ((type_max_acp(u) > 0 && u_acp_to_detonate(u) > 0)
2088 	    || u_detonate_on_death(u) > 0
2089 	    || uu_table_row_not_default(u, uu_detonate_on_hit, 0)
2090 	    || uu_table_row_not_default(u, uu_detonate_on_capture, 0)
2091 	    || uu_table_row_not_default(u, uu_detonate_approach_range, -1)
2092 	    || ut_table_row_not_default(u, ut_detonation_accident, 0)
2093 	    );
2094 }
2095 
2096 static void
dump_material_unit_summary(TextBuffer * buf,const char * category,int (* func)(int,int),int m,int no,int fire_f)2097 dump_material_unit_summary(TextBuffer * buf,const char * category,
2098                            int (*func)(int,int), int m, int no, int fire_f)
2099 {
2100     int cnt = 0;
2101     int u;
2102     int p;
2103 
2104     tbprintf(buf,"%s: ",category);
2105     for_all_unit_types(u) {
2106 	p = (*func)(u,m);
2107 	if(fire_f && !(u_acp_to_fire(u) > 0))
2108             continue;
2109 	if(p != no) {
2110 	    cnt++;
2111 	    tbprintf(buf,"%s:%d ",u_type_name(u),p);
2112 	}
2113     }
2114     if(cnt == 0)
2115         tbprintf(buf,"(none)%s%s", help_newline(), help_newline());
2116     else
2117         tbprintf(buf,"%s%s", help_newline(), help_newline());
2118     return;
2119 }
2120 
2121 static void
describe_mtype(int m,char * key,TextBuffer * buf)2122 describe_mtype(int m, char *key, TextBuffer *buf)
2123 {
2124     int u;
2125     int cnt;
2126     int o;
2127     int i;
2128     int s;
2129 
2130     append_help_phrase(buf, m_help(m));
2131     /* Display the designer's notes for this type. */
2132     if (m_notes(m) != lispnil) {
2133 	tbprintf(buf, "%sNotes:%s", help_newline(), help_newline());
2134 	append_notes(buf, m_notes(m));
2135 	tbprintf(buf, "%s%s", help_newline(), help_newline());
2136     }
2137     if (m_people(m) > 0) {
2138 	tbprintf(buf, "1 of this represents %d individuals.", m_people(m));
2139     }
2140     /* (should display unit columns here) */
2141     /* And here is a summary */
2142     tbprintf(buf, "%s%s", help_newline(), help_newline());
2143     dump_material_unit_summary(
2144         buf,"Base production", um_base_production, m, 0, 0);
2145     dump_material_unit_summary(
2146         buf,"Base consumption", um_base_consumption, m, 0, 0);
2147     dump_material_unit_summary(buf,"Storage", um_storage_x, m, 0, 0);
2148     dump_material_unit_summary(buf,"Consumption percentage as occupant",
2149 	    	               um_consumption_as_occupant, m, 100, 0);
2150     dump_material_unit_summary(buf,"Production as occupant",
2151 	    	               um_occ_production, m, -1, 0);
2152     dump_material_unit_summary(
2153         buf,"ACP to extract", um_acp_to_extract, m, 0, 0);
2154     tbprintf(
2155         buf, "%sMinimum required by a unit for the following activities.%s%s",
2156 	help_newline(), help_newline(), help_newline());
2157     dump_material_unit_summary(buf, "To act", um_to_act, m, 0, 0);
2158     dump_material_unit_summary(buf, "To move", um_to_move, m, 0, 0);
2159     dump_material_unit_summary(buf, "To attack", um_to_attack, m, 0, 0);
2160     dump_material_unit_summary(buf, "To fire", um_to_fire, m, 0, 1);
2161     tbprintf(buf, "%sIn addition to basic consumption.%s%s", help_newline(),
2162 	     help_newline(), help_newline(), help_newline());
2163     dump_material_unit_summary(buf, "Consumption per move",
2164 			       um_consumption_per_move, m, 0, 0);
2165     dump_material_unit_summary(buf, "Consumption per attack",
2166 			       um_consumption_per_attack, m, 0, 0);
2167     dump_material_unit_summary(buf, "Consumption per fire",
2168 			       um_consumption_per_fire, m, 0, 1);
2169     tbprintf(buf, "%sAny unit building another unit,%s", help_newline(),
2170 	     help_newline());
2171     tbcatline(buf, "needs (but does not necessarily use)");
2172     tbcatline(buf, "the following minimum amounts for the given unit.");
2173     dump_material_unit_summary(
2174         buf, "Needed to create ", um_to_create, m, 0, 0);
2175     dump_material_unit_summary(
2176         buf, "Needed to build ", um_to_build, m, 0, 0);
2177     tbprintf(buf, "%sAny building unit consumes as follows%s%s",
2178 	     help_newline(), help_newline(), help_newline());
2179     dump_material_unit_summary(buf,"On creation of",
2180 			       um_consumption_on_creation, m, 0, 0);
2181     dump_material_unit_summary(buf,"Per build of",
2182 			       um_consumption_per_build, m, 0, 0);
2183     tbprintf(
2184         buf,"%sAny unit repairing other units needs/uses these amounts%s%s",
2185 	help_newline(), help_newline(), help_newline());
2186     dump_material_unit_summary(
2187         buf, "To be able to repair ", um_to_repair, m, 0, 0);
2188     dump_material_unit_summary(buf, "Consumption per repair of 1 HP for",
2189 	                       um_consumption_per_repair, m, 0, 0);
2190     /*
2191      * Select a minimal set of functions for the test,
2192      * after all, if it doesnt store any, it cant use the material.
2193      */
2194     tbcatline(
2195         buf, "Distances that a unit can send or receive this material.");
2196     tbcatline(buf, "Format is unit:send:receive.");
2197     tbcatline(buf, "(NB Distances are free of terrain effects)");
2198     for_all_unit_types(u) {
2199         /* (Should consider the case where a material is sent directly
2200             to the side treasury.) */
2201 	if (um_base_production(u,m) > 0
2202 	    || um_base_consumption(u,m) > 0
2203 	    || um_storage_x(u,m) > 0) {
2204 		o = um_outlength(u,m);
2205 		i = um_inlength(u,m);
2206 		if (o >= 0 && i >= 0)
2207 	    	    tbprintf(buf, "%s:%d:%d ", u_type_name(u), o, i);
2208 		else if (i >= 0)
2209 	    	    tbprintf(buf, "%s:-:%d ", u_type_name(u), i);
2210 	}
2211     }
2212     tbprintf(buf, "%s%s", help_newline(), help_newline());
2213     tbprintf(buf, "Initial unit quantities at game start:");
2214     cnt = 0;
2215     for_all_unit_types(u) {
2216 	i = um_initial(u,m);
2217 	s = um_storage_x(u,m);
2218 	if( s > 0 && i >= 0 && i < s) {
2219 	    tbprintf(buf, "%s:%d ", u_type_name(u), i);
2220 	    cnt++;
2221 	}
2222     }
2223     if (!cnt)
2224         tbprintf(buf,"(none)");
2225     tbprintf(buf,"%s%s", help_newline(), help_newline());
2226     return;
2227 }
2228 
2229 static void
describe_ttype(int t,char * key,TextBuffer * buf)2230 describe_ttype(int t, char *key, TextBuffer *buf)
2231 {
2232     int m, ct;
2233     int u;
2234     int e;
2235     int speed;
2236     int acp;
2237     int mp;
2238     int l;
2239     int eff;
2240 
2241     append_help_phrase(buf, t_help(t));
2242     /* Display the subtype. */
2243     switch (t_subtype(t)) {
2244       case cellsubtype:
2245 	break;
2246       case bordersubtype:
2247 	tbcatline(buf, " (a border type)");
2248 	break;
2249       case connectionsubtype:
2250 	tbcatline(buf, " (a connection type)");
2251 	break;
2252       case coatingsubtype:
2253 	tbcatline(buf, " (a coating type)");
2254 	break;
2255     }
2256     /* Display the designer's notes for this type. */
2257     if (t_notes(t) != lispnil) {
2258 	tbprintf(buf, "%sNotes:%s", help_newline(), help_newline());
2259 	append_notes(buf, t_notes(t));
2260 	tbprintf(buf, "%s%s", help_newline(), help_newline());
2261     }
2262     if (t_liquid(t))
2263       tbcatline(buf, "Represents water or other liquid.");
2264     tbprintf(buf, "Generic unit capacity is %d.%s", t_capacity(t),
2265 	     help_newline());
2266     if (t_people_max(t) >= 0) {
2267 	tbprintf(buf, "Up to %d people may live in this type of terrain.%s",
2268 		 t_people_max(t), help_newline());
2269     }
2270     if (any_elev_variation) {
2271 	if (t_elev_min(t) == t_elev_max(t)) {
2272 	    tbprintf(buf, "Elevation is always %d.%s",
2273 		    t_elev_min(t), help_newline());
2274 	} else {
2275 	    tbprintf(buf, "Elevations fall between %d and %d.%s",
2276 		    t_elev_min(t), t_elev_max(t), help_newline());
2277 	}
2278     }
2279     if (t_thickness(t) > 0) {
2280 	tbprintf(buf, "Thickness is %d.%s", t_thickness(t), help_newline());
2281     }
2282     if (any_temp_variation) {
2283 	if (t_temp_min(t) == t_temp_max(t)) {
2284 	    tbprintf(buf, "Temperature is always %d.%s",
2285 		     t_temp_min(t), help_newline());
2286 	} else {
2287 	    tbprintf(buf,
2288                      "Temperatures fall between %d and %d, averaging %d.%s",
2289 		     t_temp_min(t), t_temp_max(t), t_temp_avg(t),
2290 		     help_newline());
2291 	}
2292 	if (t_temp_variability(t) > 0) {
2293 	    tbprintf(buf, "Temperature varies randomly, up to %d each turn.%s",
2294 		     t_temp_variability(t), help_newline());
2295 	}
2296     }
2297     if (any_wind_variation) {
2298 	if (t_wind_force_min(t) == t_wind_force_max(t)) {
2299 	    tbprintf(buf, "Wind force is always %d.%s",
2300 		     t_wind_force_min(t), help_newline());
2301 	} else {
2302 	    tbprintf(buf,
2303                      "Wind forces fall between %d and %d, averaging %d.%s",
2304 		     t_wind_force_min(t), t_wind_force_max(t),
2305                      t_wind_force_avg(t), help_newline());
2306 	}
2307 	if (t_wind_force_variability(t) > 0) {
2308 	    tbprintf(buf,
2309                      "%d%% chance each turn that wind force will change.%s",
2310 		     t_wind_force_variability(t), help_newline());
2311 	}
2312 	if (t_wind_variability(t) > 0) {
2313 	    tbprintf(buf,
2314                      "%d%% chance each turn that wind direction will change.%s",
2315 		     t_wind_variability(t), help_newline());
2316 	}
2317     }
2318     if (any_clouds) {
2319 	if (t_clouds_min(t) == t_clouds_max(t)) {
2320 	    tbprintf(buf, "Cloud cover is always %d.%s",
2321 		     t_clouds_min(t), help_newline());
2322 	} else {
2323 	    tbprintf(buf, "Cloud cover falls between %d and %d%s",
2324 		     t_clouds_min(t), t_clouds_max(t), help_newline());
2325 	}
2326     }
2327     /* Display relationships with materials. */
2328     if (nummtypes > 0) {
2329 	for_all_material_types(m) {
2330 	    if (tm_storage_x(t, m) > 0
2331 		|| tm_production(t, m) > 0
2332 		|| tm_consumption(t, m) > 0
2333 		|| tm_prod_from_terrain(t, m) > 0) {
2334 		tbprintf(buf, "%s:", m_type_name(m));
2335 	    }
2336 	    if (tm_storage_x(t, m) > 0) {
2337 	    	tbprintf(buf, "%sCan store up to %d", help_indent(2),
2338 			 tm_storage_x(t, m));
2339 	    	tbprintf(buf, " (normally starts game with %d)",
2340 			 min(tm_initial(t, m), tm_storage_x(t, m)));
2341 	    	tbcatline(buf, ".");
2342 	    	tbprintf(buf,
2343 "%sSides will%s always know current amount accurately.%s",
2344 			 help_indent(2), (tm_see_always(t, m) ? "" : " not"),
2345 			 help_newline());
2346 	    }
2347 	    if (tm_production(t, m) > 0 || tm_consumption(t, m) > 0) {
2348 		tbprintf(buf, "%sProduces %d and consumes %d each turn.%s",
2349 			 help_indent(2), tm_production(t, m),
2350 			 tm_consumption(t, m), help_newline());
2351 	    }
2352 	    if (tm_prod_from_terrain(t, m) > 0) {
2353 		tbprintf(buf,
2354 "%sAdvanced units can use this terrain to produce %d each turn.%s",
2355 			 help_indent(2), tm_prod_from_terrain(t, m),
2356 			 help_newline());
2357 	    }
2358 	}
2359     }
2360     /* Display relationships with any coating terrain types. */
2361     if (numcoattypes > 0) {
2362 	tbcatline(buf, "Coatings:");
2363     	for_all_terrain_types(ct) {
2364 	    if (t_is_coating(ct)) {
2365 		tbprintf(buf, "%s coats, depths %d up to %d",
2366 			 t_type_name(ct),
2367 			 tt_coat_min(ct, t), tt_coat_max(ct, t));
2368 	    }
2369     	}
2370     }
2371     /* Display damaged types. */
2372     if (tt_table_row_not_default(t, tt_damaged_type, 0)) {
2373 	tbcat_si(buf, "Type after being damaged: ");
2374 	tt_table_row_desc(buf, t, tt_damaged_type, NULL);
2375 	tbcatline(buf, ".");
2376     }
2377     /* Display exhaustion types. */
2378     if (nummtypes > 0) {
2379 	for_all_material_types(m) {
2380 	    if (tm_change_on_exhaust(t, m) > 0
2381 		&& tm_exhaust_type(t, m) != NONTTYPE) {
2382 		tbprintf(buf,
2383 			 "If exhausted of %s, %d%% chance to change to %s.%s",
2384 			 m_type_name(m), tm_change_on_exhaust(t, m),
2385 			 t_type_name(tm_exhaust_type(t, m)), help_newline());
2386 	    }
2387 	}
2388     }
2389 #ifdef DESIGNERS
2390     if (numdesigners > 0) {
2391 	tbprintf(buf, "%sFOR DESIGNERS:%s", help_newline(), help_newline());
2392 	if (t_subtype_x(t) ==
2393 	    c_number(symbol_value(intern_symbol(keyword_name(K_RIVER_X))))) {
2394 	    tbcatline(buf, "Considered to be a type of river.");
2395 	} else if (t_subtype_x(t) ==
2396 		   c_number(
2397 		     symbol_value(intern_symbol(keyword_name(K_ROAD_X))))) {
2398 	    tbcatline(buf, "Considered to be a type of road.");
2399 	}
2400 	if (tt_table_row_not_default(t, tt_drawable, 1)) {
2401 	    tbcat(buf, "Draw over terrain: ");
2402 	    tt_table_row_desc(buf, t, tt_drawable, NULL);
2403 	    tbcatline(buf, ".");
2404 	}
2405     }
2406 #endif /* DESIGNERS */
2407     tbprintf(buf, "%s%sUnits able to enter, with given MP cost.%s",
2408 	     help_newline(), help_newline(), help_newline());
2409     for_all_unit_types(u) {
2410 	e = ut_mp_to_enter(u,t);
2411 	speed = type_max_speed(u);
2412 	acp = type_max_acp(u);
2413 	mp = (acp*speed)/100;
2414 	if (speed == 0)
2415             continue;
2416 	if (e > mp)
2417             continue;
2418 	tbprintf(buf, "%s:%d ", u_type_name(u), e);
2419     }
2420     tbprintf(buf,"%s%s", help_newline(), help_newline());
2421     tbcatline(buf, "Units able to leave, with given MP cost.");
2422     for_all_unit_types(u) {
2423 	l = ut_mp_to_leave(u,t);
2424 	speed = type_max_speed(u);
2425 	acp = type_max_acp(u);
2426 	mp = (acp*speed)/100;
2427 	if (speed == 0)
2428             continue;
2429 	if (l <= mp)
2430             tbprintf(buf, "%s:%d ", u_type_name(u), l);
2431     }
2432     tbprintf(buf,"%s%s", help_newline(), help_newline());
2433     tbcatline(buf, "An attacker in this terrain, attacking the given units,");
2434     tbcatline(buf, "has the following multiplier, ( > 1 = advantage).");
2435     for_all_unit_types(u) {
2436 	eff = ut_attack_terrain_effect(u,t);
2437 	if (eff == 100)
2438 	  continue;
2439 	tbprintf(buf, "%s:%s%d.%d ", u_type_name(u), "*", eff / 100, eff % 100);
2440     }
2441     tbprintf(buf,"%s%s", help_newline(), help_newline());
2442 
2443     tbcatline(buf, "A defender in this terrain,");
2444     tbcatline(buf, "has the following multiplier, ( < 1 = advantage).");
2445     for_all_unit_types(u) {
2446 	eff = ut_defend_terrain_effect(u,t);
2447 	if (eff == 100)
2448             continue;
2449 	tbprintf(buf, "%s:%s%d.%d ", u_type_name(u), "*", eff / 100, eff % 100);
2450     }
2451     tbprintf(buf,"%s%s", help_newline(), help_newline());
2452     tbcatline(buf,"The following units vanish in this terrain.");
2453     for_all_unit_types(u) {
2454 	if (ut_vanishes_on(u,t))
2455             tbprintf(buf, "%s ", u_type_name(u));
2456     }
2457     tbprintf(buf,"%s%s", help_newline(), help_newline());
2458     tbcatline(buf, "The following units wreck in this terrain.");
2459     for_all_unit_types(u) {
2460 	if (ut_wrecks_on(u,t))
2461             tbprintf(buf, "%s ", u_type_name(u));
2462     }
2463     tbprintf(buf,"%s%s", help_newline(), help_newline());
2464 }
2465 
2466 static void
describe_atype(int a,char * key,TextBuffer * buf)2467 describe_atype(int a, char *key, TextBuffer *buf)
2468 {
2469     int	u, found = FALSE;
2470 
2471     append_help_phrase(buf, a_help(a));
2472     /* Display the designer's notes for this type. */
2473     if (a_notes(a) != lispnil) {
2474 	tbprintf(buf, "%sNotes:%s", help_newline(), help_newline());
2475 	append_notes(buf, a_notes(a));
2476 	tbprintf(buf, "%s%s", help_newline(), help_newline());
2477     }
2478     tbprintf(buf, "Research points needed for discovery: %d.%s", a_rp(a),
2479 	     help_newline());
2480     tbcatline(buf, "");
2481     if (aa_table_row_not_default(a, aa_needed_to_research, 0)) {
2482 	tbcat(buf, "Prerequisites: ");
2483 	aa_table_row_desc(buf, a, aa_needed_to_research, tb_bool_desc);
2484 	tbprintf(buf, ".%s%s", help_newline(), help_newline());
2485     } else {
2486 	tbprintf(buf, "No prerequisites.%s%s", help_newline(), help_newline());
2487     }
2488     if (aa_table_column_not_default(a, aa_needed_to_research, 0)) {
2489 	tbcat(buf, "Needed for: ");
2490 	aa_table_column_desc(buf, a, aa_needed_to_research, tb_bool_desc);
2491 	tbprintf(buf, ".%s%s", help_newline(), help_newline());
2492     } else {
2493 	tbprintf(buf, "Not needed for any further advances.%s%s",
2494 		 help_newline(), help_newline());
2495     }
2496     for_all_unit_types(u) {
2497 	if (ua_needed_to_build(u, a) > 0) {
2498 		if (found) {
2499 			tbprintf(buf, ", ");
2500 		} else {
2501 			tbcat(buf, "Enables: ");
2502 		}
2503 		tbprintf(buf, "%s", u_type_name(u));
2504 		found = TRUE;
2505 	}
2506     }
2507     if (found) {
2508 	tbcatline(buf, ".");
2509     } else	{
2510 	tbcatline(buf, "Does not enable any new unit types.");
2511     }
2512 }
2513 
2514 static void
describe_scorekeepers(int arg,char * key,TextBuffer * buf)2515 describe_scorekeepers(int arg, char *key, TextBuffer *buf)
2516 {
2517     int i = 1;
2518     Scorekeeper *sk;
2519 
2520     if (scorekeepers == NULL) {
2521 	tbcat(buf, "No scores are being kept.");
2522     } else {
2523 	for_all_scorekeepers(sk) {
2524 	    if (numscorekeepers > 1) {
2525 		tbprintf(buf, "%d.  ", i++);
2526 	    }
2527 	    if (symbolp(sk->body)
2528 		&& match_keyword(sk->body, K_LAST_SIDE_WINS)) {
2529 		tbcat(buf, "The last side left in the game wins.");
2530 		/* (should mention point values also) */
2531 	    }
2532 	    else if (symbolp(sk->body)
2533 		&& match_keyword(sk->body, K_LAST_ALLIANCE_WINS)) {
2534 		tbcat(buf, "The last alliance left in the game wins.");
2535 		/* (should mention point values also) */
2536 	    } else {
2537 		tbcat(buf, "(an indescribably complicated scorekeeper)");
2538 	    }
2539 	    tbcatline(buf, "");
2540 	}
2541     }
2542 }
2543 
2544 /* List each synthesis method and its parameters. */
2545 
2546 static void
describe_setup(int arg,char * key,TextBuffer * buf)2547 describe_setup(int arg, char *key, TextBuffer *buf)
2548 {
2549     int u, t, t2, methkey;
2550     Obj *synthlist, *methods, *method;
2551     int firstitem = TRUE;
2552 
2553     synthlist = g_synth_methods();
2554     if (synthlist == lispnil)
2555       tbcatline(buf, "No synthesis done when setting up this game.");
2556     else
2557       tbcatline(buf, "Synthesis done when setting up this game:");
2558     for (methods = synthlist; methods != lispnil; methods = cdr(methods)) {
2559 	tbcatline(buf, "");
2560 	method = car(methods);
2561 	if (symbolp(method)) {
2562 	    methkey = keyword_code(c_string(method));
2563 	    switch (methkey) {
2564 	      case K_MAKE_COUNTRIES:
2565 		tbcat(buf, "Countries:");
2566 		describe_synth_run(buf, methkey);
2567 		tbcatline(buf, "");
2568 		if (g_radius_min() != gvar_i_default(g_radius_min)) {
2569 		    tbprintf(buf, "%s%d cells across", help_indent(2),
2570 			     2 * g_radius_min() + 1);
2571 		    if (g_separation_min() != gvar_i_default(g_separation_min))
2572 		      tbprintf(buf, ", at least %d cells apart",
2573 			       g_separation_min());
2574 		    if (g_separation_max() != gvar_i_default(g_separation_max))
2575 		      tbprintf(buf, ", and at most %d cells apart",
2576 			       g_separation_max());
2577 		    tbcatline(buf, "");
2578 		}
2579 		if (t_property_not_default(t_country_min,
2580 					   tprop_i_default(t_country_min))) {
2581 		    tbprintf(buf, "%sMinimum terrain in each country: ",
2582 			     help_indent(2));
2583 		    t_property_desc(buf, t_country_min, NULL);
2584 		    tbcatline(buf, ".");
2585 		}
2586 		if (t_property_not_default(t_country_max,
2587 					   tprop_i_default(t_country_max))) {
2588 		    tbprintf(buf, "%sMaximum terrain in each country: ",
2589 			     help_indent(2));
2590 		    t_property_desc(buf, t_country_max, NULL);
2591 		    tbcatline(buf, ".");
2592 		}
2593 		if (u_property_not_default(u_start_with,
2594 					   uprop_i_default(u_start_with))) {
2595 		    tbprintf(buf, "%sStart with: ", help_indent(2));
2596 		    u_property_desc(buf, u_start_with, NULL);
2597 		    tbcatline(buf, ".");
2598 		}
2599 		if (u_property_not_default(u_indep_near_start,
2600 					   uprop_i_default(
2601 					     u_indep_near_start))) {
2602 		    tbprintf(buf, "%sIndependents nearby: ", help_indent(2));
2603 		    u_property_desc(buf, u_indep_near_start, NULL);
2604 		    tbcatline(buf, ".");
2605 		}
2606 		tbprintf(buf, "%sFavored terrain:%s", help_indent(2),
2607 			 help_newline());
2608 		for_all_unit_types(u) {
2609 		    if (u_start_with(u) > 0 || u_indep_near_start(u)) {
2610 			tbprintf(buf, "%s%s: ", help_indent(4), u_type_name(u));
2611 			ut_table_row_desc(buf, u, ut_favored, tb_percent_desc,
2612 					  NULL);
2613 			tbcatline(buf, ".");
2614 		    }
2615 		}
2616 		if (g_radius_max() != 0) {
2617 		    tbprintf(buf, "%sCountry growth:%s", help_indent(2),
2618 			     help_newline());
2619 		    if (g_radius_max() == -1) {
2620 			tbprintf(buf, "%sUp to entire world", help_indent(4));
2621 		    } else {
2622 			tbprintf(buf, "%sUp to %d cells across",
2623 				 help_indent(4), 2 * g_radius_max() + 1);
2624 		    }
2625 		    tbprintf(buf, ", %d chance to stop if blocked.%s",
2626 			     g_growth_stop(), help_newline());
2627 		    if (t_property_not_default(t_country_growth,
2628 					       tprop_i_default(
2629 						 t_country_growth))) {
2630 			tbprintf(buf, "%sGrowth chance, by terrain: ",
2631 				 help_indent(2));
2632 			t_property_desc(buf, t_country_max, tb_percent_desc);
2633 			tbcatline(buf, ".");
2634 		    }
2635 		    if (t_property_not_default(t_country_takeover,
2636 					       tprop_i_default(
2637 						 t_country_takeover))) {
2638 			tbprintf(buf, "%sTakeover chance, by terrain: ",
2639 				 help_indent(2));
2640 			t_property_desc(buf, t_country_takeover, NULL);
2641 			tbcatline(buf, ".");
2642 		    }
2643 		    if (u_property_not_default(u_unit_growth,
2644 					       uprop_i_default(
2645 						 u_unit_growth))) {
2646 			tbprintf(buf, "%sChance for additional unit: ",
2647 				 help_indent(2));
2648 			u_property_desc(buf, u_unit_growth, NULL);
2649 			tbcatline(buf, ".");
2650 		    }
2651 		    if (u_property_not_default(u_indep_growth,
2652 					       uprop_i_default(
2653 						 u_indep_growth))) {
2654 			tbprintf(buf,
2655 				 "%sChance for additional independent unit: ",
2656 				 help_indent(2));
2657 			u_property_desc(buf, u_indep_growth, NULL);
2658 			tbcatline(buf, ".");
2659 		    }
2660 		    if (u_property_not_default(u_unit_takeover,
2661 					       uprop_i_default(
2662 						 u_unit_takeover))) {
2663 			tbprintf(buf, "%sChance to take over units: ",
2664 				 help_indent(2));
2665 			u_property_desc(buf, u_unit_takeover, NULL);
2666 			tbcatline(buf, ".");
2667 		    }
2668 		    if (u_property_not_default(u_indep_takeover,
2669 					       uprop_i_default(
2670 						 u_indep_takeover))) {
2671 			tbprintf(buf,
2672 				 "%sChance to take over independent unit: ",
2673 				 help_indent(2));
2674 			u_property_desc(buf, u_indep_takeover, NULL);
2675 			tbcatline(buf, ".");
2676 		    }
2677 		    if (u_property_not_default(u_country_units_max,
2678 					       uprop_i_default(
2679 						 u_country_units_max))) {
2680 			tbprintf(buf, "%sMaximum units in country: ",
2681 				 help_indent(2));
2682 			u_property_desc(buf, u_country_units_max, NULL);
2683 			tbcatline(buf, ".");
2684 		    }
2685 		    if (t_property_not_default(t_country_people,
2686 					       tprop_i_default(
2687 						 t_country_people))) {
2688 			tbprintf(buf, "%sPeople takeover chance, by terrain: ",
2689 				 help_indent(2));
2690 			t_property_desc(buf, t_country_people, NULL);
2691 			tbcatline(buf, ".");
2692 		    }
2693 		    if (t_property_not_default(t_indep_people,
2694 					       tprop_i_default(
2695 						 t_indep_people))) {
2696 			tbprintf(buf, "%sIndependent people chance: ",
2697 				 help_indent(2));
2698 			t_property_desc(buf, t_indep_people, NULL);
2699 			tbcatline(buf, ".");
2700 		    }
2701 		}
2702 		break;
2703 	      case K_MAKE_EARTHLIKE_TERRAIN:
2704 		tbcat(buf, "Earthlike terrain:");
2705 		describe_synth_run(buf, methkey);
2706 		tbcatline(buf, "");
2707 		tbprintf(buf, "%sTerrain around edge is %s.%s",
2708 			 help_indent(2), t_type_name(g_edge_terrain()),
2709 			 help_newline());
2710 		/* (should describe tt_adj_terr_effect workings here) */
2711 		break;
2712 	      case K_MAKE_FRACTAL_PTILE_TERRAIN:
2713 		tbcat(buf, "Fractal percentile terrain:");
2714 		describe_synth_run(buf, methkey);
2715 		tbcatline(buf, "");
2716 		tbprintf(buf, "%sAlt blobs density %d, size %d, height %d%s",
2717 			 help_indent(2), g_alt_blob_density(),
2718 			 g_alt_blob_size(), g_alt_blob_height(),
2719 			 help_newline());
2720 		tbprintf(buf, "%s%d smoothing passes%s", help_indent(4),
2721 			 g_alt_smoothing(), help_newline());
2722 		tbprintf(buf, "%sLower percentiles: ", help_indent(4));
2723 		t_property_desc(buf, t_alt_min, tb_percent_desc);
2724 		tbcatline(buf, ".");
2725 		tbprintf(buf, "%sUpper percentiles: ", help_indent(4));
2726 		t_property_desc(buf, t_alt_max, tb_percent_desc);
2727 		tbcatline(buf, ".");
2728 		tbprintf(buf, "%sWet blobs density %d, size %d, height %d%s",
2729 			 help_indent(2), g_wet_blob_density(),
2730 			 g_wet_blob_size(), g_wet_blob_height(),
2731 			 help_newline());
2732 		tbprintf(buf, "%s%d smoothing passes%s", help_indent(4),
2733 			 g_wet_smoothing(), help_newline());
2734 		tbprintf(buf, "%sLower percentiles: ", help_indent(4));
2735 		t_property_desc(buf, t_wet_min, tb_percent_desc);
2736 		tbcatline(buf, ".");
2737 		tbprintf(buf, "%sUpper percentiles: ", help_indent(4));
2738 		t_property_desc(buf, t_wet_max, tb_percent_desc);
2739 		tbcatline(buf, ".");
2740 		tbprintf(buf, "%sTerrain around edge is %s.%s",
2741 			 help_indent(2), t_type_name(g_edge_terrain()),
2742 			 help_newline());
2743 		/* (should describe tt_adj_terr_effect workings here) */
2744 		break;
2745 	      case K_MAKE_INDEPENDENT_UNITS:
2746 		tbcat(buf, "Independent units:");
2747 		describe_synth_run(buf, methkey);
2748 		tbcatline(buf, "");
2749 		for_all_unit_types(u) {
2750 		    if (ut_table_row_not_default(u, ut_indep_density,
2751 						 table_default(
2752 						   ut_indep_density))) {
2753 			tbprintf(buf, "%sChance of independent %s: ",
2754 				 help_indent(2), u_type_name(u));
2755 			ut_table_row_desc(buf, u, ut_indep_density,
2756 					  tb_percent_100th_desc, "in");
2757 			tbcatline(buf, ".");
2758 		    }
2759 		}
2760 		/* (should show indep people) */
2761 		break;
2762 	      case K_MAKE_INITIAL_MATERIALS:
2763 		tbcat(buf, "Materials:");
2764 		describe_synth_run(buf, methkey);
2765 		tbcatline(buf, "");
2766 		/* (should show unit and terrain initial supply) */
2767 		break;
2768 	      case K_MAKE_MAZE_TERRAIN:
2769 		tbcat(buf, "Maze terrain:");
2770 		describe_synth_run(buf, methkey);
2771 		tbcatline(buf, "");
2772 		tbprintf(buf, "%s%d.%2.2d%% of maze is room.%s",
2773 			 help_indent(2), g_maze_room() / 100,
2774 			 g_maze_room() % 100, help_newline());
2775 		tbcat(buf, "Room terrain types will be");
2776 		for_all_terrain_types(t) {
2777 		    if (t_maze_room_occurrence(t) > 0) {
2778 			tbprintf(buf, " %s(%d)", t_type_name(t),
2779 				 t_maze_room_occurrence(t));
2780 		    }
2781 		}
2782 		tbcatline(buf, ".");
2783 		tbprintf(buf, "%s%d.%2.2d%% of maze is passageway.%s",
2784 			 help_indent(2), g_maze_passage() / 100,
2785 			 g_maze_passage() % 100, help_newline());
2786 		tbcat(buf, "Passageway terrain types will be");
2787 		for_all_terrain_types(t) {
2788 		    if (t_maze_passage_occurrence(t) > 0) {
2789 			tbprintf(buf, " %s(%d)", t_type_name(t),
2790 				 t_maze_passage_occurrence(t));
2791 		    }
2792 		}
2793 		tbcatline(buf, ".");
2794 		tbprintf(buf, "%sTerrain around edge is %s.%s",
2795 			 help_indent(2), t_type_name(g_edge_terrain()),
2796 			 help_newline());
2797 		break;
2798 	      case K_MAKE_RANDOM_DATE:
2799 		tbcat(buf, "Random date:");
2800 		describe_synth_run(buf, methkey);
2801 		tbcatline(buf, "");
2802 		break;
2803 	      case K_MAKE_RANDOM_TERRAIN:
2804 		tbcat(buf, "Random terrain:");
2805 		describe_synth_run(buf, methkey);
2806 		tbcatline(buf, "");
2807 		tbprintf(buf, "%sTerrain types will be", help_indent(2));
2808 		for_all_terrain_types(t) {
2809 		    if (t_occurrence(t) > 0) {
2810 			tbprintf(buf, " %s(%d)", t_type_name(t),
2811 				 t_occurrence(t));
2812 		    }
2813 		}
2814 		tbcatline(buf, ".");
2815 		tbprintf(buf, "%sTerrain around edge is %s.%s",
2816 			 help_indent(2), t_type_name(g_edge_terrain()),
2817 			 help_newline());
2818 		break;
2819 	      case K_MAKE_RIVERS:
2820 		tbcat(buf, "Rivers:");
2821 		describe_synth_run(buf, methkey);
2822 		tbcatline(buf, "");
2823 		if (t_property_not_default(t_river_chance,
2824 					   tprop_i_default(t_river_chance))) {
2825 		    tbprintf(buf, "%sChance to be river source: ",
2826 			     help_indent(2));
2827 		    t_property_desc(buf, t_river_chance,
2828 				    tb_percent_100th_desc);
2829 		    tbcatline(buf, ".");
2830 		    if (g_river_sink_terrain() != NONTTYPE)
2831 		      tbprintf(buf, "%sSink is %s.%s",
2832 			       help_indent(2),
2833 			       t_type_name(g_river_sink_terrain()),
2834 			       help_newline());
2835 		    else
2836 		      tbprintf(buf, "%sNo special sink terrain.%s",
2837 			       help_indent(2), help_newline());
2838 		}
2839 		for_all_terrain_types(t) {
2840 		    if (!t_is_border(t))
2841 		      continue;
2842 		    if (K_RIVER_X != t_subtype_x(t))
2843 		      continue;
2844 		    if (tt_table_row_not_default(t, tt_adj_terr_effect,
2845 						 table_default(
2846 						   tt_adj_terr_effect))) {
2847 			tbprintf(buf, "%sRiver of type %s incompatible with ",
2848 			      help_indent(2), t_type_name(t));
2849 			firstitem = TRUE;
2850 			for_all_terrain_types(t2) {
2851 			    if (tt_adj_terr_effect(t, t2) >= 0) {
2852 				if (!firstitem)
2853 				  tbprintf(buf, ", %s", t_type_name(t2));
2854 				else {
2855 				    tbprintf(buf, " %s", t_type_name(t2));
2856 				    firstitem = FALSE;
2857 				}
2858 			    }
2859 			}
2860 			tbcatline(buf, ".");
2861 		    }
2862 		}
2863 		break;
2864 	      case K_MAKE_ROADS:
2865 		tbcat(buf, "Roads:");
2866 		describe_synth_run(buf, methkey);
2867 		tbcatline(buf, "");
2868 		tbprintf(buf, "%sChance to run:%s", help_indent(2),
2869 			 help_newline());
2870 		for_all_unit_types(u) {
2871 		    if (uu_table_row_not_default(u, uu_road_chance, 0)) {
2872 			tbprintf(buf, "%sFrom %s: ", help_indent(2),
2873 				 u_type_name(u));
2874 			uu_table_row_desc(buf, u, uu_road_chance,
2875 					  tb_percent_desc, "to");
2876 			tbcatline(buf, "");
2877 		    }
2878 		    if (u_spur_chance(u) > 0) {
2879 			tbprintf(buf,
2880 "%s%d%% chance of spur, if within %d of road.%s",
2881 				 help_indent(4), u_spur_chance(u),
2882 				 u_spur_range(u), help_newline());
2883 		    }
2884 		    if (u_road_to_edge_chance(u) > 0) {
2885 			tbprintf(buf, "%s%d%% chance of road to edge.%s",
2886 				 help_indent(4), u_road_to_edge_chance(u),
2887 				 help_newline());
2888 		    }
2889 		}
2890 		for_all_terrain_types(t) {
2891 		    if (t_subtype(t) == cellsubtype
2892 			&& tt_table_row_not_default(t, tt_road_into_chance,
2893 						    table_default(
2894 						      tt_road_into_chance))) {
2895 			tbprintf(buf, "%sRouting of road from %s: ",
2896 				 help_indent(2), t_type_name(t));
2897 			/* Note: this is actually a weight, not a
2898 			   percent chance. */
2899 			tt_table_row_desc(buf, t, tt_road_into_chance,
2900 					  tb_value_desc /*, "to" */);
2901 			tbcatline(buf, ".");
2902 		    }
2903 		}
2904 		if (g_edge_road_density() > 0) {
2905 		    tbcat(buf, help_indent(2));
2906 		    tb_percent_100th_desc(buf, g_edge_road_density());
2907 		    tbprintf(buf, "%sof edge gets road run to another edge.%s",
2908 			     help_indent(2), help_newline());
2909 		}
2910 		break;
2911 	      case K_MAKE_WEATHER:
2912 		tbcat(buf, "Weather:");
2913 		describe_synth_run(buf, methkey);
2914 		tbcatline(buf, "");
2915 		break;
2916 	      case K_NAME_GEOGRAPHICAL_FEATURES:
2917 		tbcat(buf, "Names for geographical features:");
2918 		describe_synth_run(buf, methkey);
2919 		tbcatline(buf, "");
2920 		break;
2921 	      case K_NAME_UNITS_RANDOMLY:
2922 		tbcat(buf, "Names for units:");
2923 		describe_synth_run(buf, methkey);
2924 		tbcatline(buf, "");
2925 		break;
2926 	      default:
2927 		tbprintf(buf, "%s:", c_string(method));
2928 		describe_synth_run(buf, methkey);
2929 		tbcatline(buf, "");
2930 		break;
2931 	    }
2932 	} else if (consp(method)) {
2933 	    /* (what?) */
2934 	}
2935     }
2936     tbcatline(buf, "");
2937     if (g_side_lib() != lispnil)
2938       tbprintf(buf, "%d choices in side library.%s", length(g_side_lib()),
2939 	       help_newline());
2940 }
2941 
2942 static void
describe_synth_run(TextBuffer * buf,int methkey)2943 describe_synth_run(TextBuffer *buf, int methkey)
2944 {
2945     int calls, runs;
2946 
2947     if (get_synth_method_uses(methkey, &calls, &runs)) {
2948 	if (calls > 0) {
2949 	    if (calls == 1 && runs == 1) {
2950 		tbcat(buf, " (was run)");
2951 	    } else if (calls == 1 && runs == 0) {
2952 		tbcat(buf, " (was not run)");
2953 	    } else {
2954 		tbprintf(buf, " (was called %d times, was run %d times)",
2955 			calls, runs);
2956 	    }
2957 	} else {
2958 	    tbcat(buf, " (not attempted)");
2959 	}
2960     } else {
2961 	tbcat(buf, " (? ? ?)");
2962     }
2963 }
2964 
2965 static void
describe_world(int arg,char * key,TextBuffer * buf)2966 describe_world(int arg, char *key, TextBuffer *buf)
2967 {
2968     tbprintf(buf, "World circumference: %d.%s", world.circumference,
2969 	     help_newline());
2970     tbcatline(buf, "");
2971     tbprintf(buf, "Area in world: %d wide x %d high", area.width, area.height);
2972     if (area.width == world.circumference)
2973       tbprintf(buf, " (wraps completely around world).%s", help_indent(2));
2974     else
2975       tbprintf(buf, ".%s", help_indent(2));
2976 
2977     tbprintf(buf, "Latitude: %d.%sLongitude: %d.\n", area.latitude,
2978 	     help_indent(2), area.longitude);
2979     tbcatline(buf, "");
2980     if (elevations_defined()) {
2981 	tbprintf(buf, "Elevations range from %d to %d, averaging %d.%s",
2982 		 area.minelev, area.maxelev, area.avgelev, help_newline());
2983 	tbprintf(buf, "Cells are %d elevation units across.%s",
2984 		 area.cellwidth, help_newline());
2985     }
2986     if (world.yearlength > 1) {
2987 	tbprintf(buf, "Length of year: %d turns.%s", world.yearlength,
2988 		 help_newline());
2989     }
2990     if (world.daylength != 1) {
2991 	tbprintf(buf, "Length of day: %d turns.%s", world.daylength,
2992 		 help_newline());
2993 	tbprintf(buf, "Percentage daylight: %d%%.%s", world.daylight_fraction,
2994 		 help_newline());
2995 	tbprintf(buf, "Percentage twilight: %d%%.%s",
2996 		 world.twilight_fraction - world.daylight_fraction,
2997 		 help_newline());
2998     }
2999     if (area.temp_year != lispnil) {
3000 	/* (should describe temperature year cycle here) */
3001     }
3002 #ifdef DESIGNERS
3003     if (numdesigners > 0) {
3004 	tbcatline(buf, "FOR DESIGNERS:");
3005 	tbprintf(buf, "Area projection is %d.%s", area.projection,
3006 		 help_newline());
3007 	tbprintf(buf, "Default contour line color is \"%s\".%s",
3008 		 g_contour_color(), help_newline());
3009 	tbprintf(buf, "Default country border color is \"%s\".%s",
3010 		 g_country_border_color(), help_newline());
3011 	tbprintf(buf, "Default frontline color is \"%s\".%s",
3012 		 g_frontline_color(), help_newline());
3013 	tbprintf(buf, "Default grid color is \"%s\".%s",
3014 		 g_grid_color(), help_newline());
3015 	tbprintf(buf, "Default meridian color is \"%s\".%s",
3016 		 g_meridian_color(), help_newline());
3017 	tbprintf(buf, "Default shoreline color is \"%s\".%s",
3018 		 g_shoreline_color(), help_newline());
3019 	tbprintf(buf, "Default unit name color is \"%s\".%s",
3020 		 g_unit_name_color(), help_newline());
3021 	tbprintf(buf, "Default unseen color is \"%s\".%s",
3022 		 g_unseen_color(), help_newline());
3023     }
3024 #endif /* DESIGNERS */
3025 }
3026 
3027 /* The following globals don't make sense for online help, but are listed
3028    here so that a cross-check of *.def and online help doesn't list them
3029    as undocumented: g_random_state g_run_serial_number g_turn. */
3030 
3031 /* This describes a command (from cmd.def et al) in a way that all
3032    interfaces can use. */
3033 
3034 void
describe_command(int ch,char * name,char * help,int onechar,TextBuffer * buf)3035 describe_command (int ch, char *name, char *help, int onechar, TextBuffer *buf)
3036 {
3037     if (onechar && ch != '\0') {
3038 	if (ch < ' ' || ch > '~') {
3039 	    tbprintf(buf, "^%c  ", (ch ^ 0x40));
3040 	} else if (ch == ' ') {
3041 	    tbprintf(buf, "'%c' ", ch);
3042 	} else {
3043 	    tbprintf(buf, " %c  ", ch);
3044 	}
3045     } else if (!onechar && ch == '\0') {
3046 	tbcat(buf, "\"");
3047 	tbcat(buf, name);
3048 	tbcat(buf, "\"");
3049     } else
3050       return;
3051     tbcat(buf, " ");
3052     tbcat(buf, help);
3053     tbcatline(buf, "");
3054 }
3055 
3056 static int
u_property_not_default(int (* fn)(int i),int dflt)3057 u_property_not_default(int (*fn)(int i), int dflt)
3058 {
3059     int u, val;
3060 
3061     for_all_unit_types(u) {
3062 	val = (*fn)(u);
3063 	if (val != dflt)
3064 	  return TRUE;
3065     }
3066     return FALSE;
3067 }
3068 
3069 static int
t_property_not_default(int (* fn)(int i),int dflt)3070 t_property_not_default(int (*fn)(int i), int dflt)
3071 {
3072     int t, val;
3073 
3074     for_all_terrain_types(t) {
3075 	val = (*fn)(t);
3076 	if (val != dflt)
3077 	  return TRUE;
3078     }
3079     return FALSE;
3080 }
3081 
3082 static int
uu_table_row_not_default(int u,int (* fn)(int i,int j),int dflt)3083 uu_table_row_not_default(int u, int (*fn)(int i, int j), int dflt)
3084 {
3085     int u2, val2;
3086 
3087     for_all_unit_types(u2) {
3088 	val2 = (*fn)(u, u2);
3089 	if (val2 != dflt)
3090 	  return TRUE;
3091     }
3092     return FALSE;
3093 }
3094 
3095 static int
ut_table_row_not_default(int u,int (* fn)(int i,int j),int dflt)3096 ut_table_row_not_default(int u, int (*fn)(int i, int j), int dflt)
3097 {
3098     int t, val2;
3099 
3100     for_all_terrain_types(t) {
3101 	val2 = (*fn)(u, t);
3102 	if (val2 != dflt)
3103 	  return TRUE;
3104     }
3105     return FALSE;
3106 }
3107 
3108 static int
um_table_row_not_default(int u,int (* fn)(int i,int j),int dflt)3109 um_table_row_not_default(int u, int (*fn)(int i, int j), int dflt)
3110 {
3111     int m, val2;
3112 
3113     for_all_material_types(m) {
3114 	val2 = (*fn)(u, m);
3115 	if (val2 != dflt)
3116 	  return TRUE;
3117     }
3118     return FALSE;
3119 }
3120 
3121 static int
ua_table_row_not_default(int u,int (* fn)(int i,int j),int dflt)3122 ua_table_row_not_default(int u, int (*fn)(int i, int j), int dflt)
3123 {
3124     int a, val2;
3125 
3126     for_all_advance_types(a) {
3127 	val2 = (*fn)(u, a);
3128 	if (val2 != dflt)
3129 	  return TRUE;
3130     }
3131     return FALSE;
3132 }
3133 
3134 static int
tt_table_row_not_default(int t1,int (* fn)(int i,int j),int dflt)3135 tt_table_row_not_default(int t1, int (*fn)(int i, int j), int dflt)
3136 {
3137     int t2, val2;
3138 
3139     for_all_terrain_types(t2) {
3140 	val2 = (*fn)(t1, t2);
3141 	if (val2 != dflt)
3142 	  return TRUE;
3143     }
3144     return FALSE;
3145 }
3146 
3147 #if 0 /* not used currently */
3148 static int
3149 tm_table_row_not_default(int t, int (*fn)(int i, int j), int dflt)
3150 {
3151     int m, val2;
3152 
3153     for_all_material_types(m) {
3154 	val2 = (*fn)(t, m);
3155 	if (val2 != dflt)
3156 	  return TRUE;
3157     }
3158     return FALSE;
3159 }
3160 #endif
3161 
3162 static int
aa_table_row_not_default(int a1,int (* fn)(int i,int j),int dflt)3163 aa_table_row_not_default(int a1, int (*fn)(int i, int j), int dflt)
3164 {
3165     int a2, val2;
3166 
3167     for_all_advance_types(a2) {
3168 	val2 = (*fn)(a1, a2);
3169 	if (val2 != dflt)
3170 	  return TRUE;
3171     }
3172     return FALSE;
3173 }
3174 
3175 static int
uu_table_column_not_default(int u,int (* fn)(int i,int j),int dflt)3176 uu_table_column_not_default(int u, int (*fn)(int i, int j), int dflt)
3177 {
3178     int u2, val2;
3179 
3180     for_all_unit_types(u2) {
3181 	val2 = (*fn)(u2, u);
3182 	if (val2 != dflt)
3183 	  return TRUE;
3184     }
3185     return FALSE;
3186 }
3187 
3188 static int
aa_table_column_not_default(int a1,int (* fn)(int i,int j),int dflt)3189 aa_table_column_not_default(int a1, int (*fn)(int i, int j), int dflt)
3190 {
3191     int a2, val2;
3192 
3193     for_all_advance_types(a2) {
3194 	val2 = (*fn)(a2, a1);
3195 	if (val2 != dflt)
3196 	  return TRUE;
3197     }
3198     return FALSE;
3199 }
3200 
3201 struct histo {
3202     int val, num;
3203 };
3204 
3205 /* This compare will sort histogram entries in *reverse* order
3206    (most common values first). */
3207 
3208 static int
histogram_compare(CONST void * h1,CONST void * h2)3209 histogram_compare(CONST void *h1, CONST void *h2)
3210 {
3211     if (((struct histo *) h2)->num != ((struct histo *) h1)->num) {
3212     	return ((struct histo *) h2)->num - ((struct histo *) h1)->num;
3213     } else {
3214     	return ((struct histo *) h2)->val - ((struct histo *) h1)->val;
3215     }
3216 }
3217 
3218 static struct histo *u_histogram;
3219 
3220 static void
u_property_desc(TextBuffer * buf,int (* fn)(int i),void (* formatter)(TextBuffer * buf,int val))3221 u_property_desc(TextBuffer *buf, int (*fn)(int i),
3222 		void (*formatter)(TextBuffer *buf, int val))
3223 {
3224     int val, u, val2, constant = TRUE, found;
3225     int i, numentries, first;
3226 
3227     if (formatter == NULL)
3228       formatter = tb_value_desc;
3229     if (u_histogram == NULL)
3230       u_histogram =
3231 	(struct histo *) xmalloc(numutypes * sizeof(struct histo));
3232     /* Compute a histogram of all the values for the given property. */
3233     numentries = 0;
3234     val = (*fn)(0);
3235     u_histogram[numentries].val = val;
3236     u_histogram[numentries].num = 1;
3237     ++numentries;
3238     for_all_unit_types(u) {
3239 	val2 = (*fn)(u);
3240 	if (val2 == val) {
3241 	    ++(u_histogram[0].num);
3242 	} else {
3243 	    constant = FALSE;
3244 	    found = FALSE;
3245 	    for (i = 1; i < numentries; ++i) {
3246 		if (val2 == u_histogram[i].val) {
3247 		    ++(u_histogram[i].num);
3248 		    found = TRUE;
3249 		    break;
3250 		}
3251 	    }
3252 	    if (!found) {
3253 		u_histogram[numentries].val = val2;
3254 		u_histogram[numentries].num = 1;
3255 		++numentries;
3256 	    }
3257 	}
3258     }
3259     /* The constant table/row case is easily disposed of. */
3260     if (constant) {
3261 	(*formatter)(buf, val);
3262 	tbcat(buf, " for all unit types");
3263     	return;
3264     }
3265     /* Not a constant row; sort the histogram and compose a description. */
3266     qsort(u_histogram, numentries, sizeof(struct histo), histogram_compare);
3267     /* Show a "by default" clause if at least half of the entries are all
3268        the same. */
3269     if (u_histogram[0].num * 2 >= numutypes) {
3270 	(*formatter)(buf, u_histogram[0].val);
3271 	tbcat(buf, " by default");
3272     	i = 1;
3273     } else {
3274     	i = 0;
3275     }
3276     for (; i < numentries; ++i) {
3277 	if (i > 0)
3278 	  tbcat(buf, ", ");
3279 	(*formatter)(buf, u_histogram[i].val);
3280 	tbcat(buf, " for ");
3281 	first = TRUE;
3282 	for_all_unit_types(u) {
3283 	    if ((*fn)(u) == u_histogram[i].val) {
3284 		if (!first)
3285 		  /* For this and similar situations, we need a space
3286 		     in addition to the comma, so interface elements
3287 		     (such as the Mac's text display) can decide to
3288 		     add line breaks. */
3289 		  tbcat(buf, ", ");
3290 		else
3291 		  first = FALSE;
3292 		tbcat(buf, u_type_name(u));
3293 	    }
3294 	}
3295     }
3296 }
3297 
3298 static struct histo *t_histogram;
3299 
3300 static void
t_property_desc(TextBuffer * buf,int (* fn)(int i),void (* formatter)(TextBuffer * buf,int val))3301 t_property_desc(TextBuffer *buf, int (*fn)(int i),
3302 		void (*formatter)(TextBuffer *buf, int val))
3303 {
3304     int val, t, val2, constant = TRUE, found;
3305     int i, numentries, first;
3306 
3307     if (formatter == NULL)
3308       formatter = tb_value_desc;
3309     if (t_histogram == NULL)
3310       t_histogram =
3311 	(struct histo *) xmalloc(numttypes * sizeof(struct histo));
3312     /* Compute a histogram of all the values for the given property. */
3313     numentries = 0;
3314     val = (*fn)(0);
3315     t_histogram[numentries].val = val;
3316     t_histogram[numentries].num = 1;
3317     ++numentries;
3318     for_all_terrain_types(t) {
3319 	val2 = (*fn)(t);
3320 	if (val2 == val) {
3321 	    ++(t_histogram[0].num);
3322 	} else {
3323 	    constant = FALSE;
3324 	    found = FALSE;
3325 	    for (i = 1; i < numentries; ++i) {
3326 		if (val2 == t_histogram[i].val) {
3327 		    ++(t_histogram[i].num);
3328 		    found = TRUE;
3329 		    break;
3330 		}
3331 	    }
3332 	    if (!found) {
3333 		t_histogram[numentries].val = val2;
3334 		t_histogram[numentries].num = 1;
3335 		++numentries;
3336 	    }
3337 	}
3338     }
3339     /* The constant table/row case is easily disposed of. */
3340     if (constant) {
3341 	(*formatter)(buf, val);
3342 	tbcat(buf, " for all terrain types");
3343     	return;
3344     }
3345     /* Not a constant row; sort the histogram and compose a description. */
3346     qsort(t_histogram, numentries, sizeof(struct histo), histogram_compare);
3347     /* Show a "by default" clause if at least half of the entries are all
3348        the same. */
3349     if (t_histogram[0].num * 2 >= numttypes) {
3350 	(*formatter)(buf, t_histogram[0].val);
3351 	tbcat(buf, " by default");
3352     	i = 1;
3353     } else {
3354     	i = 0;
3355     }
3356     for (; i < numentries; ++i) {
3357 	if (i > 0)
3358 	  tbcat(buf, ", ");
3359 	(*formatter)(buf, t_histogram[i].val);
3360 	tbcat(buf, " for ");
3361 	first = TRUE;
3362 	for_all_terrain_types(t) {
3363 	    if ((*fn)(t) == t_histogram[i].val) {
3364 		if (!first)
3365 		  tbcat(buf, ", ");
3366 		else
3367 		  first = FALSE;
3368 		tbcat(buf, t_type_name(t));
3369 	    }
3370 	}
3371     }
3372 }
3373 
3374 /* Generate a textual description of a single unit's interaction with all other
3375    unit types wrt a given table. */
3376 
3377 static void
uu_table_row_desc(TextBuffer * buf,int u,int (* fn)(int i,int j),void (* formatter)(TextBuffer * buf,int val),char * connect)3378 uu_table_row_desc(TextBuffer *buf, int u, int (*fn)(int i, int j),
3379 		  void (*formatter)(TextBuffer *buf, int val), char *connect)
3380 {
3381     uu_table_rowcol_desc(buf, u, fn, formatter, connect, 0);
3382 }
3383 
3384 static void
uu_table_column_desc(TextBuffer * buf,int u,int (* fn)(int i,int j),void (* formatter)(TextBuffer * buf,int val),char * connect)3385 uu_table_column_desc(TextBuffer *buf, int u, int (*fn)(int i, int j),
3386 		     void (*formatter)(TextBuffer *buf, int val),
3387 		     char *connect)
3388 {
3389     uu_table_rowcol_desc(buf, u, fn, formatter, connect, 1);
3390 }
3391 
3392 static void
uu_table_rowcol_desc(TextBuffer * buf,int u,int (* fn)(int i,int j),void (* formatter)(TextBuffer * buf,int val),char * connect,int rowcol)3393 uu_table_rowcol_desc(TextBuffer *buf, int u, int (*fn)(int i, int j),
3394 		     void (*formatter)(TextBuffer *buf, int val),
3395 		     char *connect, int rowcol)
3396 {
3397     int val, val2, u2, constant = TRUE, found;
3398     int i, numentries, first;
3399 
3400     if (formatter == NULL)
3401       formatter = tb_value_desc;
3402     if (empty_string(connect))
3403       connect = "for";
3404     if (u_histogram == NULL)
3405       u_histogram =
3406 	(struct histo *) xmalloc(numutypes * sizeof(struct histo));
3407     val = (rowcol ? (*fn)(0, u) : (*fn)(u, 0));
3408     /* Compute a histogram of all the values in the row of the table. */
3409     numentries = 0;
3410     u_histogram[numentries].val = val;
3411     u_histogram[numentries].num = 1;
3412     ++numentries;
3413     for_all_unit_types(u2) {
3414 	val2 = (rowcol ? (*fn)(u2, u) : (*fn)(u, u2));
3415 	if (val2 == val) {
3416 	    ++(u_histogram[0].num);
3417 	} else {
3418 	    constant = FALSE;
3419 	    found = FALSE;
3420 	    for (i = 1; i < numentries; ++i) {
3421 		if (val2 == u_histogram[i].val) {
3422 		    ++(u_histogram[i].num);
3423 		    found = TRUE;
3424 		    break;
3425 		}
3426 	    }
3427 	    if (!found) {
3428 		u_histogram[numentries].val = val2;
3429 		u_histogram[numentries].num = 1;
3430 		++numentries;
3431 	    }
3432 	}
3433     }
3434     /* The constant table/row case is easily disposed of. */
3435     if (constant) {
3436 	(*formatter)(buf, val);
3437 	tbprintf(buf, " %s all unit types", connect);
3438     	return;
3439     }
3440     /* Not a constant row; sort the histogram and compose a description. */
3441     qsort(u_histogram, numentries, sizeof(struct histo), histogram_compare);
3442     /* Show a "by default" clause if at least half of the entries are all
3443        the same. */
3444     if (u_histogram[0].num * 2 >= numutypes) {
3445 	(*formatter)(buf, u_histogram[0].val);
3446 	tbcat(buf, " by default");
3447     	i = 1;
3448     } else {
3449     	i = 0;
3450     }
3451     for (; i < numentries; ++i) {
3452 	if (i > 0)
3453 	  tbcat(buf, ", ");
3454 	(*formatter)(buf, u_histogram[i].val);
3455 	tbprintf(buf, " %s ", connect);
3456 	first = TRUE;
3457 	for_all_unit_types(u2) {
3458 	    val2 = (rowcol ? (*fn)(u2, u) : (*fn)(u, u2));
3459 	    if (val2 == u_histogram[i].val) {
3460 		if (!first)
3461 		  tbcat(buf, ", ");
3462 		else
3463 		  first = FALSE;
3464 		tbcat(buf, u_type_name(u2));
3465 	    }
3466 	}
3467     }
3468 }
3469 
3470 /* Generate a textual description of a single unit's interaction with all
3471    terrain types wrt a given table. */
3472 
3473 static void
ut_table_row_desc(TextBuffer * buf,int u,int (* fn)(int i,int j),void (* formatter)(TextBuffer * buf,int val),char * connect)3474 ut_table_row_desc(TextBuffer *buf, int u, int (*fn)(int i, int j),
3475 		  void (*formatter)(TextBuffer *buf, int val), char *connect)
3476 {
3477     int val = (*fn)(u, 0), val2, t, constant = TRUE, found;
3478     int i, numentries, first;
3479 
3480     if (formatter == NULL)
3481       formatter = tb_value_desc;
3482     if (empty_string(connect))
3483       connect = "for";
3484     if (u_histogram == NULL)
3485       u_histogram =
3486 	(struct histo *) xmalloc(numutypes * sizeof(struct histo));
3487     /* Compute a histogram of all the values in the row of the table. */
3488     numentries = 0;
3489     u_histogram[numentries].val = val;
3490     u_histogram[numentries].num = 1;
3491     ++numentries;
3492     for_all_terrain_types(t) {
3493 	val2 = (*fn)(u, t);
3494 	if (val2 == val) {
3495 	    ++(u_histogram[0].num);
3496 	} else {
3497 	    constant = FALSE;
3498 	    found = FALSE;
3499 	    for (i = 1; i < numentries; ++i) {
3500 		if (val2 == u_histogram[i].val) {
3501 		    ++(u_histogram[i].num);
3502 		    found = TRUE;
3503 		    break;
3504 		}
3505 	    }
3506 	    if (!found) {
3507 		u_histogram[numentries].val = val2;
3508 		u_histogram[numentries].num = 1;
3509 		++numentries;
3510 	    }
3511 	}
3512     }
3513     /* The constant table/row case is easily disposed of. */
3514     if (constant) {
3515 	(*formatter)(buf, val);
3516 	tbprintf(buf, " %s all terrain types", connect);
3517     	return;
3518     }
3519     /* Not a constant row; sort the histogram and compose a description. */
3520     qsort(u_histogram, numentries, sizeof(struct histo), histogram_compare);
3521     /* Show a "by default" clause if at least half of the entries are all
3522        the same. */
3523     if (u_histogram[0].num * 2 >= numttypes) {
3524 	(*formatter)(buf, u_histogram[0].val);
3525 	tbcat(buf, " by default");
3526     	i = 1;
3527     } else {
3528     	i = 0;
3529     }
3530     for (; i < numentries; ++i) {
3531 	if (i > 0)
3532 	  tbcat(buf, ", ");
3533 	(*formatter)(buf, u_histogram[i].val);
3534 	tbprintf(buf, " %s ", connect);
3535 	first = TRUE;
3536 	for_all_terrain_types(t) {
3537 	    if ((*fn)(u, t) == u_histogram[i].val) {
3538 		if (!first)
3539 		  tbcat(buf, ", ");
3540 		else
3541 		  first = FALSE;
3542 		tbcat(buf, t_type_name(t));
3543 	    }
3544 	}
3545     }
3546 }
3547 
3548 static void
um_table_row_desc(TextBuffer * buf,int u,int (* fn)(int i,int j),void (* formatter)(TextBuffer * buf,int val))3549 um_table_row_desc(TextBuffer *buf, int u, int (*fn)(int i, int j),
3550 		  void (*formatter)(TextBuffer *buf, int val))
3551 {
3552     int val = (*fn)(u, 0), val2, m, constant = TRUE, found;
3553     int i, numentries, first;
3554     char *connect = "vs";
3555 
3556     if (formatter == NULL)
3557       formatter = tb_value_desc;
3558     if (u_histogram == NULL)
3559       u_histogram =
3560 	(struct histo *) xmalloc(numutypes * sizeof(struct histo));
3561     /* Compute a histogram of all the values in the row of the table. */
3562     numentries = 0;
3563     u_histogram[numentries].val = val;
3564     u_histogram[numentries].num = 1;
3565     ++numentries;
3566     for_all_material_types(m) {
3567 	val2 = (*fn)(u, m);
3568 	if (val2 == val) {
3569 	    ++(u_histogram[0].num);
3570 	} else {
3571 	    constant = FALSE;
3572 	    found = FALSE;
3573 	    for (i = 1; i < numentries; ++i) {
3574 		if (val2 == u_histogram[i].val) {
3575 		    ++(u_histogram[i].num);
3576 		    found = TRUE;
3577 		    break;
3578 		}
3579 	    }
3580 	    if (!found) {
3581 		u_histogram[numentries].val = val2;
3582 		u_histogram[numentries].num = 1;
3583 		++numentries;
3584 	    }
3585 	}
3586     }
3587     /* The constant table/row case is easily disposed of. */
3588     if (constant) {
3589 	(*formatter)(buf, val);
3590 	tbprintf(buf, " %s all material types", connect);
3591     	return;
3592     }
3593     /* Not a constant row; sort the histogram and compose a description. */
3594     qsort(u_histogram, numentries, sizeof(struct histo), histogram_compare);
3595     /* Show a "by default" clause if at least half of the entries are all
3596        the same. */
3597     if (u_histogram[0].num * 2 >= nummtypes) {
3598 	(*formatter)(buf, u_histogram[0].val);
3599 	tbcat(buf, " by default");
3600     	i = 1;
3601     } else {
3602     	i = 0;
3603     }
3604     for (; i < numentries; ++i) {
3605 	if (i > 0)
3606 	  tbcat(buf, ", ");
3607 	(*formatter)(buf, u_histogram[i].val);
3608 	tbprintf(buf, " %s ", connect);
3609 	first = TRUE;
3610 	for_all_material_types(m) {
3611 	    if ((*fn)(u, m) == u_histogram[i].val) {
3612 		if (!first)
3613 		  tbcat(buf, ", ");
3614 		else
3615 		  first = FALSE;
3616 		tbcat(buf, m_type_name(m));
3617 	    }
3618 	}
3619     }
3620 }
3621 
3622 static void
ua_table_row_desc(TextBuffer * buf,int u,int (* fn)(int i,int j),void (* formatter)(TextBuffer * buf,int val))3623 ua_table_row_desc(TextBuffer *buf, int u, int (*fn)(int i, int j),
3624 		  void (*formatter)(TextBuffer *buf, int val))
3625 {
3626     int val = (*fn)(u, 0), val2, a, constant = TRUE, found;
3627     int i, numentries, first;
3628     char *connect = "vs";
3629 
3630     if (formatter == NULL)
3631       formatter = tb_value_desc;
3632     if (u_histogram == NULL)
3633       u_histogram =
3634 	(struct histo *) xmalloc(numutypes * sizeof(struct histo));
3635     /* Compute a histogram of all the values in the row of the table. */
3636     numentries = 0;
3637     u_histogram[numentries].val = val;
3638     u_histogram[numentries].num = 1;
3639     ++numentries;
3640     for_all_advance_types(a) {
3641 	val2 = (*fn)(u, a);
3642 	if (val2 == val) {
3643 	    ++(u_histogram[0].num);
3644 	} else {
3645 	    constant = FALSE;
3646 	    found = FALSE;
3647 	    for (i = 1; i < numentries; ++i) {
3648 		if (val2 == u_histogram[i].val) {
3649 		    ++(u_histogram[i].num);
3650 		    found = TRUE;
3651 		    break;
3652 		}
3653 	    }
3654 	    if (!found) {
3655 		u_histogram[numentries].val = val2;
3656 		u_histogram[numentries].num = 1;
3657 		++numentries;
3658 	    }
3659 	}
3660     }
3661     /* The constant table/row case is easily disposed of. */
3662     if (constant) {
3663 	(*formatter)(buf, val);
3664 	tbprintf(buf, " %s all advance types", connect);
3665     	return;
3666     }
3667     /* Not a constant row; sort the histogram and compose a description. */
3668     qsort(u_histogram, numentries, sizeof(struct histo), histogram_compare);
3669     /* Show a "by default" clause if at least half of the entries are all
3670        the same. */
3671     if (u_histogram[0].num * 2 >= nummtypes) {
3672 	(*formatter)(buf, u_histogram[0].val);
3673 	tbcat(buf, " by default");
3674     	i = 1;
3675     } else {
3676     	i = 0;
3677     }
3678     for (; i < numentries; ++i) {
3679 	if (i > 0)
3680 	  tbcat(buf, ", ");
3681 	(*formatter)(buf, u_histogram[i].val);
3682 	tbprintf(buf, " %s ", connect);
3683 	first = TRUE;
3684 	for_all_advance_types(a) {
3685 	    if ((*fn)(u, a) == u_histogram[i].val) {
3686 		if (!first)
3687 		  tbcat(buf, ", ");
3688 		else
3689 		  first = FALSE;
3690 		tbcat(buf, a_type_name(a));
3691 	    }
3692 	}
3693     }
3694 }
3695 
3696 static void
tt_table_row_desc(TextBuffer * buf,int t0,int (* fn)(int i,int j),void (* formatter)(TextBuffer * buf,int val))3697 tt_table_row_desc(TextBuffer *buf, int t0, int (*fn)(int i, int j),
3698 		  void (*formatter)(TextBuffer *buf, int val))
3699 {
3700     int val = (*fn)(t0, 0), val2, t, constant = TRUE, found;
3701     int i, numentries, first;
3702 
3703     if (formatter == NULL)
3704       formatter = tb_value_desc;
3705     if (t_histogram == NULL)
3706       t_histogram =
3707 	(struct histo *) xmalloc(numttypes * sizeof(struct histo));
3708     /* Compute a histogram of all the values in the row of the table. */
3709     numentries = 0;
3710     t_histogram[numentries].val = val;
3711     t_histogram[numentries].num = 1;
3712     ++numentries;
3713     for_all_terrain_types(t) {
3714 	val2 = (*fn)(t0, t);
3715 	if (val2 == val) {
3716 	    ++(t_histogram[0].num);
3717 	} else {
3718 	    constant = FALSE;
3719 	    found = FALSE;
3720 	    for (i = 1; i < numentries; ++i) {
3721 		if (val2 == t_histogram[i].val) {
3722 		    ++(t_histogram[i].num);
3723 		    found = TRUE;
3724 		    break;
3725 		}
3726 	    }
3727 	    if (!found) {
3728 		t_histogram[numentries].val = val2;
3729 		t_histogram[numentries].num = 1;
3730 		++numentries;
3731 	    }
3732 	}
3733     }
3734     /* The constant table/row case is easily disposed of. */
3735     if (constant) {
3736 	(*formatter)(buf, val);
3737 	tbcat(buf, " for all terrain types");
3738     	return;
3739     }
3740     /* Not a constant row; sort the histogram and compose a description. */
3741     qsort(t_histogram, numentries, sizeof(struct histo), histogram_compare);
3742     /* Show a "by default" clause if at least half of the entries are all
3743        the same. */
3744     if (t_histogram[0].num * 2 >= numttypes) {
3745 	(*formatter)(buf, t_histogram[0].val);
3746 	tbcat(buf, " by default");
3747     	i = 1;
3748     } else {
3749     	i = 0;
3750     }
3751     for (; i < numentries; ++i) {
3752 	if (i > 0)
3753 	  tbcat(buf, ", ");
3754 	(*formatter)(buf, t_histogram[i].val);
3755 	tbcat(buf, " vs ");
3756 	first = TRUE;
3757 	for_all_terrain_types(t) {
3758 	    if ((*fn)(t0, t) == t_histogram[i].val) {
3759 		if (!first)
3760 		  tbcat(buf, ", ");
3761 		else
3762 		  first = FALSE;
3763 		tbcat(buf, t_type_name(t));
3764 	    }
3765 	}
3766     }
3767 }
3768 
3769 static void
aa_table_row_desc(TextBuffer * buf,int a0,int (* fn)(int i,int j),void (* formatter)(TextBuffer * buf,int val))3770 aa_table_row_desc(TextBuffer *buf, int a0, int (*fn)(int i, int j),
3771 		  void (*formatter)(TextBuffer *buf, int val))
3772 {
3773     aa_table_rowcol_desc(buf, a0, fn, formatter, 0);
3774 }
3775 
3776 static void
aa_table_column_desc(TextBuffer * buf,int a1,int (* fn)(int i,int j),void (* formatter)(TextBuffer * buf,int val))3777 aa_table_column_desc(TextBuffer *buf, int a1, int (*fn)(int i, int j),
3778 		  void (*formatter)(TextBuffer *buf, int val))
3779 {
3780     aa_table_rowcol_desc(buf, a1, fn, formatter, 1);
3781 }
3782 
3783 static struct histo *a_histogram;
3784 
3785 static void
aa_table_rowcol_desc(TextBuffer * buf,int a1,int (* fn)(int i,int j),void (* formatter)(TextBuffer * buf,int val),int rowcol)3786 aa_table_rowcol_desc(TextBuffer *buf, int a1, int (*fn)(int i, int j),
3787 		     void (*formatter)(TextBuffer *buf, int val), int rowcol)
3788 {
3789     int val = (*fn)(0, a1), val2, a, constant = TRUE, found;
3790     int i, numentries, first;
3791 
3792     if (formatter == NULL)
3793       formatter = tb_value_desc;
3794     if (a_histogram == NULL)
3795       a_histogram =
3796 	(struct histo *) xmalloc(numatypes * sizeof(struct histo));
3797     val = (rowcol ? (*fn)(0, a1) : (*fn)(a1, 0));
3798     /* Compute a histogram of all the values in the row of the table. */
3799     numentries = 0;
3800     a_histogram[numentries].val = val;
3801     a_histogram[numentries].num = 1;
3802     ++numentries;
3803     for_all_advance_types(a) {
3804 	val2 = (rowcol ? (*fn)(a, a1) : (*fn)(a1, a));
3805 	if (val2 == val) {
3806 	    ++(a_histogram[0].num);
3807 	} else {
3808 	    constant = FALSE;
3809 	    found = FALSE;
3810 	    for (i = 1; i < numentries; ++i) {
3811 		if (val2 == a_histogram[i].val) {
3812 		    ++(a_histogram[i].num);
3813 		    found = TRUE;
3814 		    break;
3815 		}
3816 	    }
3817 	    if (!found) {
3818 		a_histogram[numentries].val = val2;
3819 		a_histogram[numentries].num = 1;
3820 		++numentries;
3821 	    }
3822 	}
3823     }
3824     /* The constant table/row case is easily disposed of. */
3825     if (constant) {
3826 	(*formatter)(buf, val);
3827 	tbcat(buf, " for all advance types");
3828     	return;
3829     }
3830     /* Not a constant column; sort the histogram and compose a description. */
3831     qsort(a_histogram, numentries, sizeof(struct histo), histogram_compare);
3832     for (i = 0; i < numentries; ++i) {
3833 	/* Skip over advances that are not needed. */
3834     	if (a_histogram[i].val == 0)
3835     	    continue;
3836 	first = TRUE;
3837 	for_all_advance_types(a) {
3838 	    val2 = (rowcol ? (*fn)(a, a1) : (*fn)(a1, a));
3839 	    if (val2 == a_histogram[i].val) {
3840 		if (!first)
3841 		  tbcat(buf, ", ");
3842 		else
3843 		  first = FALSE;
3844 		tbcat(buf, a_type_name(a));
3845 	    }
3846 	}
3847     }
3848 }
3849 
3850 #if 0 /* not currently used */
3851 /* A simple table-printing utility. Blanks out default values so they don't
3852    clutter the table. */
3853 
3854 static void
3855 append_number(buf, value, dflt)
3856 TextBuffer *buf;
3857 int value, dflt;
3858 {
3859     if (value != dflt) {
3860 	tbprintf(buf, "%5d ", value);
3861     } else {
3862 	tbprintf(buf, "%s", help_indent(6));
3863     }
3864 }
3865 #endif
3866 
3867 static void
append_help_phrase(TextBuffer * buf,char * phrase)3868 append_help_phrase(TextBuffer *buf, char *phrase)
3869 {
3870     if (empty_string(phrase))
3871       return;
3872 
3873     /* Extra new line makes display less cluttered. */
3874     tbcat(buf, "----- ");
3875     tbcat(buf, phrase);
3876     tbprintf(buf, " -----%s%s", help_newline(), help_newline());
3877 }
3878 
3879 static void
append_notes(TextBuffer * buf,Obj * notes)3880 append_notes(TextBuffer *buf, Obj *notes)
3881 {
3882     char *notestr;
3883     Obj *rest;
3884 
3885     if (stringp(notes)) {
3886 	notestr = c_string(notes);
3887 	if (strlen(notestr) > 0) {
3888 	    tbcat(buf, notestr);
3889 	    tbcat(buf, " ");
3890 	} else {
3891 	    tbcatline(buf, "");
3892 	}
3893     } else if (consp(notes)) {
3894 	for_all_list(notes, rest) {
3895 	    append_notes(buf, car(rest));
3896 	}
3897     } else {
3898 	run_warning("notes not list or strings, ignoring");
3899     }
3900 }
3901 
3902 void
append_blurb_strings(char * buf,Obj * notes)3903 append_blurb_strings(char *buf, Obj *notes)
3904 {
3905     char *str;
3906     Obj *rest;
3907 
3908     if (stringp(notes)) {
3909 	str = c_string(notes);
3910 	if (strlen(str) > 0) {
3911 	    tnprintf(buf, BLURBSIZE, str);
3912 	    tnprintf(buf, BLURBSIZE, " ");
3913 	} else {
3914 	    tnprintf(buf, BLURBSIZE, help_newline());
3915 	}
3916     } else if (consp(notes)) {
3917 	for_all_list(notes, rest) {
3918 	    append_blurb_strings(buf, car(rest));
3919 	}
3920     } else {
3921 	run_warning("blurb not list or strings, ignoring");
3922     }
3923 }
3924 
3925 void
notify_instructions(void)3926 notify_instructions(void)
3927 {
3928     Obj *instructions = mainmodule->instructions, *rest;
3929 
3930     if (instructions != lispnil) {
3931 	if (stringp(instructions)) {
3932 	    notify_all("%s", c_string(instructions));
3933 	} else if (consp(instructions)) {
3934 	    for (rest = instructions; rest != lispnil; rest = cdr(rest)) {
3935 		if (stringp(car(rest))) {
3936 		    notify_all("%s", c_string(car(rest)));
3937 		} else {
3938 		    /* (should probably warn about this case too) */
3939 		}
3940 	    }
3941 	} else {
3942 	    run_warning("Instructions are of wrong type");
3943 	}
3944     } else {
3945 	notify_all("(no instructions supplied)");
3946     }
3947 }
3948 
3949 /* Print the news file onto the console if there is anything to print. */
3950 
3951 void
print_any_news(void)3952 print_any_news(void)
3953 {
3954     FILE *fp;
3955 
3956     fp = open_library_file(news_filename());
3957     if (fp != NULL) {
3958 	printf("\n                              XCONQ NEWS\n\n");
3959 	while (fgets(spbuf, BUFSIZE-1, fp) != NULL) {
3960 	    fputs(spbuf, stdout);
3961 	}
3962 	/* Add another blank line, to separate from init printouts. */
3963 	printf("\n");
3964 	fclose(fp);
3965     }
3966 }
3967 
3968 /* Generate a readable description of the game (design) being played. */
3969 /* This works by writing out appropriate help nodes, along with some
3970    indexing material.  This does *not* do interface-specific help,
3971    such as commands. */
3972 
3973 void
print_game_description_to_file(FILE * fp)3974 print_game_description_to_file(FILE *fp)
3975 {
3976     HelpNode *node;
3977 
3978     /* (need to work on which nodes to dump out) */
3979     for (node = first_help_node; node != first_help_node; node = node->next) {
3980 	get_help_text(node);
3981 	if (node->text != NULL) {
3982 	    fprintf(fp, "\014\n%s\n", node->key);
3983 	    fprintf(fp, "%s\n", node->text);
3984 	}
3985     }
3986 }
3987 
3988 static void
tb_value_desc(TextBuffer * buf,int val)3989 tb_value_desc(TextBuffer *buf, int val)
3990 {
3991     char charbuf[30];
3992 
3993     sprintf(charbuf, "%d", val);
3994     tbcat(buf, charbuf);
3995 }
3996 
3997 static void
tb_fraction_desc(TextBuffer * buf,int val)3998 tb_fraction_desc(TextBuffer *buf, int val)
3999 {
4000     char charbuf[30];
4001 
4002     if (val % 100 == 0)
4003       sprintf(charbuf, "%d", val / 100);
4004     else
4005       sprintf(charbuf, "%d.%2.2d", val / 100, val % 100);
4006     tbcat(buf, charbuf);
4007 }
4008 
4009 static void
tb_percent_desc(TextBuffer * buf,int val)4010 tb_percent_desc(TextBuffer *buf, int val)
4011 {
4012     tb_value_desc(buf, val);
4013     tbcat(buf, "%");
4014 }
4015 
4016 static void
tb_percent_100th_desc(TextBuffer * buf,int val)4017 tb_percent_100th_desc(TextBuffer *buf, int val)
4018 {
4019     tb_fraction_desc(buf, val);
4020     tbcat(buf, "%");
4021 }
4022 
4023 /* Print out a prob_fraction-style value */
4024 static void
tb_probfraction_desc(TextBuffer * buf,int val)4025 tb_probfraction_desc(TextBuffer *buf, int val)
4026 {
4027     char charbuf[80];
4028 
4029     if (val % 100 == 0) {
4030 	sprintf(charbuf, "%d", val / 100);
4031     } else if (val < 100) {
4032 	sprintf(charbuf, "%u%% chance of 1", val);
4033     } else {
4034 	sprintf(charbuf, "%u%% chance of %u (otherwise %u)", val % 100,
4035 	    val / 100 + 1, val / 100);
4036     }
4037     tbcat(buf, charbuf);
4038 }
4039 
4040 static void
tb_dice_desc(TextBuffer * buf,int val)4041 tb_dice_desc(TextBuffer *buf, int val)
4042 {
4043     char charbuf[30];
4044 
4045     dice_desc(charbuf, val);
4046     tbcat(buf, charbuf);
4047 }
4048 
4049 static void
tb_mult_desc(TextBuffer * buf,int val)4050 tb_mult_desc(TextBuffer *buf, int val)
4051 {
4052     char charbuf[30];
4053 
4054     sprintf(charbuf, "*%d.%2.2d", val / 100, val % 100);
4055     tbcat(buf, charbuf);
4056 }
4057 
4058 static void
tb_bool_desc(TextBuffer * buf,int val)4059 tb_bool_desc(TextBuffer *buf, int val)
4060 {
4061     tbcat(buf, (char *)(val ? "true" : "false"));
4062 }
4063 
4064 void
tbprintf(TextBuffer * buf,char * str,...)4065 tbprintf(TextBuffer *buf, char *str, ...)
4066 {
4067     va_list ap;
4068     char line[300];
4069 
4070     va_start(ap, str);
4071     vsnprintf(line, 300, str, ap);
4072     tbcat(buf, line);
4073     va_end(ap);
4074 }
4075 
4076 #undef bcopy
4077 #define bcopy(a,b,c) memcpy(b,a,c)
4078 
4079 void
tbcat(TextBuffer * buf,char * str)4080 tbcat(TextBuffer *buf, char *str)
4081 {
4082     obstack_grow(&(buf->ostack), str, strlen(str));
4083 }
4084 
4085 void
tbcat_si(TextBuffer * buf,char * str)4086 tbcat_si(TextBuffer *buf, char *str)
4087 {
4088     tbprintf(buf, "%s", help_indent(2));
4089     obstack_grow(&(buf->ostack), str, strlen(str));
4090 }
4091 
4092 void
tbcatline(TextBuffer * buf,char * str)4093 tbcatline(TextBuffer *buf, char *str)
4094 {
4095     assert_error(buf, "Tried to concatenate a line to a NULL text buffer");
4096     assert_error(str, "Tried to concatenate a NULL string to a text buffer");
4097     tbcat(buf, str);
4098     tbcat(buf, help_newline());
4099 }
4100 
4101 void
tbcatline_si(TextBuffer * buf,char * str)4102 tbcatline_si(TextBuffer *buf, char *str)
4103 {
4104     assert_error(buf, "Tried to concatenate a line to a NULL text buffer");
4105     assert_error(str, "Tried to concatenate a NULL string to a text buffer");
4106     tbprintf(buf, "%s%s%s", help_indent(2), str, help_newline());
4107 }
4108 
4109 /* Prepares a newline for the help system in the current output format. */
4110 static char *
help_newline(void)4111 help_newline(void)
4112 {
4113     switch (help_output_mode) {
4114       case HELP_OUTPUT_PLAIN_TEXT:
4115 	return "\n";
4116       case HELP_OUTPUT_HTML:
4117 	return "<BR>\n";
4118       default:
4119 	return NULL;
4120     }
4121     return NULL;
4122 }
4123 
4124 /* Prepares an indent for the help system in the current output format. */
4125 static char *
help_indent(unsigned int spaces)4126 help_indent(unsigned int spaces)
4127 {
4128     int i = 0;
4129     int indentchars = 0;
4130 
4131     if (!spaces)
4132       return "";
4133     memset(tmpbuf, 0, BUFSIZE);
4134     switch (help_output_mode) {
4135       case HELP_OUTPUT_PLAIN_TEXT:
4136 	indentchars = spaces;
4137 	break;
4138       case HELP_OUTPUT_HTML:
4139 	indentchars = spaces * 12;
4140 	break;
4141       default: break;
4142     }
4143     assert_warning_return(indentchars <= BUFSIZE,
4144 "Number of requested indentation chars exceeds buffer size",
4145 			  "");
4146     switch (help_output_mode) {
4147       case HELP_OUTPUT_PLAIN_TEXT:
4148 	memset(tmpbuf, ' ', spaces);
4149 	break;
4150       case HELP_OUTPUT_HTML:
4151 	for (i = 0; i < spaces; ++i)
4152 	  strcat(tmpbuf, "&nbsp;&nbsp;");
4153 	break;
4154       default: break;
4155     }
4156     return tmpbuf;
4157 }
4158 
4159 /* Prepares help file for a "Table of Contents" section. */
4160 
4161 void
prep_help_cc_toc(void)4162 prep_help_cc_toc(void)
4163 {
4164     char *ccfilebname = "index";
4165     char *ccfileext = NULL;
4166     char ccfilename [BUFSIZE];
4167 
4168     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4169       return;
4170     ccfileext = get_help_file_extension();
4171     make_pathname(NULL, ccfilebname, ccfileext, ccfilename);
4172     help_output_toc_filep = prep_help_file(ccfilename);
4173     write_help_file_header(help_output_toc_filep, "Table of Contents");
4174     write_help_section_header(help_output_toc_filep, "Table of Contents");
4175     if (mainmodule && mainmodule->blurb && stringp(mainmodule->blurb))
4176       fprintf(help_output_toc_filep,
4177 	      "<CENTER><H5>&quot;%s&quot;</H5></CENTER><BR>\n",
4178 	      c_string(mainmodule->blurb));
4179     write_help_file_navbar(help_output_toc_filep, HELP_PAGE_TOC, -1);
4180 }
4181 
4182 /* Prepares help file for any standard section using the temp file buffer. */
4183 
4184 static void
meta_prep_help_cc(HelpPage hpage,int hpageidx)4185 meta_prep_help_cc(HelpPage hpage, int hpageidx)
4186 {
4187     char namebuf [BUFSIZE];
4188     char numbuf [BUFSIZE];
4189     char filebnamebuf [BUFSIZE];
4190     int indentlvl = 0;
4191 
4192     strncpy(filebnamebuf, hpagedefns[hpage].filebname, BUFSIZE);
4193     strncpy(namebuf, hpagedefns[hpage].name, BUFSIZE);
4194     if (-1 < hpageidx) {
4195 	switch (hpage) {
4196 	  case HELP_PAGE_UTYPE:
4197 	    strncpy(namebuf, u_type_name(hpageidx), BUFSIZE);
4198 	    break;
4199 	  case HELP_PAGE_TTYPE:
4200 	    strncpy(namebuf, t_type_name(hpageidx), BUFSIZE);
4201 	    break;
4202 	  case HELP_PAGE_MTYPE:
4203 	    strncpy(namebuf, m_type_name(hpageidx), BUFSIZE);
4204 	    break;
4205 	  case HELP_PAGE_ATYPE:
4206 	    strncpy(namebuf, a_type_name(hpageidx), BUFSIZE);
4207 	    break;
4208 	  default: break;
4209 	}
4210 	sprintf(numbuf, "%d", hpageidx);
4211 	strcat(filebnamebuf, numbuf);
4212 	indentlvl = 1;
4213     }
4214     prep_help_cc_any(filebnamebuf, namebuf);
4215     write_help_file_navbar(help_output_tmp_filep, hpage, hpageidx);
4216     write_help_toc_entry(filebnamebuf, namebuf, indentlvl);
4217 }
4218 
4219 /* Prepares help file for any section. */
4220 
4221 void
prep_help_cc_any(char * ccfilebname,char * sectionname)4222 prep_help_cc_any(char *ccfilebname, char *sectionname)
4223 {
4224     char *ccfileext = NULL;
4225     char ccfilename [BUFSIZE];
4226 
4227     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4228       return;
4229     assert_error(ccfilebname,
4230 		 "Attempted to prepare an help file with a NULL name");
4231     assert_error(ccfilebname[0],
4232 		 "Attempted to prepare an help file with an empty name");
4233     assert_error(sectionname,
4234 		 "Attempted to prepare an help file with a NULL section name");
4235     ccfileext = get_help_file_extension();
4236     make_pathname(NULL, ccfilebname, ccfileext, ccfilename);
4237     help_output_tmp_filep = prep_help_file(ccfilename);
4238     write_help_file_header(help_output_tmp_filep, sectionname);
4239     write_help_section_header(help_output_tmp_filep, sectionname);
4240 }
4241 
4242 /* Generic core for prepping a new help system CC file. */
4243 
4244 FILE *
prep_help_file(char * hfilename)4245 prep_help_file(char *hfilename)
4246 {
4247     FILE *ccfile = NULL;
4248     char *ccfilepath = NULL;
4249 
4250     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4251       return NULL;
4252     assert_error(hfilename,
4253 		 "Attempted to open a help file with a NULL name.");
4254     assert_error(hfilename[0],
4255 		 "Attempted to open a help file with an empty name.");
4256     ccfilepath = (char *)xmalloc(strlen(help_output_dir) + 10 +
4257 				 strlen(hfilename));
4258     make_pathname(help_output_dir, hfilename, NULL, ccfilepath);
4259     ccfile = fopen(ccfilepath, "w");
4260     if (NULL == ccfile)
4261       run_error("Failed to open %s for writing.", ccfilepath);
4262     return ccfile;
4263 }
4264 
4265 /* Add TOC entry to TOC help file stream. */
4266 
4267 #define HELP_PLAIN_TEXT_COLS_MAX	72
4268 #define HELP_PLAIN_TEXT_TRUNC_COL	(HELP_PLAIN_TEXT_COLS_MAX - 1)
4269 #define HELP_PLAIN_TEXT_IDX_TRUNC_COL	39
4270 
4271 void
write_help_toc_entry(char * hfilebname,char * sectionname,int indentlvl)4272 write_help_toc_entry(char *hfilebname, char *sectionname, int indentlvl)
4273 {
4274     char ccfilename [BUFSIZE];
4275     char *ccfileext = NULL;
4276     char ptextbuf [HELP_PLAIN_TEXT_COLS_MAX];
4277     int lookupwidth = 0, indexpos = 0;
4278 
4279     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4280       return;
4281     assert_error(sectionname, "Tried to index a NULL section name");
4282     /* Build up the help file name, if the basename is not NULL. */
4283     if (hfilebname) {
4284 	ccfileext = get_help_file_extension();
4285 	make_pathname("", hfilebname, ccfileext, ccfilename);
4286     }
4287     /* Write out the TOC entry line. */
4288     switch (help_output_mode) {
4289       case HELP_OUTPUT_PLAIN_TEXT:
4290 	/* Fill output buffer with guide dots for easier lookup, ......
4291 	   and that cluttered, information-packed look. ;-) */
4292 	memset(ptextbuf, 0, HELP_PLAIN_TEXT_COLS_MAX);
4293 	/* Prepare section name with left justification, starting in the
4294 	   leftmost column. */
4295 	snprintf(ptextbuf, HELP_PLAIN_TEXT_IDX_TRUNC_COL, "%s%s",
4296 		 help_indent(indentlvl*2), sectionname);
4297 	indexpos = min(strlen(ptextbuf), HELP_PLAIN_TEXT_IDX_TRUNC_COL);
4298 	memset(ptextbuf + indexpos, '.', HELP_PLAIN_TEXT_TRUNC_COL - indexpos);
4299 	if (hfilebname) {
4300 	    lookupwidth = strlen(ccfilename) + 2;
4301 	    if (lookupwidth > (HELP_PLAIN_TEXT_COLS_MAX - 10))
4302 	      run_error(
4303 "File name is too large for table of contents help file");
4304 	    /* Prepare file name with right justification, starting in the
4305 	       rightmost column. */
4306 	    sprintf(ptextbuf + (HELP_PLAIN_TEXT_TRUNC_COL - lookupwidth),
4307 		    "..%s", ccfilename);
4308 	}
4309 	/* Output the prepared buffer. */
4310 	fprintf(help_output_toc_filep, "%s\n", ptextbuf);
4311 	break;
4312       case HELP_OUTPUT_HTML:
4313 	if (hfilebname)
4314 	    fprintf(help_output_toc_filep, "%s<A Href=\"%s\">%s</A><BR>\n",
4315 		    help_indent(indentlvl*2), ccfilename, sectionname);
4316 	else
4317 	    fprintf(help_output_toc_filep, "%s%s<BR>\n",
4318 		    help_indent(indentlvl*2), sectionname);
4319 	break;
4320       default: break;
4321     }
4322     fflush(help_output_toc_filep);
4323 }
4324 
4325 /* Flush help node to help file stream for any section. */
4326 
4327 void
flush_help_cc_tmp(HelpNode * helpnode)4328 flush_help_cc_tmp(HelpNode *helpnode)
4329 {
4330     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4331       return;
4332     flush_help_cc(help_output_tmp_filep, helpnode);
4333 }
4334 
4335 /* Generic core for flushing a completed help node to help system file. */
4336 
4337 void
flush_help_cc(FILE * ccfile,HelpNode * helpnode)4338 flush_help_cc(FILE *ccfile, HelpNode *helpnode)
4339 {
4340     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4341       return;
4342     assert_error(ccfile,
4343 		 "Attempted to flush a help node to a NULL help file");
4344     assert_error(helpnode,
4345 		 "Attempted to flush a NULL help node to a help file");
4346     fprintf(ccfile, "%s", get_help_text(helpnode));
4347     if (helpnode->text) {
4348 	free(helpnode->text);
4349 	helpnode->text = NULL;
4350     }
4351     switch (help_output_mode) {
4352       case HELP_OUTPUT_PLAIN_TEXT:
4353 	fprintf(ccfile, "\n");
4354 	break;
4355       case HELP_OUTPUT_HTML:
4356 	fprintf(ccfile, "\n<BR>\n");
4357 	break;
4358       default: break;
4359     }
4360     fflush(ccfile);
4361 }
4362 
4363 /* Finishes help file for "Table of Contents" section. */
4364 
4365 void
finish_help_cc_toc(void)4366 finish_help_cc_toc(void)
4367 {
4368     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4369       return;
4370     write_help_section_footer(help_output_toc_filep, "Table of Contents");
4371     write_help_file_navbar(help_output_toc_filep, HELP_PAGE_TOC, -1);
4372     write_help_file_footer(help_output_toc_filep, "Table of Contents");
4373     finish_help_file(help_output_toc_filep);
4374 }
4375 
4376 /* Finishes help file for any standard section using the temp file buffer. */
4377 
4378 static void
meta_finish_help_cc(HelpPage hpage,int hpageidx,HelpNode * helpnode)4379 meta_finish_help_cc(HelpPage hpage, int hpageidx, HelpNode *helpnode)
4380 {
4381     char namebuf [BUFSIZE];
4382     char numbuf [BUFSIZE];
4383     char filebnamebuf [BUFSIZE];
4384 
4385     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4386       return;
4387     strncpy(filebnamebuf, hpagedefns[hpage].filebname, BUFSIZE);
4388     strncpy(namebuf, hpagedefns[hpage].name, BUFSIZE);
4389     if (-1 < hpageidx) {
4390 	switch (hpage) {
4391 	  case HELP_PAGE_UTYPE:
4392 	    strncpy(namebuf, u_type_name(hpageidx), BUFSIZE);
4393 	    break;
4394 	  case HELP_PAGE_TTYPE:
4395 	    strncpy(namebuf, t_type_name(hpageidx), BUFSIZE);
4396 	    break;
4397 	  case HELP_PAGE_MTYPE:
4398 	    strncpy(namebuf, m_type_name(hpageidx), BUFSIZE);
4399 	    break;
4400 	  case HELP_PAGE_ATYPE:
4401 	    strncpy(namebuf, a_type_name(hpageidx), BUFSIZE);
4402 	    break;
4403 	  default: break;
4404 	}
4405 	sprintf(numbuf, "%d", hpageidx);
4406 	strcat(filebnamebuf, numbuf);
4407     }
4408     flush_help_cc_tmp(helpnode);
4409     write_help_section_footer(help_output_tmp_filep, namebuf);
4410     write_help_file_navbar(help_output_tmp_filep, hpage, hpageidx);
4411     write_help_file_footer(help_output_tmp_filep, namebuf);
4412     finish_help_file(help_output_tmp_filep);
4413 }
4414 
4415 /* Generic core for finishing an help system file. */
4416 
4417 void
finish_help_file(FILE * hfilep)4418 finish_help_file(FILE *hfilep)
4419 {
4420     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4421       return;
4422     assert_error(hfilep, "Attempted to flush a NULL help file.");
4423     fflush(hfilep);
4424     fclose(hfilep);
4425 }
4426 
4427 /* Writes header for help file. */
4428 
4429 void
write_help_file_header(FILE * hfile,char * headerdata)4430 write_help_file_header(FILE *hfile, char *headerdata)
4431 {
4432     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4433       return;
4434     assert_error(hfile,
4435 		 "Attempted to write an header to a NULL help file");
4436     switch (help_output_mode) {
4437       case HELP_OUTPUT_PLAIN_TEXT:
4438 	assert_error(headerdata,
4439 		     "Attempted to write an header with NULL header data");
4440 	if (mainmodule)
4441 	  fprintf(hfile, "%s: %s\n\n", mainmodule->name, headerdata);
4442 	else
4443 	  fprintf(hfile, "%s\n\n", headerdata);
4444 	break;
4445       case HELP_OUTPUT_HTML:
4446 	assert_error(headerdata,
4447 		     "Attempted to write an header with NULL header data");
4448 	if (mainmodule)
4449 	  fprintf(hfile,
4450 		  "<HTML>\n<HEAD>\n<TITLE>%s: %s</TITLE>\n</HEAD>\n<BODY>\n",
4451 		  mainmodule->name, headerdata);
4452 	else
4453 	  fprintf(hfile,
4454 		  "<HTML>\n<HEAD>\n<TITLE>%s</TITLE>\n</HEAD>\n<BODY>\n",
4455 		  headerdata);
4456 	break;
4457       default: break;
4458     }
4459 }
4460 
4461 /* Writes footer for help file. */
4462 
4463 void
write_help_file_footer(FILE * hfile,char * footerdata)4464 write_help_file_footer(FILE *hfile, char *footerdata)
4465 {
4466     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4467       return;
4468     assert_error(hfile,
4469 		 "Attempted to write a footer to a NULL help file");
4470     switch (help_output_mode) {
4471       case HELP_OUTPUT_PLAIN_TEXT:
4472 	fprintf(hfile, "\n\n%s\n", help_file_brand());
4473 	break;
4474       case HELP_OUTPUT_HTML:
4475 	fprintf(hfile, "<BR>\n<H6>%s</H6>\n</BODY>\n</HTML>\n",
4476 		help_file_brand());
4477 	break;
4478       default: break;
4479     }
4480 }
4481 
4482 /* Returns a branding mark for help file. */
4483 
4484 char *
help_file_brand(void)4485 help_file_brand(void)
4486 {
4487     /* TODO: Add date and time. */
4488     snprintf(tmpbuf, BUFSIZE - 1,
4489 	     "File produced by Xcscribe for Xconq version %s.",
4490 	     version_string() /*, copyright_string() */);
4491     return tmpbuf;
4492 }
4493 
4494 /* Writes header for a help section. */
4495 
4496 void
write_help_section_header(FILE * ccfile,char * headerdata)4497 write_help_section_header(FILE *ccfile, char *headerdata)
4498 {
4499     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4500       return;
4501     assert_error(ccfile,
4502 		 "Attempted to write an header to a NULL help CC file");
4503     switch (help_output_mode) {
4504       case HELP_OUTPUT_PLAIN_TEXT: break;
4505       case HELP_OUTPUT_HTML:
4506 	assert_error(headerdata,
4507 		     "Attempted to write an header with NULL header data");
4508 	fprintf(ccfile, "<CENTER><H1>%s</H1></CENTER><BR>\n", headerdata);
4509 	break;
4510       default: break;
4511     }
4512 }
4513 
4514 /* Writes footer for help section. */
4515 
4516 void
write_help_section_footer(FILE * ccfilep,char * footerdata)4517 write_help_section_footer(FILE *ccfilep, char *footerdata)
4518 {
4519     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4520       return;
4521     assert_error(ccfilep,
4522 		 "Attempted to write a footer to a NULL help file");
4523     switch (help_output_mode) {
4524       case HELP_OUTPUT_PLAIN_TEXT: case HELP_OUTPUT_HTML: break;
4525       default: break;
4526     }
4527 }
4528 
4529 /* Writes header for help subsection. */
4530 
4531 void
write_help_subsection_header(FILE * ccfile,char * headerdata)4532 write_help_subsection_header(FILE *ccfile, char *headerdata)
4533 {
4534     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4535       return;
4536     assert_error(ccfile,
4537 		 "Attempted to write an header to a NULL help CC file");
4538     switch (help_output_mode) {
4539       case HELP_OUTPUT_PLAIN_TEXT:
4540 	assert_error(headerdata,
4541 		     "Attempted to write an header with NULL header data");
4542 	fprintf(ccfile, "%s:\n\n", headerdata);
4543 	break;
4544       case HELP_OUTPUT_HTML:
4545 	assert_error(headerdata,
4546 		     "Attempted to write an header with NULL header data");
4547 	fprintf(ccfile, "<CENTER><H4>%s</H4></CENTER><BR>\n", headerdata);
4548 	break;
4549       default: break;
4550     }
4551 }
4552 
4553 /* Writes footer for help subsection. */
4554 
4555 void
write_help_subsection_footer(FILE * ccfile,char * footerdata)4556 write_help_subsection_footer(FILE *ccfile, char *footerdata)
4557 {
4558     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4559       return;
4560     assert_error(ccfile,
4561 		 "Attempted to write a footer to a NULL help CC file");
4562     switch (help_output_mode) {
4563       case HELP_OUTPUT_PLAIN_TEXT: case HELP_OUTPUT_HTML: break;
4564       default: break;
4565     }
4566 }
4567 
4568 /* Writes a break between subsections. */
4569 
4570 void
write_help_subsection_break(FILE * ccfile)4571 write_help_subsection_break(FILE *ccfile)
4572 {
4573     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4574       return;
4575     assert_error(ccfile,
4576 		 "Attempted to write a footer to a NULL help CC file");
4577     switch (help_output_mode) {
4578       case HELP_OUTPUT_PLAIN_TEXT:
4579 	fprintf(ccfile, "\n\n");
4580 	break;
4581       case HELP_OUTPUT_HTML:
4582 	fprintf(ccfile, "<BR><HR><BR>\n");
4583 	break;
4584       default: break;
4585     }
4586 }
4587 
4588 /* Returns help file's extension. */
4589 
4590 char *
get_help_file_extension(void)4591 get_help_file_extension(void)
4592 {
4593     switch (help_output_mode) {
4594       case HELP_OUTPUT_PLAIN_TEXT:
4595 	return "txt";
4596       case HELP_OUTPUT_HTML:
4597 	return "html";
4598       default: break;
4599     }
4600     return "";
4601 }
4602 
4603 /* Initialize the help pages. */
4604 
4605 static void
init_help_pages(void)4606 init_help_pages(void)
4607 {
4608     char midxbname [BUFSIZE];
4609 
4610     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4611       return;
4612     /* Fixup the master index file basename. */
4613     memset(midxbname, 0, BUFSIZE);
4614     switch (help_output_mode) {
4615       case HELP_OUTPUT_PLAIN_TEXT:
4616 #if (defined(UNIX) || defined(WIN32))
4617 	make_pathname("..", "index", "", midxbname);
4618 #else
4619 	make_pathname("::", "index", "", midxbname);
4620 #endif
4621 	break;
4622       case HELP_OUTPUT_HTML:
4623 	strcpy(midxbname, "../index");
4624 	break;
4625       default: break;
4626     }
4627     hpagedefns[HELP_PAGE_MASTER_INDEX].filebname = copy_string(midxbname);
4628     /* Fixup next and previous help pages involving variable-sized help page
4629        clusters. */
4630     hpagedefns[HELP_PAGE_TTYPE].nextpage =
4631 	((nummtypes <= 0) ? ((numatypes <= 0) ? HELP_PAGE_LAST
4632 					      : HELP_PAGE_ATYPE)
4633 			  : HELP_PAGE_MTYPE);
4634     hpagedefns[HELP_PAGE_MTYPE].nextpage =
4635 	((numatypes <= 0) ? HELP_PAGE_LAST : HELP_PAGE_ATYPE);
4636     hpagedefns[HELP_PAGE_ATYPE].prevpage =
4637 	((nummtypes <= 0) ? HELP_PAGE_TTYPE : HELP_PAGE_MTYPE);
4638     hpagedefns[HELP_PAGE_LAST].prevpage =
4639 	((numatypes <= 0) ? ((nummtypes <= 0) ? HELP_PAGE_TTYPE
4640 					      : HELP_PAGE_MTYPE)
4641 			  : HELP_PAGE_ATYPE);
4642 }
4643 
4644 /* Write a navbar for a help page. */
4645 
4646 void
write_help_file_navbar(FILE * ccfilep,HelpPage hpage,int hpageidx)4647 write_help_file_navbar(FILE *ccfilep, HelpPage hpage, int hpageidx)
4648 {
4649     char *tocfilebname = "index";
4650     char *prevfilebname = NULL;
4651     char *nextfilebname = NULL;
4652     char *tocname = "Table of Contents";
4653     char *prevname = NULL;
4654     char *nextname = NULL;
4655     char *navfileext = NULL;
4656     int prevhpageidx = -1, nexthpageidx = -1;
4657     char prevnumbuf [BUFSIZE], nextnumbuf [BUFSIZE];
4658 
4659     if (HELP_OUTPUT_CC_NONE == help_output_cc)
4660       return;
4661     assert_error(HELP_PAGE_MASTER_INDEX != hpage,
4662 "Attempted to write a navigation bar for an invalid help page.");
4663     assert_error(ccfilep, "Attempted to write to a NULL help file.");
4664     /* Set up the nav links. */
4665     switch (hpage) {
4666       case HELP_PAGE_NONE: return;
4667       default:
4668 	tocfilebname = hpagedefns[hpagedefns[hpage].tocpage].filebname;
4669 	prevfilebname = hpagedefns[hpagedefns[hpage].prevpage].filebname;
4670 	nextfilebname = hpagedefns[hpagedefns[hpage].nextpage].filebname;
4671 	tocname = hpagedefns[hpagedefns[hpage].tocpage].name;
4672 	prevname = hpagedefns[hpagedefns[hpage].prevpage].name;
4673 	nextname = hpagedefns[hpagedefns[hpage].nextpage].name;
4674     }
4675     /* In the case of utypes, ttypes, mtypes, etc..., the links may need to
4676        be edited. */
4677     switch (hpage) {
4678       case HELP_PAGE_WORLD:
4679 	nexthpageidx = 0;
4680 	break;
4681       case HELP_PAGE_UTYPE:
4682 	if (hpageidx > 0) {
4683 	    prevfilebname = hpagedefns[hpage].filebname;
4684 	    prevname = hpagedefns[hpage].name;
4685 	    prevhpageidx = hpageidx - 1;
4686 	}
4687 	if (hpageidx < (numutypes - 1)) {
4688 	    nextfilebname = hpagedefns[hpage].filebname;
4689 	    nextname = hpagedefns[hpage].name;
4690 	    nexthpageidx = hpageidx + 1;
4691 	}
4692 	else
4693 	  nexthpageidx = 0;
4694 	break;
4695       case HELP_PAGE_TTYPE:
4696 	if (hpageidx > 0) {
4697 	    prevfilebname = hpagedefns[hpage].filebname;
4698 	    prevname = hpagedefns[hpage].name;
4699 	    prevhpageidx = hpageidx - 1;
4700 	}
4701 	if (hpageidx < (numttypes - 1)) {
4702 	    nextfilebname = hpagedefns[hpage].filebname;
4703 	    nextname = hpagedefns[hpage].name;
4704 	    nexthpageidx = hpageidx + 1;
4705 	}
4706 	else
4707 	  nexthpageidx = 0;
4708 	break;
4709       case HELP_PAGE_MTYPE:
4710 	if (hpageidx > 0) {
4711 	    prevfilebname = hpagedefns[hpage].filebname;
4712 	    prevname = hpagedefns[hpage].name;
4713 	    prevhpageidx = hpageidx - 1;
4714 	}
4715 	if (hpageidx < (nummtypes - 1)) {
4716 	    nextfilebname = hpagedefns[hpage].filebname;
4717 	    nextname = hpagedefns[hpage].name;
4718 	    nexthpageidx = hpageidx + 1;
4719 	}
4720 	else
4721 	  nexthpageidx = 0;
4722 	break;
4723       case HELP_PAGE_ATYPE:
4724 	if (hpageidx > 0) {
4725 	    prevfilebname = hpagedefns[hpage].filebname;
4726 	    prevname = hpagedefns[hpage].name;
4727 	    prevhpageidx = hpageidx - 1;
4728 	}
4729 	if (hpageidx < (numatypes - 1)) {
4730 	    nextfilebname = hpagedefns[hpage].filebname;
4731 	    nextname = hpagedefns[hpage].name;
4732 	    nexthpageidx = hpageidx + 1;
4733 	}
4734 	else
4735 	  nexthpageidx = 0;
4736 	break;
4737       default: break;
4738     }
4739     /* In the case of utypes, ttypes, mtypes, etc..., even more editing may
4740        need to be done, since mtypes and atypes may not necessarily be
4741        defined for the game. */
4742     switch (hpage) {
4743       case HELP_PAGE_TTYPE:
4744 	if (hpageidx == 0)
4745 	  prevhpageidx = numutypes - 1;
4746 	if (hpageidx >= (numttypes - 1))
4747 	  nexthpageidx = ((nummtypes > 0) ? 0 : ((numatypes > 0) ? 0 : -1));
4748 	break;
4749       case HELP_PAGE_MTYPE:
4750 	if (hpageidx == 0)
4751 	  prevhpageidx = numttypes - 1;
4752 	if (hpageidx >= (nummtypes - 1))
4753 	  nexthpageidx = ((numatypes > 0) ? 0 : -1);
4754 	break;
4755       case HELP_PAGE_ATYPE:
4756 	if (hpageidx == 0)
4757 	  prevhpageidx = ((nummtypes > 0) ? nummtypes - 1 : numttypes - 1);
4758 	if (hpageidx >= (numatypes - 1))
4759 	  nexthpageidx = -1;
4760 	break;
4761       case HELP_PAGE_LAST:
4762 	prevhpageidx = ((numatypes > 0) ? numatypes - 1
4763 					: ((nummtypes > 0) ? nummtypes - 1
4764 							   : numttypes - 1));
4765       default: break;
4766     }
4767     /* Get the numeric indices of the prev and next help files, if relevant. */
4768     /* <<The decimal point can be added to these buffers, if specifying a
4769 	 "%s" for an empty string causes problems.>> */
4770     memset(prevnumbuf, 0, BUFSIZE);
4771     memset(nextnumbuf, 0, BUFSIZE);
4772     if (-1 < prevhpageidx)
4773       sprintf(prevnumbuf, "%d", prevhpageidx);
4774     if (-1 < nexthpageidx)
4775       sprintf(nextnumbuf, "%d", nexthpageidx);
4776     /* Get the extension for all the help files. */
4777     navfileext = get_help_file_extension();
4778     /* Actually write the navbar. */
4779     fprintf(ccfilep, "%s", help_newline());
4780     switch (help_output_mode) {
4781       case HELP_OUTPUT_PLAIN_TEXT:
4782 	if (tocfilebname && tocname)
4783 	  fprintf(ccfilep, "%s: %s.%s%s", tocname,
4784 		  tocfilebname, navfileext, help_newline());
4785 	if (prevfilebname && prevname)
4786 	  fprintf(ccfilep, "Previous: %s%s.%s%s", prevfilebname,
4787 		  prevnumbuf, navfileext, help_newline());
4788 	if (nextfilebname && nextname)
4789 	  fprintf(ccfilep, "Next: %s%s.%s%s", nextfilebname,
4790 		  nextnumbuf, navfileext, help_newline());
4791 	break;
4792       case HELP_OUTPUT_HTML:
4793 	/* fprintf(ccfilep, "<CENTER>"); */
4794 	if (tocfilebname && tocname)
4795 	  fprintf(ccfilep, "Up: <A Href=\"%s.%s\">%s</A>%s", tocfilebname,
4796 		  navfileext, tocname, help_indent(2));
4797 	if (prevfilebname && prevname)
4798 	  fprintf(ccfilep, "Previous: <A Href=\"%s%s.%s\">%s</A>%s",
4799 		  prevfilebname, prevnumbuf, navfileext, prevname,
4800 		  help_indent(2));
4801 	if (nextfilebname && nextname)
4802 	  fprintf(ccfilep, "Next: <A Href=\"%s%s.%s\">%s</A>%s", nextfilebname,
4803 		  nextnumbuf, navfileext, nextname, help_indent(2));
4804 	/* fprintf(ccfilep, "</CENTER>%s", help_newline()); */
4805 	fprintf(ccfilep, "%s", help_newline());
4806 	break;
4807       default: break;
4808     }
4809     fprintf(ccfilep, "%s", help_newline());
4810     fflush(ccfilep);
4811 }
4812