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