webperimental: killstack decides stack protects.
[freeciv.git] / client / text.c
blob0e21a263e1898db803d6c1aa3004e1e4009cf91b
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.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
18 #include <stdarg.h>
19 #include <string.h>
20 #include <math.h> /* ceil */
22 /* utility */
23 #include "astring.h"
24 #include "bitvector.h"
25 #include "fcintl.h"
26 #include "log.h"
27 #include "support.h"
29 /* common */
30 #include "calendar.h"
31 #include "citizens.h"
32 #include "clientutils.h"
33 #include "combat.h"
34 #include "fc_types.h" /* LINE_BREAK */
35 #include "game.h"
36 #include "government.h"
37 #include "map.h"
38 #include "research.h"
39 #include "traderoutes.h"
40 #include "unitlist.h"
42 /* client */
43 #include "client_main.h"
44 #include "climap.h"
45 #include "climisc.h"
46 #include "control.h"
47 #include "goto.h"
49 #include "text.h"
52 static int get_bulbs_per_turn(int *pours, bool *pteam, int *ptheirs);
54 /****************************************************************************
55 Return a (static) string with a tile's food/prod/trade
56 ****************************************************************************/
57 const char *get_tile_output_text(const struct tile *ptile)
59 static struct astring str = ASTRING_INIT;
60 int i;
61 char output_text[O_LAST][16];
63 for (i = 0; i < O_LAST; i++) {
64 int before_penalty = 0;
65 int x = city_tile_output(NULL, ptile, FALSE, i);
67 if (NULL != client.conn.playing) {
68 before_penalty = get_player_output_bonus(client.conn.playing,
69 get_output_type(i),
70 EFT_OUTPUT_PENALTY_TILE);
73 if (before_penalty > 0 && x > before_penalty) {
74 fc_snprintf(output_text[i], sizeof(output_text[i]), "%d(-1)", x);
75 } else {
76 fc_snprintf(output_text[i], sizeof(output_text[i]), "%d", x);
80 astr_set(&str, "%s/%s/%s", output_text[O_FOOD],
81 output_text[O_SHIELD], output_text[O_TRADE]);
83 return astr_str(&str);
86 /****************************************************************************
87 For AIs, fill the buffer with their player name prefixed with "AI". For
88 humans, just fill it with their username.
89 ****************************************************************************/
90 static inline void get_full_username(char *buf, int buflen,
91 const struct player *pplayer)
93 if (!buf || buflen < 1) {
94 return;
97 if (!pplayer) {
98 buf[0] = '\0';
99 return;
102 if (is_ai(pplayer)) {
103 /* TRANS: "AI <player name>" */
104 fc_snprintf(buf, buflen, _("AI %s"), pplayer->name);
105 } else {
106 fc_strlcpy(buf, pplayer->username, buflen);
110 /****************************************************************************
111 Fill the buffer with the player's nation name (in adjective form) and
112 optionally add the player's team name.
113 ****************************************************************************/
114 static inline void get_full_nation(char *buf, int buflen,
115 const struct player *pplayer)
117 if (!buf || buflen < 1) {
118 return;
121 if (!pplayer) {
122 buf[0] = '\0';
123 return;
126 if (pplayer->team) {
127 /* TRANS: "<nation adjective>, team <team name>" */
128 fc_snprintf(buf, buflen, _("%s, team %s"),
129 nation_adjective_for_player(pplayer),
130 team_name_translation(pplayer->team));
131 } else {
132 fc_strlcpy(buf, nation_adjective_for_player(pplayer), buflen);
136 /****************************************************************************
137 Text to popup on a middle-click in the mapview.
138 ****************************************************************************/
139 const char *popup_info_text(struct tile *ptile)
141 const char *activity_text;
142 struct city *pcity = tile_city(ptile);
143 struct unit *punit = find_visible_unit(ptile);
144 const char *diplo_nation_plural_adjectives[DS_LAST] =
145 {"" /* unused, DS_ARMISTICE */, Q_("?nation:Hostile"),
146 "" /* unused, DS_CEASEFIRE */,
147 Q_("?nation:Peaceful"), Q_("?nation:Friendly"),
148 Q_("?nation:Mysterious"), Q_("?nation:Friendly(team)")};
149 const char *diplo_city_adjectives[DS_LAST] =
150 {"" /* unused, DS_ARMISTICE */, Q_("?city:Hostile"),
151 "" /* unused, DS_CEASEFIRE */,
152 Q_("?city:Peaceful"), Q_("?city:Friendly"), Q_("?city:Mysterious"),
153 Q_("?city:Friendly(team)")};
154 static struct astring str = ASTRING_INIT;
155 char username[MAX_LEN_NAME + 32];
156 char nation[2 * MAX_LEN_NAME + 32];
157 int tile_x, tile_y, nat_x, nat_y;
158 bool first;
160 astr_clear(&str);
161 index_to_map_pos(&tile_x, &tile_y, tile_index(ptile));
162 astr_add_line(&str, _("Location: (%d, %d) [%d]"),
163 tile_x, tile_y, tile_continent(ptile));
164 index_to_native_pos(&nat_x, &nat_y, tile_index(ptile));
165 astr_add_line(&str, _("Native coordinates: (%d, %d)"),
166 nat_x, nat_y);
168 if (client_tile_get_known(ptile) == TILE_UNKNOWN) {
169 astr_add(&str, _("Unknown"));
170 return astr_str(&str);
172 astr_add_line(&str, _("Terrain: %s"), tile_get_info_text(ptile, TRUE, 0));
173 astr_add_line(&str, _("Food/Prod/Trade: %s"),
174 get_tile_output_text(ptile));
175 first = TRUE;
176 extra_type_iterate(pextra) {
177 if (pextra->category == ECAT_BONUS && tile_has_visible_extra(ptile, pextra)) {
178 if (!first) {
179 astr_add(&str, ",%s", extra_name_translation(pextra));
180 } else {
181 astr_add_line(&str, "%s", extra_name_translation(pextra));
182 first = FALSE;
185 } extra_type_iterate_end;
186 if (BORDERS_DISABLED != game.info.borders && !pcity) {
187 struct player *owner = tile_owner(ptile);
189 get_full_username(username, sizeof(username), owner);
190 get_full_nation(nation, sizeof(nation), owner);
192 if (NULL != client.conn.playing && owner == client.conn.playing) {
193 astr_add_line(&str, _("Our territory"));
194 } else if (NULL != owner && NULL == client.conn.playing) {
195 /* TRANS: "Territory of <username> (<nation + team>)" */
196 astr_add_line(&str, _("Territory of %s (%s)"), username, nation);
197 } else if (NULL != owner) {
198 struct player_diplstate *ds = player_diplstate_get(client.conn.playing,
199 owner);
201 if (ds->type == DS_CEASEFIRE) {
202 int turns = ds->turns_left;
204 astr_add_line(&str,
205 /* TRANS: "Territory of <username> (<nation + team>)
206 * (<number> turn cease-fire)" */
207 PL_("Territory of %s (%s) (%d turn cease-fire)",
208 "Territory of %s (%s) (%d turn cease-fire)",
209 turns),
210 username, nation, turns);
211 } else if (ds->type == DS_ARMISTICE) {
212 int turns = ds->turns_left;
214 astr_add_line(&str,
215 /* TRANS: "Territory of <username> (<nation + team>)
216 * (<number> turn armistice)" */
217 PL_("Territory of %s (%s) (%d turn armistice)",
218 "Territory of %s (%s) (%d turn armistice)",
219 turns),
220 username, nation, turns);
221 } else {
222 int type = ds->type;
224 /* TRANS: "Territory of <username>
225 * (<nation + team> | <diplomatic state>)" */
226 astr_add_line(&str, _("Territory of %s (%s | %s)"),
227 username, nation,
228 diplo_nation_plural_adjectives[type]);
230 } else {
231 astr_add_line(&str, _("Unclaimed territory"));
234 if (pcity) {
235 /* Look at city owner, not tile owner (the two should be the same, if
236 * borders are in use). */
237 struct player *owner = city_owner(pcity);
238 const char *improvements[improvement_count()];
239 int has_improvements = 0;
241 get_full_username(username, sizeof(username), owner);
242 get_full_nation(nation, sizeof(nation), owner);
244 if (NULL == client.conn.playing || owner == client.conn.playing) {
245 /* TRANS: "City: <city name> | <username> (<nation + team>)" */
246 astr_add_line(&str, _("City: %s | %s (%s)"),
247 city_name_get(pcity), username, nation);
248 } else {
249 struct player_diplstate *ds
250 = player_diplstate_get(client_player(), owner);
251 if (ds->type == DS_CEASEFIRE) {
252 int turns = ds->turns_left;
254 /* TRANS: "City: <city name> | <username>
255 * (<nation + team>, <number> turn cease-fire)" */
256 astr_add_line(&str, PL_("City: %s | %s (%s, %d turn cease-fire)",
257 "City: %s | %s (%s, %d turn cease-fire)",
258 turns),
259 city_name_get(pcity), username, nation, turns);
260 } else if (ds->type == DS_ARMISTICE) {
261 int turns = ds->turns_left;
263 /* TRANS: "City: <city name> | <username>
264 * (<nation + team>, <number> turn armistice)" */
265 astr_add_line(&str, PL_("City: %s | %s (%s, %d turn armistice)",
266 "City: %s | %s (%s, %d turn armistice)",
267 turns),
268 city_name_get(pcity), username, nation, turns);
269 } else {
270 /* TRANS: "City: <city name> | <username>
271 * (<nation + team>, <diplomatic state>)" */
272 astr_add_line(&str, _("City: %s | %s (%s, %s)"),
273 city_name_get(pcity), username, nation,
274 diplo_city_adjectives[ds->type]);
277 if (can_player_see_units_in_city(client_player(), pcity)) {
278 int count = unit_list_size(ptile->units);
280 if (count > 0) {
281 astr_add(&str, PL_(" | Occupied with %d unit.",
282 " | Occupied with %d units.", count), count);
283 } else {
284 astr_add(&str, _(" | Not occupied."));
286 } else {
287 if (city_is_occupied(pcity)) {
288 astr_add(&str, _(" | Occupied."));
289 } else {
290 astr_add(&str, _(" | Not occupied."));
293 improvement_iterate(pimprove) {
294 if (is_improvement_visible(pimprove)
295 && city_has_building(pcity, pimprove)) {
296 improvements[has_improvements++] =
297 improvement_name_translation(pimprove);
299 } improvement_iterate_end;
301 if (0 < has_improvements) {
302 struct astring list = ASTRING_INIT;
304 astr_build_and_list(&list, improvements, has_improvements);
305 /* TRANS: %s is a list of "and"-separated improvements. */
306 astr_add_line(&str, _(" with %s."), astr_str(&list));
307 astr_free(&list);
310 unit_list_iterate(get_units_in_focus(), pfocus_unit) {
311 struct city *hcity = game_city_by_number(pfocus_unit->homecity);
313 if (utype_can_do_action(unit_type_get(pfocus_unit), ACTION_TRADE_ROUTE)
314 && can_cities_trade(hcity, pcity)
315 && can_establish_trade_route(hcity, pcity)) {
316 /* TRANS: "Trade from Warsaw: 5" */
317 astr_add_line(&str, _("Trade from %s: %d"),
318 city_name_get(hcity),
319 trade_base_between_cities(hcity, pcity));
321 } unit_list_iterate_end;
324 const char *infratext = get_infrastructure_text(ptile->extras);
326 if (*infratext != '\0') {
327 astr_add_line(&str, _("Infrastructure: %s"), infratext);
330 activity_text = concat_tile_activity_text(ptile);
331 if (strlen(activity_text) > 0) {
332 astr_add_line(&str, _("Activity: %s"), activity_text);
334 if (punit && !pcity) {
335 struct player *owner = unit_owner(punit);
336 struct unit_type *ptype = unit_type_get(punit);
338 get_full_username(username, sizeof(username), owner);
339 get_full_nation(nation, sizeof(nation), owner);
341 if (!client_player() || owner == client_player()) {
342 struct city *hcity = player_city_by_number(owner, punit->homecity);
344 /* TRANS: "Unit: <unit type> | <username> (<nation + team>)" */
345 astr_add_line(&str, _("Unit: %s | %s (%s)"),
346 utype_name_translation(ptype), username, nation);
348 if (game.info.citizen_nationality
349 && unit_nationality(punit) != unit_owner(punit)) {
350 if (hcity != NULL) {
351 /* TRANS: on own line immediately following \n, "from <city> |
352 * <nationality> people" */
353 astr_add_line(&str, _("from %s | %s people"), city_name_get(hcity),
354 nation_adjective_for_player(unit_nationality(punit)));
355 } else {
356 /* TRANS: Nationality of the people comprising a unit, if
357 * different from owner. */
358 astr_add_line(&str, _("%s people"),
359 nation_adjective_for_player(unit_nationality(punit)));
361 } else if (hcity != NULL) {
362 /* TRANS: on own line immediately following \n, ... <city> */
363 astr_add_line(&str, _("from %s"), city_name_get(hcity));
365 } else if (NULL != owner) {
366 struct player_diplstate *ds = player_diplstate_get(client_player(),
367 owner);
368 if (ds->type == DS_CEASEFIRE) {
369 int turns = ds->turns_left;
371 /* TRANS: "Unit: <unit type> | <username> (<nation + team>,
372 * <number> turn cease-fire)" */
373 astr_add_line(&str, PL_("Unit: %s | %s (%s, %d turn cease-fire)",
374 "Unit: %s | %s (%s, %d turn cease-fire)",
375 turns),
376 utype_name_translation(ptype),
377 username, nation, turns);
378 } else if (ds->type == DS_ARMISTICE) {
379 int turns = ds->turns_left;
381 /* TRANS: "Unit: <unit type> | <username> (<nation + team>,
382 * <number> turn armistice)" */
383 astr_add_line(&str, PL_("Unit: %s | %s (%s, %d turn armistice)",
384 "Unit: %s | %s (%s, %d turn armistice)",
385 turns),
386 utype_name_translation(ptype),
387 username, nation, turns);
388 } else {
389 /* TRANS: "Unit: <unit type> | <username> (<nation + team>,
390 * <diplomatic state>)" */
391 astr_add_line(&str, _("Unit: %s | %s (%s, %s)"),
392 utype_name_translation(ptype), username, nation,
393 diplo_city_adjectives[ds->type]);
397 unit_list_iterate(get_units_in_focus(), pfocus_unit) {
398 int att_chance = FC_INFINITY, def_chance = FC_INFINITY;
399 bool found = FALSE;
401 unit_list_iterate(ptile->units, tile_unit) {
402 if (unit_owner(tile_unit) != unit_owner(pfocus_unit)) {
403 int att = unit_win_chance(pfocus_unit, tile_unit) * 100;
404 int def = (1.0 - unit_win_chance(tile_unit, pfocus_unit)) * 100;
406 found = TRUE;
408 /* Presumably the best attacker and defender will be used. */
409 att_chance = MIN(att, att_chance);
410 def_chance = MIN(def, def_chance);
412 } unit_list_iterate_end;
414 if (found) {
415 /* TRANS: "Chance to win: A:95% D:46%" */
416 astr_add_line(&str, _("Chance to win: A:%d%% D:%d%%"),
417 att_chance, def_chance);
419 } unit_list_iterate_end;
421 /* TRANS: A is attack power, D is defense power, FP is firepower,
422 * HP is hitpoints (current and max). */
423 astr_add_line(&str, _("A:%d D:%d FP:%d HP:%d/%d"),
424 ptype->attack_strength, ptype->defense_strength,
425 ptype->firepower, punit->hp, ptype->hp);
427 const char *veteran_name =
428 utype_veteran_name_translation(ptype, punit->veteran);
429 if (veteran_name) {
430 astr_add(&str, " (%s)", veteran_name);
434 if (unit_owner(punit) == client_player()
435 || client_is_global_observer()) {
436 /* Show bribe cost for own units. */
437 astr_add_line(&str, _("Probable bribe cost: %d"),
438 unit_bribe_cost(punit, NULL));
439 } else {
440 /* We can only give an (lower) boundary for units of other players. */
441 astr_add_line(&str, _("Estimated bribe cost: > %d"),
442 unit_bribe_cost(punit, client_player()));
445 if ((NULL == client.conn.playing || owner == client.conn.playing)
446 && unit_list_size(ptile->units) >= 2) {
447 /* TRANS: "5 more" units on this tile */
448 astr_add(&str, _(" (%d more)"), unit_list_size(ptile->units) - 1);
452 astr_break_lines(&str, LINE_BREAK);
453 return astr_str(&str);
456 #define FAR_CITY_SQUARE_DIST (2*(6*6))
457 /****************************************************************************
458 Returns the text describing the city and its distance.
459 ****************************************************************************/
460 const char *get_nearest_city_text(struct city *pcity, int sq_dist)
462 static struct astring str = ASTRING_INIT;
464 astr_clear(&str);
466 /* just to be sure */
467 if (!pcity) {
468 sq_dist = -1;
471 astr_add(&str, (sq_dist >= FAR_CITY_SQUARE_DIST)
472 /* TRANS: on own line immediately following \n, ... <city> */
473 ? _("far from %s")
474 : (sq_dist > 0)
475 /* TRANS: on own line immediately following \n, ... <city> */
476 ? _("near %s")
477 : (sq_dist == 0)
478 /* TRANS: on own line immediately following \n, ... <city> */
479 ? _("in %s")
480 : "%s",
481 pcity
482 ? city_name_get(pcity)
483 : "");
485 return astr_str(&str);
488 /****************************************************************************
489 Returns the unit description.
490 Used in e.g. city report tooltips.
492 FIXME: This function is not re-entrant because it returns a pointer to
493 static data.
494 ****************************************************************************/
495 const char *unit_description(struct unit *punit)
497 int pcity_near_dist;
498 struct player *owner = unit_owner(punit);
499 struct player *nationality = unit_nationality(punit);
500 struct city *pcity =
501 player_city_by_number(owner, punit->homecity);
502 struct city *pcity_near = get_nearest_city(punit, &pcity_near_dist);
503 struct unit_type *ptype = unit_type_get(punit);
504 static struct astring str = ASTRING_INIT;
505 const struct player *pplayer = client_player();
507 astr_clear(&str);
509 astr_add(&str, "%s", utype_name_translation(ptype));
512 const char *veteran_name =
513 utype_veteran_name_translation(ptype, punit->veteran);
514 if (veteran_name) {
515 astr_add(&str, " (%s)", veteran_name);
519 if (pplayer == owner) {
520 unit_upkeep_astr(punit, &str);
521 } else {
522 astr_add(&str, "\n");
524 unit_activity_astr(punit, &str);
526 if (pcity) {
527 /* TRANS: on own line immediately following \n, ... <city> */
528 astr_add_line(&str, _("from %s"), city_name_get(pcity));
529 } else {
530 astr_add(&str, "\n");
532 if (game.info.citizen_nationality) {
533 if (nationality != NULL && owner != nationality) {
534 /* TRANS: Nationality of the people comprising a unit, if
535 * different from owner. */
536 astr_add_line(&str, _("%s people"),
537 nation_adjective_for_player(nationality));
538 } else {
539 astr_add(&str, "\n");
543 astr_add_line(&str, "%s",
544 get_nearest_city_text(pcity_near, pcity_near_dist));
545 #ifdef FREECIV_DEBUG
546 astr_add_line(&str, "Unit ID: %d", punit->id);
547 #endif
549 return astr_str(&str);
552 /****************************************************************************
553 Describe the airlift capacity of a city for the given units (from their
554 current positions).
555 If pdest is non-NULL, describe its capacity as a destination, otherwise
556 describe the capacity of the city the unit's currently in (if any) as a
557 source. (If the units in the list are in different cities, this will
558 probably not give a useful result in this case.)
559 If not all of the listed units can be airlifted, return the description
560 for those that can.
561 Returns NULL if an airlift is not possible for any of the units.
562 ****************************************************************************/
563 const char *get_airlift_text(const struct unit_list *punits,
564 const struct city *pdest)
566 static struct astring str = ASTRING_INIT;
567 bool src = (pdest == NULL);
568 enum texttype { AL_IMPOSSIBLE, AL_UNKNOWN, AL_FINITE, AL_INFINITE }
569 best = AL_IMPOSSIBLE;
570 int cur = 0, max = 0;
572 unit_list_iterate(punits, punit) {
573 enum texttype this = AL_IMPOSSIBLE;
574 enum unit_airlift_result result;
576 /* NULL will tell us about the capability of airlifting from source */
577 result = test_unit_can_airlift_to(client_player(), punit, pdest);
579 switch(result) {
580 case AR_NO_MOVES:
581 case AR_WRONG_UNITTYPE:
582 case AR_OCCUPIED:
583 case AR_NOT_IN_CITY:
584 case AR_BAD_SRC_CITY:
585 case AR_BAD_DST_CITY:
586 /* No chance of an airlift. */
587 this = AL_IMPOSSIBLE;
588 break;
589 case AR_OK:
590 case AR_OK_SRC_UNKNOWN:
591 case AR_OK_DST_UNKNOWN:
592 case AR_SRC_NO_FLIGHTS:
593 case AR_DST_NO_FLIGHTS:
594 /* May or may not be able to airlift now, but there's a chance we could
595 * later */
597 const struct city *pcity = src ? tile_city(unit_tile(punit)) : pdest;
598 fc_assert_ret_val(pcity != NULL, fc_strdup("-"));
599 if (!src && (game.info.airlifting_style & AIRLIFTING_UNLIMITED_DEST)) {
600 /* No restrictions on destination (and we can infer this even for
601 * other players' cities). */
602 this = AL_INFINITE;
603 } else if (client_player() == city_owner(pcity)) {
604 /* A city we know about. */
605 int this_cur = pcity->airlift, this_max = city_airlift_max(pcity);
606 if (this_max <= 0) {
607 /* City known not to be airlift-capable. */
608 this = AL_IMPOSSIBLE;
609 } else {
610 if (src
611 && (game.info.airlifting_style & AIRLIFTING_UNLIMITED_SRC)) {
612 /* Unlimited capacity. */
613 this = AL_INFINITE;
614 } else {
615 /* Limited capacity (possibly zero right now). */
616 this = AL_FINITE;
617 /* Store the numbers. This whole setup assumes that numeric
618 * capacity isn't unit-dependent. */
619 if (best == AL_FINITE) {
620 fc_assert(cur == this_cur && max == this_max);
622 cur = this_cur;
623 max = this_max;
626 } else {
627 /* Unknown capacity. */
628 this = AL_UNKNOWN;
631 break;
634 /* Now take the most optimistic view. */
635 best = MAX(best, this);
636 } unit_list_iterate_end;
638 switch(best) {
639 case AL_IMPOSSIBLE:
640 return NULL;
641 case AL_UNKNOWN:
642 astr_set(&str, "?");
643 break;
644 case AL_FINITE:
645 astr_set(&str, "%d/%d", cur, max);
646 break;
647 case AL_INFINITE:
648 astr_set(&str, _("Yes"));
649 break;
652 return astr_str(&str);
655 /****************************************************************************
656 Return total expected bulbs.
657 ****************************************************************************/
658 static int get_bulbs_per_turn(int *pours, bool *pteam, int *ptheirs)
660 const struct research *presearch;
661 int ours = 0, theirs = 0;
662 bool team = FALSE;
664 if (!client_has_player()) {
665 return 0;
667 presearch = research_get(client_player());
669 /* Sum up science */
670 research_players_iterate(presearch, pplayer) {
671 if (pplayer == client_player()) {
672 city_list_iterate(pplayer->cities, pcity) {
673 ours += pcity->surplus[O_SCIENCE];
674 } city_list_iterate_end;
675 } else {
676 team = TRUE;
677 theirs -= pplayer->client.tech_upkeep;
679 } research_players_iterate_end;
681 if (team) {
682 theirs += presearch->client.total_bulbs_prod - ours;
684 ours -= client_player()->client.tech_upkeep;
686 if (pours) {
687 *pours = ours;
689 if (pteam) {
690 *pteam = team;
692 if (ptheirs) {
693 *ptheirs = theirs;
695 return ours + theirs;
698 /****************************************************************************
699 Returns the text to display in the science dialog.
700 ****************************************************************************/
701 const char *science_dialog_text(void)
703 bool team;
704 int ours, theirs, perturn, upkeep;
705 static struct astring str = ASTRING_INIT;
706 struct astring ourbuf = ASTRING_INIT, theirbuf = ASTRING_INIT;
707 struct research *research;
709 astr_clear(&str);
711 perturn = get_bulbs_per_turn(&ours, &team, &theirs);
713 research = research_get(client_player());
714 upkeep = client_player()->client.tech_upkeep;
716 if (NULL == client.conn.playing || (ours == 0 && theirs == 0
717 && upkeep == 0)) {
718 return _("Progress: no research");
721 if (A_UNSET == research->researching) {
722 astr_add(&str, _("Progress: no research"));
723 } else {
724 int done = research->bulbs_researched;
725 int total = research->client.researching_cost;
727 if (perturn > 0) {
728 int turns = MAX(1, ceil((double)total) / perturn);
730 astr_add(&str, PL_("Progress: %d turn/advance",
731 "Progress: %d turns/advance",
732 turns), turns);
733 } else if (perturn < 0 ) {
734 /* negative number of bulbs per turn due to tech upkeep */
735 int turns = ceil((double) done / -perturn);
737 astr_add(&str, PL_("Progress: %d turn/advance loss",
738 "Progress: %d turns/advance loss",
739 turns), turns);
740 } else {
741 /* no research */
742 astr_add(&str, _("Progress: none"));
745 astr_set(&ourbuf, PL_("%d bulb/turn", "%d bulbs/turn", ours), ours);
746 if (team) {
747 /* Techpool version */
748 astr_set(&theirbuf,
749 /* TRANS: This is appended to "%d bulb/turn" text */
750 PL_(", %d bulb/turn from team",
751 ", %d bulbs/turn from team", theirs), theirs);
752 } else {
753 astr_clear(&theirbuf);
755 astr_add(&str, " (%s%s)", astr_str(&ourbuf), astr_str(&theirbuf));
756 astr_free(&ourbuf);
757 astr_free(&theirbuf);
759 if (game.info.tech_upkeep_style != TECH_UPKEEP_NONE) {
760 /* perturn is defined as: (bulbs produced) - upkeep */
761 astr_add_line(&str, _("Bulbs produced per turn: %d"), perturn + upkeep);
762 /* TRANS: keep leading space; appended to "Bulbs produced per turn: %d" */
763 astr_add(&str, _(" (needed for technology upkeep: %d)"), upkeep);
766 return astr_str(&str);
769 /****************************************************************************
770 Get the short science-target text. This is usually shown directly in
771 the progress bar.
773 5/28 - 3 turns
775 The "percent" value, if given, will be set to the completion percentage
776 of the research target (actually it's a [0,1] scale not a percent).
777 ****************************************************************************/
778 const char *get_science_target_text(double *percent)
780 struct research *research = research_get(client_player());
781 static struct astring str = ASTRING_INIT;
783 if (!research) {
784 return "-";
787 astr_clear(&str);
788 if (research->researching == A_UNSET) {
789 astr_add(&str, _("%d/- (never)"), research->bulbs_researched);
790 if (percent) {
791 *percent = 0.0;
793 } else {
794 int total = research->client.researching_cost;
795 int done = research->bulbs_researched;
796 int perturn = get_bulbs_per_turn(NULL, NULL, NULL);
798 if (perturn > 0) {
799 int turns = ceil( (double)(total - done) / perturn );
801 astr_add(&str, PL_("%d/%d (%d turn)", "%d/%d (%d turns)", turns),
802 done, total, turns);
803 } else if (perturn < 0 ) {
804 /* negative number of bulbs per turn due to tech upkeep */
805 int turns = ceil( (double)done / -perturn );
807 astr_add(&str, PL_("%d/%d (%d turn)", "%d/%d (%d turns)", turns),
808 done, perturn, turns);
809 } else {
810 /* no research */
811 astr_add(&str, _("%d/%d (never)"), done, total);
813 if (percent) {
814 *percent = (double)done / (double)total;
815 *percent = CLIP(0.0, *percent, 1.0);
819 return astr_str(&str);
822 /****************************************************************************
823 Set the science-goal-label text as if we're researching the given goal.
824 ****************************************************************************/
825 const char *get_science_goal_text(Tech_type_id goal)
827 const struct research *research = research_get(client_player());
828 int steps = research_goal_unknown_techs(research, goal);
829 int bulbs_needed = research_goal_bulbs_required(research, goal);
830 int turns;
831 int perturn = get_bulbs_per_turn(NULL, NULL, NULL);
832 static struct astring str = ASTRING_INIT;
833 struct astring buf1 = ASTRING_INIT,
834 buf2 = ASTRING_INIT,
835 buf3 = ASTRING_INIT;
837 if (!research) {
838 return "-";
841 astr_clear(&str);
843 if (research_goal_tech_req(research, goal, research->researching)
844 || research->researching == goal) {
845 bulbs_needed -= research->bulbs_researched;
848 astr_set(&buf1,
849 PL_("%d step", "%d steps", steps), steps);
850 astr_set(&buf2,
851 PL_("%d bulb", "%d bulbs", bulbs_needed), bulbs_needed);
852 if (perturn > 0) {
853 turns = (bulbs_needed + perturn - 1) / perturn;
854 astr_set(&buf3,
855 PL_("%d turn", "%d turns", turns), turns);
856 } else {
857 astr_set(&buf3, _("never"));
859 astr_add_line(&str, "(%s - %s - %s)",
860 astr_str(&buf1), astr_str(&buf2), astr_str(&buf3));
861 astr_free(&buf1);
862 astr_free(&buf2);
863 astr_free(&buf3);
865 return astr_str(&str);
868 /****************************************************************************
869 Return the text for the label on the info panel. (This is traditionally
870 shown to the left of the mapview.)
872 Clicking on this text should bring up the get_info_label_text_popup text.
873 ****************************************************************************/
874 const char *get_info_label_text(bool moreinfo)
876 static struct astring str = ASTRING_INIT;
878 astr_clear(&str);
880 if (NULL != client.conn.playing) {
881 astr_add_line(&str, _("Population: %s"),
882 population_to_text(civ_population(client.conn.playing)));
884 astr_add_line(&str, _("Year: %s (T%d)"),
885 calendar_text(), game.info.turn);
887 if (NULL != client.conn.playing) {
888 astr_add_line(&str, _("Gold: %d (%+d)"),
889 client.conn.playing->economic.gold,
890 player_get_expected_income(client.conn.playing));
891 astr_add_line(&str, _("Tax: %d Lux: %d Sci: %d"),
892 client.conn.playing->economic.tax,
893 client.conn.playing->economic.luxury,
894 client.conn.playing->economic.science);
896 if (game.info.phase_mode == PMT_PLAYERS_ALTERNATE) {
897 if (game.info.phase < 0 || game.info.phase >= player_count()) {
898 astr_add_line(&str, _("Moving: Nobody"));
899 } else {
900 astr_add_line(&str, _("Moving: %s"),
901 player_name(player_by_number(game.info.phase)));
903 } else if (game.info.phase_mode == PMT_TEAMS_ALTERNATE) {
904 if (game.info.phase < 0 || game.info.phase >= team_count()) {
905 astr_add_line(&str, _("Moving: Nobody"));
906 } else {
907 astr_add_line(&str, _("Moving: %s"),
908 team_name_translation(team_by_number(game.info.phase)));
912 if (moreinfo) {
913 astr_add_line(&str, _("(Click for more info)"));
916 return astr_str(&str);
919 /****************************************************************************
920 Return the text for the popup label on the info panel. (This is
921 traditionally done as a popup whenever the regular info text is clicked
922 on.)
923 ****************************************************************************/
924 const char *get_info_label_text_popup(void)
926 static struct astring str = ASTRING_INIT;
928 astr_clear(&str);
930 if (NULL != client.conn.playing) {
931 astr_add_line(&str, _("%s People"),
932 population_to_text(civ_population(client.conn.playing)));
934 astr_add_line(&str, _("Year: %s"), calendar_text());
935 astr_add_line(&str, _("Turn: %d"), game.info.turn);
937 if (NULL != client.conn.playing) {
938 const struct research *presearch = research_get(client_player());
939 int perturn = get_bulbs_per_turn(NULL, NULL, NULL);
940 int upkeep = client_player()->client.tech_upkeep;
942 astr_add_line(&str, _("Gold: %d"),
943 client.conn.playing->economic.gold);
944 astr_add_line(&str, _("Net Income: %d"),
945 player_get_expected_income(client.conn.playing));
946 /* TRANS: Gold, luxury, and science rates are in percentage values. */
947 astr_add_line(&str, _("Tax rates: Gold:%d%% Luxury:%d%% Science:%d%%"),
948 client.conn.playing->economic.tax,
949 client.conn.playing->economic.luxury,
950 client.conn.playing->economic.science);
951 astr_add_line(&str, _("Researching %s: %s"),
952 research_advance_name_translation(presearch,
953 presearch->researching),
954 get_science_target_text(NULL));
955 /* perturn is defined as: (bulbs produced) - upkeep */
956 if (game.info.tech_upkeep_style != TECH_UPKEEP_NONE) {
957 astr_add_line(&str, _("Bulbs per turn: %d - %d = %d"), perturn + upkeep,
958 upkeep, perturn);
959 } else {
960 fc_assert(upkeep == 0);
961 astr_add_line(&str, _("Bulbs per turn: %d"), perturn);
965 /* See also get_global_warming_tooltip and get_nuclear_winter_tooltip. */
967 if (game.info.global_warming) {
968 int chance, rate;
969 global_warming_scaled(&chance, &rate, 100);
970 astr_add_line(&str, _("Global warming chance: %d%% (%+d%%/turn)"),
971 chance, rate);
972 } else {
973 astr_add_line(&str, _("Global warming deactivated."));
976 if (game.info.nuclear_winter) {
977 int chance, rate;
978 nuclear_winter_scaled(&chance, &rate, 100);
979 astr_add_line(&str, _("Nuclear winter chance: %d%% (%+d%%/turn)"),
980 chance, rate);
981 } else {
982 astr_add_line(&str, _("Nuclear winter deactivated."));
985 if (NULL != client.conn.playing) {
986 astr_add_line(&str, _("Government: %s"),
987 government_name_for_player(client.conn.playing));
990 return astr_str(&str);
993 /****************************************************************************
994 Return the title text for the unit info shown in the info panel.
996 FIXME: this should be renamed.
997 ****************************************************************************/
998 const char *get_unit_info_label_text1(struct unit_list *punits)
1000 static struct astring str = ASTRING_INIT;
1002 astr_clear(&str);
1004 if (punits) {
1005 int count = unit_list_size(punits);
1007 if (count == 1) {
1008 astr_add(&str, "%s", unit_name_translation(unit_list_get(punits, 0)));
1009 } else {
1010 astr_add(&str, PL_("%d unit", "%d units", count), count);
1013 return astr_str(&str);
1016 /****************************************************************************
1017 Return the text body for the unit info shown in the info panel.
1019 FIXME: this should be renamed.
1020 ****************************************************************************/
1021 const char *get_unit_info_label_text2(struct unit_list *punits, int linebreaks)
1023 static struct astring str = ASTRING_INIT;
1024 int count;
1026 astr_clear(&str);
1028 if (!punits) {
1029 return "";
1032 count = unit_list_size(punits);
1034 /* This text should always have the same number of lines if
1035 * 'linebreaks' has no flags at all. Otherwise the GUI widgets may be
1036 * confused and try to resize themselves. If caller asks for
1037 * conditional 'linebreaks', it should take care of these problems
1038 * itself. */
1040 /* Line 1. Goto or activity text. */
1041 if (count > 0 && hover_state != HOVER_NONE) {
1042 int min, max;
1044 if (!goto_get_turns(&min, &max)) {
1045 /* TRANS: Impossible to reach goto target tile */
1046 astr_add_line(&str, "%s", Q_("?goto:Unreachable"));
1047 } else if (min == max) {
1048 astr_add_line(&str, _("Turns to target: %d"), max);
1049 } else {
1050 astr_add_line(&str, _("Turns to target: %d to %d"), min, max);
1052 } else if (count == 1) {
1053 astr_add_line(&str, "%s",
1054 unit_activity_text(unit_list_get(punits, 0)));
1055 } else if (count > 1) {
1056 astr_add_line(&str, PL_("%d unit selected",
1057 "%d units selected",
1058 count),
1059 count);
1060 } else {
1061 astr_add_line(&str, _("No units selected."));
1064 /* Lines 2, 3, 4, and possible 5 vary. */
1065 if (count == 1) {
1066 struct unit *punit = unit_list_get(punits, 0);
1067 struct player *owner = unit_owner(punit);
1068 struct city *pcity = player_city_by_number(owner,
1069 punit->homecity);
1071 astr_add_line(&str, "%s", tile_get_info_text(unit_tile(punit), TRUE,
1072 linebreaks));
1074 const char *infratext = get_infrastructure_text(unit_tile(punit)->extras);
1076 if (*infratext != '\0') {
1077 astr_add_line(&str, "%s", infratext);
1078 } else {
1079 astr_add_line(&str, " ");
1082 if (pcity) {
1083 astr_add_line(&str, "%s", city_name_get(pcity));
1084 } else {
1085 astr_add_line(&str, " ");
1088 if (game.info.citizen_nationality) {
1089 struct player *nationality = unit_nationality(punit);
1091 /* Line 5, nationality text */
1092 if (nationality != NULL && owner != nationality) {
1093 /* TRANS: Nationality of the people comprising a unit, if
1094 * different from owner. */
1095 astr_add_line(&str, _("%s people"),
1096 nation_adjective_for_player(nationality));
1097 } else {
1098 astr_add_line(&str, " ");
1102 } else if (count > 1) {
1103 int mil = 0, nonmil = 0;
1104 int types_count[U_LAST], i;
1105 struct unit_type *top[3];
1107 memset(types_count, 0, sizeof(types_count));
1108 unit_list_iterate(punits, punit) {
1109 if (unit_has_type_flag(punit, UTYF_CIVILIAN)) {
1110 nonmil++;
1111 } else {
1112 mil++;
1114 types_count[utype_index(unit_type_get(punit))]++;
1115 } unit_list_iterate_end;
1117 top[0] = top[1] = top[2] = NULL;
1118 unit_type_iterate(utype) {
1119 if (!top[2]
1120 || types_count[utype_index(top[2])] < types_count[utype_index(utype)]) {
1121 top[2] = utype;
1123 if (!top[1]
1124 || types_count[utype_index(top[1])] < types_count[utype_index(top[2])]) {
1125 top[2] = top[1];
1126 top[1] = utype;
1128 if (!top[0]
1129 || types_count[utype_index(top[0])] < types_count[utype_index(utype)]) {
1130 top[1] = top[0];
1131 top[0] = utype;
1135 } unit_type_iterate_end;
1137 for (i = 0; i < 2; i++) {
1138 if (top[i] && types_count[utype_index(top[i])] > 0) {
1139 if (utype_has_flag(top[i], UTYF_CIVILIAN)) {
1140 nonmil -= types_count[utype_index(top[i])];
1141 } else {
1142 mil -= types_count[utype_index(top[i])];
1144 astr_add_line(&str, "%d: %s",
1145 types_count[utype_index(top[i])],
1146 utype_name_translation(top[i]));
1147 } else {
1148 astr_add_line(&str, " ");
1152 if (top[2] && types_count[utype_index(top[2])] > 0
1153 && types_count[utype_index(top[2])] == nonmil + mil) {
1154 astr_add_line(&str, "%d: %s", types_count[utype_index(top[2])],
1155 utype_name_translation(top[2]));
1156 } else if (nonmil > 0 && mil > 0) {
1157 astr_add_line(&str, _("Others: %d civil; %d military"), nonmil, mil);
1158 } else if (nonmil > 0) {
1159 astr_add_line(&str, _("Others: %d civilian"), nonmil);
1160 } else if (mil > 0) {
1161 astr_add_line(&str, _("Others: %d military"), mil);
1162 } else {
1163 astr_add_line(&str, " ");
1166 if (game.info.citizen_nationality) {
1167 astr_add_line(&str, " ");
1169 } else {
1170 astr_add_line(&str, " ");
1171 astr_add_line(&str, " ");
1172 astr_add_line(&str, " ");
1174 if (game.info.citizen_nationality) {
1175 astr_add_line(&str, " ");
1179 /* Line 5/6. Debug text. */
1180 #ifdef FREECIV_DEBUG
1181 if (count == 1) {
1182 astr_add_line(&str, "(Unit ID %d)", unit_list_get(punits, 0)->id);
1183 } else {
1184 astr_add_line(&str, " ");
1186 #endif /* FREECIV_DEBUG */
1188 return astr_str(&str);
1191 /****************************************************************************
1192 Return text about upgrading these unit lists.
1194 Returns TRUE iff any units can be upgraded.
1195 ****************************************************************************/
1196 bool get_units_upgrade_info(char *buf, size_t bufsz,
1197 struct unit_list *punits)
1199 if (unit_list_size(punits) == 0) {
1200 fc_snprintf(buf, bufsz, _("No units to upgrade!"));
1201 return FALSE;
1202 } else if (unit_list_size(punits) == 1) {
1203 return (UU_OK == unit_upgrade_info(unit_list_front(punits), buf, bufsz));
1204 } else {
1205 int upgrade_cost = 0;
1206 int num_upgraded = 0;
1207 int min_upgrade_cost = FC_INFINITY;
1209 unit_list_iterate(punits, punit) {
1210 if (unit_owner(punit) == client_player()
1211 && UU_OK == unit_upgrade_test(punit, FALSE)) {
1212 struct unit_type *from_unittype = unit_type_get(punit);
1213 struct unit_type *to_unittype = can_upgrade_unittype(client.conn.playing,
1214 from_unittype);
1215 int cost = unit_upgrade_price(unit_owner(punit),
1216 from_unittype, to_unittype);
1218 num_upgraded++;
1219 upgrade_cost += cost;
1220 min_upgrade_cost = MIN(min_upgrade_cost, cost);
1222 } unit_list_iterate_end;
1223 if (num_upgraded == 0) {
1224 fc_snprintf(buf, bufsz, _("None of these units may be upgraded."));
1225 return FALSE;
1226 } else {
1227 /* This may trigger sometimes if you don't have enough money for
1228 * a full upgrade. If you have enough to upgrade at least one, it
1229 * will do it. */
1230 /* Construct prompt in several parts to allow separate pluralisation
1231 * by localizations */
1232 char tbuf[MAX_LEN_MSG], ubuf[MAX_LEN_MSG];
1233 fc_snprintf(tbuf, ARRAY_SIZE(tbuf), PL_("Treasury contains %d gold.",
1234 "Treasury contains %d gold.",
1235 client_player()->economic.gold),
1236 client_player()->economic.gold);
1237 /* TRANS: this whole string is a sentence fragment that is only ever
1238 * used by including it in another string (search comments for this
1239 * string to find it) */
1240 fc_snprintf(ubuf, ARRAY_SIZE(ubuf), PL_("Upgrade %d unit",
1241 "Upgrade %d units",
1242 num_upgraded),
1243 num_upgraded);
1244 /* TRANS: This is complicated. The first %s is a pre-pluralised
1245 * sentence fragment "Upgrade %d unit(s)"; the second is pre-pluralised
1246 * "Treasury contains %d gold." So the whole thing reads
1247 * "Upgrade 13 units for 1000 gold?\nTreasury contains 2000 gold." */
1248 fc_snprintf(buf, bufsz, PL_("%s for %d gold?\n%s",
1249 "%s for %d gold?\n%s", upgrade_cost),
1250 ubuf, upgrade_cost, tbuf);
1251 return TRUE;
1256 /****************************************************************************
1257 Return text about disbanding these units.
1259 Returns TRUE iff any units can be disbanded.
1260 ****************************************************************************/
1261 bool get_units_disband_info(char *buf, size_t bufsz,
1262 struct unit_list *punits)
1264 if (unit_list_size(punits) == 0) {
1265 fc_snprintf(buf, bufsz, _("No units to disband!"));
1266 return FALSE;
1267 } else if (unit_list_size(punits) == 1) {
1268 if (!unit_can_do_action(unit_list_front(punits),
1269 ACTION_DISBAND_UNIT)) {
1270 fc_snprintf(buf, bufsz, _("%s refuses to disband!"),
1271 unit_name_translation(unit_list_front(punits)));
1272 return FALSE;
1273 } else {
1274 /* TRANS: %s is a unit type */
1275 fc_snprintf(buf, bufsz, _("Disband %s?"),
1276 unit_name_translation(unit_list_front(punits)));
1277 return TRUE;
1279 } else {
1280 int count = 0;
1281 unit_list_iterate(punits, punit) {
1282 if (unit_can_do_action(punit, ACTION_DISBAND_UNIT)) {
1283 count++;
1285 } unit_list_iterate_end;
1286 if (count == 0) {
1287 fc_snprintf(buf, bufsz, _("None of these units may be disbanded."));
1288 return FALSE;
1289 } else {
1290 /* TRANS: %d is never 0 or 1 */
1291 fc_snprintf(buf, bufsz, PL_("Disband %d unit?",
1292 "Disband %d units?", count), count);
1293 return TRUE;
1298 /****************************************************************************
1299 Get a tooltip text for the info panel research indicator. See
1300 client_research_sprite().
1301 ****************************************************************************/
1302 const char *get_bulb_tooltip(void)
1304 static struct astring str = ASTRING_INIT;
1306 astr_clear(&str);
1308 astr_add_line(&str, _("Shows your progress in "
1309 "researching the current technology."));
1311 if (NULL != client.conn.playing) {
1312 struct research *research = research_get(client_player());
1314 if (research->researching == A_UNSET) {
1315 astr_add_line(&str, _("no research target."));
1316 } else {
1317 int turns = 0;
1318 int perturn = get_bulbs_per_turn(NULL, NULL, NULL);
1319 int done = research->bulbs_researched;
1320 int total = research->client.researching_cost;
1321 struct astring buf1 = ASTRING_INIT, buf2 = ASTRING_INIT;
1323 if (perturn > 0) {
1324 turns = MAX(1, ceil((double) (total - done) / perturn));
1325 } else if (perturn < 0 ) {
1326 turns = ceil((double) done / -perturn);
1329 if (turns == 0) {
1330 astr_set(&buf1, _("No progress"));
1331 } else {
1332 astr_set(&buf1, PL_("%d turn", "%d turns", turns), turns);
1335 /* TRANS: <perturn> bulbs/turn */
1336 astr_set(&buf2, PL_("%d bulb/turn", "%d bulbs/turn", perturn), perturn);
1338 /* TRANS: <tech>: <amount>/<total bulbs> */
1339 astr_add_line(&str, _("%s: %d/%d (%s, %s)."),
1340 research_advance_name_translation(research,
1341 research->researching),
1342 research->bulbs_researched,
1343 research->client.researching_cost,
1344 astr_str(&buf1), astr_str(&buf2));
1346 astr_free(&buf1);
1347 astr_free(&buf2);
1350 return astr_str(&str);
1353 /****************************************************************************
1354 Get a tooltip text for the info panel global warning indicator. See also
1355 client_warming_sprite().
1356 ****************************************************************************/
1357 const char *get_global_warming_tooltip(void)
1359 static struct astring str = ASTRING_INIT;
1361 astr_clear(&str);
1363 if (!game.info.global_warming) {
1364 astr_add_line(&str, _("Global warming deactivated."));
1365 } else {
1366 int chance, rate;
1367 global_warming_scaled(&chance, &rate, 100);
1368 astr_add_line(&str, _("Shows the progress of global warming:"));
1369 astr_add_line(&str, _("Pollution rate: %d%%"), rate);
1370 astr_add_line(&str, _("Chance of catastrophic warming each turn: %d%%"),
1371 chance);
1374 return astr_str(&str);
1377 /****************************************************************************
1378 Get a tooltip text for the info panel nuclear winter indicator. See also
1379 client_cooling_sprite().
1380 ****************************************************************************/
1381 const char *get_nuclear_winter_tooltip(void)
1383 static struct astring str = ASTRING_INIT;
1385 astr_clear(&str);
1387 if (!game.info.nuclear_winter) {
1388 astr_add_line(&str, _("Nuclear winter deactivated."));
1389 } else {
1390 int chance, rate;
1391 nuclear_winter_scaled(&chance, &rate, 100);
1392 astr_add_line(&str, _("Shows the progress of nuclear winter:"));
1393 astr_add_line(&str, _("Fallout rate: %d%%"), rate);
1394 astr_add_line(&str, _("Chance of catastrophic winter each turn: %d%%"),
1395 chance);
1398 return astr_str(&str);
1401 /****************************************************************************
1402 Get a tooltip text for the info panel government indicator. See also
1403 government_by_number(...)->sprite.
1404 ****************************************************************************/
1405 const char *get_government_tooltip(void)
1407 static struct astring str = ASTRING_INIT;
1409 astr_clear(&str);
1411 astr_add_line(&str, _("Shows your current government:"));
1413 if (NULL != client.conn.playing) {
1414 astr_add_line(&str, "%s",
1415 government_name_for_player(client.conn.playing));
1417 return astr_str(&str);
1420 /****************************************************************************
1421 Returns a description of the given spaceship. If there is no spaceship
1422 (pship is NULL) then text with dummy values is returned.
1423 ****************************************************************************/
1424 const char *get_spaceship_descr(struct player_spaceship *pship)
1426 struct player_spaceship ship;
1427 static struct astring str = ASTRING_INIT;
1429 astr_clear(&str);
1431 if (!pship) {
1432 pship = &ship;
1433 memset(&ship, 0, sizeof(ship));
1436 /* TRANS: spaceship text; should have constant width. */
1437 astr_add_line(&str, _("Population: %5d"), pship->population);
1439 /* TRANS: spaceship text; should have constant width. */
1440 astr_add_line(&str, _("Support: %5d %%"),
1441 (int) (pship->support_rate * 100.0));
1443 /* TRANS: spaceship text; should have constant width. */
1444 astr_add_line(&str, _("Energy: %5d %%"),
1445 (int) (pship->energy_rate * 100.0));
1447 /* TRANS: spaceship text; should have constant width. */
1448 astr_add_line(&str, PL_("Mass: %5d ton",
1449 "Mass: %5d tons",
1450 pship->mass), pship->mass);
1452 if (pship->propulsion > 0) {
1453 /* TRANS: spaceship text; should have constant width. */
1454 astr_add_line(&str, _("Travel time: %5.1f years"),
1455 (float) (0.1 * ((int) (pship->travel_time * 10.0))));
1456 } else {
1457 /* TRANS: spaceship text; should have constant width. */
1458 astr_add_line(&str, "%s", _("Travel time: N/A "));
1461 /* TRANS: spaceship text; should have constant width. */
1462 astr_add_line(&str, _("Success prob.: %5d %%"),
1463 (int) (pship->success_rate * 100.0));
1465 /* TRANS: spaceship text; should have constant width. */
1466 astr_add_line(&str, _("Year of arrival: %8s"),
1467 (pship->state == SSHIP_LAUNCHED)
1468 ? textyear((int) (pship->launch_year +
1469 (int) pship->travel_time))
1470 : "- ");
1472 return astr_str(&str);
1475 /****************************************************************************
1476 Get the text showing the timeout. This is generally disaplyed on the info
1477 panel.
1478 ****************************************************************************/
1479 const char *get_timeout_label_text(void)
1481 static struct astring str = ASTRING_INIT;
1483 astr_clear(&str);
1485 if (is_waiting_turn_change() && game.tinfo.last_turn_change_time >= 1.5) {
1486 double wt = get_seconds_to_new_turn();
1488 if (wt < 0.01) {
1489 astr_add(&str, "%s", Q_("?timeout:wait"));
1490 } else {
1491 astr_add(&str, "%s: %s", Q_("?timeout:eta"), format_duration(wt));
1493 } else {
1494 if (current_turn_timeout() <= 0) {
1495 astr_add(&str, "%s", Q_("?timeout:off"));
1496 } else {
1497 astr_add(&str, "%s", format_duration(get_seconds_to_turndone()));
1501 return astr_str(&str);
1504 /****************************************************************************
1505 Format a duration, in seconds, so it comes up in minutes or hours if
1506 that would be more meaningful.
1508 (7 characters, maximum. Enough for, e.g., "99h 59m".)
1509 ****************************************************************************/
1510 const char *format_duration(int duration)
1512 static struct astring str = ASTRING_INIT;
1514 astr_clear(&str);
1516 if (duration < 0) {
1517 duration = 0;
1519 if (duration < 60) {
1520 astr_add(&str, Q_("?seconds:%02ds"), duration);
1521 } else if (duration < 3600) { /* < 60 minutes */
1522 astr_add(&str, Q_("?mins/secs:%02dm %02ds"), duration / 60, duration % 60);
1523 } else if (duration < 360000) { /* < 100 hours */
1524 astr_add(&str, Q_("?hrs/mns:%02dh %02dm"), duration / 3600, (duration / 60) % 60);
1525 } else if (duration < 8640000) { /* < 100 days */
1526 astr_add(&str, Q_("?dys/hrs:%02dd %02dh"), duration / 86400,
1527 (duration / 3600) % 24);
1528 } else {
1529 astr_add(&str, "%s", Q_("?duration:overflow"));
1532 return astr_str(&str);
1535 /****************************************************************************
1536 Return text giving the ping time for the player. This is generally used
1537 used in the playerdlg. This should only be used in playerdlg_common.c.
1538 ****************************************************************************/
1539 const char *get_ping_time_text(const struct player *pplayer)
1541 static struct astring str = ASTRING_INIT;
1543 astr_clear(&str);
1545 conn_list_iterate(pplayer->connections, pconn) {
1546 if (!pconn->observer
1547 /* Certainly not needed, but safer. */
1548 && 0 == strcmp(pconn->username, pplayer->username)) {
1549 if (pconn->ping_time != -1) {
1550 double ping_time_in_ms = 1000 * pconn->ping_time;
1552 astr_add(&str, _("%6d.%02d ms"), (int) ping_time_in_ms,
1553 ((int) (ping_time_in_ms * 100.0)) % 100);
1555 break;
1557 } conn_list_iterate_end;
1559 return astr_str(&str);
1562 /****************************************************************************
1563 Return text giving the score of the player. This should only be used
1564 in playerdlg_common.c.
1565 ****************************************************************************/
1566 const char *get_score_text(const struct player *pplayer)
1568 static struct astring str = ASTRING_INIT;
1570 astr_clear(&str);
1572 if (pplayer->score.game > 0
1573 || NULL == client.conn.playing
1574 || pplayer == client.conn.playing) {
1575 astr_add(&str, "%d", pplayer->score.game);
1576 } else {
1577 astr_add(&str, "?");
1580 return astr_str(&str);
1583 /****************************************************************************
1584 Get the title for a "report". This may include the city, economy,
1585 military, trade, player, etc., reports. Some clients may generate the
1586 text themselves to get a better GUI layout.
1587 ****************************************************************************/
1588 const char *get_report_title(const char *report_name)
1590 static struct astring str = ASTRING_INIT;
1591 const struct player *pplayer = client_player();
1593 astr_clear(&str);
1595 astr_add_line(&str, "%s", report_name);
1597 if (pplayer != NULL) {
1598 char buf[4 * MAX_LEN_NAME];
1600 /* TRANS: <nation adjective> <government name>.
1601 * E.g. "Polish Republic". */
1602 astr_add_line(&str, Q_("?nationgovernment:%s %s"),
1603 nation_adjective_for_player(pplayer),
1604 government_name_for_player(pplayer));
1606 /* TRANS: Just appending 2 strings, using the correct localized
1607 * syntax. */
1608 astr_add_line(&str, _("%s - %s"),
1609 ruler_title_for_player(pplayer, buf, sizeof(buf)),
1610 calendar_text());
1611 } else {
1612 /* TRANS: "Observer - 1985 AD" */
1613 astr_add_line(&str, _("Observer - %s"),
1614 calendar_text());
1616 return astr_str(&str);
1619 /****************************************************************************
1620 Describing buildings that affect happiness.
1621 ****************************************************************************/
1622 const char *text_happiness_buildings(const struct city *pcity)
1624 struct effect_list *plist = effect_list_new();
1625 static struct astring str = ASTRING_INIT;
1627 get_city_bonus_effects(plist, pcity, NULL, EFT_MAKE_CONTENT);
1628 if (0 < effect_list_size(plist)) {
1629 struct astring effects = ASTRING_INIT;
1631 get_effect_list_req_text(plist, &effects);
1632 astr_set(&str, _("Buildings: %s."), astr_str(&effects));
1633 astr_free(&effects);
1634 } else {
1635 astr_set(&str, _("Buildings: None."));
1637 effect_list_destroy(plist);
1639 /* Add line breaks after 80 characters. */
1640 astr_break_lines(&str, 80);
1642 return astr_str(&str);
1645 /****************************************************************************
1646 Describing nationality effects that affect happiness.
1647 ****************************************************************************/
1648 const char *text_happiness_nationality(const struct city *pcity)
1650 static struct astring str = ASTRING_INIT;
1651 int enemies = 0;
1653 astr_clear(&str);
1655 astr_add_line(&str, _("Nationality: "));
1657 if (game.info.citizen_nationality) {
1658 if (get_city_bonus(pcity, EFT_ENEMY_CITIZEN_UNHAPPY_PCT) > 0) {
1659 struct player *owner = city_owner(pcity);
1661 citizens_foreign_iterate(pcity, pslot, nationality) {
1662 if (pplayers_at_war(owner, player_slot_get_player(pslot))) {
1663 enemies += nationality;
1665 } citizens_foreign_iterate_end;
1667 if (enemies > 0) {
1668 astr_add(&str, PL_("%d enemy nationalist", "%d enemy nationalists", enemies),
1669 enemies);
1673 if (enemies == 0) {
1674 astr_add(&str, _("None."));
1676 } else {
1677 astr_add(&str, _("Disabled."));
1680 return astr_str(&str);
1683 /****************************************************************************
1684 Describing wonders that affect happiness.
1685 ****************************************************************************/
1686 const char *text_happiness_wonders(const struct city *pcity)
1688 struct effect_list *plist = effect_list_new();
1689 static struct astring str = ASTRING_INIT;
1691 get_city_bonus_effects(plist, pcity, NULL, EFT_MAKE_HAPPY);
1692 get_city_bonus_effects(plist, pcity, NULL, EFT_FORCE_CONTENT);
1693 get_city_bonus_effects(plist, pcity, NULL, EFT_NO_UNHAPPY);
1694 if (0 < effect_list_size(plist)) {
1695 struct astring effects = ASTRING_INIT;
1697 get_effect_list_req_text(plist, &effects);
1698 astr_set(&str, _("Wonders: %s."), astr_str(&effects));
1699 astr_free(&effects);
1700 } else {
1701 astr_set(&str, _("Wonders: None."));
1704 /* Add line breaks after 80 characters. */
1705 astr_break_lines(&str, 80);
1707 return astr_str(&str);
1710 /****************************************************************************
1711 Describing city factors that affect happiness.
1712 ****************************************************************************/
1713 const char *text_happiness_cities(const struct city *pcity)
1715 struct player *pplayer = city_owner(pcity);
1716 int cities = city_list_size(pplayer->cities);
1717 int content = get_player_bonus(pplayer, EFT_CITY_UNHAPPY_SIZE);
1718 int basis = get_player_bonus(pplayer, EFT_EMPIRE_SIZE_BASE);
1719 int step = get_player_bonus(pplayer, EFT_EMPIRE_SIZE_STEP);
1720 static struct astring str = ASTRING_INIT;
1722 astr_clear(&str);
1724 if (basis+step <= 0) {
1725 /* Special case where penalty is disabled; see
1726 * player_content_citizens(). */
1727 astr_add_line(&str,
1728 PL_("Cities: %d total, but no penalty for empire size.",
1729 "Cities: %d total, but no penalty for empire size.",
1730 cities),
1731 cities);
1732 astr_add_line(&str,
1733 /* TRANS: %d is number of citizens */
1734 PL_("%d content per city.",
1735 "%d content per city.", content),
1736 content);
1737 } else {
1738 /* Can have up to and including 'basis' cities without penalty */
1739 int excess = MAX(cities - basis, 0);
1740 int penalty;
1741 int unhappy, angry;
1742 int last, next;
1744 if (excess > 0) {
1745 if (step > 0) {
1746 penalty = 1 + (excess - 1) / step;
1747 } else {
1748 penalty = 1;
1750 } else {
1751 penalty = 0;
1754 unhappy = MIN(penalty, content);
1755 angry = game.info.angrycitizen ? MAX(penalty-content, 0) : 0;
1756 if (penalty >= 1) {
1757 /* 'last' is when last actual malcontent appeared, will saturate
1758 * if no angry citizens */
1759 last = basis + (unhappy+angry-1)*step;
1760 if (!game.info.angrycitizen && unhappy == content) {
1761 /* Maxed out unhappy citizens, so no more penalties */
1762 next = 0;
1763 } else {
1764 /* Angry citizens can continue appearing indefinitely */
1765 next = last + step;
1767 } else {
1768 last = 0;
1769 next = basis;
1772 astr_add_line(&str,
1773 /* TRANS: sentence fragment, will have text appended */
1774 PL_("Cities: %d total:",
1775 "Cities: %d total:", cities),
1776 cities);
1777 if (excess > 0) {
1778 astr_add(&str,
1779 /* TRANS: appended to "Cities: %d total:"; preserve leading
1780 * space. Pluralized in "nearest threshold of %d cities". */
1781 PL_(" %d over nearest threshold of %d city.",
1782 " %d over nearest threshold of %d cities.", last),
1783 cities - last, last);
1784 astr_add_line(&str,
1785 /* TRANS: Number of content [citizen(s)] ... */
1786 PL_("%d content before penalty.",
1787 "%d content before penalty.", content),
1788 content);
1789 astr_add_line(&str,
1790 PL_("%d additional unhappy citizen.",
1791 "%d additional unhappy citizens.", unhappy),
1792 unhappy);
1793 if (angry > 0) {
1794 astr_add_line(&str,
1795 PL_("%d angry citizen.",
1796 "%d angry citizens.", angry),
1797 angry);
1799 } else {
1800 astr_add(&str,
1801 /* TRANS: appended to "Cities: %d total:"; preserve leading
1802 * space. */
1803 PL_(" not more than %d, so no empire size penalty.",
1804 " not more than %d, so no empire size penalty.", next),
1805 next);
1806 astr_add_line(&str,
1807 /* TRANS: %d is number of citizens */
1808 PL_("%d content per city.",
1809 "%d content per city.", content),
1810 content);
1812 if (next >= cities && penalty < content) {
1813 astr_add_line(&str,
1814 PL_("With %d more city, another citizen will become "
1815 "unhappy.",
1816 "With %d more cities, another citizen will become "
1817 "unhappy.",
1818 next + 1 - cities),
1819 next + 1 - cities);
1820 } else if (next >= cities) {
1821 /* We maxed out the number of unhappy citizens, but they can get
1822 * angry instead. */
1823 fc_assert(game.info.angrycitizen);
1824 astr_add_line(&str,
1825 PL_("With %d more city, another citizen will become "
1826 "angry.",
1827 "With %d more cities, another citizen will become "
1828 "angry.",
1829 next + 1 - cities),
1830 next + 1 - cities);
1831 } else {
1832 /* Either no Empire_Size_Step, or we maxed out on unhappy citizens
1833 * and ruleset doesn't allow angry ones. */
1834 astr_add_line(&str,
1835 _("More cities will not cause more unhappy citizens."));
1839 return astr_str(&str);
1842 /****************************************************************************
1843 Describing units that affect happiness.
1844 ****************************************************************************/
1845 const char *text_happiness_units(const struct city *pcity)
1847 int mlmax = get_city_bonus(pcity, EFT_MARTIAL_LAW_MAX);
1848 int uhcfac = get_city_bonus(pcity, EFT_UNHAPPY_FACTOR);
1849 static struct astring str = ASTRING_INIT;
1851 astr_clear(&str);
1853 if (mlmax > 0) {
1854 int mleach = get_city_bonus(pcity, EFT_MARTIAL_LAW_EACH);
1855 if (mlmax == 100) {
1856 astr_add_line(&str, "%s", _("Unlimited martial law in effect."));
1857 } else {
1858 astr_add_line(&str, PL_("%d military unit may impose martial law.",
1859 "Up to %d military units may impose martial "
1860 "law.", mlmax), mlmax);
1862 astr_add_line(&str, PL_("Each military unit makes %d "
1863 "unhappy citizen content.",
1864 "Each military unit makes %d "
1865 "unhappy citizens content.",
1866 mleach), mleach);
1867 } else if (uhcfac > 0) {
1868 astr_add_line(&str,
1869 _("Military units in the field may cause unhappiness. "));
1870 } else {
1871 astr_add_line(&str,
1872 _("Military units have no happiness effect. "));
1874 return astr_str(&str);
1877 /****************************************************************************
1878 Describing luxuries that affect happiness.
1879 ****************************************************************************/
1880 const char *text_happiness_luxuries(const struct city *pcity)
1882 static struct astring str = ASTRING_INIT;
1884 astr_clear(&str);
1886 astr_add_line(&str,
1887 _("Luxury: %d total."),
1888 pcity->prod[O_LUXURY]);
1889 return astr_str(&str);