1 /***********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
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 ***********************************************************************/
15 #include <fc_config.h>
22 #include "bitvector.h"
31 #include "achievements.h"
33 #include "connection.h"
36 #include "government.h"
40 #include "specialist.h"
45 #include "citytools.h"
53 /* data needed for logging civ score */
58 struct logging_civ_score
{
61 struct plrdata_slot
*plrdata
;
64 /* Have to be initialized to value less than -1 so it doesn't seem like report was created at
65 * the end of previous turn in the beginning to turn 0. */
66 struct history_report latest_history_report
= { -2 };
68 static struct logging_civ_score
*score_log
= NULL
;
70 static void plrdata_slot_init(struct plrdata_slot
*plrdata
,
72 static void plrdata_slot_replace(struct plrdata_slot
*plrdata
,
74 static void plrdata_slot_free(struct plrdata_slot
*plrdata
);
76 static void page_conn_etype(struct conn_list
*dest
, const char *caption
,
77 const char *headline
, const char *lines
,
78 enum event_type event
);
86 #define HISTORIAN_FIRST HISTORIAN_RICHEST
87 #define HISTORIAN_LAST HISTORIAN_LARGEST
89 static const char *historian_message
[]={
90 /* TRANS: year <name> reports ... */
91 N_("%s %s reports on the RICHEST Civilizations in the World."),
92 /* TRANS: year <name> reports ... */
93 N_("%s %s reports on the most ADVANCED Civilizations in the World."),
94 /* TRANS: year <name> reports ... */
95 N_("%s %s reports on the most MILITARIZED Civilizations in the World."),
96 /* TRANS: year <name> reports ... */
97 N_("%s %s reports on the HAPPIEST Civilizations in the World."),
98 /* TRANS: year <name> reports ... */
99 N_("%s %s reports on the LARGEST Civilizations in the World.")
102 static const char *historian_name
[]={
103 /* TRANS: [year] <name> [reports ...] */
105 /* TRANS: [year] <name> [reports ...] */
107 /* TRANS: [year] <name> [reports ...] */
108 N_("Pliny the Elder"),
109 /* TRANS: [year] <name> [reports ...] */
111 /* TRANS: [year] <name> [reports ...] */
113 /* TRANS: [year] <name> [reports ...] */
115 /* TRANS: [year] <name> [reports ...] */
117 /* TRANS: [year] <name> [reports ...] */
121 static const char scorelog_magic
[] = "#FREECIV SCORELOG2 ";
123 struct player_score_entry
{
124 const struct player
*player
;
128 struct city_score_entry
{
133 static int get_population(const struct player
*pplayer
);
134 static int get_landarea(const struct player
*pplayer
);
135 static int get_settledarea(const struct player
*pplayer
);
136 static int get_research(const struct player
*pplayer
);
137 static int get_literacy(const struct player
*pplayer
);
138 static int get_production(const struct player
*pplayer
);
139 static int get_economics(const struct player
*pplayer
);
140 static int get_pollution(const struct player
*pplayer
);
141 static int get_mil_service(const struct player
*pplayer
);
142 static int get_culture(const struct player
*pplayer
);
144 static const char *area_to_text(int value
);
145 static const char *percent_to_text(int value
);
146 static const char *production_to_text(int value
);
147 static const char *economics_to_text(int value
);
148 static const char *science_to_text(int value
);
149 static const char *mil_service_to_text(int value
);
150 static const char *pollution_to_text(int value
);
151 static const char *culture_to_text(int value
);
153 #define GOOD_PLAYER(p) ((p)->is_alive && !is_barbarian(p))
158 static struct dem_row
{
161 int (*get_value
) (const struct player
*);
162 const char *(*to_text
) (int);
163 bool greater_values_are_better
;
165 {'N', N_("Population"), get_population
, population_to_text
, TRUE
},
166 {'A', N_("Land Area"), get_landarea
, area_to_text
, TRUE
},
167 {'S', N_("Settled Area"), get_settledarea
, area_to_text
, TRUE
},
168 {'R', N_("Research Speed"), get_research
, science_to_text
, TRUE
},
169 /* TRANS: How literate people are. */
170 {'L', N_("?ability:Literacy"), get_literacy
, percent_to_text
, TRUE
},
171 {'P', N_("Production"), get_production
, production_to_text
, TRUE
},
172 {'E', N_("Economics"), get_economics
, economics_to_text
, TRUE
},
173 {'M', N_("Military Service"), get_mil_service
, mil_service_to_text
, FALSE
},
174 {'O', N_("Pollution"), get_pollution
, pollution_to_text
, FALSE
},
175 {'C', N_("Culture"), get_culture
, culture_to_text
, TRUE
}
178 /* Demographics columns. */
185 BV_DEFINE(bv_cols
, DEM_COL_LAST
);
186 static struct dem_col
{
188 } coltable
[] = {{'q'}, {'r'}, {'b'}}; /* Corresponds to dem_flag enum */
190 /* prime number of entries makes for better scaling */
191 static const char *ranking
[] = {
192 /* TRANS: <#>: The <ranking> Poles */
193 N_("%2d: The Supreme %s"),
194 /* TRANS: <#>: The <ranking> Poles */
195 N_("%2d: The Magnificent %s"),
196 /* TRANS: <#>: The <ranking> Poles */
197 N_("%2d: The Great %s"),
198 /* TRANS: <#>: The <ranking> Poles */
199 N_("%2d: The Glorious %s"),
200 /* TRANS: <#>: The <ranking> Poles */
201 N_("%2d: The Excellent %s"),
202 /* TRANS: <#>: The <ranking> Poles */
203 N_("%2d: The Eminent %s"),
204 /* TRANS: <#>: The <ranking> Poles */
205 N_("%2d: The Distinguished %s"),
206 /* TRANS: <#>: The <ranking> Poles */
207 N_("%2d: The Average %s"),
208 /* TRANS: <#>: The <ranking> Poles */
209 N_("%2d: The Mediocre %s"),
210 /* TRANS: <#>: The <ranking> Poles */
211 N_("%2d: The Ordinary %s"),
212 /* TRANS: <#>: The <ranking> Poles */
213 N_("%2d: The Pathetic %s"),
214 /* TRANS: <#>: The <ranking> Poles */
215 N_("%2d: The Useless %s"),
216 /* TRANS: <#>: The <ranking> Poles */
217 N_("%2d: The Valueless %s"),
218 /* TRANS: <#>: The <ranking> Poles */
219 N_("%2d: The Worthless %s"),
220 /* TRANS: <#>: The <ranking> Poles */
221 N_("%2d: The Wretched %s"),
224 /**************************************************************************
225 Compare two player score entries. Used as callback for qsort.
226 **************************************************************************/
227 static int secompare(const void *a
, const void *b
)
229 return (((const struct player_score_entry
*)b
)->value
-
230 ((const struct player_score_entry
*)a
)->value
);
233 /**************************************************************************
234 Construct Historian Report
235 **************************************************************************/
236 static void historian_generic(struct history_report
*report
,
237 enum historian_type which_news
)
239 int i
, j
= 0, rank
= 0;
240 struct player_score_entry size
[player_count()];
242 report
->turn
= game
.info
.turn
;
243 players_iterate(pplayer
) {
244 if (GOOD_PLAYER(pplayer
)) {
246 case HISTORIAN_RICHEST
:
247 size
[j
].value
= pplayer
->economic
.gold
;
249 case HISTORIAN_ADVANCED
:
251 = pplayer
->score
.techs
+ research_get(pplayer
)->future_tech
;
253 case HISTORIAN_MILITARY
:
254 size
[j
].value
= pplayer
->score
.units
;
256 case HISTORIAN_HAPPIEST
:
258 (((pplayer
->score
.happy
- pplayer
->score
.unhappy
) * 1000) /
259 (1 + total_player_citizens(pplayer
)));
261 case HISTORIAN_LARGEST
:
262 size
[j
].value
= total_player_citizens(pplayer
);
265 size
[j
].player
= pplayer
;
267 } /* else the player is dead or barbarian or observer */
268 } players_iterate_end
;
270 qsort(size
, j
, sizeof(size
[0]), secompare
);
271 report
->body
[0] = '\0';
272 for (i
= 0; i
< j
; i
++) {
273 if (i
> 0 && size
[i
].value
< size
[i
- 1].value
) {
274 /* since i < j, only top entry reigns Supreme */
275 rank
= ((i
* ARRAY_SIZE(ranking
)) / j
) + 1;
277 if (rank
>= ARRAY_SIZE(ranking
)) {
278 /* clamp to final entry */
279 rank
= ARRAY_SIZE(ranking
) - 1;
281 cat_snprintf(report
->body
, REPORT_BODYSIZE
,
284 nation_plural_for_player(size
[i
].player
));
285 fc_strlcat(report
->body
, "\n", REPORT_BODYSIZE
);
287 fc_snprintf(report
->title
, REPORT_TITLESIZE
, _(historian_message
[which_news
]),
289 _(historian_name
[fc_rand(ARRAY_SIZE(historian_name
))]));
292 /**************************************************************************
293 Send history report of this turn.
294 **************************************************************************/
295 void send_current_history_report(struct conn_list
*dest
)
297 /* History report is actually constructed at the end of previous turn. */
298 if (latest_history_report
.turn
>= game
.info
.turn
- 1) {
299 page_conn_etype(dest
, _("Historian Publishes!"),
300 latest_history_report
.title
, latest_history_report
.body
,
305 /**************************************************************************
306 Returns the number of wonders the given city has.
307 **************************************************************************/
308 static int nr_wonders(struct city
*pcity
)
312 city_built_iterate(pcity
, i
) {
313 if (is_great_wonder(i
)) {
316 } city_built_iterate_end
;
321 /**************************************************************************
322 Send report listing the "best" 5 cities in the world.
323 **************************************************************************/
324 void report_top_five_cities(struct conn_list
*dest
)
326 const int NUM_BEST_CITIES
= 5;
327 /* a wonder equals WONDER_FACTOR citizen */
328 const int WONDER_FACTOR
= 5;
329 struct city_score_entry size
[NUM_BEST_CITIES
];
333 for (i
= 0; i
< NUM_BEST_CITIES
; i
++) {
338 shuffled_players_iterate(pplayer
) {
339 city_list_iterate(pplayer
->cities
, pcity
) {
340 int value_of_pcity
= city_size_get(pcity
)
341 + nr_wonders(pcity
) * WONDER_FACTOR
;
343 if (value_of_pcity
> size
[NUM_BEST_CITIES
- 1].value
) {
344 size
[NUM_BEST_CITIES
- 1].value
= value_of_pcity
;
345 size
[NUM_BEST_CITIES
- 1].city
= pcity
;
346 qsort(size
, NUM_BEST_CITIES
, sizeof(size
[0]), secompare
);
348 } city_list_iterate_end
;
349 } shuffled_players_iterate_end
;
352 for (i
= 0; i
< NUM_BEST_CITIES
; i
++) {
357 * pcity may be NULL if there are less then NUM_BEST_CITIES in
363 if (player_count() > team_count()) {
364 /* There exists a team with more than one member. */
365 char team_name
[2 * MAX_LEN_NAME
];
367 team_pretty_name(city_owner(size
[i
].city
)->team
, team_name
,
369 cat_snprintf(buffer
, sizeof(buffer
),
370 /* TRANS:"The French City of Lyon (team 3) of size 18". */
371 _("%2d: The %s City of %s (%s) of size %d, "), i
+ 1,
372 nation_adjective_for_player(city_owner(size
[i
].city
)),
373 city_name_get(size
[i
].city
), team_name
,
374 city_size_get(size
[i
].city
));
376 cat_snprintf(buffer
, sizeof(buffer
),
377 _("%2d: The %s City of %s of size %d, "), i
+ 1,
378 nation_adjective_for_player(city_owner(size
[i
].city
)),
379 city_name_get(size
[i
].city
), city_size_get(size
[i
].city
));
382 wonders
= nr_wonders(size
[i
].city
);
384 cat_snprintf(buffer
, sizeof(buffer
), _("with no wonders\n"));
386 cat_snprintf(buffer
, sizeof(buffer
),
387 PL_("with %d wonder\n", "with %d wonders\n", wonders
),
390 page_conn(dest
, _("Traveler's Report:"),
391 _("The Five Greatest Cities in the World!"), buffer
);
394 /**************************************************************************
395 Send report listing all built and destroyed wonders, and wonders
396 currently being built.
397 **************************************************************************/
398 void report_wonders_of_the_world(struct conn_list
*dest
)
404 improvement_iterate(i
) {
405 if (is_great_wonder(i
)) {
406 struct city
*pcity
= city_from_great_wonder(i
);
409 if (player_count() > team_count()) {
410 /* There exists a team with more than one member. */
411 char team_name
[2 * MAX_LEN_NAME
];
413 team_pretty_name(city_owner(pcity
)->team
, team_name
,
415 cat_snprintf(buffer
, sizeof(buffer
),
416 /* TRANS: "Colossus in Rhodes (Greek, team 2)". */
417 _("%s in %s (%s, %s)\n"),
418 city_improvement_name_translation(pcity
, i
),
419 city_name_get(pcity
),
420 nation_adjective_for_player(city_owner(pcity
)),
423 cat_snprintf(buffer
, sizeof(buffer
), _("%s in %s (%s)\n"),
424 city_improvement_name_translation(pcity
, i
),
425 city_name_get(pcity
),
426 nation_adjective_for_player(city_owner(pcity
)));
428 } else if (great_wonder_is_destroyed(i
)) {
429 cat_snprintf(buffer
, sizeof(buffer
), _("%s has been DESTROYED\n"),
430 improvement_name_translation(i
));
433 } improvement_iterate_end
;
435 improvement_iterate(i
) {
436 if (is_great_wonder(i
)) {
437 players_iterate(pplayer
) {
438 city_list_iterate(pplayer
->cities
, pcity
) {
439 if (VUT_IMPROVEMENT
== pcity
->production
.kind
440 && pcity
->production
.value
.building
== i
) {
441 if (player_count() > team_count()) {
442 /* There exists a team with more than one member. */
443 char team_name
[2 * MAX_LEN_NAME
];
445 team_pretty_name(city_owner(pcity
)->team
, team_name
,
447 cat_snprintf(buffer
, sizeof(buffer
),
448 /* TRANS: "([...] (Roman, team 4))". */
449 _("(building %s in %s (%s, %s))\n"),
450 improvement_name_translation(i
), city_name_get(pcity
),
451 nation_adjective_for_player(pplayer
), team_name
);
453 cat_snprintf(buffer
, sizeof(buffer
),
454 _("(building %s in %s (%s))\n"),
455 improvement_name_translation(i
), city_name_get(pcity
),
456 nation_adjective_for_player(pplayer
));
459 } city_list_iterate_end
;
460 } players_iterate_end
;
462 } improvement_iterate_end
;
464 page_conn(dest
, _("Traveler's Report:"),
465 _("Wonders of the World"), buffer
);
468 /****************************************************************************
469 Helper functions which return the value for the given player.
470 ****************************************************************************/
472 /****************************************************************************
474 ****************************************************************************/
475 static int get_population(const struct player
*pplayer
)
477 return pplayer
->score
.population
;
480 /****************************************************************************
481 Number of citizen units of player
482 ****************************************************************************/
483 static int get_pop(const struct player
*pplayer
)
485 return total_player_citizens(pplayer
);
488 /****************************************************************************
489 Number of citizens of player
490 ****************************************************************************/
491 static int get_real_pop(const struct player
*pplayer
)
493 return 1000 * get_pop(pplayer
);
496 /****************************************************************************
497 Land area controlled by player
498 ****************************************************************************/
499 static int get_landarea(const struct player
*pplayer
)
501 return pplayer
->score
.landarea
;
504 /****************************************************************************
506 ****************************************************************************/
507 static int get_settledarea(const struct player
*pplayer
)
509 return pplayer
->score
.settledarea
;
512 /****************************************************************************
514 ****************************************************************************/
515 static int get_research(const struct player
*pplayer
)
517 return pplayer
->score
.techout
;
520 /****************************************************************************
521 Literacy score calculated one way. See also get_literacy2() for
523 ****************************************************************************/
524 static int get_literacy(const struct player
*pplayer
)
526 int pop
= civ_population(pplayer
);
530 } else if (pop
>= 10000) {
531 return pplayer
->score
.literacy
/ (pop
/ 100);
533 return (pplayer
->score
.literacy
* 100) / pop
;
537 /****************************************************************************
539 ****************************************************************************/
540 static int get_production(const struct player
*pplayer
)
542 return pplayer
->score
.mfg
;
545 /****************************************************************************
547 ****************************************************************************/
548 static int get_economics(const struct player
*pplayer
)
550 return pplayer
->score
.bnp
;
553 /****************************************************************************
555 ****************************************************************************/
556 static int get_pollution(const struct player
*pplayer
)
558 return pplayer
->score
.pollution
;
561 /****************************************************************************
562 Military service length
563 ****************************************************************************/
564 static int get_mil_service(const struct player
*pplayer
)
566 return (pplayer
->score
.units
* 5000) / (10 + civ_population(pplayer
));
569 /****************************************************************************
571 ****************************************************************************/
572 static int get_cities(const struct player
*pplayer
)
574 return pplayer
->score
.cities
;
577 /****************************************************************************
579 ****************************************************************************/
580 static int get_techs(const struct player
*pplayer
)
582 return pplayer
->score
.techs
;
585 /****************************************************************************
586 Number of military units
587 ****************************************************************************/
588 static int get_munits(const struct player
*pplayer
)
592 /* count up military units */
593 unit_list_iterate(pplayer
->units
, punit
) {
594 if (is_military_unit(punit
)) {
597 } unit_list_iterate_end
;
602 /****************************************************************************
603 Number of city building units.
604 ****************************************************************************/
605 static int get_settlers(const struct player
*pplayer
)
609 if (!game
.scenario
.prevent_new_cities
) {
610 /* count up settlers */
611 unit_list_iterate(pplayer
->units
, punit
) {
612 if (unit_can_do_action(punit
, ACTION_FOUND_CITY
)) {
615 } unit_list_iterate_end
;
621 /****************************************************************************
623 ****************************************************************************/
624 static int get_wonders(const struct player
*pplayer
)
626 return pplayer
->score
.wonders
;
629 /****************************************************************************
631 ****************************************************************************/
632 static int get_techout(const struct player
*pplayer
)
634 return pplayer
->score
.techout
;
637 /****************************************************************************
638 Literacy score calculated one way. See also get_literacy() to see
640 ****************************************************************************/
641 static int get_literacy2(const struct player
*pplayer
)
643 return pplayer
->score
.literacy
;
646 /****************************************************************************
648 ****************************************************************************/
649 static int get_spaceship(const struct player
*pplayer
)
651 return pplayer
->score
.spaceship
;
654 /****************************************************************************
655 Number of units built
656 ****************************************************************************/
657 static int get_units_built(const struct player
*pplayer
)
659 return pplayer
->score
.units_built
;
662 /****************************************************************************
663 Number of units killed
664 ****************************************************************************/
665 static int get_units_killed(const struct player
*pplayer
)
667 return pplayer
->score
.units_killed
;
670 /****************************************************************************
672 ****************************************************************************/
673 static int get_units_lost(const struct player
*pplayer
)
675 return pplayer
->score
.units_lost
;
678 /****************************************************************************
680 ****************************************************************************/
681 static int get_gold(const struct player
*pplayer
)
683 return pplayer
->economic
.gold
;
686 /****************************************************************************
688 ****************************************************************************/
689 static int get_taxrate(const struct player
*pplayer
)
691 return pplayer
->economic
.tax
;
694 /****************************************************************************
696 ****************************************************************************/
697 static int get_scirate(const struct player
*pplayer
)
699 return pplayer
->economic
.science
;
702 /****************************************************************************
704 ****************************************************************************/
705 static int get_luxrate(const struct player
*pplayer
)
707 return pplayer
->economic
.luxury
;
710 /****************************************************************************
711 Number of rioting cities
712 ****************************************************************************/
713 static int get_riots(const struct player
*pplayer
)
717 city_list_iterate(pplayer
->cities
, pcity
) {
718 if (pcity
->anarchy
> 0) {
721 } city_list_iterate_end
;
726 /****************************************************************************
727 Number of happy citizens
728 ****************************************************************************/
729 static int get_happypop(const struct player
*pplayer
)
731 return pplayer
->score
.happy
;
734 /****************************************************************************
735 Number of content citizens
736 ****************************************************************************/
737 static int get_contentpop(const struct player
*pplayer
)
739 return pplayer
->score
.content
;
742 /****************************************************************************
743 Number of unhappy citizens
744 ****************************************************************************/
745 static int get_unhappypop(const struct player
*pplayer
)
747 return pplayer
->score
.unhappy
;
750 /****************************************************************************
751 Number of specialists.
752 ****************************************************************************/
753 static int get_specialists(const struct player
*pplayer
)
757 specialist_type_iterate(sp
) {
758 count
+= pplayer
->score
.specialists
[sp
];
759 } specialist_type_iterate_end
;
764 /****************************************************************************
766 ****************************************************************************/
767 static int get_gov(const struct player
*pplayer
)
769 return (int) government_number(government_of_player(pplayer
));
772 /****************************************************************************
774 ****************************************************************************/
775 static int get_corruption(const struct player
*pplayer
)
779 city_list_iterate(pplayer
->cities
, pcity
) {
780 result
+= pcity
->waste
[O_TRADE
];
781 } city_list_iterate_end
;
786 /****************************************************************************
788 ****************************************************************************/
789 static int get_total_score(const struct player
*pplayer
)
791 return pplayer
->score
.game
;
794 /****************************************************************************
796 ****************************************************************************/
797 static int get_culture(const struct player
*pplayer
)
799 return pplayer
->score
.culture
;
802 /**************************************************************************
803 Construct string containing value and its unit.
804 **************************************************************************/
805 static const char *value_units(int val
, const char *uni
)
809 if (fc_snprintf(buf
, sizeof(buf
), "%s%s", int_to_text(val
), uni
) == -1) {
810 log_error("String truncated in value_units()!");
816 /**************************************************************************
817 Helper functions which transform the given value to a string
818 depending on the unit.
819 **************************************************************************/
820 static const char *area_to_text(int value
)
822 /* TRANS: abbreviation of "square miles" */
823 return value_units(value
, PL_(" sq. mi.", " sq. mi.", value
));
826 /**************************************************************************
827 Construct string containing value followed by '%'. So value is already
828 considered to be in units of 1/100.
829 **************************************************************************/
830 static const char *percent_to_text(int value
)
832 return value_units(value
, "%");
835 /**************************************************************************
836 Construct string containing value followed by unit suitable for
838 **************************************************************************/
839 static const char *production_to_text(int value
)
841 int clip
= MAX(0, value
);
842 /* TRANS: "M tons" = million tons, so always plural */
843 return value_units(clip
, PL_(" M tons", " M tons", clip
));
846 /**************************************************************************
847 Construct string containing value followed by unit suitable for
849 **************************************************************************/
850 static const char *economics_to_text(int value
)
852 /* TRANS: "M goods" = million goods, so always plural */
853 return value_units(value
, PL_(" M goods", " M goods", value
));
856 /**************************************************************************
857 Construct string containing value followed by unit suitable for
859 **************************************************************************/
860 static const char *science_to_text(int value
)
862 return value_units(value
, PL_(" bulb", " bulbs", value
));
865 /**************************************************************************
866 Construct string containing value followed by unit suitable for
867 military service stats.
868 **************************************************************************/
869 static const char *mil_service_to_text(int value
)
871 return value_units(value
, PL_(" month", " months", value
));
874 /**************************************************************************
875 Construct string containing value followed by unit suitable for
877 **************************************************************************/
878 static const char *pollution_to_text(int value
)
880 return value_units(value
, PL_(" ton", " tons", value
));
883 /**************************************************************************
884 Construct string containing value followed by unit suitable for
886 **************************************************************************/
887 static const char *culture_to_text(int value
)
889 /* TRANS: Unit(s) of culture */
890 return value_units(value
, PL_(" act", " acts", value
));
893 /**************************************************************************
894 Construct one demographics line.
895 **************************************************************************/
896 static void dem_line_item(char *outptr
, size_t out_size
,
897 struct player
*pplayer
, struct dem_row
*prow
,
900 if (NULL
!= pplayer
&& BV_ISSET(selcols
, DEM_COL_QUANTITY
)) {
901 const char *text
= prow
->to_text(prow
->get_value(pplayer
));
903 cat_snprintf(outptr
, out_size
, " %s", text
);
904 cat_snprintf(outptr
, out_size
, "%*s",
905 18 - (int) get_internal_string_length(text
), "");
908 if (NULL
!= pplayer
&& BV_ISSET(selcols
, DEM_COL_RANK
)) {
909 int basis
= prow
->get_value(pplayer
);
912 players_iterate(other
) {
913 if (GOOD_PLAYER(other
)
914 && ((prow
->greater_values_are_better
915 && prow
->get_value(other
) > basis
)
916 || (!prow
->greater_values_are_better
917 && prow
->get_value(other
) < basis
))) {
920 } players_iterate_end
;
922 cat_snprintf(outptr
, out_size
, _("(ranked %d)"), place
);
925 if (NULL
== pplayer
|| BV_ISSET(selcols
, DEM_COL_BEST
)) {
926 struct player
*best_player
= pplayer
;
927 int best_value
= NULL
!= pplayer
? prow
->get_value(pplayer
) : 0;
929 players_iterate(other
) {
930 if (GOOD_PLAYER(other
)) {
931 int value
= prow
->get_value(other
);
934 || (prow
->greater_values_are_better
&& value
> best_value
)
935 || (!prow
->greater_values_are_better
&& value
< best_value
)) {
940 } players_iterate_end
;
943 || (player_has_embassy(pplayer
, best_player
)
944 && (pplayer
!= best_player
))) {
945 cat_snprintf(outptr
, out_size
, " %s: %s",
946 nation_plural_for_player(best_player
),
947 prow
->to_text(prow
->get_value(best_player
)));
952 /*************************************************************************
953 Verify that a given demography string is valid. See
954 game.demography. If the string is not valid the index of the _first_
955 invalid character is return as 'error'.
957 Other settings callback functions are in settings.c, but this one uses
958 static values from this file so it's done separately.
959 *************************************************************************/
960 bool is_valid_demography(const char *demography
, int *error
)
962 int len
= strlen(demography
), i
;
964 /* We check each character individually to see if it's valid. This
965 * does not check for duplicate entries. */
966 for (i
= 0; i
< len
; i
++) {
970 /* See if the character is a valid column label. */
971 for (j
= 0; j
< DEM_COL_LAST
; j
++) {
972 if (demography
[i
] == coltable
[j
].key
) {
982 /* See if the character is a valid row label. */
983 for (j
= 0; j
< ARRAY_SIZE(rowtable
); j
++) {
984 if (demography
[i
] == rowtable
[j
].key
) {
994 /* The character is invalid. */
999 /* Looks like all characters were valid. */
1003 /*************************************************************************
1004 Send demographics report; what gets reported depends on value of
1005 demographics server option.
1006 *************************************************************************/
1007 void report_demographics(struct connection
*pconn
)
1015 struct player
*pplayer
= pconn
->playing
;
1017 BV_CLR_ALL(selcols
);
1018 fc_assert_ret(ARRAY_SIZE(coltable
) == DEM_COL_LAST
);
1019 for (i
= 0; i
< DEM_COL_LAST
; i
++) {
1020 if (strchr(game
.server
.demography
, coltable
[i
].key
)) {
1027 for (i
= 0; i
< ARRAY_SIZE(rowtable
); i
++) {
1028 if (strchr(game
.server
.demography
, rowtable
[i
].key
)) {
1034 if ((!pconn
->observer
&& !pplayer
)
1035 || (pplayer
&& !pplayer
->is_alive
)
1038 page_conn(pconn
->self
, _("Demographics Report:"),
1039 _("Sorry, the Demographics report is unavailable."), "");
1044 fc_snprintf(civbuf
, sizeof(civbuf
), _("%s %s (%s)"),
1045 nation_adjective_for_player(pplayer
),
1046 government_name_for_player(pplayer
),
1053 for (i
= 0; i
< ARRAY_SIZE(rowtable
); i
++) {
1054 if (strchr(game
.server
.demography
, rowtable
[i
].key
)) {
1055 const char *name
= Q_(rowtable
[i
].name
);
1057 cat_snprintf(buffer
, sizeof(buffer
), "%s", name
);
1058 cat_snprintf(buffer
, sizeof(buffer
), "%*s",
1059 18 - (int) get_internal_string_length(name
), "");
1060 dem_line_item(buffer
, sizeof(buffer
), pplayer
, &rowtable
[i
], selcols
);
1061 sz_strlcat(buffer
, "\n");
1065 page_conn(pconn
->self
, _("Demographics Report:"), civbuf
, buffer
);
1068 /*************************************************************************
1069 Send achievements list
1070 *************************************************************************/
1071 void report_achievements(struct connection
*pconn
)
1075 struct player
*pplayer
= pconn
->playing
;
1077 if (pplayer
== NULL
) {
1081 fc_snprintf(civbuf
, sizeof(civbuf
), _("%s %s (%s)"),
1082 nation_adjective_for_player(pplayer
),
1083 government_name_for_player(pplayer
),
1088 achievements_iterate(pach
) {
1089 if (achievement_player_has(pach
, pplayer
)) {
1090 cat_snprintf(buffer
, sizeof(buffer
), "%s\n",
1091 achievement_name_translation(pach
));
1093 } achievements_iterate_end
;
1095 page_conn(pconn
->self
, _("Achievements List:"), civbuf
, buffer
);
1098 /**************************************************************************
1099 Allocate and initialize plrdata slot.
1100 **************************************************************************/
1101 static void plrdata_slot_init(struct plrdata_slot
*plrdata
,
1104 fc_assert_ret(plrdata
->name
== NULL
);
1106 plrdata
->name
= fc_calloc(MAX_LEN_NAME
, sizeof(plrdata
->name
));
1107 plrdata_slot_replace(plrdata
, name
);
1110 /**************************************************************************
1111 Replace plrdata slot with new one named according to input parameter.
1112 **************************************************************************/
1113 static void plrdata_slot_replace(struct plrdata_slot
*plrdata
,
1116 fc_assert_ret(plrdata
->name
!= NULL
);
1118 fc_strlcpy(plrdata
->name
, name
, MAX_LEN_NAME
);
1121 /**************************************************************************
1122 Free resources allocated for plrdata slot.
1123 **************************************************************************/
1124 static void plrdata_slot_free(struct plrdata_slot
*plrdata
)
1126 if (plrdata
->name
!= NULL
) {
1127 free(plrdata
->name
);
1128 plrdata
->name
= NULL
;
1132 /**************************************************************************
1133 Reads the whole file denoted by fp. Sets last_turn and id to the
1134 values contained in the file. Returns the player_names indexed by
1135 player_no at the end of the log file.
1137 Returns TRUE iff the file had read successfully.
1138 **************************************************************************/
1139 static bool scan_score_log(char *id
)
1141 int line_nr
, turn
, plr_no
, spaces
;
1142 struct plrdata_slot
*plrdata
;
1143 char plr_name
[MAX_LEN_NAME
], line
[80], *ptr
;
1145 fc_assert_ret_val(score_log
!= NULL
, FALSE
);
1146 fc_assert_ret_val(score_log
->fp
!= NULL
, FALSE
);
1148 score_log
->last_turn
= -1;
1151 for (line_nr
= 1;; line_nr
++) {
1152 if (!fgets(line
, sizeof(line
), score_log
->fp
)) {
1153 if (feof(score_log
->fp
) != 0) {
1156 log_error("[%s:-] Can't read scorelog file header!",
1157 game
.server
.scorefile
);
1161 ptr
= strchr(line
, '\n');
1163 log_error("[%s:%d] Line too long!", game
.server
.scorefile
, line_nr
);
1169 if (strncmp(line
, scorelog_magic
, strlen(scorelog_magic
)) != 0) {
1170 log_error("[%s:%d] Bad file magic!", game
.server
.scorefile
, line_nr
);
1175 if (strncmp(line
, "id ", strlen("id ")) == 0) {
1176 if (strlen(id
) > 0) {
1177 log_error("[%s:%d] Multiple ID entries!", game
.server
.scorefile
,
1181 fc_strlcpy(id
, line
+ strlen("id "), MAX_LEN_GAME_IDENTIFIER
);
1182 if (strcmp(id
, server
.game_identifier
) != 0) {
1183 log_error("[%s:%d] IDs don't match! game='%s' scorelog='%s'",
1184 game
.server
.scorefile
, line_nr
, server
.game_identifier
,
1190 if (strncmp(line
, "turn ", strlen("turn ")) == 0) {
1191 if (sscanf(line
+ strlen("turn "), "%d", &turn
) != 1) {
1192 log_error("[%s:%d] Bad line (turn)!", game
.server
.scorefile
,
1197 fc_assert_ret_val(turn
> score_log
->last_turn
, FALSE
);
1198 score_log
->last_turn
= turn
;
1201 if (strncmp(line
, "addplayer ", strlen("addplayer ")) == 0) {
1202 if (3 != sscanf(line
+ strlen("addplayer "), "%d %d %s",
1203 &turn
, &plr_no
, plr_name
)) {
1204 log_error("[%s:%d] Bad line (addplayer)!",
1205 game
.server
.scorefile
, line_nr
);
1209 /* Now get the complete player name if there are several parts. */
1210 ptr
= line
+ strlen("addplayer ");
1212 while (*ptr
!= '\0' && spaces
< 2) {
1218 fc_snprintf(plr_name
, sizeof(plr_name
), "%s", ptr
);
1219 log_debug("add player '%s' (from line %d: '%s')", plr_name
, line_nr
,
1222 if (0 > plr_no
|| plr_no
>= player_slot_count()) {
1223 log_error("[%s:%d] Invalid player number: %d!",
1224 game
.server
.scorefile
, line_nr
, plr_no
);
1228 plrdata
= score_log
->plrdata
+ plr_no
;
1229 if (plrdata
->name
!= NULL
) {
1230 log_error("[%s:%d] Two names for one player (id %d)!",
1231 game
.server
.scorefile
, line_nr
, plr_no
);
1235 plrdata_slot_init(plrdata
, plr_name
);
1238 if (strncmp(line
, "delplayer ", strlen("delplayer ")) == 0) {
1239 if (2 != sscanf(line
+ strlen("delplayer "), "%d %d",
1241 log_error("[%s:%d] Bad line (delplayer)!",
1242 game
.server
.scorefile
, line_nr
);
1246 if (!(plr_no
>= 0 && plr_no
< player_slot_count())) {
1247 log_error("[%s:%d] Invalid player number: %d!",
1248 game
.server
.scorefile
, line_nr
, plr_no
);
1252 plrdata
= score_log
->plrdata
+ plr_no
;
1253 if (plrdata
->name
== NULL
) {
1254 log_error("[%s:%d] Trying to remove undefined player (id %d)!",
1255 game
.server
.scorefile
, line_nr
, plr_no
);
1259 plrdata_slot_free(plrdata
);
1263 if (score_log
->last_turn
== -1) {
1264 log_error("[%s:-] Scorelog contains no turn!", game
.server
.scorefile
);
1268 if (strlen(id
) == 0) {
1269 log_error("[%s:-] Scorelog contains no ID!", game
.server
.scorefile
);
1273 if (score_log
->last_turn
+ 1 != game
.info
.turn
) {
1274 log_error("[%s:-] Scorelog doesn't match savegame!",
1275 game
.server
.scorefile
);
1282 /**************************************************************************
1283 Initialize score logging system
1284 **************************************************************************/
1285 void log_civ_score_init(void)
1287 if (score_log
!= NULL
) {
1291 score_log
= fc_calloc(1, sizeof(*score_log
));
1292 score_log
->fp
= NULL
;
1293 score_log
->last_turn
= -1;
1294 score_log
->plrdata
= fc_calloc(player_slot_count(),
1295 sizeof(*score_log
->plrdata
));
1296 player_slots_iterate(pslot
) {
1297 struct plrdata_slot
*plrdata
= score_log
->plrdata
1298 + player_slot_index(pslot
);
1299 plrdata
->name
= NULL
;
1300 } player_slots_iterate_end
;
1302 latest_history_report
.turn
= -2;
1305 /**************************************************************************
1306 Free resources allocated for score logging system
1307 **************************************************************************/
1308 void log_civ_score_free(void)
1315 if (score_log
->fp
) {
1316 fclose(score_log
->fp
);
1317 score_log
->fp
= NULL
;
1320 if (score_log
->plrdata
) {
1321 player_slots_iterate(pslot
) {
1322 struct plrdata_slot
*plrdata
= score_log
->plrdata
1323 + player_slot_index(pslot
);
1324 if (plrdata
->name
!= NULL
) {
1325 free(plrdata
->name
);
1327 } player_slots_iterate_end
;
1328 free(score_log
->plrdata
);
1335 /**************************************************************************
1336 Create a log file of the civilizations so you can see what was happening.
1337 **************************************************************************/
1338 void log_civ_score_now(void)
1340 enum { SL_CREATE
, SL_APPEND
, SL_UNSPEC
} oper
= SL_UNSPEC
;
1341 char id
[MAX_LEN_GAME_IDENTIFIER
];
1344 /* Add new tags only at end of this list. Maintaining the order of
1345 * old tags is critical. */
1346 static const struct {
1348 int (*get_value
) (const struct player
*);
1351 {"bnp", get_economics
},
1352 {"mfg", get_production
},
1353 {"cities", get_cities
},
1354 {"techs", get_techs
},
1355 {"munits", get_munits
},
1356 {"settlers", get_settlers
}, /* "original" tags end here */
1358 {"wonders", get_wonders
},
1359 {"techout", get_techout
},
1360 {"landarea", get_landarea
},
1361 {"settledarea", get_settledarea
},
1362 {"pollution", get_pollution
},
1363 {"literacy", get_literacy2
},
1364 {"spaceship", get_spaceship
}, /* new 1.8.2 tags end here */
1367 {"taxrate", get_taxrate
},
1368 {"scirate", get_scirate
},
1369 {"luxrate", get_luxrate
},
1370 {"riots", get_riots
},
1371 {"happypop", get_happypop
},
1372 {"contentpop", get_contentpop
},
1373 {"unhappypop", get_unhappypop
},
1374 {"specialists", get_specialists
},
1376 {"corruption", get_corruption
}, /* new 1.11.5 tags end here */
1378 {"score", get_total_score
}, /* New 2.1.10 tag end here. */
1380 {"unitsbuilt", get_units_built
}, /* New tags since 2.3.0. */
1381 {"unitskilled", get_units_killed
},
1382 {"unitslost", get_units_lost
},
1384 {"culture", get_culture
} /* New tag in 2.6.0. */
1387 if (!game
.server
.scorelog
) {
1395 if (!score_log
->fp
) {
1396 if (game
.info
.year
== GAME_START_YEAR
) {
1399 score_log
->fp
= fc_fopen(game
.server
.scorefile
, "r");
1400 if (!score_log
->fp
) {
1403 if (!scan_score_log(id
)) {
1404 goto log_civ_score_disable
;
1408 fclose(score_log
->fp
);
1409 score_log
->fp
= NULL
;
1415 score_log
->fp
= fc_fopen(game
.server
.scorefile
, "w");
1416 if (!score_log
->fp
) {
1417 log_error("Can't open scorelog file '%s' for creation!",
1418 game
.server
.scorefile
);
1419 goto log_civ_score_disable
;
1421 fprintf(score_log
->fp
, "%s%s\n", scorelog_magic
, VERSION_STRING
);
1422 fprintf(score_log
->fp
,
1424 "# For a specification of the format of this see doc/README.scorelog or \n"
1425 "# <http://svn.gna.org/viewcvs/freeciv/trunk/doc/README.scorelog?view=auto>.\n"
1428 fprintf(score_log
->fp
, "id %s\n", server
.game_identifier
);
1429 for (i
= 0; i
< ARRAY_SIZE(score_tags
); i
++) {
1430 fprintf(score_log
->fp
, "tag %d %s\n", i
, score_tags
[i
].name
);
1434 score_log
->fp
= fc_fopen(game
.server
.scorefile
, "a");
1435 if (!score_log
->fp
) {
1436 log_error("Can't open scorelog file '%s' for appending!",
1437 game
.server
.scorefile
);
1438 goto log_civ_score_disable
;
1442 log_error("[%s] bad operation %d", __FUNCTION__
, (int) oper
);
1443 goto log_civ_score_disable
;
1447 if (game
.info
.turn
> score_log
->last_turn
) {
1448 fprintf(score_log
->fp
, "turn %d %d %s\n", game
.info
.turn
, game
.info
.year
,
1450 score_log
->last_turn
= game
.info
.turn
;
1453 player_slots_iterate(pslot
) {
1454 struct plrdata_slot
*plrdata
= score_log
->plrdata
1455 + player_slot_index(pslot
);
1456 if (plrdata
->name
!= NULL
1457 && player_slot_is_used(pslot
)) {
1458 struct player
*pplayer
= player_slot_get_player(pslot
);
1460 if (!GOOD_PLAYER(pplayer
)) {
1461 fprintf(score_log
->fp
, "delplayer %d %d\n", game
.info
.turn
- 1,
1462 player_number(pplayer
));
1463 plrdata_slot_free(plrdata
);
1466 } player_slots_iterate_end
;
1468 players_iterate(pplayer
) {
1469 struct plrdata_slot
*plrdata
= score_log
->plrdata
+ player_index(pplayer
);
1470 if (plrdata
->name
== NULL
&& GOOD_PLAYER(pplayer
)) {
1471 switch (game
.server
.scoreloglevel
) {
1473 if (is_ai(pplayer
)) {
1477 fprintf(score_log
->fp
, "addplayer %d %d %s\n", game
.info
.turn
,
1478 player_number(pplayer
), player_name(pplayer
));
1479 plrdata_slot_init(plrdata
, player_name(pplayer
));
1482 } players_iterate_end
;
1484 players_iterate(pplayer
) {
1485 struct plrdata_slot
*plrdata
= score_log
->plrdata
+ player_index(pplayer
);
1487 if (GOOD_PLAYER(pplayer
)) {
1488 switch (game
.server
.scoreloglevel
) {
1490 if (is_ai(pplayer
) && plrdata
->name
== NULL
) {
1491 /* If a human player toggled into AI mode, don't break. */
1495 if (strcmp(plrdata
->name
, player_name(pplayer
)) != 0) {
1496 log_debug("player names does not match '%s' != '%s'", plrdata
->name
,
1497 player_name(pplayer
));
1498 fprintf(score_log
->fp
, "delplayer %d %d\n", game
.info
.turn
- 1,
1499 player_number(pplayer
));
1500 fprintf(score_log
->fp
, "addplayer %d %d %s\n", game
.info
.turn
,
1501 player_number(pplayer
), player_name(pplayer
));
1502 plrdata_slot_replace(plrdata
, player_name(pplayer
));
1506 } players_iterate_end
;
1508 for (i
= 0; i
< ARRAY_SIZE(score_tags
); i
++) {
1509 players_iterate(pplayer
) {
1510 if (!GOOD_PLAYER(pplayer
)
1511 || (game
.server
.scoreloglevel
== SL_HUMANS
&& is_ai(pplayer
))) {
1515 fprintf(score_log
->fp
, "data %d %d %d %d\n", game
.info
.turn
, i
,
1516 player_number(pplayer
), score_tags
[i
].get_value(pplayer
));
1517 } players_iterate_end
;
1520 fflush(score_log
->fp
);
1524 log_civ_score_disable
:
1526 log_civ_score_free();
1529 /**************************************************************************
1530 Produce random history report if it's time for one.
1531 **************************************************************************/
1532 void make_history_report(void)
1534 if (player_count() == 1) {
1538 if (game
.server
.scoreturn
> game
.info
.turn
) {
1542 game
.server
.scoreturn
= (game
.info
.turn
+ GAME_DEFAULT_SCORETURN
1543 + fc_rand(GAME_DEFAULT_SCORETURN
));
1545 historian_generic(&latest_history_report
, game
.server
.scoreturn
% HISTORIAN_LAST
);
1546 send_current_history_report(game
.est_connections
);
1549 /**************************************************************************
1550 Inform clients about player scores and statistics when the game ends.
1551 Called only from server/srv_main.c srv_scores()
1552 **************************************************************************/
1553 void report_final_scores(struct conn_list
*dest
)
1555 static const struct {
1557 int (*score
) (const struct player
*);
1558 } score_categories
[] = {
1559 { N_("Population\n"), get_real_pop
},
1560 /* TRANS: "M goods" = million goods */
1561 { N_("Trade\n(M goods)"), get_economics
},
1562 /* TRANS: "M tons" = million tons */
1563 { N_("Production\n(M tons)"), get_production
},
1564 { N_("Cities\n"), get_cities
},
1565 { N_("Technologies\n"), get_techs
},
1566 { N_("Military Service\n(months)"), get_mil_service
},
1567 { N_("Wonders\n"), get_wonders
},
1568 { N_("Research Speed\n(bulbs)"), get_research
},
1569 /* TRANS: "sq. mi." is abbreviation for "square miles" */
1570 { N_("Land Area\n(sq. mi.)"), get_landarea
},
1571 /* TRANS: "sq. mi." is abbreviation for "square miles" */
1572 { N_("Settled Area\n(sq. mi.)"), get_settledarea
},
1573 { N_("Literacy\n(%)"), get_literacy
},
1574 { N_("Culture\n"), get_culture
},
1575 { N_("Spaceship\n"), get_spaceship
},
1576 { N_("Built Units\n"), get_units_built
},
1577 { N_("Killed Units\n"), get_units_killed
},
1578 { N_("Unit Losses\n"), get_units_lost
},
1580 const size_t score_categories_num
= ARRAY_SIZE(score_categories
);
1583 struct player_score_entry size
[player_count()];
1584 struct packet_endgame_report packet
;
1586 fc_assert(score_categories_num
<= ARRAY_SIZE(packet
.category_name
));
1589 dest
= game
.est_connections
;
1592 packet
.category_num
= score_categories_num
;
1593 for (j
= 0; j
< score_categories_num
; j
++) {
1594 sz_strlcpy(packet
.category_name
[j
], score_categories
[j
].name
);
1598 players_iterate(pplayer
) {
1599 if (is_barbarian(pplayer
) == FALSE
) {
1600 size
[i
].value
= pplayer
->score
.game
;
1601 size
[i
].player
= pplayer
;
1604 } players_iterate_end
;
1606 qsort(size
, i
, sizeof(size
[0]), secompare
);
1608 packet
.player_num
= i
;
1610 lsend_packet_endgame_report(dest
, &packet
);
1612 for (i
= 0; i
< packet
.player_num
; i
++) {
1613 struct packet_endgame_player ppacket
;
1614 const struct player
*pplayer
= size
[i
].player
;
1616 ppacket
.category_num
= score_categories_num
;
1617 ppacket
.player_id
= player_number(pplayer
);
1618 ppacket
.score
= size
[i
].value
;
1619 for (j
= 0; j
< score_categories_num
; j
++) {
1620 ppacket
.category_score
[j
] = score_categories
[j
].score(pplayer
);
1623 ppacket
.winner
= pplayer
->is_winner
;
1625 lsend_packet_endgame_player(dest
, &ppacket
);
1629 /**************************************************************************
1630 This function pops up a non-modal message dialog on the player's desktop
1631 **************************************************************************/
1632 void page_conn(struct conn_list
*dest
, const char *caption
,
1633 const char *headline
, const char *lines
) {
1634 page_conn_etype(dest
, caption
, headline
, lines
, E_REPORT
);
1638 /****************************************************************************
1639 This function pops up a non-modal message dialog on the player's desktop
1641 event == E_REPORT: message should not be ignored by clients watching
1642 AI players with ai_popup_windows off. Example:
1643 Server Options, Demographics Report, etc.
1645 event == E_BROADCAST_REPORT: message can safely be ignored by clients
1646 watching AI players with ai_popup_windows off. For
1647 example: Herodot's report... and similar messages.
1648 ****************************************************************************/
1649 static void page_conn_etype(struct conn_list
*dest
, const char *caption
,
1650 const char *headline
, const char *lines
,
1651 enum event_type event
)
1653 struct packet_page_msg packet
;
1657 sz_strlcpy(packet
.caption
, caption
);
1658 sz_strlcpy(packet
.headline
, headline
);
1659 packet
.event
= event
;
1660 len
= strlen(lines
);
1661 if ((len
% (MAX_LEN_CONTENT
- 1)) == 0) {
1662 packet
.parts
= len
/ (MAX_LEN_CONTENT
- 1);
1664 packet
.parts
= len
/ (MAX_LEN_CONTENT
- 1) + 1;
1668 lsend_packet_page_msg(dest
, &packet
);
1670 for (i
= 0; i
< packet
.parts
; i
++) {
1671 struct packet_page_msg_part part
;
1674 plen
= MIN(len
, (MAX_LEN_CONTENT
- 1));
1675 strncpy(part
.lines
, &(lines
[(MAX_LEN_CONTENT
- 1) * i
]), plen
);
1676 part
.lines
[plen
] = '\0';
1678 lsend_packet_page_msg_part(dest
, &part
);
1684 /**************************************************************************
1685 Return current history report
1686 **************************************************************************/
1687 struct history_report
*history_report_get(void)
1689 return &latest_history_report
;