1 /***********************************************************************
2  Freeciv - Copyright (C) 2002 - The Freeciv Project
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; either version 2, or (at your option)
6    any later version.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 ***********************************************************************/
13 
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
17 
18 #include <stdarg.h>
19 #include <string.h>
20 #include <math.h> /* ceil */
21 
22 /* utility */
23 #include "astring.h"
24 #include "bitvector.h"
25 #include "fcintl.h"
26 #include "log.h"
27 #include "support.h"
28 
29 /* common */
30 #include "calendar.h"
31 #include "citizens.h"
32 #include "clientutils.h"
33 #include "combat.h"
34 #include "culture.h"
35 #include "fc_types.h" /* LINE_BREAK */
36 #include "game.h"
37 #include "government.h"
38 #include "map.h"
39 #include "research.h"
40 #include "traderoutes.h"
41 #include "unitlist.h"
42 
43 /* client */
44 #include "client_main.h"
45 #include "climap.h"
46 #include "climisc.h"
47 #include "control.h"
48 #include "goto.h"
49 
50 #include "text.h"
51 
52 
53 static int get_bulbs_per_turn(int *pours, bool *pteam, int *ptheirs);
54 
55 /****************************************************************************
56   Return a (static) string with a tile's food/prod/trade
57 ****************************************************************************/
get_tile_output_text(const struct tile * ptile)58 const char *get_tile_output_text(const struct tile *ptile)
59 {
60   static struct astring str = ASTRING_INIT;
61   int i;
62   char output_text[O_LAST][16];
63 
64   for (i = 0; i < O_LAST; i++) {
65     int before_penalty = 0;
66     int x = city_tile_output(NULL, ptile, FALSE, i);
67 
68     if (NULL != client.conn.playing) {
69       before_penalty = get_player_output_bonus(client.conn.playing,
70                                                get_output_type(i),
71                                                EFT_OUTPUT_PENALTY_TILE);
72     }
73 
74     if (before_penalty > 0 && x > before_penalty) {
75       fc_snprintf(output_text[i], sizeof(output_text[i]), "%d(-1)", x);
76     } else {
77       fc_snprintf(output_text[i], sizeof(output_text[i]), "%d", x);
78     }
79   }
80 
81   astr_set(&str, "%s/%s/%s", output_text[O_FOOD],
82            output_text[O_SHIELD], output_text[O_TRADE]);
83 
84   return astr_str(&str);
85 }
86 
87 /****************************************************************************
88   For AIs, fill the buffer with their player name prefixed with "AI". For
89   humans, just fill it with their username.
90 ****************************************************************************/
get_full_username(char * buf,int buflen,const struct player * pplayer)91 static inline void get_full_username(char *buf, int buflen,
92                                      const struct player *pplayer)
93 {
94   if (!buf || buflen < 1) {
95     return;
96   }
97 
98   if (!pplayer) {
99     buf[0] = '\0';
100     return;
101   }
102 
103   if (pplayer->ai_controlled) {
104     /* TRANS: "AI <player name>" */
105     fc_snprintf(buf, buflen, _("AI %s"), pplayer->name);
106   } else {
107     fc_strlcpy(buf, pplayer->username, buflen);
108   }
109 }
110 
111 /****************************************************************************
112   Fill the buffer with the player's nation name (in adjective form) and
113   optionally add the player's team name.
114 ****************************************************************************/
get_full_nation(char * buf,int buflen,const struct player * pplayer)115 static inline void get_full_nation(char *buf, int buflen,
116                                    const struct player *pplayer)
117 {
118   if (!buf || buflen < 1) {
119     return;
120   }
121 
122   if (!pplayer) {
123     buf[0] = '\0';
124     return;
125   }
126 
127   if (pplayer->team) {
128     /* TRANS: "<nation adjective>, team <team name>" */
129     fc_snprintf(buf, buflen, _("%s, team %s"),
130                 nation_adjective_for_player(pplayer),
131                 team_name_translation(pplayer->team));
132   } else {
133     fc_strlcpy(buf, nation_adjective_for_player(pplayer), buflen);
134   }
135 }
136 
137 /****************************************************************************
138   Text to popup on a middle-click in the mapview.
139 ****************************************************************************/
popup_info_text(struct tile * ptile)140 const char *popup_info_text(struct tile *ptile)
141 {
142   const char *activity_text;
143   struct city *pcity = tile_city(ptile);
144   struct unit *punit = find_visible_unit(ptile);
145   const char *diplo_nation_plural_adjectives[DS_LAST] =
146     {"" /* unused, DS_ARMISTICE */, Q_("?nation:Hostile"),
147      "" /* unused, DS_CEASEFIRE */,
148      Q_("?nation:Peaceful"), Q_("?nation:Friendly"),
149      Q_("?nation:Mysterious"), Q_("?nation:Friendly(team)")};
150   const char *diplo_city_adjectives[DS_LAST] =
151     {"" /* unused, DS_ARMISTICE */, Q_("?city:Hostile"),
152      "" /* unused, DS_CEASEFIRE */,
153      Q_("?city:Peaceful"), Q_("?city:Friendly"), Q_("?city:Mysterious"),
154      Q_("?city:Friendly(team)")};
155   static struct astring str = ASTRING_INIT;
156   char username[MAX_LEN_NAME + 32];
157   char nation[2 * MAX_LEN_NAME + 32];
158   int tile_x, tile_y, nat_x, nat_y;
159   bool first;
160 
161   astr_clear(&str);
162   index_to_map_pos(&tile_x, &tile_y, tile_index(ptile));
163   astr_add_line(&str, _("Location: (%d, %d) [%d]"),
164                 tile_x, tile_y, tile_continent(ptile));
165   index_to_native_pos(&nat_x, &nat_y, tile_index(ptile));
166   astr_add_line(&str, _("Native coordinates: (%d, %d)"),
167                 nat_x, nat_y);
168 
169   if (client_tile_get_known(ptile) == TILE_UNKNOWN) {
170     astr_add(&str, _("Unknown"));
171     return astr_str(&str);
172   }
173   astr_add_line(&str, _("Terrain: %s"),  tile_get_info_text(ptile, TRUE, 0));
174   astr_add_line(&str, _("Food/Prod/Trade: %s"),
175 		get_tile_output_text(ptile));
176   first = TRUE;
177   extra_type_iterate(pextra) {
178     if (pextra->category == ECAT_BONUS && tile_has_visible_extra(ptile, pextra)) {
179       if (!first) {
180         astr_add(&str, ",%s", extra_name_translation(pextra));
181       } else {
182         astr_add_line(&str, "%s", extra_name_translation(pextra));
183         first = FALSE;
184       }
185     }
186   } extra_type_iterate_end;
187   if (BORDERS_DISABLED != game.info.borders && !pcity) {
188     struct player *owner = tile_owner(ptile);
189 
190     get_full_username(username, sizeof(username), owner);
191     get_full_nation(nation, sizeof(nation), owner);
192 
193     if (NULL != client.conn.playing && owner == client.conn.playing) {
194       astr_add_line(&str, _("Our territory"));
195     } else if (NULL != owner && NULL == client.conn.playing) {
196       /* TRANS: "Territory of <username> (<nation + team>)" */
197       astr_add_line(&str, _("Territory of %s (%s)"), username, nation);
198     } else if (NULL != owner) {
199       struct player_diplstate *ds = player_diplstate_get(client.conn.playing,
200                                                          owner);
201 
202       if (ds->type == DS_CEASEFIRE) {
203         int turns = ds->turns_left;
204 
205         astr_add_line(&str,
206                       /* TRANS: "Territory of <username> (<nation + team>)
207                        * (<number> turn cease-fire)" */
208                       PL_("Territory of %s (%s) (%d turn cease-fire)",
209                           "Territory of %s (%s) (%d turn cease-fire)",
210                           turns),
211                       username, nation, turns);
212       } else if (ds->type == DS_ARMISTICE) {
213         int turns = ds->turns_left;
214 
215         astr_add_line(&str,
216                       /* TRANS: "Territory of <username> (<nation + team>)
217                        * (<number> turn armistice)" */
218                       PL_("Territory of %s (%s) (%d turn armistice)",
219                           "Territory of %s (%s) (%d turn armistice)",
220                           turns),
221                       username, nation, turns);
222       } else {
223         int type = ds->type;
224 
225         /* TRANS: "Territory of <username>
226          * (<nation + team> | <diplomatic state>)" */
227         astr_add_line(&str, _("Territory of %s (%s | %s)"),
228                       username, nation,
229                       diplo_nation_plural_adjectives[type]);
230       }
231     } else {
232       astr_add_line(&str, _("Unclaimed territory"));
233     }
234   }
235   if (pcity) {
236     /* Look at city owner, not tile owner (the two should be the same, if
237      * borders are in use). */
238     struct player *owner = city_owner(pcity);
239     const char *improvements[improvement_count()];
240     int has_improvements = 0;
241 
242     get_full_username(username, sizeof(username), owner);
243     get_full_nation(nation, sizeof(nation), owner);
244 
245     if (NULL == client.conn.playing || owner == client.conn.playing) {
246       /* TRANS: "City: <city name> | <username> (<nation + team>)" */
247       astr_add_line(&str, _("City: %s | %s (%s)"),
248                     city_name_get(pcity), username, nation);
249     } else {
250       struct player_diplstate *ds
251         = player_diplstate_get(client_player(), owner);
252       if (ds->type == DS_CEASEFIRE) {
253         int turns = ds->turns_left;
254 
255         /* TRANS:  "City: <city name> | <username>
256          * (<nation + team>, <number> turn cease-fire)" */
257         astr_add_line(&str, PL_("City: %s | %s (%s, %d turn cease-fire)",
258                                 "City: %s | %s (%s, %d turn cease-fire)",
259                                 turns),
260                       city_name_get(pcity), username, nation, turns);
261       } else if (ds->type == DS_ARMISTICE) {
262         int turns = ds->turns_left;
263 
264         /* TRANS:  "City: <city name> | <username>
265          * (<nation + team>, <number> turn armistice)" */
266         astr_add_line(&str, PL_("City: %s | %s (%s, %d turn armistice)",
267                                 "City: %s | %s (%s, %d turn armistice)",
268                                 turns),
269                       city_name_get(pcity), username, nation, turns);
270       } else {
271         /* TRANS: "City: <city name> | <username>
272          * (<nation + team>, <diplomatic state>)" */
273         astr_add_line(&str, _("City: %s | %s (%s, %s)"),
274                       city_name_get(pcity), username, nation,
275                       diplo_city_adjectives[ds->type]);
276       }
277     }
278     if (can_player_see_units_in_city(client_player(), pcity)) {
279       int count = unit_list_size(ptile->units);
280 
281       if (count > 0) {
282         /* TRANS: preserve leading space */
283         astr_add(&str, PL_(" | Occupied with %d unit.",
284                                 " | Occupied with %d units.", count), count);
285       } else {
286         /* TRANS: preserve leading space */
287         astr_add(&str, _(" | Not occupied."));
288       }
289     } else {
290       if (pcity->client.occupied) {
291         /* TRANS: preserve leading space */
292         astr_add(&str, _(" | Occupied."));
293       } else {
294         /* TRANS: preserve leading space */
295         astr_add(&str, _(" | Not occupied."));
296       }
297     }
298     improvement_iterate(pimprove) {
299       if (is_improvement_visible(pimprove)
300           && city_has_building(pcity, pimprove)) {
301         improvements[has_improvements++] =
302             improvement_name_translation(pimprove);
303       }
304     } improvement_iterate_end;
305 
306     if (0 < has_improvements) {
307       struct astring list = ASTRING_INIT;
308 
309       astr_build_and_list(&list, improvements, has_improvements);
310       /* TRANS: %s is a list of "and"-separated improvements. */
311       astr_add_line(&str, _("   with %s."), astr_str(&list));
312       astr_free(&list);
313     }
314 
315     unit_list_iterate(get_units_in_focus(), pfocus_unit) {
316       struct city *hcity = game_city_by_number(pfocus_unit->homecity);
317 
318       if (utype_can_do_action(unit_type_get(pfocus_unit), ACTION_TRADE_ROUTE)
319           && can_cities_trade(hcity, pcity)
320           && can_establish_trade_route(hcity, pcity)) {
321 	/* TRANS: "Trade from Warsaw: 5" */
322 	astr_add_line(&str, _("Trade from %s: %d"),
323                       city_name_get(hcity),
324                       trade_between_cities(hcity, pcity));
325       }
326     } unit_list_iterate_end;
327   }
328   {
329     const char *infratext = get_infrastructure_text(ptile->extras);
330 
331     if (*infratext != '\0') {
332       astr_add_line(&str, _("Infrastructure: %s"), infratext);
333     }
334   }
335   activity_text = concat_tile_activity_text(ptile);
336   if (strlen(activity_text) > 0) {
337     astr_add_line(&str, _("Activity: %s"), activity_text);
338   }
339   if (punit && !pcity) {
340     struct player *owner = unit_owner(punit);
341     struct unit_type *ptype = unit_type_get(punit);
342 
343     get_full_username(username, sizeof(username), owner);
344     get_full_nation(nation, sizeof(nation), owner);
345 
346     if (!client_player() || owner == client_player()) {
347       struct city *hcity = player_city_by_number(owner, punit->homecity);
348 
349       /* TRANS: "Unit: <unit type> | <username> (<nation + team>)" */
350       astr_add_line(&str, _("Unit: %s | %s (%s)"),
351                     utype_name_translation(ptype), username, nation);
352 
353       if (game.info.citizen_nationality
354           && unit_nationality(punit) != unit_owner(punit)) {
355         if (hcity != NULL) {
356           /* TRANS: on own line immediately following \n, "from <city> |
357            * <nationality> people" */
358           astr_add_line(&str, _("from %s | %s people"), city_name_get(hcity),
359                         nation_adjective_for_player(unit_nationality(punit)));
360         } else {
361           /* TRANS: Nationality of the people comprising a unit, if
362            * different from owner. */
363           astr_add_line(&str, _("%s people"),
364                         nation_adjective_for_player(unit_nationality(punit)));
365         }
366       } else if (hcity != NULL) {
367         /* TRANS: on own line immediately following \n, ... <city> */
368         astr_add_line(&str, _("from %s"), city_name_get(hcity));
369       }
370     } else if (NULL != owner) {
371       struct player_diplstate *ds = player_diplstate_get(client_player(),
372                                                          owner);
373       if (ds->type == DS_CEASEFIRE) {
374         int turns = ds->turns_left;
375 
376         /* TRANS:  "Unit: <unit type> | <username> (<nation + team>,
377          * <number> turn cease-fire)" */
378         astr_add_line(&str, PL_("Unit: %s | %s (%s, %d turn cease-fire)",
379                                 "Unit: %s | %s (%s, %d turn cease-fire)",
380                                 turns),
381                       utype_name_translation(ptype),
382                       username, nation, turns);
383       } else if (ds->type == DS_ARMISTICE) {
384         int turns = ds->turns_left;
385 
386         /* TRANS:  "Unit: <unit type> | <username> (<nation + team>,
387          * <number> turn armistice)" */
388         astr_add_line(&str, PL_("Unit: %s | %s (%s, %d turn armistice)",
389                                 "Unit: %s | %s (%s, %d turn armistice)",
390                                 turns),
391                       utype_name_translation(ptype),
392                       username, nation, turns);
393       } else {
394         /* TRANS: "Unit: <unit type> | <username> (<nation + team>,
395          * <diplomatic state>)" */
396         astr_add_line(&str, _("Unit: %s | %s (%s, %s)"),
397                       utype_name_translation(ptype), username, nation,
398                       diplo_city_adjectives[ds->type]);
399       }
400     }
401 
402     unit_list_iterate(get_units_in_focus(), pfocus_unit) {
403       int att_chance = FC_INFINITY, def_chance = FC_INFINITY;
404       bool found = FALSE;
405 
406       unit_list_iterate(ptile->units, tile_unit) {
407 	if (unit_owner(tile_unit) != unit_owner(pfocus_unit)) {
408 	  int att = unit_win_chance(pfocus_unit, tile_unit) * 100;
409 	  int def = (1.0 - unit_win_chance(tile_unit, pfocus_unit)) * 100;
410 
411 	  found = TRUE;
412 
413 	  /* Presumably the best attacker and defender will be used. */
414 	  att_chance = MIN(att, att_chance);
415 	  def_chance = MIN(def, def_chance);
416 	}
417       } unit_list_iterate_end;
418 
419       if (found) {
420 	/* TRANS: "Chance to win: A:95% D:46%" */
421 	astr_add_line(&str, _("Chance to win: A:%d%% D:%d%%"),
422 		      att_chance, def_chance);
423       }
424     } unit_list_iterate_end;
425 
426     /* TRANS: A is attack power, D is defense power, FP is firepower,
427      * HP is hitpoints (current and max). */
428     astr_add_line(&str, _("A:%d D:%d FP:%d HP:%d/%d"),
429                   ptype->attack_strength, ptype->defense_strength,
430                   ptype->firepower, punit->hp, ptype->hp);
431     {
432       const char *veteran_name =
433         utype_veteran_name_translation(ptype, punit->veteran);
434       if (veteran_name) {
435         astr_add(&str, " (%s)", veteran_name);
436       }
437     }
438 
439     if (unit_owner(punit) == client_player()
440         || client_is_global_observer()) {
441       /* Show bribe cost for own units. */
442       astr_add_line(&str, _("Probable bribe cost: %d"),
443                     unit_bribe_cost(punit, NULL));
444     } else {
445       /* We can only give an (lower) boundary for units of other players. */
446       astr_add_line(&str, _("Estimated bribe cost: > %d"),
447                     unit_bribe_cost(punit, client_player()));
448     }
449 
450     if ((NULL == client.conn.playing || owner == client.conn.playing)
451         && unit_list_size(ptile->units) >= 2) {
452       /* TRANS: "5 more" units on this tile */
453       astr_add(&str, _("  (%d more)"), unit_list_size(ptile->units) - 1);
454     }
455   }
456 
457   astr_break_lines(&str, LINE_BREAK);
458   return astr_str(&str);
459 }
460 
461 #define FAR_CITY_SQUARE_DIST (2*(6*6))
462 /****************************************************************************
463   Returns the text describing the city and its distance.
464 ****************************************************************************/
get_nearest_city_text(struct city * pcity,int sq_dist)465 const char *get_nearest_city_text(struct city *pcity, int sq_dist)
466 {
467   static struct astring str = ASTRING_INIT;
468 
469   astr_clear(&str);
470 
471   /* just to be sure */
472   if (!pcity) {
473     sq_dist = -1;
474   }
475 
476   astr_add(&str, (sq_dist >= FAR_CITY_SQUARE_DIST)
477            /* TRANS: on own line immediately following \n, ... <city> */
478            ? _("far from %s")
479            : (sq_dist > 0)
480            /* TRANS: on own line immediately following \n, ... <city> */
481            ? _("near %s")
482            : (sq_dist == 0)
483            /* TRANS: on own line immediately following \n, ... <city> */
484            ? _("in %s")
485            : "%s",
486            pcity
487            ? city_name_get(pcity)
488            : "");
489 
490   return astr_str(&str);
491 }
492 
493 /****************************************************************************
494   Returns the unit description.
495   Used in e.g. city report tooltips.
496 
497   FIXME: This function is not re-entrant because it returns a pointer to
498   static data.
499 ****************************************************************************/
unit_description(struct unit * punit)500 const char *unit_description(struct unit *punit)
501 {
502   int pcity_near_dist;
503   struct player *owner = unit_owner(punit);
504   struct player *nationality = unit_nationality(punit);
505   struct city *pcity =
506       player_city_by_number(owner, punit->homecity);
507   struct city *pcity_near = get_nearest_city(punit, &pcity_near_dist);
508   struct unit_type *ptype = unit_type_get(punit);
509   static struct astring str = ASTRING_INIT;
510   const struct player *pplayer = client_player();
511 
512   astr_clear(&str);
513 
514   astr_add(&str, "%s", utype_name_translation(ptype));
515 
516   {
517     const char *veteran_name =
518       utype_veteran_name_translation(ptype, punit->veteran);
519     if (veteran_name) {
520       astr_add(&str, " (%s)", veteran_name);
521     }
522   }
523 
524   if (pplayer == owner) {
525     unit_upkeep_astr(punit, &str);
526   } else {
527     astr_add(&str, "\n");
528   }
529   unit_activity_astr(punit, &str);
530 
531   if (pcity) {
532     /* TRANS: on own line immediately following \n, ... <city> */
533     astr_add_line(&str, _("from %s"), city_name_get(pcity));
534   } else {
535     astr_add(&str, "\n");
536   }
537   if (game.info.citizen_nationality) {
538     if (nationality != NULL && owner != nationality) {
539       /* TRANS: Nationality of the people comprising a unit, if
540        * different from owner. */
541       astr_add_line(&str, _("%s people"),
542                     nation_adjective_for_player(nationality));
543     } else {
544       astr_add(&str, "\n");
545     }
546   }
547 
548   astr_add_line(&str, "%s",
549 		get_nearest_city_text(pcity_near, pcity_near_dist));
550 #ifdef DEBUG
551   astr_add_line(&str, "Unit ID: %d", punit->id);
552 #endif
553 
554   return astr_str(&str);
555 }
556 
557 /****************************************************************************
558   Describe the airlift capacity of a city for the given units (from their
559   current positions).
560   If pdest is non-NULL, describe its capacity as a destination, otherwise
561   describe the capacity of the city the unit's currently in (if any) as a
562   source. (If the units in the list are in different cities, this will
563   probably not give a useful result in this case.)
564   If not all of the listed units can be airlifted, return the description
565   for those that can.
566   Returns NULL if an airlift is not possible for any of the units.
567 ****************************************************************************/
get_airlift_text(const struct unit_list * punits,const struct city * pdest)568 const char *get_airlift_text(const struct unit_list *punits,
569                              const struct city *pdest)
570 {
571   static struct astring str = ASTRING_INIT;
572   bool src = (pdest == NULL);
573   enum texttype { AL_IMPOSSIBLE, AL_UNKNOWN, AL_FINITE, AL_INFINITE }
574       best = AL_IMPOSSIBLE;
575   int cur = 0, max = 0;
576 
577   unit_list_iterate(punits, punit) {
578     enum texttype this = AL_IMPOSSIBLE;
579     enum unit_airlift_result result;
580 
581     /* NULL will tell us about the capability of airlifting from source */
582     result = test_unit_can_airlift_to(client_player(), punit, pdest);
583 
584     switch(result) {
585     case AR_NO_MOVES:
586     case AR_WRONG_UNITTYPE:
587     case AR_OCCUPIED:
588     case AR_NOT_IN_CITY:
589     case AR_BAD_SRC_CITY:
590     case AR_BAD_DST_CITY:
591       /* No chance of an airlift. */
592       this = AL_IMPOSSIBLE;
593       break;
594     case AR_OK:
595     case AR_OK_SRC_UNKNOWN:
596     case AR_OK_DST_UNKNOWN:
597     case AR_SRC_NO_FLIGHTS:
598     case AR_DST_NO_FLIGHTS:
599       /* May or may not be able to airlift now, but there's a chance we could
600        * later */
601       {
602         const struct city *pcity = src ? tile_city(unit_tile(punit)) : pdest;
603         fc_assert_ret_val(pcity != NULL, fc_strdup("-"));
604         if (!src && (game.info.airlifting_style & AIRLIFTING_UNLIMITED_DEST)) {
605           /* No restrictions on destination (and we can infer this even for
606            * other players' cities). */
607           this = AL_INFINITE;
608         } else if (client_player() == city_owner(pcity)) {
609           /* A city we know about. */
610           int this_cur = pcity->airlift, this_max = city_airlift_max(pcity);
611           if (this_max <= 0) {
612             /* City known not to be airlift-capable. */
613             this = AL_IMPOSSIBLE;
614           } else {
615             if (src
616                 && (game.info.airlifting_style & AIRLIFTING_UNLIMITED_SRC)) {
617               /* Unlimited capacity. */
618               this = AL_INFINITE;
619             } else {
620               /* Limited capacity (possibly zero right now). */
621               this = AL_FINITE;
622               /* Store the numbers. This whole setup assumes that numeric
623                * capacity isn't unit-dependent. */
624               if (best == AL_FINITE) {
625                 fc_assert(cur == this_cur && max == this_max);
626               }
627               cur = this_cur;
628               max = this_max;
629             }
630           }
631         } else {
632           /* Unknown capacity. */
633           this = AL_UNKNOWN;
634         }
635       }
636       break;
637     }
638 
639     /* Now take the most optimistic view. */
640     best = MAX(best, this);
641   } unit_list_iterate_end;
642 
643   switch(best) {
644   case AL_IMPOSSIBLE:
645     return NULL;
646   case AL_UNKNOWN:
647     astr_set(&str, "?");
648     break;
649   case AL_FINITE:
650     astr_set(&str, "%d/%d", cur, max);
651     break;
652   case AL_INFINITE:
653     astr_set(&str, _("Yes"));
654     break;
655   }
656 
657   return astr_str(&str);
658 }
659 
660 /****************************************************************************
661   Return total expected bulbs.
662 ****************************************************************************/
get_bulbs_per_turn(int * pours,bool * pteam,int * ptheirs)663 static int get_bulbs_per_turn(int *pours, bool *pteam, int *ptheirs)
664 {
665   const struct research *presearch;
666   int ours = 0, theirs = 0;
667   bool team = FALSE;
668 
669   if (!client_has_player()) {
670     return 0;
671   }
672   presearch = research_get(client_player());
673 
674   /* Sum up science */
675   research_players_iterate(presearch, pplayer) {
676     if (pplayer == client_player()) {
677       city_list_iterate(pplayer->cities, pcity) {
678         ours += pcity->surplus[O_SCIENCE];
679       } city_list_iterate_end;
680     } else {
681       team = TRUE;
682       theirs -= pplayer->client.tech_upkeep;
683     }
684   } research_players_iterate_end;
685 
686   if (team) {
687     theirs += presearch->client.total_bulbs_prod - ours;
688   }
689   ours -= client_player()->client.tech_upkeep;
690 
691   if (pours) {
692     *pours = ours;
693   }
694   if (pteam) {
695     *pteam = team;
696   }
697   if (ptheirs) {
698     *ptheirs = theirs;
699   }
700   return ours + theirs;
701 }
702 
703 /************************************************************************//**
704   Return turns until research complete. -1 for never.
705 ****************************************************************************/
turns_to_research_done(const struct research * presearch,int per_turn)706 static int turns_to_research_done(const struct research *presearch,
707                                   int per_turn)
708 {
709   if (per_turn > 0) {
710     return ceil((double)(presearch->client.researching_cost
711                          - presearch->bulbs_researched) / per_turn);
712   } else {
713     return -1;
714   }
715 }
716 
717 /************************************************************************//**
718   Return turns per advance (based on currently researched advance).
719   -1 for no progress.
720 ****************************************************************************/
turns_per_advance(const struct research * presearch,int per_turn)721 static int turns_per_advance(const struct research *presearch, int per_turn)
722 {
723   if (per_turn > 0) {
724     return MAX(1, ceil((double)presearch->client.researching_cost) / per_turn);
725   } else {
726     return -1;
727   }
728 }
729 
730 /************************************************************************//**
731   Return turns until an advance is lost due to tech upkeep.
732   -1 if we're not on the way to losing an advance.
733 ****************************************************************************/
turns_to_tech_loss(const struct research * presearch,int per_turn)734 static int turns_to_tech_loss(const struct research *presearch, int per_turn)
735 {
736   if (per_turn >= 0 || game.info.techloss_forgiveness == -1) {
737     /* With techloss_forgiveness == -1, we'll never lose a tech, just
738      * get further into debt. */
739     return -1;
740   } else {
741     int bulbs_to_loss = presearch->bulbs_researched
742                         + (presearch->client.researching_cost
743                            * game.info.techloss_forgiveness / 100);
744 
745     return ceil((double)bulbs_to_loss / -per_turn);
746   }
747 }
748 
749 /****************************************************************************
750   Returns the text to display in the science dialog.
751 ****************************************************************************/
science_dialog_text(void)752 const char *science_dialog_text(void)
753 {
754   bool team;
755   int ours, theirs, perturn, upkeep;
756   static struct astring str = ASTRING_INIT;
757   struct astring ourbuf = ASTRING_INIT, theirbuf = ASTRING_INIT;
758   struct research *research;
759 
760   astr_clear(&str);
761 
762   perturn = get_bulbs_per_turn(&ours, &team, &theirs);
763 
764   research = research_get(client_player());
765   upkeep = client_player()->client.tech_upkeep;
766 
767   if (NULL == client.conn.playing || (ours == 0 && theirs == 0
768                                       && upkeep == 0)) {
769     return _("Progress: no research");
770   }
771 
772   if (A_UNSET == research->researching) {
773     astr_add(&str, _("Progress: no research"));
774   } else {
775     int turns;
776 
777     if ((turns = turns_per_advance(research, perturn)) >= 0) {
778       astr_add(&str, PL_("Progress: %d turn/advance",
779                          "Progress: %d turns/advance",
780                          turns), turns);
781     } else if ((turns = turns_to_tech_loss(research, perturn)) >= 0) {
782       /* FIXME: turns to next loss is not a good predictor of turns to
783        * following loss, due to techloss_restore etc. But it'll do. */
784       astr_add(&str, PL_("Progress: %d turn/advance loss",
785                          "Progress: %d turns/advance loss",
786                          turns), turns);
787     } else {
788       /* no forward progress -- no research, or tech loss disallowed */
789       if (perturn < 0) {
790         astr_add(&str, _("Progress: decreasing!"));
791       } else {
792         astr_add(&str, _("Progress: none"));
793       }
794     }
795   }
796   astr_set(&ourbuf, PL_("%d bulb/turn", "%d bulbs/turn", ours), ours);
797   if (team) {
798     /* Techpool version */
799     astr_set(&theirbuf,
800              /* TRANS: This is appended to "%d bulb/turn" text */
801              PL_(", %d bulb/turn from team",
802                  ", %d bulbs/turn from team", theirs), theirs);
803   } else {
804     astr_clear(&theirbuf);
805   }
806   astr_add(&str, " (%s%s)", astr_str(&ourbuf), astr_str(&theirbuf));
807   astr_free(&ourbuf);
808   astr_free(&theirbuf);
809 
810   if (game.info.tech_upkeep_style != TECH_UPKEEP_NONE) {
811     /* perturn is defined as: (bulbs produced) - upkeep */
812     astr_add_line(&str, _("Bulbs produced per turn: %d"), perturn + upkeep);
813     /* TRANS: keep leading space; appended to "Bulbs produced per turn: %d" */
814     astr_add(&str, _(" (needed for technology upkeep: %d)"), upkeep);
815   }
816 
817   return astr_str(&str);
818 }
819 
820 /****************************************************************************
821   Get the short science-target text.  This is usually shown directly in
822   the progress bar.
823 
824      5/28 - 3 turns
825 
826   The "percent" value, if given, will be set to the completion percentage
827   of the research target (actually it's a [0,1] scale not a percent).
828 ****************************************************************************/
get_science_target_text(double * percent)829 const char *get_science_target_text(double *percent)
830 {
831   struct research *research = research_get(client_player());
832   static struct astring str = ASTRING_INIT;
833 
834   if (!research) {
835     return "-";
836   }
837 
838   astr_clear(&str);
839   if (research->researching == A_UNSET) {
840     astr_add(&str, _("%d/- (never)"), research->bulbs_researched);
841     if (percent) {
842       *percent = 0.0;
843     }
844   } else {
845     int total = research->client.researching_cost;
846     int done = research->bulbs_researched;
847     int perturn = get_bulbs_per_turn(NULL, NULL, NULL);
848     int turns;
849 
850     if ((turns = turns_to_research_done(research, perturn)) >= 0) {
851       astr_add(&str, PL_("%d/%d (%d turn)", "%d/%d (%d turns)", turns),
852                done, total, turns);
853     } else if ((turns = turns_to_tech_loss(research, perturn)) >= 0) {
854       astr_add(&str, PL_("%d/%d (%d turn to loss)",
855                          "%d/%d (%d turns to loss)", turns),
856                done, total, turns);
857     } else {
858       /* no forward progress -- no research, or tech loss disallowed */
859       astr_add(&str, _("%d/%d (never)"), done, total);
860     }
861     if (percent) {
862       *percent = (double)done / (double)total;
863       *percent = CLIP(0.0, *percent, 1.0);
864     }
865   }
866 
867   return astr_str(&str);
868 }
869 
870 /****************************************************************************
871   Set the science-goal-label text as if we're researching the given goal.
872 ****************************************************************************/
get_science_goal_text(Tech_type_id goal)873 const char *get_science_goal_text(Tech_type_id goal)
874 {
875   const struct research *research = research_get(client_player());
876   int steps = research_goal_unknown_techs(research, goal);
877   int bulbs_needed = research_goal_bulbs_required(research, goal);
878   int turns;
879   int perturn = get_bulbs_per_turn(NULL, NULL, NULL);
880   static struct astring str = ASTRING_INIT;
881   struct astring buf1 = ASTRING_INIT,
882                  buf2 = ASTRING_INIT,
883                  buf3 = ASTRING_INIT;
884 
885   if (!research) {
886     return "-";
887   }
888 
889   astr_clear(&str);
890 
891   if (research_goal_tech_req(research, goal, research->researching)
892       || research->researching == goal) {
893     bulbs_needed -= research->bulbs_researched;
894   }
895 
896   astr_set(&buf1,
897            PL_("%d step", "%d steps", steps), steps);
898   astr_set(&buf2,
899            PL_("%d bulb", "%d bulbs", bulbs_needed), bulbs_needed);
900   if (perturn > 0) {
901     turns = (bulbs_needed + perturn - 1) / perturn;
902     astr_set(&buf3,
903              PL_("%d turn", "%d turns", turns), turns);
904   } else {
905     astr_set(&buf3, _("never"));
906   }
907   astr_add_line(&str, "(%s - %s - %s)",
908                 astr_str(&buf1), astr_str(&buf2), astr_str(&buf3));
909   astr_free(&buf1);
910   astr_free(&buf2);
911   astr_free(&buf3);
912 
913   return astr_str(&str);
914 }
915 
916 /****************************************************************************
917   Return the text for the label on the info panel.  (This is traditionally
918   shown to the left of the mapview.)
919 
920   Clicking on this text should bring up the get_info_label_text_popup text.
921 ****************************************************************************/
get_info_label_text(bool moreinfo)922 const char *get_info_label_text(bool moreinfo)
923 {
924   static struct astring str = ASTRING_INIT;
925 
926   astr_clear(&str);
927 
928   if (NULL != client.conn.playing) {
929     astr_add_line(&str, _("Population: %s"),
930 		  population_to_text(civ_population(client.conn.playing)));
931   }
932   astr_add_line(&str, _("Year: %s (T%d)"),
933 		calendar_text(), game.info.turn);
934 
935   if (NULL != client.conn.playing) {
936     astr_add_line(&str, _("Gold: %d (%+d)"),
937                   client.conn.playing->economic.gold,
938                   player_get_expected_income(client.conn.playing));
939     astr_add_line(&str, _("Tax: %d Lux: %d Sci: %d"),
940                   client.conn.playing->economic.tax,
941                   client.conn.playing->economic.luxury,
942                   client.conn.playing->economic.science);
943   }
944   if (game.info.phase_mode == PMT_PLAYERS_ALTERNATE) {
945     if (game.info.phase < 0 || game.info.phase >= player_count()) {
946       astr_add_line(&str, _("Moving: Nobody"));
947     } else {
948       astr_add_line(&str, _("Moving: %s"),
949                     player_name(player_by_number(game.info.phase)));
950     }
951   } else if (game.info.phase_mode == PMT_TEAMS_ALTERNATE) {
952     if (game.info.phase < 0 || game.info.phase >= team_count()) {
953       astr_add_line(&str, _("Moving: Nobody"));
954     } else {
955       astr_add_line(&str, _("Moving: %s"),
956                     team_name_translation(team_by_number(game.info.phase)));
957     }
958   }
959 
960   if (moreinfo) {
961     astr_add_line(&str, _("(Click for more info)"));
962   }
963 
964   return astr_str(&str);
965 }
966 
967 /****************************************************************************
968   Return the text for the popup label on the info panel.  (This is
969   traditionally done as a popup whenever the regular info text is clicked
970   on.)
971 ****************************************************************************/
get_info_label_text_popup(void)972 const char *get_info_label_text_popup(void)
973 {
974   static struct astring str = ASTRING_INIT;
975 
976   astr_clear(&str);
977 
978   if (NULL != client.conn.playing) {
979     astr_add_line(&str, _("%s People"),
980 		  population_to_text(civ_population(client.conn.playing)));
981   }
982   astr_add_line(&str, _("Year: %s"), calendar_text());
983   astr_add_line(&str, _("Turn: %d"), game.info.turn);
984 
985   if (NULL != client.conn.playing) {
986     const struct research *presearch = research_get(client_player());
987     int perturn = get_bulbs_per_turn(NULL, NULL, NULL);
988     int upkeep = client_player()->client.tech_upkeep;
989 
990     astr_add_line(&str, _("Gold: %d"),
991 		  client.conn.playing->economic.gold);
992     astr_add_line(&str, _("Net Income: %d"),
993 		  player_get_expected_income(client.conn.playing));
994     /* TRANS: Gold, luxury, and science rates are in percentage values. */
995     astr_add_line(&str, _("Tax rates: Gold:%d%% Luxury:%d%% Science:%d%%"),
996 		  client.conn.playing->economic.tax,
997 		  client.conn.playing->economic.luxury,
998 		  client.conn.playing->economic.science);
999     astr_add_line(&str, _("Researching %s: %s"),
1000                   research_advance_name_translation(presearch,
1001                                                     presearch->researching),
1002 		  get_science_target_text(NULL));
1003     /* perturn is defined as: (bulbs produced) - upkeep */
1004     if (game.info.tech_upkeep_style != TECH_UPKEEP_NONE) {
1005       astr_add_line(&str, _("Bulbs per turn: %d - %d = %d"), perturn + upkeep,
1006                     upkeep, perturn);
1007     } else {
1008       fc_assert(upkeep == 0);
1009       astr_add_line(&str, _("Bulbs per turn: %d"), perturn);
1010     }
1011     {
1012       int history_perturn = nation_history_gain(client.conn.playing);
1013       city_list_iterate(client.conn.playing->cities, pcity) {
1014         history_perturn += city_history_gain(pcity);
1015       } city_list_iterate_end;
1016       astr_add_line(&str, _("Culture: %d (%+d/turn)"),
1017                     player_culture(client.conn.playing), history_perturn);
1018     }
1019   }
1020 
1021   /* See also get_global_warming_tooltip and get_nuclear_winter_tooltip. */
1022 
1023   if (game.info.global_warming) {
1024     int chance, rate;
1025     global_warming_scaled(&chance, &rate, 100);
1026     astr_add_line(&str, _("Global warming chance: %d%% (%+d%%/turn)"),
1027                   chance, rate);
1028   } else {
1029     astr_add_line(&str, _("Global warming deactivated."));
1030   }
1031 
1032   if (game.info.nuclear_winter) {
1033     int chance, rate;
1034     nuclear_winter_scaled(&chance, &rate, 100);
1035     astr_add_line(&str, _("Nuclear winter chance: %d%% (%+d%%/turn)"),
1036                   chance, rate);
1037   } else {
1038     astr_add_line(&str, _("Nuclear winter deactivated."));
1039   }
1040 
1041   if (NULL != client.conn.playing) {
1042     astr_add_line(&str, _("Government: %s"),
1043 		  government_name_for_player(client.conn.playing));
1044   }
1045 
1046   return astr_str(&str);
1047 }
1048 
1049 /****************************************************************************
1050   Return the title text for the unit info shown in the info panel.
1051 
1052   FIXME: this should be renamed.
1053 ****************************************************************************/
get_unit_info_label_text1(struct unit_list * punits)1054 const char *get_unit_info_label_text1(struct unit_list *punits)
1055 {
1056   static struct astring str = ASTRING_INIT;
1057 
1058   astr_clear(&str);
1059 
1060   if (punits) {
1061     int count = unit_list_size(punits);
1062 
1063     if (count == 1) {
1064       astr_add(&str, "%s", unit_name_translation(unit_list_get(punits, 0)));
1065     } else {
1066       astr_add(&str, PL_("%d unit", "%d units", count), count);
1067     }
1068   }
1069   return astr_str(&str);
1070 }
1071 
1072 /****************************************************************************
1073   Return the text body for the unit info shown in the info panel.
1074 
1075   FIXME: this should be renamed.
1076 ****************************************************************************/
get_unit_info_label_text2(struct unit_list * punits,int linebreaks)1077 const char *get_unit_info_label_text2(struct unit_list *punits, int linebreaks)
1078 {
1079   static struct astring str = ASTRING_INIT;
1080   int count;
1081 
1082   astr_clear(&str);
1083 
1084   if (!punits) {
1085     return "";
1086   }
1087 
1088   count = unit_list_size(punits);
1089 
1090   /* This text should always have the same number of lines if
1091    * 'linebreaks' has no flags at all. Otherwise the GUI widgets may be
1092    * confused and try to resize themselves. If caller asks for
1093    * conditional 'linebreaks', it should take care of these problems
1094    * itself. */
1095 
1096   /* Line 1. Goto or activity text. */
1097   if (count > 0 && hover_state != HOVER_NONE) {
1098     int min, max;
1099 
1100     if (!goto_get_turns(&min, &max)) {
1101       /* TRANS: Impossible to reach goto target tile */
1102       astr_add_line(&str, "%s", Q_("?goto:Unreachable"));
1103     } else if (min == max) {
1104       astr_add_line(&str, _("Turns to target: %d"), max);
1105     } else {
1106       astr_add_line(&str, _("Turns to target: %d to %d"), min, max);
1107     }
1108   } else if (count == 1) {
1109     astr_add_line(&str, "%s",
1110 		  unit_activity_text(unit_list_get(punits, 0)));
1111   } else if (count > 1) {
1112     astr_add_line(&str, PL_("%d unit selected",
1113 			    "%d units selected",
1114 			    count),
1115 		  count);
1116   } else {
1117     astr_add_line(&str, _("No units selected."));
1118   }
1119 
1120   /* Lines 2, 3, 4, and possible 5 vary. */
1121   if (count == 1) {
1122     struct unit *punit = unit_list_get(punits, 0);
1123     struct player *owner = unit_owner(punit);
1124     struct city *pcity = player_city_by_number(owner,
1125                                                punit->homecity);
1126 
1127     astr_add_line(&str, "%s", tile_get_info_text(unit_tile(punit), TRUE,
1128                                                  linebreaks));
1129     {
1130       const char *infratext = get_infrastructure_text(unit_tile(punit)->extras);
1131 
1132       if (*infratext != '\0') {
1133         astr_add_line(&str, "%s", infratext);
1134       } else {
1135         astr_add_line(&str, " ");
1136       }
1137     }
1138     if (pcity) {
1139       astr_add_line(&str, "%s", city_name_get(pcity));
1140     } else {
1141       astr_add_line(&str, " ");
1142     }
1143 
1144     if (game.info.citizen_nationality) {
1145       struct player *nationality = unit_nationality(punit);
1146 
1147       /* Line 5, nationality text */
1148       if (nationality != NULL && owner != nationality) {
1149         /* TRANS: Nationality of the people comprising a unit, if
1150          * different from owner. */
1151         astr_add_line(&str, _("%s people"),
1152                       nation_adjective_for_player(nationality));
1153       } else {
1154         astr_add_line(&str, " ");
1155       }
1156     }
1157 
1158   } else if (count > 1) {
1159     int mil = 0, nonmil = 0;
1160     int types_count[U_LAST], i;
1161     struct unit_type *top[3];
1162 
1163     memset(types_count, 0, sizeof(types_count));
1164     unit_list_iterate(punits, punit) {
1165       if (unit_has_type_flag(punit, UTYF_CIVILIAN)) {
1166 	nonmil++;
1167       } else {
1168 	mil++;
1169       }
1170       types_count[utype_index(unit_type_get(punit))]++;
1171     } unit_list_iterate_end;
1172 
1173     top[0] = top[1] = top[2] = NULL;
1174     unit_type_iterate(utype) {
1175       if (!top[2]
1176 	  || types_count[utype_index(top[2])] < types_count[utype_index(utype)]) {
1177 	top[2] = utype;
1178 
1179 	if (!top[1]
1180 	    || types_count[utype_index(top[1])] < types_count[utype_index(top[2])]) {
1181 	  top[2] = top[1];
1182 	  top[1] = utype;
1183 
1184 	  if (!top[0]
1185 	      || types_count[utype_index(top[0])] < types_count[utype_index(utype)]) {
1186 	    top[1] = top[0];
1187 	    top[0] = utype;
1188 	  }
1189 	}
1190       }
1191     } unit_type_iterate_end;
1192 
1193     for (i = 0; i < 2; i++) {
1194       if (top[i] && types_count[utype_index(top[i])] > 0) {
1195 	if (utype_has_flag(top[i], UTYF_CIVILIAN)) {
1196 	  nonmil -= types_count[utype_index(top[i])];
1197 	} else {
1198 	  mil -= types_count[utype_index(top[i])];
1199 	}
1200 	astr_add_line(&str, "%d: %s",
1201 		      types_count[utype_index(top[i])],
1202 		      utype_name_translation(top[i]));
1203       } else {
1204 	astr_add_line(&str, " ");
1205       }
1206     }
1207 
1208     if (top[2] && types_count[utype_index(top[2])] > 0
1209         && types_count[utype_index(top[2])] == nonmil + mil) {
1210       astr_add_line(&str, "%d: %s", types_count[utype_index(top[2])],
1211                     utype_name_translation(top[2]));
1212     } else if (nonmil > 0 && mil > 0) {
1213       astr_add_line(&str, _("Others: %d civil; %d military"), nonmil, mil);
1214     } else if (nonmil > 0) {
1215       astr_add_line(&str, _("Others: %d civilian"), nonmil);
1216     } else if (mil > 0) {
1217       astr_add_line(&str, _("Others: %d military"), mil);
1218     } else {
1219       astr_add_line(&str, " ");
1220     }
1221 
1222     if (game.info.citizen_nationality) {
1223       astr_add_line(&str, " ");
1224     }
1225   } else {
1226     astr_add_line(&str, " ");
1227     astr_add_line(&str, " ");
1228     astr_add_line(&str, " ");
1229 
1230     if (game.info.citizen_nationality) {
1231       astr_add_line(&str, " ");
1232     }
1233   }
1234 
1235   /* Line 5/6. Debug text. */
1236 #ifdef DEBUG
1237   if (count == 1) {
1238     astr_add_line(&str, "(Unit ID %d)", unit_list_get(punits, 0)->id);
1239   } else {
1240     astr_add_line(&str, " ");
1241   }
1242 #endif /* DEBUG */
1243 
1244   return astr_str(&str);
1245 }
1246 
1247 /****************************************************************************
1248   Return text about upgrading these unit lists.
1249 
1250   Returns TRUE iff any units can be upgraded.
1251 ****************************************************************************/
get_units_upgrade_info(char * buf,size_t bufsz,struct unit_list * punits)1252 bool get_units_upgrade_info(char *buf, size_t bufsz,
1253                             struct unit_list *punits)
1254 {
1255   if (unit_list_size(punits) == 0) {
1256     fc_snprintf(buf, bufsz, _("No units to upgrade!"));
1257     return FALSE;
1258   } else if (unit_list_size(punits) == 1) {
1259     return (UU_OK == unit_upgrade_info(unit_list_front(punits), buf, bufsz));
1260   } else {
1261     int upgrade_cost = 0;
1262     int num_upgraded = 0;
1263     int min_upgrade_cost = FC_INFINITY;
1264 
1265     unit_list_iterate(punits, punit) {
1266       if (unit_owner(punit) == client_player()
1267           && UU_OK == unit_upgrade_test(punit, FALSE)) {
1268 	struct unit_type *from_unittype = unit_type_get(punit);
1269 	struct unit_type *to_unittype = can_upgrade_unittype(client.conn.playing,
1270 							     from_unittype);
1271 	int cost = unit_upgrade_price(unit_owner(punit),
1272 					   from_unittype, to_unittype);
1273 
1274 	num_upgraded++;
1275 	upgrade_cost += cost;
1276 	min_upgrade_cost = MIN(min_upgrade_cost, cost);
1277       }
1278     } unit_list_iterate_end;
1279     if (num_upgraded == 0) {
1280       fc_snprintf(buf, bufsz, _("None of these units may be upgraded."));
1281       return FALSE;
1282     } else {
1283       /* This may trigger sometimes if you don't have enough money for
1284        * a full upgrade.  If you have enough to upgrade at least one, it
1285        * will do it. */
1286       /* Construct prompt in several parts to allow separate pluralisation
1287        * by localizations */
1288       char tbuf[MAX_LEN_MSG], ubuf[MAX_LEN_MSG];
1289       fc_snprintf(tbuf, ARRAY_SIZE(tbuf), PL_("Treasury contains %d gold.",
1290                                               "Treasury contains %d gold.",
1291                                               client_player()->economic.gold),
1292                   client_player()->economic.gold);
1293       /* TRANS: this whole string is a sentence fragment that is only ever
1294        * used by including it in another string (search comments for this
1295        * string to find it) */
1296       fc_snprintf(ubuf, ARRAY_SIZE(ubuf), PL_("Upgrade %d unit",
1297                                               "Upgrade %d units",
1298                                               num_upgraded),
1299                   num_upgraded);
1300       /* TRANS: This is complicated. The first %s is a pre-pluralised
1301        * sentence fragment "Upgrade %d unit(s)"; the second is pre-pluralised
1302        * "Treasury contains %d gold." So the whole thing reads
1303        * "Upgrade 13 units for 1000 gold?\nTreasury contains 2000 gold." */
1304       fc_snprintf(buf, bufsz, PL_("%s for %d gold?\n%s",
1305                                   "%s for %d gold?\n%s", upgrade_cost),
1306                   ubuf, upgrade_cost, tbuf);
1307       return TRUE;
1308     }
1309   }
1310 }
1311 
1312 /****************************************************************************
1313   Return text about disbanding these units.
1314 
1315   Returns TRUE iff any units can be disbanded.
1316 ****************************************************************************/
get_units_disband_info(char * buf,size_t bufsz,struct unit_list * punits)1317 bool get_units_disband_info(char *buf, size_t bufsz,
1318 			    struct unit_list *punits)
1319 {
1320   if (unit_list_size(punits) == 0) {
1321     fc_snprintf(buf, bufsz, _("No units to disband!"));
1322     return FALSE;
1323   } else if (unit_list_size(punits) == 1) {
1324     if (unit_has_type_flag(unit_list_front(punits), UTYF_UNDISBANDABLE)) {
1325       fc_snprintf(buf, bufsz, _("%s refuses to disband!"),
1326                   unit_name_translation(unit_list_front(punits)));
1327       return FALSE;
1328     } else {
1329       /* TRANS: %s is a unit type */
1330       fc_snprintf(buf, bufsz, _("Disband %s?"),
1331                   unit_name_translation(unit_list_front(punits)));
1332       return TRUE;
1333     }
1334   } else {
1335     int count = 0;
1336     unit_list_iterate(punits, punit) {
1337       if (!unit_has_type_flag(punit, UTYF_UNDISBANDABLE)) {
1338         count++;
1339       }
1340     } unit_list_iterate_end;
1341     if (count == 0) {
1342       fc_snprintf(buf, bufsz, _("None of these units may be disbanded."));
1343       return FALSE;
1344     } else {
1345       /* TRANS: %d is never 0 or 1 */
1346       fc_snprintf(buf, bufsz, PL_("Disband %d unit?",
1347                                   "Disband %d units?", count), count);
1348       return TRUE;
1349     }
1350   }
1351 }
1352 
1353 /****************************************************************************
1354   Get a tooltip text for the info panel research indicator.  See
1355   client_research_sprite().
1356 ****************************************************************************/
get_bulb_tooltip(void)1357 const char *get_bulb_tooltip(void)
1358 {
1359   static struct astring str = ASTRING_INIT;
1360 
1361   astr_clear(&str);
1362 
1363   astr_add_line(&str, _("Shows your progress in "
1364 			"researching the current technology."));
1365 
1366   if (NULL != client.conn.playing) {
1367     struct research *research = research_get(client_player());
1368 
1369     if (research->researching == A_UNSET) {
1370       astr_add_line(&str, _("No research target."));
1371     } else {
1372       int turns;
1373       int perturn = get_bulbs_per_turn(NULL, NULL, NULL);
1374       struct astring buf1 = ASTRING_INIT, buf2 = ASTRING_INIT;
1375 
1376       if ((turns = turns_to_research_done(research, perturn)) >= 0) {
1377         astr_set(&buf1, PL_("%d turn", "%d turns", turns), turns);
1378       } else if ((turns = turns_to_tech_loss(research, perturn)) >= 0) {
1379         astr_set(&buf1, PL_("%d turn to loss", "%d turns to loss", turns),
1380                  turns);
1381       } else {
1382         if (perturn < 0) {
1383           astr_set(&buf1, _("Decreasing"));
1384         } else {
1385           astr_set(&buf1, _("No progress"));
1386         }
1387       }
1388 
1389       /* TRANS: <perturn> bulbs/turn */
1390       astr_set(&buf2, PL_("%d bulb/turn", "%d bulbs/turn", perturn), perturn);
1391 
1392       /* TRANS: <tech>: <amount>/<total bulbs> */
1393       astr_add_line(&str, _("%s: %d/%d (%s, %s)."),
1394                     research_advance_name_translation(research,
1395                                                       research->researching),
1396                     research->bulbs_researched,
1397                     research->client.researching_cost,
1398                     astr_str(&buf1), astr_str(&buf2));
1399 
1400       astr_free(&buf1);
1401       astr_free(&buf2);
1402     }
1403   }
1404   return astr_str(&str);
1405 }
1406 
1407 /****************************************************************************
1408   Get a tooltip text for the info panel global warning indicator.  See also
1409   client_warming_sprite().
1410 ****************************************************************************/
get_global_warming_tooltip(void)1411 const char *get_global_warming_tooltip(void)
1412 {
1413   static struct astring str = ASTRING_INIT;
1414 
1415   astr_clear(&str);
1416 
1417   if (!game.info.global_warming) {
1418     astr_add_line(&str, _("Global warming deactivated."));
1419   } else {
1420     int chance, rate;
1421     global_warming_scaled(&chance, &rate, 100);
1422     astr_add_line(&str, _("Shows the progress of global warming:"));
1423     astr_add_line(&str, _("Pollution rate: %d%%"), rate);
1424     astr_add_line(&str, _("Chance of catastrophic warming each turn: %d%%"),
1425                   chance);
1426   }
1427 
1428   return astr_str(&str);
1429 }
1430 
1431 /****************************************************************************
1432   Get a tooltip text for the info panel nuclear winter indicator.  See also
1433   client_cooling_sprite().
1434 ****************************************************************************/
get_nuclear_winter_tooltip(void)1435 const char *get_nuclear_winter_tooltip(void)
1436 {
1437   static struct astring str = ASTRING_INIT;
1438 
1439   astr_clear(&str);
1440 
1441   if (!game.info.nuclear_winter) {
1442     astr_add_line(&str, _("Nuclear winter deactivated."));
1443   } else {
1444     int chance, rate;
1445     nuclear_winter_scaled(&chance, &rate, 100);
1446     astr_add_line(&str, _("Shows the progress of nuclear winter:"));
1447     astr_add_line(&str, _("Fallout rate: %d%%"), rate);
1448     astr_add_line(&str, _("Chance of catastrophic winter each turn: %d%%"),
1449                   chance);
1450   }
1451 
1452   return astr_str(&str);
1453 }
1454 
1455 /****************************************************************************
1456   Get a tooltip text for the info panel government indicator.  See also
1457   government_by_number(...)->sprite.
1458 ****************************************************************************/
get_government_tooltip(void)1459 const char *get_government_tooltip(void)
1460 {
1461   static struct astring str = ASTRING_INIT;
1462 
1463   astr_clear(&str);
1464 
1465   astr_add_line(&str, _("Shows your current government:"));
1466 
1467   if (NULL != client.conn.playing) {
1468     astr_add_line(&str, "%s",
1469 		  government_name_for_player(client.conn.playing));
1470   }
1471   return astr_str(&str);
1472 }
1473 
1474 /****************************************************************************
1475   Returns a description of the given spaceship.  If there is no spaceship
1476   (pship is NULL) then text with dummy values is returned.
1477 ****************************************************************************/
get_spaceship_descr(struct player_spaceship * pship)1478 const char *get_spaceship_descr(struct player_spaceship *pship)
1479 {
1480   struct player_spaceship ship;
1481   static struct astring str = ASTRING_INIT;
1482 
1483   astr_clear(&str);
1484 
1485   if (!pship) {
1486     pship = &ship;
1487     memset(&ship, 0, sizeof(ship));
1488   }
1489 
1490   /* TRANS: spaceship text; should have constant width. */
1491   astr_add_line(&str, _("Population:      %5d"), pship->population);
1492 
1493   /* TRANS: spaceship text; should have constant width. */
1494   astr_add_line(&str, _("Support:         %5d %%"),
1495 		(int) (pship->support_rate * 100.0));
1496 
1497   /* TRANS: spaceship text; should have constant width. */
1498   astr_add_line(&str, _("Energy:          %5d %%"),
1499 		(int) (pship->energy_rate * 100.0));
1500 
1501   /* TRANS: spaceship text; should have constant width. */
1502   astr_add_line(&str, PL_("Mass:            %5d ton",
1503 			  "Mass:            %5d tons",
1504 			  pship->mass), pship->mass);
1505 
1506   if (pship->propulsion > 0) {
1507     /* TRANS: spaceship text; should have constant width. */
1508     astr_add_line(&str, _("Travel time:     %5.1f years"),
1509 		  (float) (0.1 * ((int) (pship->travel_time * 10.0))));
1510   } else {
1511     /* TRANS: spaceship text; should have constant width. */
1512     astr_add_line(&str, "%s", _("Travel time:        N/A     "));
1513   }
1514 
1515   /* TRANS: spaceship text; should have constant width. */
1516   astr_add_line(&str, _("Success prob.:   %5d %%"),
1517 		(int) (pship->success_rate * 100.0));
1518 
1519   /* TRANS: spaceship text; should have constant width. */
1520   astr_add_line(&str, _("Year of arrival: %8s"),
1521 		(pship->state == SSHIP_LAUNCHED)
1522 		? textyear((int) (pship->launch_year +
1523 				  (int) pship->travel_time))
1524 		: "-   ");
1525 
1526   return astr_str(&str);
1527 }
1528 
1529 /****************************************************************************
1530   Get the text showing the timeout.  This is generally disaplyed on the info
1531   panel.
1532 ****************************************************************************/
get_timeout_label_text(void)1533 const char *get_timeout_label_text(void)
1534 {
1535   static struct astring str = ASTRING_INIT;
1536 
1537   astr_clear(&str);
1538 
1539   if (is_waiting_turn_change() && game.tinfo.last_turn_change_time >= 1.5) {
1540     double wt = get_seconds_to_new_turn();
1541 
1542     if (wt < 0.01) {
1543       astr_add(&str, "%s", Q_("?timeout:wait"));
1544     } else {
1545       astr_add(&str, "%s: %s", Q_("?timeout:eta"), format_duration(wt));
1546     }
1547   } else {
1548     if (current_turn_timeout() <= 0) {
1549       astr_add(&str, "%s", Q_("?timeout:off"));
1550     } else {
1551       astr_add(&str, "%s", format_duration(get_seconds_to_turndone()));
1552     }
1553   }
1554 
1555   return astr_str(&str);
1556 }
1557 
1558 /****************************************************************************
1559   Format a duration, in seconds, so it comes up in minutes or hours if
1560   that would be more meaningful.
1561 
1562   (7 characters, maximum.  Enough for, e.g., "99h 59m".)
1563 ****************************************************************************/
format_duration(int duration)1564 const char *format_duration(int duration)
1565 {
1566   static struct astring str = ASTRING_INIT;
1567 
1568   astr_clear(&str);
1569 
1570   if (duration < 0) {
1571     duration = 0;
1572   }
1573   if (duration < 60) {
1574     astr_add(&str, Q_("?seconds:%02ds"), duration);
1575   } else if (duration < 3600) {	/* < 60 minutes */
1576     astr_add(&str, Q_("?mins/secs:%02dm %02ds"), duration / 60, duration % 60);
1577   } else if (duration < 360000) {	/* < 100 hours */
1578     astr_add(&str, Q_("?hrs/mns:%02dh %02dm"), duration / 3600, (duration / 60) % 60);
1579   } else if (duration < 8640000) {	/* < 100 days */
1580     astr_add(&str, Q_("?dys/hrs:%02dd %02dh"), duration / 86400,
1581 	(duration / 3600) % 24);
1582   } else {
1583     astr_add(&str, "%s", Q_("?duration:overflow"));
1584   }
1585 
1586   return astr_str(&str);
1587 }
1588 
1589 /****************************************************************************
1590   Return text giving the ping time for the player.  This is generally used
1591   used in the playerdlg.  This should only be used in playerdlg_common.c.
1592 ****************************************************************************/
get_ping_time_text(const struct player * pplayer)1593 const char *get_ping_time_text(const struct player *pplayer)
1594 {
1595   static struct astring str = ASTRING_INIT;
1596 
1597   astr_clear(&str);
1598 
1599   conn_list_iterate(pplayer->connections, pconn) {
1600     if (!pconn->observer
1601 	/* Certainly not needed, but safer. */
1602 	&& 0 == strcmp(pconn->username, pplayer->username)) {
1603       if (pconn->ping_time != -1) {
1604 	double ping_time_in_ms = 1000 * pconn->ping_time;
1605 
1606 	astr_add(&str, _("%6d.%02d ms"), (int) ping_time_in_ms,
1607 		 ((int) (ping_time_in_ms * 100.0)) % 100);
1608       }
1609       break;
1610     }
1611   } conn_list_iterate_end;
1612 
1613   return astr_str(&str);
1614 }
1615 
1616 /****************************************************************************
1617   Return text giving the score of the player. This should only be used
1618   in playerdlg_common.c.
1619 ****************************************************************************/
get_score_text(const struct player * pplayer)1620 const char *get_score_text(const struct player *pplayer)
1621 {
1622   static struct astring str = ASTRING_INIT;
1623 
1624   astr_clear(&str);
1625 
1626   if (pplayer->score.game > 0
1627       || NULL == client.conn.playing
1628       || pplayer == client.conn.playing) {
1629     astr_add(&str, "%d", pplayer->score.game);
1630   } else {
1631     astr_add(&str, "?");
1632   }
1633 
1634   return astr_str(&str);
1635 }
1636 
1637 /****************************************************************************
1638   Get the title for a "report".  This may include the city, economy,
1639   military, trade, player, etc., reports.  Some clients may generate the
1640   text themselves to get a better GUI layout.
1641 ****************************************************************************/
get_report_title(const char * report_name)1642 const char *get_report_title(const char *report_name)
1643 {
1644   static struct astring str = ASTRING_INIT;
1645   const struct player *pplayer = client_player();
1646 
1647   astr_clear(&str);
1648 
1649   astr_add_line(&str, "%s", report_name);
1650 
1651   if (pplayer != NULL) {
1652     char buf[4 * MAX_LEN_NAME];
1653 
1654     /* TRANS: <nation adjective> <government name>.
1655      * E.g. "Polish Republic". */
1656     astr_add_line(&str, Q_("?nationgovernment:%s %s"),
1657                   nation_adjective_for_player(pplayer),
1658                   government_name_for_player(pplayer));
1659 
1660     /* TRANS: Just appending 2 strings, using the correct localized
1661      * syntax. */
1662     astr_add_line(&str, _("%s - %s"),
1663                   ruler_title_for_player(pplayer, buf, sizeof(buf)),
1664                   calendar_text());
1665   } else {
1666     /* TRANS: "Observer - 1985 AD" */
1667     astr_add_line(&str, _("Observer - %s"),
1668 		  calendar_text());
1669   }
1670   return astr_str(&str);
1671 }
1672 
1673 /**********************************************************************//**
1674   Returns custom part of the action selection dialog button text for the
1675   specified action (given that the action is possible).
1676 **************************************************************************/
get_act_sel_action_custom_text(struct action * paction,const struct act_prob prob,const struct unit * actor_unit,const struct city * target_city)1677 const char *get_act_sel_action_custom_text(struct action *paction,
1678                                            const struct act_prob prob,
1679                                            const struct unit *actor_unit,
1680                                            const struct city *target_city)
1681 {
1682   static struct astring custom = ASTRING_INIT;
1683 
1684   struct city *actor_homecity = unit_home(actor_unit);
1685 
1686   if (!action_prob_possible(prob)) {
1687     /* No info since impossible. */
1688     return NULL;
1689   }
1690 
1691   fc_assert_ret_val((action_get_target_kind(paction) != ATK_CITY
1692                      || target_city != NULL),
1693                     NULL);
1694 
1695   if (paction->id == ACTION_TRADE_ROUTE) {
1696     int revenue = get_caravan_enter_city_trade_bonus(actor_homecity,
1697                                                      target_city,
1698                                                      TRUE);
1699 
1700     if (revenue > 0) {
1701       astr_set(&custom,
1702                /* TRANS: Estimated one time bonus and recurring revenue for
1703                 * the Establish Trade _Route action. */
1704                _("%d one time bonus + %d trade"),
1705                revenue,
1706                trade_between_cities(actor_homecity, target_city));
1707     } else {
1708       astr_set(&custom,
1709                /* TRANS: Estimated recurring revenue for
1710                 * the Establish Trade _Route action. */
1711                _("%d trade"),
1712                trade_between_cities(actor_homecity, target_city));
1713     }
1714   } else if (paction->id == ACTION_MARKETPLACE) {
1715     int revenue = get_caravan_enter_city_trade_bonus(actor_homecity,
1716                                                      target_city,
1717                                                      FALSE);
1718 
1719     if (revenue > 0) {
1720       astr_set(&custom,
1721                /* TRANS: Estimated one time bonus for the Enter Marketplace
1722                 * action. */
1723                _("%d one time bonus"), revenue);
1724     } else {
1725       /* No info to add. */
1726       return NULL;
1727     }
1728   } else if (paction->id == ACTION_HELP_WONDER
1729              && city_owner(target_city) == client.conn.playing) {
1730     /* Can only give remaining production for domestic and existing
1731      * cities. */
1732     astr_set(&custom, _("%d remaining"),
1733              impr_build_shield_cost(target_city->production.value.building)
1734              - target_city->shield_stock);
1735   } else {
1736     /* No info to add. */
1737     return NULL;
1738   }
1739 
1740   return astr_str(&custom);
1741 }
1742 
1743 /****************************************************************************
1744   Describing buildings that affect happiness.
1745 ****************************************************************************/
text_happiness_buildings(const struct city * pcity)1746 const char *text_happiness_buildings(const struct city *pcity)
1747 {
1748   struct effect_list *plist = effect_list_new();
1749   static struct astring str = ASTRING_INIT;
1750 
1751   get_city_bonus_effects(plist, pcity, NULL, EFT_MAKE_CONTENT);
1752   if (0 < effect_list_size(plist)) {
1753     struct astring effects = ASTRING_INIT;
1754 
1755     get_effect_list_req_text(plist, &effects);
1756     astr_set(&str, _("Buildings: %s."), astr_str(&effects));
1757     astr_free(&effects);
1758   } else {
1759     astr_set(&str, _("Buildings: None."));
1760   }
1761   effect_list_destroy(plist);
1762 
1763   /* Add line breaks after 80 characters. */
1764   astr_break_lines(&str, 80);
1765 
1766   return astr_str(&str);
1767 }
1768 
1769 /****************************************************************************
1770   Describing nationality effects that affect happiness.
1771 ****************************************************************************/
text_happiness_nationality(const struct city * pcity)1772 const char *text_happiness_nationality(const struct city *pcity)
1773 {
1774   static struct astring str = ASTRING_INIT;
1775   int enemies = 0;
1776 
1777   astr_clear(&str);
1778 
1779   astr_add_line(&str, _("Nationality: "));
1780 
1781   if (game.info.citizen_nationality) {
1782     if (get_city_bonus(pcity, EFT_ENEMY_CITIZEN_UNHAPPY_PCT) > 0) {
1783       struct player *owner = city_owner(pcity);
1784 
1785       citizens_foreign_iterate(pcity, pslot, nationality) {
1786         if (pplayers_at_war(owner, player_slot_get_player(pslot))) {
1787           enemies += nationality;
1788         }
1789       } citizens_foreign_iterate_end;
1790 
1791       if (enemies > 0) {
1792         astr_add(&str, PL_("%d enemy nationalist", "%d enemy nationalists", enemies),
1793                  enemies);
1794       }
1795     }
1796 
1797     if (enemies == 0) {
1798       astr_add(&str, _("None."));
1799     }
1800   } else {
1801     astr_add(&str, _("Disabled."));
1802   }
1803 
1804   return astr_str(&str);
1805 }
1806 
1807 /****************************************************************************
1808   Describing wonders that affect happiness.
1809 ****************************************************************************/
text_happiness_wonders(const struct city * pcity)1810 const char *text_happiness_wonders(const struct city *pcity)
1811 {
1812   struct effect_list *plist = effect_list_new();
1813   static struct astring str = ASTRING_INIT;
1814 
1815   get_city_bonus_effects(plist, pcity, NULL, EFT_MAKE_HAPPY);
1816   get_city_bonus_effects(plist, pcity, NULL, EFT_FORCE_CONTENT);
1817   get_city_bonus_effects(plist, pcity, NULL, EFT_NO_UNHAPPY);
1818   if (0 < effect_list_size(plist)) {
1819     struct astring effects = ASTRING_INIT;
1820 
1821     get_effect_list_req_text(plist, &effects);
1822     astr_set(&str, _("Wonders: %s."), astr_str(&effects));
1823     astr_free(&effects);
1824   } else {
1825     astr_set(&str, _("Wonders: None."));
1826   }
1827 
1828   /* Add line breaks after 80 characters. */
1829   astr_break_lines(&str, 80);
1830   effect_list_destroy(plist);
1831 
1832   return astr_str(&str);
1833 }
1834 
1835 /****************************************************************************
1836   Describing city factors that affect happiness.
1837 ****************************************************************************/
text_happiness_cities(const struct city * pcity)1838 const char *text_happiness_cities(const struct city *pcity)
1839 {
1840   struct player *pplayer = city_owner(pcity);
1841   int cities = city_list_size(pplayer->cities);
1842   int content = get_player_bonus(pplayer, EFT_CITY_UNHAPPY_SIZE);
1843   int basis = get_player_bonus(pplayer, EFT_EMPIRE_SIZE_BASE);
1844   int step = get_player_bonus(pplayer, EFT_EMPIRE_SIZE_STEP);
1845   static struct astring str = ASTRING_INIT;
1846 
1847   astr_clear(&str);
1848 
1849   if (basis+step <= 0) {
1850     /* Special case where penalty is disabled; see
1851      * player_content_citizens(). */
1852     astr_add_line(&str,
1853                   PL_("Cities: %d total, but no penalty for empire size.",
1854                       "Cities: %d total, but no penalty for empire size.",
1855                       cities),
1856                   cities);
1857     astr_add_line(&str,
1858                   /* TRANS: %d is number of citizens */
1859                   PL_("%d content per city.",
1860                       "%d content per city.", content),
1861                   content);
1862   } else {
1863     /* Can have up to and including 'basis' cities without penalty */
1864     int excess = MAX(cities - basis, 0);
1865     int penalty;
1866     int unhappy, angry;
1867     int last, next;
1868 
1869     if (excess > 0) {
1870       if (step > 0) {
1871         penalty = 1 + (excess - 1) / step;
1872       } else {
1873         penalty = 1;
1874       }
1875     } else {
1876       penalty = 0;
1877     }
1878 
1879     unhappy = MIN(penalty, content);
1880     angry = game.info.angrycitizen ? MAX(penalty-content, 0) : 0;
1881     if (penalty >= 1) {
1882       /* 'last' is when last actual malcontent appeared, will saturate
1883        * if no angry citizens */
1884       last = basis + (unhappy+angry-1)*step;
1885       if (!game.info.angrycitizen && unhappy == content) {
1886         /* Maxed out unhappy citizens, so no more penalties */
1887         next = 0;
1888       } else {
1889         /* Angry citizens can continue appearing indefinitely */
1890         next = last + step;
1891       }
1892     } else {
1893       last = 0;
1894       next = basis;
1895     }
1896 
1897     astr_add_line(&str,
1898                   /* TRANS: sentence fragment, will have text appended */
1899                   PL_("Cities: %d total:",
1900                       "Cities: %d total:", cities),
1901                   cities);
1902     if (excess > 0) {
1903       astr_add(&str,
1904                /* TRANS: appended to "Cities: %d total:"; preserve leading
1905                 * space. Pluralized in "nearest threshold of %d cities". */
1906                PL_(" %d over nearest threshold of %d city.",
1907                    " %d over nearest threshold of %d cities.", last),
1908                cities - last, last);
1909       astr_add_line(&str,
1910                     /* TRANS: Number of content [citizen(s)] ... */
1911                     PL_("%d content before penalty.",
1912                         "%d content before penalty.", content),
1913                     content);
1914       astr_add_line(&str,
1915                     PL_("%d additional unhappy citizen.",
1916                         "%d additional unhappy citizens.", unhappy),
1917                     unhappy);
1918       if (angry > 0) {
1919         astr_add_line(&str,
1920                       PL_("%d angry citizen.",
1921                           "%d angry citizens.", angry),
1922                       angry);
1923       }
1924     } else {
1925       astr_add(&str,
1926                /* TRANS: appended to "Cities: %d total:"; preserve leading
1927                 * space. */
1928                PL_(" not more than %d, so no empire size penalty.",
1929                    " not more than %d, so no empire size penalty.", next),
1930                next);
1931       astr_add_line(&str,
1932                     /* TRANS: %d is number of citizens */
1933                     PL_("%d content per city.",
1934                         "%d content per city.", content),
1935                     content);
1936     }
1937     if (next >= cities && penalty < content) {
1938       astr_add_line(&str,
1939                     PL_("With %d more city, another citizen will become "
1940                         "unhappy.",
1941                         "With %d more cities, another citizen will become "
1942                         "unhappy.",
1943                         next + 1 - cities),
1944                     next + 1 - cities);
1945     } else if (next >= cities) {
1946       /* We maxed out the number of unhappy citizens, but they can get
1947        * angry instead. */
1948       fc_assert(game.info.angrycitizen);
1949       astr_add_line(&str,
1950                     PL_("With %d more city, another citizen will become "
1951                         "angry.",
1952                         "With %d more cities, another citizen will become "
1953                         "angry.",
1954                         next + 1 - cities),
1955                     next + 1 - cities);
1956     } else {
1957       /* Either no Empire_Size_Step, or we maxed out on unhappy citizens
1958        * and ruleset doesn't allow angry ones. */
1959       astr_add_line(&str,
1960                     _("More cities will not cause more unhappy citizens."));
1961     }
1962   }
1963 
1964   return astr_str(&str);
1965 }
1966 
1967 /****************************************************************************
1968   Describing units that affect happiness.
1969 ****************************************************************************/
text_happiness_units(const struct city * pcity)1970 const char *text_happiness_units(const struct city *pcity)
1971 {
1972   int mlmax = get_city_bonus(pcity, EFT_MARTIAL_LAW_MAX);
1973   int uhcfac = get_city_bonus(pcity, EFT_UNHAPPY_FACTOR);
1974   static struct astring str = ASTRING_INIT;
1975 
1976   astr_clear(&str);
1977 
1978   if (mlmax > 0) {
1979     int mleach = get_city_bonus(pcity, EFT_MARTIAL_LAW_EACH);
1980     if (mlmax == 100) {
1981       astr_add_line(&str, "%s", _("Unlimited martial law in effect."));
1982     } else {
1983       astr_add_line(&str, PL_("%d military unit may impose martial law.",
1984                               "Up to %d military units may impose martial "
1985                               "law.", mlmax), mlmax);
1986     }
1987     astr_add_line(&str, PL_("Each military unit makes %d "
1988                             "unhappy citizen content.",
1989                             "Each military unit makes %d "
1990                             "unhappy citizens content.",
1991                             mleach), mleach);
1992   } else if (uhcfac > 0) {
1993     astr_add_line(&str,
1994                   _("Military units in the field may cause unhappiness. "));
1995   } else {
1996     astr_add_line(&str,
1997                   _("Military units have no happiness effect. "));
1998   }
1999   return astr_str(&str);
2000 }
2001 
2002 /****************************************************************************
2003   Describing luxuries that affect happiness.
2004 ****************************************************************************/
text_happiness_luxuries(const struct city * pcity)2005 const char *text_happiness_luxuries(const struct city *pcity)
2006 {
2007   static struct astring str = ASTRING_INIT;
2008 
2009   astr_clear(&str);
2010 
2011   astr_add_line(&str,
2012                 _("Luxury: %d total."),
2013                 pcity->prod[O_LUXURY]);
2014   return astr_str(&str);
2015 }
2016