Stop sharing requirement_unit_state_ereq().
[freeciv.git] / client / helpdata.c
blob575213dbd917122c6cfd6f63e9335037243110f8
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.
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 ***********************************************************************/
14 /***********************************************************************
15 This module is for generic handling of help data, independent
16 of gui considerations.
17 ***********************************************************************/
19 #ifdef HAVE_CONFIG_H
20 #include <fc_config.h>
21 #endif
23 #include <stdio.h>
24 #include <string.h>
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"
37 /* common */
38 #include "effects.h"
39 #include "game.h"
40 #include "government.h"
41 #include "map.h"
42 #include "movement.h"
43 #include "multipliers.h"
44 #include "reqtext.h"
45 #include "research.h"
46 #include "specialist.h"
47 #include "tilespec.h"
48 #include "unit.h"
49 #include "version.h"
51 /* client */
52 #include "client_main.h"
53 #include "climisc.h"
54 #include "gui_main_g.h" /* client_string */
56 #include "helpdata.h"
58 /* helper macro for easy conversion from snprintf and cat_snprintf */
59 #define CATLSTR(_b, _s, _t) fc_strlcat(_b, _t, _s)
61 /* This must be in same order as enum in helpdlg_g.h */
62 static const char * const help_type_names[] = {
63 "(Any)", "(Text)", "Units", "Improvements", "Wonders",
64 "Techs", "Terrain", "Extras", "Goods", "Specialists", "Governments",
65 "Ruleset", "Tileset", "Nations", "Multipliers", NULL
68 /*define MAX_LAST (MAX(MAX(MAX(A_LAST,B_LAST),U_LAST),terrain_count()))*/
70 #define SPECLIST_TAG help
71 #define SPECLIST_TYPE struct help_item
72 #include "speclist.h"
74 #define help_list_iterate(helplist, phelp) \
75 TYPED_LIST_ITERATE(struct help_item, helplist, phelp)
76 #define help_list_iterate_end LIST_ITERATE_END
78 static const struct help_list_link *help_nodes_iterator;
79 static struct help_list *help_nodes;
80 static bool help_nodes_init = FALSE;
81 /* helpnodes_init is not quite the same as booted in boot_help_texts();
82 latter can be 0 even after call, eg if couldn't find helpdata.txt.
85 /****************************************************************
86 Initialize.
87 *****************************************************************/
88 void helpdata_init(void)
90 help_nodes = help_list_new();
93 /****************************************************************
94 Clean up.
95 *****************************************************************/
96 void helpdata_done(void)
98 help_list_destroy(help_nodes);
101 /****************************************************************
102 Make sure help_nodes is initialised.
103 Should call this just about everywhere which uses help_nodes,
104 to be careful... or at least where called by external
105 (non-static) functions.
106 *****************************************************************/
107 static void check_help_nodes_init(void)
109 if (!help_nodes_init) {
110 help_nodes_init = TRUE; /* before help_iter_start to avoid recursion! */
111 help_iter_start();
115 /****************************************************************
116 Free all allocations associated with help_nodes.
117 *****************************************************************/
118 void free_help_texts(void)
120 check_help_nodes_init();
121 help_list_iterate(help_nodes, ptmp) {
122 free(ptmp->topic);
123 free(ptmp->text);
124 free(ptmp);
125 } help_list_iterate_end;
126 help_list_clear(help_nodes);
129 /****************************************************************************
130 Returns whether we should show help for this nation.
131 ****************************************************************************/
132 static bool show_help_for_nation(const struct nation_type *pnation)
134 return client_nation_is_in_current_set(pnation);
137 /****************************************************************************
138 Insert fixed-width table describing veteran system.
139 If only one veteran level, inserts 'nolevels' if non-NULL.
140 Otherwise, insert 'intro' then a table.
141 ****************************************************************************/
142 static bool insert_veteran_help(char *outbuf, size_t outlen,
143 const struct veteran_system *veteran,
144 const char *intro, const char *nolevels)
146 /* game.veteran can be NULL in pregame; if so, keep quiet about
147 * veteran levels */
148 if (!veteran) {
149 return FALSE;
152 fc_assert_ret_val(veteran->levels >= 1, FALSE);
154 if (veteran->levels == 1) {
155 /* Only a single veteran level. Don't bother to name it. */
156 if (nolevels) {
157 CATLSTR(outbuf, outlen, nolevels);
158 return TRUE;
159 } else {
160 return FALSE;
162 } else {
163 int i;
164 fc_assert_ret_val(veteran->definitions != NULL, FALSE);
165 if (intro) {
166 CATLSTR(outbuf, outlen, intro);
167 CATLSTR(outbuf, outlen, "\n\n");
169 /* raise_chance and work_raise_chance don't get to the client, so we
170 * can't report them */
171 CATLSTR(outbuf, outlen,
172 /* TRANS: Header for fixed-width veteran level table.
173 * TRANS: Translators cannot change column widths :(
174 * TRANS: "Level name" left-justified, other two right-justified */
175 _("Veteran level Power factor Move bonus\n"));
176 CATLSTR(outbuf, outlen,
177 /* TRANS: Part of header for veteran level table. */
178 _("--------------------------------------------"));
179 for (i = 0; i < veteran->levels; i++) {
180 const struct veteran_level *level = &veteran->definitions[i];
181 const char *name = name_translation_get(&level->name);
182 /* Use get_internal_string_length() for correct alignment with
183 * multibyte character encodings */
184 cat_snprintf(outbuf, outlen,
185 "\n%s%*s %4d%% %12s",
186 name, MAX(0, 25 - (int)get_internal_string_length(name)), "",
187 level->power_fact,
188 /* e.g. "- ", "+ 1/3", "+ 1 ", "+ 2 2/3" */
189 move_points_text_full(level->move_bonus, TRUE, "+ ", "-", TRUE));
191 return TRUE;
195 /****************************************************************************
196 Insert generated text for the helpdata "name".
197 Returns TRUE if anything was added.
198 ****************************************************************************/
199 static bool insert_generated_text(char *outbuf, size_t outlen, const char *name)
201 if (!game.client.ruleset_init) {
202 return FALSE;
205 if (0 == strcmp(name, "TerrainAlterations")) {
206 int clean_pollution_time = -1, clean_fallout_time = -1;
207 bool terrain_independent_extras = FALSE;
209 CATLSTR(outbuf, outlen,
210 /* TRANS: Header for fixed-width terrain alteration table.
211 * TRANS: Translators cannot change column widths :( */
212 _("Terrain Irrigation Mining Transform\n"));
213 CATLSTR(outbuf, outlen,
214 "----------------------------------------------------------------\n");
215 terrain_type_iterate(pterrain) {
216 if (0 != strlen(terrain_rule_name(pterrain))) {
217 char irrigation_time[4], mining_time[4], transform_time[4];
218 const char *terrain, *irrigation_result,
219 *mining_result,*transform_result;
220 struct universal for_terr = { .kind = VUT_TERRAIN, .value = { .terrain = pterrain }};
222 fc_snprintf(irrigation_time, sizeof(irrigation_time),
223 "%d", pterrain->irrigation_time);
224 fc_snprintf(mining_time, sizeof(mining_time),
225 "%d", pterrain->mining_time);
226 fc_snprintf(transform_time, sizeof(transform_time),
227 "%d", pterrain->transform_time);
228 terrain = terrain_name_translation(pterrain);
229 irrigation_result =
230 (pterrain->irrigation_result == pterrain
231 || pterrain->irrigation_result == T_NONE
232 || effect_cumulative_max(EFT_IRRIG_TF_POSSIBLE, &for_terr) <= 0) ? ""
233 : terrain_name_translation(pterrain->irrigation_result);
234 mining_result =
235 (pterrain->mining_result == pterrain
236 || pterrain->mining_result == T_NONE
237 || effect_cumulative_max(EFT_MINING_TF_POSSIBLE, &for_terr) <= 0) ? ""
238 : terrain_name_translation(pterrain->mining_result);
239 transform_result =
240 (pterrain->transform_result == pterrain
241 || pterrain->transform_result == T_NONE
242 || effect_cumulative_max(EFT_TRANSFORM_POSSIBLE, &for_terr) <= 0) ? ""
243 : terrain_name_translation(pterrain->transform_result);
244 /* Use get_internal_string_length() for correct alignment with
245 * multibyte character encodings */
246 cat_snprintf(outbuf, outlen,
247 "%s%*s %3s %s%*s %3s %s%*s %3s %s\n",
248 terrain,
249 MAX(0, 12 - (int)get_internal_string_length(terrain)), "",
250 (pterrain->irrigation_result == T_NONE) ? "-" : irrigation_time,
251 irrigation_result,
252 MAX(0, 12 - (int)get_internal_string_length(irrigation_result)), "",
253 (pterrain->mining_result == T_NONE) ? "-" : mining_time,
254 mining_result,
255 MAX(0, 12 - (int)get_internal_string_length(mining_result)), "",
256 (pterrain->transform_result == T_NONE) ? "-" : transform_time,
257 transform_result);
259 if (clean_pollution_time != 0) {
260 if (clean_pollution_time < 0) {
261 clean_pollution_time = pterrain->clean_pollution_time;
262 } else {
263 if (clean_pollution_time != pterrain->clean_pollution_time) {
264 clean_pollution_time = 0; /* give up */
268 if (clean_fallout_time != 0) {
269 if (clean_fallout_time < 0) {
270 clean_fallout_time = pterrain->clean_fallout_time;
271 } else {
272 if (clean_fallout_time != pterrain->clean_fallout_time) {
273 clean_fallout_time = 0; /* give up */
278 } terrain_type_iterate_end;
280 extra_type_by_cause_iterate(EC_BASE, pextra) {
281 if (pextra->buildable && pextra->build_time > 0) {
282 terrain_independent_extras = TRUE;
283 break;
285 } extra_type_by_cause_iterate_end;
286 if (!terrain_independent_extras) {
287 extra_type_by_cause_iterate(EC_ROAD, pextra) {
288 if (pextra->buildable && pextra->build_time > 0) {
289 terrain_independent_extras = TRUE;
290 break;
292 } extra_type_by_cause_iterate_end;
295 if (clean_pollution_time > 0 || clean_fallout_time > 0
296 || terrain_independent_extras) {
297 CATLSTR(outbuf, outlen, "\n");
298 CATLSTR(outbuf, outlen,
299 _("Time taken for the following activities is independent of "
300 "terrain:\n"));
301 CATLSTR(outbuf, outlen, "\n");
302 CATLSTR(outbuf, outlen,
303 /* TRANS: Header for fixed-width terrain alteration table.
304 * TRANS: Translators cannot change column widths :( */
305 _("Activity Time\n"));
306 CATLSTR(outbuf, outlen,
307 "---------------------------");
308 if (clean_pollution_time > 0)
309 cat_snprintf(outbuf, outlen,
310 _("\nClean pollution %3d"), clean_pollution_time);
311 if (clean_fallout_time > 0)
312 cat_snprintf(outbuf, outlen,
313 _("\nClean fallout %3d"), clean_fallout_time);
314 extra_type_by_cause_iterate(EC_ROAD, pextra) {
315 if (pextra->buildable && pextra->build_time > 0) {
316 const char *rname = extra_name_translation(pextra);
318 cat_snprintf(outbuf, outlen,
319 "\n%s%*s %3d",
320 rname,
321 MAX(0, 18 - (int)get_internal_string_length(rname)), "",
322 pextra->build_time);
324 } extra_type_by_cause_iterate_end;
325 extra_type_by_cause_iterate(EC_BASE, pextra) {
326 if (pextra->buildable && pextra->build_time > 0) {
327 const char *bname = extra_name_translation(pextra);
329 cat_snprintf(outbuf, outlen,
330 "\n%s%*s %3d",
331 bname,
332 MAX(0, 18 - (int)get_internal_string_length(bname)), "",
333 pextra->build_time);
335 } extra_type_by_cause_iterate_end;
337 return TRUE;
338 } else if (0 == strcmp(name, "VeteranLevels")) {
339 return insert_veteran_help(outbuf, outlen, game.veteran,
340 _("In this ruleset, the following veteran levels are defined:"),
341 _("This ruleset has no default veteran levels defined."));
342 } else if (0 == strcmp(name, "FreecivVersion")) {
343 const char *ver = freeciv_name_version();
345 cat_snprintf(outbuf, outlen,
346 /* TRANS: First %s is version string, e.g.,
347 * "Freeciv version 2.3.0-beta1 (beta version)" (translated).
348 * Second %s is client_string, e.g., "gui-gtk-2.0". */
349 _("This is %s, %s client."), ver, client_string);
350 insert_client_build_info(outbuf, outlen);
352 return TRUE;
353 } else if (0 == strcmp(name, "DefaultMetaserver")) {
354 cat_snprintf(outbuf, outlen, " %s", FREECIV_META_URL);
356 return TRUE;
358 log_error("Unknown directive '$%s' in help", name);
359 return FALSE;
362 /****************************************************************************
363 Append text to 'buf' if the given requirements list 'subjreqs' contains
364 'psource', implying that ability to build the subject 'subjstr' is
365 affected by 'psource'.
366 'strs' is an array of (possibly i18n-qualified) format strings covering
367 the various cases where additional requirements apply.
368 ****************************************************************************/
369 static void insert_allows_single(struct universal *psource,
370 struct requirement_vector *psubjreqs,
371 const char *subjstr,
372 const char *const *strs,
373 char *buf, size_t bufsz) {
374 struct strvec *coreqs = strvec_new();
375 struct strvec *conoreqs = strvec_new();
376 struct astring coreqstr = ASTRING_INIT;
377 struct astring conoreqstr = ASTRING_INIT;
378 char buf2[bufsz];
380 /* FIXME: show other data like range and survives. */
382 requirement_vector_iterate(psubjreqs, req) {
383 if (!req->quiet && are_universals_equal(psource, &req->source)) {
384 if (req->present) {
385 /* psource enables the subject, but other sources may
386 * also be required (or required to be absent). */
387 requirement_vector_iterate(psubjreqs, coreq) {
388 if (!coreq->quiet && !are_universals_equal(psource, &coreq->source)) {
389 universal_name_translation(&coreq->source, buf2, sizeof(buf2));
390 strvec_append(coreq->present ? coreqs : conoreqs, buf2);
392 } requirement_vector_iterate_end;
394 if (0 < strvec_size(coreqs)) {
395 if (0 < strvec_size(conoreqs)) {
396 cat_snprintf(buf, bufsz,
397 Q_(strs[0]), /* "Allows %s (with %s but no %s)." */
398 subjstr,
399 strvec_to_and_list(coreqs, &coreqstr),
400 strvec_to_or_list(conoreqs, &conoreqstr));
401 } else {
402 cat_snprintf(buf, bufsz,
403 Q_(strs[1]), /* "Allows %s (with %s)." */
404 subjstr,
405 strvec_to_and_list(coreqs, &coreqstr));
407 } else {
408 if (0 < strvec_size(conoreqs)) {
409 cat_snprintf(buf, bufsz,
410 Q_(strs[2]), /* "Allows %s (absent %s)." */
411 subjstr,
412 strvec_to_and_list(conoreqs, &conoreqstr));
413 } else {
414 cat_snprintf(buf, bufsz,
415 Q_(strs[3]), /* "Allows %s." */
416 subjstr);
419 } else {
420 /* psource can, on its own, prevent the subject. */
421 cat_snprintf(buf, bufsz,
422 Q_(strs[4]), /* "Prevents %s." */
423 subjstr);
425 cat_snprintf(buf, bufsz, "\n");
427 } requirement_vector_iterate_end;
429 strvec_destroy(coreqs);
430 strvec_destroy(conoreqs);
431 astr_free(&coreqstr);
432 astr_free(&conoreqstr);
435 /****************************************************************************
436 Generate text for what this requirement source allows. Something like
438 "Allows Communism (with University).\n"
439 "Allows Mfg. Plant (with Factory).\n"
440 "Allows Library (absent Fundamentalism).\n"
441 "Prevents Harbor.\n"
443 This should be called to generate helptext for every possible source
444 type. Note this doesn't handle effects but rather requirements to
445 create/maintain things (currently only building/government reqs).
447 NB: This function overwrites any existing buffer contents by writing the
448 generated text to the start of the given 'buf' pointer (i.e. it does
449 NOT append like cat_snprintf).
450 ****************************************************************************/
451 static void insert_allows(struct universal *psource,
452 char *buf, size_t bufsz)
454 buf[0] = '\0';
456 governments_iterate(pgov) {
457 static const char *const govstrs[] = {
458 /* TRANS: First %s is a government name. */
459 N_("?gov:* Allows %s (with %s but no %s)."),
460 /* TRANS: First %s is a government name. */
461 N_("?gov:* Allows %s (with %s)."),
462 /* TRANS: First %s is a government name. */
463 N_("?gov:* Allows %s (absent %s)."),
464 /* TRANS: %s is a government name. */
465 N_("?gov:* Allows %s."),
466 /* TRANS: %s is a government name. */
467 N_("?gov:* Prevents %s.")
469 insert_allows_single(psource, &pgov->reqs,
470 government_name_translation(pgov), govstrs,
471 buf, bufsz);
472 } governments_iterate_end;
474 improvement_iterate(pimprove) {
475 static const char *const imprstrs[] = {
476 /* TRANS: First %s is a building name. */
477 N_("?improvement:* Allows %s (with %s but no %s)."),
478 /* TRANS: First %s is a building name. */
479 N_("?improvement:* Allows %s (with %s)."),
480 /* TRANS: First %s is a building name. */
481 N_("?improvement:* Allows %s (absent %s)."),
482 /* TRANS: %s is a building name. */
483 N_("?improvement:* Allows %s."),
484 /* TRANS: %s is a building name. */
485 N_("?improvement:* Prevents %s.")
487 insert_allows_single(psource, &pimprove->reqs,
488 improvement_name_translation(pimprove), imprstrs,
489 buf, bufsz);
490 } improvement_iterate_end;
493 /****************************************************************
494 Allocate and initialize new help item
495 *****************************************************************/
496 static struct help_item *new_help_item(int type)
498 struct help_item *pitem;
500 pitem = fc_malloc(sizeof(struct help_item));
501 pitem->topic = NULL;
502 pitem->text = NULL;
503 pitem->type = type;
504 return pitem;
507 /****************************************************************
508 for help_list_sort(); sort by topic via compare_strings()
509 (sort topics with more leading spaces after those with fewer)
510 *****************************************************************/
511 static int help_item_compar(const struct help_item *const *ppa,
512 const struct help_item *const *ppb)
514 const struct help_item *ha, *hb;
515 char *ta, *tb;
516 ha = *ppa;
517 hb = *ppb;
518 for (ta = ha->topic, tb = hb->topic; *ta != '\0' && *tb != '\0'; ta++, tb++) {
519 if (*ta != ' ') {
520 if (*tb == ' ') return -1;
521 break;
522 } else if (*tb != ' ') {
523 if (*ta == ' ') return 1;
524 break;
527 return compare_strings(ta, tb);
530 /****************************************************************
531 pplayer may be NULL.
532 *****************************************************************/
533 void boot_help_texts(void)
535 static bool booted = FALSE;
537 struct section_file *sf;
538 const char *filename;
539 struct help_item *pitem;
540 int i;
541 struct section_list *sec;
542 const char **paras;
543 size_t npara;
544 char long_buffer[64000]; /* HACK: this may be overrun. */
546 check_help_nodes_init();
548 /* need to do something like this or bad things happen */
549 popdown_help_dialog();
551 if (!booted) {
552 log_verbose("Booting help texts");
553 } else {
554 /* free memory allocated last time booted */
555 free_help_texts();
556 log_verbose("Rebooting help texts");
559 filename = fileinfoname(get_data_dirs(), "helpdata.txt");
560 if (!filename) {
561 log_error("Did not read help texts");
562 return;
564 /* after following call filename may be clobbered; use sf->filename instead */
565 if (!(sf = secfile_load(filename, FALSE))) {
566 /* this is now unlikely to happen */
567 log_error("failed reading help-texts from '%s':\n%s", filename,
568 secfile_error());
569 return;
572 sec = secfile_sections_by_name_prefix(sf, "help_");
574 if (NULL != sec) {
575 section_list_iterate(sec, psection) {
576 char help_text_buffer[MAX_LEN_PACKET];
577 const char *sec_name = section_name(psection);
578 const char *gen_str = secfile_lookup_str(sf, "%s.generate", sec_name);
580 if (gen_str) {
581 enum help_page_type current_type = HELP_ANY;
582 int level = strspn(gen_str, " ");
584 gen_str += level;
586 for (i = 2; help_type_names[i]; i++) {
587 if (strcmp(gen_str, help_type_names[i]) == 0) {
588 current_type = i;
589 break;
592 if (current_type == HELP_ANY) {
593 log_error("bad help-generate category \"%s\"", gen_str);
594 continue;
597 if (!booted) {
598 if (current_type == HELP_EXTRA) {
599 size_t ncats;
601 /* Avoid warnings about entries unused on this round,
602 * when the entries in question are valid once help system has been booted */
603 (void) secfile_lookup_str_vec(sf, &ncats,
604 "%s.categories", sec_name);
606 continue; /* on initial boot data tables are empty */
610 /* Note these should really fill in pitem->text from auto-gen
611 data instead of doing it later on the fly, but I don't want
612 to change that now. --dwp
614 char name[2048];
615 struct help_list *category_nodes = help_list_new();
617 switch (current_type) {
618 case HELP_UNIT:
619 unit_type_iterate(punittype) {
620 pitem = new_help_item(current_type);
621 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
622 utype_name_translation(punittype));
623 pitem->topic = fc_strdup(name);
624 pitem->text = fc_strdup("");
625 help_list_append(category_nodes, pitem);
626 } unit_type_iterate_end;
627 break;
628 case HELP_TECH:
629 advance_index_iterate(A_FIRST, advi) {
630 if (valid_advance_by_number(advi)) {
631 pitem = new_help_item(current_type);
632 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
633 advance_name_translation(advance_by_number(advi)));
634 pitem->topic = fc_strdup(name);
635 pitem->text = fc_strdup("");
636 help_list_append(category_nodes, pitem);
638 } advance_index_iterate_end;
639 break;
640 case HELP_TERRAIN:
641 terrain_type_iterate(pterrain) {
642 if (0 != strlen(terrain_rule_name(pterrain))) {
643 pitem = new_help_item(current_type);
644 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
645 terrain_name_translation(pterrain));
646 pitem->topic = fc_strdup(name);
647 pitem->text = fc_strdup("");
648 help_list_append(category_nodes, pitem);
650 } terrain_type_iterate_end;
651 break;
652 case HELP_EXTRA:
654 const char **cats;
655 size_t ncats;
656 cats = secfile_lookup_str_vec(sf, &ncats,
657 "%s.categories", sec_name);
658 extra_type_iterate(pextra) {
659 /* If categories not specified, don't filter */
660 if (cats) {
661 bool include = FALSE;
662 const char *cat = extra_category_name(pextra->category);
663 int ci;
665 for (ci = 0; ci < ncats; ci++) {
666 if (fc_strcasecmp(cats[ci], cat) == 0) {
667 include = TRUE;
668 break;
671 if (!include) {
672 continue;
675 pitem = new_help_item(current_type);
676 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
677 extra_name_translation(pextra));
678 pitem->topic = fc_strdup(name);
679 pitem->text = fc_strdup("");
680 help_list_append(category_nodes, pitem);
681 } extra_type_iterate_end;
682 FC_FREE(cats);
684 break;
685 case HELP_GOODS:
686 goods_type_iterate(pgood) {
687 pitem = new_help_item(current_type);
688 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
689 goods_name_translation(pgood));
690 pitem->topic = fc_strdup(name);
691 pitem->text = fc_strdup("");
692 help_list_append(category_nodes, pitem);
693 } goods_type_iterate_end;
694 break;
695 case HELP_SPECIALIST:
696 specialist_type_iterate(sp) {
697 struct specialist *pspec = specialist_by_number(sp);
699 pitem = new_help_item(current_type);
700 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
701 specialist_plural_translation(pspec));
702 pitem->topic = fc_strdup(name);
703 pitem->text = fc_strdup("");
704 help_list_append(category_nodes, pitem);
705 } specialist_type_iterate_end;
706 break;
707 case HELP_GOVERNMENT:
708 governments_iterate(gov) {
709 pitem = new_help_item(current_type);
710 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
711 government_name_translation(gov));
712 pitem->topic = fc_strdup(name);
713 pitem->text = fc_strdup("");
714 help_list_append(category_nodes, pitem);
715 } governments_iterate_end;
716 break;
717 case HELP_IMPROVEMENT:
718 improvement_iterate(pimprove) {
719 if (valid_improvement(pimprove) && !is_great_wonder(pimprove)) {
720 pitem = new_help_item(current_type);
721 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
722 improvement_name_translation(pimprove));
723 pitem->topic = fc_strdup(name);
724 pitem->text = fc_strdup("");
725 help_list_append(category_nodes, pitem);
727 } improvement_iterate_end;
728 break;
729 case HELP_WONDER:
730 improvement_iterate(pimprove) {
731 if (valid_improvement(pimprove) && is_great_wonder(pimprove)) {
732 pitem = new_help_item(current_type);
733 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
734 improvement_name_translation(pimprove));
735 pitem->topic = fc_strdup(name);
736 pitem->text = fc_strdup("");
737 help_list_append(category_nodes, pitem);
739 } improvement_iterate_end;
740 break;
741 case HELP_RULESET:
743 int desc_len;
744 int len;
746 pitem = new_help_item(HELP_RULESET);
747 /* pitem->topic = fc_strdup(_(game.control.name)); */
748 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
749 Q_(HELP_RULESET_ITEM));
750 pitem->topic = fc_strdup(name);
751 if (game.ruleset_description != NULL) {
752 desc_len = strlen("\n\n") + strlen(game.ruleset_description);
753 } else {
754 desc_len = 0;
756 if (game.ruleset_summary != NULL) {
757 if (game.control.version[0] != '\0') {
758 len = strlen(_(game.control.name))
759 + strlen(" ")
760 + strlen(game.control.version)
761 + strlen("\n\n")
762 + strlen(_(game.ruleset_summary))
763 + 1;
765 pitem->text = fc_malloc(len + desc_len);
766 fc_snprintf(pitem->text, len, "%s %s\n\n%s",
767 _(game.control.name), game.control.version,
768 _(game.ruleset_summary));
769 } else {
770 len = strlen(_(game.control.name))
771 + strlen("\n\n")
772 + strlen(_(game.ruleset_summary))
773 + 1;
775 pitem->text = fc_malloc(len + desc_len);
776 fc_snprintf(pitem->text, len, "%s\n\n%s",
777 _(game.control.name), _(game.ruleset_summary));
779 } else {
780 const char *nodesc = _("Current ruleset contains no summary.");
782 if (game.control.version[0] != '\0') {
783 len = strlen(_(game.control.name))
784 + strlen(" ")
785 + strlen(game.control.version)
786 + strlen("\n\n")
787 + strlen(nodesc)
788 + 1;
790 pitem->text = fc_malloc(len + desc_len);
791 fc_snprintf(pitem->text, len, "%s %s\n\n%s",
792 _(game.control.name), game.control.version,
793 nodesc);
794 } else {
795 len = strlen(_(game.control.name))
796 + strlen("\n\n")
797 + strlen(nodesc)
798 + 1;
800 pitem->text = fc_malloc(len + desc_len);
801 fc_snprintf(pitem->text, len, "%s\n\n%s",
802 _(game.control.name),
803 nodesc);
806 if (game.ruleset_description != NULL) {
807 fc_strlcat(pitem->text, "\n\n", len + desc_len);
808 fc_strlcat(pitem->text, game.ruleset_description, len + desc_len);
810 help_list_append(help_nodes, pitem);
812 break;
813 case HELP_TILESET:
815 int desc_len;
816 int len;
817 const char *ts_name = tileset_name_get(tileset);
818 const char *version = tileset_version(tileset);
819 const char *summary = tileset_summary(tileset);
820 const char *description = tileset_description(tileset);
822 pitem = new_help_item(HELP_TILESET);
823 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
824 Q_(HELP_TILESET_ITEM));
825 pitem->topic = fc_strdup(name);
826 if (description != NULL) {
827 desc_len = strlen("\n\n") + strlen(description);
828 } else {
829 desc_len = 0;
831 if (summary != NULL) {
832 if (version[0] != '\0') {
833 len = strlen(_(ts_name))
834 + strlen(" ")
835 + strlen(version)
836 + strlen("\n\n")
837 + strlen(_(summary))
838 + 1;
840 pitem->text = fc_malloc(len + desc_len);
841 fc_snprintf(pitem->text, len, "%s %s\n\n%s",
842 _(ts_name), version, _(summary));
843 } else {
844 len = strlen(_(ts_name))
845 + strlen("\n\n")
846 + strlen(_(summary))
847 + 1;
849 pitem->text = fc_malloc(len + desc_len);
850 fc_snprintf(pitem->text, len, "%s\n\n%s",
851 _(ts_name), _(summary));
853 } else {
854 const char *nodesc = _("Current tileset contains no summary.");
856 if (version[0] != '\0') {
857 len = strlen(_(ts_name))
858 + strlen(" ")
859 + strlen(version)
860 + strlen("\n\n")
861 + strlen(nodesc)
862 + 1;
864 pitem->text = fc_malloc(len + desc_len);
865 fc_snprintf(pitem->text, len, "%s %s\n\n%s",
866 _(ts_name), version,
867 nodesc);
868 } else {
869 len = strlen(_(ts_name))
870 + strlen("\n\n")
871 + strlen(nodesc)
872 + 1;
874 pitem->text = fc_malloc(len + desc_len);
875 fc_snprintf(pitem->text, len, "%s\n\n%s",
876 _(ts_name),
877 nodesc);
880 if (description != NULL) {
881 fc_strlcat(pitem->text, "\n\n", len + desc_len);
882 fc_strlcat(pitem->text, description, len + desc_len);
884 help_list_append(help_nodes, pitem);
886 break;
887 case HELP_NATIONS:
888 nations_iterate(pnation) {
889 if (client_state() < C_S_RUNNING
890 || show_help_for_nation(pnation)) {
891 pitem = new_help_item(current_type);
892 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
893 nation_plural_translation(pnation));
894 pitem->topic = fc_strdup(name);
895 pitem->text = fc_strdup("");
896 help_list_append(category_nodes, pitem);
898 } nations_iterate_end;
899 break;
900 case HELP_MULTIPLIER:
901 multipliers_iterate(pmul) {
902 help_text_buffer[0] = '\0';
903 pitem = new_help_item(current_type);
904 fc_snprintf(name, sizeof(name), "%*s%s", level, "",
905 name_translation_get(&pmul->name));
906 pitem->topic = fc_strdup(name);
907 if (pmul->helptext) {
908 const char *sep = "";
909 strvec_iterate(pmul->helptext, text) {
910 cat_snprintf(help_text_buffer, sizeof(help_text_buffer),
911 "%s%s", sep, text);
912 sep = "\n\n";
913 } strvec_iterate_end;
915 pitem->text = fc_strdup(help_text_buffer);
916 help_list_append(help_nodes, pitem);
917 } multipliers_iterate_end;
918 break;
919 default:
920 log_error("Bad current_type: %d.", current_type);
921 break;
923 help_list_sort(category_nodes, help_item_compar);
924 help_list_iterate(category_nodes, ptmp) {
925 help_list_append(help_nodes, ptmp);
926 } help_list_iterate_end;
927 help_list_destroy(category_nodes);
928 continue;
932 /* It wasn't a "generate" node: */
934 pitem = new_help_item(HELP_TEXT);
935 pitem->topic = fc_strdup(Q_(secfile_lookup_str(sf, "%s.name",
936 sec_name)));
938 paras = secfile_lookup_str_vec(sf, &npara, "%s.text", sec_name);
940 long_buffer[0] = '\0';
941 for (i = 0; i < npara; i++) {
942 bool inserted;
943 const char *para = paras[i];
945 if (strncmp(para, "$", 1) == 0) {
946 inserted =
947 insert_generated_text(long_buffer, sizeof(long_buffer), para+1);
948 } else {
949 sz_strlcat(long_buffer, _(para));
950 inserted = TRUE;
952 if (inserted && i != npara - 1) {
953 sz_strlcat(long_buffer, "\n\n");
956 free(paras);
957 paras = NULL;
958 pitem->text=fc_strdup(long_buffer);
959 help_list_append(help_nodes, pitem);
960 } section_list_iterate_end;
962 section_list_destroy(sec);
965 secfile_check_unused(sf);
966 secfile_destroy(sf);
967 booted = TRUE;
968 log_verbose("Booted help texts ok");
971 /****************************************************************
972 The following few functions are essentially wrappers for the
973 help_nodes help_list. This allows us to avoid exporting the
974 help_list, and instead only access it through a controlled
975 interface.
976 *****************************************************************/
978 /****************************************************************
979 Number of help items.
980 *****************************************************************/
981 int num_help_items(void)
983 check_help_nodes_init();
984 return help_list_size(help_nodes);
987 /****************************************************************
988 Return pointer to given help_item.
989 Returns NULL for 1 past end.
990 Returns NULL and prints error message for other out-of bounds.
991 *****************************************************************/
992 const struct help_item *get_help_item(int pos)
994 int size;
996 check_help_nodes_init();
997 size = help_list_size(help_nodes);
998 if (pos < 0 || pos > size) {
999 log_error("Bad index %d to get_help_item (size %d)", pos, size);
1000 return NULL;
1002 if (pos == size) {
1003 return NULL;
1005 return help_list_get(help_nodes, pos);
1008 /****************************************************************
1009 Find help item by name and type.
1010 Returns help item, and sets (*pos) to position in list.
1011 If no item, returns pointer to static internal item with
1012 some faked data, and sets (*pos) to -1.
1013 *****************************************************************/
1014 const struct help_item*
1015 get_help_item_spec(const char *name, enum help_page_type htype, int *pos)
1017 int idx;
1018 const struct help_item *pitem = NULL;
1019 static struct help_item vitem; /* v = virtual */
1020 static char vtopic[128];
1021 static char vtext[256];
1023 check_help_nodes_init();
1024 idx = 0;
1025 help_list_iterate(help_nodes, ptmp) {
1026 char *p = ptmp->topic;
1028 while (*p == ' ') {
1029 p++;
1031 if (strcmp(name, p) == 0 && (htype == HELP_ANY || htype == ptmp->type)) {
1032 pitem = ptmp;
1033 break;
1035 idx++;
1037 help_list_iterate_end;
1039 if (!pitem) {
1040 idx = -1;
1041 vitem.topic = vtopic;
1042 sz_strlcpy(vtopic, name);
1043 vitem.text = vtext;
1044 if (htype == HELP_ANY || htype == HELP_TEXT) {
1045 fc_snprintf(vtext, sizeof(vtext),
1046 _("Sorry, no help topic for %s.\n"), vitem.topic);
1047 vitem.type = HELP_TEXT;
1048 } else {
1049 fc_snprintf(vtext, sizeof(vtext),
1050 _("Sorry, no help topic for %s.\n"
1051 "This page was auto-generated.\n\n"),
1052 vitem.topic);
1053 vitem.type = htype;
1055 pitem = &vitem;
1057 *pos = idx;
1058 return pitem;
1061 /****************************************************************
1062 Start iterating through help items;
1063 that is, reset iterator to start position.
1064 (Could iterate using get_help_item(), but that would be
1065 less efficient due to scanning to find pos.)
1066 *****************************************************************/
1067 void help_iter_start(void)
1069 check_help_nodes_init();
1070 help_nodes_iterator = help_list_head(help_nodes);
1073 /****************************************************************
1074 Returns next help item; after help_iter_start(), this is
1075 the first item. At end, returns NULL.
1076 *****************************************************************/
1077 const struct help_item *help_iter_next(void)
1079 const struct help_item *pitem;
1081 check_help_nodes_init();
1082 pitem = help_list_link_data(help_nodes_iterator);
1083 if (pitem) {
1084 help_nodes_iterator = help_list_link_next(help_nodes_iterator);
1087 return pitem;
1091 /****************************************************************
1092 FIXME:
1093 Also, in principle these could be auto-generated once, inserted
1094 into pitem->text, and then don't need to keep re-generating them.
1095 Only thing to be careful of would be changeable data, but don't
1096 have that here (for ruleset change or spacerace change must
1097 re-boot helptexts anyway). Eg, genuinely dynamic information
1098 which could be useful would be if help system said which wonders
1099 have been built (or are being built and by who/where?)
1100 *****************************************************************/
1102 /**************************************************************************
1103 Write dynamic text for buildings (including wonders). This includes
1104 the ruleset helptext as well as any automatically generated text.
1106 pplayer may be NULL.
1107 user_text, if non-NULL, will be appended to the text.
1108 **************************************************************************/
1109 char *helptext_building(char *buf, size_t bufsz, struct player *pplayer,
1110 const char *user_text, struct impr_type *pimprove)
1112 bool reqs = FALSE;
1113 struct universal source = {
1114 .kind = VUT_IMPROVEMENT,
1115 .value = {.building = pimprove}
1118 fc_assert_ret_val(NULL != buf && 0 < bufsz, NULL);
1119 buf[0] = '\0';
1121 if (NULL == pimprove) {
1122 return buf;
1125 if (NULL != pimprove->helptext) {
1126 strvec_iterate(pimprove->helptext, text) {
1127 cat_snprintf(buf, bufsz, "%s\n\n", _(text));
1128 } strvec_iterate_end;
1131 /* Add requirement text for improvement itself */
1132 requirement_vector_iterate(&pimprove->reqs, preq) {
1133 if (req_text_insert_nl(buf, bufsz, pplayer, preq, VERB_DEFAULT)) {
1134 reqs = TRUE;
1136 } requirement_vector_iterate_end;
1137 if (reqs) {
1138 fc_strlcat(buf, "\n", bufsz);
1141 requirement_vector_iterate(&pimprove->obsolete_by, pobs) {
1142 if (VUT_ADVANCE == pobs->source.kind && pobs->present) {
1143 cat_snprintf(buf, bufsz,
1144 _("* The discovery of %s will make %s obsolete.\n"),
1145 advance_name_translation(pobs->source.value.advance),
1146 improvement_name_translation(pimprove));
1148 if (VUT_IMPROVEMENT == pobs->source.kind && pobs->present) {
1149 cat_snprintf(buf, bufsz,
1150 /* TRANS: both %s are improvement names */
1151 _("* The presence of %s in the city will make %s "
1152 "obsolete.\n"),
1153 improvement_name_translation(pobs->source.value.building),
1154 improvement_name_translation(pimprove));
1156 } requirement_vector_iterate_end;
1158 if (is_small_wonder(pimprove)) {
1159 cat_snprintf(buf, bufsz,
1160 _("* A 'small wonder': at most one of your cities may "
1161 "possess this improvement.\n"));
1163 /* (Great wonders are in their own help section explaining their
1164 * uniqueness, so we don't mention it here.) */
1166 if (building_has_effect(pimprove, EFT_ENABLE_NUKE)
1167 && num_role_units(action_id_get_role(ACTION_NUKE)) > 0) {
1168 struct unit_type *u = get_role_unit(action_id_get_role(ACTION_NUKE), 0);
1170 cat_snprintf(buf, bufsz,
1171 /* TRANS: 'Allows all players with knowledge of atomic
1172 * power to build nuclear units.' */
1173 _("* Allows all players with knowledge of %s "
1174 "to build %s units.\n"),
1175 advance_name_translation(u->require_advance),
1176 utype_name_translation(u));
1179 insert_allows(&source, buf + strlen(buf), bufsz - strlen(buf));
1181 unit_type_iterate(u) {
1182 if (u->need_improvement == pimprove) {
1183 if (A_NEVER != u->require_advance) {
1184 cat_snprintf(buf, bufsz, _("* Allows %s (with %s).\n"),
1185 utype_name_translation(u),
1186 advance_name_translation(u->require_advance));
1187 } else {
1188 cat_snprintf(buf, bufsz, _("* Allows %s.\n"),
1189 utype_name_translation(u));
1192 } unit_type_iterate_end;
1195 int i;
1197 for (i = 0; i < MAX_NUM_BUILDING_LIST; i++) {
1198 Impr_type_id n = game.rgame.global_init_buildings[i];
1199 if (n == B_LAST) {
1200 break;
1201 } else if (improvement_by_number(n) == pimprove) {
1202 cat_snprintf(buf, bufsz,
1203 _("* All players start with this improvement in their "
1204 "first city.\n"));
1205 break;
1210 /* Assume no-one will set the same building in both global and nation
1211 * init_buildings... */
1212 nations_iterate(pnation) {
1213 int i;
1215 /* Avoid mentioning nations not in current set. */
1216 if (!show_help_for_nation(pnation)) {
1217 continue;
1219 for (i = 0; i < MAX_NUM_BUILDING_LIST; i++) {
1220 Impr_type_id n = pnation->init_buildings[i];
1221 if (n == B_LAST) {
1222 break;
1223 } else if (improvement_by_number(n) == pimprove) {
1224 cat_snprintf(buf, bufsz,
1225 /* TRANS: %s is a nation plural */
1226 _("* The %s start with this improvement in their "
1227 "first city.\n"), nation_plural_translation(pnation));
1228 break;
1231 } nations_iterate_end;
1233 if (improvement_has_flag(pimprove, IF_SAVE_SMALL_WONDER)) {
1234 cat_snprintf(buf, bufsz,
1235 /* TRANS: don't translate 'savepalace' */
1236 _("* If you lose the city containing this improvement, "
1237 "it will be rebuilt for free in another of your cities "
1238 "(if the 'savepalace' server setting is enabled).\n"));
1241 if (user_text && user_text[0] != '\0') {
1242 cat_snprintf(buf, bufsz, "\n\n%s", user_text);
1244 return buf;
1247 /****************************************************************
1248 Is unit type ever able to build an extra
1249 *****************************************************************/
1250 static bool help_is_extra_buildable(struct extra_type *pextra,
1251 struct unit_type *ptype)
1253 if (!pextra->buildable) {
1254 return FALSE;
1257 return are_reqs_active(NULL, NULL, NULL, NULL, NULL,
1258 NULL, ptype, NULL, NULL, NULL, &pextra->reqs,
1259 RPT_POSSIBLE);
1262 /****************************************************************
1263 Is unit type ever able to clean out an extra
1264 *****************************************************************/
1265 static bool help_is_extra_cleanable(struct extra_type *pextra,
1266 struct unit_type *ptype)
1268 return are_reqs_active(NULL, NULL, NULL, NULL, NULL,
1269 NULL, ptype, NULL, NULL, NULL, &pextra->rmreqs,
1270 RPT_POSSIBLE);
1273 /****************************************************************
1274 Append misc dynamic text for units.
1275 Transport capacity, unit flags, fuel.
1277 pplayer may be NULL.
1278 *****************************************************************/
1279 char *helptext_unit(char *buf, size_t bufsz, struct player *pplayer,
1280 const char *user_text, struct unit_type *utype)
1282 bool has_vet_levels;
1283 int flagid;
1284 struct unit_class *pclass;
1285 int fuel;
1287 fc_assert_ret_val(NULL != buf && 0 < bufsz && NULL != user_text, NULL);
1289 if (!utype) {
1290 log_error("Unknown unit!");
1291 fc_strlcpy(buf, user_text, bufsz);
1292 return buf;
1295 has_vet_levels = utype_veteran_levels(utype) > 1;
1297 buf[0] = '\0';
1299 pclass = utype_class(utype);
1300 cat_snprintf(buf, bufsz,
1301 _("* Belongs to %s unit class."),
1302 uclass_name_translation(pclass));
1303 if (NULL != pclass->helptext) {
1304 strvec_iterate(pclass->helptext, text) {
1305 cat_snprintf(buf, bufsz, "\n%s\n", _(text));
1306 } strvec_iterate_end;
1307 } else {
1308 CATLSTR(buf, bufsz, "\n");
1310 if (uclass_has_flag(pclass, UCF_CAN_OCCUPY_CITY)
1311 && !utype_has_flag(utype, UTYF_CIVILIAN)) {
1312 CATLSTR(buf, bufsz, _(" * Can occupy empty enemy cities.\n"));
1314 if (!uclass_has_flag(pclass, UCF_TERRAIN_SPEED)) {
1315 CATLSTR(buf, bufsz, _(" * Speed is not affected by terrain.\n"));
1317 if (!uclass_has_flag(pclass, UCF_TERRAIN_DEFENSE)) {
1318 CATLSTR(buf, bufsz, _(" * Does not get defense bonuses from terrain.\n"));
1320 if (!uclass_has_flag(pclass, UCF_ZOC)) {
1321 CATLSTR(buf, bufsz, _(" * Not subject to zones of control.\n"));
1322 } else if (!utype_has_flag(utype, UTYF_IGZOC)) {
1323 CATLSTR(buf, bufsz, _(" * Subject to zones of control.\n"));
1325 if (uclass_has_flag(pclass, UCF_DAMAGE_SLOWS)) {
1326 CATLSTR(buf, bufsz, _(" * Slowed down while damaged.\n"));
1328 if (uclass_has_flag(pclass, UCF_MISSILE)) {
1329 CATLSTR(buf, bufsz, _(" * Gets used up in making an attack.\n"));
1331 if (uclass_has_flag(pclass, UCF_CAN_FORTIFY)
1332 && !utype_has_flag(utype, UTYF_CANT_FORTIFY)) {
1333 if (utype->defense_strength > 0) {
1334 CATLSTR(buf, bufsz,
1335 /* xgettext:no-c-format */
1336 _(" * Gets a 50% defensive bonus while in cities.\n"));
1337 CATLSTR(buf, bufsz,
1338 /* xgettext:no-c-format */
1339 _(" * May fortify, granting a 50% defensive bonus when not in "
1340 "a city.\n"));
1341 } else {
1342 CATLSTR(buf, bufsz,
1343 _(" * May fortify to stay put.\n"));
1346 if (uclass_has_flag(pclass, UCF_UNREACHABLE)) {
1347 CATLSTR(buf, bufsz,
1348 _(" * Is unreachable. Most units cannot attack this one.\n"));
1350 if (uclass_has_flag(pclass, UCF_CAN_PILLAGE)) {
1351 CATLSTR(buf, bufsz,
1352 _(" * Can pillage tile improvements.\n"));
1354 if (uclass_has_flag(pclass, UCF_DOESNT_OCCUPY_TILE)
1355 && !utype_has_flag(utype, UTYF_CIVILIAN)) {
1356 CATLSTR(buf, bufsz,
1357 _(" * Doesn't prevent enemy cities from working the tile it's on.\n"));
1359 if (can_attack_non_native(utype)) {
1360 CATLSTR(buf, bufsz,
1361 _(" * Can attack units on non-native tiles.\n"));
1363 for (flagid = UCF_USER_FLAG_1; flagid <= UCF_LAST_USER_FLAG; flagid++) {
1364 if (uclass_has_flag(pclass, flagid)) {
1365 const char *helptxt = unit_class_flag_helptxt(flagid);
1367 if (helptxt != NULL) {
1368 CATLSTR(buf, bufsz, Q_("?bullet: * "));
1369 CATLSTR(buf, bufsz, _(helptxt));
1370 CATLSTR(buf, bufsz, "\n");
1375 /* The unit's combat bonuses. Won't mention that another unit type has a
1376 * combat bonus against this unit type. Doesn't handle complex cases like
1377 * when a unit type has multiple combat bonuses of the same kind. */
1378 combat_bonus_list_iterate(utype->bonuses, cbonus) {
1379 const char *against[utype_count()];
1380 int targets = 0;
1382 if (cbonus->quiet) {
1383 /* Handled in the help text of the ruleset. */
1384 continue;
1387 /* Find the unit types of the bonus targets. */
1388 unit_type_iterate(utype2) {
1389 if (utype_has_flag(utype2, cbonus->flag)) {
1390 against[targets++] = utype_name_translation(utype2);
1392 } unit_type_iterate_end;
1394 if (targets > 0) {
1395 struct astring list = ASTRING_INIT;
1397 switch (cbonus->type) {
1398 case CBONUS_DEFENSE_MULTIPLIER:
1399 cat_snprintf(buf, bufsz,
1400 /* TRANS: multipied by ... or-list of unit types */
1401 _("* %dx defense bonus if attacked by %s.\n"),
1402 cbonus->value + 1,
1403 astr_build_or_list(&list, against, targets));
1404 break;
1405 case CBONUS_DEFENSE_DIVIDER:
1406 cat_snprintf(buf, bufsz,
1407 /* TRANS: defense divider ... or-list of unit types */
1408 _("* reduces target's defense to 1 / %d when "
1409 "attacking %s.\n"),
1410 cbonus->value + 1,
1411 astr_build_or_list(&list, against, targets));
1412 break;
1413 case CBONUS_FIREPOWER1:
1414 cat_snprintf(buf, bufsz,
1415 /* TRANS: or-list of unit types */
1416 _("* reduces target's fire power to 1 when "
1417 "attacking %s.\n"),
1418 astr_build_and_list(&list, against, targets));
1419 break;
1422 astr_free(&list);
1424 } combat_bonus_list_iterate_end;
1426 if (utype->need_improvement) {
1427 cat_snprintf(buf, bufsz,
1428 _("* Can only be built if there is %s in the city.\n"),
1429 improvement_name_translation(utype->need_improvement));
1432 if (utype->need_government) {
1433 cat_snprintf(buf, bufsz,
1434 _("* Can only be built with %s as government.\n"),
1435 government_name_translation(utype->need_government));
1438 if (utype_has_flag(utype, UTYF_CANESCAPE)) {
1439 CATLSTR(buf, bufsz, _("* Can escape once stack defender is lost.\n"));
1441 if (utype_has_flag(utype, UTYF_CANKILLESCAPING)) {
1442 CATLSTR(buf, bufsz, _("* Can pursue escaping units and kill them.\n"));
1445 if (utype_has_flag(utype, UTYF_NOBUILD)) {
1446 CATLSTR(buf, bufsz, _("* May not be built in cities.\n"));
1448 if (utype_has_flag(utype, UTYF_BARBARIAN_ONLY)) {
1449 CATLSTR(buf, bufsz, _("* Only barbarians may build this.\n"));
1451 if (utype_has_flag(utype, UTYF_NEWCITY_GAMES_ONLY)) {
1452 CATLSTR(buf, bufsz, _("* Can only be built in games where new cities "
1453 "are allowed.\n"));
1454 if (game.scenario.prevent_new_cities) {
1455 CATLSTR(buf, bufsz, _(" - New cities are not allowed in the current "
1456 "game.\n"));
1457 } else {
1458 CATLSTR(buf, bufsz, _(" - New cities are allowed in the current "
1459 "game.\n"));
1462 nations_iterate(pnation) {
1463 int i, count = 0;
1465 /* Avoid mentioning nations not in current set. */
1466 if (!show_help_for_nation(pnation)) {
1467 continue;
1469 for (i = 0; i < MAX_NUM_UNIT_LIST; i++) {
1470 if (!pnation->init_units[i]) {
1471 break;
1472 } else if (pnation->init_units[i] == utype) {
1473 count++;
1476 if (count > 0) {
1477 cat_snprintf(buf, bufsz,
1478 /* TRANS: %s is a nation plural */
1479 PL_("* The %s start the game with %d of these units.\n",
1480 "* The %s start the game with %d of these units.\n",
1481 count),
1482 nation_plural_translation(pnation), count);
1484 } nations_iterate_end;
1486 const char *types[utype_count()];
1487 int i = 0;
1488 unit_type_iterate(utype2) {
1489 if (utype2->converted_to == utype) {
1490 types[i++] = utype_name_translation(utype2);
1492 } unit_type_iterate_end;
1493 if (i > 0) {
1494 struct astring list = ASTRING_INIT;
1495 astr_build_or_list(&list, types, i);
1496 cat_snprintf(buf, bufsz,
1497 /* TRANS: %s is a list of unit types separated by "or". */
1498 _("* May be obtained by conversion of %s.\n"),
1499 astr_str(&list));
1500 astr_free(&list);
1503 if (NULL != utype->converted_to) {
1504 cat_snprintf(buf, bufsz,
1505 /* TRANS: %s is a unit type. "MP" = movement points. */
1506 PL_("* May be converted into %s (takes %d MP).\n",
1507 "* May be converted into %s (takes %d MP).\n",
1508 utype->convert_time),
1509 utype_name_translation(utype->converted_to),
1510 utype->convert_time);
1512 if (utype_has_flag(utype, UTYF_NOHOME)) {
1513 CATLSTR(buf, bufsz, _("* Never has a home city.\n"));
1515 if (utype_has_flag(utype, UTYF_GAMELOSS)) {
1516 CATLSTR(buf, bufsz, _("* Losing this unit will lose you the game!\n"));
1518 if (utype_has_flag(utype, UTYF_UNIQUE)) {
1519 CATLSTR(buf, bufsz,
1520 _("* Each player may only have one of this type of unit.\n"));
1522 for (flagid = UTYF_USER_FLAG_1 ; flagid <= UTYF_LAST_USER_FLAG; flagid++) {
1523 if (utype_has_flag(utype, flagid)) {
1524 const char *helptxt = unit_type_flag_helptxt(flagid);
1526 if (helptxt != NULL) {
1527 CATLSTR(buf, bufsz, Q_("?bullet:* "));
1528 CATLSTR(buf, bufsz, _(helptxt));
1529 CATLSTR(buf, bufsz, "\n");
1533 if (utype->pop_cost > 0) {
1534 cat_snprintf(buf, bufsz,
1535 PL_("* Costs %d population to build.\n",
1536 "* Costs %d population to build.\n", utype->pop_cost),
1537 utype->pop_cost);
1539 if (0 < utype->transport_capacity) {
1540 const char *classes[uclass_count()];
1541 int i = 0;
1542 struct astring list = ASTRING_INIT;
1544 unit_class_iterate(uclass) {
1545 if (can_unit_type_transport(utype, uclass)) {
1546 classes[i++] = uclass_name_translation(uclass);
1548 } unit_class_iterate_end;
1549 astr_build_or_list(&list, classes, i);
1551 cat_snprintf(buf, bufsz,
1552 /* TRANS: %s is a list of unit classes separated by "or". */
1553 PL_("* Can carry and refuel %d %s unit.\n",
1554 "* Can carry and refuel up to %d %s units.\n",
1555 utype->transport_capacity),
1556 utype->transport_capacity, astr_str(&list));
1557 astr_free(&list);
1558 if (uclass_has_flag(utype_class(utype), UCF_UNREACHABLE)) {
1559 /* Document restrictions on when units can load/unload */
1560 bool has_restricted_load = FALSE, has_unrestricted_load = FALSE,
1561 has_restricted_unload = FALSE, has_unrestricted_unload = FALSE;
1562 unit_type_iterate(pcargo) {
1563 if (can_unit_type_transport(utype, utype_class(pcargo))) {
1564 if (utype_can_freely_load(pcargo, utype)) {
1565 has_unrestricted_load = TRUE;
1566 } else {
1567 has_restricted_load = TRUE;
1569 if (utype_can_freely_unload(pcargo, utype)) {
1570 has_unrestricted_unload = TRUE;
1571 } else {
1572 has_restricted_unload = TRUE;
1575 } unit_type_iterate_end;
1576 if (has_restricted_load) {
1577 if (has_unrestricted_load) {
1578 /* At least one type of cargo can load onto us freely.
1579 * The specific exceptions will be documented in cargo help. */
1580 CATLSTR(buf, bufsz,
1581 _(" * Some cargo cannot be loaded except in a city or a "
1582 "base native to this transport.\n"));
1583 } else {
1584 /* No exceptions */
1585 CATLSTR(buf, bufsz,
1586 _(" * Cargo cannot be loaded except in a city or a "
1587 "base native to this transport.\n"));
1589 } /* else, no restricted cargo exists; keep quiet */
1590 if (has_restricted_unload) {
1591 if (has_unrestricted_unload) {
1592 /* At least one type of cargo can unload from us freely. */
1593 CATLSTR(buf, bufsz,
1594 _(" * Some cargo cannot be unloaded except in a city or a "
1595 "base native to this transport.\n"));
1596 } else {
1597 /* No exceptions */
1598 CATLSTR(buf, bufsz,
1599 _(" * Cargo cannot be unloaded except in a city or a "
1600 "base native to this transport.\n"));
1602 } /* else, no restricted cargo exists; keep quiet */
1605 if (utype_has_flag(utype, UTYF_COAST_STRICT)) {
1606 CATLSTR(buf, bufsz, _("* Must stay next to coast.\n"));
1609 /* Document exceptions to embark/disembark restrictions that we
1610 * have as cargo. */
1611 bv_unit_classes embarks, disembarks;
1612 BV_CLR_ALL(embarks);
1613 BV_CLR_ALL(disembarks);
1614 /* Determine which of our transport classes have restrictions in the first
1615 * place (that is, contain at least one transport which carries at least
1616 * one type of cargo which is restricted).
1617 * We'll suppress output for classes not in this set, since this cargo
1618 * type is not behaving exceptionally in such cases. */
1619 unit_type_iterate(utrans) {
1620 const Unit_Class_id trans_class = uclass_index(utype_class(utrans));
1621 /* Don't waste time repeating checks on classes we've already checked,
1622 * or weren't under consideration in the first place */
1623 if (!BV_ISSET(embarks, trans_class)
1624 && BV_ISSET(utype->embarks, trans_class)) {
1625 unit_type_iterate(other_cargo) {
1626 if (can_unit_type_transport(utrans, utype_class(other_cargo))
1627 && !utype_can_freely_load(other_cargo, utrans)) {
1628 /* At least one load restriction in transport class, which
1629 * we aren't subject to */
1630 BV_SET(embarks, trans_class);
1632 } unit_type_iterate_end; /* cargo */
1634 if (!BV_ISSET(disembarks, trans_class)
1635 && BV_ISSET(utype->disembarks, trans_class)) {
1636 unit_type_iterate(other_cargo) {
1637 if (can_unit_type_transport(utrans, utype_class(other_cargo))
1638 && !utype_can_freely_unload(other_cargo, utrans)) {
1639 /* At least one load restriction in transport class, which
1640 * we aren't subject to */
1641 BV_SET(disembarks, trans_class);
1643 } unit_type_iterate_end; /* cargo */
1645 } unit_class_iterate_end; /* transports */
1647 if (BV_ISSET_ANY(embarks)) {
1648 /* Build list of embark exceptions */
1649 const char *eclasses[uclass_count()];
1650 int i = 0;
1651 struct astring elist = ASTRING_INIT;
1653 unit_class_iterate(uclass) {
1654 if (BV_ISSET(embarks, uclass_index(uclass))) {
1655 eclasses[i++] = uclass_name_translation(uclass);
1657 } unit_class_iterate_end;
1658 astr_build_or_list(&elist, eclasses, i);
1659 if (BV_ARE_EQUAL(embarks, disembarks)) {
1660 /* A common case: the list of disembark exceptions is identical */
1661 cat_snprintf(buf, bufsz,
1662 /* TRANS: %s is a list of unit classes separated
1663 * by "or". */
1664 _("* May load onto and unload from %s transports even "
1665 "when underway.\n"),
1666 astr_str(&elist));
1667 } else {
1668 cat_snprintf(buf, bufsz,
1669 /* TRANS: %s is a list of unit classes separated
1670 * by "or". */
1671 _("* May load onto %s transports even when underway.\n"),
1672 astr_str(&elist));
1674 astr_free(&elist);
1676 if (BV_ISSET_ANY(disembarks) && !BV_ARE_EQUAL(embarks, disembarks)) {
1677 /* Build list of disembark exceptions (if different from embarking) */
1678 const char *dclasses[uclass_count()];
1679 int i = 0;
1680 struct astring dlist = ASTRING_INIT;
1682 unit_class_iterate(uclass) {
1683 if (BV_ISSET(disembarks, uclass_index(uclass))) {
1684 dclasses[i++] = uclass_name_translation(uclass);
1686 } unit_class_iterate_end;
1687 astr_build_or_list(&dlist, dclasses, i);
1688 cat_snprintf(buf, bufsz,
1689 /* TRANS: %s is a list of unit classes separated
1690 * by "or". */
1691 _("* May unload from %s transports even when underway.\n"),
1692 astr_str(&dlist));
1693 astr_free(&dlist);
1696 if (utype_has_flag(utype, UTYF_SETTLERS)) {
1697 struct universal for_utype = { .kind = VUT_UTYPE, .value = { .utype = utype }};
1698 struct astring extras_and = ASTRING_INIT;
1699 struct strvec *extras_vec = strvec_new();
1701 /* Roads, rail, mines, irrigation. */
1702 extra_type_by_cause_iterate(EC_ROAD, pextra) {
1703 if (help_is_extra_buildable(pextra, utype)) {
1704 strvec_append(extras_vec, extra_name_translation(pextra));
1706 } extra_type_by_cause_iterate_end;
1707 if (strvec_size(extras_vec) > 0) {
1708 strvec_to_and_list(extras_vec, &extras_and);
1709 /* TRANS: %s is list of extra types separated by ',' and 'and' */
1710 cat_snprintf(buf, bufsz, _("* Can build %s on tiles.\n"),
1711 astr_str(&extras_and));
1712 strvec_clear(extras_vec);
1715 if (effect_cumulative_max(EFT_MINING_POSSIBLE, &for_utype) > 0) {
1716 extra_type_by_cause_iterate(EC_MINE, pextra) {
1717 if (help_is_extra_buildable(pextra, utype)) {
1718 strvec_append(extras_vec, extra_name_translation(pextra));
1720 } extra_type_by_cause_iterate_end;
1722 if (strvec_size(extras_vec) > 0) {
1723 strvec_to_and_list(extras_vec, &extras_and);
1724 cat_snprintf(buf, bufsz, _("* Can build %s on tiles.\n"),
1725 astr_str(&extras_and));
1726 strvec_clear(extras_vec);
1729 if (effect_cumulative_max(EFT_MINING_TF_POSSIBLE, &for_utype) > 0) {
1730 CATLSTR(buf, bufsz, _("* Can convert terrain to another type by "
1731 "mining.\n"));
1734 if (effect_cumulative_max(EFT_IRRIG_POSSIBLE, &for_utype) > 0) {
1735 extra_type_by_cause_iterate(EC_IRRIGATION, pextra) {
1736 if (help_is_extra_buildable(pextra, utype)) {
1737 strvec_append(extras_vec, extra_name_translation(pextra));
1739 } extra_type_by_cause_iterate_end;
1741 if (strvec_size(extras_vec) > 0) {
1742 strvec_to_and_list(extras_vec, &extras_and);
1743 cat_snprintf(buf, bufsz, _("* Can build %s on tiles.\n"),
1744 astr_str(&extras_and));
1745 strvec_clear(extras_vec);
1748 if (effect_cumulative_max(EFT_IRRIG_TF_POSSIBLE, &for_utype) > 0) {
1749 CATLSTR(buf, bufsz, _("* Can convert terrain to another type by "
1750 "irrigation.\n"));
1752 if (effect_cumulative_max(EFT_TRANSFORM_POSSIBLE, &for_utype) > 0) {
1753 CATLSTR(buf, bufsz, _("* Can transform terrain to another type.\n"));
1756 extra_type_by_cause_iterate(EC_BASE, pextra) {
1757 if (help_is_extra_buildable(pextra, utype)) {
1758 strvec_append(extras_vec, extra_name_translation(pextra));
1760 } extra_type_by_cause_iterate_end;
1762 if (strvec_size(extras_vec) > 0) {
1763 strvec_to_and_list(extras_vec, &extras_and);
1764 cat_snprintf(buf, bufsz, _("* Can build %s on tiles.\n"),
1765 astr_str(&extras_and));
1766 strvec_clear(extras_vec);
1769 /* Pollution, fallout. */
1770 extra_type_by_rmcause_iterate(ERM_CLEANPOLLUTION, pextra) {
1771 if (help_is_extra_cleanable(pextra, utype)) {
1772 strvec_append(extras_vec, extra_name_translation(pextra));
1774 } extra_type_by_rmcause_iterate_end;
1776 if (strvec_size(extras_vec) > 0) {
1777 strvec_to_and_list(extras_vec, &extras_and);
1778 /* TRANS: list of extras separated by "and" */
1779 cat_snprintf(buf, bufsz, _("* Can clean %s from tiles.\n"),
1780 astr_str(&extras_and));
1781 strvec_clear(extras_vec);
1784 extra_type_by_rmcause_iterate(ERM_CLEANFALLOUT, pextra) {
1785 if (help_is_extra_cleanable(pextra, utype)) {
1786 strvec_append(extras_vec, extra_name_translation(pextra));
1788 } extra_type_by_rmcause_iterate_end;
1790 if (strvec_size(extras_vec) > 0) {
1791 strvec_to_and_list(extras_vec, &extras_and);
1792 /* TRANS: list of extras separated by "and" */
1793 cat_snprintf(buf, bufsz, _("* Can clean %s from tiles.\n"),
1794 astr_str(&extras_and));
1795 strvec_clear(extras_vec);
1798 strvec_destroy(extras_vec);
1801 if (utype_has_flag(utype, UTYF_SPY)) {
1802 CATLSTR(buf, bufsz, _("* Performs better diplomatic actions.\n"));
1804 if (utype_has_flag(utype, UTYF_DIPLOMAT)
1805 || utype_has_flag(utype, UTYF_SUPERSPY)) {
1806 CATLSTR(buf, bufsz, _("* Defends cities against diplomatic actions.\n"));
1808 if (utype_has_flag(utype, UTYF_SUPERSPY)) {
1809 CATLSTR(buf, bufsz, _("* Will never lose a diplomat-versus-diplomat fight.\n"));
1811 if (utype_has_flag(utype, UTYF_SPY)
1812 && utype_has_flag(utype, UTYF_SUPERSPY)) {
1813 CATLSTR(buf, bufsz, _("* Will always survive a spy mission.\n"));
1815 if (utype_has_flag(utype, UTYF_PARTIAL_INVIS)) {
1816 CATLSTR(buf, bufsz,
1817 _("* Is invisible except when next to an enemy unit or city.\n"));
1819 if (utype_has_flag(utype, UTYF_ONLY_NATIVE_ATTACK)) {
1820 CATLSTR(buf, bufsz,
1821 _("* Can only attack units on native tiles.\n"));
1823 if (game.info.slow_invasions
1824 && utype_has_flag(utype, UTYF_BEACH_LANDER)) {
1825 /* BeachLander only matters when slow_invasions are enabled. */
1826 CATLSTR(buf, bufsz,
1827 _("* Won't lose all movement when moving from non-native "
1828 "terrain to native terrain.\n"));
1830 if (!uclass_has_flag(utype_class(utype), UCF_MISSILE)
1831 && utype_has_flag(utype, UTYF_ONEATTACK)) {
1832 CATLSTR(buf, bufsz,
1833 _("* Making an attack ends this unit's turn.\n"));
1835 if (utype_has_flag(utype, UTYF_CITYBUSTER)) {
1836 CATLSTR(buf, bufsz,
1837 _("* Gets double firepower when attacking cities.\n"));
1839 if (utype_has_flag(utype, UTYF_IGTER)) {
1840 cat_snprintf(buf, bufsz,
1841 /* TRANS: "MP" = movement points. %s may have a
1842 * fractional part. */
1843 _("* Ignores terrain effects (moving costs at most %s MP "
1844 "per tile).\n"),
1845 move_points_text(terrain_control.igter_cost, TRUE));
1847 if (utype_has_flag(utype, UTYF_NOZOC)) {
1848 CATLSTR(buf, bufsz, _("* Never imposes a zone of control.\n"));
1849 } else {
1850 CATLSTR(buf, bufsz, _("* May impose a zone of control on its adjacent "
1851 "tiles.\n"));
1853 if (utype_has_flag(utype, UTYF_IGZOC)) {
1854 CATLSTR(buf, bufsz, _("* Not subject to zones of control imposed "
1855 "by other units.\n"));
1857 if (utype_has_flag(utype, UTYF_CIVILIAN)) {
1858 CATLSTR(buf, bufsz,
1859 _("* A non-military unit:\n"));
1860 CATLSTR(buf, bufsz,
1861 _(" * Cannot attack.\n"));
1862 CATLSTR(buf, bufsz,
1863 _(" * Doesn't impose martial law.\n"));
1864 CATLSTR(buf, bufsz,
1865 _(" * Can enter foreign territory regardless of peace treaty.\n"));
1866 CATLSTR(buf, bufsz,
1867 _(" * Doesn't prevent enemy cities from working the tile it's on.\n"));
1869 if (utype_has_flag(utype, UTYF_FIELDUNIT)) {
1870 CATLSTR(buf, bufsz,
1871 _("* A field unit: one unhappiness applies even when non-aggressive.\n"));
1873 if (utype_has_flag(utype, UTYF_SHIELD2GOLD)) {
1874 /* FIXME: the conversion shield => gold is activated if
1875 * EFT_SHIELD2GOLD_FACTOR is not equal null; how to determine
1876 * possible sources? */
1877 CATLSTR(buf, bufsz,
1878 _("* Under certain conditions the shield upkeep of this unit can "
1879 "be converted to gold upkeep.\n"));
1882 unit_class_iterate(target) {
1883 if (uclass_has_flag(target, UCF_UNREACHABLE)
1884 && BV_ISSET(utype->targets, uclass_index(target))) {
1885 cat_snprintf(buf, bufsz,
1886 _("* Can attack against %s units, which are usually not "
1887 "reachable.\n"),
1888 uclass_name_translation(target));
1890 } unit_class_iterate_end;
1892 fuel = utype_fuel(utype);
1893 if (fuel > 0) {
1894 const char *types[utype_count()];
1895 int i = 0;
1897 unit_type_iterate(transport) {
1898 if (can_unit_type_transport(transport, utype_class(utype))) {
1899 types[i++] = utype_name_translation(transport);
1901 } unit_type_iterate_end;
1903 if (0 == i) {
1904 if (utype_has_flag(utype, UTYF_COAST)) {
1905 if (fuel == 1) {
1906 cat_snprintf(buf, bufsz,
1907 _("* Unit has to end each turn next to coast or"
1908 " in a city or a base.\n"));
1909 } else {
1910 cat_snprintf(buf, bufsz,
1911 /* Pluralization for the benefit of languages with
1912 * duals etc */
1913 /* TRANS: Never called for 'turns = 1' case */
1914 PL_("* Unit has to be next to coast, in a city or a base"
1915 " after %d turn.\n",
1916 "* Unit has to be next to coast, in a city or a base"
1917 " after %d turns.\n",
1918 fuel),
1919 fuel);
1921 } else {
1922 cat_snprintf(buf, bufsz,
1923 PL_("* Unit has to be in a city or a base"
1924 " after %d turn.\n",
1925 "* Unit has to be in a city or a base"
1926 " after %d turns.\n",
1927 fuel),
1928 fuel);
1930 } else {
1931 struct astring list = ASTRING_INIT;
1933 if (utype_has_flag(utype, UTYF_COAST)) {
1934 cat_snprintf(buf, bufsz,
1935 /* TRANS: %s is a list of unit types separated by "or" */
1936 PL_("* Unit has to be next to coast, in a city, a base, or on a %s"
1937 " after %d turn.\n",
1938 "* Unit has to be next to coast, in a city, a base, or on a %s"
1939 " after %d turns.\n",
1940 fuel),
1941 astr_build_or_list(&list, types, i), fuel);
1942 } else {
1943 cat_snprintf(buf, bufsz,
1944 /* TRANS: %s is a list of unit types separated by "or" */
1945 PL_("* Unit has to be in a city, a base, or on a %s"
1946 " after %d turn.\n",
1947 "* Unit has to be in a city, a base, or on a %s"
1948 " after %d turns.\n",
1949 fuel),
1950 astr_build_or_list(&list, types, i), fuel);
1952 astr_free(&list);
1955 action_iterate(act) {
1956 struct action *paction = action_by_number(act);
1958 if (action_by_number(act)->quiet) {
1959 /* The ruleset documents this action it self. */
1960 continue;
1963 if (utype_can_do_action(utype, act)) {
1964 const char *target_adjective;
1965 const char *blockers[MAX_NUM_ACTIONS];
1966 int i = 0;
1968 /* Generic action information. */
1969 cat_snprintf(buf, bufsz,
1970 /* TRANS: %s is the action's ruleset defined ui name */
1971 _("* Can do the action \'%s\'.\n"),
1972 action_id_name_translation(act));
1974 switch (action_id_get_target_kind(act)) {
1975 case ATK_SELF:
1976 /* No target. */
1977 break;
1978 default:
1979 if (!can_utype_do_act_if_tgt_diplrel(utype, act,
1980 DRO_FOREIGN, TRUE)) {
1981 /* TRANS: describes the target of an action. */
1982 target_adjective = _("domestic ");
1983 } else if (!can_utype_do_act_if_tgt_diplrel(utype, act,
1984 DRO_FOREIGN, FALSE)) {
1985 /* TRANS: describes the target of an action. */
1986 target_adjective = _("foreign ");
1987 } else {
1988 /* Both foreign and domestic targets are acceptable. */
1989 target_adjective = "";
1992 cat_snprintf(buf, bufsz,
1993 /* TRANS: The first %s may be an adjective (that
1994 * includes a space). The next is the name of its
1995 * target kind. */
1996 _(" * is done to %s%s.\n"),
1997 target_adjective,
1998 _(action_target_kind_name(
1999 action_id_get_target_kind(act))));
2002 if (action_id_get_target_kind(act) != ATK_SELF) {
2003 /* Distance to target is relevant. */
2005 /* FIXME: move paratroopers_range to the action and remove this
2006 * variable once actions are generalized. */
2007 int relative_max = action_has_result(paction, ACTION_PARADROP) ?
2008 MIN(paction->max_distance, utype->paratroopers_range) :
2009 paction->max_distance;
2011 if (paction->min_distance == relative_max) {
2012 /* Only one distance to target is acceptable */
2014 if (paction->min_distance == 0) {
2015 cat_snprintf(buf, bufsz,
2016 /* TRANS: distance between an actor unit and its
2017 * target when performing a specific action. */
2018 _(" * target must be at the same tile.\n"));
2019 } else {
2020 cat_snprintf(buf, bufsz,
2021 /* TRANS: distance between an actor unit and its
2022 * target when performing a specific action. */
2023 PL_(" * target must be exactly %d tile away.\n",
2024 " * target must be exactly %d tiles away.\n",
2025 paction->min_distance),
2026 paction->min_distance);
2028 } else if (relative_max == ACTION_DISTANCE_UNLIMITED) {
2029 /* No max distance */
2031 if (paction->min_distance == 0) {
2032 cat_snprintf(buf, bufsz,
2033 /* TRANS: distance between an actor unit and its
2034 * target when performing a specific action. */
2035 _(" * target can be anywhere.\n"));
2036 } else {
2037 cat_snprintf(buf, bufsz,
2038 /* TRANS: distance between an actor unit and its
2039 * target when performing a specific action. */
2040 PL_(" * target must be at least %d tile away.\n",
2041 " * target must be at least %d tiles away.\n",
2042 paction->min_distance),
2043 paction->min_distance);
2045 } else if (paction->min_distance == 0) {
2046 /* No min distance */
2048 cat_snprintf(buf, bufsz,
2049 /* TRANS: distance between an actor unit and its
2050 * target when performing a specific action. */
2051 PL_(" * target can be max %d tile away.\n",
2052 " * target can be max %d tiles away.\n",
2053 relative_max),
2054 relative_max);
2055 } else {
2056 /* Full range. */
2058 cat_snprintf(buf, bufsz,
2059 /* TRANS: distance between an actor unit and its
2060 * target when performing a specific action. */
2061 PL_(" * target must be between %d and %d tile away.\n",
2062 " * target must be between %d and %d tiles away.\n",
2063 relative_max),
2064 paction->min_distance, relative_max);
2068 /* Custom action specific information. */
2069 switch (act) {
2070 case ACTION_HELP_WONDER:
2071 cat_snprintf(buf, bufsz,
2072 /* TRANS: the %d is the number of shields the unit can
2073 * contribute. */
2074 _(" * adds %d production.\n"),
2075 utype_build_shield_cost(utype));
2076 break;
2077 case ACTION_FOUND_CITY:
2078 if (game.scenario.prevent_new_cities) {
2079 cat_snprintf(buf, bufsz,
2080 /* TRANS: is talking about an action. */
2081 _(" * is disabled in the current game.\n"));
2083 cat_snprintf(buf, bufsz,
2084 /* TRANS: the %d is initial population. */
2085 PL_(" * initial population: %d.\n",
2086 " * initial population: %d.\n",
2087 utype->city_size),
2088 utype->city_size);
2089 break;
2090 case ACTION_JOIN_CITY:
2091 cat_snprintf(buf, bufsz,
2092 /* TRANS: the %d is population. */
2093 PL_(" * max target size: %d.\n",
2094 " * max target size: %d.\n",
2095 game.info.add_to_size_limit - utype_pop_value(utype)),
2096 game.info.add_to_size_limit - utype_pop_value(utype));
2097 cat_snprintf(buf, bufsz,
2098 /* TRANS: the %d is the population added. */
2099 PL_(" * adds %d population.\n",
2100 " * adds %d population.\n",
2101 utype_pop_value(utype)),
2102 utype_pop_value(utype));
2103 break;
2104 case ACTION_BOMBARD:
2105 cat_snprintf(buf, bufsz,
2106 /* TRANS: %d is bombard rate. */
2107 _(" * %d per turn.\n"),
2108 utype->bombard_rate);
2109 cat_snprintf(buf, bufsz,
2110 /* TRANS: talking about bombard */
2111 _(" * These attacks will only damage (never kill)"
2112 " defenders, but damage all"
2113 " defenders on a tile, and have no risk for the"
2114 " attacker.\n"));
2115 break;
2116 default:
2117 /* No action specific details. */
2118 break;
2121 action_iterate(blocker) {
2122 if (!utype_can_do_action(utype, blocker)) {
2123 /* Can't block since never legal. */
2124 continue;
2127 if (action_id_would_be_blocked_by(act, blocker)) {
2128 char *quoted = fc_malloc(MAX_LEN_NAME);
2130 fc_snprintf(quoted, MAX_LEN_NAME,
2131 /* TRANS: %s is an action that can block another. */
2132 _("\'%s\'"), action_id_name_translation(blocker));
2133 blockers[i] = quoted;
2135 i++;
2137 } action_iterate_end;
2139 if (i > 0) {
2140 struct astring blist = ASTRING_INIT;
2142 cat_snprintf(buf, bufsz,
2143 /* TRANS: %s is a list of actions separated by "or". */
2144 _(" * can't be done if %s is legal.\n"),
2145 astr_build_or_list(&blist, blockers, i));
2147 astr_free(&blist);
2150 } action_iterate_end;
2151 action_iterate(act) {
2152 bool vulnerable;
2154 if (action_by_number(act)->quiet) {
2155 /* The ruleset documents this action it self. */
2156 continue;
2159 /* Not relevant */
2160 if (action_id_get_target_kind(act) != ATK_UNIT
2161 && action_id_get_target_kind(act) != ATK_UNITS
2162 && action_id_get_target_kind(act) != ATK_SELF) {
2163 continue;
2166 /* All units are immune to this since its not enabled */
2167 if (action_enabler_list_size(action_enablers_for_action(act)) == 0) {
2168 continue;
2171 /* Must be immune in all cases */
2172 vulnerable = FALSE;
2173 action_enabler_list_iterate(action_enablers_for_action(act), enabler) {
2174 if (requirement_fulfilled_by_unit_type(utype,
2175 &(enabler->target_reqs))) {
2176 vulnerable = TRUE;
2177 break;
2179 } action_enabler_list_iterate_end;
2181 if (!vulnerable) {
2182 cat_snprintf(buf, bufsz,
2183 _("* Doing the action \'%s\' to this unit"
2184 " is impossible.\n"),
2185 action_id_name_translation(act));
2187 } action_iterate_end;
2188 if (!has_vet_levels) {
2189 /* Only mention this if the game generally does have veteran levels. */
2190 if (game.veteran->levels > 1) {
2191 CATLSTR(buf, bufsz, _("* Will never achieve veteran status.\n"));
2193 } else {
2194 /* Not useful currently: */
2195 #if 0
2196 /* Some units can never become veteran through combat in practice. */
2197 bool veteran_through_combat =
2198 !((!utype_can_do_action(utype, ACTION_ATTACK)
2199 || uclass_has_flag(utype_class(utype), UCF_MISSILE))
2200 && utype->defense_strength == 0);
2201 #endif
2202 /* FIXME: if we knew the raise chances on the client, we could be
2203 * more specific here about whether veteran status can be acquired
2204 * through combat/missions/work. Should also take into account
2205 * UTYF_NO_VETERAN when writing this text. (Gna patch #4794) */
2206 CATLSTR(buf, bufsz, _("* May acquire veteran status.\n"));
2207 if (utype_veteran_has_power_bonus(utype)) {
2208 if ((!utype_can_do_action(utype, ACTION_NUKE)
2209 && utype_can_do_action(utype, ACTION_ATTACK))
2210 || utype->defense_strength > 0) {
2211 CATLSTR(buf, bufsz,
2212 _(" * Veterans have increased strength in combat.\n"));
2214 /* SUPERSPY always wins/escapes */
2215 if ((utype_has_flag(utype, UTYF_DIPLOMAT)
2216 || utype_has_flag(utype, UTYF_SPY))
2217 && !utype_has_flag(utype, UTYF_SUPERSPY)) {
2218 CATLSTR(buf, bufsz,
2219 _(" * Veterans have improved chances in diplomatic "
2220 "contests.\n"));
2221 if (utype_has_flag(utype, UTYF_SPY)) {
2222 CATLSTR(buf, bufsz,
2223 _(" * Veterans are more likely to survive missions.\n"));
2226 if (utype_has_flag(utype, UTYF_SETTLERS)) {
2227 CATLSTR(buf, bufsz,
2228 _(" * Veterans work faster.\n"));
2232 if (strlen(buf) > 0) {
2233 CATLSTR(buf, bufsz, "\n");
2235 if (has_vet_levels && utype->veteran) {
2236 /* The case where the unit has only a single veteran level has already
2237 * been handled above, so keep quiet here if that happens */
2238 if (insert_veteran_help(buf, bufsz, utype->veteran,
2239 _("This type of unit has its own veteran levels:"), NULL)) {
2240 CATLSTR(buf, bufsz, "\n\n");
2243 if (NULL != utype->helptext) {
2244 strvec_iterate(utype->helptext, text) {
2245 cat_snprintf(buf, bufsz, "%s\n\n", _(text));
2246 } strvec_iterate_end;
2248 CATLSTR(buf, bufsz, user_text);
2249 return buf;
2252 /****************************************************************************
2253 Append misc dynamic text for advance/technology.
2255 pplayer may be NULL.
2256 ****************************************************************************/
2257 void helptext_advance(char *buf, size_t bufsz, struct player *pplayer,
2258 const char *user_text, int i)
2260 struct astring astr = ASTRING_INIT;
2261 struct advance *vap = valid_advance_by_number(i);
2262 struct universal source = {
2263 .kind = VUT_ADVANCE,
2264 .value = {.advance = vap}
2266 int flagid;
2268 fc_assert_ret(NULL != buf && 0 < bufsz && NULL != user_text);
2269 fc_strlcpy(buf, user_text, bufsz);
2271 if (NULL == vap) {
2272 log_error("Unknown tech %d.", i);
2273 return;
2276 if (game.control.num_tech_classes > 0) {
2277 if (vap->tclass == NULL) {
2278 cat_snprintf(buf, bufsz, _("Belongs to the default tech class.\n\n"));
2279 } else {
2280 cat_snprintf(buf, bufsz, _("Belongs to tech class %s.\n\n"),
2281 tech_class_name_translation(vap->tclass));
2285 if (NULL != pplayer) {
2286 const struct research *presearch = research_get(pplayer);
2288 if (research_invention_state(presearch, i) != TECH_KNOWN) {
2289 if (research_invention_state(presearch, i) == TECH_PREREQS_KNOWN) {
2290 int bulbs = research_total_bulbs_required(presearch, i, FALSE);
2292 cat_snprintf(buf, bufsz,
2293 PL_("Starting now, researching %s would need %d bulb.",
2294 "Starting now, researching %s would need %d bulbs.",
2295 bulbs),
2296 advance_name_translation(vap), bulbs);
2297 } else if (research_invention_reachable(presearch, i)) {
2298 /* Split string into two to allow localization of two pluralizations. */
2299 char buf2[MAX_LEN_MSG];
2300 int bulbs = research_goal_bulbs_required(presearch, i);
2302 fc_snprintf(buf2, ARRAY_SIZE(buf2),
2303 /* TRANS: appended to another sentence. Preserve the
2304 * leading space. */
2305 PL_(" The whole project will require %d bulb to complete.",
2306 " The whole project will require %d bulbs to complete.",
2307 bulbs),
2308 bulbs);
2309 cat_snprintf(buf, bufsz,
2310 /* TRANS: last %s is a sentence pluralized separately. */
2311 PL_("To reach %s you need to obtain %d other"
2312 " technology first.%s",
2313 "To reach %s you need to obtain %d other"
2314 " technologies first.%s",
2315 research_goal_unknown_techs(presearch, i) - 1),
2316 advance_name_translation(vap),
2317 research_goal_unknown_techs(presearch, i) - 1, buf2);
2318 } else {
2319 CATLSTR(buf, bufsz,
2320 _("You cannot research this technology."));
2322 if (!techs_have_fixed_costs()
2323 && research_invention_reachable(presearch, i)) {
2324 CATLSTR(buf, bufsz,
2325 _(" This number may vary depending on what "
2326 "other players research.\n"));
2327 } else {
2328 CATLSTR(buf, bufsz, "\n");
2332 CATLSTR(buf, bufsz, "\n");
2335 if (requirement_vector_size(&vap->research_reqs) > 0) {
2336 CATLSTR(buf, bufsz, _("Requirements to research:\n"));
2337 requirement_vector_iterate(&vap->research_reqs, preq) {
2338 (void) req_text_insert_nl(buf, bufsz, pplayer, preq, VERB_DEFAULT);
2339 } requirement_vector_iterate_end;
2340 CATLSTR(buf, bufsz, "\n");
2343 insert_allows(&source, buf + strlen(buf), bufsz - strlen(buf));
2346 int j;
2348 for (j = 0; j < MAX_NUM_TECH_LIST; j++) {
2349 if (game.rgame.global_init_techs[j] == A_LAST) {
2350 break;
2351 } else if (game.rgame.global_init_techs[j] == i) {
2352 CATLSTR(buf, bufsz,
2353 _("* All players start the game with knowledge of this "
2354 "technology.\n"));
2355 break;
2360 /* Assume no-one will set the same tech in both global and nation
2361 * init_tech... */
2362 nations_iterate(pnation) {
2363 int j;
2365 /* Avoid mentioning nations not in current set. */
2366 if (!show_help_for_nation(pnation)) {
2367 continue;
2369 for (j = 0; j < MAX_NUM_TECH_LIST; j++) {
2370 if (pnation->init_techs[j] == A_LAST) {
2371 break;
2372 } else if (pnation->init_techs[j] == i) {
2373 cat_snprintf(buf, bufsz,
2374 /* TRANS: %s is a nation plural */
2375 _("* The %s start the game with knowledge of this "
2376 "technology.\n"), nation_plural_translation(pnation));
2377 break;
2380 } nations_iterate_end;
2382 if (advance_has_flag(i, TF_BONUS_TECH)) {
2383 cat_snprintf(buf, bufsz,
2384 _("* The first player to research %s gets"
2385 " an immediate advance.\n"),
2386 advance_name_translation(vap));
2389 for (flagid = TECH_USER_1 ; flagid <= TECH_USER_LAST; flagid++) {
2390 if (advance_has_flag(i, flagid)) {
2391 const char *helptxt = tech_flag_helptxt(flagid);
2393 if (helptxt != NULL) {
2394 CATLSTR(buf, bufsz, Q_("?bullet:* "));
2395 CATLSTR(buf, bufsz, _(helptxt));
2396 CATLSTR(buf, bufsz, "\n");
2401 /* FIXME: bases -- but there is no good way to find out which bases a tech
2402 * can enable currently, so we have to remain silent. */
2404 if (game.info.tech_upkeep_style != TECH_UPKEEP_NONE) {
2405 CATLSTR(buf, bufsz,
2406 _("* To preserve this technology for our nation some bulbs "
2407 "are needed each turn.\n"));
2410 if (NULL != vap->helptext) {
2411 if (strlen(buf) > 0) {
2412 CATLSTR(buf, bufsz, "\n");
2414 strvec_iterate(vap->helptext, text) {
2415 cat_snprintf(buf, bufsz, "%s\n\n", _(text));
2416 } strvec_iterate_end;
2419 astr_free(&astr);
2422 /****************************************************************
2423 Append text for terrain.
2424 *****************************************************************/
2425 void helptext_terrain(char *buf, size_t bufsz, struct player *pplayer,
2426 const char *user_text, struct terrain *pterrain)
2428 struct universal source = {
2429 .kind = VUT_TERRAIN,
2430 .value = {.terrain = pterrain}
2432 int flagid;
2434 fc_assert_ret(NULL != buf && 0 < bufsz);
2435 buf[0] = '\0';
2437 if (!pterrain) {
2438 log_error("Unknown terrain!");
2439 return;
2442 insert_allows(&source, buf + strlen(buf), bufsz - strlen(buf));
2443 if (terrain_has_flag(pterrain, TER_NO_CITIES)) {
2444 CATLSTR(buf, bufsz,
2445 _("* You cannot build cities on this terrain."));
2446 CATLSTR(buf, bufsz, "\n");
2448 if (pterrain->road_time == 0) {
2449 /* Can't build roads; only mention if ruleset has buildable roads */
2450 extra_type_by_cause_iterate(EC_ROAD, pextra) {
2451 if (pextra->buildable) {
2452 CATLSTR(buf, bufsz,
2453 _("* Paths cannot be built on this terrain."));
2454 CATLSTR(buf, bufsz, "\n");
2455 break;
2457 } extra_type_by_cause_iterate_end;
2459 if (pterrain->base_time == 0) {
2460 /* Can't build bases; only mention if ruleset has buildable bases */
2461 extra_type_by_cause_iterate(EC_BASE, pextra) {
2462 if (pextra->buildable) {
2463 CATLSTR(buf, bufsz,
2464 _("* Bases cannot be built on this terrain."));
2465 CATLSTR(buf, bufsz, "\n");
2466 break;
2468 } extra_type_by_cause_iterate_end;
2470 if (terrain_has_flag(pterrain, TER_UNSAFE_COAST)
2471 && terrain_type_terrain_class(pterrain) != TC_OCEAN) {
2472 CATLSTR(buf, bufsz,
2473 _("* The coastline of this terrain is unsafe."));
2474 CATLSTR(buf, bufsz, "\n");
2477 const char *classes[uclass_count()];
2478 int i = 0;
2480 unit_class_iterate(uclass) {
2481 if (is_native_to_class(uclass, pterrain, NULL)) {
2482 classes[i++] = uclass_name_translation(uclass);
2484 } unit_class_iterate_end;
2486 if (0 < i) {
2487 struct astring list = ASTRING_INIT;
2489 /* TRANS: %s is a list of unit classes separated by "and". */
2490 cat_snprintf(buf, bufsz, _("* Can be traveled by %s units.\n"),
2491 astr_build_and_list(&list, classes, i));
2492 astr_free(&list);
2495 if (terrain_has_flag(pterrain, TER_NO_ZOC)) {
2496 CATLSTR(buf, bufsz,
2497 _("* Units on this terrain neither impose zones of control "
2498 "nor are restricted by them.\n"));
2499 } else {
2500 CATLSTR(buf, bufsz,
2501 _("* Units on this terrain may impose a zone of control, or "
2502 "be restricted by one.\n"));
2504 if (terrain_has_flag(pterrain, TER_NO_FORTIFY)) {
2505 CATLSTR(buf, bufsz,
2506 _("* No units can fortify on this terrain.\n"));
2507 } else {
2508 CATLSTR(buf, bufsz,
2509 _("* Units able to fortify may do so on this terrain.\n"));
2511 for (flagid = TER_USER_1 ; flagid <= TER_USER_LAST; flagid++) {
2512 if (terrain_has_flag(pterrain, flagid)) {
2513 const char *helptxt = terrain_flag_helptxt(flagid);
2515 if (helptxt != NULL) {
2516 CATLSTR(buf, bufsz, Q_("?bullet:* "));
2517 CATLSTR(buf, bufsz, _(helptxt));
2518 CATLSTR(buf, bufsz, "\n");
2523 if (NULL != pterrain->helptext) {
2524 if (buf[0] != '\0') {
2525 CATLSTR(buf, bufsz, "\n");
2527 strvec_iterate(pterrain->helptext, text) {
2528 cat_snprintf(buf, bufsz, "%s\n\n", _(text));
2529 } strvec_iterate_end;
2531 if (user_text && user_text[0] != '\0') {
2532 CATLSTR(buf, bufsz, "\n\n");
2533 CATLSTR(buf, bufsz, user_text);
2537 /****************************************************************************
2538 Return a textual representation of the F/P/T bonus a road provides to a
2539 terrain if supplied, or the terrain-independent bonus if pterrain == NULL.
2540 e.g. "0/0/+1", "0/+50%/0", or for a complex road "+2/+1+50%/0".
2541 Returns a pointer to a static string, so caller should not free
2542 (or NULL if there is no effect at all).
2543 ****************************************************************************/
2544 const char *helptext_road_bonus_str(const struct terrain *pterrain,
2545 const struct road_type *proad)
2547 static char str[64];
2548 bool has_effect = FALSE;
2550 str[0] = '\0';
2551 output_type_iterate(o) {
2552 switch (o) {
2553 case O_FOOD:
2554 case O_SHIELD:
2555 case O_TRADE:
2557 int bonus = proad->tile_bonus[o];
2558 int incr = proad->tile_incr_const[o];
2560 if (pterrain) {
2561 incr +=
2562 proad->tile_incr[o] * pterrain->road_output_incr_pct[o] / 100;
2564 if (str[0] != '\0') {
2565 CATLSTR(str, sizeof(str), "/");
2567 if (incr == 0 && bonus == 0) {
2568 cat_snprintf(str, sizeof(str), "%d", incr);
2569 } else {
2570 has_effect = TRUE;
2571 if (incr != 0) {
2572 cat_snprintf(str, sizeof(str), "%+d", incr);
2574 if (bonus != 0) {
2575 cat_snprintf(str, sizeof(str), "%+d%%", bonus);
2579 break;
2580 default:
2581 /* FIXME: there's nothing actually stopping roads having gold, etc
2582 * bonuses */
2583 fc_assert(proad->tile_incr_const[o] == 0
2584 && proad->tile_incr[o] == 0
2585 && proad->tile_bonus[o] == 0);
2586 break;
2588 } output_type_iterate_end;
2590 return has_effect ? str : NULL;
2593 /****************************************************************************
2594 Append misc dynamic text for extras.
2595 Assumes build time and conflicts are handled in the GUI front-end.
2597 pplayer may be NULL.
2598 ****************************************************************************/
2599 void helptext_extra(char *buf, size_t bufsz, struct player *pplayer,
2600 const char *user_text, struct extra_type *pextra)
2602 struct base_type *pbase;
2603 struct road_type *proad;
2604 struct universal source = {
2605 .kind = VUT_EXTRA,
2606 .value = {.extra = pextra}
2609 int flagid;
2611 fc_assert_ret(NULL != buf && 0 < bufsz);
2612 buf[0] = '\0';
2614 if (!pextra) {
2615 log_error("Unknown extra!");
2616 return;
2619 if (is_extra_caused_by(pextra, EC_BASE)) {
2620 pbase = pextra->data.base;
2621 } else {
2622 pbase = NULL;
2625 if (is_extra_caused_by(pextra, EC_ROAD)) {
2626 proad = pextra->data.road;
2627 } else {
2628 proad = NULL;
2631 if (pextra->helptext != NULL) {
2632 strvec_iterate(pextra->helptext, text) {
2633 cat_snprintf(buf, bufsz, "%s\n\n", _(text));
2634 } strvec_iterate_end;
2637 insert_allows(&source, buf + strlen(buf), bufsz - strlen(buf));
2639 if (is_extra_caused_by(pextra, EC_POLLUTION)) {
2640 CATLSTR(buf, bufsz,
2641 _("* May randomly appear around polluting city.\n"));
2644 if (is_extra_caused_by(pextra, EC_FALLOUT)) {
2645 CATLSTR(buf, bufsz,
2646 _("* May randomly appear around nuclear blast.\n"));
2649 if (is_extra_caused_by(pextra, EC_HUT)
2650 || (proad != NULL && road_has_flag(proad, RF_RIVER))) {
2651 CATLSTR(buf, bufsz,
2652 _("* Placed by map generator.\n"));
2655 if (is_extra_caused_by(pextra, EC_APPEARANCE)) {
2656 CATLSTR(buf, bufsz,
2657 _("* May appear spontaneously.\n"));
2660 if (pextra->eus == EUS_HIDDEN) {
2661 CATLSTR(buf, bufsz,
2662 _("* Units inside are hidden from non-allied players.\n"));
2665 /* XXX Non-zero requirement vector is not a good test of whether
2666 * req_text_insert_nl() will give any output. */
2667 if (requirement_vector_size(&pextra->reqs) > 0) {
2668 if (pextra->buildable && is_extra_caused_by_worker_action(pextra)) {
2669 CATLSTR(buf, bufsz, _("Requirements to build:\n"));
2671 requirement_vector_iterate(&pextra->reqs, preq) {
2672 (void) req_text_insert_nl(buf, bufsz, pplayer, preq, VERB_DEFAULT);
2673 } requirement_vector_iterate_end;
2674 CATLSTR(buf, bufsz, "\n");
2678 const char *classes[uclass_count()];
2679 int i = 0;
2681 unit_class_iterate(uclass) {
2682 if (is_native_extra_to_uclass(pextra, uclass)) {
2683 classes[i++] = uclass_name_translation(uclass);
2685 } unit_class_iterate_end;
2687 if (0 < i) {
2688 struct astring list = ASTRING_INIT;
2690 if (proad != NULL) {
2691 /* TRANS: %s is a list of unit classes separated by "and". */
2692 cat_snprintf(buf, bufsz, _("* Can be traveled by %s units.\n"),
2693 astr_build_and_list(&list, classes, i));
2694 } else {
2695 /* TRANS: %s is a list of unit classes separated by "and". */
2696 cat_snprintf(buf, bufsz, _("* Native to %s units.\n"),
2697 astr_build_and_list(&list, classes, i));
2699 astr_free(&list);
2701 if (extra_has_flag(pextra, EF_NATIVE_TILE)) {
2702 CATLSTR(buf, bufsz,
2703 _(" * Such units can move onto this tile even if it would "
2704 "not normally be suitable terrain.\n"));
2706 if (pbase != NULL) {
2707 if (base_has_flag(pbase, BF_NOT_AGGRESSIVE)) {
2708 /* "3 tiles" is hardcoded in is_friendly_city_near() */
2709 CATLSTR(buf, bufsz,
2710 _(" * Such units situated here are not considered aggressive "
2711 "if this tile is within 3 tiles of a friendly city.\n"));
2713 if (territory_claiming_base(pbase)) {
2714 CATLSTR(buf, bufsz,
2715 _(" * Can be captured by such units if at war with the "
2716 "nation that currently owns it.\n"));
2719 if (pextra->defense_bonus) {
2720 cat_snprintf(buf, bufsz,
2721 _(" * Such units get a %d%% defense bonus on this "
2722 "tile.\n"),
2723 pextra->defense_bonus);
2728 if (proad != NULL && road_provides_move_bonus(proad)) {
2729 if (proad->move_cost == 0) {
2730 CATLSTR(buf, bufsz, _("* Allows infinite movement.\n"));
2731 } else {
2732 cat_snprintf(buf, bufsz,
2733 /* TRANS: "MP" = movement points. Second %s may have a
2734 * fractional part. */
2735 _("* Movement cost along %s is %s MP.\n"),
2736 extra_name_translation(pextra),
2737 move_points_text(proad->move_cost, TRUE));
2741 if (is_extra_removed_by(pextra, ERM_PILLAGE)) {
2742 CATLSTR(buf, bufsz,
2743 _("* Can be pillaged by units.\n"));
2745 if (is_extra_removed_by(pextra, ERM_CLEANPOLLUTION) || is_extra_removed_by(pextra, ERM_CLEANFALLOUT)) {
2746 CATLSTR(buf, bufsz,
2747 _("* Can be cleaned by units.\n"));
2749 if (game.info.killstack
2750 && extra_has_flag(pextra, EF_NO_STACK_DEATH)) {
2751 CATLSTR(buf, bufsz,
2752 _("* Defeat of one unit does not cause death of all other units "
2753 "on this tile.\n"));
2755 if (pbase != NULL) {
2756 if (territory_claiming_base(pbase)) {
2757 CATLSTR(buf, bufsz,
2758 _("* Extends national borders of the building nation.\n"));
2760 if (pbase->vision_main_sq >= 0) {
2761 CATLSTR(buf, bufsz,
2762 _("* Grants permanent vision of an area around the tile to "
2763 "its owner.\n"));
2765 if (pbase->vision_invis_sq >= 0) {
2766 CATLSTR(buf, bufsz,
2767 _("* Allows the owner to see normally invisible units in an "
2768 "area around the tile.\n"));
2771 for (flagid = EF_USER_FLAG_1; flagid <= EF_LAST_USER_FLAG; flagid++) {
2772 if (extra_has_flag(pextra, flagid)) {
2773 const char *helptxt = extra_flag_helptxt(flagid);
2775 if (helptxt != NULL) {
2776 CATLSTR(buf, bufsz, Q_("?bullet:* "));
2777 CATLSTR(buf, bufsz, _(helptxt));
2778 CATLSTR(buf, bufsz, "\n");
2783 /* Table of terrain-specific attributes, if needed */
2784 if (proad != NULL || pbase != NULL) {
2785 bool road, do_time, do_bonus;
2787 road = (proad != NULL);
2788 /* Terrain-dependent build time? */
2789 do_time = pextra->buildable && pextra->build_time == 0;
2790 if (road) {
2791 /* Terrain-dependent output bonus? */
2792 do_bonus = FALSE;
2793 output_type_iterate(o) {
2794 if (proad->tile_incr[o] > 0) {
2795 do_bonus = TRUE;
2796 fc_assert(o == O_FOOD || o == O_SHIELD || o == O_TRADE);
2798 } output_type_iterate_end;
2799 } else {
2800 /* Bases don't have output bonuses */
2801 do_bonus = FALSE;
2804 if (do_time || do_bonus) {
2805 if (do_time && do_bonus) {
2806 CATLSTR(buf, bufsz,
2807 _("\nTime to build and output bonus depends on terrain:\n\n"));
2808 CATLSTR(buf, bufsz,
2809 /* TRANS: Header for fixed-width road properties table.
2810 * TRANS: Translators cannot change column widths :( */
2811 _("Terrain Time Bonus F/P/T\n"
2812 "----------------------------------\n"));
2813 } else if (do_time) {
2814 CATLSTR(buf, bufsz,
2815 _("\nTime to build depends on terrain:\n\n"));
2816 CATLSTR(buf, bufsz,
2817 /* TRANS: Header for fixed-width extra properties table.
2818 * TRANS: Translators cannot change column widths :( */
2819 _("Terrain Time\n"
2820 "------------------\n"));
2821 } else {
2822 fc_assert(do_bonus);
2823 CATLSTR(buf, bufsz,
2824 /* TRANS: Header for fixed-width road properties table.
2825 * TRANS: Translators cannot change column widths :( */
2826 _("\nYields an output bonus with some terrains:\n\n"));
2827 CATLSTR(buf, bufsz,
2828 _("Terrain Bonus F/P/T\n"
2829 "-------------------------\n"));;
2831 terrain_type_iterate(t) {
2832 int turns = road ? terrain_extra_build_time(t, ACTIVITY_GEN_ROAD, pextra)
2833 : terrain_extra_build_time(t, ACTIVITY_BASE, pextra);
2834 const char *bonus_text
2835 = road ? helptext_road_bonus_str(t, proad) : NULL;
2836 if (turns > 0 || bonus_text) {
2837 const char *terrain = terrain_name_translation(t);
2839 cat_snprintf(buf, bufsz,
2840 "%s%*s ", terrain,
2841 MAX(0, 12 - (int)get_internal_string_length(terrain)),
2842 "");
2843 if (do_time) {
2844 if (turns > 0) {
2845 cat_snprintf(buf, bufsz, "%3d ", turns);
2846 } else {
2847 CATLSTR(buf, bufsz, " - ");
2850 if (do_bonus) {
2851 fc_assert(proad != NULL);
2852 cat_snprintf(buf, bufsz, " %s", bonus_text ? bonus_text : "-");
2854 CATLSTR(buf, bufsz, "\n");
2856 } terrain_type_iterate_end;
2857 } /* else rely on client-specific display */
2860 if (user_text && user_text[0] != '\0') {
2861 CATLSTR(buf, bufsz, "\n\n");
2862 CATLSTR(buf, bufsz, user_text);
2866 /****************************************************************************
2867 Append misc dynamic text for goods.
2868 Assumes effects are described in the help text.
2870 pplayer may be NULL.
2871 ****************************************************************************/
2872 void helptext_goods(char *buf, size_t bufsz, struct player *pplayer,
2873 const char *user_text, struct goods_type *pgood)
2875 bool reqs = FALSE;
2877 fc_assert_ret(NULL != buf && 0 < bufsz);
2878 buf[0] = '\0';
2880 if (NULL != pgood->helptext) {
2881 strvec_iterate(pgood->helptext, text) {
2882 cat_snprintf(buf, bufsz, "%s\n\n", _(text));
2883 } strvec_iterate_end;
2886 cat_snprintf(buf, bufsz, _("Sending city enjoys %d%% income from the route.\n"),
2887 pgood->from_pct);
2888 cat_snprintf(buf, bufsz, _("Receiving city enjoys %d%% income from the route.\n\n"),
2889 pgood->to_pct);
2891 /* Requirements for this good. */
2892 requirement_vector_iterate(&pgood->reqs, preq) {
2893 if (req_text_insert_nl(buf, bufsz, pplayer, preq, VERB_DEFAULT)) {
2894 reqs = TRUE;
2896 } requirement_vector_iterate_end;
2897 if (reqs) {
2898 fc_strlcat(buf, "\n", bufsz);
2901 CATLSTR(buf, bufsz, user_text);
2904 /****************************************************************************
2905 Append misc dynamic text for specialists.
2906 Assumes effects are described in the help text.
2908 pplayer may be NULL.
2909 ****************************************************************************/
2910 void helptext_specialist(char *buf, size_t bufsz, struct player *pplayer,
2911 const char *user_text, struct specialist *pspec)
2913 bool reqs = FALSE;
2915 fc_assert_ret(NULL != buf && 0 < bufsz);
2916 buf[0] = '\0';
2918 if (NULL != pspec->helptext) {
2919 strvec_iterate(pspec->helptext, text) {
2920 cat_snprintf(buf, bufsz, "%s\n\n", _(text));
2921 } strvec_iterate_end;
2924 /* Requirements for this specialist. */
2925 requirement_vector_iterate(&pspec->reqs, preq) {
2926 if (req_text_insert_nl(buf, bufsz, pplayer, preq, VERB_DEFAULT)) {
2927 reqs = TRUE;
2929 } requirement_vector_iterate_end;
2930 if (reqs) {
2931 fc_strlcat(buf, "\n", bufsz);
2934 CATLSTR(buf, bufsz, user_text);
2937 /****************************************************************
2938 Append text for government.
2940 pplayer may be NULL.
2942 TODO: Generalize the effects code for use elsewhere. Add
2943 other requirements.
2944 *****************************************************************/
2945 void helptext_government(char *buf, size_t bufsz, struct player *pplayer,
2946 const char *user_text, struct government *gov)
2948 bool reqs = FALSE;
2949 struct universal source = {
2950 .kind = VUT_GOVERNMENT,
2951 .value = {.govern = gov}
2954 fc_assert_ret(NULL != buf && 0 < bufsz);
2955 buf[0] = '\0';
2957 if (NULL != gov->helptext) {
2958 strvec_iterate(gov->helptext, text) {
2959 cat_snprintf(buf, bufsz, "%s\n\n", _(text));
2960 } strvec_iterate_end;
2963 /* Add requirement text for government itself */
2964 requirement_vector_iterate(&gov->reqs, preq) {
2965 if (req_text_insert_nl(buf, bufsz, pplayer, preq, VERB_DEFAULT)) {
2966 reqs = TRUE;
2968 } requirement_vector_iterate_end;
2969 if (reqs) {
2970 fc_strlcat(buf, "\n", bufsz);
2973 /* Effects */
2974 CATLSTR(buf, bufsz, _("Features:\n"));
2975 insert_allows(&source, buf + strlen(buf), bufsz - strlen(buf));
2976 effect_list_iterate(get_req_source_effects(&source), peffect) {
2977 Output_type_id output_type = O_LAST;
2978 struct unit_class *unitclass = NULL;
2979 struct unit_type *unittype = NULL;
2980 enum unit_type_flag_id unitflag = unit_type_flag_id_invalid();
2981 struct strvec *outputs = strvec_new();
2982 struct astring outputs_or = ASTRING_INIT;
2983 struct astring outputs_and = ASTRING_INIT;
2984 bool too_complex = FALSE;
2985 bool world_value_valid = TRUE;
2987 /* Grab output type, if there is one */
2988 requirement_vector_iterate(&peffect->reqs, preq) {
2989 /* Treat an effect with any negated requirements as too complex for
2990 * us to explain here.
2991 * Also don't try to explain an effect with any requirements explicitly
2992 * marked as 'quiet' by ruleset author. */
2993 if (!preq->present || preq->quiet) {
2994 too_complex = TRUE;
2995 continue;
2997 switch (preq->source.kind) {
2998 case VUT_OTYPE:
2999 /* We should never have multiple outputtype requirements
3000 * in one list in the first place (it simply makes no sense,
3001 * output cannot be of multiple types)
3002 * Ruleset loading code should check against that. */
3003 fc_assert(output_type == O_LAST);
3004 output_type = preq->source.value.outputtype;
3005 strvec_append(outputs, get_output_name(output_type));
3006 break;
3007 case VUT_UCLASS:
3008 fc_assert(unitclass == NULL);
3009 unitclass = preq->source.value.uclass;
3010 /* FIXME: can't easily get world bonus for unit class */
3011 world_value_valid = FALSE;
3012 break;
3013 case VUT_UTYPE:
3014 fc_assert(unittype == NULL);
3015 unittype = preq->source.value.utype;
3016 break;
3017 case VUT_UTFLAG:
3018 if (!unit_type_flag_id_is_valid(unitflag)) {
3019 unitflag = preq->source.value.unitflag;
3020 /* FIXME: can't easily get world bonus for unit type flag */
3021 world_value_valid = FALSE;
3022 } else {
3023 /* Already have a unit flag requirement. More than one is too
3024 * complex for us to explain, so say nothing. */
3025 /* FIXME: we could handle this */
3026 too_complex = TRUE;
3028 break;
3029 case VUT_GOVERNMENT:
3030 /* This is government we are generating helptext for.
3031 * ...or if not, it's ruleset bug that should never make it
3032 * this far. Fix ruleset loading code. */
3033 fc_assert(preq->source.value.govern == gov);
3034 break;
3035 default:
3036 too_complex = TRUE;
3037 world_value_valid = FALSE;
3038 break;
3040 } requirement_vector_iterate_end;
3042 if (!too_complex) {
3043 /* Only list effects that don't have extra requirements too complex
3044 * for us to handle.
3045 * Anything more complicated will have to be documented by hand by the
3046 * ruleset author. */
3048 /* Guard condition for simple player-wide effects descriptions.
3049 * (FIXME: in many cases, e.g. EFT_MAKE_CONTENT, additional requirements
3050 * like unittype will be ignored for gameplay, but will affect our
3051 * help here.) */
3052 const bool playerwide
3053 = world_value_valid && !unittype && (output_type == O_LAST);
3054 /* In some cases we give absolute values (world bonus + gov bonus).
3055 * We assume the fact that there's an effect with a gov requirement
3056 * is sufficient reason to list it in that gov's help.
3057 * Guard accesses to these with 'playerwide' or 'world_value_valid'. */
3058 int world_value = -999, net_value = -999;
3059 if (world_value_valid) {
3060 /* Get government-independent world value of effect if the extra
3061 * requirements were simple enough. */
3062 struct output_type *potype =
3063 output_type != O_LAST ? get_output_type(output_type) : NULL;
3064 world_value =
3065 get_target_bonus_effects(NULL, NULL, NULL, NULL, NULL, NULL,
3066 NULL, unittype, potype, NULL, NULL,
3067 peffect->type);
3068 net_value = peffect->value + world_value;
3071 if (output_type == O_LAST) {
3072 /* There was no outputtype requirement. Effect is active for all
3073 * output types. Generate lists for that. */
3074 bool harvested_only = TRUE; /* Consider only output types from fields */
3076 if (peffect->type == EFT_UPKEEP_FACTOR
3077 || peffect->type == EFT_UNIT_UPKEEP_FREE_PER_CITY
3078 || peffect->type == EFT_OUTPUT_BONUS
3079 || peffect->type == EFT_OUTPUT_BONUS_2) {
3080 /* Effect can use or require any kind of output */
3081 harvested_only = FALSE;
3084 output_type_iterate(ot) {
3085 struct output_type *pot = get_output_type(ot);
3087 if (!harvested_only || pot->harvested) {
3088 strvec_append(outputs, _(pot->name));
3090 } output_type_iterate_end;
3093 if (0 == strvec_size(outputs)) {
3094 /* TRANS: Empty output type list, should never happen. */
3095 astr_set(&outputs_or, "%s", Q_("?outputlist: Nothing "));
3096 astr_set(&outputs_and, "%s", Q_("?outputlist: Nothing "));
3097 } else {
3098 strvec_to_or_list(outputs, &outputs_or);
3099 strvec_to_and_list(outputs, &outputs_and);
3102 switch (peffect->type) {
3103 case EFT_UNHAPPY_FACTOR:
3104 if (playerwide) {
3105 /* FIXME: EFT_MAKE_CONTENT_MIL_PER would cancel this out. We assume
3106 * no-one will set both, so we don't bother handling it. */
3107 cat_snprintf(buf, bufsz,
3108 PL_("* Military units away from home and field units"
3109 " will each cause %d citizen to become unhappy.\n",
3110 "* Military units away from home and field units"
3111 " will each cause %d citizens to become unhappy.\n",
3112 net_value),
3113 net_value);
3114 } /* else too complicated or silly ruleset */
3115 break;
3116 case EFT_ENEMY_CITIZEN_UNHAPPY_PCT:
3117 if (playerwide && net_value != world_value) {
3118 if (world_value > 0) {
3119 if (net_value > 0) {
3120 cat_snprintf(buf, bufsz,
3121 _("* Unhappiness from foreign citizens due to "
3122 "war with their home state is %d%% the usual "
3123 "value.\n"),
3124 (net_value * 100) / world_value);
3125 } else {
3126 CATLSTR(buf, bufsz,
3127 _("* No unhappiness from foreign citizens even when "
3128 "at war with their home state.\n"));
3130 } else {
3131 cat_snprintf(buf, bufsz,
3132 /* TRANS: not pluralised as gettext doesn't support
3133 * fractional numbers, which this might be */
3134 _("* Each foreign citizen causes %.2g unhappiness "
3135 "in their city while you are at war with their "
3136 "home state.\n"),
3137 (double)net_value / 100);
3140 break;
3141 case EFT_MAKE_CONTENT_MIL:
3142 if (playerwide) {
3143 cat_snprintf(buf, bufsz,
3144 PL_("* Each of your cities will avoid %d unhappiness"
3145 " caused by units.\n",
3146 "* Each of your cities will avoid %d unhappiness"
3147 " caused by units.\n",
3148 peffect->value),
3149 peffect->value);
3151 break;
3152 case EFT_MAKE_CONTENT:
3153 if (playerwide) {
3154 cat_snprintf(buf, bufsz,
3155 PL_("* Each of your cities will avoid %d unhappiness,"
3156 " not including that caused by aggression.\n",
3157 "* Each of your cities will avoid %d unhappiness,"
3158 " not including that caused by aggression.\n",
3159 peffect->value),
3160 peffect->value);
3162 break;
3163 case EFT_FORCE_CONTENT:
3164 if (playerwide) {
3165 cat_snprintf(buf, bufsz,
3166 PL_("* Each of your cities will avoid %d unhappiness,"
3167 " including that caused by aggression.\n",
3168 "* Each of your cities will avoid %d unhappiness,"
3169 " including that caused by aggression.\n",
3170 peffect->value),
3171 peffect->value);
3173 break;
3174 case EFT_UPKEEP_FACTOR:
3175 if (world_value_valid && !unittype) {
3176 if (net_value == 0) {
3177 if (output_type != O_LAST) {
3178 cat_snprintf(buf, bufsz,
3179 /* TRANS: %s is the output type, like 'shield'
3180 * or 'gold'. */
3181 _("* You pay no %s upkeep for your units.\n"),
3182 astr_str(&outputs_or));
3183 } else {
3184 CATLSTR(buf, bufsz,
3185 _("* You pay no upkeep for your units.\n"));
3187 } else if (net_value != world_value) {
3188 double ratio = (double)net_value / world_value;
3189 if (output_type != O_LAST) {
3190 cat_snprintf(buf, bufsz,
3191 /* TRANS: %s is the output type, like 'shield'
3192 * or 'gold'. */
3193 _("* You pay %.2g times normal %s upkeep for your "
3194 "units.\n"),
3195 ratio, astr_str(&outputs_and));
3196 } else {
3197 cat_snprintf(buf, bufsz,
3198 _("* You pay %.2g times normal upkeep for your "
3199 "units.\n"),
3200 ratio);
3202 } /* else this effect somehow has no effect; keep quiet */
3203 } /* else there was some extra condition making it complicated */
3204 break;
3205 case EFT_UNIT_UPKEEP_FREE_PER_CITY:
3206 if (!unittype) {
3207 if (output_type != O_LAST) {
3208 cat_snprintf(buf, bufsz,
3209 /* TRANS: %s is the output type, like 'shield' or
3210 * 'gold'; pluralised in %d but there is currently
3211 * no way to control the singular/plural name of the
3212 * output type; sorry */
3213 PL_("* Each of your cities will avoid paying %d %s"
3214 " upkeep for your units.\n",
3215 "* Each of your cities will avoid paying %d %s"
3216 " upkeep for your units.\n", peffect->value),
3217 peffect->value, astr_str(&outputs_and));
3218 } else {
3219 cat_snprintf(buf, bufsz,
3220 /* TRANS: Amount is subtracted from upkeep cost
3221 * for each upkeep type. */
3222 PL_("* Each of your cities will avoid paying %d"
3223 " upkeep for your units.\n",
3224 "* Each of your cities will avoid paying %d"
3225 " upkeep for your units.\n", peffect->value),
3226 peffect->value);
3228 } /* else too complicated */
3229 break;
3230 case EFT_CIVIL_WAR_CHANCE:
3231 if (playerwide) {
3232 cat_snprintf(buf, bufsz,
3233 _("* If you lose your capital,"
3234 " the chance of civil war is %d%%.\n"),
3235 net_value);
3237 break;
3238 case EFT_EMPIRE_SIZE_BASE:
3239 if (playerwide) {
3240 cat_snprintf(buf, bufsz,
3241 PL_("* You can have %d city before an "
3242 "additional unhappy citizen appears in each city "
3243 "due to civilization size.\n",
3244 "* You can have up to %d cities before an "
3245 "additional unhappy citizen appears in each city "
3246 "due to civilization size.\n", net_value),
3247 net_value);
3249 break;
3250 case EFT_EMPIRE_SIZE_STEP:
3251 if (playerwide) {
3252 cat_snprintf(buf, bufsz,
3253 PL_("* After the first unhappy citizen due to"
3254 " civilization size, for each %d additional city"
3255 " another unhappy citizen will appear.\n",
3256 "* After the first unhappy citizen due to"
3257 " civilization size, for each %d additional cities"
3258 " another unhappy citizen will appear.\n",
3259 net_value),
3260 net_value);
3262 break;
3263 case EFT_MAX_RATES:
3264 if (playerwide && game.info.changable_tax) {
3265 if (net_value < 100) {
3266 cat_snprintf(buf, bufsz,
3267 _("* The maximum rate you can set for science,"
3268 " gold, or luxuries is %d%%.\n"),
3269 net_value);
3270 } else {
3271 CATLSTR(buf, bufsz,
3272 _("* Has unlimited science/gold/luxuries rates.\n"));
3275 break;
3276 case EFT_MARTIAL_LAW_EACH:
3277 if (playerwide) {
3278 cat_snprintf(buf, bufsz,
3279 PL_("* Your units may impose martial law."
3280 " Each military unit inside a city will force %d"
3281 " unhappy citizen to become content.\n",
3282 "* Your units may impose martial law."
3283 " Each military unit inside a city will force %d"
3284 " unhappy citizens to become content.\n",
3285 peffect->value),
3286 peffect->value);
3288 break;
3289 case EFT_MARTIAL_LAW_MAX:
3290 if (playerwide && net_value < 100) {
3291 cat_snprintf(buf, bufsz,
3292 PL_("* A maximum of %d unit in each city can enforce"
3293 " martial law.\n",
3294 "* A maximum of %d units in each city can enforce"
3295 " martial law.\n",
3296 net_value),
3297 net_value);
3299 break;
3300 case EFT_RAPTURE_GROW:
3301 if (playerwide && net_value > 0) {
3302 cat_snprintf(buf, bufsz,
3303 _("* You may grow your cities by means of "
3304 "celebrations."));
3305 if (game.info.celebratesize > 1) {
3306 cat_snprintf(buf, bufsz,
3307 /* TRANS: Preserve leading space. %d should always be
3308 * 2 or greater. */
3309 _(" (Cities below size %d cannot grow in this way.)"),
3310 game.info.celebratesize);
3312 cat_snprintf(buf, bufsz, "\n");
3314 break;
3315 case EFT_REVOLUTION_UNHAPPINESS:
3316 if (playerwide) {
3317 cat_snprintf(buf, bufsz,
3318 PL_("* If a city is in disorder for more than %d turn "
3319 "in a row, government will fall into anarchy.\n",
3320 "* If a city is in disorder for more than %d turns "
3321 "in a row, government will fall into anarchy.\n",
3322 net_value),
3323 net_value);
3325 break;
3326 case EFT_HAS_SENATE:
3327 if (playerwide && net_value > 0) {
3328 CATLSTR(buf, bufsz,
3329 _("* Has a senate that may prevent declaration of war.\n"));
3331 break;
3332 case EFT_INSPIRE_PARTISANS:
3333 if (playerwide && net_value > 0) {
3334 CATLSTR(buf, bufsz,
3335 _("* Allows partisans when cities are taken by the "
3336 "enemy.\n"));
3338 break;
3339 case EFT_HAPPINESS_TO_GOLD:
3340 if (playerwide && net_value > 0) {
3341 CATLSTR(buf, bufsz,
3342 _("* Buildings that normally confer bonuses against"
3343 " unhappiness will instead give gold.\n"));
3345 break;
3346 case EFT_FANATICS:
3347 if (playerwide && net_value > 0) {
3348 struct strvec *fanatics = strvec_new();
3349 struct astring fanaticstr = ASTRING_INIT;
3351 unit_type_iterate(putype) {
3352 if (utype_has_flag(putype, UTYF_FANATIC)) {
3353 strvec_append(fanatics, utype_name_translation(putype));
3355 } unit_type_iterate_end;
3356 cat_snprintf(buf, bufsz,
3357 /* TRANS: %s is list of unit types separated by 'or' */
3358 _("* Pays no upkeep for %s.\n"),
3359 strvec_to_or_list(fanatics, &fanaticstr));
3360 strvec_destroy(fanatics);
3361 astr_free(&fanaticstr);
3363 break;
3364 case EFT_NO_UNHAPPY:
3365 if (playerwide && net_value > 0) {
3366 CATLSTR(buf, bufsz, _("* Has no unhappy citizens.\n"));
3368 break;
3369 case EFT_VETERAN_BUILD:
3371 int conditions = 0;
3372 if (unitclass) {
3373 conditions++;
3375 if (unittype) {
3376 conditions++;
3378 if (unit_type_flag_id_is_valid(unitflag)) {
3379 conditions++;
3381 if (conditions > 1) {
3382 /* More than one requirement on units, too complicated for us
3383 * to describe. */
3384 break;
3386 if (unitclass) {
3387 /* FIXME: account for multiple veteran levels, or negative
3388 * values. This might lie for complicated rulesets! */
3389 cat_snprintf(buf, bufsz,
3390 /* TRANS: %s is a unit class */
3391 Q_("?unitclass:* New %s units will be veteran.\n"),
3392 uclass_name_translation(unitclass));
3393 } else if (unit_type_flag_id_is_valid(unitflag)) {
3394 /* FIXME: same problems as unitclass */
3395 cat_snprintf(buf, bufsz,
3396 /* TRANS: %s is a (translatable) unit type flag */
3397 Q_("?unitflag:* New %s units will be veteran.\n"),
3398 unit_type_flag_id_translated_name(unitflag));
3399 } else if (unittype != NULL) {
3400 if (world_value_valid && net_value > 0) {
3401 /* Here we can be specific about veteran level, and get
3402 * net value correct. */
3403 int maxlvl = utype_veteran_system(unittype)->levels - 1;
3404 const struct veteran_level *vlevel =
3405 utype_veteran_level(unittype, MIN(net_value, maxlvl));
3406 cat_snprintf(buf, bufsz,
3407 /* TRANS: "* New Partisan units will have the rank
3408 * of elite." */
3409 Q_("?unittype:* New %s units will have the rank "
3410 "of %s.\n"),
3411 utype_name_translation(unittype),
3412 name_translation_get(&vlevel->name));
3413 } /* else complicated */
3414 } else {
3415 /* No extra criteria. */
3416 /* FIXME: same problems as above */
3417 cat_snprintf(buf, bufsz,
3418 _("* New units will be veteran.\n"));
3421 break;
3422 case EFT_OUTPUT_PENALTY_TILE:
3423 if (world_value_valid) {
3424 cat_snprintf(buf, bufsz,
3425 /* TRANS: %s is list of output types, with 'or';
3426 * pluralised in %d but of course the output types
3427 * can't be pluralised; sorry */
3428 PL_("* Each worked tile that gives more than %d %s will"
3429 " suffer a -1 penalty, unless the city working it"
3430 " is celebrating.",
3431 "* Each worked tile that gives more than %d %s will"
3432 " suffer a -1 penalty, unless the city working it"
3433 " is celebrating.", net_value),
3434 net_value, astr_str(&outputs_or));
3435 if (game.info.celebratesize > 1) {
3436 cat_snprintf(buf, bufsz,
3437 /* TRANS: Preserve leading space. %d should always be
3438 * 2 or greater. */
3439 _(" (Cities below size %d will not celebrate.)"),
3440 game.info.celebratesize);
3442 cat_snprintf(buf, bufsz, "\n");
3444 break;
3445 case EFT_OUTPUT_INC_TILE_CELEBRATE:
3446 cat_snprintf(buf, bufsz,
3447 /* TRANS: %s is list of output types, with 'or' */
3448 PL_("* Each worked tile with at least 1 %s will yield"
3449 " %d more of it while the city working it is"
3450 " celebrating.",
3451 "* Each worked tile with at least 1 %s will yield"
3452 " %d more of it while the city working it is"
3453 " celebrating.", peffect->value),
3454 astr_str(&outputs_or), peffect->value);
3455 if (game.info.celebratesize > 1) {
3456 cat_snprintf(buf, bufsz,
3457 /* TRANS: Preserve leading space. %d should always be
3458 * 2 or greater. */
3459 _(" (Cities below size %d will not celebrate.)"),
3460 game.info.celebratesize);
3462 cat_snprintf(buf, bufsz, "\n");
3463 break;
3464 case EFT_OUTPUT_INC_TILE:
3465 cat_snprintf(buf, bufsz,
3466 /* TRANS: %s is list of output types, with 'or' */
3467 PL_("* Each worked tile with at least 1 %s will yield"
3468 " %d more of it.\n",
3469 "* Each worked tile with at least 1 %s will yield"
3470 " %d more of it.\n", peffect->value),
3471 astr_str(&outputs_or), peffect->value);
3472 break;
3473 case EFT_OUTPUT_BONUS:
3474 case EFT_OUTPUT_BONUS_2:
3475 /* FIXME: makes most sense iff world_value == 0 */
3476 cat_snprintf(buf, bufsz,
3477 /* TRANS: %s is list of output types, with 'and' */
3478 _("* %s production is increased %d%%.\n"),
3479 astr_str(&outputs_and), peffect->value);
3480 break;
3481 case EFT_OUTPUT_WASTE:
3482 if (world_value_valid) {
3483 if (net_value > 30) {
3484 cat_snprintf(buf, bufsz,
3485 /* TRANS: %s is list of output types, with 'and' */
3486 _("* %s production will suffer massive losses.\n"),
3487 astr_str(&outputs_and));
3488 } else if (net_value >= 15) {
3489 cat_snprintf(buf, bufsz,
3490 /* TRANS: %s is list of output types, with 'and' */
3491 _("* %s production will suffer some losses.\n"),
3492 astr_str(&outputs_and));
3493 } else if (net_value > 0) {
3494 cat_snprintf(buf, bufsz,
3495 /* TRANS: %s is list of output types, with 'and' */
3496 _("* %s production will suffer a small amount "
3497 "of losses.\n"),
3498 astr_str(&outputs_and));
3501 break;
3502 case EFT_HEALTH_PCT:
3503 if (playerwide) {
3504 if (peffect->value > 0) {
3505 CATLSTR(buf, bufsz, _("* Increases the chance of plague"
3506 " within your cities.\n"));
3507 } else if (peffect->value < 0) {
3508 CATLSTR(buf, bufsz, _("* Decreases the chance of plague"
3509 " within your cities.\n"));
3512 break;
3513 case EFT_OUTPUT_WASTE_BY_REL_DISTANCE:
3514 /* Semi-arbitrary scaling to get likely ruleset values in roughly
3515 * the same range as WASTE_BY_DISTANCE */
3516 /* FIXME: use different wording? */
3517 net_value = (net_value + 39) / 40; /* round up */
3518 /* fall through to: */
3519 case EFT_OUTPUT_WASTE_BY_DISTANCE:
3520 if (world_value_valid) {
3521 if (net_value >= 3) {
3522 cat_snprintf(buf, bufsz,
3523 /* TRANS: %s is list of output types, with 'and' */
3524 _("* %s losses will increase quickly"
3525 " with distance from capital.\n"),
3526 astr_str(&outputs_and));
3527 } else if (net_value == 2) {
3528 cat_snprintf(buf, bufsz,
3529 /* TRANS: %s is list of output types, with 'and' */
3530 _("* %s losses will increase"
3531 " with distance from capital.\n"),
3532 astr_str(&outputs_and));
3533 } else if (net_value > 0) {
3534 cat_snprintf(buf, bufsz,
3535 /* TRANS: %s is list of output types, with 'and' */
3536 _("* %s losses will increase slowly"
3537 " with distance from capital.\n"),
3538 astr_str(&outputs_and));
3541 break;
3542 case EFT_MIGRATION_PCT:
3543 if (playerwide) {
3544 if (peffect->value > 0) {
3545 CATLSTR(buf, bufsz, _("* Increases the chance of migration"
3546 " into your cities.\n"));
3547 } else if (peffect->value < 0) {
3548 CATLSTR(buf, bufsz, _("* Decreases the chance of migration"
3549 " into your cities.\n"));
3552 break;
3553 case EFT_BORDER_VISION:
3554 if (game.info.borders == BORDERS_ENABLED
3555 && playerwide && net_value > 0) {
3556 CATLSTR(buf, bufsz, _("* All tiles inside your borders are"
3557 " monitored.\n"));
3559 break;
3560 default:
3561 break;
3565 strvec_destroy(outputs);
3566 astr_free(&outputs_or);
3567 astr_free(&outputs_and);
3569 } effect_list_iterate_end;
3571 unit_type_iterate(utype) {
3572 if (utype->need_government == gov) {
3573 cat_snprintf(buf, bufsz,
3574 _("* Allows you to build %s.\n"),
3575 utype_name_translation(utype));
3577 } unit_type_iterate_end;
3579 /* Action immunity */
3580 action_iterate(act) {
3581 if (action_by_number(act)->quiet) {
3582 /* The ruleset documents this action it self. */
3583 continue;
3586 if (action_immune_government(gov, act)) {
3587 cat_snprintf(buf, bufsz,
3588 _("* Makes it impossible to do the action \'%s\'"
3589 " to your %s.\n"),
3590 action_id_name_translation(act),
3591 _(action_target_kind_name(
3592 action_id_get_target_kind(act))));
3594 } action_iterate_end;
3596 if (user_text && user_text[0] != '\0') {
3597 cat_snprintf(buf, bufsz, "\n%s", user_text);
3601 /****************************************************************
3602 Returns pointer to static string with eg: "1 shield, 1 unhappy"
3603 *****************************************************************/
3604 char *helptext_unit_upkeep_str(struct unit_type *utype)
3606 static char buf[128];
3607 int any = 0;
3609 if (!utype) {
3610 log_error("Unknown unit!");
3611 return "";
3614 buf[0] = '\0';
3615 output_type_iterate(o) {
3616 if (utype->upkeep[o] > 0) {
3617 /* TRANS: "2 Food" or ", 1 Shield" */
3618 cat_snprintf(buf, sizeof(buf), _("%s%d %s"),
3619 (any > 0 ? Q_("?blistmore:, ") : ""), utype->upkeep[o],
3620 get_output_name(o));
3621 any++;
3623 } output_type_iterate_end;
3624 if (utype->happy_cost > 0) {
3625 /* TRANS: "2 Unhappy" or ", 1 Unhappy" */
3626 cat_snprintf(buf, sizeof(buf), _("%s%d Unhappy"),
3627 (any > 0 ? Q_("?blistmore:, ") : ""), utype->happy_cost);
3628 any++;
3631 if (any == 0) {
3632 /* strcpy(buf, _("None")); */
3633 fc_snprintf(buf, sizeof(buf), "%d", 0);
3635 return buf;
3638 /****************************************************************************
3639 Returns nation legend and characteristics
3640 ****************************************************************************/
3641 void helptext_nation(char *buf, size_t bufsz, struct nation_type *pnation,
3642 const char *user_text)
3644 struct universal source = {
3645 .kind = VUT_NATION,
3646 .value = {.nation = pnation}
3648 bool print_break = TRUE;
3649 #define PRINT_BREAK() do { \
3650 if (print_break) { \
3651 if (buf[0] != '\0') { \
3652 CATLSTR(buf, bufsz, "\n\n"); \
3654 print_break = FALSE; \
3656 } while (FALSE)
3658 fc_assert_ret(NULL != buf && 0 < bufsz);
3659 buf[0] = '\0';
3661 if (pnation->legend[0] != '\0') {
3662 /* Client side legend is stored already translated */
3663 cat_snprintf(buf, bufsz, "%s", pnation->legend);
3666 if (pnation->init_government) {
3667 PRINT_BREAK();
3668 cat_snprintf(buf, bufsz,
3669 _("Initial government is %s.\n"),
3670 government_name_translation(pnation->init_government));
3672 if (pnation->init_techs[0] != A_LAST) {
3673 const char *tech_names[MAX_NUM_TECH_LIST];
3674 int i;
3675 struct astring list = ASTRING_INIT;
3677 for (i = 0; i < MAX_NUM_TECH_LIST; i++) {
3678 if (pnation->init_techs[i] == A_LAST) {
3679 break;
3681 tech_names[i] =
3682 advance_name_translation(advance_by_number(pnation->init_techs[i]));
3684 astr_build_and_list(&list, tech_names, i);
3685 PRINT_BREAK();
3686 if (game.rgame.global_init_techs[0] != A_LAST) {
3687 cat_snprintf(buf, bufsz,
3688 /* TRANS: %s is an and-separated list of techs */
3689 _("Starts with knowledge of %s in addition to the standard "
3690 "starting technologies.\n"), astr_str(&list));
3691 } else {
3692 cat_snprintf(buf, bufsz,
3693 /* TRANS: %s is an and-separated list of techs */
3694 _("Starts with knowledge of %s.\n"), astr_str(&list));
3696 astr_free(&list);
3698 if (pnation->init_units[0]) {
3699 const struct unit_type *utypes[MAX_NUM_UNIT_LIST];
3700 int count[MAX_NUM_UNIT_LIST];
3701 int i, j, n = 0, total = 0;
3703 /* Count how many of each type there is. */
3704 for (i = 0; i < MAX_NUM_UNIT_LIST; i++) {
3705 if (!pnation->init_units[i]) {
3706 break;
3708 for (j = 0; j < n; j++) {
3709 if (pnation->init_units[i] == utypes[j]) {
3710 count[j]++;
3711 total++;
3712 break;
3715 if (j == n) {
3716 utypes[n] = pnation->init_units[i];
3717 count[n] = 1;
3718 total++;
3719 n++;
3723 /* Construct the list of unit types and counts. */
3724 struct astring utype_names[MAX_NUM_UNIT_LIST];
3725 struct astring list = ASTRING_INIT;
3727 for (i = 0; i < n; i++) {
3728 astr_init(&utype_names[i]);
3729 if (count[i] > 1) {
3730 /* TRANS: a unit type followed by a count. For instance,
3731 * "Fighter (2)" means two Fighters. Count is never 1.
3732 * Used in a list. */
3733 astr_set(&utype_names[i], _("%s (%d)"),
3734 utype_name_translation(utypes[i]), count[i]);
3735 } else {
3736 astr_set(&utype_names[i], "%s", utype_name_translation(utypes[i]));
3740 const char *utype_name_strs[MAX_NUM_UNIT_LIST];
3742 for (i = 0; i < n; i++) {
3743 utype_name_strs[i] = astr_str(&utype_names[i]);
3745 astr_build_and_list(&list, utype_name_strs, n);
3747 for (i = 0; i < n; i++) {
3748 astr_free(&utype_names[i]);
3750 PRINT_BREAK();
3751 cat_snprintf(buf, bufsz,
3752 /* TRANS: %s is an and-separated list of unit types
3753 * possibly with counts. Plurality is in total number of
3754 * units represented. */
3755 PL_("Starts with the following additional unit: %s.\n",
3756 "Starts with the following additional units: %s.\n",
3757 total), astr_str(&list));
3758 astr_free(&list);
3761 if (pnation->init_buildings[0] != B_LAST) {
3762 const char *impr_names[MAX_NUM_BUILDING_LIST];
3763 int i;
3764 struct astring list = ASTRING_INIT;
3766 for (i = 0; i < MAX_NUM_BUILDING_LIST; i++) {
3767 if (pnation->init_buildings[i] == B_LAST) {
3768 break;
3770 impr_names[i] =
3771 improvement_name_translation(
3772 improvement_by_number(pnation->init_buildings[i]));
3774 astr_build_and_list(&list, impr_names, i);
3775 PRINT_BREAK();
3776 if (game.rgame.global_init_buildings[0] != B_LAST) {
3777 cat_snprintf(buf, bufsz,
3778 /* TRANS: %s is an and-separated list of improvements */
3779 _("First city will get %s for free in addition to the "
3780 "standard improvements.\n"), astr_str(&list));
3781 } else {
3782 cat_snprintf(buf, bufsz,
3783 /* TRANS: %s is an and-separated list of improvements */
3784 _("First city will get %s for free.\n"), astr_str(&list));
3786 astr_free(&list);
3789 if (buf[0] != '\0') {
3790 CATLSTR(buf, bufsz, "\n");
3792 insert_allows(&source, buf + strlen(buf), bufsz - strlen(buf));
3794 if (user_text && user_text[0] != '\0') {
3795 if (buf[0] != '\0') {
3796 CATLSTR(buf, bufsz, "\n");
3798 CATLSTR(buf, bufsz, user_text);
3800 #undef PRINT_BREAK