1 /***********************************************************************
2  Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; either version 2, or (at your option)
6    any later version.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 ***********************************************************************/
13 
14 /***********************************************************************
15  This module is for generic handling of help data, independent
16  of gui considerations.
17 ***********************************************************************/
18 
19 #ifdef HAVE_CONFIG_H
20 #include <fc_config.h>
21 #endif
22 
23 #include <stdio.h>
24 #include <string.h>
25 
26 /* utility */
27 #include "astring.h"
28 #include "bitvector.h"
29 #include "fciconv.h"
30 #include "fcintl.h"
31 #include "log.h"
32 #include "mem.h"
33 #include "registry.h"
34 #include "string_vector.h"
35 #include "support.h"
36 
37 /* common */
38 #include "achievements.h"
39 #include "actions.h"
40 #include "calendar.h"
41 #include "city.h"
42 #include "effects.h"
43 #include "game.h"
44 #include "government.h"
45 #include "map.h"
46 #include "movement.h"
47 #include "multipliers.h"
48 #include "requirements.h"
49 #include "research.h"
50 #include "road.h"
51 #include "specialist.h"
52 #include "tilespec.h"
53 #include "unit.h"
54 #include "version.h"
55 
56 /* client */
57 #include "client_main.h"
58 #include "climisc.h"
59 #include "gui_main_g.h" /* client_string */
60 
61 #include "helpdata.h"
62 
63 /* helper macro for easy conversion from snprintf and cat_snprintf */
64 #define CATLSTR(_b, _s, _t) fc_strlcat(_b, _t, _s)
65 
66 /* This must be in same order as enum in helpdlg_g.h */
67 static const char * const help_type_names[] = {
68   "(Any)", "(Text)", "Units", "Improvements", "Wonders",
69   "Techs", "Terrain", "Extras", "Specialists", "Governments",
70   "Ruleset", "Tileset", "Nations", "Multipliers", NULL
71 };
72 
73 #define SPECLIST_TAG help
74 #define SPECLIST_TYPE struct help_item
75 #include "speclist.h"
76 
77 #define help_list_iterate(helplist, phelp) \
78     TYPED_LIST_ITERATE(struct help_item, helplist, phelp)
79 #define help_list_iterate_end  LIST_ITERATE_END
80 
81 static const struct help_list_link *help_nodes_iterator;
82 static struct help_list *help_nodes;
83 static bool help_nodes_init = FALSE;
84 /* help_nodes_init is not quite the same as booted in boot_help_texts();
85    latter can be FALSE even after call, eg if couldn't find helpdata.txt.
86 */
87 
88 /****************************************************************
89   Initialize.
90 *****************************************************************/
helpdata_init(void)91 void helpdata_init(void)
92 {
93   help_nodes = help_list_new();
94 }
95 
96 /****************************************************************
97   Clean up.
98 *****************************************************************/
helpdata_done(void)99 void helpdata_done(void)
100 {
101   help_list_destroy(help_nodes);
102 }
103 
104 /****************************************************************
105   Make sure help_nodes is initialised.
106   Should call this just about everywhere which uses help_nodes,
107   to be careful...  or at least where called by external
108   (non-static) functions.
109 *****************************************************************/
check_help_nodes_init(void)110 static void check_help_nodes_init(void)
111 {
112   if (!help_nodes_init) {
113     help_nodes_init = TRUE;    /* before help_iter_start to avoid recursion! */
114     help_iter_start();
115   }
116 }
117 
118 /****************************************************************
119   Free all allocations associated with help_nodes.
120 *****************************************************************/
free_help_texts(void)121 void free_help_texts(void)
122 {
123   check_help_nodes_init();
124   help_list_iterate(help_nodes, ptmp) {
125     free(ptmp->topic);
126     free(ptmp->text);
127     free(ptmp);
128   } help_list_iterate_end;
129   help_list_clear(help_nodes);
130 }
131 
132 /****************************************************************************
133   Returns whether we should show help for this nation.
134 ****************************************************************************/
show_help_for_nation(const struct nation_type * pnation)135 static bool show_help_for_nation(const struct nation_type *pnation)
136 {
137   return client_nation_is_in_current_set(pnation);
138 }
139 
140 /****************************************************************************
141   Insert fixed-width table describing veteran system.
142   If only one veteran level, inserts 'nolevels' if non-NULL.
143   Otherwise, insert 'intro' then a table.
144 ****************************************************************************/
insert_veteran_help(char * outbuf,size_t outlen,const struct veteran_system * veteran,const char * intro,const char * nolevels)145 static bool insert_veteran_help(char *outbuf, size_t outlen,
146                                 const struct veteran_system *veteran,
147                                 const char *intro, const char *nolevels)
148 {
149   /* game.veteran can be NULL in pregame; if so, keep quiet about
150    * veteran levels */
151   if (!veteran) {
152     return FALSE;
153   }
154 
155   fc_assert_ret_val(veteran->levels >= 1, FALSE);
156 
157   if (veteran->levels == 1) {
158     /* Only a single veteran level. Don't bother to name it. */
159     if (nolevels) {
160       CATLSTR(outbuf, outlen, nolevels);
161       return TRUE;
162     } else {
163       return FALSE;
164     }
165   } else {
166     int i;
167     fc_assert_ret_val(veteran->definitions != NULL, FALSE);
168     if (intro) {
169       CATLSTR(outbuf, outlen, intro);
170       CATLSTR(outbuf, outlen, "\n\n");
171     }
172     /* raise_chance and work_raise_chance don't get to the client, so we
173      * can't report them */
174     CATLSTR(outbuf, outlen,
175             /* TRANS: Header for fixed-width veteran level table.
176              * TRANS: Translators cannot change column widths :(
177              * TRANS: "Level name" left-justified, other two right-justified */
178             _("Veteran level      Power factor   Move bonus\n"));
179     CATLSTR(outbuf, outlen,
180             /* TRANS: Part of header for veteran level table. */
181             _("--------------------------------------------"));
182     for (i = 0; i < veteran->levels; i++) {
183       const struct veteran_level *level = &veteran->definitions[i];
184       const char *name = name_translation_get(&level->name);
185       /* Use get_internal_string_length() for correct alignment with
186        * multibyte character encodings */
187       cat_snprintf(outbuf, outlen,
188           "\n%s%*s %4d%% %12s",
189           name, MAX(0, 25 - (int)get_internal_string_length(name)), "",
190           level->power_fact,
191           /* e.g. "-    ", "+ 1/3", "+ 1    ", "+ 2 2/3" */
192           move_points_text_full(level->move_bonus, TRUE, "+ ", "-", TRUE));
193     }
194     return TRUE;
195   }
196 }
197 
198 /****************************************************************************
199   Insert generated text for the helpdata "name".
200   Returns TRUE if anything was added.
201 ****************************************************************************/
insert_generated_text(char * outbuf,size_t outlen,const char * name)202 static bool insert_generated_text(char *outbuf, size_t outlen, const char *name)
203 {
204   if (!game.client.ruleset_init) {
205     return FALSE;
206   }
207 
208   if (0 == strcmp(name, "TerrainAlterations")) {
209     int clean_pollution_time = -1, clean_fallout_time = -1, pillage_time = -1;
210     bool terrain_independent_extras = FALSE;
211 
212     CATLSTR(outbuf, outlen,
213             /* TRANS: Header for fixed-width terrain alteration table.
214              * TRANS: Translators cannot change column widths :( */
215             _("Terrain       Irrigation       Mining           Transform\n"));
216     CATLSTR(outbuf, outlen,
217             "----------------------------------------------------------------\n");
218     terrain_type_iterate(pterrain) {
219       if (0 != strlen(terrain_rule_name(pterrain))) {
220         char irrigation_time[4], mining_time[4], transform_time[4];
221         const char *terrain, *irrigation_result,
222                    *mining_result,*transform_result;
223         struct universal for_terr = { .kind = VUT_TERRAIN, .value = { .terrain = pterrain }};
224 
225         fc_snprintf(irrigation_time, sizeof(irrigation_time),
226                     "%d", pterrain->irrigation_time);
227         fc_snprintf(mining_time, sizeof(mining_time),
228                     "%d", pterrain->mining_time);
229         fc_snprintf(transform_time, sizeof(transform_time),
230                     "%d", pterrain->transform_time);
231         terrain = terrain_name_translation(pterrain);
232         irrigation_result =
233           (pterrain->irrigation_result == pterrain
234            || pterrain->irrigation_result == T_NONE
235            || effect_cumulative_max(EFT_IRRIG_TF_POSSIBLE, &for_terr) <= 0) ? ""
236           : terrain_name_translation(pterrain->irrigation_result);
237         mining_result =
238           (pterrain->mining_result == pterrain
239            || pterrain->mining_result == T_NONE
240            || effect_cumulative_max(EFT_MINING_TF_POSSIBLE, &for_terr) <= 0) ? ""
241           : terrain_name_translation(pterrain->mining_result);
242         transform_result =
243           (pterrain->transform_result == pterrain
244            || pterrain->transform_result == T_NONE
245            || effect_cumulative_max(EFT_TRANSFORM_POSSIBLE, &for_terr) <= 0) ? ""
246           : terrain_name_translation(pterrain->transform_result);
247         /* Use get_internal_string_length() for correct alignment with
248          * multibyte character encodings */
249         cat_snprintf(outbuf, outlen,
250             "%s%*s %3s %s%*s %3s %s%*s %3s %s\n",
251             terrain,
252             MAX(0, 12 - (int)get_internal_string_length(terrain)), "",
253             (pterrain->irrigation_result == T_NONE) ? "-" : irrigation_time,
254             irrigation_result,
255             MAX(0, 12 - (int)get_internal_string_length(irrigation_result)), "",
256             (pterrain->mining_result == T_NONE) ? "-" : mining_time,
257             mining_result,
258             MAX(0, 12 - (int)get_internal_string_length(mining_result)), "",
259             (pterrain->transform_result == T_NONE) ? "-" : transform_time,
260             transform_result);
261 
262         if (clean_pollution_time != 0 && pterrain->clean_pollution_time != 0) {
263           if (clean_pollution_time < 0) {
264             clean_pollution_time = pterrain->clean_pollution_time;
265           } else {
266             if (clean_pollution_time != pterrain->clean_pollution_time) {
267               clean_pollution_time = 0; /* give up */
268             }
269           }
270         }
271         if (clean_fallout_time != 0 && pterrain->clean_fallout_time != 0) {
272           if (clean_fallout_time < 0) {
273             clean_fallout_time = pterrain->clean_fallout_time;
274           } else {
275             if (clean_fallout_time != pterrain->clean_fallout_time) {
276               clean_fallout_time = 0; /* give up */
277             }
278           }
279         }
280         if (pillage_time != 0 && pterrain->pillage_time != 0) {
281           if (pillage_time < 0) {
282             pillage_time = pterrain->pillage_time;
283           } else {
284             if (pillage_time != pterrain->pillage_time) {
285               pillage_time = 0; /* give up */
286             }
287           }
288         }
289       }
290     } terrain_type_iterate_end;
291 
292     /* Examine extras to see if time of removal activities really is
293      * terrain-independent, and take into account removal_time_factor.
294      * XXX: this is rather overwrought to handle cases which the ruleset
295      *      author could express much more simply for the same result */
296     {
297       int time = -1, factor = -1;
298       extra_type_by_rmcause_iterate(ERM_CLEANPOLLUTION, pextra) {
299         if (pextra->removal_time == 0) {
300           if (factor < 0) {
301             factor = pextra->removal_time_factor;
302           } else if (factor != pextra->removal_time_factor) {
303             factor = 0; /* give up */
304           }
305         } else {
306           if (time < 0) {
307             time = pextra->removal_time;
308           } else if (time != pextra->removal_time) {
309             time = 0; /* give up */
310           }
311         }
312       } extra_type_by_rmcause_iterate_end;
313       if (factor < 0) {
314         /* No extra has terrain-dependent clean time; use extra's time */
315         if (time >= 0) {
316           clean_pollution_time = time;
317         } else {
318           clean_pollution_time = 0;
319         }
320       } else if (clean_pollution_time != 0) {
321         /* At least one extra's time depends on terrain */
322         fc_assert(clean_pollution_time > 0);
323         if (time > 0 && factor > 0 && time != clean_pollution_time * factor) {
324           clean_pollution_time = 0;
325         } else if (time >= 0) {
326           clean_pollution_time = time;
327         } else if (factor >= 0) {
328           clean_pollution_time = clean_pollution_time * factor;
329         } else {
330           fc_assert(FALSE);
331         }
332       }
333     }
334 
335     {
336       int time = -1, factor = -1;
337       extra_type_by_rmcause_iterate(ERM_CLEANFALLOUT, pextra) {
338         if (pextra->removal_time == 0) {
339           if (factor < 0) {
340             factor = pextra->removal_time_factor;
341           } else if (factor != pextra->removal_time_factor) {
342             factor = 0; /* give up */
343           }
344         } else {
345           if (time < 0) {
346             time = pextra->removal_time;
347           } else if (time != pextra->removal_time) {
348             time = 0; /* give up */
349           }
350         }
351       } extra_type_by_rmcause_iterate_end;
352       if (factor < 0) {
353         /* No extra has terrain-dependent clean time; use extra's time */
354         if (time >= 0) {
355           clean_fallout_time = time;
356         } else {
357           clean_fallout_time = 0;
358         }
359       } else if (clean_fallout_time != 0) {
360         /* At least one extra's time depends on terrain */
361         fc_assert(clean_fallout_time > 0);
362         if (time > 0 && factor > 0 && time != clean_fallout_time * factor) {
363           clean_fallout_time = 0;
364         } else if (time >= 0) {
365           clean_fallout_time = time;
366         } else if (factor >= 0) {
367           clean_fallout_time = clean_fallout_time * factor;
368         } else {
369           fc_assert(FALSE);
370         }
371       }
372     }
373 
374     {
375       int time = -1, factor = -1;
376       extra_type_by_rmcause_iterate(ERM_PILLAGE, pextra) {
377         if (pextra->removal_time == 0) {
378           if (factor < 0) {
379             factor = pextra->removal_time_factor;
380           } else if (factor != pextra->removal_time_factor) {
381             factor = 0; /* give up */
382           }
383         } else {
384           if (time < 0) {
385             time = pextra->removal_time;
386           } else if (time != pextra->removal_time) {
387             time = 0; /* give up */
388           }
389         }
390       } extra_type_by_rmcause_iterate_end;
391       if (factor < 0) {
392         /* No extra has terrain-dependent pillage time; use extra's time */
393         if (time >= 0) {
394           pillage_time = time;
395         } else {
396           pillage_time = 0;
397         }
398       } else if (pillage_time != 0) {
399         /* At least one extra's time depends on terrain */
400         fc_assert(pillage_time > 0);
401         if (time > 0 && factor > 0 && time != pillage_time * factor) {
402           pillage_time = 0;
403         } else if (time >= 0) {
404           pillage_time = time;
405         } else if (factor >= 0) {
406           pillage_time = pillage_time * factor;
407         } else {
408           fc_assert(FALSE);
409         }
410       }
411     }
412 
413     /* Check whether there are any bases or roads whose build time is
414      * independent of terrain */
415 
416     extra_type_by_cause_iterate(EC_BASE, pextra) {
417       if (pextra->buildable && pextra->build_time > 0) {
418         terrain_independent_extras = TRUE;
419         break;
420       }
421     } extra_type_by_cause_iterate_end;
422     if (!terrain_independent_extras) {
423       extra_type_by_cause_iterate(EC_ROAD, pextra) {
424         if (pextra->buildable && pextra->build_time > 0) {
425           terrain_independent_extras = TRUE;
426           break;
427         }
428       } extra_type_by_cause_iterate_end;
429     }
430 
431     if (clean_pollution_time > 0 || clean_fallout_time > 0 || pillage_time > 0
432         || terrain_independent_extras) {
433       CATLSTR(outbuf, outlen, "\n");
434       CATLSTR(outbuf, outlen,
435               _("Time taken for the following activities is independent of "
436                 "terrain:\n"));
437       CATLSTR(outbuf, outlen, "\n");
438       CATLSTR(outbuf, outlen,
439               /* TRANS: Header for fixed-width terrain alteration table.
440                * TRANS: Translators cannot change column widths :( */
441               _("Activity            Time\n"));
442       CATLSTR(outbuf, outlen,
443               "---------------------------");
444       if (clean_pollution_time > 0)
445 	cat_snprintf(outbuf, outlen,
446 		     _("\nClean pollution    %3d"), clean_pollution_time);
447       if (clean_fallout_time > 0)
448 	cat_snprintf(outbuf, outlen,
449 		     _("\nClean fallout      %3d"), clean_fallout_time);
450       if (pillage_time > 0)
451 	cat_snprintf(outbuf, outlen,
452 		     _("\nPillage            %3d"), pillage_time);
453       extra_type_by_cause_iterate(EC_ROAD, pextra) {
454         if (pextra->buildable && pextra->build_time > 0) {
455           const char *rname = extra_name_translation(pextra);
456 
457           cat_snprintf(outbuf, outlen,
458                        "\n%s%*s %3d",
459                        rname,
460                        MAX(0, 18 - (int)get_internal_string_length(rname)), "",
461                        pextra->build_time);
462         }
463       } extra_type_by_cause_iterate_end;
464       extra_type_by_cause_iterate(EC_BASE, pextra) {
465         if (pextra->buildable && pextra->build_time > 0) {
466           const char *bname = extra_name_translation(pextra);
467 
468           cat_snprintf(outbuf, outlen,
469                        "\n%s%*s %3d",
470                        bname,
471                        MAX(0, 18 - (int)get_internal_string_length(bname)), "",
472                        pextra->build_time);
473         }
474       } extra_type_by_cause_iterate_end;
475     }
476     return TRUE;
477   } else if (0 == strcmp(name, "VeteranLevels")) {
478     return insert_veteran_help(outbuf, outlen, game.veteran,
479         _("In this ruleset, the following veteran levels are defined:"),
480         _("This ruleset has no default veteran levels defined."));
481   } else if (0 == strcmp(name, "FreecivVersion")) {
482     const char *ver = freeciv_name_version();
483 
484     cat_snprintf(outbuf, outlen,
485                  /* TRANS: First %s is version string, e.g.,
486                   * "Freeciv version 2.3.0-beta1 (beta version)" (translated).
487                   * Second %s is client_string, e.g., "gui-gtk-2.0". */
488                  _("This is %s, %s client."), ver, client_string);
489     insert_client_build_info(outbuf, outlen);
490 
491     return TRUE;
492   } else if (0 == strcmp(name, "DefaultMetaserver")) {
493     cat_snprintf(outbuf, outlen, "  %s", FREECIV_META_URL);
494 
495     return TRUE;
496   }
497   log_error("Unknown directive '$%s' in help", name);
498   return FALSE;
499 }
500 
501 /****************************************************************
502   Append text for the requirement.  Something like
503 
504     "Requires knowledge of the technology Communism.\n"
505 
506   pplayer may be NULL.  Note that it must be updated everytime
507   a new requirement type or range is defined.
508 *****************************************************************/
insert_requirement(char * buf,size_t bufsz,struct player * pplayer,const struct requirement * preq,const char * prefix)509 static bool insert_requirement(char *buf, size_t bufsz,
510                                struct player *pplayer,
511                                const struct requirement *preq,
512                                const char *prefix)
513 {
514   if (preq->quiet) {
515     return FALSE;
516   }
517 
518   switch (preq->source.kind) {
519   case VUT_NONE:
520     return FALSE;
521 
522   case VUT_ADVANCE:
523     switch (preq->range) {
524     case REQ_RANGE_PLAYER:
525       CATLSTR(buf, bufsz, prefix);
526       if (preq->present) {
527         cat_snprintf(buf, bufsz,
528                      _("Requires knowledge of the technology %s.\n"),
529                      advance_name_translation(preq->source.value.advance));
530       } else {
531         cat_snprintf(buf, bufsz,
532                      _("Prevented by knowledge of the technology %s.\n"),
533                      advance_name_translation(preq->source.value.advance));
534       }
535       return TRUE;
536     case REQ_RANGE_TEAM:
537       CATLSTR(buf, bufsz, prefix);
538       if (preq->present) {
539         cat_snprintf(buf, bufsz,
540                      _("Requires that a player on your team knows the "
541                        "technology %s.\n"),
542                      advance_name_translation(preq->source.value.advance));
543       } else {
544         cat_snprintf(buf, bufsz,
545                      _("Prevented if any player on your team knows the "
546                        "technology %s.\n"),
547                      advance_name_translation(preq->source.value.advance));
548       }
549       return TRUE;
550     case REQ_RANGE_ALLIANCE:
551       CATLSTR(buf, bufsz, prefix);
552       if (preq->present) {
553         cat_snprintf(buf, bufsz,
554                      _("Requires that a player allied to you knows the "
555                        "technology %s.\n"),
556                      advance_name_translation(preq->source.value.advance));
557       } else {
558         cat_snprintf(buf, bufsz,
559                      _("Prevented if any player allied to you knows the "
560                        "technology %s.\n"),
561                      advance_name_translation(preq->source.value.advance));
562       }
563       return TRUE;
564     case REQ_RANGE_WORLD:
565       CATLSTR(buf, bufsz, prefix);
566       if (preq->survives) {
567         if (preq->present) {
568           cat_snprintf(buf, bufsz,
569                        _("Requires that someone has discovered the "
570                          "technology %s.\n"),
571                        advance_name_translation(preq->source.value.advance));
572         } else {
573           cat_snprintf(buf, bufsz,
574                        _("Requires that no-one has yet discovered the "
575                         "technology %s.\n"),
576                        advance_name_translation(preq->source.value.advance));
577         }
578       } else {
579         if (preq->present) {
580           cat_snprintf(buf, bufsz,
581                        _("Requires that some player knows the "
582                          "technology %s.\n"),
583                        advance_name_translation(preq->source.value.advance));
584         } else {
585           cat_snprintf(buf, bufsz,
586                        _("Requires that no player knows the "
587                          "technology %s.\n"),
588                        advance_name_translation(preq->source.value.advance));
589         }
590       }
591       return TRUE;
592     case REQ_RANGE_LOCAL:
593     case REQ_RANGE_CADJACENT:
594     case REQ_RANGE_ADJACENT:
595     case REQ_RANGE_CITY:
596     case REQ_RANGE_TRADEROUTE:
597     case REQ_RANGE_CONTINENT:
598     case REQ_RANGE_COUNT:
599       /* Not supported. */
600       break;
601     }
602     break;
603 
604   case VUT_TECHFLAG:
605     switch (preq->range) {
606     case REQ_RANGE_PLAYER:
607       CATLSTR(buf, bufsz, prefix);
608       if (preq->present) {
609         cat_snprintf(buf, bufsz,
610                      /* TRANS: %s is a (translatable) tech flag. */
611                      _("Requires knowledge of a technology with the "
612                        "\"%s\" flag.\n"),
613                      tech_flag_id_translated_name(preq->source.value.techflag));
614       } else {
615         cat_snprintf(buf, bufsz,
616                      /* TRANS: %s is a (translatable) tech flag. */
617                      _("Prevented by knowledge of any technology with the "
618                        "\"%s\" flag.\n"),
619                      tech_flag_id_translated_name(preq->source.value.techflag));
620       }
621       return TRUE;
622     case REQ_RANGE_TEAM:
623       CATLSTR(buf, bufsz, prefix);
624       if (preq->present) {
625         cat_snprintf(buf, bufsz,
626                      /* TRANS: %s is a (translatable) tech flag. */
627                      _("Requires that a player on your team knows "
628                        "a technology with the \"%s\" flag.\n"),
629                      tech_flag_id_translated_name(preq->source.value.techflag));
630       } else {
631         cat_snprintf(buf, bufsz,
632                      /* TRANS: %s is a (translatable) tech flag. */
633                      _("Prevented if any player on your team knows "
634                        "any technology with the \"%s\" flag.\n"),
635                      tech_flag_id_translated_name(preq->source.value.techflag));
636       }
637       return TRUE;
638     case REQ_RANGE_ALLIANCE:
639       CATLSTR(buf, bufsz, prefix);
640       if (preq->present) {
641         cat_snprintf(buf, bufsz,
642                      /* TRANS: %s is a (translatable) tech flag. */
643                      _("Requires that a player allied to you knows "
644                        "a technology with the \"%s\" flag.\n"),
645                      tech_flag_id_translated_name(preq->source.value.techflag));
646       } else {
647         cat_snprintf(buf, bufsz,
648                      /* TRANS: %s is a (translatable) tech flag. */
649                      _("Prevented if any player allied to you knows "
650                        "any technology with the \"%s\" flag.\n"),
651                      tech_flag_id_translated_name(preq->source.value.techflag));
652       }
653       return TRUE;
654     case REQ_RANGE_WORLD:
655       CATLSTR(buf, bufsz, prefix);
656       if (preq->present) {
657         cat_snprintf(buf, bufsz,
658                      /* TRANS: %s is a (translatable) tech flag. */
659                      _("Requires that some player knows a technology "
660                        "with the \"%s\" flag.\n"),
661                      tech_flag_id_translated_name(preq->source.value.techflag));
662       } else {
663         cat_snprintf(buf, bufsz,
664                      /* TRANS: %s is a (translatable) tech flag. */
665                      _("Requires that no player knows any technology with "
666                        "the \"%s\" flag.\n"),
667                      tech_flag_id_translated_name(preq->source.value.techflag));
668       }
669       return TRUE;
670     case REQ_RANGE_LOCAL:
671     case REQ_RANGE_CADJACENT:
672     case REQ_RANGE_ADJACENT:
673     case REQ_RANGE_CITY:
674     case REQ_RANGE_TRADEROUTE:
675     case REQ_RANGE_CONTINENT:
676     case REQ_RANGE_COUNT:
677       /* Not supported. */
678       break;
679     }
680     break;
681 
682   case VUT_GOVERNMENT:
683     if (preq->range != REQ_RANGE_PLAYER) {
684       break;
685     }
686     CATLSTR(buf, bufsz, prefix);
687     if (preq->present) {
688       cat_snprintf(buf, bufsz, _("Requires the %s government.\n"),
689                    government_name_translation(preq->source.value.govern));
690     } else {
691       cat_snprintf(buf, bufsz, _("Not available under the %s government.\n"),
692                    government_name_translation(preq->source.value.govern));
693     }
694     return TRUE;
695 
696   case VUT_ACHIEVEMENT:
697     switch (preq->range) {
698     case REQ_RANGE_PLAYER:
699       CATLSTR(buf, bufsz, prefix);
700       if (preq->present) {
701         cat_snprintf(buf, bufsz, _("Requires you to have achieved \"%s\".\n"),
702                      achievement_name_translation(preq->source.value.achievement));
703       } else {
704         cat_snprintf(buf, bufsz, _("Not available once you have achieved "
705                                    "\"%s\".\n"),
706                      achievement_name_translation(preq->source.value.achievement));
707       }
708       return TRUE;
709     case REQ_RANGE_TEAM:
710       CATLSTR(buf, bufsz, prefix);
711       if (preq->present) {
712         cat_snprintf(buf, bufsz, _("Requires that at least one of your "
713                                    "team-mates has achieved \"%s\".\n"),
714                      achievement_name_translation(preq->source.value.achievement));
715       } else {
716         cat_snprintf(buf, bufsz, _("Not available if any of your team-mates "
717                                    "has achieved \"%s\".\n"),
718                      achievement_name_translation(preq->source.value.achievement));
719       }
720       return TRUE;
721     case REQ_RANGE_ALLIANCE:
722       CATLSTR(buf, bufsz, prefix);
723       if (preq->present) {
724         cat_snprintf(buf, bufsz, _("Requires that at least one of your allies "
725                                    "has achieved \"%s\".\n"),
726                      achievement_name_translation(preq->source.value.achievement));
727       } else {
728         cat_snprintf(buf, bufsz, _("Not available if any of your allies has "
729                                    "achieved \"%s\".\n"),
730                      achievement_name_translation(preq->source.value.achievement));
731       }
732       return TRUE;
733     case REQ_RANGE_WORLD:
734       CATLSTR(buf, bufsz, prefix);
735       if (preq->present) {
736         cat_snprintf(buf, bufsz, _("Requires that at least one player "
737                                    "has achieved \"%s\".\n"),
738                      achievement_name_translation(preq->source.value.achievement));
739       } else {
740         cat_snprintf(buf, bufsz, _("Not available if any player has "
741                                    "achieved \"%s\".\n"),
742                      achievement_name_translation(preq->source.value.achievement));
743       }
744       return TRUE;
745     case REQ_RANGE_LOCAL:
746     case REQ_RANGE_CADJACENT:
747     case REQ_RANGE_ADJACENT:
748     case REQ_RANGE_CITY:
749     case REQ_RANGE_TRADEROUTE:
750     case REQ_RANGE_CONTINENT:
751     case REQ_RANGE_COUNT:
752       /* Not supported. */
753       break;
754     }
755     break;
756 
757   case VUT_IMPROVEMENT:
758     switch (preq->range) {
759     case REQ_RANGE_WORLD:
760       if (is_great_wonder(preq->source.value.building)) {
761         CATLSTR(buf, bufsz, prefix);
762         if (preq->survives) {
763           if (preq->present) {
764             if (can_improvement_go_obsolete(preq->source.value.building)) {
765               cat_snprintf(buf, bufsz,
766                            /* TRANS: %s is a wonder */
767                            _("Requires that %s was built at some point, "
768                              "and that it has not yet been rendered "
769                              "obsolete.\n"),
770                            improvement_name_translation
771                            (preq->source.value.building));
772             } else {
773               cat_snprintf(buf, bufsz,
774                            /* TRANS: %s is a wonder */
775                            _("Requires that %s was built at some point.\n"),
776                            improvement_name_translation
777                            (preq->source.value.building));
778             }
779           } else {
780             if (can_improvement_go_obsolete(preq->source.value.building)) {
781               cat_snprintf(buf, bufsz,
782                            /* TRANS: %s is a wonder */
783                            _("Prevented if %s has ever been built, "
784                              "unless it would be obsolete.\n"),
785                            improvement_name_translation
786                            (preq->source.value.building));
787             } else {
788               cat_snprintf(buf, bufsz,
789                            /* TRANS: %s is a wonder */
790                            _("Prevented if %s has ever been built.\n"),
791                            improvement_name_translation
792                            (preq->source.value.building));
793             }
794           }
795         } else {
796           /* Non-surviving requirement */
797           if (preq->present) {
798             if (can_improvement_go_obsolete(preq->source.value.building)) {
799               cat_snprintf(buf, bufsz,
800                            /* TRANS: %s is a wonder */
801                            _("Requires %s to be owned by any player "
802                              "and not yet obsolete.\n"),
803                            improvement_name_translation
804                            (preq->source.value.building));
805             } else {
806               cat_snprintf(buf, bufsz,
807                            /* TRANS: %s is a wonder */
808                            _("Requires %s to be owned by any player.\n"),
809                            improvement_name_translation
810                            (preq->source.value.building));
811             }
812           } else {
813             if (can_improvement_go_obsolete(preq->source.value.building)) {
814               cat_snprintf(buf, bufsz,
815                            /* TRANS: %s is a wonder */
816                            _("Prevented if %s is currently owned by "
817                              "any player, unless it is obsolete.\n"),
818                            improvement_name_translation
819                            (preq->source.value.building));
820             } else {
821               cat_snprintf(buf, bufsz,
822                            /* TRANS: %s is a wonder */
823                            _("Prevented if %s is currently owned by "
824                              "any player.\n"),
825                            improvement_name_translation
826                            (preq->source.value.building));
827             }
828           }
829         }
830         return TRUE;
831       }
832       /* non-great-wonder world-ranged requirements not supported */
833       break;
834     case REQ_RANGE_ALLIANCE:
835       if (is_wonder(preq->source.value.building)) {
836         CATLSTR(buf, bufsz, prefix);
837         if (preq->survives) {
838           if (preq->present) {
839             if (can_improvement_go_obsolete(preq->source.value.building)) {
840               cat_snprintf(buf, bufsz,
841                            /* TRANS: %s is a wonder */
842                            _("Requires someone who is currently allied to "
843                              "you to have built %s at some point, and for "
844                              "it not to have been rendered obsolete.\n"),
845                            improvement_name_translation
846                            (preq->source.value.building));
847             } else {
848               cat_snprintf(buf, bufsz,
849                            /* TRANS: %s is a wonder */
850                            _("Requires someone who is currently allied to "
851                              "you to have built %s at some point.\n"),
852                            improvement_name_translation
853                            (preq->source.value.building));
854             }
855           } else {
856             if (can_improvement_go_obsolete(preq->source.value.building)) {
857               cat_snprintf(buf, bufsz,
858                            /* TRANS: %s is a wonder */
859                            _("Prevented if someone currently allied to you "
860                              "has ever built %s, unless it would be "
861                              "obsolete.\n"),
862                            improvement_name_translation
863                            (preq->source.value.building));
864             } else {
865               cat_snprintf(buf, bufsz,
866                            /* TRANS: %s is a wonder */
867                            _("Prevented if someone currently allied to you "
868                              "has ever built %s.\n"),
869                            improvement_name_translation
870                            (preq->source.value.building));
871             }
872           }
873         } else {
874           /* Non-surviving requirement */
875           if (preq->present) {
876             if (can_improvement_go_obsolete(preq->source.value.building)) {
877               cat_snprintf(buf, bufsz,
878                            /* TRANS: %s is a wonder */
879                            _("Requires someone allied to you to own %s, "
880                              "and for it not to have been rendered "
881                              "obsolete.\n"),
882                            improvement_name_translation
883                            (preq->source.value.building));
884             } else {
885               cat_snprintf(buf, bufsz,
886                            /* TRANS: %s is a wonder */
887                            _("Requires someone allied to you to own %s.\n"),
888                            improvement_name_translation
889                            (preq->source.value.building));
890             }
891           } else {
892             if (can_improvement_go_obsolete(preq->source.value.building)) {
893               cat_snprintf(buf, bufsz,
894                            /* TRANS: %s is a wonder */
895                            _("Prevented if someone allied to you owns %s, "
896                              "unless it is obsolete.\n"),
897                            improvement_name_translation
898                            (preq->source.value.building));
899             } else {
900               cat_snprintf(buf, bufsz,
901                            /* TRANS: %s is a wonder */
902                            _("Prevented if someone allied to you owns %s.\n"),
903                            improvement_name_translation
904                            (preq->source.value.building));
905             }
906           }
907         }
908         return TRUE;
909       }
910       /* non-wonder alliance-ranged requirements not supported */
911       break;
912     case REQ_RANGE_TEAM:
913       if (is_wonder(preq->source.value.building)) {
914         CATLSTR(buf, bufsz, prefix);
915         if (preq->survives) {
916           if (preq->present) {
917             if (can_improvement_go_obsolete(preq->source.value.building)) {
918               cat_snprintf(buf, bufsz,
919                            /* TRANS: %s is a wonder */
920                            _("Requires someone on your team to have "
921                              "built %s at some point, and for it not "
922                              "to have been rendered obsolete.\n"),
923                            improvement_name_translation
924                            (preq->source.value.building));
925             } else {
926               cat_snprintf(buf, bufsz,
927                            /* TRANS: %s is a wonder */
928                            _("Requires someone on your team to have "
929                              "built %s at some point.\n"),
930                            improvement_name_translation
931                            (preq->source.value.building));
932             }
933           } else {
934             if (can_improvement_go_obsolete(preq->source.value.building)) {
935               cat_snprintf(buf, bufsz,
936                            /* TRANS: %s is a wonder */
937                            _("Prevented if someone on your team has ever "
938                              "built %s, unless it would be obsolete.\n"),
939                            improvement_name_translation
940                            (preq->source.value.building));
941             } else {
942               cat_snprintf(buf, bufsz,
943                            /* TRANS: %s is a wonder */
944                            _("Prevented if someone on your team has ever "
945                              "built %s.\n"),
946                            improvement_name_translation
947                            (preq->source.value.building));
948             }
949           }
950         } else {
951           /* Non-surviving requirement */
952           if (preq->present) {
953             if (can_improvement_go_obsolete(preq->source.value.building)) {
954               cat_snprintf(buf, bufsz,
955                            /* TRANS: %s is a wonder */
956                            _("Requires someone on your team to own %s, "
957                              "and for it not to have been rendered "
958                              "obsolete.\n"),
959                            improvement_name_translation
960                            (preq->source.value.building));
961             } else {
962               cat_snprintf(buf, bufsz,
963                            /* TRANS: %s is a wonder */
964                            _("Requires someone on your team to own %s.\n"),
965                            improvement_name_translation
966                            (preq->source.value.building));
967             }
968           } else {
969             if (can_improvement_go_obsolete(preq->source.value.building)) {
970               cat_snprintf(buf, bufsz,
971                            /* TRANS: %s is a wonder */
972                            _("Prevented if someone on your team owns %s, "
973                              "unless it is obsolete.\n"),
974                            improvement_name_translation
975                            (preq->source.value.building));
976             } else {
977               cat_snprintf(buf, bufsz,
978                            /* TRANS: %s is a wonder */
979                            _("Prevented if someone on your team owns %s.\n"),
980                            improvement_name_translation
981                            (preq->source.value.building));
982             }
983           }
984         }
985         return TRUE;
986       }
987       /* non-wonder team-ranged requirements not supported */
988       break;
989     case REQ_RANGE_PLAYER:
990       if (is_wonder(preq->source.value.building)) {
991         CATLSTR(buf, bufsz, prefix);
992         if (preq->survives) {
993           if (preq->present) {
994             if (can_improvement_go_obsolete(preq->source.value.building)) {
995               cat_snprintf(buf, bufsz,
996                            /* TRANS: %s is a wonder */
997                            _("Requires you to have built %s at some point, "
998                              "and for it not to have been rendered "
999                              "obsolete.\n"),
1000                            improvement_name_translation
1001                            (preq->source.value.building));
1002             } else {
1003               cat_snprintf(buf, bufsz,
1004                            /* TRANS: %s is a wonder */
1005                            _("Requires you to have built %s at some point.\n"),
1006                            improvement_name_translation
1007                            (preq->source.value.building));
1008             }
1009           } else {
1010             if (can_improvement_go_obsolete(preq->source.value.building)) {
1011               cat_snprintf(buf, bufsz,
1012                            /* TRANS: %s is a wonder */
1013                            _("Prevented if you have ever built %s, "
1014                              "unless it would be obsolete.\n"),
1015                            improvement_name_translation
1016                            (preq->source.value.building));
1017             } else {
1018               cat_snprintf(buf, bufsz,
1019                            /* TRANS: %s is a wonder */
1020                            _("Prevented if you have ever built %s.\n"),
1021                            improvement_name_translation
1022                            (preq->source.value.building));
1023             }
1024           }
1025         } else {
1026           /* Non-surviving requirement */
1027           if (preq->present) {
1028             if (can_improvement_go_obsolete(preq->source.value.building)) {
1029               cat_snprintf(buf, bufsz,
1030                            /* TRANS: %s is a wonder */
1031                            _("Requires you to own %s, which must not "
1032                              "be obsolete.\n"),
1033                            improvement_name_translation
1034                            (preq->source.value.building));
1035             } else {
1036               cat_snprintf(buf, bufsz,
1037                            /* TRANS: %s is a wonder */
1038                            _("Requires you to own %s.\n"),
1039                            improvement_name_translation
1040                            (preq->source.value.building));
1041             }
1042           } else {
1043             if (can_improvement_go_obsolete(preq->source.value.building)) {
1044               cat_snprintf(buf, bufsz,
1045                            /* TRANS: %s is a wonder */
1046                            _("Prevented if you own %s, unless it is "
1047                              "obsolete.\n"),
1048                            improvement_name_translation
1049                            (preq->source.value.building));
1050             } else {
1051               cat_snprintf(buf, bufsz,
1052                            /* TRANS: %s is a wonder */
1053                            _("Prevented if you own %s.\n"),
1054                            improvement_name_translation
1055                            (preq->source.value.building));
1056             }
1057           }
1058         }
1059         return TRUE;
1060       }
1061       /* non-wonder player-ranged requirements not supported */
1062       break;
1063     case REQ_RANGE_CONTINENT:
1064       if (is_wonder(preq->source.value.building)) {
1065         CATLSTR(buf, bufsz, prefix);
1066         if (preq->present) {
1067           if (can_improvement_go_obsolete(preq->source.value.building)) {
1068             cat_snprintf(buf, bufsz,
1069                          /* TRANS: %s is a wonder */
1070                          _("Requires %s in one of your cities on the same "
1071                            "continent, and not yet obsolete.\n"),
1072                          improvement_name_translation
1073                          (preq->source.value.building));
1074           } else {
1075             cat_snprintf(buf, bufsz,
1076                          /* TRANS: %s is a wonder */
1077                          _("Requires %s in one of your cities on the same "
1078                            "continent.\n"),
1079                          improvement_name_translation
1080                          (preq->source.value.building));
1081           }
1082         } else {
1083           if (can_improvement_go_obsolete(preq->source.value.building)) {
1084             cat_snprintf(buf, bufsz,
1085                          /* TRANS: %s is a wonder */
1086                          _("Prevented if %s is in one of your cities on the "
1087                            "same continent, unless it is obsolete.\n"),
1088                          improvement_name_translation
1089                          (preq->source.value.building));
1090           } else {
1091             cat_snprintf(buf, bufsz,
1092                          /* TRANS: %s is a wonder */
1093                          _("Prevented if %s is in one of your cities on the "
1094                            "same continent.\n"),
1095                          improvement_name_translation
1096                          (preq->source.value.building));
1097           }
1098         }
1099         return TRUE;
1100       }
1101       /* surviving or non-wonder continent-ranged requirements not supported */
1102       break;
1103     case REQ_RANGE_TRADEROUTE:
1104       CATLSTR(buf, bufsz, prefix);
1105       if (preq->present) {
1106         if (can_improvement_go_obsolete(preq->source.value.building)) {
1107           /* Should only apply to wonders */
1108           cat_snprintf(buf, bufsz,
1109                        /* TRANS: %s is a building or wonder */
1110                        _("Requires %s in the city or a trade partner "
1111                          "(and not yet obsolete).\n"),
1112                        improvement_name_translation
1113                        (preq->source.value.building));
1114         } else {
1115           cat_snprintf(buf, bufsz,
1116                        /* TRANS: %s is a building or wonder */
1117                        _("Requires %s in the city or a trade partner.\n"),
1118                        improvement_name_translation
1119                        (preq->source.value.building));
1120         }
1121       } else {
1122         if (can_improvement_go_obsolete(preq->source.value.building)) {
1123           /* Should only apply to wonders */
1124           cat_snprintf(buf, bufsz,
1125                        /* TRANS: %s is a building or wonder */
1126                        _("Prevented by %s in the city or a trade partner "
1127                          "(unless it is obsolete).\n"),
1128                        improvement_name_translation
1129                        (preq->source.value.building));
1130         } else {
1131           cat_snprintf(buf, bufsz,
1132                        /* TRANS: %s is a building or wonder */
1133                        _("Prevented by %s in the city or a trade partner.\n"),
1134                        improvement_name_translation
1135                        (preq->source.value.building));
1136         }
1137       }
1138       return TRUE;
1139     case REQ_RANGE_CITY:
1140       CATLSTR(buf, bufsz, prefix);
1141       if (preq->present) {
1142         if (can_improvement_go_obsolete(preq->source.value.building)) {
1143           /* Should only apply to wonders */
1144           cat_snprintf(buf, bufsz,
1145                        /* TRANS: %s is a building or wonder */
1146                        _("Requires %s in the city (and not yet obsolete).\n"),
1147                        improvement_name_translation
1148                        (preq->source.value.building));
1149         } else {
1150           cat_snprintf(buf, bufsz,
1151                        /* TRANS: %s is a building or wonder */
1152                        _("Requires %s in the city.\n"),
1153                        improvement_name_translation
1154                        (preq->source.value.building));
1155         }
1156       } else {
1157         if (can_improvement_go_obsolete(preq->source.value.building)) {
1158           /* Should only apply to wonders */
1159           cat_snprintf(buf, bufsz,
1160                        /* TRANS: %s is a building or wonder */
1161                        _("Prevented by %s in the city (unless it is "
1162                          "obsolete).\n"),
1163                        improvement_name_translation
1164                        (preq->source.value.building));
1165         } else {
1166           cat_snprintf(buf, bufsz,
1167                        /* TRANS: %s is a building or wonder */
1168                        _("Prevented by %s in the city.\n"),
1169                        improvement_name_translation
1170                        (preq->source.value.building));
1171         }
1172       }
1173       return TRUE;
1174     case REQ_RANGE_LOCAL:
1175       CATLSTR(buf, bufsz, prefix);
1176       if (preq->present) {
1177         cat_snprintf(buf, bufsz,
1178                      _("Only applies to \"%s\" buildings.\n"),
1179                      improvement_name_translation
1180                      (preq->source.value.building));
1181       } else {
1182         cat_snprintf(buf, bufsz,
1183                      _("Does not apply to \"%s\" buildings.\n"),
1184                      improvement_name_translation
1185                      (preq->source.value.building));
1186       }
1187       return TRUE;
1188     case REQ_RANGE_CADJACENT:
1189     case REQ_RANGE_ADJACENT:
1190     case REQ_RANGE_COUNT:
1191       /* Not supported. */
1192       break;
1193     }
1194     break;
1195 
1196   case VUT_EXTRA:
1197     switch (preq->range) {
1198     case REQ_RANGE_LOCAL:
1199       CATLSTR(buf, bufsz, prefix);
1200       if (preq->present) {
1201         cat_snprintf(buf, bufsz,
1202                      Q_("?extra:Requires %s on the tile.\n"),
1203                      extra_name_translation(preq->source.value.extra));
1204       } else {
1205         cat_snprintf(buf, bufsz,
1206                      Q_("?extra:Prevented by %s on the tile.\n"),
1207                      extra_name_translation(preq->source.value.extra));
1208       }
1209       return TRUE;
1210     case REQ_RANGE_CADJACENT:
1211       CATLSTR(buf, bufsz, prefix);
1212       if (preq->present) {
1213         cat_snprintf(buf, bufsz,
1214                      Q_("?extra:Requires %s on the tile or a cardinally "
1215                         "adjacent tile.\n"),
1216                      extra_name_translation(preq->source.value.extra));
1217         } else {
1218         cat_snprintf(buf, bufsz,
1219                      Q_("?extra:Prevented by %s on the tile or any cardinally "
1220                         "adjacent tile.\n"),
1221                      extra_name_translation(preq->source.value.extra));
1222         }
1223       return TRUE;
1224     case REQ_RANGE_ADJACENT:
1225       CATLSTR(buf, bufsz, prefix);
1226       if (preq->present) {
1227         cat_snprintf(buf, bufsz,
1228                      Q_("?extra:Requires %s on the tile or an adjacent "
1229                         "tile.\n"),
1230                      extra_name_translation(preq->source.value.extra));
1231       } else {
1232         cat_snprintf(buf, bufsz,
1233                      Q_("?extra:Prevented by %s on the tile or any adjacent "
1234                         "tile.\n"),
1235                      extra_name_translation(preq->source.value.extra));
1236       }
1237       return TRUE;
1238     case REQ_RANGE_CITY:
1239       CATLSTR(buf, bufsz, prefix);
1240       if (preq->present) {
1241         cat_snprintf(buf, bufsz,
1242                      Q_("?extra:Requires %s on a tile within the city "
1243                         "radius.\n"),
1244                      extra_name_translation(preq->source.value.extra));
1245       } else {
1246         cat_snprintf(buf, bufsz,
1247                      Q_("?extra:Prevented by %s on any tile within the city "
1248                         "radius.\n"),
1249                      extra_name_translation(preq->source.value.extra));
1250       }
1251       return TRUE;
1252     case REQ_RANGE_TRADEROUTE:
1253       CATLSTR(buf, bufsz, prefix);
1254       if (preq->present) {
1255         cat_snprintf(buf, bufsz,
1256                      Q_("?extra:Requires %s on a tile within the city "
1257                         "radius, or the city radius of a trade partner.\n"),
1258                      extra_name_translation(preq->source.value.extra));
1259       } else {
1260         cat_snprintf(buf, bufsz,
1261                      Q_("?extra:Prevented by %s on any tile within the city "
1262                         "radius or the city radius of a trade partner.\n"),
1263                      extra_name_translation(preq->source.value.extra));
1264       }
1265       return TRUE;
1266     case REQ_RANGE_CONTINENT:
1267     case REQ_RANGE_PLAYER:
1268     case REQ_RANGE_TEAM:
1269     case REQ_RANGE_ALLIANCE:
1270     case REQ_RANGE_WORLD:
1271     case REQ_RANGE_COUNT:
1272       /* Not supported. */
1273       break;
1274     }
1275     break;
1276 
1277   case VUT_TERRAIN:
1278     switch (preq->range) {
1279     case REQ_RANGE_LOCAL:
1280       CATLSTR(buf, bufsz, prefix);
1281       if (preq->present) {
1282         cat_snprintf(buf, bufsz, Q_("?terrain:Requires %s on the tile.\n"),
1283                      terrain_name_translation(preq->source.value.terrain));
1284       } else {
1285         cat_snprintf(buf, bufsz, Q_("?terrain:Prevented by %s on the tile.\n"),
1286                      terrain_name_translation(preq->source.value.terrain));
1287       }
1288       return TRUE;
1289     case REQ_RANGE_CADJACENT:
1290       CATLSTR(buf, bufsz, prefix);
1291       if (preq->present) {
1292         cat_snprintf(buf, bufsz,
1293                      Q_("?terrain:Requires %s on the tile or a cardinally "
1294                         "adjacent tile.\n"),
1295                      terrain_name_translation(preq->source.value.terrain));
1296       } else {
1297         cat_snprintf(buf, bufsz,
1298                      Q_("?terrain:Prevented by %s on the tile or any "
1299                         "cardinally adjacent tile.\n"),
1300                      terrain_name_translation(preq->source.value.terrain));
1301       }
1302       return TRUE;
1303     case REQ_RANGE_ADJACENT:
1304       CATLSTR(buf, bufsz, prefix);
1305       if (preq->present) {
1306         cat_snprintf(buf, bufsz,
1307                      Q_("?terrain:Requires %s on the tile or an adjacent "
1308                         "tile.\n"),
1309                      terrain_name_translation(preq->source.value.terrain));
1310       } else {
1311         cat_snprintf(buf, bufsz,
1312                      Q_("?terrain:Prevented by %s on the tile or any "
1313                         "adjacent tile.\n"),
1314                      terrain_name_translation(preq->source.value.terrain));
1315       }
1316       return TRUE;
1317     case REQ_RANGE_CITY:
1318       CATLSTR(buf, bufsz, prefix);
1319       if (preq->present) {
1320         cat_snprintf(buf, bufsz,
1321                      Q_("?terrain:Requires %s on a tile within the city "
1322                         "radius.\n"),
1323                      terrain_name_translation(preq->source.value.terrain));
1324       } else {
1325         cat_snprintf(buf, bufsz,
1326                      Q_("?terrain:Prevented by %s on any tile within the city "
1327                         "radius.\n"),
1328                      terrain_name_translation(preq->source.value.terrain));
1329       }
1330       return TRUE;
1331     case REQ_RANGE_TRADEROUTE:
1332       CATLSTR(buf, bufsz, prefix);
1333       if (preq->present) {
1334         cat_snprintf(buf, bufsz,
1335                      Q_("?terrain:Requires %s on a tile within the city "
1336                         "radius, or the city radius of a trade partner.\n"),
1337                      terrain_name_translation(preq->source.value.terrain));
1338       } else {
1339         cat_snprintf(buf, bufsz,
1340                      Q_("?terrain:Prevented by %s on any tile within the city "
1341                         "radius or the city radius of a trade partner.\n"),
1342                      terrain_name_translation(preq->source.value.terrain));
1343       }
1344       return TRUE;
1345     case REQ_RANGE_CONTINENT:
1346     case REQ_RANGE_PLAYER:
1347     case REQ_RANGE_TEAM:
1348     case REQ_RANGE_ALLIANCE:
1349     case REQ_RANGE_WORLD:
1350     case REQ_RANGE_COUNT:
1351       /* Not supported. */
1352       break;
1353     }
1354     break;
1355 
1356   case VUT_RESOURCE:
1357     switch (preq->range) {
1358     case REQ_RANGE_LOCAL:
1359       CATLSTR(buf, bufsz, prefix);
1360       if (preq->present) {
1361         cat_snprintf(buf, bufsz,
1362                      Q_("?resource:Requires %s on the tile.\n"),
1363                      resource_name_translation(preq->source.value.resource));
1364       } else {
1365         cat_snprintf(buf, bufsz,
1366                      Q_("?resource:Prevented by %s on the tile.\n"),
1367                      resource_name_translation(preq->source.value.resource));
1368       }
1369       return TRUE;
1370     case REQ_RANGE_CADJACENT:
1371       CATLSTR(buf, bufsz, prefix);
1372       if (preq->present) {
1373         cat_snprintf(buf, bufsz,
1374                      Q_("?resource:Requires %s on the tile or a cardinally "
1375                         "adjacent tile.\n"),
1376                      resource_name_translation(preq->source.value.resource));
1377       } else {
1378         cat_snprintf(buf, bufsz,
1379                      Q_("?resource:Prevented by %s on the tile or any "
1380                         "cardinally adjacent tile.\n"),
1381                      resource_name_translation(preq->source.value.resource));
1382       }
1383       return TRUE;
1384     case REQ_RANGE_ADJACENT:
1385       CATLSTR(buf, bufsz, prefix);
1386       if (preq->present) {
1387         cat_snprintf(buf, bufsz,
1388                      Q_("?resource:Requires %s on the tile or an adjacent "
1389                         "tile.\n"),
1390                      resource_name_translation(preq->source.value.resource));
1391       } else {
1392         cat_snprintf(buf, bufsz,
1393                      Q_("?resource:Prevented by %s on the tile or any "
1394                         "adjacent tile.\n"),
1395                      resource_name_translation(preq->source.value.resource));
1396       }
1397       return TRUE;
1398     case REQ_RANGE_CITY:
1399       CATLSTR(buf, bufsz, prefix);
1400       if (preq->present) {
1401         cat_snprintf(buf, bufsz,
1402                      Q_("?resource:Requires %s on a tile within the "
1403                         "city radius.\n"),
1404                      resource_name_translation(preq->source.value.resource));
1405       } else {
1406         cat_snprintf(buf, bufsz,
1407                      Q_("?resource:Prevented by %s on any tile within the "
1408                         "city radius.\n"),
1409                      resource_name_translation(preq->source.value.resource));
1410       }
1411       return TRUE;
1412     case REQ_RANGE_TRADEROUTE:
1413       CATLSTR(buf, bufsz, prefix);
1414       if (preq->present) {
1415         cat_snprintf(buf, bufsz,
1416                      Q_("?resource:Requires %s on a tile within the "
1417                         "city radius or the city radius of a trade partner.\n"),
1418                      resource_name_translation(preq->source.value.resource));
1419       } else {
1420         cat_snprintf(buf, bufsz,
1421                      Q_("?resource:Prevented by %s on any tile within the "
1422                         "city radius or the city radius of a trade partner.\n"),
1423                      resource_name_translation(preq->source.value.resource));
1424       }
1425       return TRUE;
1426     case REQ_RANGE_CONTINENT:
1427     case REQ_RANGE_PLAYER:
1428     case REQ_RANGE_TEAM:
1429     case REQ_RANGE_ALLIANCE:
1430     case REQ_RANGE_WORLD:
1431     case REQ_RANGE_COUNT:
1432       /* Not supported. */
1433       break;
1434     }
1435     break;
1436 
1437   case VUT_NATION:
1438     switch (preq->range) {
1439     case REQ_RANGE_PLAYER:
1440       CATLSTR(buf, bufsz, prefix);
1441       if (preq->present) {
1442         cat_snprintf(buf, bufsz,
1443                      /* TRANS: "... playing as the Swedes." */
1444                      _("Requires that you are playing as the %s.\n"),
1445                      nation_plural_translation(preq->source.value.nation));
1446       } else {
1447         cat_snprintf(buf, bufsz,
1448                      /* TRANS: "... playing as the Turks." */
1449                      _("Requires that you are not playing as the %s.\n"),
1450                      nation_plural_translation(preq->source.value.nation));
1451       }
1452       return TRUE;
1453     case REQ_RANGE_TEAM:
1454       CATLSTR(buf, bufsz, prefix);
1455       if (preq->present) {
1456         cat_snprintf(buf, bufsz,
1457                      /* TRANS: "... same team as the Indonesians." */
1458                      _("Requires that you are on the same team as "
1459                        "the %s.\n"),
1460                      nation_plural_translation(preq->source.value.nation));
1461       } else {
1462         cat_snprintf(buf, bufsz,
1463                      /* TRANS: "... same team as the Greeks." */
1464                      _("Requires that you are not on the same team as "
1465                        "the %s.\n"),
1466                      nation_plural_translation(preq->source.value.nation));
1467       }
1468       return TRUE;
1469     case REQ_RANGE_ALLIANCE:
1470       CATLSTR(buf, bufsz, prefix);
1471       if (preq->present) {
1472         cat_snprintf(buf, bufsz,
1473                      /* TRANS: "... allied with the Koreans." */
1474                      _("Requires that you are allied with the %s.\n"),
1475                      nation_plural_translation(preq->source.value.nation));
1476       } else {
1477         cat_snprintf(buf, bufsz,
1478                      /* TRANS: "... allied with the Danes." */
1479                      _("Requires that you are not allied with the %s.\n"),
1480                      nation_plural_translation(preq->source.value.nation));
1481       }
1482       return TRUE;
1483     case REQ_RANGE_WORLD:
1484       CATLSTR(buf, bufsz, prefix);
1485       if (preq->survives) {
1486         if (preq->present) {
1487           cat_snprintf(buf, bufsz,
1488                        /* TRANS: "Requires the Apaches to have ..." */
1489                        _("Requires the %s to have been in the game.\n"),
1490                        nation_plural_translation(preq->source.value.nation));
1491         } else {
1492           cat_snprintf(buf, bufsz,
1493                        /* TRANS: "Requires the Celts never to have ..." */
1494                        _("Requires the %s never to have been in the "
1495                          "game.\n"),
1496                        nation_plural_translation(preq->source.value.nation));
1497         }
1498       } else {
1499         if (preq->present) {
1500           cat_snprintf(buf, bufsz,
1501                        /* TRANS: "Requires the Belgians in the game." */
1502                        _("Requires the %s in the game.\n"),
1503                        nation_plural_translation(preq->source.value.nation));
1504         } else {
1505           cat_snprintf(buf, bufsz,
1506                        /* TRANS: "Requires that the Russians are not ... */
1507                        _("Requires that the %s are not in the game.\n"),
1508                        nation_plural_translation(preq->source.value.nation));
1509         }
1510       }
1511       return TRUE;
1512     case REQ_RANGE_LOCAL:
1513     case REQ_RANGE_CADJACENT:
1514     case REQ_RANGE_ADJACENT:
1515     case REQ_RANGE_CITY:
1516     case REQ_RANGE_TRADEROUTE:
1517     case REQ_RANGE_CONTINENT:
1518     case REQ_RANGE_COUNT:
1519       /* Not supported. */
1520       break;
1521     }
1522     break;
1523 
1524   case VUT_NATIONGROUP:
1525     switch (preq->range) {
1526     case REQ_RANGE_PLAYER:
1527       CATLSTR(buf, bufsz, prefix);
1528       if (preq->present) {
1529         cat_snprintf(buf, bufsz,
1530                      /* TRANS: nation group: "... playing African nation." */
1531                      _("Requires that you are playing %s nation.\n"),
1532                      nation_group_name_translation(preq->source.value.nationgroup));
1533       } else {
1534         cat_snprintf(buf, bufsz,
1535                      /* TRANS: nation group: "... playing Imaginary nation." */
1536                      _("Prevented if you are playing %s nation.\n"),
1537                      nation_group_name_translation(preq->source.value.nationgroup));
1538       }
1539       return TRUE;
1540     case REQ_RANGE_TEAM:
1541       CATLSTR(buf, bufsz, prefix);
1542       if (preq->present) {
1543         cat_snprintf(buf, bufsz,
1544                      /* TRANS: nation group: "Requires Medieval nation ..." */
1545                      _("Requires %s nation on your team.\n"),
1546                      nation_group_name_translation(preq->source.value.nationgroup));
1547       } else {
1548         cat_snprintf(buf, bufsz,
1549                      /* TRANS: nation group: "Prevented by Medieval nation ..." */
1550                      _("Prevented by %s nation on your team.\n"),
1551                      nation_group_name_translation(preq->source.value.nationgroup));
1552       }
1553       return TRUE;
1554     case REQ_RANGE_ALLIANCE:
1555       CATLSTR(buf, bufsz, prefix);
1556       if (preq->present) {
1557         cat_snprintf(buf, bufsz,
1558                      /* TRANS: nation group: "Requires Modern nation ..." */
1559                      _("Requires %s nation in alliance with you.\n"),
1560                      nation_group_name_translation(preq->source.value.nationgroup));
1561       } else {
1562         cat_snprintf(buf, bufsz,
1563                      /* TRANS: nation group: "Prevented by Modern nation ..." */
1564                      _("Prevented if %s nation is in alliance with you.\n"),
1565                      nation_group_name_translation(preq->source.value.nationgroup));
1566       }
1567       return TRUE;
1568     case REQ_RANGE_WORLD:
1569       CATLSTR(buf, bufsz, prefix);
1570       if (preq->present) {
1571         cat_snprintf(buf, bufsz,
1572                      /* TRANS: nation group: "Requires Asian nation ..." */
1573                      _("Requires %s nation in the game.\n"),
1574                      nation_group_name_translation(preq->source.value.nationgroup));
1575       } else {
1576         cat_snprintf(buf, bufsz,
1577                      /* TRANS: nation group: "Prevented by Asian nation ..." */
1578                      _("Prevented by %s nation in the game.\n"),
1579                      nation_group_name_translation(preq->source.value.nationgroup));
1580       }
1581       return TRUE;
1582     case REQ_RANGE_LOCAL:
1583     case REQ_RANGE_CADJACENT:
1584     case REQ_RANGE_ADJACENT:
1585     case REQ_RANGE_CITY:
1586     case REQ_RANGE_TRADEROUTE:
1587     case REQ_RANGE_CONTINENT:
1588     case REQ_RANGE_COUNT:
1589       /* Not supported. */
1590       break;
1591     }
1592     break;
1593 
1594   case VUT_STYLE:
1595     if (preq->range != REQ_RANGE_PLAYER) {
1596       break;
1597     }
1598     CATLSTR(buf, bufsz, prefix);
1599     if (preq->present) {
1600       cat_snprintf(buf, bufsz,
1601                    /* TRANS: "Requires that you are playing Asian style
1602                     * nation." */
1603                    _("Requires that you are playing %s style nation.\n"),
1604                    style_name_translation(preq->source.value.style));
1605     } else {
1606       cat_snprintf(buf, bufsz,
1607                    /* TRANS: "Requires that you are not playing Classical
1608                     * style nation." */
1609                    _("Requires that you are not playing %s style nation.\n"),
1610                    style_name_translation(preq->source.value.style));
1611     }
1612     return TRUE;
1613 
1614   case VUT_NATIONALITY:
1615     switch (preq->range) {
1616     case REQ_RANGE_TRADEROUTE:
1617       CATLSTR(buf, bufsz, prefix);
1618       if (preq->present) {
1619         cat_snprintf(buf, bufsz,
1620                      /* TRANS: "Requires at least one Barbarian citizen ..." */
1621                      _("Requires at least one %s citizen in the city or a "
1622                        "trade partner.\n"),
1623                      nation_adjective_translation(preq->source.value.nationality));
1624       } else {
1625         cat_snprintf(buf, bufsz,
1626                      /* TRANS: "... no Pirate citizens ..." */
1627                      _("Requires that there are no %s citizens in "
1628                        "the city or any trade partners.\n"),
1629                      nation_adjective_translation(preq->source.value.nationality));
1630       }
1631       return TRUE;
1632     case REQ_RANGE_CITY:
1633       CATLSTR(buf, bufsz, prefix);
1634       if (preq->present) {
1635         cat_snprintf(buf, bufsz,
1636                      /* TRANS: "Requires at least one Barbarian citizen ..." */
1637                      _("Requires at least one %s citizen in the city.\n"),
1638                      nation_adjective_translation(preq->source.value.nationality));
1639       } else {
1640         cat_snprintf(buf, bufsz,
1641                      /* TRANS: "... no Pirate citizens ..." */
1642                      _("Requires that there are no %s citizens in "
1643                        "the city.\n"),
1644                      nation_adjective_translation(preq->source.value.nationality));
1645       }
1646       return TRUE;
1647     case REQ_RANGE_WORLD:
1648     case REQ_RANGE_ALLIANCE:
1649     case REQ_RANGE_TEAM:
1650     case REQ_RANGE_PLAYER:
1651     case REQ_RANGE_LOCAL:
1652     case REQ_RANGE_CADJACENT:
1653     case REQ_RANGE_ADJACENT:
1654     case REQ_RANGE_CONTINENT:
1655     case REQ_RANGE_COUNT:
1656       /* Not supported. */
1657       break;
1658     }
1659     break;
1660 
1661   case VUT_DIPLREL:
1662     switch (preq->range) {
1663     case REQ_RANGE_PLAYER:
1664       CATLSTR(buf, bufsz, prefix);
1665       if (preq->present) {
1666         cat_snprintf(buf, bufsz,
1667                      /* TRANS: in this and following strings, '%s' can be one
1668                       * of a wide range of relationships; e.g., 'Peace',
1669                       * 'Never met', 'Is foreign', 'Hosts embassy',
1670                       * 'Provided Casus Belli' */
1671                      _("Requires that you have the relationship '%s' with at "
1672                        "least one other living player.\n"),
1673                      diplrel_name_translation(preq->source.value.diplrel));
1674       } else {
1675         cat_snprintf(buf, bufsz,
1676                      _("Requires that you do not have the relationship '%s' "
1677                        "with any living player.\n"),
1678                      diplrel_name_translation(preq->source.value.diplrel));
1679       }
1680       return TRUE;
1681     case REQ_RANGE_TEAM:
1682       CATLSTR(buf, bufsz, prefix);
1683       if (preq->present) {
1684         cat_snprintf(buf, bufsz,
1685                      _("Requires that somebody on your team has the "
1686                        "relationship '%s' with at least one other living "
1687                        "player.\n"),
1688                      diplrel_name_translation(preq->source.value.diplrel));
1689       } else {
1690         cat_snprintf(buf, bufsz,
1691                      _("Requires that nobody on your team has the "
1692                        "relationship '%s' with any living player.\n"),
1693                      diplrel_name_translation(preq->source.value.diplrel));
1694       }
1695       return TRUE;
1696     case REQ_RANGE_ALLIANCE:
1697       CATLSTR(buf, bufsz, prefix);
1698       if (preq->present) {
1699         cat_snprintf(buf, bufsz,
1700                      _("Requires that somebody in your alliance has the "
1701                        "relationship '%s' with at least one other living "
1702                        "player.\n"),
1703                      diplrel_name_translation(preq->source.value.diplrel));
1704       } else {
1705         cat_snprintf(buf, bufsz,
1706                      _("Requires that nobody in your alliance has the "
1707                        "relationship '%s' with any living player.\n"),
1708                      diplrel_name_translation(preq->source.value.diplrel));
1709       }
1710       return TRUE;
1711     case REQ_RANGE_WORLD:
1712       CATLSTR(buf, bufsz, prefix);
1713       if (preq->present) {
1714         cat_snprintf(buf, bufsz,
1715                      _("Requires the relationship '%s' between two living "
1716                        "players.\n"),
1717                      diplrel_name_translation(preq->source.value.diplrel));
1718       } else {
1719         cat_snprintf(buf, bufsz,
1720                      _("Requires that no two living players have the "
1721                        "relationship '%s'.\n"),
1722                      diplrel_name_translation(preq->source.value.diplrel));
1723       }
1724       return TRUE;
1725     case REQ_RANGE_LOCAL:
1726       CATLSTR(buf, bufsz, prefix);
1727       if (preq->present) {
1728         cat_snprintf(buf, bufsz,
1729                      _("Requires that you have the relationship '%s' with the "
1730                        "other player.\n"),
1731                      diplrel_name_translation(preq->source.value.diplrel));
1732       } else {
1733         cat_snprintf(buf, bufsz,
1734                      _("Requires that you do not have the relationship '%s' "
1735                        "with the other player.\n"),
1736                      diplrel_name_translation(preq->source.value.diplrel));
1737       }
1738       return TRUE;
1739     case REQ_RANGE_CADJACENT:
1740     case REQ_RANGE_ADJACENT:
1741     case REQ_RANGE_CITY:
1742     case REQ_RANGE_TRADEROUTE:
1743     case REQ_RANGE_CONTINENT:
1744     case REQ_RANGE_COUNT:
1745       /* Not supported. */
1746       break;
1747     }
1748     break;
1749 
1750   case VUT_UTYPE:
1751     switch (preq->range) {
1752     case REQ_RANGE_LOCAL:
1753       CATLSTR(buf, bufsz, prefix);
1754       if (preq->present) {
1755         /* TRANS: %s is a single kind of unit (e.g., "Settlers"). */
1756         cat_snprintf(buf, bufsz, Q_("?unit:Requires %s.\n"),
1757                      utype_name_translation(preq->source.value.utype));
1758       } else {
1759         /* TRANS: %s is a single kind of unit (e.g., "Settlers"). */
1760         cat_snprintf(buf, bufsz, Q_("?unit:Does not apply to %s.\n"),
1761                      utype_name_translation(preq->source.value.utype));
1762       }
1763       return TRUE;
1764     case REQ_RANGE_CADJACENT:
1765     case REQ_RANGE_ADJACENT:
1766     case REQ_RANGE_CITY:
1767     case REQ_RANGE_TRADEROUTE:
1768     case REQ_RANGE_CONTINENT:
1769     case REQ_RANGE_PLAYER:
1770     case REQ_RANGE_TEAM:
1771     case REQ_RANGE_ALLIANCE:
1772     case REQ_RANGE_WORLD:
1773     case REQ_RANGE_COUNT:
1774       /* Not supported. */
1775       break;
1776     }
1777     break;
1778 
1779   case VUT_UTFLAG:
1780     switch (preq->range) {
1781     case REQ_RANGE_LOCAL:
1782       {
1783         struct astring astr = ASTRING_INIT;
1784 
1785          /* Unit type flags mean nothing to users. Explicitly list the unit
1786          * types with those flags. */
1787         if (role_units_translations(&astr, preq->source.value.unitflag,
1788                                     TRUE)) {
1789           CATLSTR(buf, bufsz, prefix);
1790           if (preq->present) {
1791             /* TRANS: %s is a list of unit types separated by "or". */
1792             cat_snprintf(buf, bufsz, Q_("?ulist:Requires %s.\n"),
1793                          astr_str(&astr));
1794           } else {
1795             /* TRANS: %s is a list of unit types separated by "or". */
1796             cat_snprintf(buf, bufsz, Q_("?ulist:Does not apply to %s.\n"),
1797                          astr_str(&astr));
1798           }
1799           astr_free(&astr);
1800           return TRUE;
1801         }
1802       }
1803       break;
1804     case REQ_RANGE_CADJACENT:
1805     case REQ_RANGE_ADJACENT:
1806     case REQ_RANGE_CITY:
1807     case REQ_RANGE_TRADEROUTE:
1808     case REQ_RANGE_CONTINENT:
1809     case REQ_RANGE_PLAYER:
1810     case REQ_RANGE_TEAM:
1811     case REQ_RANGE_ALLIANCE:
1812     case REQ_RANGE_WORLD:
1813     case REQ_RANGE_COUNT:
1814       /* Not supported. */
1815       break;
1816     }
1817     break;
1818 
1819   case VUT_UCLASS:
1820     switch (preq->range) {
1821     case REQ_RANGE_LOCAL:
1822       CATLSTR(buf, bufsz, prefix);
1823       if (preq->present) {
1824         /* TRANS: %s is a single unit class (e.g., "Air"). */
1825         cat_snprintf(buf, bufsz, Q_("?uclass:Requires %s units.\n"),
1826                      uclass_name_translation(preq->source.value.uclass));
1827       } else {
1828         /* TRANS: %s is a single unit class (e.g., "Air"). */
1829         cat_snprintf(buf, bufsz, Q_("?uclass:Does not apply to %s units.\n"),
1830                      uclass_name_translation(preq->source.value.uclass));
1831       }
1832       return TRUE;
1833     case REQ_RANGE_CADJACENT:
1834     case REQ_RANGE_ADJACENT:
1835     case REQ_RANGE_CITY:
1836     case REQ_RANGE_TRADEROUTE:
1837     case REQ_RANGE_CONTINENT:
1838     case REQ_RANGE_PLAYER:
1839     case REQ_RANGE_TEAM:
1840     case REQ_RANGE_ALLIANCE:
1841     case REQ_RANGE_WORLD:
1842     case REQ_RANGE_COUNT:
1843       /* Not supported. */
1844       break;
1845     }
1846     break;
1847 
1848   case VUT_UCFLAG:
1849     {
1850       const char *classes[uclass_count()];
1851       int i = 0;
1852       bool done = FALSE;
1853       struct astring list = ASTRING_INIT;
1854 
1855       unit_class_iterate(uclass) {
1856         if (uclass_has_flag(uclass, preq->source.value.unitclassflag)) {
1857           classes[i++] = uclass_name_translation(uclass);
1858         }
1859       } unit_class_iterate_end;
1860       astr_build_or_list(&list, classes, i);
1861 
1862       switch (preq->range) {
1863       case REQ_RANGE_LOCAL:
1864         CATLSTR(buf, bufsz, prefix);
1865         if (preq->present) {
1866           /* TRANS: %s is a list of unit classes separated by "or". */
1867           cat_snprintf(buf, bufsz, Q_("?uclasslist:Requires %s units.\n"),
1868                        astr_str(&list));
1869         } else {
1870           /* TRANS: %s is a list of unit classes separated by "or". */
1871           cat_snprintf(buf, bufsz, Q_("?uclasslist:Does not apply to "
1872                                       "%s units.\n"),
1873                        astr_str(&list));
1874         }
1875         done = TRUE;
1876         break;
1877       case REQ_RANGE_CADJACENT:
1878       case REQ_RANGE_ADJACENT:
1879       case REQ_RANGE_CITY:
1880       case REQ_RANGE_TRADEROUTE:
1881       case REQ_RANGE_CONTINENT:
1882       case REQ_RANGE_PLAYER:
1883       case REQ_RANGE_TEAM:
1884       case REQ_RANGE_ALLIANCE:
1885       case REQ_RANGE_WORLD:
1886       case REQ_RANGE_COUNT:
1887         /* Not supported. */
1888         break;
1889       }
1890       astr_free(&list);
1891       if (done) {
1892         return TRUE;
1893       }
1894     }
1895     break;
1896 
1897   case VUT_UNITSTATE:
1898     {
1899       switch (preq->range) {
1900       case REQ_RANGE_LOCAL:
1901         switch (preq->source.value.unit_state) {
1902         case USP_TRANSPORTED:
1903           CATLSTR(buf, bufsz, prefix);
1904           if (preq->present) {
1905             cat_snprintf(buf, bufsz,
1906                          _("Requires that the unit is transported.\n"));
1907           } else {
1908             cat_snprintf(buf, bufsz,
1909                          _("Requires that the unit isn't transported.\n"));
1910           }
1911           return TRUE;
1912         case USP_LIVABLE_TILE:
1913           CATLSTR(buf, bufsz, prefix);
1914           if (preq->present) {
1915             cat_snprintf(buf, bufsz,
1916                          _("Requires that the unit is on livable tile.\n"));
1917           } else {
1918             cat_snprintf(buf, bufsz,
1919                          _("Requires that the unit isn't on livable tile.\n"));
1920           }
1921           return TRUE;
1922         case USP_COUNT:
1923           fc_assert_msg(preq->source.value.unit_state != USP_COUNT,
1924                         "Invalid unit state property.");
1925         }
1926         break;
1927       case REQ_RANGE_CADJACENT:
1928       case REQ_RANGE_ADJACENT:
1929       case REQ_RANGE_CITY:
1930       case REQ_RANGE_TRADEROUTE:
1931       case REQ_RANGE_CONTINENT:
1932       case REQ_RANGE_PLAYER:
1933       case REQ_RANGE_TEAM:
1934       case REQ_RANGE_ALLIANCE:
1935       case REQ_RANGE_WORLD:
1936       case REQ_RANGE_COUNT:
1937         /* Not supported. */
1938         break;
1939       }
1940     }
1941     break;
1942 
1943   case VUT_MINMOVES:
1944     {
1945       switch (preq->range) {
1946       case REQ_RANGE_LOCAL:
1947         CATLSTR(buf, bufsz, prefix);
1948         if (preq->present) {
1949           cat_snprintf(buf, bufsz,
1950                        /* %s is numeric move points; it may have a
1951                         * fractional part ("1 1/3 MP"). */
1952                        _("Requires that the unit has at least %s MP left.\n"),
1953                        move_points_text(preq->source.value.minmoves, TRUE));
1954         } else {
1955           cat_snprintf(buf, bufsz,
1956                        /* %s is numeric move points; it may have a
1957                         * fractional part ("1 1/3 MP"). */
1958                        _("Requires that the unit has less than %s MP left.\n"),
1959                        move_points_text(preq->source.value.minmoves, TRUE));
1960         }
1961         return TRUE;
1962       case REQ_RANGE_CADJACENT:
1963       case REQ_RANGE_ADJACENT:
1964       case REQ_RANGE_CITY:
1965       case REQ_RANGE_TRADEROUTE:
1966       case REQ_RANGE_CONTINENT:
1967       case REQ_RANGE_PLAYER:
1968       case REQ_RANGE_TEAM:
1969       case REQ_RANGE_ALLIANCE:
1970       case REQ_RANGE_WORLD:
1971       case REQ_RANGE_COUNT:
1972         /* Not supported. */
1973         break;
1974       }
1975     }
1976     break;
1977 
1978   case VUT_MINVETERAN:
1979     if (preq->range != REQ_RANGE_LOCAL) {
1980       break;
1981     }
1982     CATLSTR(buf, bufsz, prefix);
1983     /* FIXME: this would be better with veteran level names, but that's
1984      * potentially unit type dependent. */
1985     if (preq->present) {
1986       cat_snprintf(buf, bufsz,
1987                    PL_("Requires a unit with at least %d veteran level.\n",
1988                        "Requires a unit with at least %d veteran levels.\n",
1989                        preq->source.value.minveteran),
1990                    preq->source.value.minveteran);
1991     } else {
1992       cat_snprintf(buf, bufsz,
1993                    PL_("Requires a unit with fewer than %d veteran level.\n",
1994                        "Requires a unit with fewer than %d veteran levels.\n",
1995                        preq->source.value.minveteran),
1996                    preq->source.value.minveteran);
1997     }
1998     return TRUE;
1999 
2000     case VUT_MINHP:
2001       if (preq->range != REQ_RANGE_LOCAL) {
2002         break;
2003       }
2004 
2005       CATLSTR(buf, bufsz, prefix);
2006       if (preq->present) {
2007         cat_snprintf(buf, bufsz,
2008                      PL_("Requires a unit with at least %d hit point left.\n",
2009                          "Requires a unit with at least %d hit points left.\n",
2010                          preq->source.value.min_hit_points),
2011                      preq->source.value.min_hit_points);
2012       } else {
2013         cat_snprintf(buf, bufsz,
2014                      PL_("Requires a unit with fewer than %d hit point "
2015                          "left.\n",
2016                          "Requires a unit with fewer than %d hit points "
2017                          "left.\n",
2018                          preq->source.value.min_hit_points),
2019                      preq->source.value.min_hit_points);
2020       }
2021       return TRUE;
2022 
2023   case VUT_OTYPE:
2024     if (preq->range != REQ_RANGE_LOCAL) {
2025       break;
2026     }
2027     CATLSTR(buf, bufsz, prefix);
2028     if (preq->present) {
2029       /* TRANS: "Applies only to Food." */
2030       cat_snprintf(buf, bufsz, Q_("?output:Applies only to %s.\n"),
2031                    get_output_name(preq->source.value.outputtype));
2032     } else {
2033       /* TRANS: "Does not apply to Food." */
2034       cat_snprintf(buf, bufsz, Q_("?output:Does not apply to %s.\n"),
2035                    get_output_name(preq->source.value.outputtype));
2036     }
2037     return TRUE;
2038 
2039   case VUT_SPECIALIST:
2040     if (preq->range != REQ_RANGE_LOCAL) {
2041       break;
2042     }
2043     CATLSTR(buf, bufsz, prefix);
2044     if (preq->present) {
2045       /* TRANS: "Applies only to Scientists." */
2046       cat_snprintf(buf, bufsz, Q_("?specialist:Applies only to %s.\n"),
2047                    specialist_plural_translation(preq->source.value.specialist));
2048     } else {
2049       /* TRANS: "Does not apply to Scientists." */
2050       cat_snprintf(buf, bufsz, Q_("?specialist:Does not apply to %s.\n"),
2051                    specialist_plural_translation(preq->source.value.specialist));
2052     }
2053     return TRUE;
2054 
2055   case VUT_MINSIZE:
2056     switch (preq->range) {
2057     case REQ_RANGE_TRADEROUTE:
2058       CATLSTR(buf, bufsz, prefix);
2059       if (preq->present) {
2060         cat_snprintf(buf, bufsz,
2061                      PL_("Requires a minimum city size of %d for this "
2062                          "city or a trade partner.\n",
2063                          "Requires a minimum city size of %d for this "
2064                          "city or a trade partner.\n",
2065                          preq->source.value.minsize),
2066                      preq->source.value.minsize);
2067       } else {
2068         cat_snprintf(buf, bufsz,
2069                      PL_("Requires the city size to be less than %d "
2070                          "for this city and all trade partners.\n",
2071                          "Requires the city size to be less than %d "
2072                          "for this city and all trade partners.\n",
2073                          preq->source.value.minsize),
2074                      preq->source.value.minsize);
2075       }
2076       return TRUE;
2077     case REQ_RANGE_CITY:
2078       CATLSTR(buf, bufsz, prefix);
2079       if (preq->present) {
2080         cat_snprintf(buf, bufsz,
2081                      PL_("Requires a minimum city size of %d.\n",
2082                          "Requires a minimum city size of %d.\n",
2083                          preq->source.value.minsize),
2084                      preq->source.value.minsize);
2085       } else {
2086         cat_snprintf(buf, bufsz,
2087                      PL_("Requires the city size to be less than %d.\n",
2088                          "Requires the city size to be less than %d.\n",
2089                          preq->source.value.minsize),
2090                    preq->source.value.minsize);
2091       }
2092       return TRUE;
2093     case REQ_RANGE_LOCAL:
2094     case REQ_RANGE_CADJACENT:
2095     case REQ_RANGE_ADJACENT:
2096     case REQ_RANGE_CONTINENT:
2097     case REQ_RANGE_PLAYER:
2098     case REQ_RANGE_TEAM:
2099     case REQ_RANGE_ALLIANCE:
2100     case REQ_RANGE_WORLD:
2101     case REQ_RANGE_COUNT:
2102       /* Not supported. */
2103       break;
2104     }
2105     break;
2106 
2107   case VUT_MINCULTURE:
2108     switch (preq->range) {
2109     case REQ_RANGE_CITY:
2110       CATLSTR(buf, bufsz, prefix);
2111       if (preq->present) {
2112         cat_snprintf(buf, bufsz,
2113                      PL_("Requires a minimum culture of %d in the city.\n",
2114                          "Requires a minimum culture of %d in the city.\n",
2115                          preq->source.value.minculture),
2116                      preq->source.value.minculture);
2117       } else {
2118         cat_snprintf(buf, bufsz,
2119                      PL_("Requires the culture in the city to be less "
2120                          "than %d.\n",
2121                          "Requires the culture in the city to be less "
2122                          "than %d.\n",
2123                          preq->source.value.minculture),
2124                      preq->source.value.minculture);
2125       }
2126       return TRUE;
2127     case REQ_RANGE_TRADEROUTE:
2128       CATLSTR(buf, bufsz, prefix);
2129       if (preq->present) {
2130         cat_snprintf(buf, bufsz,
2131                      PL_("Requires a minimum culture of %d in this city or "
2132                          "a trade partner.\n",
2133                          "Requires a minimum culture of %d in this city or "
2134                          "a trade partner.\n",
2135                          preq->source.value.minculture),
2136                       preq->source.value.minculture);
2137       } else {
2138         cat_snprintf(buf, bufsz,
2139                      PL_("Requires the culture in this city and all trade "
2140                          "partners to be less than %d.\n",
2141                          "Requires the culture in this city and all trade "
2142                          "partners to be less than %d.\n",
2143                          preq->source.value.minculture),
2144                       preq->source.value.minculture);
2145       }
2146       return TRUE;
2147     case REQ_RANGE_PLAYER:
2148       CATLSTR(buf, bufsz, prefix);
2149       if (preq->present) {
2150         cat_snprintf(buf, bufsz,
2151                      PL_("Requires your nation to have culture "
2152                          "of at least %d.\n",
2153                          "Requires your nation to have culture "
2154                          "of at least %d.\n",
2155                          preq->source.value.minculture),
2156                      preq->source.value.minculture);
2157       } else {
2158         cat_snprintf(buf, bufsz,
2159                      PL_("Prevented if your nation has culture of "
2160                          "%d or more.\n",
2161                          "Prevented if your nation has culture of "
2162                          "%d or more.\n",
2163                          preq->source.value.minculture),
2164                      preq->source.value.minculture);
2165       }
2166       return TRUE;
2167     case REQ_RANGE_TEAM:
2168       CATLSTR(buf, bufsz, prefix);
2169       if (preq->present) {
2170         cat_snprintf(buf, bufsz,
2171                      PL_("Requires someone on your team to have culture of "
2172                          "at least %d.\n",
2173                          "Requires someone on your team to have culture of "
2174                          "at least %d.\n",
2175                          preq->source.value.minculture),
2176                      preq->source.value.minculture);
2177       } else {
2178         cat_snprintf(buf, bufsz,
2179                      PL_("Prevented if anyone on your team has culture of "
2180                          "%d or more.\n",
2181                          "Prevented if anyone on your team has culture of "
2182                          "%d or more.\n",
2183                          preq->source.value.minculture),
2184                      preq->source.value.minculture);
2185       }
2186       return TRUE;
2187     case REQ_RANGE_ALLIANCE:
2188       CATLSTR(buf, bufsz, prefix);
2189       if (preq->present) {
2190         cat_snprintf(buf, bufsz,
2191                      PL_("Requires someone in your current alliance to "
2192                          "have culture of at least %d.\n",
2193                          "Requires someone in your current alliance to "
2194                          "have culture of at least %d.\n",
2195                          preq->source.value.minculture),
2196                      preq->source.value.minculture);
2197       } else {
2198         cat_snprintf(buf, bufsz,
2199                      PL_("Prevented if anyone in your current alliance has "
2200                          "culture of %d or more.\n",
2201                          "Prevented if anyone in your current alliance has "
2202                          "culture of %d or more.\n",
2203                          preq->source.value.minculture),
2204                      preq->source.value.minculture);
2205       }
2206       return TRUE;
2207     case REQ_RANGE_WORLD:
2208       CATLSTR(buf, bufsz, prefix);
2209       if (preq->present) {
2210         cat_snprintf(buf, bufsz,
2211                      PL_("Requires that some player has culture of at "
2212                          "least %d.\n",
2213                          "Requires that some player has culture of at "
2214                          "least %d.\n",
2215                          preq->source.value.minculture),
2216                      preq->source.value.minculture);
2217       } else {
2218         cat_snprintf(buf, bufsz,
2219                      PL_("Requires that no player has culture of %d "
2220                          "or more.\n",
2221                          "Requires that no player has culture of %d "
2222                          "or more.\n",
2223                          preq->source.value.minculture),
2224                      preq->source.value.minculture);
2225       }
2226       return TRUE;
2227     case REQ_RANGE_LOCAL:
2228     case REQ_RANGE_CADJACENT:
2229     case REQ_RANGE_ADJACENT:
2230     case REQ_RANGE_CONTINENT:
2231     case REQ_RANGE_COUNT:
2232       break;
2233     }
2234     break;
2235 
2236   case VUT_MAXTILEUNITS:
2237     switch (preq->range) {
2238     case REQ_RANGE_LOCAL:
2239       CATLSTR(buf, bufsz, prefix);
2240       if (preq->present) {
2241         cat_snprintf(buf, bufsz,
2242                      PL_("At most %d unit may be present on the tile.\n",
2243                          "At most %d units may be present on the tile.\n",
2244                          preq->source.value.max_tile_units),
2245                      preq->source.value.max_tile_units);
2246       } else {
2247         cat_snprintf(buf, bufsz,
2248                      PL_("There must be more than %d unit present on "
2249                          "the tile.\n",
2250                          "There must be more than %d units present on "
2251                          "the tile.\n",
2252                          preq->source.value.max_tile_units),
2253                      preq->source.value.max_tile_units);
2254       }
2255       return TRUE;
2256     case REQ_RANGE_CADJACENT:
2257       CATLSTR(buf, bufsz, prefix);
2258       if (preq->present) {
2259         cat_snprintf(buf, bufsz,
2260                      PL_("The tile or at least one cardinally adjacent tile "
2261                          "must have %d unit or fewer.\n",
2262                          "The tile or at least one cardinally adjacent tile "
2263                          "must have %d units or fewer.\n",
2264                          preq->source.value.max_tile_units),
2265                      preq->source.value.max_tile_units);
2266       } else {
2267         cat_snprintf(buf, bufsz,
2268                      PL_("The tile and all cardinally adjacent tiles must "
2269                          "have more than %d unit each.\n",
2270                          "The tile and all cardinally adjacent tiles must "
2271                          "have more than %d units each.\n",
2272                          preq->source.value.max_tile_units),
2273                      preq->source.value.max_tile_units);
2274       }
2275       return TRUE;
2276     case REQ_RANGE_ADJACENT:
2277       CATLSTR(buf, bufsz, prefix);
2278       if (preq->present) {
2279         cat_snprintf(buf, bufsz,
2280                      PL_("The tile or at least one adjacent tile must have "
2281                          "%d unit or fewer.\n",
2282                          "The tile or at least one adjacent tile must have "
2283                          "%d units or fewer.\n",
2284                          preq->source.value.max_tile_units),
2285                      preq->source.value.max_tile_units);
2286       } else {
2287         cat_snprintf(buf, bufsz,
2288                      PL_("The tile and all adjacent tiles must have more "
2289                          "than %d unit each.\n",
2290                          "The tile and all adjacent tiles must have more "
2291                          "than %d units each.\n",
2292                          preq->source.value.max_tile_units),
2293                      preq->source.value.max_tile_units);
2294       }
2295       return TRUE;
2296     case REQ_RANGE_CITY:
2297     case REQ_RANGE_TRADEROUTE:
2298     case REQ_RANGE_CONTINENT:
2299     case REQ_RANGE_PLAYER:
2300     case REQ_RANGE_TEAM:
2301     case REQ_RANGE_ALLIANCE:
2302     case REQ_RANGE_WORLD:
2303     case REQ_RANGE_COUNT:
2304       /* Not supported. */
2305       break;
2306     }
2307     break;
2308 
2309   case VUT_AI_LEVEL:
2310     if (preq->range != REQ_RANGE_PLAYER) {
2311       break;
2312     }
2313     CATLSTR(buf, bufsz, prefix);
2314     if (preq->present) {
2315       cat_snprintf(buf, bufsz,
2316                    /* TRANS: AI level (e.g., "Handicapped") */
2317                    _("Applies to %s AI players.\n"),
2318                    ai_level_translated_name(preq->source.value.ai_level));
2319     } else {
2320       cat_snprintf(buf, bufsz,
2321                    /* TRANS: AI level (e.g., "Cheating") */
2322                    _("Does not apply to %s AI players.\n"),
2323                    ai_level_translated_name(preq->source.value.ai_level));
2324     }
2325     return TRUE;
2326 
2327   case VUT_TERRAINCLASS:
2328     switch (preq->range) {
2329     case REQ_RANGE_LOCAL:
2330       CATLSTR(buf, bufsz, prefix);
2331       if (preq->present) {
2332         cat_snprintf(buf, bufsz,
2333                      /* TRANS: %s is a terrain class */
2334                      Q_("?terrainclass:Requires %s terrain on the tile.\n"),
2335                      terrain_class_name_translation
2336                      (preq->source.value.terrainclass));
2337       } else {
2338         cat_snprintf(buf, bufsz,
2339                      /* TRANS: %s is a terrain class */
2340                      Q_("?terrainclass:Prevented by %s terrain on the tile.\n"),
2341                      terrain_class_name_translation
2342                      (preq->source.value.terrainclass));
2343       }
2344       return TRUE;
2345     case REQ_RANGE_CADJACENT:
2346       CATLSTR(buf, bufsz, prefix);
2347       if (preq->present) {
2348         cat_snprintf(buf, bufsz,
2349                      /* TRANS: %s is a terrain class */
2350                      Q_("?terrainclass:Requires %s terrain on the tile or a "
2351                         "cardinally adjacent tile.\n"),
2352                      terrain_class_name_translation
2353                      (preq->source.value.terrainclass));
2354       } else {
2355         cat_snprintf(buf, bufsz,
2356                      /* TRANS: %s is a terrain class */
2357                      Q_("?terrainclass:Prevented by %s terrain on the tile or "
2358                         "any cardinally adjacent tile.\n"),
2359                      terrain_class_name_translation
2360                      (preq->source.value.terrainclass));
2361       }
2362       return TRUE;
2363     case REQ_RANGE_ADJACENT:
2364       CATLSTR(buf, bufsz, prefix);
2365       if (preq->present) {
2366         cat_snprintf(buf, bufsz,
2367                      /* TRANS: %s is a terrain class */
2368                      Q_("?terrainclass:Requires %s terrain on the tile or an "
2369                         "adjacent tile.\n"),
2370                      terrain_class_name_translation
2371                      (preq->source.value.terrainclass));
2372       } else {
2373         cat_snprintf(buf, bufsz,
2374                      /* TRANS: %s is a terrain class */
2375                      Q_("?terrainclass:Prevented by %s terrain on the tile or "
2376                         "any adjacent tile.\n"),
2377                      terrain_class_name_translation
2378                      (preq->source.value.terrainclass));
2379       }
2380       return TRUE;
2381     case REQ_RANGE_CITY:
2382       CATLSTR(buf, bufsz, prefix);
2383       if (preq->present) {
2384         cat_snprintf(buf, bufsz,
2385                      /* TRANS: %s is a terrain class */
2386                      Q_("?terrainclass:Requires %s terrain on a tile within "
2387                         "the city radius.\n"),
2388                      terrain_class_name_translation
2389                      (preq->source.value.terrainclass));
2390       } else {
2391         cat_snprintf(buf, bufsz,
2392                      /* TRANS: %s is a terrain class */
2393                      Q_("?terrainclass:Prevented by %s terrain on any tile "
2394                         "within the city radius.\n"),
2395                      terrain_class_name_translation
2396                      (preq->source.value.terrainclass));
2397       }
2398       return TRUE;
2399     case REQ_RANGE_TRADEROUTE:
2400       CATLSTR(buf, bufsz, prefix);
2401       if (preq->present) {
2402         cat_snprintf(buf, bufsz,
2403                      /* TRANS: %s is a terrain class */
2404                      Q_("?terrainclass:Requires %s terrain on a tile within "
2405                         "the city radius or the city radius of a trade "
2406                         "partner.\n"),
2407                      terrain_class_name_translation
2408                      (preq->source.value.terrainclass));
2409       } else {
2410         cat_snprintf(buf, bufsz,
2411                      /* TRANS: %s is a terrain class */
2412                      Q_("?terrainclass:Prevented by %s terrain on any tile "
2413                         "within the city radius or the city radius of a trade "
2414                         "partner.\n"),
2415                      terrain_class_name_translation
2416                      (preq->source.value.terrainclass));
2417       }
2418       return TRUE;
2419     case REQ_RANGE_CONTINENT:
2420     case REQ_RANGE_PLAYER:
2421     case REQ_RANGE_TEAM:
2422     case REQ_RANGE_ALLIANCE:
2423     case REQ_RANGE_WORLD:
2424     case REQ_RANGE_COUNT:
2425       /* Not supported. */
2426       break;
2427     }
2428     break;
2429 
2430   case VUT_TERRFLAG:
2431     switch (preq->range) {
2432     case REQ_RANGE_LOCAL:
2433       CATLSTR(buf, bufsz, prefix);
2434       if (preq->present) {
2435         cat_snprintf(buf, bufsz,
2436                      /* TRANS: %s is a (translatable) terrain flag. */
2437                      _("Requires terrain with the \"%s\" flag on the tile.\n"),
2438                      terrain_flag_id_translated_name(preq->source.value.terrainflag));
2439       } else {
2440         cat_snprintf(buf, bufsz,
2441                      /* TRANS: %s is a (translatable) terrain flag. */
2442                      _("Prevented by terrain with the \"%s\" flag on the "
2443                        "tile.\n"),
2444                      terrain_flag_id_translated_name(preq->source.value.terrainflag));
2445       }
2446       return TRUE;
2447     case REQ_RANGE_CADJACENT:
2448       CATLSTR(buf, bufsz, prefix);
2449       if (preq->present) {
2450         cat_snprintf(buf, bufsz,
2451                      /* TRANS: %s is a (translatable) terrain flag. */
2452                      _("Requires terrain with the \"%s\" flag on the "
2453                        "tile or a cardinally adjacent tile.\n"),
2454                      terrain_flag_id_translated_name(preq->source.value.terrainflag));
2455       } else {
2456         cat_snprintf(buf, bufsz,
2457                      /* TRANS: %s is a (translatable) terrain flag. */
2458                      _("Prevented by terrain with the \"%s\" flag on "
2459                        "the tile or any cardinally adjacent tile.\n"),
2460                      terrain_flag_id_translated_name(preq->source.value.terrainflag));
2461       }
2462       return TRUE;
2463     case REQ_RANGE_ADJACENT:
2464       CATLSTR(buf, bufsz, prefix);
2465       if (preq->present) {
2466         cat_snprintf(buf, bufsz,
2467                      /* TRANS: %s is a (translatable) terrain flag. */
2468                      _("Requires terrain with the \"%s\" flag on the "
2469                        "tile or an adjacent tile.\n"),
2470                      terrain_flag_id_translated_name(preq->source.value.terrainflag));
2471       } else {
2472         cat_snprintf(buf, bufsz,
2473                      /* TRANS: %s is a (translatable) terrain flag. */
2474                      _("Prevented by terrain with the \"%s\" flag on "
2475                        "the tile or any adjacent tile.\n"),
2476                      terrain_flag_id_translated_name(preq->source.value.terrainflag));
2477       }
2478       return TRUE;
2479     case REQ_RANGE_CITY:
2480       CATLSTR(buf, bufsz, prefix);
2481       if (preq->present) {
2482         cat_snprintf(buf, bufsz,
2483                      /* TRANS: %s is a (translatable) terrain flag. */
2484                      _("Requires terrain with the \"%s\" flag on a tile "
2485                        "within the city radius.\n"),
2486                      terrain_flag_id_translated_name(preq->source.value.terrainflag));
2487       } else {
2488         cat_snprintf(buf, bufsz,
2489                      /* TRANS: %s is a (translatable) terrain flag. */
2490                      _("Prevented by terrain with the \"%s\" flag on any tile "
2491                        "within the city radius.\n"),
2492                      terrain_flag_id_translated_name(preq->source.value.terrainflag));
2493       }
2494       return TRUE;
2495     case REQ_RANGE_TRADEROUTE:
2496       CATLSTR(buf, bufsz, prefix);
2497       if (preq->present) {
2498         cat_snprintf(buf, bufsz,
2499                      /* TRANS: %s is a (translatable) terrain flag. */
2500                      _("Requires terrain with the \"%s\" flag on a tile "
2501                        "within the city radius or the city radius of "
2502                        "a trade partner.\n"),
2503                      terrain_flag_id_translated_name(preq->source.value.terrainflag));
2504       } else {
2505         cat_snprintf(buf, bufsz,
2506                      /* TRANS: %s is a (translatable) terrain flag. */
2507                      _("Prevented by terrain with the \"%s\" flag on any tile "
2508                        "within the city radius or the city radius of "
2509                        "a trade partner.\n"),
2510                      terrain_flag_id_translated_name(preq->source.value.terrainflag));
2511       }
2512       return TRUE;
2513     case REQ_RANGE_CONTINENT:
2514     case REQ_RANGE_PLAYER:
2515     case REQ_RANGE_TEAM:
2516     case REQ_RANGE_ALLIANCE:
2517     case REQ_RANGE_WORLD:
2518     case REQ_RANGE_COUNT:
2519       /* Not supported. */
2520       break;
2521     }
2522     break;
2523 
2524   case VUT_BASEFLAG:
2525     switch (preq->range) {
2526     case REQ_RANGE_LOCAL:
2527       CATLSTR(buf, bufsz, prefix);
2528       if (preq->present) {
2529         cat_snprintf(buf, bufsz,
2530                      /* TRANS: %s is a (translatable) base flag. */
2531                      _("Requires a base with the \"%s\" flag on the tile.\n"),
2532                      base_flag_id_translated_name(preq->source.value.baseflag));
2533       } else {
2534         cat_snprintf(buf, bufsz,
2535                      /* TRANS: %s is a (translatable) base flag. */
2536                      _("Prevented by a base with the \"%s\" flag on the "
2537                        "tile.\n"),
2538                      base_flag_id_translated_name(preq->source.value.baseflag));
2539       }
2540       return TRUE;
2541     case REQ_RANGE_CADJACENT:
2542       CATLSTR(buf, bufsz, prefix);
2543       if (preq->present) {
2544         cat_snprintf(buf, bufsz,
2545                      /* TRANS: %s is a (translatable) base flag. */
2546                      _("Requires a base with the \"%s\" flag on the "
2547                        "tile or a cardinally adjacent tile.\n"),
2548                      base_flag_id_translated_name(preq->source.value.baseflag));
2549       } else {
2550         cat_snprintf(buf, bufsz,
2551                      /* TRANS: %s is a (translatable) base flag. */
2552                      _("Prevented by a base with the \"%s\" flag on "
2553                        "the tile or any cardinally adjacent tile.\n"),
2554                      base_flag_id_translated_name(preq->source.value.baseflag));
2555       }
2556       return TRUE;
2557     case REQ_RANGE_ADJACENT:
2558       CATLSTR(buf, bufsz, prefix);
2559       if (preq->present) {
2560         cat_snprintf(buf, bufsz,
2561                      /* TRANS: %s is a (translatable) base flag. */
2562                      _("Requires a base with the \"%s\" flag on the "
2563                        "tile or an adjacent tile.\n"),
2564                      base_flag_id_translated_name(preq->source.value.baseflag));
2565       } else {
2566         cat_snprintf(buf, bufsz,
2567                      /* TRANS: %s is a (translatable) base flag. */
2568                      _("Prevented by a base with the \"%s\" flag on "
2569                        "the tile or any adjacent tile.\n"),
2570                      base_flag_id_translated_name(preq->source.value.baseflag));
2571       }
2572       return TRUE;
2573     case REQ_RANGE_CITY:
2574       CATLSTR(buf, bufsz, prefix);
2575       if (preq->present) {
2576         cat_snprintf(buf, bufsz,
2577                      /* TRANS: %s is a (translatable) base flag. */
2578                      _("Requires a base with the \"%s\" flag on a tile "
2579                        "within the city radius.\n"),
2580                      base_flag_id_translated_name(preq->source.value.baseflag));
2581       } else {
2582         cat_snprintf(buf, bufsz,
2583                      /* TRANS: %s is a (translatable) base flag. */
2584                      _("Prevented by a base with the \"%s\" flag on any tile "
2585                        "within the city radius.\n"),
2586                      base_flag_id_translated_name(preq->source.value.baseflag));
2587       }
2588       return TRUE;
2589     case REQ_RANGE_TRADEROUTE:
2590       CATLSTR(buf, bufsz, prefix);
2591       if (preq->present) {
2592         cat_snprintf(buf, bufsz,
2593                      /* TRANS: %s is a (translatable) base flag. */
2594                      _("Requires a base with the \"%s\" flag on a tile "
2595                        "within the city radius or the city radius of a "
2596                        "trade partner.\n"),
2597                      base_flag_id_translated_name(preq->source.value.baseflag));
2598       } else {
2599         cat_snprintf(buf, bufsz,
2600                      /* TRANS: %s is a (translatable) base flag. */
2601                      _("Prevented by a base with the \"%s\" flag on any tile "
2602                        "within the city radius or the city radius of a "
2603                        "trade partner.\n"),
2604                      base_flag_id_translated_name(preq->source.value.baseflag));
2605       }
2606       return TRUE;
2607     case REQ_RANGE_CONTINENT:
2608     case REQ_RANGE_PLAYER:
2609     case REQ_RANGE_TEAM:
2610     case REQ_RANGE_ALLIANCE:
2611     case REQ_RANGE_WORLD:
2612     case REQ_RANGE_COUNT:
2613       /* Not supported. */
2614       break;
2615     }
2616     break;
2617 
2618   case VUT_ROADFLAG:
2619     switch (preq->range) {
2620     case REQ_RANGE_LOCAL:
2621       CATLSTR(buf, bufsz, prefix);
2622       if (preq->present) {
2623         cat_snprintf(buf, bufsz,
2624                      /* TRANS: %s is a (translatable) road flag. */
2625                      _("Requires a road with the \"%s\" flag on the tile.\n"),
2626                      road_flag_id_translated_name(preq->source.value.roadflag));
2627       } else {
2628         cat_snprintf(buf, bufsz,
2629                      /* TRANS: %s is a (translatable) road flag. */
2630                      _("Prevented by a road with the \"%s\" flag on the "
2631                        "tile.\n"),
2632                      road_flag_id_translated_name(preq->source.value.roadflag));
2633       }
2634       return TRUE;
2635     case REQ_RANGE_CADJACENT:
2636       CATLSTR(buf, bufsz, prefix);
2637       if (preq->present) {
2638         cat_snprintf(buf, bufsz,
2639                      /* TRANS: %s is a (translatable) road flag. */
2640                      _("Requires a road with the \"%s\" flag on the "
2641                        "tile or a cardinally adjacent tile.\n"),
2642                      road_flag_id_translated_name(preq->source.value.roadflag));
2643       } else {
2644         cat_snprintf(buf, bufsz,
2645                      /* TRANS: %s is a (translatable) road flag. */
2646                      _("Prevented by a road with the \"%s\" flag on "
2647                        "the tile or any cardinally adjacent tile.\n"),
2648                      road_flag_id_translated_name(preq->source.value.roadflag));
2649       }
2650       return TRUE;
2651     case REQ_RANGE_ADJACENT:
2652       CATLSTR(buf, bufsz, prefix);
2653       if (preq->present) {
2654         cat_snprintf(buf, bufsz,
2655                      /* TRANS: %s is a (translatable) road flag. */
2656                      _("Requires a road with the \"%s\" flag on the "
2657                        "tile or an adjacent tile.\n"),
2658                      road_flag_id_translated_name(preq->source.value.roadflag));
2659       } else {
2660         cat_snprintf(buf, bufsz,
2661                      /* TRANS: %s is a (translatable) road flag. */
2662                      _("Prevented by a road with the \"%s\" flag on "
2663                        "the tile or any adjacent tile.\n"),
2664                      road_flag_id_translated_name(preq->source.value.roadflag));
2665       }
2666       return TRUE;
2667     case REQ_RANGE_CITY:
2668       CATLSTR(buf, bufsz, prefix);
2669       if (preq->present) {
2670         cat_snprintf(buf, bufsz,
2671                      /* TRANS: %s is a (translatable) road flag. */
2672                      _("Requires a road with the \"%s\" flag on a tile "
2673                        "within the city radius.\n"),
2674                      road_flag_id_translated_name(preq->source.value.roadflag));
2675       } else {
2676         cat_snprintf(buf, bufsz,
2677                      /* TRANS: %s is a (translatable) road flag. */
2678                      _("Prevented by a road with the \"%s\" flag on any tile "
2679                        "within the city radius.\n"),
2680                      road_flag_id_translated_name(preq->source.value.roadflag));
2681       }
2682       return TRUE;
2683     case REQ_RANGE_TRADEROUTE:
2684       CATLSTR(buf, bufsz, prefix);
2685       if (preq->present) {
2686         cat_snprintf(buf, bufsz,
2687                      /* TRANS: %s is a (translatable) road flag. */
2688                      _("Requires a road with the \"%s\" flag on a tile "
2689                        "within the city radius or the city radius of a "
2690                        "trade partner.\n"),
2691                      road_flag_id_translated_name(preq->source.value.roadflag));
2692       } else {
2693         cat_snprintf(buf, bufsz,
2694                      /* TRANS: %s is a (translatable) road flag. */
2695                      _("Prevented by a road with the \"%s\" flag on any tile "
2696                        "within the city radius or the city radius of a "
2697                        "trade partner.\n"),
2698                      road_flag_id_translated_name(preq->source.value.roadflag));
2699       }
2700       return TRUE;
2701     case REQ_RANGE_CONTINENT:
2702     case REQ_RANGE_PLAYER:
2703     case REQ_RANGE_TEAM:
2704     case REQ_RANGE_ALLIANCE:
2705     case REQ_RANGE_WORLD:
2706     case REQ_RANGE_COUNT:
2707       /* Not supported. */
2708       break;
2709     }
2710     break;
2711 
2712   case VUT_EXTRAFLAG:
2713     switch (preq->range) {
2714     case REQ_RANGE_LOCAL:
2715       CATLSTR(buf, bufsz, prefix);
2716       if (preq->present) {
2717         cat_snprintf(buf, bufsz,
2718                      /* TRANS: %s is a (translatable) extra flag. */
2719                      _("Requires an extra with the \"%s\" flag on the tile.\n"),
2720                      extra_flag_id_translated_name(preq->source.value.extraflag));
2721       } else {
2722         cat_snprintf(buf, bufsz,
2723                      /* TRANS: %s is a (translatable) extra flag. */
2724                      _("Prevented by an extra with the \"%s\" flag on the "
2725                        "tile.\n"),
2726                      extra_flag_id_translated_name(preq->source.value.extraflag));
2727       }
2728       return TRUE;
2729     case REQ_RANGE_CADJACENT:
2730       CATLSTR(buf, bufsz, prefix);
2731       if (preq->present) {
2732         cat_snprintf(buf, bufsz,
2733                      /* TRANS: %s is a (translatable) extra flag. */
2734                      _("Requires an extra with the \"%s\" flag on the "
2735                        "tile or a cardinally adjacent tile.\n"),
2736                      extra_flag_id_translated_name(preq->source.value.extraflag));
2737       } else {
2738         cat_snprintf(buf, bufsz,
2739                      /* TRANS: %s is a (translatable) extra flag. */
2740                      _("Prevented by an extra with the \"%s\" flag on "
2741                        "the tile or any cardinally adjacent tile.\n"),
2742                      extra_flag_id_translated_name(preq->source.value.extraflag));
2743       }
2744       return TRUE;
2745     case REQ_RANGE_ADJACENT:
2746       CATLSTR(buf, bufsz, prefix);
2747       if (preq->present) {
2748         cat_snprintf(buf, bufsz,
2749                      /* TRANS: %s is a (translatable) extra flag. */
2750                      _("Requires an extra with the \"%s\" flag on the "
2751                        "tile or an adjacent tile.\n"),
2752                      extra_flag_id_translated_name(preq->source.value.extraflag));
2753       } else {
2754         cat_snprintf(buf, bufsz,
2755                      /* TRANS: %s is a (translatable) extra flag. */
2756                      _("Prevented by an extra with the \"%s\" flag on "
2757                        "the tile or any adjacent tile.\n"),
2758                      extra_flag_id_translated_name(preq->source.value.extraflag));
2759       }
2760       return TRUE;
2761     case REQ_RANGE_CITY:
2762       CATLSTR(buf, bufsz, prefix);
2763       if (preq->present) {
2764         cat_snprintf(buf, bufsz,
2765                      /* TRANS: %s is a (translatable) extra flag. */
2766                      _("Requires an extra with the \"%s\" flag on a tile "
2767                        "within the city radius.\n"),
2768                      extra_flag_id_translated_name(preq->source.value.extraflag));
2769       } else {
2770         cat_snprintf(buf, bufsz,
2771                      /* TRANS: %s is a (translatable) extra flag. */
2772                      _("Prevented by an extra with the \"%s\" flag on any tile "
2773                        "within the city radius.\n"),
2774                      extra_flag_id_translated_name(preq->source.value.extraflag));
2775       }
2776       return TRUE;
2777     case REQ_RANGE_TRADEROUTE:
2778       CATLSTR(buf, bufsz, prefix);
2779       if (preq->present) {
2780         cat_snprintf(buf, bufsz,
2781                      /* TRANS: %s is a (translatable) extra flag. */
2782                      _("Requires an extra with the \"%s\" flag on a tile "
2783                        "within the city radius or the city radius of a "
2784                        "trade partner.\n"),
2785                      extra_flag_id_translated_name(preq->source.value.extraflag));
2786       } else {
2787         cat_snprintf(buf, bufsz,
2788                      /* TRANS: %s is a (translatable) extra flag. */
2789                      _("Prevented by an extra with the \"%s\" flag on any tile "
2790                        "within the city radius or the city radius of a "
2791                        "trade partner.\n"),
2792                      extra_flag_id_translated_name(preq->source.value.extraflag));
2793       }
2794       return TRUE;
2795     case REQ_RANGE_CONTINENT:
2796     case REQ_RANGE_PLAYER:
2797     case REQ_RANGE_TEAM:
2798     case REQ_RANGE_ALLIANCE:
2799     case REQ_RANGE_WORLD:
2800     case REQ_RANGE_COUNT:
2801       /* Not supported. */
2802       break;
2803     }
2804     break;
2805 
2806   case VUT_MINYEAR:
2807     if (preq->range != REQ_RANGE_WORLD) {
2808       break;
2809     }
2810     CATLSTR(buf, bufsz, prefix);
2811     if (preq->present) {
2812       cat_snprintf(buf, bufsz,
2813                    _("Requires the game to have reached the year %s.\n"),
2814                    textyear(preq->source.value.minyear));
2815     } else {
2816       cat_snprintf(buf, bufsz,
2817                    _("Requires that the game has not yet reached the "
2818                      "year %s.\n"),
2819                    textyear(preq->source.value.minyear));
2820     }
2821     return TRUE;
2822 
2823   case VUT_TOPO:
2824     if (preq->range != REQ_RANGE_WORLD) {
2825       break;
2826     }
2827     CATLSTR(buf, bufsz, prefix);
2828     if (preq->present) {
2829       cat_snprintf(buf, bufsz,
2830                    /* TRANS: topology flag name ("WrapX", "ISO", etc) */
2831                    _("Requires %s map.\n"),
2832                    _(topo_flag_name(preq->source.value.topo_property)));
2833     } else {
2834       cat_snprintf(buf, bufsz,
2835                    /* TRANS: topology flag name ("WrapX", "ISO", etc) */
2836                    _("Prevented on %s map.\n"),
2837                    _(topo_flag_name(preq->source.value.topo_property)));
2838     }
2839     return TRUE;
2840 
2841   case VUT_AGE:
2842     CATLSTR(buf, bufsz, prefix);
2843     if (preq->present) {
2844       cat_snprintf(buf, bufsz,
2845                    _("Requires age of %d turns.\n"),
2846                    preq->source.value.age);
2847     } else {
2848       cat_snprintf(buf, bufsz,
2849                    _("Prevented if age is over %d turns.\n"),
2850                    preq->source.value.age);
2851     }
2852     return TRUE;
2853 
2854   case VUT_TERRAINALTER:
2855     switch (preq->range) {
2856     case REQ_RANGE_LOCAL:
2857       CATLSTR(buf, bufsz, prefix);
2858       if (preq->present) {
2859         cat_snprintf(buf, bufsz,
2860                      _("Requires terrain on which alteration %s is "
2861                        "possible.\n"),
2862                      Q_(terrain_alteration_name(preq->source.value.terrainalter)));
2863       } else {
2864         cat_snprintf(buf, bufsz,
2865                      _("Prevented by terrain on which alteration %s "
2866                        "can be made.\n"),
2867                      Q_(terrain_alteration_name(preq->source.value.terrainalter)));
2868       }
2869       return TRUE;
2870     case REQ_RANGE_CADJACENT:
2871     case REQ_RANGE_ADJACENT:
2872     case REQ_RANGE_CITY:
2873     case REQ_RANGE_TRADEROUTE:
2874     case REQ_RANGE_CONTINENT:
2875     case REQ_RANGE_PLAYER:
2876     case REQ_RANGE_TEAM:
2877     case REQ_RANGE_ALLIANCE:
2878     case REQ_RANGE_WORLD:
2879     case REQ_RANGE_COUNT:
2880       /* Not supported. */
2881       break;
2882     }
2883     break;
2884 
2885   case VUT_CITYTILE:
2886     if (preq->source.value.citytile == CITYT_LAST) {
2887       break;
2888     } else {
2889       static char *tile_property = NULL;
2890 
2891       switch (preq->source.value.citytile) {
2892       case CITYT_CENTER:
2893         tile_property = _("city centers");
2894         break;
2895       case CITYT_CLAIMED:
2896         tile_property = _("claimed tiles");
2897         break;
2898       case CITYT_LAST:
2899         fc_assert(preq->source.value.citytile != CITYT_LAST);
2900         break;
2901       }
2902 
2903       switch (preq->range) {
2904       case REQ_RANGE_LOCAL:
2905         CATLSTR(buf, bufsz, prefix);
2906         if (preq->present) {
2907           cat_snprintf(buf, bufsz,
2908                        /* TRANS: tile property ("city centers", etc) */
2909                        Q_("?tileprop:Applies only to %s.\n"), tile_property);
2910         } else {
2911           cat_snprintf(buf, bufsz,
2912                        /* TRANS: tile property ("city centers", etc) */
2913                        Q_("?tileprop:Does not apply to %s.\n"), tile_property);
2914         }
2915         return TRUE;
2916       case REQ_RANGE_CADJACENT:
2917         CATLSTR(buf, bufsz, prefix);
2918         if (preq->present) {
2919           /* TRANS: tile property ("city centers", etc) */
2920           cat_snprintf(buf, bufsz, Q_("?tileprop:Applies only to %s and "
2921                                       "cardinally adjacent tiles.\n"),
2922                        tile_property);
2923         } else {
2924           /* TRANS: tile property ("city centers", etc) */
2925           cat_snprintf(buf, bufsz, Q_("?tileprop:Does not apply to %s or "
2926                                       "cardinally adjacent tiles.\n"),
2927                        tile_property);
2928         }
2929         return TRUE;
2930       case REQ_RANGE_ADJACENT:
2931         CATLSTR(buf, bufsz, prefix);
2932         if (preq->present) {
2933           /* TRANS: tile property ("city centers", etc) */
2934           cat_snprintf(buf, bufsz, Q_("?tileprop:Applies only to %s and "
2935                                       "adjacent tiles.\n"), tile_property);
2936         } else {
2937           /* TRANS: tile property ("city centers", etc) */
2938           cat_snprintf(buf, bufsz, Q_("?tileprop:Does not apply to %s or "
2939                                       "adjacent tiles.\n"), tile_property);
2940         }
2941         return TRUE;
2942       case REQ_RANGE_CITY:
2943       case REQ_RANGE_TRADEROUTE:
2944       case REQ_RANGE_CONTINENT:
2945       case REQ_RANGE_PLAYER:
2946       case REQ_RANGE_TEAM:
2947       case REQ_RANGE_ALLIANCE:
2948       case REQ_RANGE_WORLD:
2949       case REQ_RANGE_COUNT:
2950         /* Not supported. */
2951         break;
2952       }
2953     }
2954 
2955   case VUT_COUNT:
2956     break;
2957   }
2958 
2959   {
2960     char text[256];
2961 
2962     log_error("%s requirement %s in range %d is not supported in helpdata.c.",
2963               preq->present ? "Present" : "Absent",
2964               universal_name_translation(&preq->source, text, sizeof(text)),
2965               preq->range);
2966   }
2967 
2968   return FALSE;
2969 }
2970 
2971 /****************************************************************************
2972   Append text to 'buf' if the given requirements list 'subjreqs' contains
2973   'psource', implying that ability to build the subject 'subjstr' is
2974   affected by 'psource'.
2975   'strs' is an array of (possibly i18n-qualified) format strings covering
2976   the various cases where additional requirements apply.
2977 ****************************************************************************/
insert_allows_single(struct universal * psource,struct requirement_vector * psubjreqs,const char * subjstr,const char * const * strs,char * buf,size_t bufsz,const char * prefix)2978 static void insert_allows_single(struct universal *psource,
2979                                  struct requirement_vector *psubjreqs,
2980                                  const char *subjstr,
2981                                  const char *const *strs,
2982                                  char *buf, size_t bufsz,
2983                                  const char *prefix)
2984 {
2985   struct strvec *coreqs = strvec_new();
2986   struct strvec *conoreqs = strvec_new();
2987   struct astring coreqstr = ASTRING_INIT;
2988   struct astring conoreqstr = ASTRING_INIT;
2989   char buf2[bufsz];
2990 
2991   /* FIXME: show other data like range and survives. */
2992 
2993   requirement_vector_iterate(psubjreqs, req) {
2994     if (!req->quiet && are_universals_equal(psource, &req->source)) {
2995       /* We're definitely going to print _something_. */
2996       CATLSTR(buf, bufsz, prefix);
2997       if (req->present) {
2998         /* psource enables the subject, but other sources may
2999          * also be required (or required to be absent). */
3000         requirement_vector_iterate(psubjreqs, coreq) {
3001           if (!coreq->quiet && !are_universals_equal(psource, &coreq->source)) {
3002             universal_name_translation(&coreq->source, buf2, sizeof(buf2));
3003             strvec_append(coreq->present ? coreqs : conoreqs, buf2);
3004           }
3005         } requirement_vector_iterate_end;
3006 
3007         if (0 < strvec_size(coreqs)) {
3008           if (0 < strvec_size(conoreqs)) {
3009             cat_snprintf(buf, bufsz,
3010                          Q_(strs[0]), /* "Allows %s (with %s but no %s)." */
3011                          subjstr,
3012                          strvec_to_and_list(coreqs, &coreqstr),
3013                          strvec_to_or_list(conoreqs, &conoreqstr));
3014           } else {
3015             cat_snprintf(buf, bufsz,
3016                          Q_(strs[1]), /* "Allows %s (with %s)." */
3017                          subjstr,
3018                          strvec_to_and_list(coreqs, &coreqstr));
3019           }
3020         } else {
3021           if (0 < strvec_size(conoreqs)) {
3022             cat_snprintf(buf, bufsz,
3023                          Q_(strs[2]), /* "Allows %s (absent %s)." */
3024                          subjstr,
3025                          strvec_to_and_list(conoreqs, &conoreqstr));
3026           } else {
3027             cat_snprintf(buf, bufsz,
3028                          Q_(strs[3]), /* "Allows %s." */
3029                          subjstr);
3030           }
3031         }
3032       } else {
3033         /* psource can, on its own, prevent the subject. */
3034         cat_snprintf(buf, bufsz,
3035                      Q_(strs[4]), /* "Prevents %s." */
3036                      subjstr);
3037       }
3038       cat_snprintf(buf, bufsz, "\n");
3039     }
3040   } requirement_vector_iterate_end;
3041 
3042   strvec_destroy(coreqs);
3043   strvec_destroy(conoreqs);
3044   astr_free(&coreqstr);
3045   astr_free(&conoreqstr);
3046 }
3047 
3048 /****************************************************************************
3049   Generate text for what this requirement source allows.  Something like
3050 
3051     "Allows Communism (with University).\n"
3052     "Allows Mfg. Plant (with Factory).\n"
3053     "Allows Library (absent Fundamentalism).\n"
3054     "Prevents Harbor.\n"
3055 
3056   This should be called to generate helptext for every possible source
3057   type.  Note this doesn't handle effects but rather requirements to
3058   create/maintain things (currently only building/government reqs).
3059 
3060   NB: This function overwrites any existing buffer contents by writing the
3061   generated text to the start of the given 'buf' pointer (i.e. it does
3062   NOT append like cat_snprintf).
3063 ****************************************************************************/
insert_allows(struct universal * psource,char * buf,size_t bufsz,const char * prefix)3064 static void insert_allows(struct universal *psource,
3065 			  char *buf, size_t bufsz, const char *prefix)
3066 {
3067   buf[0] = '\0';
3068 
3069   governments_iterate(pgov) {
3070     static const char *const govstrs[] = {
3071       /* TRANS: First %s is a government name. */
3072       N_("?gov:Allows %s (with %s but no %s)."),
3073       /* TRANS: First %s is a government name. */
3074       N_("?gov:Allows %s (with %s)."),
3075       /* TRANS: First %s is a government name. */
3076       N_("?gov:Allows %s (absent %s)."),
3077       /* TRANS: %s is a government name. */
3078       N_("?gov:Allows %s."),
3079       /* TRANS: %s is a government name. */
3080       N_("?gov:Prevents %s.")
3081     };
3082     insert_allows_single(psource, &pgov->reqs,
3083                          government_name_translation(pgov), govstrs,
3084                          buf, bufsz, prefix);
3085   } governments_iterate_end;
3086 
3087   improvement_iterate(pimprove) {
3088     if (valid_improvement(pimprove)) {
3089       static const char *const imprstrs[] = {
3090         /* TRANS: First %s is a building name. */
3091         N_("?improvement:Allows %s (with %s but no %s)."),
3092         /* TRANS: First %s is a building name. */
3093         N_("?improvement:Allows %s (with %s)."),
3094         /* TRANS: First %s is a building name. */
3095         N_("?improvement:Allows %s (absent %s)."),
3096         /* TRANS: %s is a building name. */
3097         N_("?improvement:Allows %s."),
3098         /* TRANS: %s is a building name. */
3099         N_("?improvement:Prevents %s.")
3100       };
3101       insert_allows_single(psource, &pimprove->reqs,
3102                            improvement_name_translation(pimprove), imprstrs,
3103                            buf, bufsz, prefix);
3104     }
3105   } improvement_iterate_end;
3106 }
3107 
3108 /****************************************************************
3109   Allocate and initialize new help item
3110 *****************************************************************/
new_help_item(int type)3111 static struct help_item *new_help_item(int type)
3112 {
3113   struct help_item *pitem;
3114 
3115   pitem = fc_malloc(sizeof(struct help_item));
3116   pitem->topic = NULL;
3117   pitem->text = NULL;
3118   pitem->type = type;
3119   return pitem;
3120 }
3121 
3122 /****************************************************************
3123  for help_list_sort(); sort by topic via compare_strings()
3124  (sort topics with more leading spaces after those with fewer)
3125 *****************************************************************/
help_item_compar(const struct help_item * const * ppa,const struct help_item * const * ppb)3126 static int help_item_compar(const struct help_item *const *ppa,
3127                             const struct help_item *const *ppb)
3128 {
3129   const struct help_item *ha, *hb;
3130   char *ta, *tb;
3131   ha = *ppa;
3132   hb = *ppb;
3133   for (ta = ha->topic, tb = hb->topic; *ta != '\0' && *tb != '\0'; ta++, tb++) {
3134     if (*ta != ' ') {
3135       if (*tb == ' ') return -1;
3136       break;
3137     } else if (*tb != ' ') {
3138       if (*ta == ' ') return 1;
3139       break;
3140     }
3141   }
3142   return compare_strings(ta, tb);
3143 }
3144 
3145 /****************************************************************
3146   pplayer may be NULL.
3147 *****************************************************************/
boot_help_texts(void)3148 void boot_help_texts(void)
3149 {
3150   static bool booted = FALSE;
3151 
3152   struct section_file *sf;
3153   const char *filename;
3154   struct help_item *pitem;
3155   int i;
3156   struct section_list *sec;
3157   const char **paras;
3158   size_t npara;
3159   char long_buffer[64000]; /* HACK: this may be overrun. */
3160 
3161   check_help_nodes_init();
3162 
3163   /* need to do something like this or bad things happen */
3164   popdown_help_dialog();
3165 
3166   if (!booted) {
3167     log_verbose("Booting help texts");
3168   } else {
3169     /* free memory allocated last time booted */
3170     free_help_texts();
3171     log_verbose("Rebooting help texts");
3172   }
3173 
3174   filename = fileinfoname(get_data_dirs(), "helpdata.txt");
3175   if (!filename) {
3176     log_error("Did not read help texts");
3177     return;
3178   }
3179   /* after following call filename may be clobbered; use sf->filename instead */
3180   if (!(sf = secfile_load(filename, FALSE))) {
3181     /* this is now unlikely to happen */
3182     log_error("failed reading help-texts from '%s':\n%s", filename,
3183               secfile_error());
3184     return;
3185   }
3186 
3187   sec = secfile_sections_by_name_prefix(sf, "help_");
3188 
3189   if (NULL != sec) {
3190     section_list_iterate(sec, psection) {
3191       char help_text_buffer[MAX_LEN_PACKET];
3192       const char *sec_name = section_name(psection);
3193       const char *gen_str = secfile_lookup_str(sf, "%s.generate", sec_name);
3194 
3195       if (gen_str) {
3196         enum help_page_type current_type = HELP_ANY;
3197         int level = strspn(gen_str, " ");
3198 
3199         gen_str += level;
3200 
3201         for (i = 2; help_type_names[i]; i++) {
3202           if (strcmp(gen_str, help_type_names[i]) == 0) {
3203             current_type = i;
3204             break;
3205           }
3206         }
3207         if (current_type == HELP_ANY) {
3208           log_error("bad help-generate category \"%s\"", gen_str);
3209           continue;
3210         }
3211 
3212         if (!booted) {
3213           if (current_type == HELP_EXTRA) {
3214             size_t ncats;
3215 
3216             /* Avoid warnings about entries unused on this round,
3217              * when the entries in question are valid once help system has been booted */
3218             (void) secfile_lookup_str_vec(sf, &ncats,
3219                                           "%s.categories", sec_name);
3220           }
3221           continue; /* on initial boot data tables are empty */
3222         }
3223 
3224         {
3225           /* Note these should really fill in pitem->text from auto-gen
3226              data instead of doing it later on the fly, but I don't want
3227              to change that now.  --dwp
3228           */
3229           char name[2048];
3230           struct help_list *category_nodes = help_list_new();
3231 
3232           switch (current_type) {
3233           case HELP_UNIT:
3234             unit_type_iterate(punittype) {
3235               pitem = new_help_item(current_type);
3236               fc_snprintf(name, sizeof(name), "%*s%s", level, "",
3237                           utype_name_translation(punittype));
3238               pitem->topic = fc_strdup(name);
3239               pitem->text = fc_strdup("");
3240               help_list_append(category_nodes, pitem);
3241             } unit_type_iterate_end;
3242             break;
3243           case HELP_TECH:
3244             advance_index_iterate(A_FIRST, advi) {
3245               if (valid_advance_by_number(advi)) {
3246                 pitem = new_help_item(current_type);
3247                 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
3248                             advance_name_translation(advance_by_number(advi)));
3249                 pitem->topic = fc_strdup(name);
3250                 pitem->text = fc_strdup("");
3251                 help_list_append(category_nodes, pitem);
3252               }
3253             } advance_index_iterate_end;
3254             break;
3255           case HELP_TERRAIN:
3256             terrain_type_iterate(pterrain) {
3257               if (0 != strlen(terrain_rule_name(pterrain))) {
3258                 pitem = new_help_item(current_type);
3259                 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
3260                             terrain_name_translation(pterrain));
3261                 pitem->topic = fc_strdup(name);
3262                 pitem->text = fc_strdup("");
3263                 help_list_append(category_nodes, pitem);
3264               }
3265             } terrain_type_iterate_end;
3266             break;
3267           case HELP_EXTRA:
3268             {
3269               const char **cats;
3270               size_t ncats;
3271               cats = secfile_lookup_str_vec(sf, &ncats,
3272                                             "%s.categories", sec_name);
3273               extra_type_iterate(pextra) {
3274                 /* If categories not specified, don't filter */
3275                 if (cats) {
3276                   bool include = FALSE;
3277                   const char *cat = extra_category_name(pextra->category);
3278                   int ci;
3279 
3280                   for (ci = 0; ci < ncats; ci++) {
3281                     if (fc_strcasecmp(cats[ci], cat) == 0) {
3282                       include = TRUE;
3283                       break;
3284                     }
3285                   }
3286                   if (!include) {
3287                     continue;
3288                   }
3289                 }
3290                 pitem = new_help_item(current_type);
3291                 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
3292                             extra_name_translation(pextra));
3293                 pitem->topic = fc_strdup(name);
3294                 pitem->text = fc_strdup("");
3295                 help_list_append(category_nodes, pitem);
3296               } extra_type_iterate_end;
3297               FC_FREE(cats);
3298             }
3299             break;
3300           case HELP_SPECIALIST:
3301             specialist_type_iterate(sp) {
3302               struct specialist *pspec = specialist_by_number(sp);
3303               pitem = new_help_item(current_type);
3304               fc_snprintf(name, sizeof(name), "%*s%s", level, "",
3305                           specialist_plural_translation(pspec));
3306               pitem->topic = fc_strdup(name);
3307               pitem->text = fc_strdup("");
3308               help_list_append(category_nodes, pitem);
3309             } specialist_type_iterate_end;
3310             break;
3311           case HELP_GOVERNMENT:
3312             governments_iterate(gov) {
3313               pitem = new_help_item(current_type);
3314               fc_snprintf(name, sizeof(name), "%*s%s", level, "",
3315                           government_name_translation(gov));
3316               pitem->topic = fc_strdup(name);
3317               pitem->text = fc_strdup("");
3318               help_list_append(category_nodes, pitem);
3319             } governments_iterate_end;
3320             break;
3321           case HELP_IMPROVEMENT:
3322             improvement_iterate(pimprove) {
3323               if (valid_improvement(pimprove) && !is_great_wonder(pimprove)) {
3324                 pitem = new_help_item(current_type);
3325                 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
3326                             improvement_name_translation(pimprove));
3327                 pitem->topic = fc_strdup(name);
3328                 pitem->text = fc_strdup("");
3329                 help_list_append(category_nodes, pitem);
3330               }
3331             } improvement_iterate_end;
3332             break;
3333           case HELP_WONDER:
3334             improvement_iterate(pimprove) {
3335               if (valid_improvement(pimprove) && is_great_wonder(pimprove)) {
3336                 pitem = new_help_item(current_type);
3337                 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
3338                             improvement_name_translation(pimprove));
3339                 pitem->topic = fc_strdup(name);
3340                 pitem->text = fc_strdup("");
3341                 help_list_append(category_nodes, pitem);
3342               }
3343             } improvement_iterate_end;
3344             break;
3345           case HELP_RULESET:
3346             {
3347               int desc_len;
3348               int len;
3349 
3350               pitem = new_help_item(HELP_RULESET);
3351               /*           pitem->topic = fc_strdup(_(game.control.name)); */
3352               fc_snprintf(name, sizeof(name), "%*s%s", level, "",
3353                           Q_(HELP_RULESET_ITEM));
3354               pitem->topic = fc_strdup(name);
3355               if (game.ruleset_description != NULL) {
3356                 desc_len = strlen("\n\n") + strlen(game.ruleset_description);
3357               } else {
3358                 desc_len = 0;
3359               }
3360               if (game.ruleset_summary != NULL) {
3361                 if (game.control.version[0] != '\0') {
3362                   len = strlen(_(game.control.name))
3363                     + strlen(" ")
3364                     + strlen(game.control.version)
3365                     + strlen("\n\n")
3366                     + strlen(_(game.ruleset_summary))
3367                     + 1;
3368 
3369                   pitem->text = fc_malloc(len + desc_len);
3370                   fc_snprintf(pitem->text, len, "%s %s\n\n%s",
3371                               _(game.control.name), game.control.version,
3372                               _(game.ruleset_summary));
3373                 } else {
3374                   len = strlen(_(game.control.name))
3375                     + strlen("\n\n")
3376                     + strlen(_(game.ruleset_summary))
3377                     + 1;
3378 
3379                   pitem->text = fc_malloc(len + desc_len);
3380                   fc_snprintf(pitem->text, len, "%s\n\n%s",
3381                               _(game.control.name), _(game.ruleset_summary));
3382                 }
3383               } else {
3384                 const char *nodesc = _("Current ruleset contains no summary.");
3385 
3386                 if (game.control.version[0] != '\0') {
3387                   len = strlen(_(game.control.name))
3388                     + strlen(" ")
3389                     + strlen(game.control.version)
3390                     + strlen("\n\n")
3391                     + strlen(nodesc)
3392                     + 1;
3393 
3394                   pitem->text = fc_malloc(len + desc_len);
3395                   fc_snprintf(pitem->text, len, "%s %s\n\n%s",
3396                               _(game.control.name), game.control.version,
3397                               nodesc);
3398                 } else {
3399                   len = strlen(_(game.control.name))
3400                     + strlen("\n\n")
3401                     + strlen(nodesc)
3402                     + 1;
3403 
3404                   pitem->text = fc_malloc(len + desc_len);
3405                   fc_snprintf(pitem->text, len, "%s\n\n%s",
3406                               _(game.control.name),
3407                               nodesc);
3408                 }
3409               }
3410               if (game.ruleset_description != NULL) {
3411                 fc_strlcat(pitem->text, "\n\n", len + desc_len);
3412                 fc_strlcat(pitem->text, game.ruleset_description, len + desc_len);
3413               }
3414               help_list_append(help_nodes, pitem);
3415             }
3416             break;
3417           case HELP_TILESET:
3418             {
3419               int desc_len;
3420               int len;
3421               const char *ts_name = tileset_name_get(tileset);
3422               const char *version = tileset_version(tileset);
3423               const char *summary = tileset_summary(tileset);
3424               const char *description = tileset_description(tileset);
3425 
3426               pitem = new_help_item(HELP_TILESET);
3427               fc_snprintf(name, sizeof(name), "%*s%s", level, "",
3428                           Q_(HELP_TILESET_ITEM));
3429               pitem->topic = fc_strdup(name);
3430               if (description != NULL) {
3431                 desc_len = strlen("\n\n") + strlen(description);
3432               } else {
3433                 desc_len = 0;
3434               }
3435               if (summary != NULL) {
3436                 if (version[0] != '\0') {
3437                   len = strlen(_(ts_name))
3438                     + strlen(" ")
3439                     + strlen(version)
3440                     + strlen("\n\n")
3441                     + strlen(_(summary))
3442                     + 1;
3443 
3444                   pitem->text = fc_malloc(len + desc_len);
3445                   fc_snprintf(pitem->text, len, "%s %s\n\n%s",
3446                               _(ts_name), version, _(summary));
3447                 } else {
3448                   len = strlen(_(ts_name))
3449                     + strlen("\n\n")
3450                     + strlen(_(summary))
3451                     + 1;
3452 
3453                   pitem->text = fc_malloc(len + desc_len);
3454                   fc_snprintf(pitem->text, len, "%s\n\n%s",
3455                               _(ts_name), _(summary));
3456                 }
3457               } else {
3458                 const char *nodesc = _("Current tileset contains no summary.");
3459 
3460                 if (version[0] != '\0') {
3461                   len = strlen(_(ts_name))
3462                     + strlen(" ")
3463                     + strlen(version)
3464                     + strlen("\n\n")
3465                     + strlen(nodesc)
3466                     + 1;
3467 
3468                   pitem->text = fc_malloc(len + desc_len);
3469                   fc_snprintf(pitem->text, len, "%s %s\n\n%s",
3470                               _(ts_name), version,
3471                               nodesc);
3472                 } else {
3473                   len = strlen(_(ts_name))
3474                     + strlen("\n\n")
3475                     + strlen(nodesc)
3476                     + 1;
3477 
3478                   pitem->text = fc_malloc(len + desc_len);
3479                   fc_snprintf(pitem->text, len, "%s\n\n%s",
3480                               _(ts_name),
3481                               nodesc);
3482                 }
3483               }
3484               if (description != NULL) {
3485                 fc_strlcat(pitem->text, "\n\n", len + desc_len);
3486                 fc_strlcat(pitem->text, description, len + desc_len);
3487               }
3488               help_list_append(help_nodes, pitem);
3489             }
3490             break;
3491           case HELP_NATIONS:
3492             nations_iterate(pnation) {
3493               if (client_state() < C_S_RUNNING
3494                   || show_help_for_nation(pnation)) {
3495                 pitem = new_help_item(current_type);
3496                 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
3497                             nation_plural_translation(pnation));
3498                 pitem->topic = fc_strdup(name);
3499                 pitem->text = fc_strdup("");
3500                 help_list_append(category_nodes, pitem);
3501               }
3502             } nations_iterate_end;
3503             break;
3504 	  case HELP_MULTIPLIER:
3505             multipliers_iterate(pmul) {
3506               help_text_buffer[0] = '\0';
3507               pitem = new_help_item(current_type);
3508               fc_snprintf(name, sizeof(name), "%*s%s", level, "",
3509                           name_translation_get(&pmul->name));
3510               pitem->topic = fc_strdup(name);
3511               if (pmul->helptext) {
3512                 const char *sep = "";
3513                 strvec_iterate(pmul->helptext, text) {
3514                   cat_snprintf(help_text_buffer, sizeof(help_text_buffer),
3515                                "%s%s", sep, text);
3516                   sep = "\n\n";
3517                 } strvec_iterate_end;
3518               }
3519               pitem->text = fc_strdup(help_text_buffer);
3520               help_list_append(help_nodes, pitem);
3521             } multipliers_iterate_end;
3522             break;
3523           default:
3524             log_error("Bad current_type: %d.", current_type);
3525             break;
3526           }
3527           help_list_sort(category_nodes, help_item_compar);
3528           help_list_iterate(category_nodes, ptmp) {
3529             help_list_append(help_nodes, ptmp);
3530           } help_list_iterate_end;
3531           help_list_destroy(category_nodes);
3532           continue;
3533         }
3534       }
3535 
3536       /* It wasn't a "generate" node: */
3537 
3538       pitem = new_help_item(HELP_TEXT);
3539       pitem->topic = fc_strdup(Q_(secfile_lookup_str(sf, "%s.name",
3540                                                      sec_name)));
3541 
3542       paras = secfile_lookup_str_vec(sf, &npara, "%s.text", sec_name);
3543 
3544       long_buffer[0] = '\0';
3545       for (i=0; i<npara; i++) {
3546         bool inserted;
3547         const char *para = paras[i];
3548         if(strncmp(para, "$", 1)==0) {
3549           inserted =
3550             insert_generated_text(long_buffer, sizeof(long_buffer), para+1);
3551         } else {
3552           sz_strlcat(long_buffer, _(para));
3553           inserted = TRUE;
3554         }
3555         if (inserted && i!=npara-1) {
3556           sz_strlcat(long_buffer, "\n\n");
3557         }
3558       }
3559       free(paras);
3560       paras = NULL;
3561       pitem->text=fc_strdup(long_buffer);
3562       help_list_append(help_nodes, pitem);
3563     } section_list_iterate_end;
3564 
3565     section_list_destroy(sec);
3566   }
3567 
3568   secfile_check_unused(sf);
3569   secfile_destroy(sf);
3570   booted = TRUE;
3571   log_verbose("Booted help texts ok");
3572 }
3573 
3574 /****************************************************************
3575   The following few functions are essentially wrappers for the
3576   help_nodes help_list.  This allows us to avoid exporting the
3577   help_list, and instead only access it through a controlled
3578   interface.
3579 *****************************************************************/
3580 
3581 /****************************************************************
3582   Number of help items.
3583 *****************************************************************/
num_help_items(void)3584 int num_help_items(void)
3585 {
3586   check_help_nodes_init();
3587   return help_list_size(help_nodes);
3588 }
3589 
3590 /****************************************************************
3591   Return pointer to given help_item.
3592   Returns NULL for 1 past end.
3593   Returns NULL and prints error message for other out-of bounds.
3594 *****************************************************************/
get_help_item(int pos)3595 const struct help_item *get_help_item(int pos)
3596 {
3597   int size;
3598 
3599   check_help_nodes_init();
3600   size = help_list_size(help_nodes);
3601   if (pos < 0 || pos > size) {
3602     log_error("Bad index %d to get_help_item (size %d)", pos, size);
3603     return NULL;
3604   }
3605   if (pos == size) {
3606     return NULL;
3607   }
3608   return help_list_get(help_nodes, pos);
3609 }
3610 
3611 /****************************************************************
3612   Find help item by name and type.
3613   Returns help item, and sets (*pos) to position in list.
3614   If no item, returns pointer to static internal item with
3615   some faked data, and sets (*pos) to -1.
3616 *****************************************************************/
3617 const struct help_item*
get_help_item_spec(const char * name,enum help_page_type htype,int * pos)3618 get_help_item_spec(const char *name, enum help_page_type htype, int *pos)
3619 {
3620   int idx;
3621   const struct help_item *pitem = NULL;
3622   static struct help_item vitem; /* v = virtual */
3623   static char vtopic[128];
3624   static char vtext[256];
3625 
3626   check_help_nodes_init();
3627   idx = 0;
3628   help_list_iterate(help_nodes, ptmp) {
3629     char *p=ptmp->topic;
3630     while (*p == ' ') {
3631       p++;
3632     }
3633     if(strcmp(name, p)==0 && (htype==HELP_ANY || htype==ptmp->type)) {
3634       pitem = ptmp;
3635       break;
3636     }
3637     idx++;
3638   }
3639   help_list_iterate_end;
3640 
3641   if(!pitem) {
3642     idx = -1;
3643     vitem.topic = vtopic;
3644     sz_strlcpy(vtopic, name);
3645     vitem.text = vtext;
3646     if(htype==HELP_ANY || htype==HELP_TEXT) {
3647       fc_snprintf(vtext, sizeof(vtext),
3648 		  _("Sorry, no help topic for %s.\n"), vitem.topic);
3649       vitem.type = HELP_TEXT;
3650     } else {
3651       fc_snprintf(vtext, sizeof(vtext),
3652 		  _("Sorry, no help topic for %s.\n"
3653 		    "This page was auto-generated.\n\n"),
3654 		  vitem.topic);
3655       vitem.type = htype;
3656     }
3657     pitem = &vitem;
3658   }
3659   *pos = idx;
3660   return pitem;
3661 }
3662 
3663 /****************************************************************
3664   Start iterating through help items;
3665   that is, reset iterator to start position.
3666   (Could iterate using get_help_item(), but that would be
3667   less efficient due to scanning to find pos.)
3668 *****************************************************************/
help_iter_start(void)3669 void help_iter_start(void)
3670 {
3671   check_help_nodes_init();
3672   help_nodes_iterator = help_list_head(help_nodes);
3673 }
3674 
3675 /****************************************************************
3676   Returns next help item; after help_iter_start(), this is
3677   the first item.  At end, returns NULL.
3678 *****************************************************************/
help_iter_next(void)3679 const struct help_item *help_iter_next(void)
3680 {
3681   const struct help_item *pitem;
3682 
3683   check_help_nodes_init();
3684   pitem = help_list_link_data(help_nodes_iterator);
3685   if (pitem) {
3686     help_nodes_iterator = help_list_link_next(help_nodes_iterator);
3687   }
3688 
3689   return pitem;
3690 }
3691 
3692 
3693 /****************************************************************
3694   FIXME:
3695   Also, in principle these could be auto-generated once, inserted
3696   into pitem->text, and then don't need to keep re-generating them.
3697   Only thing to be careful of would be changeable data, but don't
3698   have that here (for ruleset change or spacerace change must
3699   re-boot helptexts anyway).  Eg, genuinely dynamic information
3700   which could be useful would be if help system said which wonders
3701   have been built (or are being built and by who/where?)
3702 *****************************************************************/
3703 
3704 /**************************************************************************
3705   Write dynamic text for buildings (including wonders).  This includes
3706   the ruleset helptext as well as any automatically generated text.
3707 
3708   pplayer may be NULL.
3709   user_text, if non-NULL, will be appended to the text.
3710 **************************************************************************/
helptext_building(char * buf,size_t bufsz,struct player * pplayer,const char * user_text,struct impr_type * pimprove)3711 char *helptext_building(char *buf, size_t bufsz, struct player *pplayer,
3712                         const char *user_text, struct impr_type *pimprove)
3713 {
3714   bool reqs = FALSE;
3715   struct universal source = {
3716     .kind = VUT_IMPROVEMENT,
3717     .value = {.building = pimprove}
3718   };
3719 
3720   fc_assert_ret_val(NULL != buf && 0 < bufsz, NULL);
3721   buf[0] = '\0';
3722 
3723   if (NULL == pimprove) {
3724     return buf;
3725   }
3726 
3727   if (NULL != pimprove->helptext) {
3728     strvec_iterate(pimprove->helptext, text) {
3729       cat_snprintf(buf, bufsz, "%s\n\n", _(text));
3730     } strvec_iterate_end;
3731   }
3732 
3733   /* Add requirement text for improvement itself */
3734   requirement_vector_iterate(&pimprove->reqs, preq) {
3735     if (insert_requirement(buf, bufsz, pplayer, preq, "")) {
3736       reqs = TRUE;
3737     }
3738   } requirement_vector_iterate_end;
3739   if (reqs) {
3740     fc_strlcat(buf, "\n", bufsz);
3741   }
3742 
3743   requirement_vector_iterate(&pimprove->obsolete_by, pobs) {
3744     if (VUT_ADVANCE == pobs->source.kind && pobs->present) {
3745       cat_snprintf(buf, bufsz,
3746                    _("* The discovery of %s will make %s obsolete.\n"),
3747                    advance_name_translation(pobs->source.value.advance),
3748                    improvement_name_translation(pimprove));
3749     }
3750     if (VUT_IMPROVEMENT == pobs->source.kind && pobs->present) {
3751       cat_snprintf(buf, bufsz,
3752                    /* TRANS: both %s are improvement names */
3753                    _("* The presence of %s in the city will make %s "
3754                      "obsolete.\n"),
3755                      improvement_name_translation(pobs->source.value.building),
3756                      improvement_name_translation(pimprove));
3757     }
3758   } requirement_vector_iterate_end;
3759 
3760   if (is_small_wonder(pimprove)) {
3761     cat_snprintf(buf, bufsz,
3762                  _("* A 'small wonder': at most one of your cities may "
3763                    "possess this improvement.\n"));
3764   }
3765   /* (Great wonders are in their own help section explaining their
3766    * uniqueness, so we don't mention it here.) */
3767 
3768   if (building_has_effect(pimprove, EFT_ENABLE_NUKE)
3769       && num_role_units(UTYF_NUCLEAR) > 0) {
3770     struct unit_type *u = get_role_unit(UTYF_NUCLEAR, 0);
3771 
3772     cat_snprintf(buf, bufsz,
3773 		 /* TRANS: 'Allows all players with knowledge of atomic
3774 		  * power to build nuclear units.' */
3775 		 _("* Allows all players with knowledge of %s "
3776 		   "to build %s units.\n"),
3777                  advance_name_translation(u->require_advance),
3778 		 utype_name_translation(u));
3779   }
3780 
3781   insert_allows(&source, buf + strlen(buf), bufsz - strlen(buf),
3782                 /* TRANS: bullet point; note trailing space */
3783                 Q_("?bullet:* "));
3784 
3785   unit_type_iterate(u) {
3786     if (u->need_improvement == pimprove) {
3787       if (valid_advance(u->require_advance)
3788           && advance_by_number(A_NONE) != u->require_advance) {
3789 	cat_snprintf(buf, bufsz, _("* Allows %s (with %s).\n"),
3790 		     utype_name_translation(u),
3791                      advance_name_translation(u->require_advance));
3792       } else {
3793 	cat_snprintf(buf, bufsz, _("* Allows %s.\n"),
3794 		     utype_name_translation(u));
3795       }
3796     }
3797   } unit_type_iterate_end;
3798 
3799   {
3800     int i;
3801 
3802     for (i = 0; i < MAX_NUM_BUILDING_LIST; i++) {
3803       Impr_type_id n = game.rgame.global_init_buildings[i];
3804       if (n == B_LAST) {
3805         break;
3806       } else if (improvement_by_number(n) == pimprove) {
3807         cat_snprintf(buf, bufsz,
3808                      _("* All players start with this improvement in their "
3809                        "first city.\n"));
3810         break;
3811       }
3812     }
3813   }
3814 
3815   /* Assume no-one will set the same building in both global and nation
3816    * init_buildings... */
3817   nations_iterate(pnation) {
3818     int i;
3819 
3820     /* Avoid mentioning nations not in current set. */
3821     if (!show_help_for_nation(pnation)) {
3822       continue;
3823     }
3824     for (i = 0; i < MAX_NUM_BUILDING_LIST; i++) {
3825       Impr_type_id n = pnation->init_buildings[i];
3826       if (n == B_LAST) {
3827         break;
3828       } else if (improvement_by_number(n) == pimprove) {
3829         cat_snprintf(buf, bufsz,
3830                      /* TRANS: %s is a nation plural */
3831                      _("* The %s start with this improvement in their "
3832                        "first city.\n"), nation_plural_translation(pnation));
3833         break;
3834       }
3835     }
3836   } nations_iterate_end;
3837 
3838   if (improvement_has_flag(pimprove, IF_SAVE_SMALL_WONDER)) {
3839     cat_snprintf(buf, bufsz,
3840                  /* TRANS: don't translate 'savepalace' */
3841                  _("* If you lose the city containing this improvement, "
3842                    "it will be rebuilt for free in another of your cities "
3843                    "(if the 'savepalace' server setting is enabled).\n"));
3844   }
3845 
3846   if (user_text && user_text[0] != '\0') {
3847     cat_snprintf(buf, bufsz, "\n\n%s", user_text);
3848   }
3849   return buf;
3850 }
3851 
3852 /****************************************************************
3853   Is unit type ever able to build an extra
3854 *****************************************************************/
help_is_extra_buildable(struct extra_type * pextra,struct unit_type * ptype)3855 static bool help_is_extra_buildable(struct extra_type *pextra,
3856                                     struct unit_type *ptype)
3857 {
3858   if (!pextra->buildable) {
3859     return FALSE;
3860   }
3861 
3862   return are_reqs_active(NULL, NULL, NULL, NULL, NULL,
3863                          NULL, ptype, NULL, NULL, &pextra->reqs,
3864                          RPT_POSSIBLE);
3865 }
3866 
3867 /****************************************************************
3868   Is unit type ever able to clean out an extra
3869 *****************************************************************/
help_is_extra_cleanable(struct extra_type * pextra,struct unit_type * ptype)3870 static bool help_is_extra_cleanable(struct extra_type *pextra,
3871                                     struct unit_type *ptype)
3872 {
3873   return are_reqs_active(NULL, NULL, NULL, NULL, NULL,
3874                          NULL, ptype, NULL, NULL, &pextra->rmreqs,
3875                          RPT_POSSIBLE);
3876 }
3877 
3878 /****************************************************************
3879   Append misc dynamic text for units.
3880   Transport capacity, unit flags, fuel.
3881 
3882   pplayer may be NULL.
3883 *****************************************************************/
helptext_unit(char * buf,size_t bufsz,struct player * pplayer,const char * user_text,struct unit_type * utype)3884 char *helptext_unit(char *buf, size_t bufsz, struct player *pplayer,
3885 		    const char *user_text, struct unit_type *utype)
3886 {
3887   bool has_vet_levels;
3888   int flagid;
3889   struct unit_class *pclass;
3890 
3891   fc_assert_ret_val(NULL != buf && 0 < bufsz && NULL != user_text, NULL);
3892 
3893   if (!utype) {
3894     log_error("Unknown unit!");
3895     fc_strlcpy(buf, user_text, bufsz);
3896     return buf;
3897   }
3898 
3899   has_vet_levels = utype_veteran_levels(utype) > 1;
3900 
3901   buf[0] = '\0';
3902 
3903   pclass = utype_class(utype);
3904   cat_snprintf(buf, bufsz,
3905                _("* Belongs to %s unit class."),
3906                uclass_name_translation(pclass));
3907   if (NULL != pclass->helptext) {
3908     strvec_iterate(pclass->helptext, text) {
3909       cat_snprintf(buf, bufsz, "\n%s\n", _(text));
3910     } strvec_iterate_end;
3911   } else {
3912     CATLSTR(buf, bufsz, "\n");
3913   }
3914   if (uclass_has_flag(pclass, UCF_CAN_OCCUPY_CITY)
3915       && !utype_has_flag(utype, UTYF_CIVILIAN)) {
3916     /* TRANS: indented unit class property, preserve leading spaces */
3917     CATLSTR(buf, bufsz, _("  * Can occupy empty enemy cities.\n"));
3918   }
3919   if (!uclass_has_flag(pclass, UCF_TERRAIN_SPEED)) {
3920     /* TRANS: indented unit class property, preserve leading spaces */
3921     CATLSTR(buf, bufsz, _("  * Speed is not affected by terrain.\n"));
3922   }
3923   if (!uclass_has_flag(pclass, UCF_TERRAIN_DEFENSE)) {
3924     /* TRANS: indented unit class property, preserve leading spaces */
3925     CATLSTR(buf, bufsz, _("  * Does not get defense bonuses from terrain.\n"));
3926   }
3927   if (!uclass_has_flag(pclass, UCF_ZOC)) {
3928     /* TRANS: indented unit class property, preserve leading spaces */
3929     CATLSTR(buf, bufsz, _("  * Not subject to zones of control.\n"));
3930   } else if (!utype_has_flag(utype, UTYF_IGZOC)) {
3931     /* TRANS: indented unit class property, preserve leading spaces */
3932     CATLSTR(buf, bufsz, _("  * Subject to zones of control.\n"));
3933   }
3934   if (uclass_has_flag(pclass, UCF_DAMAGE_SLOWS)) {
3935     /* TRANS: indented unit class property, preserve leading spaces */
3936     CATLSTR(buf, bufsz, _("  * Slowed down while damaged.\n"));
3937   }
3938   if (uclass_has_flag(pclass, UCF_MISSILE)) {
3939     /* TRANS: indented unit class property, preserve leading spaces */
3940     CATLSTR(buf, bufsz, _("  * Gets used up in making an attack.\n"));
3941   }
3942   if (uclass_has_flag(pclass, UCF_CAN_FORTIFY)
3943       && !utype_has_flag(utype, UTYF_CANT_FORTIFY)) {
3944     if (utype->defense_strength > 0) {
3945       CATLSTR(buf, bufsz,
3946               /* TRANS: indented unit class property, preserve leading spaces */
3947               /* xgettext:no-c-format */
3948               _("  * Gets a 50% defensive bonus while in cities.\n"));
3949       CATLSTR(buf, bufsz,
3950               /* TRANS: indented unit class property, preserve leading spaces */
3951               /* xgettext:no-c-format */
3952               _("  * May fortify, granting a 50% defensive bonus when not in "
3953                 "a city.\n"));
3954     } else {
3955       CATLSTR(buf, bufsz,
3956               /* TRANS: indented unit class property, preserve leading spaces */
3957               _("  * May fortify to stay put.\n"));
3958     }
3959   }
3960   if (uclass_has_flag(pclass, UCF_UNREACHABLE)) {
3961     CATLSTR(buf, bufsz,
3962             /* TRANS: indented unit class property, preserve leading spaces */
3963 	    _("  * Is unreachable. Most units cannot attack this one.\n"));
3964   }
3965   if (uclass_has_flag(pclass, UCF_CAN_PILLAGE)) {
3966     CATLSTR(buf, bufsz,
3967             /* TRANS: indented unit class property, preserve leading spaces */
3968 	    _("  * Can pillage tile improvements.\n"));
3969   }
3970   if (uclass_has_flag(pclass, UCF_DOESNT_OCCUPY_TILE)
3971       && !utype_has_flag(utype, UTYF_CIVILIAN)) {
3972     CATLSTR(buf, bufsz,
3973             /* TRANS: indented unit class property, preserve leading spaces */
3974 	    _("  * Doesn't prevent enemy cities from working the tile it's on.\n"));
3975   }
3976   if (can_attack_non_native(utype)) {
3977     CATLSTR(buf, bufsz,
3978             /* TRANS: indented unit class property, preserve leading spaces */
3979 	    _("  * Can attack units on non-native tiles.\n"));
3980   }
3981   /* Must use flag to distinguish from UTYF_MARINES text. */
3982   if (utype->attack_strength > 0
3983       && uclass_has_flag(pclass, UCF_ATT_FROM_NON_NATIVE)) {
3984     CATLSTR(buf, bufsz,
3985             /* TRANS: indented unit class property, preserve leading spaces */
3986             _("  * Can launch attack from non-native tiles.\n"));
3987   }
3988   if (uclass_has_flag(pclass, UCF_AIRLIFTABLE)) {
3989     CATLSTR(buf, bufsz,
3990             /* TRANS: indented unit class property, preserve leading spaces */
3991             _("  * Can be airlifted from a suitable city.\n"));
3992   }
3993 
3994   /* The unit's combat bonuses. Won't mention that another unit type has a
3995    * combat bonus against this unit type. Doesn't handle complex cases like
3996    * when a unit type has multiple combat bonuses of the same kind. */
3997   combat_bonus_list_iterate(utype->bonuses, cbonus) {
3998     const char *against[utype_count()];
3999     int targets = 0;
4000 
4001     if (cbonus->quiet) {
4002       /* Handled in the help text of the ruleset. */
4003       continue;
4004     }
4005 
4006     /* Find the unit types of the bonus targets. */
4007     unit_type_iterate(utype2) {
4008       if (utype_has_flag(utype2, cbonus->flag)) {
4009         against[targets++] = utype_name_translation(utype2);
4010       }
4011     } unit_type_iterate_end;
4012 
4013     if (targets > 0) {
4014       struct astring list = ASTRING_INIT;
4015 
4016       switch (cbonus->type) {
4017       case CBONUS_DEFENSE_MULTIPLIER:
4018         cat_snprintf(buf, bufsz,
4019                      /* TRANS: multipied by ... or-list of unit types */
4020                      _("* %dx defense bonus if attacked by %s.\n"),
4021                      cbonus->value + 1,
4022                      astr_build_or_list(&list, against, targets));
4023         break;
4024       case CBONUS_DEFENSE_DIVIDER:
4025         cat_snprintf(buf, bufsz,
4026                      /* TRANS: defense divider ... or-list of unit types */
4027                      _("* Reduces target's defense to 1 / %d when "
4028                        "attacking %s.\n"),
4029                      cbonus->value + 1,
4030                      astr_build_or_list(&list, against, targets));
4031         break;
4032       case CBONUS_FIREPOWER1:
4033         cat_snprintf(buf, bufsz,
4034                      /* TRANS: or-list of unit types */
4035                      _("* Reduces target's firepower to 1 when "
4036                        "attacking %s.\n"),
4037                      astr_build_and_list(&list, against, targets));
4038         break;
4039       }
4040 
4041       astr_free(&list);
4042     }
4043   } combat_bonus_list_iterate_end;
4044 
4045   if (utype->need_improvement) {
4046     cat_snprintf(buf, bufsz,
4047                  _("* Can only be built if there is %s in the city.\n"),
4048                  improvement_name_translation(utype->need_improvement));
4049   }
4050 
4051   if (utype->need_government) {
4052     cat_snprintf(buf, bufsz,
4053                  _("* Can only be built with %s as government.\n"),
4054                  government_name_translation(utype->need_government));
4055   }
4056 
4057   if (utype_has_flag(utype, UTYF_NOBUILD)) {
4058     CATLSTR(buf, bufsz, _("* May not be built in cities.\n"));
4059   }
4060   if (utype_has_flag(utype, UTYF_BARBARIAN_ONLY)) {
4061     CATLSTR(buf, bufsz, _("* Only barbarians may build this.\n"));
4062   }
4063   if (utype_has_flag(utype, UTYF_NEWCITY_GAMES_ONLY)) {
4064     CATLSTR(buf, bufsz, _("* Can only be built in games where new cities "
4065                           "are allowed.\n"));
4066     if (game.scenario.prevent_new_cities) {
4067       /* TRANS: indented; preserve leading spaces */
4068       CATLSTR(buf, bufsz, _("  - New cities are not allowed in the current "
4069                             "game.\n"));
4070     } else {
4071       /* TRANS: indented; preserve leading spaces */
4072       CATLSTR(buf, bufsz, _("  - New cities are allowed in the current "
4073                             "game.\n"));
4074     }
4075   }
4076   nations_iterate(pnation) {
4077     int i, count = 0;
4078 
4079     /* Avoid mentioning nations not in current set. */
4080     if (!show_help_for_nation(pnation)) {
4081       continue;
4082     }
4083     for (i = 0; i < MAX_NUM_UNIT_LIST; i++) {
4084       if (!pnation->init_units[i]) {
4085         break;
4086       } else if (pnation->init_units[i] == utype) {
4087         count++;
4088       }
4089     }
4090     if (count > 0) {
4091       cat_snprintf(buf, bufsz,
4092                    /* TRANS: %s is a nation plural */
4093                    PL_("* The %s start the game with %d of these units.\n",
4094                        "* The %s start the game with %d of these units.\n",
4095                        count),
4096                    nation_plural_translation(pnation), count);
4097     }
4098   } nations_iterate_end;
4099   {
4100     const char *types[utype_count()];
4101     int i = 0;
4102     unit_type_iterate(utype2) {
4103       if (utype2->converted_to == utype) {
4104         types[i++] = utype_name_translation(utype2);
4105       }
4106     } unit_type_iterate_end;
4107     if (i > 0) {
4108       struct astring list = ASTRING_INIT;
4109       astr_build_or_list(&list, types, i);
4110       cat_snprintf(buf, bufsz,
4111                    /* TRANS: %s is a list of unit types separated by "or". */
4112                    _("* May be obtained by conversion of %s.\n"),
4113                    astr_str(&list));
4114       astr_free(&list);
4115     }
4116   }
4117   if (NULL != utype->converted_to) {
4118     cat_snprintf(buf, bufsz,
4119                  /* TRANS: %s is a unit type. "MP" = movement points. */
4120                  PL_("* May be converted into %s (takes %d MP).\n",
4121                      "* May be converted into %s (takes %d MP).\n",
4122                      utype->convert_time),
4123                  utype_name_translation(utype->converted_to),
4124                  utype->convert_time);
4125   }
4126   if (utype_has_flag(utype, UTYF_NOHOME)) {
4127     CATLSTR(buf, bufsz, _("* Never has a home city.\n"));
4128   }
4129   if (utype_has_flag(utype, UTYF_GAMELOSS)) {
4130     CATLSTR(buf, bufsz, _("* Losing this unit will lose you the game!\n"));
4131   }
4132   if (utype_has_flag(utype, UTYF_UNIQUE)) {
4133     CATLSTR(buf, bufsz,
4134 	    _("* Each player may only have one of this type of unit.\n"));
4135   }
4136   for (flagid = UTYF_USER_FLAG_1 ; flagid <= UTYF_LAST_USER_FLAG; flagid++) {
4137     if (utype_has_flag(utype, flagid)) {
4138       const char *helptxt = unit_type_flag_helptxt(flagid);
4139 
4140       if (helptxt != NULL) {
4141         /* TRANS: bullet point; note trailing space */
4142         CATLSTR(buf, bufsz, Q_("?bullet:* "));
4143         CATLSTR(buf, bufsz, _(helptxt));
4144         CATLSTR(buf, bufsz, "\n");
4145       }
4146     }
4147   }
4148   if (utype->pop_cost > 0) {
4149     cat_snprintf(buf, bufsz,
4150                  PL_("* Costs %d population to build.\n",
4151                      "* Costs %d population to build.\n", utype->pop_cost),
4152                  utype->pop_cost);
4153   }
4154   if (0 < utype->transport_capacity) {
4155     const char *classes[uclass_count()];
4156     int i = 0;
4157     struct astring list = ASTRING_INIT;
4158 
4159     unit_class_iterate(uclass) {
4160       if (can_unit_type_transport(utype, uclass)) {
4161         classes[i++] = uclass_name_translation(uclass);
4162       }
4163     } unit_class_iterate_end;
4164     astr_build_or_list(&list, classes, i);
4165 
4166     cat_snprintf(buf, bufsz,
4167                  /* TRANS: %s is a list of unit classes separated by "or". */
4168                  PL_("* Can carry and refuel %d %s unit.\n",
4169                      "* Can carry and refuel up to %d %s units.\n",
4170                      utype->transport_capacity),
4171                  utype->transport_capacity, astr_str(&list));
4172     astr_free(&list);
4173     if (uclass_has_flag(utype_class(utype), UCF_UNREACHABLE)) {
4174       /* Document restrictions on when units can load/unload */
4175       bool has_restricted_load = FALSE, has_unrestricted_load = FALSE,
4176            has_restricted_unload = FALSE, has_unrestricted_unload = FALSE;
4177       unit_type_iterate(pcargo) {
4178         if (can_unit_type_transport(utype, utype_class(pcargo))) {
4179           if (utype_can_freely_load(pcargo, utype)) {
4180             has_unrestricted_load = TRUE;
4181           } else {
4182             has_restricted_load = TRUE;
4183           }
4184           if (utype_can_freely_unload(pcargo, utype)) {
4185             has_unrestricted_unload = TRUE;
4186           } else {
4187             has_restricted_unload = TRUE;
4188           }
4189         }
4190       } unit_type_iterate_end;
4191       if (has_restricted_load) {
4192         if (has_unrestricted_load) {
4193           /* At least one type of cargo can load onto us freely.
4194            * The specific exceptions will be documented in cargo help. */
4195           CATLSTR(buf, bufsz,
4196                   /* TRANS: indented; preserve leading spaces */
4197                   _("  * Some cargo cannot be loaded except in a city or a "
4198                     "base native to this transport.\n"));
4199         } else {
4200           /* No exceptions */
4201           CATLSTR(buf, bufsz,
4202                   /* TRANS: indented; preserve leading spaces */
4203                   _("  * Cargo cannot be loaded except in a city or a "
4204                     "base native to this transport.\n"));
4205         }
4206       } /* else, no restricted cargo exists; keep quiet */
4207       if (has_restricted_unload) {
4208         if (has_unrestricted_unload) {
4209           /* At least one type of cargo can unload from us freely. */
4210           CATLSTR(buf, bufsz,
4211                   /* TRANS: indented; preserve leading spaces */
4212                   _("  * Some cargo cannot be unloaded except in a city or a "
4213                     "base native to this transport.\n"));
4214         } else {
4215           /* No exceptions */
4216           CATLSTR(buf, bufsz,
4217                   /* TRANS: indented; preserve leading spaces */
4218                   _("  * Cargo cannot be unloaded except in a city or a "
4219                     "base native to this transport.\n"));
4220         }
4221       } /* else, no restricted cargo exists; keep quiet */
4222     }
4223   }
4224   if (utype_has_flag(utype, UTYF_TRIREME)) {
4225     CATLSTR(buf, bufsz, _("* Must stay next to coast.\n"));
4226   }
4227   {
4228     /* Document exceptions to embark/disembark restrictions that we
4229      * have as cargo. */
4230     bv_unit_classes embarks, disembarks;
4231     BV_CLR_ALL(embarks);
4232     BV_CLR_ALL(disembarks);
4233     /* Determine which of our transport classes have restrictions in the first
4234      * place (that is, contain at least one transport which carries at least
4235      * one type of cargo which is restricted).
4236      * We'll suppress output for classes not in this set, since this cargo
4237      * type is not behaving exceptionally in such cases. */
4238     unit_type_iterate(utrans) {
4239       const Unit_Class_id trans_class = uclass_index(utype_class(utrans));
4240       /* Don't waste time repeating checks on classes we've already checked,
4241        * or weren't under consideration in the first place */
4242       if (!BV_ISSET(embarks, trans_class)
4243           && BV_ISSET(utype->embarks, trans_class)) {
4244         unit_type_iterate(other_cargo) {
4245           if (can_unit_type_transport(utrans, utype_class(other_cargo))
4246               && !utype_can_freely_load(other_cargo, utrans)) {
4247             /* At least one load restriction in transport class, which
4248              * we aren't subject to */
4249             BV_SET(embarks, trans_class);
4250           }
4251         } unit_type_iterate_end; /* cargo */
4252       }
4253       if (!BV_ISSET(disembarks, trans_class)
4254           && BV_ISSET(utype->disembarks, trans_class)) {
4255         unit_type_iterate(other_cargo) {
4256           if (can_unit_type_transport(utrans, utype_class(other_cargo))
4257               && !utype_can_freely_unload(other_cargo, utrans)) {
4258             /* At least one load restriction in transport class, which
4259              * we aren't subject to */
4260             BV_SET(disembarks, trans_class);
4261           }
4262         } unit_type_iterate_end; /* cargo */
4263       }
4264     } unit_class_iterate_end; /* transports */
4265 
4266     if (BV_ISSET_ANY(embarks)) {
4267       /* Build list of embark exceptions */
4268       const char *eclasses[uclass_count()];
4269       int i = 0;
4270       struct astring elist = ASTRING_INIT;
4271 
4272       unit_class_iterate(uclass) {
4273         if (BV_ISSET(embarks, uclass_index(uclass))) {
4274           eclasses[i++] = uclass_name_translation(uclass);
4275         }
4276       } unit_class_iterate_end;
4277       astr_build_or_list(&elist, eclasses, i);
4278       if (BV_ARE_EQUAL(embarks, disembarks)) {
4279         /* A common case: the list of disembark exceptions is identical */
4280         cat_snprintf(buf, bufsz,
4281                      /* TRANS: %s is a list of unit classes separated
4282                       * by "or". */
4283                      _("* May load onto and unload from %s transports even "
4284                        "when underway.\n"),
4285                      astr_str(&elist));
4286       } else {
4287         cat_snprintf(buf, bufsz,
4288                      /* TRANS: %s is a list of unit classes separated
4289                       * by "or". */
4290                      _("* May load onto %s transports even when underway.\n"),
4291                      astr_str(&elist));
4292       }
4293       astr_free(&elist);
4294     }
4295     if (BV_ISSET_ANY(disembarks) && !BV_ARE_EQUAL(embarks, disembarks)) {
4296       /* Build list of disembark exceptions (if different from embarking) */
4297       const char *dclasses[uclass_count()];
4298       int i = 0;
4299       struct astring dlist = ASTRING_INIT;
4300 
4301       unit_class_iterate(uclass) {
4302         if (BV_ISSET(disembarks, uclass_index(uclass))) {
4303           dclasses[i++] = uclass_name_translation(uclass);
4304         }
4305       } unit_class_iterate_end;
4306       astr_build_or_list(&dlist, dclasses, i);
4307       cat_snprintf(buf, bufsz,
4308                    /* TRANS: %s is a list of unit classes separated
4309                     * by "or". */
4310                    _("* May unload from %s transports even when underway.\n"),
4311                    astr_str(&dlist));
4312       astr_free(&dlist);
4313     }
4314   }
4315   if (utype_has_flag(utype, UTYF_UNDISBANDABLE)) {
4316     CATLSTR(buf, bufsz, _("* May not be disbanded.\n"));
4317   } else {
4318     CATLSTR(buf, bufsz,
4319 	    /* xgettext:no-c-format */
4320 	    _("* May be disbanded in a city to recover 50% of the"
4321 	      " production cost.\n"));
4322   }
4323   if (utype_is_cityfounder(utype)) {
4324     cat_snprintf(buf, bufsz,
4325                  PL_("* Can build new cities (initial population %d).\n",
4326                      "* Can build new cities (initial population %d).\n",
4327                      utype->city_size),
4328                  utype->city_size);
4329   }
4330   if (utype_has_flag(utype, UTYF_ADD_TO_CITY)) {
4331     cat_snprintf(buf, bufsz,
4332                  /* TRANS: Plural in "%d population", not "size %d". */
4333 		 PL_("* Can add on %d population to cities of no more than"
4334                      " size %d.\n",
4335 		     "* Can add on %d population to cities of no more than"
4336                      " size %d.\n", utype_pop_value(utype)),
4337 		 utype_pop_value(utype),
4338 		 game.info.add_to_size_limit - utype_pop_value(utype));
4339   }
4340   if (utype_has_flag(utype, UTYF_SETTLERS)) {
4341     struct universal for_utype = { .kind = VUT_UTYPE, .value = { .utype = utype }};
4342     struct astring extras_and = ASTRING_INIT;
4343     struct strvec *extras_vec = strvec_new();
4344 
4345     /* Roads, rail, mines, irrigation. */
4346     extra_type_by_cause_iterate(EC_ROAD, pextra) {
4347       if (help_is_extra_buildable(pextra, utype)) {
4348         strvec_append(extras_vec, extra_name_translation(pextra));
4349       }
4350     } extra_type_by_cause_iterate_end;
4351     if (strvec_size(extras_vec) > 0) {
4352       strvec_to_and_list(extras_vec, &extras_and);
4353       /* TRANS: %s is list of extra types separated by ',' and 'and' */
4354       cat_snprintf(buf, bufsz, _("* Can build %s on tiles.\n"),
4355                    astr_str(&extras_and));
4356       strvec_clear(extras_vec);
4357     }
4358 
4359     if (effect_cumulative_max(EFT_MINING_POSSIBLE, &for_utype) > 0) {
4360       extra_type_by_cause_iterate(EC_MINE, pextra) {
4361         if (help_is_extra_buildable(pextra, utype)) {
4362           strvec_append(extras_vec, extra_name_translation(pextra));
4363         }
4364       } extra_type_by_cause_iterate_end;
4365 
4366       if (strvec_size(extras_vec) > 0) {
4367         strvec_to_and_list(extras_vec, &extras_and);
4368         cat_snprintf(buf, bufsz, _("* Can build %s on tiles.\n"),
4369                      astr_str(&extras_and));
4370         strvec_clear(extras_vec);
4371       }
4372     }
4373     if (effect_cumulative_max(EFT_MINING_TF_POSSIBLE, &for_utype) > 0) {
4374       CATLSTR(buf, bufsz, _("* Can convert terrain to another type by "
4375                             "mining.\n"));
4376     }
4377 
4378     if (effect_cumulative_max(EFT_IRRIG_POSSIBLE, &for_utype) > 0) {
4379       extra_type_by_cause_iterate(EC_IRRIGATION, pextra) {
4380         if (help_is_extra_buildable(pextra, utype)) {
4381           strvec_append(extras_vec, extra_name_translation(pextra));
4382         }
4383       } extra_type_by_cause_iterate_end;
4384 
4385       if (strvec_size(extras_vec) > 0) {
4386         strvec_to_and_list(extras_vec, &extras_and);
4387         cat_snprintf(buf, bufsz, _("* Can build %s on tiles.\n"),
4388                      astr_str(&extras_and));
4389         strvec_clear(extras_vec);
4390       }
4391     }
4392     if (effect_cumulative_max(EFT_IRRIG_TF_POSSIBLE, &for_utype) > 0) {
4393       CATLSTR(buf, bufsz, _("* Can convert terrain to another type by "
4394                             "irrigation.\n"));
4395     }
4396     if (effect_cumulative_max(EFT_TRANSFORM_POSSIBLE, &for_utype) > 0) {
4397       CATLSTR(buf, bufsz, _("* Can transform terrain to another type.\n"));
4398     }
4399 
4400     extra_type_by_cause_iterate(EC_BASE, pextra) {
4401       if (help_is_extra_buildable(pextra, utype)) {
4402         strvec_append(extras_vec, extra_name_translation(pextra));
4403       }
4404     } extra_type_by_cause_iterate_end;
4405 
4406     if (strvec_size(extras_vec) > 0) {
4407       strvec_to_and_list(extras_vec, &extras_and);
4408       cat_snprintf(buf, bufsz, _("* Can build %s on tiles.\n"),
4409                    astr_str(&extras_and));
4410       strvec_clear(extras_vec);
4411     }
4412 
4413     /* Pollution, fallout. */
4414     extra_type_by_rmcause_iterate(ERM_CLEANPOLLUTION, pextra) {
4415       if (help_is_extra_cleanable(pextra, utype)) {
4416         strvec_append(extras_vec, extra_name_translation(pextra));
4417      }
4418     } extra_type_by_rmcause_iterate_end;
4419 
4420     if (strvec_size(extras_vec) > 0) {
4421       strvec_to_and_list(extras_vec, &extras_and);
4422       /* TRANS: list of extras separated by "and" */
4423       cat_snprintf(buf, bufsz, _("* Can clean %s from tiles.\n"),
4424                    astr_str(&extras_and));
4425       strvec_clear(extras_vec);
4426     }
4427 
4428     extra_type_by_rmcause_iterate(ERM_CLEANFALLOUT, pextra) {
4429       if (help_is_extra_cleanable(pextra, utype)) {
4430         strvec_append(extras_vec, extra_name_translation(pextra));
4431       }
4432     } extra_type_by_rmcause_iterate_end;
4433 
4434     if (strvec_size(extras_vec) > 0) {
4435       strvec_to_and_list(extras_vec, &extras_and);
4436       /* TRANS: list of extras separated by "and" */
4437       cat_snprintf(buf, bufsz, _("* Can clean %s from tiles.\n"),
4438                    astr_str(&extras_and));
4439       strvec_clear(extras_vec);
4440     }
4441 
4442     strvec_destroy(extras_vec);
4443   }
4444 
4445   if (utype_has_flag(utype, UTYF_SPY)) {
4446     CATLSTR(buf, bufsz, _("* Performs better diplomatic actions.\n"));
4447   }
4448   if (utype_has_flag(utype, UTYF_DIPLOMAT)
4449       || utype_has_flag(utype, UTYF_SUPERSPY)) {
4450     CATLSTR(buf, bufsz, _("* Defends cities against diplomatic actions.\n"));
4451   }
4452   if (utype_has_flag(utype, UTYF_SUPERSPY)) {
4453     CATLSTR(buf, bufsz, _("* Will never lose a diplomat-versus-diplomat fight.\n"));
4454   }
4455   if (utype_has_flag(utype, UTYF_SPY)
4456       && utype_has_flag(utype, UTYF_SUPERSPY)) {
4457     CATLSTR(buf, bufsz, _("* Will always survive a spy mission.\n"));
4458   }
4459   if (utype_has_flag(utype, UTYF_PARTIAL_INVIS)) {
4460     CATLSTR(buf, bufsz,
4461             _("* Is invisible except when next to an enemy unit or city.\n"));
4462   }
4463   if (utype_has_flag(utype, UTYF_ONLY_NATIVE_ATTACK)) {
4464     CATLSTR(buf, bufsz,
4465             _("* Can only attack units on native tiles.\n"));
4466   }
4467   /* Must use flag to distinguish from UCF_ATT_FROM_NON_NATIVE text. */
4468   if (utype_has_flag(utype, UTYF_MARINES)) {
4469     CATLSTR(buf, bufsz,
4470             _("* Can launch attack from non-native tiles.\n"));
4471   }
4472   if (utype_has_flag(utype, UTYF_PARATROOPERS)) {
4473     cat_snprintf(buf, bufsz,
4474                  PL_("* Can be paradropped from a friendly city or suitable "
4475                       "base (range: %d tile).\n",
4476                      "* Can be paradropped from a friendly city or suitable "
4477                       "base (range: %d tiles).\n",
4478                      utype->paratroopers_range),
4479                  utype->paratroopers_range);
4480   }
4481   if (!uclass_has_flag(utype_class(utype), UCF_MISSILE)
4482       && utype_has_flag(utype, UTYF_ONEATTACK)) {
4483     CATLSTR(buf, bufsz,
4484 	    _("* Making an attack ends this unit's turn.\n"));
4485   }
4486   if (utype_has_flag(utype, UTYF_NUCLEAR)) {
4487     CATLSTR(buf, bufsz,
4488 	    _("* This unit's attack causes a nuclear explosion!\n"));
4489   }
4490   if (utype_has_flag(utype, UTYF_CITYBUSTER)) {
4491     CATLSTR(buf, bufsz,
4492 	    _("* Gets double firepower when attacking cities.\n"));
4493   }
4494   if (utype_has_flag(utype, UTYF_BOMBARDER)) {
4495     /* FIXME: also they only happen against land units. We leave the
4496      * ruleset author to document this. */
4497     cat_snprintf(buf, bufsz,
4498 		 _("* Does bombard attacks (%d per turn). These attacks will"
4499 		   " only damage (never kill) defenders, but damage all"
4500                    " defenders on a tile, and have no risk for the"
4501                    " attacker.\n"),
4502 		 utype->bombard_rate);
4503   }
4504   if (utype_has_flag(utype, UTYF_IGTER)) {
4505     cat_snprintf(buf, bufsz,
4506                  /* TRANS: "MP" = movement points. %s may have a
4507                   * fractional part. */
4508                  _("* Ignores terrain effects (moving costs at most %s MP "
4509                    "per tile).\n"),
4510                  move_points_text(terrain_control.igter_cost, TRUE));
4511   }
4512   if (utype_has_flag(utype, UTYF_NOZOC)) {
4513     CATLSTR(buf, bufsz, _("* Never imposes a zone of control.\n"));
4514   } else {
4515     CATLSTR(buf, bufsz, _("* May impose a zone of control on its adjacent "
4516                           "tiles.\n"));
4517   }
4518   if (utype_has_flag(utype, UTYF_IGZOC)) {
4519     CATLSTR(buf, bufsz, _("* Not subject to zones of control imposed "
4520                           "by other units.\n"));
4521   }
4522   if (utype_has_flag(utype, UTYF_CIVILIAN)) {
4523     CATLSTR(buf, bufsz,
4524             _("* A non-military unit:\n"));
4525     CATLSTR(buf, bufsz,
4526             /* TRANS: indented; preserve leading spaces */
4527             _("  * Cannot attack.\n"));
4528     CATLSTR(buf, bufsz,
4529             /* TRANS: indented; preserve leading spaces */
4530             _("  * Doesn't impose martial law.\n"));
4531     CATLSTR(buf, bufsz,
4532             /* TRANS: indented; preserve leading spaces */
4533             _("  * Can enter foreign territory regardless of peace treaty.\n"));
4534     CATLSTR(buf, bufsz,
4535             /* TRANS: indented; preserve leading spaces */
4536             _("  * Doesn't prevent enemy cities from working the tile it's on.\n"));
4537   }
4538   if (utype_has_flag(utype, UTYF_FIELDUNIT)) {
4539     CATLSTR(buf, bufsz,
4540             _("* A field unit: one unhappiness applies even when non-aggressive.\n"));
4541   }
4542   if (utype_has_flag(utype, UTYF_CAPTURER)) {
4543     CATLSTR(buf, bufsz, _("* Can capture some enemy units.\n"));
4544   }
4545   if (utype_has_flag(utype, UTYF_CAPTURABLE)) {
4546     CATLSTR(buf, bufsz, _("* Can be captured by some enemy units.\n"));
4547   }
4548   if (utype_has_flag(utype, UTYF_SHIELD2GOLD)) {
4549     /* FIXME: the conversion shield => gold is activated if
4550      *        EFT_SHIELD2GOLD_FACTOR is not equal null; how to determine
4551      *        possible sources? */
4552     CATLSTR(buf, bufsz,
4553             _("* Under certain conditions the shield upkeep of this unit can "
4554               "be converted to gold upkeep.\n"));
4555   }
4556 
4557   unit_class_iterate(target) {
4558     if (uclass_has_flag(target, UCF_UNREACHABLE)
4559         && BV_ISSET(utype->targets, uclass_index(target))) {
4560       cat_snprintf(buf, bufsz,
4561                    _("* Can attack against %s units, which are usually not "
4562                      "reachable.\n"),
4563                    uclass_name_translation(target));
4564     }
4565   } unit_class_iterate_end;
4566   if (utype_fuel(utype)) {
4567     const char *types[utype_count()];
4568     int i = 0;
4569 
4570     unit_type_iterate(transport) {
4571       if (can_unit_type_transport(transport, utype_class(utype))) {
4572         types[i++] = utype_name_translation(transport);
4573       }
4574     } unit_type_iterate_end;
4575 
4576     if (0 == i) {
4577      cat_snprintf(buf, bufsz,
4578                    PL_("* Unit has to be in a city or a base"
4579                        " after %d turn.\n",
4580                        "* Unit has to be in a city or a base"
4581                        " after %d turns.\n",
4582                        utype_fuel(utype)),
4583                   utype_fuel(utype));
4584     } else {
4585       struct astring list = ASTRING_INIT;
4586 
4587       cat_snprintf(buf, bufsz,
4588                    /* TRANS: %s is a list of unit types separated by "or" */
4589                    PL_("* Unit has to be in a city, a base, or on a %s"
4590                        " after %d turn.\n",
4591                        "* Unit has to be in a city, a base, or on a %s"
4592                        " after %d turns.\n",
4593                        utype_fuel(utype)),
4594                    astr_build_or_list(&list, types, i), utype_fuel(utype));
4595       astr_free(&list);
4596     }
4597   }
4598   action_iterate(act) {
4599     if (action_by_number(act)->quiet) {
4600       /* The ruleset documents this action it self. */
4601       continue;
4602     }
4603 
4604     if (action_id_get_actor_kind(act) != AAK_UNIT) {
4605       continue;
4606     }
4607 
4608     action_enabler_list_iterate(action_enablers_for_action(act), enabler) {
4609       if (requirement_fulfilled_by_unit_type(utype,
4610                                              &(enabler->actor_reqs))) {
4611         switch (act) {
4612         case ACTION_HELP_WONDER:
4613           cat_snprintf(buf, bufsz,
4614                        /* TRANS: the first %s is the ruleset defined ui
4615                         * name of the "Help Wonder" action, the next %s is
4616                         * the name of its target kind ("individual cities")
4617                         * and the %d is the number of shields the unit can
4618                         * contribute. */
4619                        _("* Can do the action \'%s\' to some %s"
4620                          " (adds %d production).\n"),
4621                        /* The action may have a ruleset defined ui name. */
4622                        action_id_name_translation(act),
4623                        /* Keep the style consistent with the help for the
4624                         * other actions. */
4625                        _(action_target_kind_name(
4626                            action_id_get_target_kind(act))),
4627                        /* The custom information. */
4628                        utype_build_shield_cost(utype));
4629           break;
4630         default:
4631           /* Generic action information. */
4632           cat_snprintf(buf, bufsz,
4633                        /* TRANS: the first %s is the action's ruleset
4634                         * defined ui name and the next %s is the name of
4635                         * its target kind. */
4636                        _("* Can do the action \'%s\' to some %s.\n"),
4637                        action_id_name_translation(act),
4638                        _(action_target_kind_name(
4639                            action_id_get_target_kind(act))));
4640           break;
4641         }
4642 
4643         /* The unit's ability to perform this action was just documented.
4644          * Move on to check if the unit can perform the next action too. */
4645         break;
4646       }
4647     } action_enabler_list_iterate_end;
4648   } action_iterate_end;
4649   action_iterate(act) {
4650     bool vulnerable;
4651 
4652     if (action_by_number(act)->quiet) {
4653       /* The ruleset documents this action it self. */
4654       continue;
4655     }
4656 
4657     /* Not relevant */
4658     if (action_id_get_target_kind(act) != ATK_UNIT) {
4659       continue;
4660     }
4661 
4662     /* All units are immune to this since its not enabled */
4663     if (action_enabler_list_size(action_enablers_for_action(act)) == 0) {
4664       continue;
4665     }
4666 
4667     /* Must be immune in all cases */
4668     vulnerable = FALSE;
4669     action_enabler_list_iterate(action_enablers_for_action(act), enabler) {
4670       if (requirement_fulfilled_by_unit_type(utype,
4671                                              &(enabler->target_reqs))) {
4672         vulnerable = TRUE;
4673         break;
4674       }
4675     } action_enabler_list_iterate_end;
4676 
4677     if (!vulnerable) {
4678       cat_snprintf(buf, bufsz,
4679                    _("* Doing the action \'%s\' to this unit"
4680                      " is impossible.\n"),
4681                    action_id_name_translation(act));
4682     }
4683   } action_iterate_end;
4684   if (!has_vet_levels) {
4685     /* Only mention this if the game generally does have veteran levels. */
4686     if (game.veteran->levels > 1) {
4687       CATLSTR(buf, bufsz, _("* Will never achieve veteran status.\n"));
4688     }
4689   } else {
4690     /* Not useful currently: */
4691 #if 0
4692     /* Some units can never become veteran through combat in practice. */
4693     bool veteran_through_combat =
4694       !((utype->attack_strength == 0
4695          || uclass_has_flag(utype_class(utype), UCF_MISSILE))
4696         && utype->defense_strength == 0);
4697 #endif
4698     /* FIXME: if we knew the raise chances on the client, we could be
4699      * more specific here about whether veteran status can be acquired
4700      * through combat/missions/work. Should also take into account
4701      * UTYF_NO_VETERAN when writing this text. (Gna patch #4794) */
4702     CATLSTR(buf, bufsz, _("* May acquire veteran status.\n"));
4703     if (utype_veteran_has_power_bonus(utype)) {
4704       if ((!utype_has_flag(utype, UTYF_NUCLEAR) && utype->attack_strength > 0)
4705           || utype->defense_strength > 0) {
4706         CATLSTR(buf, bufsz,
4707                 /* TRANS: indented; preserve leading spaces */
4708                 _("  * Veterans have increased strength in combat.\n"));
4709       }
4710       /* SUPERSPY always wins/escapes */
4711       if ((utype_has_flag(utype, UTYF_DIPLOMAT)
4712            || utype_has_flag(utype, UTYF_SPY))
4713           && !utype_has_flag(utype, UTYF_SUPERSPY)) {
4714         CATLSTR(buf, bufsz,
4715                 /* TRANS: indented; preserve leading spaces */
4716                 _("  * Veterans have improved chances in diplomatic "
4717                   "contests.\n"));
4718         if (utype_has_flag(utype, UTYF_SPY) &&
4719             (utype_can_do_action(utype, ACTION_SPY_POISON)
4720              || utype_can_do_action(utype, ACTION_SPY_SABOTAGE_UNIT)
4721              || utype_can_do_action(utype, ACTION_SPY_STEAL_TECH)
4722              || utype_can_do_action(utype, ACTION_SPY_TARGETED_STEAL_TECH)
4723              || utype_can_do_action(utype, ACTION_SPY_INCITE_CITY)
4724              || utype_can_do_action(utype, ACTION_SPY_SABOTAGE_CITY)
4725              || utype_can_do_action(utype, ACTION_SPY_TARGETED_SABOTAGE_CITY)
4726              || utype_can_do_action(utype, ACTION_SPY_STEAL_GOLD))) {
4727           CATLSTR(buf, bufsz,
4728                 /* TRANS: indented; preserve leading spaces */
4729                   _("  * Veterans are more likely to survive missions.\n"));
4730         }
4731       }
4732       if (utype_has_flag(utype, UTYF_SETTLERS)) {
4733         CATLSTR(buf, bufsz,
4734                 /* TRANS: indented; preserve leading spaces */
4735                 _("  * Veterans work faster.\n"));
4736       }
4737     }
4738   }
4739   if (strlen(buf) > 0) {
4740     CATLSTR(buf, bufsz, "\n");
4741   }
4742   if (has_vet_levels && utype->veteran) {
4743     /* The case where the unit has only a single veteran level has already
4744      * been handled above, so keep quiet here if that happens */
4745     if (insert_veteran_help(buf, bufsz, utype->veteran,
4746             _("This type of unit has its own veteran levels:"), NULL)) {
4747       CATLSTR(buf, bufsz, "\n\n");
4748     }
4749   }
4750   if (NULL != utype->helptext) {
4751     strvec_iterate(utype->helptext, text) {
4752       cat_snprintf(buf, bufsz, "%s\n\n", _(text));
4753     } strvec_iterate_end;
4754   }
4755   CATLSTR(buf, bufsz, user_text);
4756   return buf;
4757 }
4758 
4759 /****************************************************************************
4760   Append misc dynamic text for advance/technology.
4761 
4762   pplayer may be NULL.
4763 ****************************************************************************/
helptext_advance(char * buf,size_t bufsz,struct player * pplayer,const char * user_text,int i)4764 void helptext_advance(char *buf, size_t bufsz, struct player *pplayer,
4765                       const char *user_text, int i)
4766 {
4767   struct astring astr = ASTRING_INIT;
4768   struct advance *vap = valid_advance_by_number(i);
4769   struct universal source = {
4770     .kind = VUT_ADVANCE,
4771     .value = {.advance = vap}
4772   };
4773   int flagid;
4774 
4775   fc_assert_ret(NULL != buf && 0 < bufsz && NULL != user_text);
4776   fc_strlcpy(buf, user_text, bufsz);
4777 
4778   if (NULL == vap) {
4779     log_error("Unknown tech %d.", i);
4780     return;
4781   }
4782 
4783   if (NULL != pplayer) {
4784     const struct research *presearch = research_get(pplayer);
4785 
4786     if (research_invention_state(presearch, i) != TECH_KNOWN) {
4787       if (research_invention_state(presearch, i) == TECH_PREREQS_KNOWN) {
4788         int bulbs = research_total_bulbs_required(presearch, i, FALSE);
4789 
4790         cat_snprintf(buf, bufsz,
4791                      PL_("Starting now, researching %s would need %d bulb.",
4792                          "Starting now, researching %s would need %d bulbs.",
4793                          bulbs),
4794                      advance_name_translation(vap), bulbs);
4795       } else if (research_invention_reachable(presearch, i)) {
4796         /* Split string into two to allow localization of two pluralizations. */
4797         char buf2[MAX_LEN_MSG];
4798         int bulbs = research_goal_bulbs_required(presearch, i);
4799 
4800         fc_snprintf(buf2, ARRAY_SIZE(buf2),
4801                     /* TRANS: appended to another sentence. Preserve the
4802                      * leading space. */
4803                     PL_(" The whole project will require %d bulb to complete.",
4804                         " The whole project will require %d bulbs to complete.",
4805                         bulbs),
4806                     bulbs);
4807         cat_snprintf(buf, bufsz,
4808                      /* TRANS: last %s is a sentence pluralized separately. */
4809                      PL_("To research %s you need to research %d other"
4810                          " technology first.%s",
4811                          "To research %s you need to research %d other"
4812                          " technologies first.%s",
4813                          research_goal_unknown_techs(presearch, i) - 1),
4814                      advance_name_translation(vap),
4815                      research_goal_unknown_techs(presearch, i) - 1, buf2);
4816       } else {
4817         CATLSTR(buf, bufsz,
4818                 _("You cannot research this technology."));
4819       }
4820       if (!techs_have_fixed_costs()
4821           && research_invention_reachable(presearch, i)) {
4822         CATLSTR(buf, bufsz,
4823                 /* TRANS: preserve leading space */
4824                 _(" This number may vary depending on what "
4825                   "other players research.\n"));
4826       } else {
4827         CATLSTR(buf, bufsz, "\n");
4828       }
4829     }
4830 
4831     CATLSTR(buf, bufsz, "\n");
4832   }
4833 
4834   insert_allows(&source, buf + strlen(buf), bufsz - strlen(buf),
4835                 /* TRANS: bullet point; note trailing space */
4836                 Q_("?bullet:* "));
4837 
4838   {
4839     int j;
4840 
4841     for (j = 0; j < MAX_NUM_TECH_LIST; j++) {
4842       if (game.rgame.global_init_techs[j] == A_LAST) {
4843         break;
4844       } else if (game.rgame.global_init_techs[j] == i) {
4845         CATLSTR(buf, bufsz,
4846                 _("* All players start the game with knowledge of this "
4847                   "technology.\n"));
4848         break;
4849       }
4850     }
4851   }
4852 
4853   /* Assume no-one will set the same tech in both global and nation
4854    * init_tech... */
4855   nations_iterate(pnation) {
4856     int j;
4857 
4858     /* Avoid mentioning nations not in current set. */
4859     if (!show_help_for_nation(pnation)) {
4860       continue;
4861     }
4862     for (j = 0; j < MAX_NUM_TECH_LIST; j++) {
4863       if (pnation->init_techs[j] == A_LAST) {
4864         break;
4865       } else if (pnation->init_techs[j] == i) {
4866         cat_snprintf(buf, bufsz,
4867                      /* TRANS: %s is a nation plural */
4868                      _("* The %s start the game with knowledge of this "
4869                        "technology.\n"), nation_plural_translation(pnation));
4870         break;
4871       }
4872     }
4873   } nations_iterate_end;
4874 
4875   /* Explain the effects of root_reqs. */
4876   {
4877     bv_techs roots, rootsofroots;
4878 
4879     BV_CLR_ALL(roots);
4880     BV_CLR_ALL(rootsofroots);
4881     advance_root_req_iterate(vap, proot) {
4882       if (proot == vap) {
4883         /* Don't say anything at all if this tech is a self-root-req one;
4884          * assume that the ruleset help will explain how to get it. */
4885         BV_CLR_ALL(roots);
4886         break;
4887       }
4888       BV_SET(roots, advance_number(proot));
4889       if (advance_requires(proot, AR_ROOT) != proot) {
4890         /* Now find out what roots each of this tech's root_req has, so that
4891          * we can suppress them. If tech A has roots B/C, and B has root C,
4892          * it's not worth saying that A needs C, and can lead to overwhelming
4893          * lists. */
4894         /* (Special case: don't do this if the root is a self-root-req tech,
4895          * since it would appear in its own root iteration; in the scenario
4896          * where S is a self-root tech that is root for T, this would prevent
4897          * S appearing in T's help.) */
4898         /* FIXME this is quite inefficient */
4899         advance_root_req_iterate(proot, prootroot) {
4900           BV_SET(rootsofroots, advance_number(prootroot));
4901         } advance_root_req_iterate_end;
4902       }
4903     } advance_root_req_iterate_end;
4904 
4905     /* Filter out all but the direct root reqs. */
4906     BV_CLR_ALL_FROM(roots, rootsofroots);
4907 
4908     if (BV_ISSET_ANY(roots)) {
4909       const char *root_techs[A_LAST];
4910       size_t n_roots = 0;
4911       struct astring root_list = ASTRING_INIT;
4912 
4913       advance_index_iterate(A_FIRST, root) {
4914         if (BV_ISSET(roots, root)) {
4915           root_techs[n_roots++]
4916             = advance_name_translation(advance_by_number(root));
4917         }
4918       } advance_index_iterate_end;
4919       fc_assert(n_roots > 0);
4920       cat_snprintf(buf, bufsz,
4921                    /* TRANS: 'and'-separated list of techs */
4922                    _("* Only those who know %s can acquire this "
4923                      "technology (by any means).\n"),
4924                    astr_build_and_list(&root_list, root_techs, n_roots));
4925       astr_free(&root_list);
4926     }
4927   }
4928 
4929   if (advance_has_flag(i, TF_BONUS_TECH)) {
4930     cat_snprintf(buf, bufsz,
4931 		 _("* The first player to learn %s gets"
4932 		   " an immediate advance.\n"),
4933                  advance_name_translation(vap));
4934   }
4935 
4936   for (flagid = TECH_USER_1 ; flagid <= TECH_USER_LAST; flagid++) {
4937     if (advance_has_flag(i, flagid)) {
4938       const char *helptxt = tech_flag_helptxt(flagid);
4939 
4940       if (helptxt != NULL) {
4941         /* TRANS: bullet point; note trailing space */
4942         CATLSTR(buf, bufsz, Q_("?bullet:* "));
4943         CATLSTR(buf, bufsz, _(helptxt));
4944         CATLSTR(buf, bufsz, "\n");
4945       }
4946     }
4947   }
4948 
4949   /* FIXME: bases -- but there is no good way to find out which bases a tech
4950    * can enable currently, so we have to remain silent. */
4951 
4952   if (game.info.tech_upkeep_style != TECH_UPKEEP_NONE) {
4953     CATLSTR(buf, bufsz,
4954             _("* To preserve this technology for our nation some bulbs "
4955               "are needed each turn.\n"));
4956   }
4957 
4958   if (NULL != vap->helptext) {
4959     if (strlen(buf) > 0) {
4960       CATLSTR(buf, bufsz, "\n");
4961     }
4962     strvec_iterate(vap->helptext, text) {
4963       cat_snprintf(buf, bufsz, "%s\n\n", _(text));
4964     } strvec_iterate_end;
4965   }
4966 
4967   astr_free(&astr);
4968 }
4969 
4970 /****************************************************************
4971   Append text for terrain.
4972 *****************************************************************/
helptext_terrain(char * buf,size_t bufsz,struct player * pplayer,const char * user_text,struct terrain * pterrain)4973 void helptext_terrain(char *buf, size_t bufsz, struct player *pplayer,
4974 		      const char *user_text, struct terrain *pterrain)
4975 {
4976   struct universal source = {
4977     .kind = VUT_TERRAIN,
4978     .value = {.terrain = pterrain}
4979   };
4980   int flagid;
4981 
4982   fc_assert_ret(NULL != buf && 0 < bufsz);
4983   buf[0] = '\0';
4984 
4985   if (!pterrain) {
4986     log_error("Unknown terrain!");
4987     return;
4988   }
4989 
4990   insert_allows(&source, buf + strlen(buf), bufsz - strlen(buf),
4991                 /* TRANS: bullet point; note trailing space */
4992                 Q_("?bullet:* "));
4993   if (terrain_has_flag(pterrain, TER_NO_CITIES)) {
4994     CATLSTR(buf, bufsz,
4995 	    _("* You cannot build cities on this terrain."));
4996     CATLSTR(buf, bufsz, "\n");
4997   }
4998   if (pterrain->road_time == 0) {
4999     /* Can't build roads; only mention if ruleset has buildable roads */
5000     extra_type_by_cause_iterate(EC_ROAD, pextra) {
5001       if (pextra->buildable) {
5002         CATLSTR(buf, bufsz,
5003                 _("* Paths cannot be built on this terrain."));
5004         CATLSTR(buf, bufsz, "\n");
5005         break;
5006       }
5007     } extra_type_by_cause_iterate_end;
5008   }
5009   if (pterrain->base_time == 0) {
5010     /* Can't build bases; only mention if ruleset has buildable bases */
5011     extra_type_by_cause_iterate(EC_BASE, pextra) {
5012       if (pextra->buildable) {
5013         CATLSTR(buf, bufsz,
5014                 _("* Bases cannot be built on this terrain."));
5015         CATLSTR(buf, bufsz, "\n");
5016         break;
5017       }
5018     } extra_type_by_cause_iterate_end;
5019   }
5020   if (terrain_has_flag(pterrain, TER_UNSAFE_COAST)
5021       && terrain_type_terrain_class(pterrain) != TC_OCEAN) {
5022     CATLSTR(buf, bufsz,
5023 	    _("* The coastline of this terrain is unsafe."));
5024     CATLSTR(buf, bufsz, "\n");
5025   }
5026   {
5027     const char *classes[uclass_count()];
5028     int i = 0;
5029 
5030     unit_class_iterate(uclass) {
5031       if (is_native_to_class(uclass, pterrain, NULL)) {
5032         classes[i++] = uclass_name_translation(uclass);
5033       }
5034     } unit_class_iterate_end;
5035 
5036     if (0 < i) {
5037       struct astring list = ASTRING_INIT;
5038 
5039       /* TRANS: %s is a list of unit classes separated by "and". */
5040       cat_snprintf(buf, bufsz, _("* Can be traveled by %s units.\n"),
5041                    astr_build_and_list(&list, classes, i));
5042       astr_free(&list);
5043     }
5044   }
5045   if (terrain_has_flag(pterrain, TER_NO_ZOC)) {
5046     CATLSTR(buf, bufsz,
5047             _("* Units on this terrain neither impose zones of control "
5048               "nor are restricted by them.\n"));
5049   } else {
5050     CATLSTR(buf, bufsz,
5051             _("* Units on this terrain may impose a zone of control, or "
5052               "be restricted by one.\n"));
5053   }
5054   if (terrain_has_flag(pterrain, TER_NO_FORTIFY)) {
5055     CATLSTR(buf, bufsz,
5056             _("* No units can fortify on this terrain.\n"));
5057   } else {
5058     CATLSTR(buf, bufsz,
5059             _("* Units able to fortify may do so on this terrain.\n"));
5060   }
5061   for (flagid = TER_USER_1 ; flagid <= TER_USER_LAST; flagid++) {
5062     if (terrain_has_flag(pterrain, flagid)) {
5063       const char *helptxt = terrain_flag_helptxt(flagid);
5064 
5065       if (helptxt != NULL) {
5066         /* TRANS: bullet point; note trailing space */
5067         CATLSTR(buf, bufsz, Q_("?bullet:* "));
5068         CATLSTR(buf, bufsz, _(helptxt));
5069         CATLSTR(buf, bufsz, "\n");
5070       }
5071     }
5072   }
5073 
5074   if (NULL != pterrain->helptext) {
5075     if (buf[0] != '\0') {
5076       CATLSTR(buf, bufsz, "\n");
5077     }
5078     strvec_iterate(pterrain->helptext, text) {
5079       cat_snprintf(buf, bufsz, "%s\n\n", _(text));
5080     } strvec_iterate_end;
5081   }
5082   if (user_text && user_text[0] != '\0') {
5083     CATLSTR(buf, bufsz, "\n\n");
5084     CATLSTR(buf, bufsz, user_text);
5085   }
5086 }
5087 
5088 /****************************************************************************
5089   Return a textual representation of the F/P/T bonus a road provides to a
5090   terrain if supplied, or the terrain-independent bonus if pterrain==NULL.
5091   e.g. "0/0/+1", "0/+50%/0", or for a complex road "+2/+1+50%/0".
5092   Returns a pointer to a static string, so caller should not free
5093   (or NULL if there is no effect at all).
5094 ****************************************************************************/
helptext_road_bonus_str(const struct terrain * pterrain,const struct road_type * proad)5095 const char *helptext_road_bonus_str(const struct terrain *pterrain,
5096                                     const struct road_type *proad)
5097 {
5098   static char str[64];
5099   str[0] = '\0';
5100   bool has_effect = FALSE;
5101   output_type_iterate(o) {
5102     switch (o) {
5103     case O_FOOD:
5104     case O_SHIELD:
5105     case O_TRADE:
5106       {
5107         int bonus = proad->tile_bonus[o];
5108         int incr = proad->tile_incr_const[o];
5109         if (pterrain) {
5110           incr +=
5111             proad->tile_incr[o] * pterrain->road_output_incr_pct[o] / 100;
5112         }
5113         if (str[0] != '\0') {
5114           CATLSTR(str, sizeof(str), "/");
5115         }
5116         if (incr == 0 && bonus == 0) {
5117           cat_snprintf(str, sizeof(str), "%d", incr);
5118         } else {
5119           has_effect = TRUE;
5120           if (incr != 0) {
5121             cat_snprintf(str, sizeof(str), "%+d", incr);
5122           }
5123           if (bonus != 0) {
5124             cat_snprintf(str, sizeof(str), "%+d%%", bonus);
5125           }
5126         }
5127       }
5128       break;
5129     default:
5130       /* FIXME: there's nothing actually stopping roads having gold, etc
5131        * bonuses */
5132       fc_assert(proad->tile_incr_const[o] == 0
5133                 && proad->tile_incr[o] == 0
5134                 && proad->tile_bonus[o] == 0);
5135       break;
5136     }
5137   } output_type_iterate_end;
5138 
5139   return has_effect ? str : NULL;
5140 }
5141 
5142 /**********************************************************************//**
5143   Calculate any fixed food/prod/trade bonus that 'pextra' will always add
5144   to terrain type, independent of any other modifications. Does not
5145   consider percentage bonuses.
5146   Result written into 'bonus' which should hold 3 ints (F/P/T).
5147 **************************************************************************/
extra_bonus_for_terrain(struct extra_type * pextra,struct terrain * pterrain,int * bonus)5148 static void extra_bonus_for_terrain(struct extra_type *pextra,
5149                                     struct terrain *pterrain,
5150                                     int *bonus)
5151 {
5152   struct universal req_pattern[] = {
5153     { .kind = VUT_EXTRA,   .value.extra = pextra },
5154     { .kind = VUT_TERRAIN, .value.terrain = pterrain },
5155     { .kind = VUT_OTYPE    /* value filled in later */ }
5156   };
5157 
5158   fc_assert_ret(bonus != NULL);
5159 
5160   /* Irrigation-like food bonuses */
5161   bonus[0] = (pterrain->irrigation_food_incr
5162               * effect_value_from_universals(EFT_IRRIGATION_PCT, req_pattern,
5163                                              2 /* just extra+terrain */)) / 100;
5164 
5165   /* Mining-like shield bonuses */
5166   bonus[1] = (pterrain->mining_shield_incr
5167               * effect_value_from_universals(EFT_MINING_PCT, req_pattern,
5168                                              2 /* just extra+terrain */)) / 100;
5169 
5170   bonus[2] = 0; /* no trade bonuses so far */
5171 
5172   /* Now add fixed bonuses from roads (but not percentage bonus) */
5173   if (extra_road_get(pextra)) {
5174     const struct road_type *proad = extra_road_get(pextra);
5175 
5176     output_type_iterate(o) {
5177       switch (o) {
5178       case O_FOOD:
5179       case O_SHIELD:
5180       case O_TRADE:
5181         bonus[o] += proad->tile_incr_const[o]
5182           + proad->tile_incr[o] * pterrain->road_output_incr_pct[o] / 100;
5183         break;
5184       default:
5185         /* not dealing with other output types here */
5186         break;
5187       }
5188     } output_type_iterate_end;
5189   }
5190 
5191   /* Fixed bonuses for extra, possibly unrelated to terrain type */
5192 
5193   output_type_iterate(o) {
5194     /* Fill in rest of requirement template */
5195     req_pattern[2].value.outputtype = o;
5196     switch (o) {
5197     case O_FOOD:
5198     case O_SHIELD:
5199     case O_TRADE:
5200       bonus[o] += effect_value_from_universals(EFT_OUTPUT_ADD_TILE,
5201                                                req_pattern,
5202                                                ARRAY_SIZE(req_pattern));
5203       /* Any of the above bonuses is sufficient to trigger
5204        * Output_Inc_Tile, if underlying terrain does not */
5205       if (bonus[o] > 0 || pterrain->output[o] > 0) {
5206         bonus[o] += effect_value_from_universals(EFT_OUTPUT_INC_TILE,
5207                                                  req_pattern,
5208                                                  ARRAY_SIZE(req_pattern));
5209       }
5210       break;
5211     default:
5212       break;
5213     }
5214   } output_type_iterate_end;
5215 }
5216 
5217 /**********************************************************************//**
5218   Return a brief description specific to the extra and terrain, when
5219   extra is built by cause 'act'.
5220   Returns number of turns to build, and selected bonuses.
5221   Returns a pointer to a static string, so caller should not free.
5222 **************************************************************************/
helptext_extra_for_terrain_str(struct extra_type * pextra,struct terrain * pterrain,enum unit_activity act)5223 const char *helptext_extra_for_terrain_str(struct extra_type *pextra,
5224                                            struct terrain *pterrain,
5225                                            enum unit_activity act)
5226 {
5227   static char buffer[256];
5228   int btime;
5229   int bonus[3];
5230 
5231   btime = terrain_extra_build_time(pterrain, act, pextra);
5232   fc_snprintf(buffer, sizeof(buffer), PL_("%d turn", "%d turns", btime),
5233               btime);
5234   extra_bonus_for_terrain(pextra, pterrain, bonus);
5235   if (bonus[0] > 0) {
5236     cat_snprintf(buffer, sizeof(buffer),
5237                  PL_(", +%d food", ", +%d food", bonus[0]), bonus[0]);
5238   }
5239   if (bonus[1] > 0) {
5240     cat_snprintf(buffer, sizeof(buffer),
5241                  PL_(", +%d shield", ", +%d shields", bonus[1]), bonus[1]);
5242   }
5243   if (bonus[2] > 0) {
5244     cat_snprintf(buffer, sizeof(buffer),
5245                  PL_(", +%d trade", ", +%d trade", bonus[2]), bonus[2]);
5246   }
5247 
5248   return buffer;
5249 }
5250 
5251 /****************************************************************************
5252   Append misc dynamic text for extras.
5253   Assumes build time and conflicts are handled in the GUI front-end.
5254 
5255   pplayer may be NULL.
5256 ****************************************************************************/
helptext_extra(char * buf,size_t bufsz,struct player * pplayer,const char * user_text,struct extra_type * pextra)5257 void helptext_extra(char *buf, size_t bufsz, struct player *pplayer,
5258                     const char *user_text, struct extra_type *pextra)
5259 {
5260   size_t group_start;
5261   struct base_type *pbase;
5262   struct road_type *proad;
5263   struct universal source = {
5264     .kind = VUT_EXTRA,
5265     .value = {.extra = pextra}
5266   };
5267 
5268   fc_assert_ret(NULL != buf && 0 < bufsz);
5269   buf[0] = '\0';
5270 
5271   if (!pextra) {
5272     log_error("Unknown extra!");
5273     return;
5274   }
5275 
5276   if (is_extra_caused_by(pextra, EC_BASE)) {
5277     pbase = pextra->data.base;
5278   } else {
5279     pbase = NULL;
5280   }
5281 
5282   if (is_extra_caused_by(pextra, EC_ROAD)) {
5283     proad = pextra->data.road;
5284   } else {
5285     proad = NULL;
5286   }
5287 
5288   if (pextra->helptext != NULL) {
5289     strvec_iterate(pextra->helptext, text) {
5290       cat_snprintf(buf, bufsz, "%s\n\n", _(text));
5291     } strvec_iterate_end;
5292   }
5293 
5294   /* Describe how extra is created and destroyed */
5295 
5296   group_start = strlen(buf);
5297 
5298   if (pextra->buildable) {
5299     if (is_extra_caused_by(pextra, EC_IRRIGATION)) {
5300       CATLSTR(buf, bufsz,
5301               _("Build by issuing an \"irrigate\" order.\n"));
5302     }
5303     if (is_extra_caused_by(pextra, EC_MINE)) {
5304       CATLSTR(buf, bufsz,
5305               _("Build by issuing a \"mine\" order.\n"));
5306     }
5307     if (is_extra_caused_by(pextra, EC_ROAD)) {
5308       CATLSTR(buf, bufsz,
5309               _("Build by issuing a \"road\" order.\n"));
5310     }
5311     if (is_extra_caused_by(pextra, EC_BASE)) {
5312       fc_assert(pbase);
5313       if (pbase->gui_type == BASE_GUI_OTHER) {
5314         cat_snprintf(buf, bufsz,
5315                 _("Build by issuing a \"build base\" order.\n"));
5316       } else {
5317         const char *order = "";
5318 
5319         switch (pbase->gui_type) {
5320         case BASE_GUI_FORTRESS:
5321           order = Q_(terrain_control.gui_type_base0);
5322           break;
5323         case BASE_GUI_AIRBASE:
5324           order = Q_(terrain_control.gui_type_base1);
5325           break;
5326         default:
5327           fc_assert(FALSE);
5328           break;
5329         }
5330         cat_snprintf(buf, bufsz,
5331                      /* TRANS: %s is a gui_type base string from a ruleset */
5332                      _("Build by issuing a \"%s\" order.\n"), order);
5333       }
5334     }
5335   }
5336 
5337   if (is_extra_caused_by(pextra, EC_POLLUTION)) {
5338     CATLSTR(buf, bufsz,
5339             _("May randomly appear around polluting city.\n"));
5340   }
5341 
5342   if (is_extra_caused_by(pextra, EC_FALLOUT)) {
5343     CATLSTR(buf, bufsz,
5344             _("May randomly appear around nuclear blast.\n"));
5345   }
5346 
5347   if (is_extra_caused_by(pextra, EC_HUT)
5348       || (proad != NULL && road_has_flag(proad, RF_RIVER))) {
5349     CATLSTR(buf, bufsz,
5350             _("Placed by map generator.\n"));
5351   }
5352 
5353   if (requirement_vector_size(&pextra->reqs) > 0) {
5354     char reqsbuf[8192] = "";
5355     bool buildable = pextra->buildable
5356       && is_extra_caused_by_worker_action(pextra);
5357 
5358     requirement_vector_iterate(&pextra->reqs, preq) {
5359       (void) insert_requirement(reqsbuf, sizeof(reqsbuf), pplayer, preq,
5360                                 /* TRANS: bullet point; note trailing space */
5361                                 buildable ? Q_("?bullet:* ") : "");
5362     } requirement_vector_iterate_end;
5363     if (reqsbuf[0] != '\0') {
5364       if (buildable) {
5365         CATLSTR(buf, bufsz, _("Requirements to build:\n"));
5366       }
5367       CATLSTR(buf, bufsz, reqsbuf);
5368     }
5369   }
5370 
5371   if (buf[group_start] != '\0') {
5372     CATLSTR(buf, bufsz, "\n"); /* group separator */
5373   }
5374 
5375   group_start = strlen(buf);
5376 
5377   if (is_extra_removed_by(pextra, ERM_PILLAGE)) {
5378     int pillage_time = -1;
5379 
5380     if (pextra->removal_time != 0) {
5381       pillage_time = pextra->removal_time;
5382     } else {
5383       terrain_type_iterate(pterrain) {
5384         int terr_pillage_time = pterrain->pillage_time
5385                                 * pextra->removal_time_factor;
5386 
5387         if (terr_pillage_time != 0) {
5388           if (pillage_time < 0) {
5389             pillage_time = terr_pillage_time;
5390           } else if (pillage_time != terr_pillage_time) {
5391             /* Give up */
5392             pillage_time = -1;
5393             break;
5394           }
5395         }
5396       } terrain_type_iterate_end;
5397     }
5398     if (pillage_time < 0) {
5399       CATLSTR(buf, bufsz,
5400               _("Can be pillaged by units (time is terrain-dependent).\n"));
5401     } else if (pillage_time > 0) {
5402       cat_snprintf(buf, bufsz,
5403                    PL_("Can be pillaged by units (takes %d turn).\n",
5404                        "Can be pillaged by units (takes %d turns).\n",
5405                        pillage_time), pillage_time);
5406     }
5407   }
5408   if (is_extra_removed_by(pextra, ERM_CLEANPOLLUTION)
5409       || is_extra_removed_by(pextra, ERM_CLEANFALLOUT)) {
5410     int clean_time = -1;
5411 
5412     if (pextra->removal_time != 0) {
5413       clean_time = pextra->removal_time;
5414     } else {
5415       terrain_type_iterate(pterrain) {
5416         int terr_clean_time = -1;
5417 
5418         if (is_extra_removed_by(pextra, ERM_CLEANPOLLUTION)
5419             && pterrain->clean_pollution_time != 0) {
5420           terr_clean_time = pterrain->clean_pollution_time
5421                             * pextra->removal_time_factor;
5422         }
5423         if (is_extra_removed_by(pextra, ERM_CLEANFALLOUT)
5424             && pterrain->clean_fallout_time != 0) {
5425           int terr_clean_fall_time = pterrain->clean_fallout_time
5426                                      * pextra->removal_time_factor;
5427           if (terr_clean_time > 0
5428               && terr_clean_time != terr_clean_fall_time) {
5429             /* Pollution/fallout cleaning activities taking different time
5430              * on same terrain. Give up. */
5431             clean_time = -1;
5432             break;
5433           }
5434           terr_clean_time = terr_clean_fall_time;
5435         }
5436         if (clean_time < 0) {
5437           clean_time = terr_clean_time;
5438         } else if (clean_time != terr_clean_time) {
5439           /* Give up */
5440           clean_time = -1;
5441           break;
5442         }
5443       } terrain_type_iterate_end;
5444     }
5445     if (clean_time < 0) {
5446       CATLSTR(buf, bufsz,
5447               _("Can be cleaned by units (time is terrain-dependent).\n"));
5448     } else if (clean_time > 0) {
5449       cat_snprintf(buf, bufsz,
5450                    PL_("Can be cleaned by units (takes %d turn).\n",
5451                        "Can be cleaned by units (takes %d turns).\n",
5452                        clean_time), clean_time);
5453     }
5454   }
5455 
5456   if (buf[group_start] != '\0') {
5457     CATLSTR(buf, bufsz, "\n"); /* group separator */
5458   }
5459 
5460   /* Describe what other elements are enabled by extra */
5461 
5462   group_start = strlen(buf);
5463 
5464   insert_allows(&source, buf + strlen(buf), bufsz - strlen(buf), "");
5465 
5466   if (buf[group_start] != '\0') {
5467     CATLSTR(buf, bufsz, "\n"); /* group separator */
5468   }
5469 
5470   /* Describe other properties of extras */
5471 
5472   {
5473     const char *classes[uclass_count()];
5474     int i = 0;
5475 
5476     unit_class_iterate(uclass) {
5477       if (is_native_extra_to_uclass(pextra, uclass)) {
5478         classes[i++] = uclass_name_translation(uclass);
5479       }
5480     } unit_class_iterate_end;
5481 
5482     if (0 < i) {
5483       struct astring list = ASTRING_INIT;
5484 
5485       if (proad != NULL) {
5486         /* TRANS: %s is a list of unit classes separated by "and". */
5487         cat_snprintf(buf, bufsz, _("* Can be traveled by %s units.\n"),
5488                      astr_build_and_list(&list, classes, i));
5489       } else {
5490         /* TRANS: %s is a list of unit classes separated by "and". */
5491         cat_snprintf(buf, bufsz, _("* Native to %s units.\n"),
5492                      astr_build_and_list(&list, classes, i));
5493       }
5494       astr_free(&list);
5495 
5496       if (extra_has_flag(pextra, EF_NATIVE_TILE)) {
5497         CATLSTR(buf, bufsz,
5498                 /* TRANS: indented; preserve leading spaces */
5499                 _("  * Such units can move onto this tile even if it would "
5500                   "not normally be suitable terrain.\n"));
5501       }
5502       if (pbase != NULL) {
5503         if (base_has_flag(pbase, BF_NOT_AGGRESSIVE)) {
5504           /* "3 tiles" is hardcoded in is_friendly_city_near() */
5505           CATLSTR(buf, bufsz,
5506                   /* TRANS: indented; preserve leading spaces */
5507                   _("  * Such units situated here are not considered aggressive "
5508                     "if this tile is within 3 tiles of a friendly city.\n"));
5509         }
5510         if (territory_claiming_base(pbase)) {
5511           CATLSTR(buf, bufsz,
5512                   /* TRANS: indented; preserve leading spaces */
5513                   _("  * Can be captured by such units if at war with the "
5514                     "nation that currently owns it.\n"));
5515         }
5516       }
5517       if (pextra->defense_bonus) {
5518         cat_snprintf(buf, bufsz,
5519                      /* TRANS: indented; preserve leading spaces */
5520                      _("  * Such units get a %d%% defense bonus on this "
5521                        "tile.\n"),
5522                      pextra->defense_bonus);
5523       }
5524       if (pbase != NULL) {
5525         if (base_has_flag(pbase, BF_DIPLOMAT_DEFENSE)) {
5526           CATLSTR(buf, bufsz,
5527                   /* TRANS: indented; preserve leading spaces */
5528                   /* xgettext:no-c-format */
5529                   _("  * Diplomatic units get a 25% defense bonus in "
5530                     "diplomatic fights.\n"));
5531         }
5532       }
5533     }
5534   }
5535 
5536   if (proad != NULL && road_provides_move_bonus(proad)) {
5537     if (proad->move_cost == 0) {
5538       CATLSTR(buf, bufsz, _("* Allows infinite movement.\n"));
5539     } else {
5540       cat_snprintf(buf, bufsz,
5541                    /* TRANS: "MP" = movement points. Second %s may have a
5542                     * fractional part. */
5543                    _("* Movement cost along %s is %s MP.\n"),
5544                    extra_name_translation(pextra),
5545                    move_points_text(proad->move_cost, TRUE));
5546     }
5547   }
5548 
5549   if (pbase != NULL) {
5550     if (game.info.killstack
5551         && base_has_flag(pbase, BF_NO_STACK_DEATH)) {
5552       CATLSTR(buf, bufsz,
5553               _("* Defeat of one unit does not cause death of all other units "
5554                 "on this tile.\n"));
5555     }
5556     if (base_has_flag(pbase, BF_PARADROP_FROM)) {
5557       CATLSTR(buf, bufsz,
5558               _("* Units can paradrop from this tile.\n"));
5559     }
5560     if (territory_claiming_base(pbase)) {
5561       CATLSTR(buf, bufsz,
5562               _("* Extends national borders of the building nation.\n"));
5563     }
5564     if (pbase->vision_main_sq >= 0) {
5565       CATLSTR(buf, bufsz,
5566               _("* Grants permanent vision of an area around the tile to "
5567                 "its owner.\n"));
5568     }
5569     if (pbase->vision_invis_sq >= 0) {
5570       CATLSTR(buf, bufsz,
5571               _("* Allows the owner to see normally invisible units in an "
5572                 "area around the tile.\n"));
5573     }
5574   }
5575 
5576   /* Table of terrain-specific attributes, if needed */
5577   if (proad != NULL || pbase != NULL) {
5578     bool road, do_time, do_bonus;
5579 
5580     road = (proad != NULL);
5581     /* Terrain-dependent build time? */
5582     do_time = pextra->buildable && pextra->build_time == 0;
5583     if (road) {
5584       /* Terrain-dependent output bonus? */
5585       do_bonus = FALSE;
5586       output_type_iterate(o) {
5587         if (proad->tile_incr[o] > 0) {
5588           do_bonus = TRUE;
5589           fc_assert(o == O_FOOD || o == O_SHIELD || o == O_TRADE);
5590         }
5591       } output_type_iterate_end;
5592     } else {
5593       /* Bases don't have output bonuses */
5594       do_bonus = FALSE;
5595     }
5596 
5597     if (do_time || do_bonus) {
5598       if (do_time && do_bonus) {
5599         CATLSTR(buf, bufsz,
5600                 _("\nTime to build and output bonus depends on terrain:\n\n"));
5601         CATLSTR(buf, bufsz,
5602                 /* TRANS: Header for fixed-width road properties table.
5603                  * TRANS: Translators cannot change column widths :( */
5604                 _("Terrain       Time     Bonus F/P/T\n"
5605                   "----------------------------------\n"));
5606       } else if (do_time) {
5607         CATLSTR(buf, bufsz,
5608                 _("\nTime to build depends on terrain:\n\n"));
5609         CATLSTR(buf, bufsz,
5610                 /* TRANS: Header for fixed-width extra properties table.
5611                  * TRANS: Translators cannot change column widths :( */
5612                 _("Terrain       Time\n"
5613                   "------------------\n"));
5614       } else {
5615         fc_assert(do_bonus);
5616         CATLSTR(buf, bufsz,
5617                 /* TRANS: Header for fixed-width road properties table.
5618                  * TRANS: Translators cannot change column widths :( */
5619                 _("\nYields an output bonus with some terrains:\n\n"));
5620         CATLSTR(buf, bufsz,
5621                 _("Terrain       Bonus F/P/T\n"
5622                   "-------------------------\n"));;
5623       }
5624       terrain_type_iterate(t) {
5625         int turns = road ? terrain_extra_build_time(t, ACTIVITY_GEN_ROAD, pextra)
5626                            : terrain_extra_build_time(t, ACTIVITY_BASE, pextra);
5627         const char *bonus_text
5628           = road ? helptext_road_bonus_str(t, proad) : NULL;
5629         if (turns > 0 || bonus_text) {
5630           const char *terrain = terrain_name_translation(t);
5631 
5632           cat_snprintf(buf, bufsz,
5633                        "%s%*s ", terrain,
5634                        MAX(0, 12 - (int)get_internal_string_length(terrain)),
5635                        "");
5636           if (do_time) {
5637             if (turns > 0) {
5638               cat_snprintf(buf, bufsz, "%3d      ", turns);
5639             } else {
5640               CATLSTR(buf, bufsz, "  -      ");
5641             }
5642           }
5643           if (do_bonus) {
5644             fc_assert(proad != NULL);
5645             cat_snprintf(buf, bufsz, " %s", bonus_text ? bonus_text : "-");
5646           }
5647           CATLSTR(buf, bufsz, "\n");
5648         }
5649       } terrain_type_iterate_end;
5650     } /* else rely on client-specific display */
5651   }
5652 
5653   if (user_text && user_text[0] != '\0') {
5654     CATLSTR(buf, bufsz, "\n\n");
5655     CATLSTR(buf, bufsz, user_text);
5656   }
5657 }
5658 
5659 /****************************************************************************
5660   Append misc dynamic text for specialists.
5661   Assumes effects are described in the help text.
5662 
5663   pplayer may be NULL.
5664 ****************************************************************************/
helptext_specialist(char * buf,size_t bufsz,struct player * pplayer,const char * user_text,struct specialist * pspec)5665 void helptext_specialist(char *buf, size_t bufsz, struct player *pplayer,
5666                          const char *user_text, struct specialist *pspec)
5667 {
5668   bool reqs = FALSE;
5669 
5670   fc_assert_ret(NULL != buf && 0 < bufsz);
5671   buf[0] = '\0';
5672 
5673   if (NULL != pspec->helptext) {
5674     strvec_iterate(pspec->helptext, text) {
5675       cat_snprintf(buf, bufsz, "%s\n\n", _(text));
5676     } strvec_iterate_end;
5677   }
5678 
5679   /* Requirements for this specialist. */
5680   requirement_vector_iterate(&pspec->reqs, preq) {
5681     if (insert_requirement(buf, bufsz, pplayer, preq, "")) {
5682       reqs = TRUE;
5683     }
5684   } requirement_vector_iterate_end;
5685   if (reqs) {
5686     fc_strlcat(buf, "\n", bufsz);
5687   }
5688 
5689   CATLSTR(buf, bufsz, user_text);
5690 }
5691 
5692 /****************************************************************
5693   Append text for government.
5694 
5695   pplayer may be NULL.
5696 
5697   TODO: Generalize the effects code for use elsewhere. Add
5698   other requirements.
5699 *****************************************************************/
helptext_government(char * buf,size_t bufsz,struct player * pplayer,const char * user_text,struct government * gov)5700 void helptext_government(char *buf, size_t bufsz, struct player *pplayer,
5701                          const char *user_text, struct government *gov)
5702 {
5703   bool reqs = FALSE;
5704   struct universal source = {
5705     .kind = VUT_GOVERNMENT,
5706     .value = {.govern = gov}
5707   };
5708 
5709   fc_assert_ret(NULL != buf && 0 < bufsz);
5710   buf[0] = '\0';
5711 
5712   if (NULL != gov->helptext) {
5713     strvec_iterate(gov->helptext, text) {
5714       cat_snprintf(buf, bufsz, "%s\n\n", _(text));
5715     } strvec_iterate_end;
5716   }
5717 
5718   /* Add requirement text for government itself */
5719   requirement_vector_iterate(&gov->reqs, preq) {
5720     if (insert_requirement(buf, bufsz, pplayer, preq, "")) {
5721       reqs = TRUE;
5722     }
5723   } requirement_vector_iterate_end;
5724   if (reqs) {
5725     fc_strlcat(buf, "\n", bufsz);
5726   }
5727 
5728   /* Effects */
5729   CATLSTR(buf, bufsz, _("Features:\n"));
5730   insert_allows(&source, buf + strlen(buf), bufsz - strlen(buf),
5731                 /* TRANS: bullet point; note trailing space */
5732                 Q_("?bullet:* "));
5733   effect_list_iterate(get_req_source_effects(&source), peffect) {
5734     Output_type_id output_type = O_LAST;
5735     struct unit_class *unitclass = NULL;
5736     struct unit_type *unittype = NULL;
5737     enum unit_type_flag_id unitflag = unit_type_flag_id_invalid();
5738     struct strvec *outputs = strvec_new();
5739     struct astring outputs_or = ASTRING_INIT;
5740     struct astring outputs_and = ASTRING_INIT;
5741     bool too_complex = FALSE;
5742     bool world_value_valid = TRUE;
5743 
5744     /* Grab output type, if there is one */
5745     requirement_vector_iterate(&peffect->reqs, preq) {
5746       /* Treat an effect with any negated requirements as too complex for
5747        * us to explain here.
5748        * Also don't try to explain an effect with any requirements explicitly
5749        * marked as 'quiet' by ruleset author. */
5750       if (!preq->present || preq->quiet) {
5751         too_complex = TRUE;
5752         continue;
5753       }
5754       switch (preq->source.kind) {
5755        case VUT_OTYPE:
5756          /* We should never have multiple outputtype requirements
5757           * in one list in the first place (it simply makes no sense,
5758           * output cannot be of multiple types)
5759           * Ruleset loading code should check against that. */
5760          fc_assert(output_type == O_LAST);
5761          output_type = preq->source.value.outputtype;
5762          strvec_append(outputs, get_output_name(output_type));
5763          break;
5764        case VUT_UCLASS:
5765          fc_assert(unitclass == NULL);
5766          unitclass = preq->source.value.uclass;
5767          /* FIXME: can't easily get world bonus for unit class */
5768          world_value_valid = FALSE;
5769          break;
5770        case VUT_UTYPE:
5771          fc_assert(unittype == NULL);
5772          unittype = preq->source.value.utype;
5773          break;
5774        case VUT_UTFLAG:
5775          if (!unit_type_flag_id_is_valid(unitflag)) {
5776            unitflag = preq->source.value.unitflag;
5777            /* FIXME: can't easily get world bonus for unit type flag */
5778            world_value_valid = FALSE;
5779          } else {
5780            /* Already have a unit flag requirement. More than one is too
5781             * complex for us to explain, so say nothing. */
5782            /* FIXME: we could handle this */
5783            too_complex = TRUE;
5784          }
5785          break;
5786        case VUT_GOVERNMENT:
5787          /* This is government we are generating helptext for.
5788           * ...or if not, it's ruleset bug that should never make it
5789           * this far. Fix ruleset loading code. */
5790          fc_assert(preq->source.value.govern == gov);
5791          break;
5792        default:
5793          too_complex = TRUE;
5794          world_value_valid = FALSE;
5795          break;
5796       };
5797     } requirement_vector_iterate_end;
5798 
5799     if (!too_complex) {
5800       /* Only list effects that don't have extra requirements too complex
5801        * for us to handle.
5802        * Anything more complicated will have to be documented by hand by the
5803        * ruleset author. */
5804 
5805       /* Guard condition for simple player-wide effects descriptions.
5806        * (FIXME: in many cases, e.g. EFT_MAKE_CONTENT, additional requirements
5807        * like unittype will be ignored for gameplay, but will affect our
5808        * help here.) */
5809       const bool playerwide
5810         = world_value_valid && !unittype && (output_type == O_LAST);
5811       /* In some cases we give absolute values (world bonus + gov bonus).
5812        * We assume the fact that there's an effect with a gov requirement
5813        * is sufficient reason to list it in that gov's help.
5814        * Guard accesses to these with 'playerwide' or 'world_value_valid'. */
5815       int world_value = -999, net_value = -999;
5816       if (world_value_valid) {
5817         /* Get government-independent world value of effect if the extra
5818          * requirements were simple enough. */
5819         struct output_type *potype =
5820           output_type != O_LAST ? get_output_type(output_type) : NULL;
5821         world_value =
5822           get_target_bonus_effects(NULL, NULL, NULL, NULL, NULL, NULL,
5823                                    NULL, unittype, potype, NULL,
5824                                    peffect->type);
5825         net_value = peffect->value + world_value;
5826       }
5827 
5828       if (output_type == O_LAST) {
5829         /* There was no outputtype requirement. Effect is active for all
5830          * output types. Generate lists for that. */
5831         bool harvested_only = TRUE; /* Consider only output types from fields */
5832 
5833         if (peffect->type == EFT_UPKEEP_FACTOR
5834             || peffect->type == EFT_UNIT_UPKEEP_FREE_PER_CITY
5835             || peffect->type == EFT_OUTPUT_BONUS
5836             || peffect->type == EFT_OUTPUT_BONUS_2) {
5837           /* Effect can use or require any kind of output */
5838           harvested_only = FALSE;
5839         }
5840 
5841         output_type_iterate(ot) {
5842           struct output_type *pot = get_output_type(ot);
5843 
5844           if (!harvested_only || pot->harvested) {
5845             strvec_append(outputs, _(pot->name));
5846           }
5847         } output_type_iterate_end;
5848       }
5849 
5850       if (0 == strvec_size(outputs)) {
5851          /* TRANS: Empty output type list, should never happen. */
5852         astr_set(&outputs_or, "%s", Q_("?outputlist: Nothing "));
5853         astr_set(&outputs_and, "%s", Q_("?outputlist: Nothing "));
5854       } else {
5855         strvec_to_or_list(outputs, &outputs_or);
5856         strvec_to_and_list(outputs, &outputs_and);
5857       }
5858 
5859       switch (peffect->type) {
5860       case EFT_UNHAPPY_FACTOR:
5861         if (playerwide) {
5862           /* FIXME: EFT_MAKE_CONTENT_MIL_PER would cancel this out. We assume
5863            * no-one will set both, so we don't bother handling it. */
5864           cat_snprintf(buf, bufsz,
5865                        PL_("* Military units away from home and field units"
5866                            " will each cause %d citizen to become unhappy.\n",
5867                            "* Military units away from home and field units"
5868                            " will each cause %d citizens to become unhappy.\n",
5869                            net_value),
5870                        net_value);
5871         } /* else too complicated or silly ruleset */
5872         break;
5873       case EFT_ENEMY_CITIZEN_UNHAPPY_PCT:
5874         if (playerwide && net_value != world_value) {
5875           if (world_value > 0) {
5876             if (net_value > 0) {
5877               cat_snprintf(buf, bufsz,
5878                            _("* Unhappiness from foreign citizens due to "
5879                              "war with their home state is %d%% the usual "
5880                              "value.\n"),
5881                            (net_value * 100) / world_value);
5882             } else {
5883               CATLSTR(buf, bufsz,
5884                       _("* No unhappiness from foreign citizens even when "
5885                         "at war with their home state.\n"));
5886             }
5887           } else {
5888             cat_snprintf(buf, bufsz,
5889                          /* TRANS: not pluralised as gettext doesn't support
5890                           * fractional numbers, which this might be */
5891                          _("* Each foreign citizen causes %.2g unhappiness "
5892                            "in their city while you are at war with their "
5893                            "home state.\n"),
5894                          (double)net_value / 100);
5895           }
5896         }
5897         break;
5898       case EFT_MAKE_CONTENT_MIL:
5899         if (playerwide) {
5900           cat_snprintf(buf, bufsz,
5901                        PL_("* Each of your cities will avoid %d unhappiness"
5902                            " caused by units.\n",
5903                            "* Each of your cities will avoid %d unhappiness"
5904                            " caused by units.\n",
5905                            peffect->value),
5906                        peffect->value);
5907         }
5908         break;
5909       case EFT_MAKE_CONTENT:
5910         if (playerwide) {
5911           cat_snprintf(buf, bufsz,
5912                        PL_("* Each of your cities will avoid %d unhappiness,"
5913                            " not including that caused by aggression.\n",
5914                            "* Each of your cities will avoid %d unhappiness,"
5915                            " not including that caused by aggression.\n",
5916                            peffect->value),
5917                        peffect->value);
5918         }
5919         break;
5920       case EFT_FORCE_CONTENT:
5921         if (playerwide) {
5922           cat_snprintf(buf, bufsz,
5923                        PL_("* Each of your cities will avoid %d unhappiness,"
5924                            " including that caused by aggression.\n",
5925                            "* Each of your cities will avoid %d unhappiness,"
5926                            " including that caused by aggression.\n",
5927                            peffect->value),
5928                        peffect->value);
5929         }
5930         break;
5931       case EFT_UPKEEP_FACTOR:
5932         if (world_value_valid && !unittype) {
5933           if (net_value == 0) {
5934             if (output_type != O_LAST) {
5935               cat_snprintf(buf, bufsz,
5936                            /* TRANS: %s is the output type, like 'shield'
5937                             * or 'gold'. */
5938                            _("* You pay no %s upkeep for your units.\n"),
5939                            astr_str(&outputs_or));
5940             } else {
5941               CATLSTR(buf, bufsz,
5942                       _("* You pay no upkeep for your units.\n"));
5943             }
5944           } else if (net_value != world_value) {
5945             double ratio = (double)net_value / world_value;
5946             if (output_type != O_LAST) {
5947               cat_snprintf(buf, bufsz,
5948                            /* TRANS: %s is the output type, like 'shield'
5949                             * or 'gold'. */
5950                            _("* You pay %.2g times normal %s upkeep for your "
5951                              "units.\n"),
5952                            ratio, astr_str(&outputs_and));
5953             } else {
5954               cat_snprintf(buf, bufsz,
5955                            _("* You pay %.2g times normal upkeep for your "
5956                              "units.\n"),
5957                            ratio);
5958             }
5959           } /* else this effect somehow has no effect; keep quiet */
5960         } /* else there was some extra condition making it complicated */
5961         break;
5962       case EFT_UNIT_UPKEEP_FREE_PER_CITY:
5963         if (!unittype) {
5964           if (output_type != O_LAST) {
5965             cat_snprintf(buf, bufsz,
5966                          /* TRANS: %s is the output type, like 'shield' or
5967                           * 'gold'; pluralised in %d but there is currently
5968                           * no way to control the singular/plural name of the
5969                           * output type; sorry */
5970                          PL_("* Each of your cities will avoid paying %d %s"
5971                              " upkeep for your units.\n",
5972                              "* Each of your cities will avoid paying %d %s"
5973                              " upkeep for your units.\n", peffect->value),
5974                          peffect->value, astr_str(&outputs_and));
5975           } else {
5976             cat_snprintf(buf, bufsz,
5977                          /* TRANS: Amount is subtracted from upkeep cost
5978                           * for each upkeep type. */
5979                          PL_("* Each of your cities will avoid paying %d"
5980                              " upkeep for your units.\n",
5981                              "* Each of your cities will avoid paying %d"
5982                              " upkeep for your units.\n", peffect->value),
5983                          peffect->value);
5984           }
5985         } /* else too complicated */
5986         break;
5987       case EFT_CIVIL_WAR_CHANCE:
5988         if (playerwide) {
5989           cat_snprintf(buf, bufsz,
5990                        _("* If you lose your capital,"
5991                          " the base chance of civil war is %d%%.\n"),
5992                        net_value);
5993         }
5994         break;
5995       case EFT_EMPIRE_SIZE_BASE:
5996         if (playerwide) {
5997           cat_snprintf(buf, bufsz,
5998                        PL_("* You can have %d city before an "
5999                            "additional unhappy citizen appears in each city "
6000                            "due to civilization size.\n",
6001                            "* You can have up to %d cities before an "
6002                            "additional unhappy citizen appears in each city "
6003                            "due to civilization size.\n", net_value),
6004                        net_value);
6005         }
6006         break;
6007       case EFT_EMPIRE_SIZE_STEP:
6008         if (playerwide) {
6009           cat_snprintf(buf, bufsz,
6010                        PL_("* After the first unhappy citizen due to"
6011                            " civilization size, for each %d additional city"
6012                            " another unhappy citizen will appear.\n",
6013                            "* After the first unhappy citizen due to"
6014                            " civilization size, for each %d additional cities"
6015                            " another unhappy citizen will appear.\n",
6016                            net_value),
6017                        net_value);
6018         }
6019         break;
6020       case EFT_MAX_RATES:
6021         if (playerwide && game.info.changable_tax) {
6022           if (net_value < 100) {
6023             cat_snprintf(buf, bufsz,
6024                          _("* The maximum rate you can set for science,"
6025                             " gold, or luxuries is %d%%.\n"),
6026                          net_value);
6027           } else {
6028             CATLSTR(buf, bufsz,
6029                     _("* Has unlimited science/gold/luxuries rates.\n"));
6030           }
6031         }
6032         break;
6033       case EFT_MARTIAL_LAW_EACH:
6034         if (playerwide) {
6035           cat_snprintf(buf, bufsz,
6036                        PL_("* Your units may impose martial law."
6037                            " Each military unit inside a city will force %d"
6038                            " unhappy citizen to become content.\n",
6039                            "* Your units may impose martial law."
6040                            " Each military unit inside a city will force %d"
6041                            " unhappy citizens to become content.\n",
6042                            peffect->value),
6043                        peffect->value);
6044         }
6045         break;
6046       case EFT_MARTIAL_LAW_MAX:
6047         if (playerwide && net_value < 100) {
6048           cat_snprintf(buf, bufsz,
6049                        PL_("* A maximum of %d unit in each city can enforce"
6050                            " martial law.\n",
6051                            "* A maximum of %d units in each city can enforce"
6052                            " martial law.\n",
6053                            net_value),
6054                        net_value);
6055         }
6056         break;
6057       case EFT_RAPTURE_GROW:
6058         if (playerwide && net_value > 0) {
6059           cat_snprintf(buf, bufsz,
6060                        _("* You may grow your cities by means of "
6061                          "celebrations."));
6062           if (game.info.celebratesize > 1) {
6063             cat_snprintf(buf, bufsz,
6064                          /* TRANS: Preserve leading space. %d should always be
6065                           * 2 or greater. */
6066                          _(" (Cities below size %d cannot grow in this way.)"),
6067                          game.info.celebratesize);
6068           }
6069           cat_snprintf(buf, bufsz, "\n");
6070         }
6071         break;
6072       case EFT_REVOLUTION_UNHAPPINESS:
6073         if (playerwide) {
6074           cat_snprintf(buf, bufsz,
6075                        PL_("* If a city is in disorder for more than %d turn "
6076                            "in a row, government will fall into anarchy.\n",
6077                            "* If a city is in disorder for more than %d turns "
6078                            "in a row, government will fall into anarchy.\n",
6079                            net_value),
6080                        net_value);
6081         }
6082         break;
6083       case EFT_HAS_SENATE:
6084         if (playerwide && net_value > 0) {
6085           CATLSTR(buf, bufsz,
6086                   _("* Has a senate that may prevent declaration of war.\n"));
6087         }
6088         break;
6089       case EFT_INSPIRE_PARTISANS:
6090         if (playerwide && net_value > 0) {
6091           CATLSTR(buf, bufsz,
6092                   _("* Allows partisans when cities are taken by the "
6093                     "enemy.\n"));
6094         }
6095         break;
6096       case EFT_HAPPINESS_TO_GOLD:
6097         if (playerwide && net_value > 0) {
6098           CATLSTR(buf, bufsz,
6099                   _("* Buildings that normally confer bonuses against"
6100                     " unhappiness will instead give gold.\n"));
6101         }
6102         break;
6103       case EFT_FANATICS:
6104         if (playerwide && net_value > 0) {
6105           struct strvec *fanatics = strvec_new();
6106           struct astring fanaticstr = ASTRING_INIT;
6107 
6108           unit_type_iterate(putype) {
6109             if (utype_has_flag(putype, UTYF_FANATIC)) {
6110               strvec_append(fanatics, utype_name_translation(putype));
6111             }
6112           } unit_type_iterate_end;
6113           cat_snprintf(buf, bufsz,
6114                        /* TRANS: %s is list of unit types separated by 'or' */
6115                        _("* Pays no upkeep for %s.\n"),
6116                        strvec_to_or_list(fanatics, &fanaticstr));
6117           strvec_destroy(fanatics);
6118           astr_free(&fanaticstr);
6119         }
6120         break;
6121       case EFT_NO_UNHAPPY:
6122         if (playerwide && net_value > 0) {
6123           CATLSTR(buf, bufsz, _("* Has no unhappy citizens.\n"));
6124         }
6125         break;
6126       case EFT_VETERAN_BUILD:
6127         {
6128           int conditions = 0;
6129           if (unitclass) {
6130             conditions++;
6131           }
6132           if (unittype) {
6133             conditions++;
6134           }
6135           if (unit_type_flag_id_is_valid(unitflag)) {
6136             conditions++;
6137           }
6138           if (conditions > 1) {
6139             /* More than one requirement on units, too complicated for us
6140              * to describe. */
6141             break;
6142           }
6143           if (unitclass) {
6144             /* FIXME: account for multiple veteran levels, or negative
6145              * values. This might lie for complicated rulesets! */
6146             cat_snprintf(buf, bufsz,
6147                          /* TRANS: %s is a unit class */
6148                          Q_("?unitclass:* New %s units will be veteran.\n"),
6149                          uclass_name_translation(unitclass));
6150           } else if (unit_type_flag_id_is_valid(unitflag)) {
6151             /* FIXME: same problems as unitclass */
6152             cat_snprintf(buf, bufsz,
6153                          /* TRANS: %s is a (translatable) unit type flag */
6154                          Q_("?unitflag:* New %s units will be veteran.\n"),
6155                          unit_type_flag_id_translated_name(unitflag));
6156           } else if (unittype != NULL) {
6157             if (world_value_valid && net_value > 0) {
6158               /* Here we can be specific about veteran level, and get
6159                * net value correct. */
6160               int maxlvl = utype_veteran_system(unittype)->levels - 1;
6161               const struct veteran_level *vlevel =
6162                 utype_veteran_level(unittype, MIN(net_value, maxlvl));
6163               cat_snprintf(buf, bufsz,
6164                            /* TRANS: "* New Partisan units will have the rank
6165                             * of elite." */
6166                            Q_("?unittype:* New %s units will have the rank "
6167                               "of %s.\n"),
6168                            utype_name_translation(unittype),
6169                            name_translation_get(&vlevel->name));
6170             } /* else complicated */
6171           } else {
6172             /* No extra criteria. */
6173             /* FIXME: same problems as above */
6174             cat_snprintf(buf, bufsz,
6175                          _("* New units will be veteran.\n"));
6176           }
6177         }
6178         break;
6179       case EFT_OUTPUT_PENALTY_TILE:
6180         if (world_value_valid) {
6181           cat_snprintf(buf, bufsz,
6182                        /* TRANS: %s is list of output types, with 'or';
6183                         * pluralised in %d but of course the output types
6184                         * can't be pluralised; sorry */
6185                        PL_("* Each worked tile that gives more than %d %s will"
6186                            " suffer a -1 penalty, unless the city working it"
6187                            " is celebrating.",
6188                            "* Each worked tile that gives more than %d %s will"
6189                            " suffer a -1 penalty, unless the city working it"
6190                            " is celebrating.", net_value),
6191                        net_value, astr_str(&outputs_or));
6192           if (game.info.celebratesize > 1) {
6193             cat_snprintf(buf, bufsz,
6194                          /* TRANS: Preserve leading space. %d should always be
6195                           * 2 or greater. */
6196                          _(" (Cities below size %d will not celebrate.)"),
6197                          game.info.celebratesize);
6198           }
6199           cat_snprintf(buf, bufsz, "\n");
6200         }
6201         break;
6202       case EFT_OUTPUT_INC_TILE_CELEBRATE:
6203         cat_snprintf(buf, bufsz,
6204                      /* TRANS: %s is list of output types, with 'or' */
6205                      PL_("* Each worked tile with at least 1 %s will yield"
6206                          " %d more of it while the city working it is"
6207                          " celebrating.",
6208                          "* Each worked tile with at least 1 %s will yield"
6209                          " %d more of it while the city working it is"
6210                          " celebrating.", peffect->value),
6211                      astr_str(&outputs_or), peffect->value);
6212         if (game.info.celebratesize > 1) {
6213           cat_snprintf(buf, bufsz,
6214                        /* TRANS: Preserve leading space. %d should always be
6215                         * 2 or greater. */
6216                        _(" (Cities below size %d will not celebrate.)"),
6217                        game.info.celebratesize);
6218         }
6219         cat_snprintf(buf, bufsz, "\n");
6220         break;
6221       case EFT_OUTPUT_INC_TILE:
6222         cat_snprintf(buf, bufsz,
6223                      /* TRANS: %s is list of output types, with 'or' */
6224                      PL_("* Each worked tile with at least 1 %s will yield"
6225                          " %d more of it.\n",
6226                          "* Each worked tile with at least 1 %s will yield"
6227                          " %d more of it.\n", peffect->value),
6228                      astr_str(&outputs_or), peffect->value);
6229         break;
6230       case EFT_OUTPUT_BONUS:
6231       case EFT_OUTPUT_BONUS_2:
6232         /* FIXME: makes most sense iff world_value == 0 */
6233         cat_snprintf(buf, bufsz,
6234                      /* TRANS: %s is list of output types, with 'and' */
6235                      _("* %s production is increased %d%%.\n"),
6236                      astr_str(&outputs_and), peffect->value);
6237         break;
6238       case EFT_OUTPUT_WASTE:
6239         if (world_value_valid) {
6240           if (net_value > 30) {
6241             cat_snprintf(buf, bufsz,
6242                          /* TRANS: %s is list of output types, with 'and' */
6243                          _("* %s production will suffer massive losses.\n"),
6244                          astr_str(&outputs_and));
6245           } else if (net_value >= 15) {
6246             cat_snprintf(buf, bufsz,
6247                          /* TRANS: %s is list of output types, with 'and' */
6248                          _("* %s production will suffer some losses.\n"),
6249                          astr_str(&outputs_and));
6250           } else if (net_value > 0) {
6251             cat_snprintf(buf, bufsz,
6252                          /* TRANS: %s is list of output types, with 'and' */
6253                          _("* %s production will suffer a small amount "
6254                            "of losses.\n"),
6255                          astr_str(&outputs_and));
6256           }
6257         }
6258         break;
6259       case EFT_HEALTH_PCT:
6260         if (playerwide) {
6261           if (peffect->value > 0) {
6262             CATLSTR(buf, bufsz, _("* Increases the chance of plague"
6263                                   " within your cities.\n"));
6264           } else if (peffect->value < 0) {
6265             CATLSTR(buf, bufsz, _("* Decreases the chance of plague"
6266                                   " within your cities.\n"));
6267           }
6268         }
6269         break;
6270       case EFT_OUTPUT_WASTE_BY_DISTANCE:
6271         if (world_value_valid) {
6272           if (net_value >= 3) {
6273             cat_snprintf(buf, bufsz,
6274                          /* TRANS: %s is list of output types, with 'and' */
6275                          _("* %s losses will increase quickly"
6276                            " with distance from capital.\n"),
6277                          astr_str(&outputs_and));
6278           } else if (net_value == 2) {
6279             cat_snprintf(buf, bufsz,
6280                          /* TRANS: %s is list of output types, with 'and' */
6281                          _("* %s losses will increase"
6282                            " with distance from capital.\n"),
6283                          astr_str(&outputs_and));
6284           } else if (net_value > 0) {
6285             cat_snprintf(buf, bufsz,
6286                          /* TRANS: %s is list of output types, with 'and' */
6287                          _("* %s losses will increase slowly"
6288                            " with distance from capital.\n"),
6289                          astr_str(&outputs_and));
6290           }
6291         }
6292         break;
6293       case EFT_MIGRATION_PCT:
6294         if (playerwide) {
6295           if (peffect->value > 0) {
6296             CATLSTR(buf, bufsz, _("* Increases the chance of migration"
6297                                   " into your cities.\n"));
6298           } else if (peffect->value < 0) {
6299             CATLSTR(buf, bufsz, _("* Decreases the chance of migration"
6300                                   " into your cities.\n"));
6301           }
6302         }
6303         break;
6304       default:
6305         break;
6306       };
6307     }
6308 
6309     strvec_destroy(outputs);
6310     astr_free(&outputs_or);
6311     astr_free(&outputs_and);
6312 
6313   } effect_list_iterate_end;
6314 
6315   unit_type_iterate(utype) {
6316     if (utype->need_government == gov) {
6317       cat_snprintf(buf, bufsz,
6318                    _("* Allows you to build %s.\n"),
6319                    utype_name_translation(utype));
6320     }
6321   } unit_type_iterate_end;
6322 
6323   /* Action immunity */
6324   action_iterate(act) {
6325     if (action_by_number(act)->quiet) {
6326       /* The ruleset documents this action it self. */
6327       continue;
6328     }
6329 
6330     if (action_immune_government(gov, act)) {
6331       cat_snprintf(buf, bufsz,
6332                    /* TRANS: action name ... action target
6333                     * ("individual units", etc) */
6334                    _("* Makes it impossible to do the action \'%s\'"
6335                      " to your %s.\n"),
6336                    action_id_name_translation(act),
6337                    _(action_target_kind_name(
6338                        action_id_get_target_kind(act))));
6339     }
6340   } action_iterate_end;
6341 
6342   if (user_text && user_text[0] != '\0') {
6343     cat_snprintf(buf, bufsz, "\n%s", user_text);
6344   }
6345 }
6346 
6347 /****************************************************************
6348   Returns pointer to static string with eg: "1 shield, 1 unhappy"
6349 *****************************************************************/
helptext_unit_upkeep_str(struct unit_type * utype)6350 char *helptext_unit_upkeep_str(struct unit_type *utype)
6351 {
6352   static char buf[128];
6353   int any = 0;
6354 
6355   if (!utype) {
6356     log_error("Unknown unit!");
6357     return "";
6358   }
6359 
6360 
6361   buf[0] = '\0';
6362   output_type_iterate(o) {
6363     if (utype->upkeep[o] > 0) {
6364       /* TRANS: "2 Food" or ", 1 Shield" */
6365       cat_snprintf(buf, sizeof(buf), _("%s%d %s"),
6366 	      (any > 0 ? Q_("?blistmore:, ") : ""), utype->upkeep[o],
6367 	      get_output_name(o));
6368       any++;
6369     }
6370   } output_type_iterate_end;
6371   if (utype->happy_cost > 0) {
6372     /* TRANS: "2 Unhappy" or ", 1 Unhappy" */
6373     cat_snprintf(buf, sizeof(buf), _("%s%d Unhappy"),
6374 	    (any > 0 ? Q_("?blistmore:, ") : ""), utype->happy_cost);
6375     any++;
6376   }
6377 
6378   if (any == 0) {
6379     /* strcpy(buf, _("None")); */
6380     fc_snprintf(buf, sizeof(buf), "%d", 0);
6381   }
6382   return buf;
6383 }
6384 
6385 /****************************************************************************
6386   Returns nation legend and characteristics
6387 ****************************************************************************/
helptext_nation(char * buf,size_t bufsz,struct nation_type * pnation,const char * user_text)6388 void helptext_nation(char *buf, size_t bufsz, struct nation_type *pnation,
6389 		     const char *user_text)
6390 {
6391   struct universal source = {
6392     .kind = VUT_NATION,
6393     .value = {.nation = pnation}
6394   };
6395   bool print_break = TRUE;
6396 #define PRINT_BREAK() do { \
6397     if (print_break) { \
6398       if (buf[0] != '\0') { \
6399         CATLSTR(buf, bufsz, "\n\n"); \
6400       } \
6401       print_break = FALSE; \
6402     } \
6403   } while(0)
6404 
6405   fc_assert_ret(NULL != buf && 0 < bufsz);
6406   buf[0] = '\0';
6407 
6408   if (pnation->legend[0] != '\0') {
6409     /* Client side legend is stored already translated */
6410     cat_snprintf(buf, bufsz, "%s", pnation->legend);
6411   }
6412 
6413   if (pnation->init_government) {
6414     PRINT_BREAK();
6415     cat_snprintf(buf, bufsz,
6416                  _("Initial government is %s.\n"),
6417                  government_name_translation(pnation->init_government));
6418   }
6419   if (pnation->init_techs[0] != A_LAST) {
6420     const char *tech_names[MAX_NUM_TECH_LIST];
6421     int i;
6422     struct astring list = ASTRING_INIT;
6423     for (i = 0; i < MAX_NUM_TECH_LIST; i++) {
6424       if (pnation->init_techs[i] == A_LAST) {
6425         break;
6426       }
6427       tech_names[i] =
6428         advance_name_translation(advance_by_number(pnation->init_techs[i]));
6429     }
6430     astr_build_and_list(&list, tech_names, i);
6431     PRINT_BREAK();
6432     if (game.rgame.global_init_techs[0] != A_LAST) {
6433       cat_snprintf(buf, bufsz,
6434                    /* TRANS: %s is an and-separated list of techs */
6435                    _("Starts with knowledge of %s in addition to the standard "
6436                      "starting technologies.\n"), astr_str(&list));
6437     } else {
6438       cat_snprintf(buf, bufsz,
6439                    /* TRANS: %s is an and-separated list of techs */
6440                    _("Starts with knowledge of %s.\n"), astr_str(&list));
6441     }
6442     astr_free(&list);
6443   }
6444   if (pnation->init_units[0]) {
6445     const struct unit_type *utypes[MAX_NUM_UNIT_LIST];
6446     int count[MAX_NUM_UNIT_LIST];
6447     int i, j, n = 0, total = 0;
6448     /* Count how many of each type there is. */
6449     for (i = 0; i < MAX_NUM_UNIT_LIST; i++) {
6450       if (!pnation->init_units[i]) {
6451         break;
6452       }
6453       for (j = 0; j < n; j++) {
6454         if (pnation->init_units[i] == utypes[j]) {
6455           count[j]++;
6456           total++;
6457           break;
6458         }
6459       }
6460       if (j == n) {
6461         utypes[n] = pnation->init_units[i];
6462         count[n] = 1;
6463         total++;
6464         n++;
6465       }
6466     }
6467     {
6468       /* Construct the list of unit types and counts. */
6469       struct astring utype_names[MAX_NUM_UNIT_LIST];
6470       struct astring list = ASTRING_INIT;
6471       for (i = 0; i < n; i++) {
6472         astr_init(&utype_names[i]);
6473         if (count[i] > 1) {
6474           /* TRANS: a unit type followed by a count. For instance,
6475            * "Fighter (2)" means two Fighters. Count is never 1.
6476            * Used in a list. */
6477           astr_set(&utype_names[i], _("%s (%d)"),
6478                    utype_name_translation(utypes[i]), count[i]);
6479         } else {
6480           astr_set(&utype_names[i], "%s", utype_name_translation(utypes[i]));
6481         }
6482       }
6483       {
6484         const char *utype_name_strs[MAX_NUM_UNIT_LIST];
6485         for (i = 0; i < n; i++) {
6486           utype_name_strs[i] = astr_str(&utype_names[i]);
6487         }
6488         astr_build_and_list(&list, utype_name_strs, n);
6489       }
6490       for (i = 0; i < n; i++) {
6491         astr_free(&utype_names[i]);
6492       }
6493       PRINT_BREAK();
6494       cat_snprintf(buf, bufsz,
6495                    /* TRANS: %s is an and-separated list of unit types
6496                     * possibly with counts. Plurality is in total number of
6497                     * units represented. */
6498                    PL_("Starts with the following additional unit: %s.\n",
6499                        "Starts with the following additional units: %s.\n",
6500                       total), astr_str(&list));
6501       astr_free(&list);
6502     }
6503   }
6504   if (pnation->init_buildings[0] != B_LAST) {
6505     const char *impr_names[MAX_NUM_BUILDING_LIST];
6506     int i;
6507     struct astring list = ASTRING_INIT;
6508     for (i = 0; i < MAX_NUM_BUILDING_LIST; i++) {
6509       if (pnation->init_buildings[i] == B_LAST) {
6510         break;
6511       }
6512       impr_names[i] =
6513         improvement_name_translation(
6514           improvement_by_number(pnation->init_buildings[i]));
6515     }
6516     astr_build_and_list(&list, impr_names, i);
6517     PRINT_BREAK();
6518     if (game.rgame.global_init_buildings[0] != B_LAST) {
6519       cat_snprintf(buf, bufsz,
6520                    /* TRANS: %s is an and-separated list of improvements */
6521                    _("First city will get %s for free in addition to the "
6522                      "standard improvements.\n"), astr_str(&list));
6523     } else {
6524       cat_snprintf(buf, bufsz,
6525                    /* TRANS: %s is an and-separated list of improvements */
6526                    _("First city will get %s for free.\n"), astr_str(&list));
6527     }
6528     astr_free(&list);
6529   }
6530 
6531   if (buf[0] != '\0') {
6532     CATLSTR(buf, bufsz, "\n");
6533   }
6534   insert_allows(&source, buf + strlen(buf), bufsz - strlen(buf), "");
6535 
6536   if (user_text && user_text[0] != '\0') {
6537     if (buf[0] != '\0') {
6538       CATLSTR(buf, bufsz, "\n");
6539     }
6540     CATLSTR(buf, bufsz, user_text);
6541   }
6542 #undef PRINT_BREAK
6543 }
6544