Updated Scottish Gaelic localisation from Michael Bauer
[freeciv.git] / client / text.c
blobc3bb7d9d9001c29aaa5207864bc30b4c074bf352
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 "citizens.h"
31 #include "combat.h"
32 #include "fc_types.h" /* LINE_BREAK */
33 #include "game.h"
34 #include "government.h"
35 #include "map.h"
36 #include "research.h"
37 #include "traderoutes.h"
38 #include "unitlist.h"
40 /* client */
41 #include "client_main.h"
42 #include "climap.h"
43 #include "climisc.h"
44 #include "control.h"
45 #include "goto.h"
47 #include "text.h"
50 static int get_bulbs_per_turn(int *pours, bool *pteam, int *ptheirs);
52 /****************************************************************************
53 Return a (static) string with a tile's food/prod/trade
54 ****************************************************************************/
55 const char *get_tile_output_text(const struct tile *ptile)
57 static struct astring str = ASTRING_INIT;
58 int i;
59 char output_text[O_LAST][16];
61 for (i = 0; i < O_LAST; i++) {
62 int before_penalty = 0;
63 int x = city_tile_output(NULL, ptile, FALSE, i);
65 if (NULL != client.conn.playing) {
66 before_penalty = get_player_output_bonus(client.conn.playing,
67 get_output_type(i),
68 EFT_OUTPUT_PENALTY_TILE);
71 if (before_penalty > 0 && x > before_penalty) {
72 fc_snprintf(output_text[i], sizeof(output_text[i]), "%d(-1)", x);
73 } else {
74 fc_snprintf(output_text[i], sizeof(output_text[i]), "%d", x);
78 astr_set(&str, "%s/%s/%s", output_text[O_FOOD],
79 output_text[O_SHIELD], output_text[O_TRADE]);
81 return astr_str(&str);
84 /****************************************************************************
85 For AIs, fill the buffer with their player name prefixed with "AI". For
86 humans, just fill it with their username.
87 ****************************************************************************/
88 static inline void get_full_username(char *buf, int buflen,
89 const struct player *pplayer)
91 if (!buf || buflen < 1) {
92 return;
95 if (!pplayer) {
96 buf[0] = '\0';
97 return;
100 if (pplayer->ai_controlled) {
101 /* TRANS: "AI <player name>" */
102 fc_snprintf(buf, buflen, _("AI %s"), pplayer->name);
103 } else {
104 fc_strlcpy(buf, pplayer->username, buflen);
108 /****************************************************************************
109 Fill the buffer with the player's nation name (in adjective form) and
110 optionally add the player's team name.
111 ****************************************************************************/
112 static inline void get_full_nation(char *buf, int buflen,
113 const struct player *pplayer)
115 if (!buf || buflen < 1) {
116 return;
119 if (!pplayer) {
120 buf[0] = '\0';
121 return;
124 if (pplayer->team) {
125 /* TRANS: "<nation adjective>, team <team name>" */
126 fc_snprintf(buf, buflen, _("%s, team %s"),
127 nation_adjective_for_player(pplayer),
128 team_name_translation(pplayer->team));
129 } else {
130 fc_strlcpy(buf, nation_adjective_for_player(pplayer), buflen);
134 /****************************************************************************
135 Text to popup on a middle-click in the mapview.
136 ****************************************************************************/
137 const char *popup_info_text(struct tile *ptile)
139 const char *activity_text;
140 struct city *pcity = tile_city(ptile);
141 struct unit *punit = find_visible_unit(ptile);
142 const char *diplo_nation_plural_adjectives[DS_LAST] =
143 {Q_("?nation:Neutral"), Q_("?nation:Hostile"),
144 Q_("?nation:Neutral"),
145 Q_("?nation:Peaceful"), Q_("?nation:Friendly"),
146 Q_("?nation:Mysterious"), Q_("?nation:Friendly(team)")};
147 const char *diplo_city_adjectives[DS_LAST] =
148 {Q_("?city:Neutral"), Q_("?city:Hostile"),
149 Q_("?nation:Neutral"),
150 Q_("?city:Peaceful"), Q_("?city:Friendly"), Q_("?city:Mysterious"),
151 Q_("?city:Friendly(team)")};
152 static struct astring str = ASTRING_INIT;
153 char username[MAX_LEN_NAME + 32];
154 char nation[2 * MAX_LEN_NAME + 32];
155 int tile_x, tile_y, nat_x, nat_y;
157 astr_clear(&str);
158 index_to_map_pos(&tile_x, &tile_y, tile_index(ptile));
159 astr_add_line(&str, _("Location: (%d, %d) [%d]"),
160 tile_x, tile_y, tile_continent(ptile));
161 index_to_native_pos(&nat_x, &nat_y, tile_index(ptile));
162 astr_add_line(&str, _("Native coordinates: (%d, %d)"),
163 nat_x, nat_y);
165 if (client_tile_get_known(ptile) == TILE_UNKNOWN) {
166 astr_add(&str, _("Unknown"));
167 return astr_str(&str);
169 astr_add_line(&str, _("Terrain: %s"), tile_get_info_text(ptile, 0));
170 astr_add_line(&str, _("Food/Prod/Trade: %s"),
171 get_tile_output_text(ptile));
172 if (tile_has_special(ptile, S_HUT)) {
173 astr_add_line(&str, _("Minor Tribe Village"));
175 if (BORDERS_DISABLED != game.info.borders && !pcity) {
176 struct player *owner = tile_owner(ptile);
178 get_full_username(username, sizeof(username), owner);
179 get_full_nation(nation, sizeof(nation), owner);
181 if (NULL != client.conn.playing && owner == client.conn.playing) {
182 astr_add_line(&str, _("Our territory"));
183 } else if (NULL != owner && NULL == client.conn.playing) {
184 /* TRANS: "Territory of <username> (<nation + team>)" */
185 astr_add_line(&str, _("Territory of %s (%s)"), username, nation);
186 } else if (NULL != owner) {
187 struct player_diplstate *ds = player_diplstate_get(client.conn.playing,
188 owner);
190 if (ds->type == DS_CEASEFIRE) {
191 int turns = ds->turns_left;
193 astr_add_line(&str,
194 /* TRANS: "Territory of <username> (<nation + team>)
195 * (<number> turn cease-fire)" */
196 PL_("Territory of %s (%s) (%d turn cease-fire)",
197 "Territory of %s (%s) (%d turn cease-fire)",
198 turns),
199 username, nation, turns);
200 } else {
201 int type = ds->type;
203 /* TRANS: "Territory of <username>
204 * (<nation + team> | <diplomatic state>)" */
205 astr_add_line(&str, _("Territory of %s (%s | %s)"),
206 username, nation,
207 diplo_nation_plural_adjectives[type]);
209 } else {
210 astr_add_line(&str, _("Unclaimed territory"));
213 if (pcity) {
214 /* Look at city owner, not tile owner (the two should be the same, if
215 * borders are in use). */
216 struct player *owner = city_owner(pcity);
217 const char *improvements[improvement_count()];
218 int has_improvements = 0;
220 get_full_username(username, sizeof(username), owner);
221 get_full_nation(nation, sizeof(nation), owner);
223 if (NULL == client.conn.playing || owner == client.conn.playing) {
224 /* TRANS: "City: <city name> | <username> (<nation + team>)" */
225 astr_add_line(&str, _("City: %s | %s (%s)"),
226 city_name(pcity), username, nation);
227 } else {
228 struct player_diplstate *ds
229 = player_diplstate_get(client_player(), owner);
230 if (ds->type == DS_CEASEFIRE) {
231 int turns = ds->turns_left;
233 /* TRANS: "City: <city name> | <username>
234 * (<nation + team>, <number> turn cease-fire)" */
235 astr_add_line(&str, PL_("City: %s | %s (%s, %d turn cease-fire)",
236 "City: %s | %s (%s, %d turn cease-fire)",
237 turns),
238 city_name(pcity), username, nation, turns);
239 } else {
240 /* TRANS: "City: <city name> | <username>
241 * (<nation + team>, <diplomatic state>)" */
242 astr_add_line(&str, _("City: %s | %s (%s, %s)"),
243 city_name(pcity), username, nation,
244 diplo_city_adjectives[ds->type]);
247 if (can_player_see_units_in_city(client_player(), pcity)) {
248 int count = unit_list_size(ptile->units);
250 if (count > 0) {
251 astr_add(&str, PL_(" | Occupied with %d unit.",
252 " | Occupied with %d units.", count), count);
253 } else {
254 astr_add(&str, _(" | Not occupied."));
256 } else {
257 if (pcity->client.occupied) {
258 astr_add(&str, _(" | Occupied."));
259 } else {
260 astr_add(&str, _(" | Not occupied."));
263 improvement_iterate(pimprove) {
264 if (is_improvement_visible(pimprove)
265 && city_has_building(pcity, pimprove)) {
266 improvements[has_improvements++] =
267 improvement_name_translation(pimprove);
269 } improvement_iterate_end;
271 if (0 < has_improvements) {
272 struct astring list = ASTRING_INIT;
274 astr_build_and_list(&list, improvements, has_improvements);
275 /* TRANS: %s is a list of "and"-separated improvements. */
276 astr_add_line(&str, _(" with %s."), astr_str(&list));
277 astr_free(&list);
280 unit_list_iterate(get_units_in_focus(), pfocus_unit) {
281 struct city *hcity = game_city_by_number(pfocus_unit->homecity);
283 if (unit_has_type_flag(pfocus_unit, UTYF_TRADE_ROUTE)
284 && can_cities_trade(hcity, pcity)
285 && can_establish_trade_route(hcity, pcity)) {
286 /* TRANS: "Trade from Warsaw: 5" */
287 astr_add_line(&str, _("Trade from %s: %d"),
288 city_name(hcity),
289 trade_between_cities(hcity, pcity));
291 } unit_list_iterate_end;
294 const char *infratext = get_infrastructure_text(ptile->special,
295 ptile->bases,
296 ptile->roads);
297 if (*infratext != '\0') {
298 astr_add_line(&str, _("Infrastructure: %s"), infratext);
301 activity_text = concat_tile_activity_text(ptile);
302 if (strlen(activity_text) > 0) {
303 astr_add_line(&str, _("Activity: %s"), activity_text);
305 if (punit && !pcity) {
306 struct player *owner = unit_owner(punit);
307 struct unit_type *ptype = unit_type(punit);
309 get_full_username(username, sizeof(username), owner);
310 get_full_nation(nation, sizeof(nation), owner);
312 if (!client_player() || owner == client_player()) {
313 struct city *pcity = player_city_by_number(owner, punit->homecity);
315 if (pcity) {
316 /* TRANS: "Unit: <unit type> | <username>
317 * (<nation + team>, <homecity>)" */
318 astr_add_line(&str, _("Unit: %s | %s (%s, %s)"),
319 utype_name_translation(ptype), username,
320 nation, city_name(pcity));
321 } else {
322 /* TRANS: "Unit: <unit type> | <username> (<nation + team>)" */
323 astr_add_line(&str, _("Unit: %s | %s (%s)"),
324 utype_name_translation(ptype), username, nation);
326 } else if (NULL != owner) {
327 struct player_diplstate *ds = player_diplstate_get(client_player(),
328 owner);
329 if (ds->type == DS_CEASEFIRE) {
330 int turns = ds->turns_left;
332 /* TRANS: "Unit: <unit type> | <username> (<nation + team>,
333 * <number> turn cease-fire)" */
334 astr_add_line(&str, PL_("Unit: %s | %s (%s, %d turn cease-fire)",
335 "Unit: %s | %s (%s, %d turn cease-fire)",
336 turns),
337 utype_name_translation(ptype),
338 username, nation, turns);
339 } else {
340 /* TRANS: "Unit: <unit type> | <username> (<nation + team>,
341 * <diplomatic state>)" */
342 astr_add_line(&str, _("Unit: %s | %s (%s, %s)"),
343 utype_name_translation(ptype), username, nation,
344 diplo_city_adjectives[ds->type]);
348 unit_list_iterate(get_units_in_focus(), pfocus_unit) {
349 int att_chance = FC_INFINITY, def_chance = FC_INFINITY;
350 bool found = FALSE;
352 unit_list_iterate(ptile->units, tile_unit) {
353 if (unit_owner(tile_unit) != unit_owner(pfocus_unit)) {
354 int att = unit_win_chance(pfocus_unit, tile_unit) * 100;
355 int def = (1.0 - unit_win_chance(tile_unit, pfocus_unit)) * 100;
357 found = TRUE;
359 /* Presumably the best attacker and defender will be used. */
360 att_chance = MIN(att, att_chance);
361 def_chance = MIN(def, def_chance);
363 } unit_list_iterate_end;
365 if (found) {
366 /* TRANS: "Chance to win: A:95% D:46%" */
367 astr_add_line(&str, _("Chance to win: A:%d%% D:%d%%"),
368 att_chance, def_chance);
370 } unit_list_iterate_end;
372 /* TRANS: A is attack power, D is defense power, FP is firepower,
373 * HP is hitpoints (current and max). */
374 astr_add_line(&str, _("A:%d D:%d FP:%d HP:%d/%d"),
375 ptype->attack_strength, ptype->defense_strength,
376 ptype->firepower, punit->hp, ptype->hp);
378 const char *veteran_name =
379 utype_veteran_name_translation(ptype, punit->veteran);
380 if (veteran_name) {
381 astr_add(&str, " (%s)", veteran_name);
385 if (unit_owner(punit) == client_player()
386 || client_is_global_observer()) {
387 /* Show bribe cost for own units. */
388 astr_add_line(&str, _("Bribe cost: %d"), unit_bribe_cost(punit));
389 } else {
390 /* We can only give an (lower) boundary for units of other players. */
391 astr_add_line(&str, _("Estimated bribe cost: > %d"),
392 unit_bribe_cost(punit));
395 if ((NULL == client.conn.playing || owner == client.conn.playing)
396 && unit_list_size(ptile->units) >= 2) {
397 /* TRANS: "5 more" units on this tile */
398 astr_add(&str, _(" (%d more)"), unit_list_size(ptile->units) - 1);
402 astr_break_lines(&str, LINE_BREAK);
403 return astr_str(&str);
406 /****************************************************************************
407 Creates the activity progress text for the given tile.
409 This should only be used inside popup_info_text and should eventually be
410 made static.
411 ****************************************************************************/
412 const char *concat_tile_activity_text(struct tile *ptile)
414 int activity_total[ACTIVITY_LAST];
415 int activity_units[ACTIVITY_LAST];
416 int base_total[MAX_BASE_TYPES];
417 int base_units[MAX_BASE_TYPES];
418 int road_total[MAX_ROAD_TYPES];
419 int road_units[MAX_ROAD_TYPES];
420 int num_activities = 0;
421 int pillaging = 0;
422 int remains, turns;
423 static struct astring str = ASTRING_INIT;
425 astr_clear(&str);
427 memset(activity_total, 0, sizeof(activity_total));
428 memset(activity_units, 0, sizeof(activity_units));
429 memset(base_total, 0, sizeof(base_total));
430 memset(base_units, 0, sizeof(base_units));
431 memset(road_total, 0, sizeof(road_total));
432 memset(road_units, 0, sizeof(road_units));
434 unit_list_iterate(ptile->units, punit) {
435 if (punit->activity == ACTIVITY_PILLAGE) {
436 pillaging = 1;
437 } else if (punit->activity == ACTIVITY_BASE) {
438 base_total[punit->activity_target.obj.base] += punit->activity_count;
439 base_total[punit->activity_target.obj.base] += get_activity_rate_this_turn(punit);
440 base_units[punit->activity_target.obj.base] += get_activity_rate(punit);
441 } else if (punit->activity == ACTIVITY_GEN_ROAD) {
442 road_total[punit->activity_target.obj.road] += punit->activity_count;
443 road_total[punit->activity_target.obj.road] += get_activity_rate_this_turn(punit);
444 road_units[punit->activity_target.obj.road] += get_activity_rate(punit);
445 } else {
446 activity_total[punit->activity] += punit->activity_count;
447 activity_total[punit->activity] += get_activity_rate_this_turn(punit);
448 activity_units[punit->activity] += get_activity_rate(punit);
450 } unit_list_iterate_end;
452 if (pillaging) {
453 bv_special pillage_spe = get_unit_tile_pillage_set(ptile);
454 bv_bases pillage_bases = get_unit_tile_pillage_base_set(ptile);
455 bv_roads pillage_roads = get_unit_tile_pillage_road_set(ptile);
456 if (BV_ISSET_ANY(pillage_spe)
457 || BV_ISSET_ANY(pillage_bases)
458 || BV_ISSET_ANY(pillage_roads)) {
459 astr_add(&str, "%s(%s)", _("Pillage"),
460 get_infrastructure_text(pillage_spe, pillage_bases, pillage_roads));
461 } else {
462 /* Untargeted pillaging is happening. */
463 astr_add(&str, "%s", _("Pillage"));
465 num_activities++;
468 activity_type_iterate(i) {
469 if (i == ACTIVITY_BASE) {
470 base_type_iterate(bp) {
471 Base_type_id b = base_index(bp);
472 if (base_units[b] > 0) {
473 remains = tile_activity_base_time(ptile, b) - base_total[b];
474 if (remains > 0) {
475 turns = 1 + (remains + base_units[b] - 1) / base_units[b];
476 } else {
477 /* base will be finished this turn */
478 turns = 1;
480 if (num_activities > 0) {
481 astr_add(&str, "/");
483 astr_add(&str, "%s(%d)", base_name_translation(bp), turns);
484 num_activities++;
486 } base_type_iterate_end;
487 } else if (i == ACTIVITY_GEN_ROAD) {
488 road_type_iterate(rp) {
489 Road_type_id r = road_index(rp);
490 if (road_units[r] > 0) {
491 remains = tile_activity_road_time(ptile, r) - road_total[r];
492 if (remains > 0) {
493 turns = 1 + (remains + road_units[r] - 1) / road_units[r];
494 } else {
495 /* road will be finished this turn */
496 turns = 1;
498 if (num_activities > 0) {
499 astr_add(&str, "/");
501 astr_add(&str, "%s(%d)", road_name_translation(rp), turns);
502 num_activities++;
504 } road_type_iterate_end;
505 } else if (is_build_or_clean_activity(i) && activity_units[i] > 0) {
506 if (num_activities > 0) {
507 astr_add(&str, "/");
509 remains = tile_activity_time(i, ptile) - activity_total[i];
510 if (remains > 0) {
511 turns = 1 + (remains + activity_units[i] - 1) / activity_units[i];
512 } else {
513 /* activity will be finished this turn */
514 turns = 1;
516 astr_add(&str, "%s(%d)", get_activity_text(i), turns);
517 num_activities++;
519 } activity_type_iterate_end;
521 return astr_str(&str);
524 #define FAR_CITY_SQUARE_DIST (2*(6*6))
526 /****************************************************************************
527 Returns the text describing the city and its distance.
528 ****************************************************************************/
529 const char *get_nearest_city_text(struct city *pcity, int sq_dist)
531 static struct astring str = ASTRING_INIT;
533 astr_clear(&str);
535 /* just to be sure */
536 if (!pcity) {
537 sq_dist = -1;
540 astr_add(&str, (sq_dist >= FAR_CITY_SQUARE_DIST)
541 /* TRANS: on own line immediately following \n, ... <city> */
542 ? _("far from %s")
543 : (sq_dist > 0)
544 /* TRANS: on own line immediately following \n, ... <city> */
545 ? _("near %s")
546 : (sq_dist == 0)
547 /* TRANS: on own line immediately following \n, ... <city> */
548 ? _("in %s")
549 : "%s",
550 pcity
551 ? city_name(pcity)
552 : "");
554 return astr_str(&str);
557 /****************************************************************************
558 Returns the unit description.
560 FIXME: This function is not re-entrant because it returns a pointer to
561 static data.
562 ****************************************************************************/
563 const char *unit_description(struct unit *punit)
565 int pcity_near_dist;
566 struct player *owner = unit_owner(punit);
567 struct player *nationality = unit_nationality(punit);
568 struct city *pcity =
569 player_city_by_number(owner, punit->homecity);
570 struct city *pcity_near = get_nearest_city(punit, &pcity_near_dist);
571 struct unit_type *ptype = unit_type(punit);
572 static struct astring str = ASTRING_INIT;
573 const struct player *pplayer = client_player();
575 astr_clear(&str);
577 astr_add(&str, "%s", utype_name_translation(ptype));
580 const char *veteran_name =
581 utype_veteran_name_translation(ptype, punit->veteran);
582 if (veteran_name) {
583 astr_add(&str, " (%s)", veteran_name);
587 if (pplayer == owner) {
588 unit_upkeep_astr(punit, &str);
590 astr_add(&str, "\n");
591 unit_activity_astr(punit, &str);
593 if (pcity) {
594 /* TRANS: on own line immediately following \n, ... <city> */
595 astr_add_line(&str, _("from %s"), city_name(pcity));
596 } else {
597 astr_add(&str, "\n");
599 if (game.info.citizen_nationality) {
600 if (nationality != NULL && owner != nationality) {
601 /* TRANS: Nationality of the soldiers in unit, can be different from owner. */
602 astr_add(&str, _("%s people"), nation_adjective_for_player(nationality));
603 } else {
604 astr_add(&str, "\n");
608 astr_add_line(&str, "%s",
609 get_nearest_city_text(pcity_near, pcity_near_dist));
610 #ifdef DEBUG
611 astr_add_line(&str, "Unit ID: %d", punit->id);
612 #endif
614 return astr_str(&str);
617 /****************************************************************************
618 Describe the airlift capacity of a city for the given units (from their
619 current positions).
620 If pdest is non-NULL, describe its capacity as a destination, otherwise
621 describe the capacity of the city the unit's currently in (if any) as a
622 source. (If the units in the list are in different cities, this will
623 probably not give a useful result in this case.)
624 If not all of the listed units can be airlifted, return the description
625 for those that can.
626 Returns NULL if an airlift is not possible for any of the units.
627 ****************************************************************************/
628 const char *get_airlift_text(const struct unit_list *punits,
629 const struct city *pdest)
631 static struct astring str = ASTRING_INIT;
632 bool src = (pdest == NULL);
633 enum texttype { AL_IMPOSSIBLE, AL_UNKNOWN, AL_FINITE, AL_INFINITE }
634 best = AL_IMPOSSIBLE;
635 int cur = 0, max = 0;
637 unit_list_iterate(punits, punit) {
638 enum texttype this = AL_IMPOSSIBLE;
639 enum unit_airlift_result result;
641 /* NULL will tell us about the capability of airlifting from source */
642 result = test_unit_can_airlift_to(client_player(), punit, pdest);
644 switch(result) {
645 case AR_NO_MOVES:
646 case AR_WRONG_UNITTYPE:
647 case AR_OCCUPIED:
648 case AR_NOT_IN_CITY:
649 case AR_BAD_SRC_CITY:
650 case AR_BAD_DST_CITY:
651 /* No chance of an airlift. */
652 this = AL_IMPOSSIBLE;
653 break;
654 case AR_OK:
655 case AR_OK_SRC_UNKNOWN:
656 case AR_OK_DST_UNKNOWN:
657 case AR_SRC_NO_FLIGHTS:
658 case AR_DST_NO_FLIGHTS:
659 /* May or may not be able to airlift now, but there's a chance we could
660 * later */
662 const struct city *pcity = src ? tile_city(unit_tile(punit)) : pdest;
663 fc_assert_ret_val(pcity != NULL, fc_strdup("-"));
664 if (!src && (game.info.airlifting_style & AIRLIFTING_UNLIMITED_DEST)) {
665 /* No restrictions on destination (and we can infer this even for
666 * other players' cities). */
667 this = AL_INFINITE;
668 } else if (client_player() == city_owner(pcity)) {
669 /* A city we know about. */
670 int this_cur = pcity->airlift, this_max = city_airlift_max(pcity);
671 if (this_max <= 0) {
672 /* City known not to be airlift-capable. */
673 this = AL_IMPOSSIBLE;
674 } else {
675 if (src
676 && (game.info.airlifting_style & AIRLIFTING_UNLIMITED_SRC)) {
677 /* Unlimited capacity. */
678 this = AL_INFINITE;
679 } else {
680 /* Limited capacity (possibly zero right now). */
681 this = AL_FINITE;
682 /* Store the numbers. This whole setup assumes that numeric
683 * capacity isn't unit-dependent. */
684 if (best == AL_FINITE) {
685 fc_assert(cur == this_cur && max == this_max);
687 cur = this_cur;
688 max = this_max;
691 } else {
692 /* Unknown capacity. */
693 this = AL_UNKNOWN;
696 break;
699 /* Now take the most optimistic view. */
700 best = MAX(best, this);
701 } unit_list_iterate_end;
703 switch(best) {
704 case AL_IMPOSSIBLE:
705 return NULL;
706 case AL_UNKNOWN:
707 astr_set(&str, "?");
708 break;
709 case AL_FINITE:
710 astr_set(&str, "%d/%d", cur, max);
711 break;
712 case AL_INFINITE:
713 astr_set(&str, _("Yes"));
714 break;
717 return astr_str(&str);
720 /****************************************************************************
721 Return total expected bulbs.
722 ****************************************************************************/
723 static int get_bulbs_per_turn(int *pours, bool *pteam, int *ptheirs)
725 const struct player_research *presearch;
726 int ours = 0, theirs = 0;
727 bool team = FALSE;
729 if (!client_has_player()) {
730 return 0;
732 presearch = player_research_get(client_player());
734 /* Sum up science */
735 players_iterate(pplayer) {
736 if (pplayer == client_player()) {
737 city_list_iterate(pplayer->cities, pcity) {
738 ours += pcity->prod[O_SCIENCE];
739 } city_list_iterate_end;
741 if (game.info.tech_upkeep_style != TECH_UPKEEP_NONE) {
742 ours -= player_research_get(pplayer)->tech_upkeep;
744 } else if (presearch == player_research_get(pplayer)) {
745 team = TRUE;
746 theirs += pplayer->bulbs_last_turn;
748 if (game.info.tech_upkeep_style != TECH_UPKEEP_NONE) {
749 theirs -= presearch->tech_upkeep;
752 } players_iterate_end;
754 if (pours) {
755 *pours = ours;
757 if (pteam) {
758 *pteam = team;
760 if (ptheirs) {
761 *ptheirs = theirs;
763 return ours + theirs;
766 /****************************************************************************
767 Returns the text to display in the science dialog.
768 ****************************************************************************/
769 const char *science_dialog_text(void)
771 bool team;
772 int ours, theirs, perturn, upkeep;
773 static struct astring str = ASTRING_INIT;
774 struct astring ourbuf = ASTRING_INIT, theirbuf = ASTRING_INIT;
775 struct player_research *research;
777 astr_clear(&str);
779 perturn = get_bulbs_per_turn(&ours, &team, &theirs);
781 research = player_research_get(client_player());
782 upkeep = research->tech_upkeep;
784 if (NULL == client.conn.playing || (ours == 0 && theirs == 0
785 && upkeep == 0)) {
786 return _("Progress: no research");
789 if (A_UNSET == research->researching) {
790 astr_add(&str, _("Progress: no research"));
791 } else {
792 int done = research->bulbs_researched;
793 int total = total_bulbs_required(client_player());
795 if (perturn > 0) {
796 int turns = MAX(1, ceil((double)total) / perturn);
798 astr_add(&str, PL_("Progress: %d turn/advance",
799 "Progress: %d turns/advance",
800 turns), turns);
801 } else if (perturn < 0 ) {
802 /* negative number of bulbs per turn due to tech upkeep */
803 int turns = ceil((double) done / -perturn);
805 astr_add(&str, PL_("Progress: %d turn/advance loss",
806 "Progress: %d turns/advance loss",
807 turns), turns);
808 } else {
809 /* no research */
810 astr_add(&str, _("Progress: none"));
813 astr_set(&ourbuf, PL_("%d bulb/turn", "%d bulbs/turn", ours), ours);
814 if (team) {
815 /* Techpool version */
816 astr_set(&theirbuf,
817 /* TRANS: This is appended to "%d bulb/turn" text */
818 PL_(", %d bulb/turn from team",
819 ", %d bulbs/turn from team", theirs), theirs);
820 } else {
821 astr_clear(&theirbuf);
823 astr_add(&str, " (%s%s)", astr_str(&ourbuf), astr_str(&theirbuf));
824 astr_free(&ourbuf);
825 astr_free(&theirbuf);
827 if (game.info.tech_upkeep_style != TECH_UPKEEP_NONE) {
828 /* perturn is defined as: (bulbs produced) - upkeep */
829 astr_add_line(&str, _("Bulbs produced per turn: %d"), perturn + upkeep);
830 /* TRANS: keep leading space; appended to "Bulbs produced per turn: %d" */
831 astr_add(&str, _(" (needed for technology upkeep: %d)"), upkeep);
834 return astr_str(&str);
837 /****************************************************************************
838 Get the short science-target text. This is usually shown directly in
839 the progress bar.
841 5/28 - 3 turns
843 The "percent" value, if given, will be set to the completion percentage
844 of the research target (actually it's a [0,1] scale not a percent).
845 ****************************************************************************/
846 const char *get_science_target_text(double *percent)
848 struct player_research *research = player_research_get(client_player());
849 static struct astring str = ASTRING_INIT;
851 if (!research) {
852 return "-";
855 astr_clear(&str);
856 if (research->researching == A_UNSET) {
857 astr_add(&str, _("%d/- (never)"), research->bulbs_researched);
858 if (percent) {
859 *percent = 0.0;
861 } else {
862 int total = total_bulbs_required(client.conn.playing);
863 int done = research->bulbs_researched;
864 int perturn = get_bulbs_per_turn(NULL, NULL, NULL);
866 if (perturn > 0) {
867 int turns = ceil( (double)(total - done) / perturn );
869 astr_add(&str, PL_("%d/%d (%d turn)", "%d/%d (%d turns)", turns),
870 done, total, turns);
871 } else if (perturn < 0 ) {
872 /* negative number of bulbs per turn due to tech upkeep */
873 int turns = ceil( (double)done / -perturn );
875 astr_add(&str, PL_("%d/%d (%d turn)", "%d/%d (%d turns)", turns),
876 done, perturn, turns);
877 } else {
878 /* no research */
879 astr_add(&str, _("%d/%d (never)"), done, total);
881 if (percent) {
882 *percent = (double)done / (double)total;
883 *percent = CLIP(0.0, *percent, 1.0);
887 return astr_str(&str);
890 /****************************************************************************
891 Set the science-goal-label text as if we're researching the given goal.
892 ****************************************************************************/
893 const char *get_science_goal_text(Tech_type_id goal)
895 int steps = num_unknown_techs_for_goal(client.conn.playing, goal);
896 int bulbs_needed = total_bulbs_required_for_goal(client.conn.playing, goal);
897 int turns;
898 int perturn = get_bulbs_per_turn(NULL, NULL, NULL);
899 struct player_research* research = player_research_get(client_player());
900 static struct astring str = ASTRING_INIT;
901 struct astring buf1 = ASTRING_INIT,
902 buf2 = ASTRING_INIT,
903 buf3 = ASTRING_INIT;
905 if (!research) {
906 return "-";
909 astr_clear(&str);
911 if (is_tech_a_req_for_goal(client.conn.playing,
912 research->researching, goal)
913 || research->researching == goal) {
914 bulbs_needed -= research->bulbs_researched;
917 astr_set(&buf1,
918 PL_("%d step", "%d steps", steps), steps);
919 astr_set(&buf2,
920 PL_("%d bulb", "%d bulbs", bulbs_needed), bulbs_needed);
921 if (perturn > 0) {
922 turns = (bulbs_needed + perturn - 1) / perturn;
923 astr_set(&buf3,
924 PL_("%d turn", "%d turns", turns), turns);
925 } else {
926 astr_set(&buf3, _("never"));
928 astr_add_line(&str, "(%s - %s - %s)",
929 astr_str(&buf1), astr_str(&buf2), astr_str(&buf3));
930 astr_free(&buf1);
931 astr_free(&buf2);
932 astr_free(&buf3);
934 return astr_str(&str);
937 /****************************************************************************
938 Return the text for the label on the info panel. (This is traditionally
939 shown to the left of the mapview.)
941 Clicking on this text should bring up the get_info_label_text_popup text.
942 ****************************************************************************/
943 const char *get_info_label_text(bool moreinfo)
945 static struct astring str = ASTRING_INIT;
947 astr_clear(&str);
949 if (NULL != client.conn.playing) {
950 astr_add_line(&str, _("Population: %s"),
951 population_to_text(civ_population(client.conn.playing)));
953 astr_add_line(&str, _("Year: %s (T%d)"),
954 textyear(game.info.year), game.info.turn);
956 if (NULL != client.conn.playing) {
957 astr_add_line(&str, _("Gold: %d (%+d)"),
958 client.conn.playing->economic.gold,
959 player_get_expected_income(client.conn.playing));
960 astr_add_line(&str, _("Tax: %d Lux: %d Sci: %d"),
961 client.conn.playing->economic.tax,
962 client.conn.playing->economic.luxury,
963 client.conn.playing->economic.science);
965 if (game.info.phase_mode == PMT_PLAYERS_ALTERNATE) {
966 if (game.info.phase < 0 || game.info.phase >= player_count()) {
967 astr_add_line(&str, _("Moving: Nobody"));
968 } else {
969 astr_add_line(&str, _("Moving: %s"),
970 player_name(player_by_number(game.info.phase)));
972 } else if (game.info.phase_mode == PMT_TEAMS_ALTERNATE) {
973 if (game.info.phase < 0 || game.info.phase >= team_count()) {
974 astr_add_line(&str, _("Moving: Nobody"));
975 } else {
976 astr_add_line(&str, _("Moving: %s"),
977 team_name_translation(team_by_number(game.info.phase)));
981 if (moreinfo) {
982 astr_add_line(&str, _("(Click for more info)"));
985 return astr_str(&str);
988 /****************************************************************************
989 Return the text for the popup label on the info panel. (This is
990 traditionally done as a popup whenever the regular info text is clicked
991 on.)
992 ****************************************************************************/
993 const char *get_info_label_text_popup(void)
995 static struct astring str = ASTRING_INIT;
997 astr_clear(&str);
999 if (NULL != client.conn.playing) {
1000 astr_add_line(&str, _("%s People"),
1001 population_to_text(civ_population(client.conn.playing)));
1003 astr_add_line(&str, _("Year: %s"), textyear(game.info.year));
1004 astr_add_line(&str, _("Turn: %d"), game.info.turn);
1006 if (NULL != client.conn.playing) {
1007 int perturn = get_bulbs_per_turn(NULL, NULL, NULL);
1008 int upkeep = player_research_get(client_player())->tech_upkeep;
1010 astr_add_line(&str, _("Gold: %d"),
1011 client.conn.playing->economic.gold);
1012 astr_add_line(&str, _("Net Income: %d"),
1013 player_get_expected_income(client.conn.playing));
1014 /* TRANS: Gold, luxury, and science rates are in percentage values. */
1015 astr_add_line(&str, _("Tax rates: Gold:%d%% Luxury:%d%% Science:%d%%"),
1016 client.conn.playing->economic.tax,
1017 client.conn.playing->economic.luxury,
1018 client.conn.playing->economic.science);
1019 astr_add_line(&str, _("Researching %s: %s"),
1020 advance_name_researching(client.conn.playing),
1021 get_science_target_text(NULL));
1022 /* perturn is defined as: (bulbs produced) - upkeep */
1023 if (game.info.tech_upkeep_style != TECH_UPKEEP_NONE) {
1024 astr_add_line(&str, _("Bulbs per turn: %d - %d = %d"), perturn + upkeep,
1025 upkeep, perturn);
1026 } else {
1027 fc_assert(upkeep == 0);
1028 astr_add_line(&str, _("Bulbs per turn: %d"), perturn);
1032 /* See also get_global_warming_tooltip and get_nuclear_winter_tooltip. */
1034 if (game.info.global_warming) {
1035 int chance, rate;
1036 global_warming_scaled(&chance, &rate, 100);
1037 astr_add_line(&str, _("Global warming chance: %d%% (%+d%%/turn)"),
1038 chance, rate);
1039 } else {
1040 astr_add_line(&str, _("Global warming deactivated."));
1043 if (game.info.nuclear_winter) {
1044 int chance, rate;
1045 nuclear_winter_scaled(&chance, &rate, 100);
1046 astr_add_line(&str, _("Nuclear winter chance: %d%% (%+d%%/turn)"),
1047 chance, rate);
1048 } else {
1049 astr_add_line(&str, _("Nuclear winter deactivated."));
1052 if (NULL != client.conn.playing) {
1053 astr_add_line(&str, _("Government: %s"),
1054 government_name_for_player(client.conn.playing));
1057 return astr_str(&str);
1060 /****************************************************************************
1061 Return the title text for the unit info shown in the info panel.
1063 FIXME: this should be renamed.
1064 ****************************************************************************/
1065 const char *get_unit_info_label_text1(struct unit_list *punits)
1067 static struct astring str = ASTRING_INIT;
1069 astr_clear(&str);
1071 if (punits) {
1072 int count = unit_list_size(punits);
1074 if (count == 1) {
1075 astr_add(&str, "%s", unit_name_translation(unit_list_get(punits, 0)));
1076 } else {
1077 astr_add(&str, PL_("%d unit", "%d units", count), count);
1080 return astr_str(&str);
1083 /****************************************************************************
1084 Return the text body for the unit info shown in the info panel.
1086 FIXME: this should be renamed.
1087 ****************************************************************************/
1088 const char *get_unit_info_label_text2(struct unit_list *punits, int linebreaks)
1090 static struct astring str = ASTRING_INIT;
1091 int count;
1093 astr_clear(&str);
1095 if (!punits) {
1096 return "";
1099 count = unit_list_size(punits);
1101 /* This text should always have the same number of lines if
1102 * 'linebreaks' has no flags at all. Otherwise the GUI widgets may be
1103 * confused and try to resize themselves. If caller asks for
1104 * conditional 'linebreaks', it should take care of these problems
1105 * itself. */
1107 /* Line 1. Goto or activity text. */
1108 if (count > 0 && hover_state != HOVER_NONE) {
1109 int min, max;
1111 if (!goto_get_turns(&min, &max)) {
1112 /* TRANS: Impossible to reach goto target tile */
1113 astr_add_line(&str, "%s", Q_("?goto:Unreachable"));
1114 } else if (min == max) {
1115 astr_add_line(&str, _("Turns to target: %d"), max);
1116 } else {
1117 astr_add_line(&str, _("Turns to target: %d to %d"), min, max);
1119 } else if (count == 1) {
1120 astr_add_line(&str, "%s",
1121 unit_activity_text(unit_list_get(punits, 0)));
1122 } else if (count > 1) {
1123 astr_add_line(&str, PL_("%d unit selected",
1124 "%d units selected",
1125 count),
1126 count);
1127 } else {
1128 astr_add_line(&str, _("No units selected."));
1131 /* Lines 2, 3, 4, and possible 5 vary. */
1132 if (count == 1) {
1133 struct unit *punit = unit_list_get(punits, 0);
1134 struct player *owner = unit_owner(punit);
1135 struct city *pcity = player_city_by_number(owner,
1136 punit->homecity);
1138 astr_add_line(&str, "%s", tile_get_info_text(unit_tile(punit),
1139 linebreaks));
1141 const char *infratext
1142 = get_infrastructure_text(unit_tile(punit)->special,
1143 unit_tile(punit)->bases,
1144 unit_tile(punit)->roads);
1145 if (*infratext != '\0') {
1146 astr_add_line(&str, "%s", infratext);
1147 } else {
1148 astr_add_line(&str, " ");
1151 if (pcity) {
1152 astr_add_line(&str, "%s", city_name(pcity));
1153 } else {
1154 astr_add_line(&str, " ");
1157 if (game.info.citizen_nationality) {
1158 struct player *nationality = unit_nationality(punit);
1160 /* Line 5, nationality text */
1161 if (nationality != NULL && owner != nationality) {
1162 astr_add(&str, _("%s people"), nation_adjective_for_player(nationality));
1163 } else {
1164 astr_add(&str, " ");
1168 } else if (count > 1) {
1169 int mil = 0, nonmil = 0;
1170 int types_count[U_LAST], i;
1171 struct unit_type *top[3];
1173 memset(types_count, 0, sizeof(types_count));
1174 unit_list_iterate(punits, punit) {
1175 if (unit_has_type_flag(punit, UTYF_CIVILIAN)) {
1176 nonmil++;
1177 } else {
1178 mil++;
1180 types_count[utype_index(unit_type(punit))]++;
1181 } unit_list_iterate_end;
1183 top[0] = top[1] = top[2] = NULL;
1184 unit_type_iterate(utype) {
1185 if (!top[2]
1186 || types_count[utype_index(top[2])] < types_count[utype_index(utype)]) {
1187 top[2] = utype;
1189 if (!top[1]
1190 || types_count[utype_index(top[1])] < types_count[utype_index(top[2])]) {
1191 top[2] = top[1];
1192 top[1] = utype;
1194 if (!top[0]
1195 || types_count[utype_index(top[0])] < types_count[utype_index(utype)]) {
1196 top[1] = top[0];
1197 top[0] = utype;
1201 } unit_type_iterate_end;
1203 for (i = 0; i < 2; i++) {
1204 if (top[i] && types_count[utype_index(top[i])] > 0) {
1205 if (utype_has_flag(top[i], UTYF_CIVILIAN)) {
1206 nonmil -= types_count[utype_index(top[i])];
1207 } else {
1208 mil -= types_count[utype_index(top[i])];
1210 astr_add_line(&str, "%d: %s",
1211 types_count[utype_index(top[i])],
1212 utype_name_translation(top[i]));
1213 } else {
1214 astr_add_line(&str, " ");
1218 if (top[2] && types_count[utype_index(top[2])] > 0
1219 && types_count[utype_index(top[2])] == nonmil + mil) {
1220 astr_add_line(&str, "%d: %s", types_count[utype_index(top[2])],
1221 utype_name_translation(top[2]));
1222 } else if (nonmil > 0 && mil > 0) {
1223 astr_add_line(&str, _("Others: %d civil; %d military"), nonmil, mil);
1224 } else if (nonmil > 0) {
1225 astr_add_line(&str, _("Others: %d civilian"), nonmil);
1226 } else if (mil > 0) {
1227 astr_add_line(&str, _("Others: %d military"), mil);
1228 } else {
1229 astr_add_line(&str, " ");
1232 if (game.info.citizen_nationality) {
1233 astr_add_line(&str, " ");
1235 } else {
1236 astr_add_line(&str, " ");
1237 astr_add_line(&str, " ");
1238 astr_add_line(&str, " ");
1240 if (game.info.citizen_nationality) {
1241 astr_add_line(&str, " ");
1245 /* Line 5/6. Debug text. */
1246 #ifdef DEBUG
1247 if (count == 1) {
1248 astr_add_line(&str, "(Unit ID %d)", unit_list_get(punits, 0)->id);
1249 } else {
1250 astr_add_line(&str, " ");
1252 #endif /* DEBUG */
1254 return astr_str(&str);
1257 /****************************************************************************
1258 Return text about upgrading these unit lists.
1260 Returns TRUE iff any units can be upgraded.
1261 ****************************************************************************/
1262 bool get_units_upgrade_info(char *buf, size_t bufsz,
1263 struct unit_list *punits)
1265 if (unit_list_size(punits) == 0) {
1266 fc_snprintf(buf, bufsz, _("No units to upgrade!"));
1267 return FALSE;
1268 } else if (unit_list_size(punits) == 1) {
1269 return (UU_OK == unit_upgrade_info(unit_list_front(punits), buf, bufsz));
1270 } else {
1271 int upgrade_cost = 0;
1272 int num_upgraded = 0;
1273 int min_upgrade_cost = FC_INFINITY;
1275 unit_list_iterate(punits, punit) {
1276 if (unit_owner(punit) == client_player()
1277 && UU_OK == unit_upgrade_test(punit, FALSE)) {
1278 struct unit_type *from_unittype = unit_type(punit);
1279 struct unit_type *to_unittype = can_upgrade_unittype(client.conn.playing,
1280 unit_type(punit));
1281 int cost = unit_upgrade_price(unit_owner(punit),
1282 from_unittype, to_unittype);
1284 num_upgraded++;
1285 upgrade_cost += cost;
1286 min_upgrade_cost = MIN(min_upgrade_cost, cost);
1288 } unit_list_iterate_end;
1289 if (num_upgraded == 0) {
1290 fc_snprintf(buf, bufsz, _("None of these units may be upgraded."));
1291 return FALSE;
1292 } else {
1293 /* This may trigger sometimes if you don't have enough money for
1294 * a full upgrade. If you have enough to upgrade at least one, it
1295 * will do it. */
1296 /* Construct prompt in several parts to allow separate pluralisation
1297 * by localizations */
1298 char tbuf[MAX_LEN_MSG], ubuf[MAX_LEN_MSG];
1299 fc_snprintf(tbuf, ARRAY_SIZE(tbuf), PL_("Treasury contains %d gold.",
1300 "Treasury contains %d gold.",
1301 client_player()->economic.gold),
1302 client_player()->economic.gold);
1303 /* TRANS: this whole string is a sentence fragment that is only ever
1304 * used by including it in another string (search comments for this
1305 * string to find it) */
1306 fc_snprintf(ubuf, ARRAY_SIZE(ubuf), PL_("Upgrade %d unit",
1307 "Upgrade %d units",
1308 num_upgraded),
1309 num_upgraded);
1310 /* TRANS: This is complicated. The first %s is a pre-pluralised
1311 * sentence fragment "Upgrade %d unit(s)"; the second is pre-pluralised
1312 * "Treasury contains %d gold." So the whole thing reads
1313 * "Upgrade 13 units for 1000 gold?\nTreasury contains 2000 gold." */
1314 fc_snprintf(buf, bufsz, PL_("%s for %d gold?\n%s",
1315 "%s for %d gold?\n%s", upgrade_cost),
1316 ubuf, upgrade_cost, tbuf);
1317 return TRUE;
1322 /****************************************************************************
1323 Return text about disbanding these units.
1325 Returns TRUE iff any units can be disbanded.
1326 ****************************************************************************/
1327 bool get_units_disband_info(char *buf, size_t bufsz,
1328 struct unit_list *punits)
1330 if (unit_list_size(punits) == 0) {
1331 fc_snprintf(buf, bufsz, _("No units to disband!"));
1332 return FALSE;
1333 } else if (unit_list_size(punits) == 1) {
1334 if (unit_has_type_flag(unit_list_front(punits), UTYF_UNDISBANDABLE)) {
1335 fc_snprintf(buf, bufsz, _("%s refuses to disband!"),
1336 unit_name_translation(unit_list_front(punits)));
1337 return FALSE;
1338 } else {
1339 /* TRANS: %s is a unit type */
1340 fc_snprintf(buf, bufsz, _("Disband %s?"),
1341 unit_name_translation(unit_list_front(punits)));
1342 return TRUE;
1344 } else {
1345 int count = 0;
1346 unit_list_iterate(punits, punit) {
1347 if (!unit_has_type_flag(punit, UTYF_UNDISBANDABLE)) {
1348 count++;
1350 } unit_list_iterate_end;
1351 if (count == 0) {
1352 fc_snprintf(buf, bufsz, _("None of these units may be disbanded."));
1353 return FALSE;
1354 } else {
1355 /* TRANS: %d is never 0 or 1 */
1356 fc_snprintf(buf, bufsz, PL_("Disband %d unit?",
1357 "Disband %d units?", count), count);
1358 return TRUE;
1363 /****************************************************************************
1364 Get a tooltip text for the info panel research indicator. See
1365 client_research_sprite().
1366 ****************************************************************************/
1367 const char *get_bulb_tooltip(void)
1369 static struct astring str = ASTRING_INIT;
1371 astr_clear(&str);
1373 astr_add_line(&str, _("Shows your progress in "
1374 "researching the current technology."));
1376 if (NULL != client.conn.playing) {
1377 struct player_research *research = player_research_get(client_player());
1379 if (research->researching == A_UNSET) {
1380 astr_add_line(&str, _("no research target."));
1381 } else {
1382 int turns = 0;
1383 int perturn = get_bulbs_per_turn(NULL, NULL, NULL);
1384 int done = research->bulbs_researched;
1385 int total = total_bulbs_required(client_player());
1386 struct astring buf1 = ASTRING_INIT, buf2 = ASTRING_INIT;
1388 if (perturn > 0) {
1389 turns = MAX(1, ceil((double) (total - done) / perturn));
1390 } else if (perturn < 0 ) {
1391 turns = ceil((double) done / -perturn);
1394 if (turns == 0) {
1395 astr_set(&buf1, _("No progress"));
1396 } else {
1397 astr_set(&buf1, PL_("%d turn", "%d turns", turns), turns);
1400 /* TRANS: <perturn> bulbs/turn */
1401 astr_set(&buf2, PL_("%d bulb/turn", "%d bulbs/turn", perturn), perturn);
1403 /* TRANS: <tech>: <amount>/<total bulbs> */
1404 astr_add_line(&str, _("%s: %d/%d (%s, %s)."),
1405 advance_name_researching(client.conn.playing),
1406 research->bulbs_researched,
1407 total_bulbs_required(client.conn.playing),
1408 astr_str(&buf1), astr_str(&buf2));
1410 astr_free(&buf1);
1411 astr_free(&buf2);
1414 return astr_str(&str);
1417 /****************************************************************************
1418 Get a tooltip text for the info panel global warning indicator. See also
1419 client_warming_sprite().
1420 ****************************************************************************/
1421 const char *get_global_warming_tooltip(void)
1423 static struct astring str = ASTRING_INIT;
1425 astr_clear(&str);
1427 if (!game.info.global_warming) {
1428 astr_add_line(&str, _("Global warming deactivated."));
1429 } else {
1430 int chance, rate;
1431 global_warming_scaled(&chance, &rate, 100);
1432 astr_add_line(&str, _("Shows the progress of global warming:"));
1433 astr_add_line(&str, _("Pollution rate: %d%%"), rate);
1434 astr_add_line(&str, _("Chance of catastrophic warming each turn: %d%%"),
1435 chance);
1438 return astr_str(&str);
1441 /****************************************************************************
1442 Get a tooltip text for the info panel nuclear winter indicator. See also
1443 client_cooling_sprite().
1444 ****************************************************************************/
1445 const char *get_nuclear_winter_tooltip(void)
1447 static struct astring str = ASTRING_INIT;
1449 astr_clear(&str);
1451 if (!game.info.nuclear_winter) {
1452 astr_add_line(&str, _("Nuclear winter deactivated."));
1453 } else {
1454 int chance, rate;
1455 nuclear_winter_scaled(&chance, &rate, 100);
1456 astr_add_line(&str, _("Shows the progress of nuclear winter:"));
1457 astr_add_line(&str, _("Fallout rate: %d%%"), rate);
1458 astr_add_line(&str, _("Chance of catastrophic winter each turn: %d%%"),
1459 chance);
1462 return astr_str(&str);
1465 /****************************************************************************
1466 Get a tooltip text for the info panel government indicator. See also
1467 government_by_number(...)->sprite.
1468 ****************************************************************************/
1469 const char *get_government_tooltip(void)
1471 static struct astring str = ASTRING_INIT;
1473 astr_clear(&str);
1475 astr_add_line(&str, _("Shows your current government:"));
1477 if (NULL != client.conn.playing) {
1478 astr_add_line(&str, "%s",
1479 government_name_for_player(client.conn.playing));
1481 return astr_str(&str);
1484 /****************************************************************************
1485 Returns a description of the given spaceship. If there is no spaceship
1486 (pship is NULL) then text with dummy values is returned.
1487 ****************************************************************************/
1488 const char *get_spaceship_descr(struct player_spaceship *pship)
1490 struct player_spaceship ship;
1491 static struct astring str = ASTRING_INIT;
1493 astr_clear(&str);
1495 if (!pship) {
1496 pship = &ship;
1497 memset(&ship, 0, sizeof(ship));
1500 /* TRANS: spaceship text; should have constant width. */
1501 astr_add_line(&str, _("Population: %5d"), pship->population);
1503 /* TRANS: spaceship text; should have constant width. */
1504 astr_add_line(&str, _("Support: %5d %%"),
1505 (int) (pship->support_rate * 100.0));
1507 /* TRANS: spaceship text; should have constant width. */
1508 astr_add_line(&str, _("Energy: %5d %%"),
1509 (int) (pship->energy_rate * 100.0));
1511 /* TRANS: spaceship text; should have constant width. */
1512 astr_add_line(&str, PL_("Mass: %5d ton",
1513 "Mass: %5d tons",
1514 pship->mass), pship->mass);
1516 if (pship->propulsion > 0) {
1517 /* TRANS: spaceship text; should have constant width. */
1518 astr_add_line(&str, _("Travel time: %5.1f years"),
1519 (float) (0.1 * ((int) (pship->travel_time * 10.0))));
1520 } else {
1521 /* TRANS: spaceship text; should have constant width. */
1522 astr_add_line(&str, "%s", _("Travel time: N/A "));
1525 /* TRANS: spaceship text; should have constant width. */
1526 astr_add_line(&str, _("Success prob.: %5d %%"),
1527 (int) (pship->success_rate * 100.0));
1529 /* TRANS: spaceship text; should have constant width. */
1530 astr_add_line(&str, _("Year of arrival: %8s"),
1531 (pship->state == SSHIP_LAUNCHED)
1532 ? textyear((int) (pship->launch_year +
1533 (int) pship->travel_time))
1534 : "- ");
1536 return astr_str(&str);
1539 /****************************************************************************
1540 Get the text showing the timeout. This is generally disaplyed on the info
1541 panel.
1542 ****************************************************************************/
1543 const char *get_timeout_label_text(void)
1545 static struct astring str = ASTRING_INIT;
1547 astr_clear(&str);
1549 if (game.info.timeout <= 0) {
1550 astr_add(&str, "%s", Q_("?timeout:off"));
1551 } else {
1552 astr_add(&str, "%s", format_duration(get_seconds_to_turndone()));
1555 return astr_str(&str);
1558 /****************************************************************************
1559 Format a duration, in seconds, so it comes up in minutes or hours if
1560 that would be more meaningful.
1562 (7 characters, maximum. Enough for, e.g., "99h 59m".)
1563 ****************************************************************************/
1564 const char *format_duration(int duration)
1566 static struct astring str = ASTRING_INIT;
1568 astr_clear(&str);
1570 if (duration < 0) {
1571 duration = 0;
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"));
1586 return astr_str(&str);
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 ****************************************************************************/
1593 const char *get_ping_time_text(const struct player *pplayer)
1595 static struct astring str = ASTRING_INIT;
1597 astr_clear(&str);
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;
1606 astr_add(&str, _("%6d.%02d ms"), (int) ping_time_in_ms,
1607 ((int) (ping_time_in_ms * 100.0)) % 100);
1609 break;
1611 } conn_list_iterate_end;
1613 return astr_str(&str);
1616 /****************************************************************************
1617 Return text giving the score of the player. This should only be used
1618 in playerdlg_common.c.
1619 ****************************************************************************/
1620 const char *get_score_text(const struct player *pplayer)
1622 static struct astring str = ASTRING_INIT;
1624 astr_clear(&str);
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, "?");
1634 return astr_str(&str);
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 ****************************************************************************/
1642 const char *get_report_title(const char *report_name)
1644 static struct astring str = ASTRING_INIT;
1645 const struct player *pplayer = client_player();
1647 astr_clear(&str);
1649 astr_add_line(&str, "%s", report_name);
1651 if (pplayer != NULL) {
1652 char buf[4 * MAX_LEN_NAME];
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));
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 textyear(game.info.year));
1665 } else {
1666 /* TRANS: "Observer - 1985 AD" */
1667 astr_add_line(&str, _("Observer - %s"),
1668 textyear(game.info.year));
1670 return astr_str(&str);
1673 /****************************************************************************
1674 Describing buildings that affect happiness.
1675 ****************************************************************************/
1676 const char *text_happiness_buildings(const struct city *pcity)
1678 char buf[512];
1679 int faces = 0;
1680 struct effect_list *plist = effect_list_new();
1681 static struct astring str = ASTRING_INIT;
1683 astr_clear(&str);
1685 astr_add_line(&str, _("Buildings: "));
1687 get_city_bonus_effects(plist, pcity, NULL, EFT_MAKE_CONTENT);
1689 effect_list_iterate(plist, peffect) {
1690 get_effect_req_text(peffect, buf, sizeof(buf));
1691 if (faces++ > 0) {
1692 /* only one comment to translators needed. */
1693 astr_add(&str, Q_("?clistmore:, %s"), buf);
1694 } else {
1695 astr_add(&str, "%s", buf);
1697 } effect_list_iterate_end;
1698 effect_list_destroy(plist);
1700 if (faces == 0) {
1701 astr_add(&str, _("None. "));
1702 } else {
1703 astr_add(&str, "%s", Q_("?clistend:."));
1706 /* Add line breaks after 80 characters. */
1707 astr_break_lines(&str, 80);
1709 return astr_str(&str);
1712 /****************************************************************************
1713 Describing nationality effects that affect happiness.
1714 ****************************************************************************/
1715 const char *text_happiness_nationality(const struct city *pcity)
1717 static struct astring str = ASTRING_INIT;
1718 int enemies = 0;
1720 astr_clear(&str);
1722 astr_add_line(&str, _("Nationality: "));
1724 if (game.info.citizen_nationality) {
1725 if (get_city_bonus(pcity, EFT_ENEMY_CITIZEN_UNHAPPY_PCT) > 0) {
1726 struct player *owner = city_owner(pcity);
1728 citizens_foreign_iterate(pcity, pslot, nationality) {
1729 if (pplayers_at_war(owner, player_slot_get_player(pslot))) {
1730 enemies += nationality;
1732 } citizens_foreign_iterate_end;
1734 if (enemies > 0) {
1735 astr_add(&str, PL_("%d enemy nationalist", "%d enemy nationalists", enemies),
1736 enemies);
1740 if (enemies == 0) {
1741 astr_add(&str, _("None."));
1743 } else {
1744 astr_add(&str, _("Disabled."));
1747 return astr_str(&str);
1750 /****************************************************************************
1751 Describing wonders that affect happiness.
1752 ****************************************************************************/
1753 const char *text_happiness_wonders(const struct city *pcity)
1755 char buf[512];
1756 int faces = 0;
1757 struct effect_list *plist = effect_list_new();
1758 static struct astring str = ASTRING_INIT;
1760 astr_clear(&str);
1762 astr_add_line(&str, _("Wonders: "));
1763 get_city_bonus_effects(plist, pcity, NULL, EFT_MAKE_HAPPY);
1764 get_city_bonus_effects(plist, pcity, NULL, EFT_FORCE_CONTENT);
1765 get_city_bonus_effects(plist, pcity, NULL, EFT_NO_UNHAPPY);
1767 effect_list_iterate(plist, peffect) {
1768 get_effect_req_text(peffect, buf, sizeof(buf));
1769 if (faces++ > 0) {
1770 /* only one comment to translators needed. */
1771 astr_add(&str, Q_("?clistmore:, %s"), buf);
1772 } else {
1773 astr_add(&str, "%s", buf);
1775 } effect_list_iterate_end;
1777 effect_list_destroy(plist);
1779 if (faces == 0) {
1780 astr_add(&str, _("None. "));
1781 } else {
1782 astr_add(&str, "%s", Q_("?clistend:."));
1785 /* Add line breaks after 80 characters. */
1786 astr_break_lines(&str, 80);
1788 return astr_str(&str);
1791 /****************************************************************************
1792 Describing city factors that affect happiness.
1793 ****************************************************************************/
1794 const char *text_happiness_cities(const struct city *pcity)
1796 struct player *pplayer = city_owner(pcity);
1797 int cities = city_list_size(pplayer->cities);
1798 int content = get_player_bonus(pplayer, EFT_CITY_UNHAPPY_SIZE);
1799 int basis = get_player_bonus(pplayer, EFT_EMPIRE_SIZE_BASE);
1800 int step = get_player_bonus(pplayer, EFT_EMPIRE_SIZE_STEP);
1801 static struct astring str = ASTRING_INIT;
1803 astr_clear(&str);
1805 if (basis+step <= 0) {
1806 /* Special case where penalty is disabled; see
1807 * player_content_citizens(). */
1808 astr_add_line(&str,
1809 _("Cities: %d total, but no penalty for empire size."),
1810 cities);
1811 } else {
1812 int excess = cities - basis;
1813 int penalty = 0;
1815 if (excess > 0) {
1816 if (step > 0)
1817 penalty = 1 + (excess - 1) / step;
1818 else
1819 penalty = 1;
1820 } else {
1821 excess = 0;
1822 penalty = 0;
1825 astr_add_line(&str,
1826 _("Cities: %d total, %d over threshold of %d cities."),
1827 cities, excess, basis);
1828 astr_add_line(&str,
1829 /* TRANS: 0-21 content [citizen(s)] ... */
1830 PL_("%d content before penalty.",
1831 "%d content before penalty.",
1832 content),
1833 content);
1834 astr_add_line(&str,
1835 /* TRANS: 0-21 unhappy citizen(s). */
1836 PL_("%d additional unhappy citizen.",
1837 "%d additional unhappy citizens.",
1838 penalty),
1839 penalty);
1842 return astr_str(&str);
1845 /****************************************************************************
1846 Describing units that affect happiness.
1847 ****************************************************************************/
1848 const char *text_happiness_units(const struct city *pcity)
1850 int mlmax = get_city_bonus(pcity, EFT_MARTIAL_LAW_MAX);
1851 int uhcfac = get_city_bonus(pcity, EFT_UNHAPPY_FACTOR);
1852 static struct astring str = ASTRING_INIT;
1854 astr_clear(&str);
1856 if (mlmax > 0) {
1857 int mleach = get_city_bonus(pcity, EFT_MARTIAL_LAW_EACH);
1858 if (mlmax == 100) {
1859 astr_add_line(&str, "%s", _("Unlimited martial law in effect."));
1860 } else {
1861 astr_add_line(&str, PL_("%d military unit may impose martial law.",
1862 "Up to %d military units may impose martial "
1863 "law.", mlmax), mlmax);
1865 astr_add_line(&str, PL_("Each military unit makes %d "
1866 "unhappy citizen content.",
1867 "Each military unit makes %d "
1868 "unhappy citizens content.",
1869 mleach), mleach);
1870 } else if (uhcfac > 0) {
1871 astr_add_line(&str,
1872 _("Military units in the field may cause unhappiness. "));
1873 } else {
1874 astr_add_line(&str,
1875 _("Military units have no happiness effect. "));
1877 return astr_str(&str);
1880 /****************************************************************************
1881 Describing luxuries that affect happiness.
1882 ****************************************************************************/
1883 const char *text_happiness_luxuries(const struct city *pcity)
1885 static struct astring str = ASTRING_INIT;
1887 astr_clear(&str);
1889 astr_add_line(&str,
1890 _("Luxury: %d total."),
1891 pcity->prod[O_LUXURY]);
1892 return astr_str(&str);