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))
154 #define AI_PLAYER(p) ((p)->ai_controlled)
159 static struct dem_row
{
162 int (*get_value
) (const struct player
*);
163 const char *(*to_text
) (int);
164 bool greater_values_are_better
;
166 {'N', N_("Population"), get_population
, population_to_text
, TRUE
},
167 {'A', N_("Land Area"), get_landarea
, area_to_text
, TRUE
},
168 {'S', N_("Settled Area"), get_settledarea
, area_to_text
, TRUE
},
169 {'R', N_("Research Speed"), get_research
, science_to_text
, TRUE
},
170 /* TRANS: How literate people are. */
171 {'L', N_("?ability:Literacy"), get_literacy
, percent_to_text
, TRUE
},
172 {'P', N_("Production"), get_production
, production_to_text
, TRUE
},
173 {'E', N_("Economics"), get_economics
, economics_to_text
, TRUE
},
174 {'M', N_("Military Service"), get_mil_service
, mil_service_to_text
, FALSE
},
175 {'O', N_("Pollution"), get_pollution
, pollution_to_text
, FALSE
},
176 {'C', N_("Culture"), get_culture
, culture_to_text
, TRUE
}
179 /* Demographics columns. */
186 BV_DEFINE(bv_cols
, DEM_COL_LAST
);
187 static struct dem_col
{
189 } coltable
[] = {{'q'}, {'r'}, {'b'}}; /* Corresponds to dem_flag enum */
191 /* prime number of entries makes for better scaling */
192 static const char *ranking
[] = {
193 /* TRANS: <#>: The <ranking> Poles */
194 N_("%2d: The Supreme %s"),
195 /* TRANS: <#>: The <ranking> Poles */
196 N_("%2d: The Magnificent %s"),
197 /* TRANS: <#>: The <ranking> Poles */
198 N_("%2d: The Great %s"),
199 /* TRANS: <#>: The <ranking> Poles */
200 N_("%2d: The Glorious %s"),
201 /* TRANS: <#>: The <ranking> Poles */
202 N_("%2d: The Excellent %s"),
203 /* TRANS: <#>: The <ranking> Poles */
204 N_("%2d: The Eminent %s"),
205 /* TRANS: <#>: The <ranking> Poles */
206 N_("%2d: The Distinguished %s"),
207 /* TRANS: <#>: The <ranking> Poles */
208 N_("%2d: The Average %s"),
209 /* TRANS: <#>: The <ranking> Poles */
210 N_("%2d: The Mediocre %s"),
211 /* TRANS: <#>: The <ranking> Poles */
212 N_("%2d: The Ordinary %s"),
213 /* TRANS: <#>: The <ranking> Poles */
214 N_("%2d: The Pathetic %s"),
215 /* TRANS: <#>: The <ranking> Poles */
216 N_("%2d: The Useless %s"),
217 /* TRANS: <#>: The <ranking> Poles */
218 N_("%2d: The Valueless %s"),
219 /* TRANS: <#>: The <ranking> Poles */
220 N_("%2d: The Worthless %s"),
221 /* TRANS: <#>: The <ranking> Poles */
222 N_("%2d: The Wretched %s"),
225 /**************************************************************************
226 Compare two player score entries. Used as callback for qsort.
227 **************************************************************************/
228 static int secompare(const void *a
, const void *b
)
230 return (((const struct player_score_entry
*)b
)->value
-
231 ((const struct player_score_entry
*)a
)->value
);
234 /**************************************************************************
235 Construct Historian Report
236 **************************************************************************/
237 static void historian_generic(struct history_report
*report
,
238 enum historian_type which_news
)
240 int i
, j
= 0, rank
= 0;
241 struct player_score_entry size
[player_count()];
243 report
->turn
= game
.info
.turn
;
244 players_iterate(pplayer
) {
245 if (GOOD_PLAYER(pplayer
)) {
247 case HISTORIAN_RICHEST
:
248 size
[j
].value
= pplayer
->economic
.gold
;
250 case HISTORIAN_ADVANCED
:
252 = pplayer
->score
.techs
+ research_get(pplayer
)->future_tech
;
254 case HISTORIAN_MILITARY
:
255 size
[j
].value
= pplayer
->score
.units
;
257 case HISTORIAN_HAPPIEST
:
259 (((pplayer
->score
.happy
- pplayer
->score
.unhappy
) * 1000) /
260 (1 + total_player_citizens(pplayer
)));
262 case HISTORIAN_LARGEST
:
263 size
[j
].value
= total_player_citizens(pplayer
);
266 size
[j
].player
= pplayer
;
268 } /* else the player is dead or barbarian or observer */
269 } players_iterate_end
;
271 qsort(size
, j
, sizeof(size
[0]), secompare
);
272 report
->body
[0] = '\0';
273 for (i
= 0; i
< j
; i
++) {
274 if (i
> 0 && size
[i
].value
< size
[i
- 1].value
) {
275 /* since i < j, only top entry reigns Supreme */
276 rank
= ((i
* ARRAY_SIZE(ranking
)) / j
) + 1;
278 if (rank
>= ARRAY_SIZE(ranking
)) {
279 /* clamp to final entry */
280 rank
= ARRAY_SIZE(ranking
) - 1;
282 cat_snprintf(report
->body
, REPORT_BODYSIZE
,
285 nation_plural_for_player(size
[i
].player
));
286 fc_strlcat(report
->body
, "\n", REPORT_BODYSIZE
);
288 fc_snprintf(report
->title
, REPORT_TITLESIZE
, _(historian_message
[which_news
]),
290 _(historian_name
[fc_rand(ARRAY_SIZE(historian_name
))]));
293 /**************************************************************************
294 Send history report of this turn.
295 **************************************************************************/
296 void send_current_history_report(struct conn_list
*dest
)
298 /* History report is actually constructed at the end of previous turn. */
299 if (latest_history_report
.turn
>= game
.info
.turn
- 1) {
300 page_conn_etype(dest
, _("Historian Publishes!"),
301 latest_history_report
.title
, latest_history_report
.body
,
306 /**************************************************************************
307 Returns the number of wonders the given city has.
308 **************************************************************************/
309 static int nr_wonders(struct city
*pcity
)
313 city_built_iterate(pcity
, i
) {
314 if (is_great_wonder(i
)) {
317 } city_built_iterate_end
;
322 /**************************************************************************
323 Send report listing the "best" 5 cities in the world.
324 **************************************************************************/
325 void report_top_five_cities(struct conn_list
*dest
)
327 const int NUM_BEST_CITIES
= 5;
328 /* a wonder equals WONDER_FACTOR citizen */
329 const int WONDER_FACTOR
= 5;
330 struct city_score_entry size
[NUM_BEST_CITIES
];
334 for (i
= 0; i
< NUM_BEST_CITIES
; i
++) {
339 shuffled_players_iterate(pplayer
) {
340 city_list_iterate(pplayer
->cities
, pcity
) {
341 int value_of_pcity
= city_size_get(pcity
)
342 + nr_wonders(pcity
) * WONDER_FACTOR
;
344 if (value_of_pcity
> size
[NUM_BEST_CITIES
- 1].value
) {
345 size
[NUM_BEST_CITIES
- 1].value
= value_of_pcity
;
346 size
[NUM_BEST_CITIES
- 1].city
= pcity
;
347 qsort(size
, NUM_BEST_CITIES
, sizeof(size
[0]), secompare
);
349 } city_list_iterate_end
;
350 } shuffled_players_iterate_end
;
353 for (i
= 0; i
< NUM_BEST_CITIES
; i
++) {
358 * pcity may be NULL if there are less then NUM_BEST_CITIES in
364 if (player_count() > team_count()) {
365 /* There exists a team with more than one member. */
366 char team_name
[2 * MAX_LEN_NAME
];
368 team_pretty_name(city_owner(size
[i
].city
)->team
, team_name
,
370 cat_snprintf(buffer
, sizeof(buffer
),
371 /* TRANS:"The French City of Lyon (team 3) of size 18". */
372 _("%2d: The %s City of %s (%s) of size %d, "), i
+ 1,
373 nation_adjective_for_player(city_owner(size
[i
].city
)),
374 city_name_get(size
[i
].city
), team_name
,
375 city_size_get(size
[i
].city
));
377 cat_snprintf(buffer
, sizeof(buffer
),
378 _("%2d: The %s City of %s of size %d, "), i
+ 1,
379 nation_adjective_for_player(city_owner(size
[i
].city
)),
380 city_name_get(size
[i
].city
), city_size_get(size
[i
].city
));
383 wonders
= nr_wonders(size
[i
].city
);
385 cat_snprintf(buffer
, sizeof(buffer
), _("with no wonders\n"));
387 cat_snprintf(buffer
, sizeof(buffer
),
388 PL_("with %d wonder\n", "with %d wonders\n", wonders
),
391 page_conn(dest
, _("Traveler's Report:"),
392 _("The Five Greatest Cities in the World!"), buffer
);
395 /**************************************************************************
396 Send report listing all built and destroyed wonders, and wonders
397 currently being built.
398 **************************************************************************/
399 void report_wonders_of_the_world(struct conn_list
*dest
)
405 improvement_iterate(i
) {
406 if (is_great_wonder(i
)) {
407 struct city
*pcity
= city_from_great_wonder(i
);
410 if (player_count() > team_count()) {
411 /* There exists a team with more than one member. */
412 char team_name
[2 * MAX_LEN_NAME
];
414 team_pretty_name(city_owner(pcity
)->team
, team_name
,
416 cat_snprintf(buffer
, sizeof(buffer
),
417 /* TRANS: "Colossus in Rhodes (Greek, team 2)". */
418 _("%s in %s (%s, %s)\n"),
419 city_improvement_name_translation(pcity
, i
),
420 city_name_get(pcity
),
421 nation_adjective_for_player(city_owner(pcity
)),
424 cat_snprintf(buffer
, sizeof(buffer
), _("%s in %s (%s)\n"),
425 city_improvement_name_translation(pcity
, i
),
426 city_name_get(pcity
),
427 nation_adjective_for_player(city_owner(pcity
)));
429 } else if (great_wonder_is_destroyed(i
)) {
430 cat_snprintf(buffer
, sizeof(buffer
), _("%s has been DESTROYED\n"),
431 improvement_name_translation(i
));
434 } improvement_iterate_end
;
436 improvement_iterate(i
) {
437 if (is_great_wonder(i
)) {
438 players_iterate(pplayer
) {
439 city_list_iterate(pplayer
->cities
, pcity
) {
440 if (VUT_IMPROVEMENT
== pcity
->production
.kind
441 && pcity
->production
.value
.building
== i
) {
442 if (player_count() > team_count()) {
443 /* There exists a team with more than one member. */
444 char team_name
[2 * MAX_LEN_NAME
];
446 team_pretty_name(city_owner(pcity
)->team
, team_name
,
448 cat_snprintf(buffer
, sizeof(buffer
),
449 /* TRANS: "([...] (Roman, team 4))". */
450 _("(building %s in %s (%s, %s))\n"),
451 improvement_name_translation(i
), city_name_get(pcity
),
452 nation_adjective_for_player(pplayer
), team_name
);
454 cat_snprintf(buffer
, sizeof(buffer
),
455 _("(building %s in %s (%s))\n"),
456 improvement_name_translation(i
), city_name_get(pcity
),
457 nation_adjective_for_player(pplayer
));
460 } city_list_iterate_end
;
461 } players_iterate_end
;
463 } improvement_iterate_end
;
465 page_conn(dest
, _("Traveler's Report:"),
466 _("Wonders of the World"), buffer
);
469 /****************************************************************************
470 Helper functions which return the value for the given player.
471 ****************************************************************************/
473 /****************************************************************************
475 ****************************************************************************/
476 static int get_population(const struct player
*pplayer
)
478 return pplayer
->score
.population
;
481 /****************************************************************************
482 Number of citizen units of player
483 ****************************************************************************/
484 static int get_pop(const struct player
*pplayer
)
486 return total_player_citizens(pplayer
);
489 /****************************************************************************
490 Number of citizens of player
491 ****************************************************************************/
492 static int get_real_pop(const struct player
*pplayer
)
494 return 1000 * get_pop(pplayer
);
497 /****************************************************************************
498 Land area controlled by player
499 ****************************************************************************/
500 static int get_landarea(const struct player
*pplayer
)
502 return pplayer
->score
.landarea
;
505 /****************************************************************************
507 ****************************************************************************/
508 static int get_settledarea(const struct player
*pplayer
)
510 return pplayer
->score
.settledarea
;
513 /****************************************************************************
515 ****************************************************************************/
516 static int get_research(const struct player
*pplayer
)
518 return pplayer
->score
.techout
;
521 /****************************************************************************
522 Literacy score calculated one way. See also get_literacy2() for
524 ****************************************************************************/
525 static int get_literacy(const struct player
*pplayer
)
527 int pop
= civ_population(pplayer
);
531 } else if (pop
>= 10000) {
532 return pplayer
->score
.literacy
/ (pop
/ 100);
534 return (pplayer
->score
.literacy
* 100) / pop
;
538 /****************************************************************************
540 ****************************************************************************/
541 static int get_production(const struct player
*pplayer
)
543 return pplayer
->score
.mfg
;
546 /****************************************************************************
548 ****************************************************************************/
549 static int get_economics(const struct player
*pplayer
)
551 return pplayer
->score
.bnp
;
554 /****************************************************************************
556 ****************************************************************************/
557 static int get_pollution(const struct player
*pplayer
)
559 return pplayer
->score
.pollution
;
562 /****************************************************************************
563 Military service length
564 ****************************************************************************/
565 static int get_mil_service(const struct player
*pplayer
)
567 return (pplayer
->score
.units
* 5000) / (10 + civ_population(pplayer
));
570 /****************************************************************************
572 ****************************************************************************/
573 static int get_cities(const struct player
*pplayer
)
575 return pplayer
->score
.cities
;
578 /****************************************************************************
580 ****************************************************************************/
581 static int get_techs(const struct player
*pplayer
)
583 return pplayer
->score
.techs
;
586 /****************************************************************************
587 Number of military units
588 ****************************************************************************/
589 static int get_munits(const struct player
*pplayer
)
593 /* count up military units */
594 unit_list_iterate(pplayer
->units
, punit
) {
595 if (is_military_unit(punit
)) {
598 } unit_list_iterate_end
;
603 /****************************************************************************
604 Number of city building units.
605 ****************************************************************************/
606 static int get_settlers(const struct player
*pplayer
)
610 if (!game
.scenario
.prevent_new_cities
) {
611 /* count up settlers */
612 unit_list_iterate(pplayer
->units
, punit
) {
613 if (unit_has_type_flag(punit
, UTYF_CITIES
)) {
616 } unit_list_iterate_end
;
622 /****************************************************************************
624 ****************************************************************************/
625 static int get_wonders(const struct player
*pplayer
)
627 return pplayer
->score
.wonders
;
630 /****************************************************************************
632 ****************************************************************************/
633 static int get_techout(const struct player
*pplayer
)
635 return pplayer
->score
.techout
;
638 /****************************************************************************
639 Literacy score calculated one way. See also get_literacy() to see
641 ****************************************************************************/
642 static int get_literacy2(const struct player
*pplayer
)
644 return pplayer
->score
.literacy
;
647 /****************************************************************************
649 ****************************************************************************/
650 static int get_spaceship(const struct player
*pplayer
)
652 return pplayer
->score
.spaceship
;
655 /****************************************************************************
656 Number of units built
657 ****************************************************************************/
658 static int get_units_built(const struct player
*pplayer
)
660 return pplayer
->score
.units_built
;
663 /****************************************************************************
664 Number of units killed
665 ****************************************************************************/
666 static int get_units_killed(const struct player
*pplayer
)
668 return pplayer
->score
.units_killed
;
671 /****************************************************************************
673 ****************************************************************************/
674 static int get_units_lost(const struct player
*pplayer
)
676 return pplayer
->score
.units_lost
;
679 /****************************************************************************
681 ****************************************************************************/
682 static int get_gold(const struct player
*pplayer
)
684 return pplayer
->economic
.gold
;
687 /****************************************************************************
689 ****************************************************************************/
690 static int get_taxrate(const struct player
*pplayer
)
692 return pplayer
->economic
.tax
;
695 /****************************************************************************
697 ****************************************************************************/
698 static int get_scirate(const struct player
*pplayer
)
700 return pplayer
->economic
.science
;
703 /****************************************************************************
705 ****************************************************************************/
706 static int get_luxrate(const struct player
*pplayer
)
708 return pplayer
->economic
.luxury
;
711 /****************************************************************************
712 Number of rioting cities
713 ****************************************************************************/
714 static int get_riots(const struct player
*pplayer
)
718 city_list_iterate(pplayer
->cities
, pcity
) {
719 if (pcity
->anarchy
> 0) {
722 } city_list_iterate_end
;
727 /****************************************************************************
728 Number of happy citizens
729 ****************************************************************************/
730 static int get_happypop(const struct player
*pplayer
)
732 return pplayer
->score
.happy
;
735 /****************************************************************************
736 Number of content citizens
737 ****************************************************************************/
738 static int get_contentpop(const struct player
*pplayer
)
740 return pplayer
->score
.content
;
743 /****************************************************************************
744 Number of unhappy citizens
745 ****************************************************************************/
746 static int get_unhappypop(const struct player
*pplayer
)
748 return pplayer
->score
.unhappy
;
751 /****************************************************************************
752 Number of specialists.
753 ****************************************************************************/
754 static int get_specialists(const struct player
*pplayer
)
758 specialist_type_iterate(sp
) {
759 count
+= pplayer
->score
.specialists
[sp
];
760 } specialist_type_iterate_end
;
765 /****************************************************************************
767 ****************************************************************************/
768 static int get_gov(const struct player
*pplayer
)
770 return (int) government_number(government_of_player(pplayer
));
773 /****************************************************************************
775 ****************************************************************************/
776 static int get_corruption(const struct player
*pplayer
)
780 city_list_iterate(pplayer
->cities
, pcity
) {
781 result
+= pcity
->waste
[O_TRADE
];
782 } city_list_iterate_end
;
787 /****************************************************************************
789 ****************************************************************************/
790 static int get_total_score(const struct player
*pplayer
)
792 return pplayer
->score
.game
;
795 /****************************************************************************
797 ****************************************************************************/
798 static int get_culture(const struct player
*pplayer
)
800 return pplayer
->score
.culture
;
803 /**************************************************************************
804 Construct string containing value and its unit.
805 **************************************************************************/
806 static const char *value_units(int val
, const char *uni
)
810 if (fc_snprintf(buf
, sizeof(buf
), "%s%s", int_to_text(val
), uni
) == -1) {
811 log_error("String truncated in value_units()!");
817 /**************************************************************************
818 Helper functions which transform the given value to a string
819 depending on the unit.
820 **************************************************************************/
821 static const char *area_to_text(int value
)
823 /* TRANS: abbreviation of "square miles" */
824 return value_units(value
, PL_(" sq. mi.", " sq. mi.", value
));
827 /**************************************************************************
828 Construct string containing value followed by '%'. So value is already
829 considered to be in units of 1/100.
830 **************************************************************************/
831 static const char *percent_to_text(int value
)
833 return value_units(value
, "%");
836 /**************************************************************************
837 Construct string containing value followed by unit suitable for
839 **************************************************************************/
840 static const char *production_to_text(int value
)
842 int clip
= MAX(0, value
);
843 /* TRANS: "M tons" = million tons, so always plural */
844 return value_units(clip
, PL_(" M tons", " M tons", clip
));
847 /**************************************************************************
848 Construct string containing value followed by unit suitable for
850 **************************************************************************/
851 static const char *economics_to_text(int value
)
853 /* TRANS: "M goods" = million goods, so always plural */
854 return value_units(value
, PL_(" M goods", " M goods", value
));
857 /**************************************************************************
858 Construct string containing value followed by unit suitable for
860 **************************************************************************/
861 static const char *science_to_text(int value
)
863 return value_units(value
, PL_(" bulb", " bulbs", value
));
866 /**************************************************************************
867 Construct string containing value followed by unit suitable for
868 military service stats.
869 **************************************************************************/
870 static const char *mil_service_to_text(int value
)
872 return value_units(value
, PL_(" month", " months", value
));
875 /**************************************************************************
876 Construct string containing value followed by unit suitable for
878 **************************************************************************/
879 static const char *pollution_to_text(int value
)
881 return value_units(value
, PL_(" ton", " tons", value
));
884 /**************************************************************************
885 Construct string containing value followed by unit suitable for
887 **************************************************************************/
888 static const char *culture_to_text(int value
)
890 /* TRANS: Unit(s) of culture */
891 return value_units(value
, PL_(" act", " acts", value
));
894 /**************************************************************************
895 Construct one demographics line.
896 **************************************************************************/
897 static void dem_line_item(char *outptr
, size_t out_size
,
898 struct player
*pplayer
, struct dem_row
*prow
,
901 if (NULL
!= pplayer
&& BV_ISSET(selcols
, DEM_COL_QUANTITY
)) {
902 const char *text
= prow
->to_text(prow
->get_value(pplayer
));
904 cat_snprintf(outptr
, out_size
, " %s", text
);
905 cat_snprintf(outptr
, out_size
, "%*s",
906 18 - (int) get_internal_string_length(text
), "");
909 if (NULL
!= pplayer
&& BV_ISSET(selcols
, DEM_COL_RANK
)) {
910 int basis
= prow
->get_value(pplayer
);
913 players_iterate(other
) {
914 if (GOOD_PLAYER(other
)
915 && ((prow
->greater_values_are_better
916 && prow
->get_value(other
) > basis
)
917 || (!prow
->greater_values_are_better
918 && prow
->get_value(other
) < basis
))) {
921 } players_iterate_end
;
923 cat_snprintf(outptr
, out_size
, _("(ranked %d)"), place
);
926 if (NULL
== pplayer
|| BV_ISSET(selcols
, DEM_COL_BEST
)) {
927 struct player
*best_player
= pplayer
;
928 int best_value
= NULL
!= pplayer
? prow
->get_value(pplayer
) : 0;
930 players_iterate(other
) {
931 if (GOOD_PLAYER(other
)) {
932 int value
= prow
->get_value(other
);
935 || (prow
->greater_values_are_better
&& value
> best_value
)
936 || (!prow
->greater_values_are_better
&& value
< best_value
)) {
941 } players_iterate_end
;
944 || (player_has_embassy(pplayer
, best_player
)
945 && (pplayer
!= best_player
))) {
946 cat_snprintf(outptr
, out_size
, " %s: %s",
947 nation_plural_for_player(best_player
),
948 prow
->to_text(prow
->get_value(best_player
)));
953 /*************************************************************************
954 Verify that a given demography string is valid. See
955 game.demography. If the string is not valid the index of the _first_
956 invalid character is return as 'error'.
958 Other settings callback functions are in settings.c, but this one uses
959 static values from this file so it's done separately.
960 *************************************************************************/
961 bool is_valid_demography(const char *demography
, int *error
)
963 int len
= strlen(demography
), i
;
965 /* We check each character individually to see if it's valid. This
966 * does not check for duplicate entries. */
967 for (i
= 0; i
< len
; i
++) {
971 /* See if the character is a valid column label. */
972 for (j
= 0; j
< DEM_COL_LAST
; j
++) {
973 if (demography
[i
] == coltable
[j
].key
) {
983 /* See if the character is a valid row label. */
984 for (j
= 0; j
< ARRAY_SIZE(rowtable
); j
++) {
985 if (demography
[i
] == rowtable
[j
].key
) {
995 /* The character is invalid. */
1000 /* Looks like all characters were valid. */
1004 /*************************************************************************
1005 Send demographics report; what gets reported depends on value of
1006 demographics server option.
1007 *************************************************************************/
1008 void report_demographics(struct connection
*pconn
)
1016 struct player
*pplayer
= pconn
->playing
;
1018 BV_CLR_ALL(selcols
);
1019 fc_assert_ret(ARRAY_SIZE(coltable
) == DEM_COL_LAST
);
1020 for (i
= 0; i
< DEM_COL_LAST
; i
++) {
1021 if (strchr(game
.server
.demography
, coltable
[i
].key
)) {
1028 for (i
= 0; i
< ARRAY_SIZE(rowtable
); i
++) {
1029 if (strchr(game
.server
.demography
, rowtable
[i
].key
)) {
1035 if ((!pconn
->observer
&& !pplayer
)
1036 || (pplayer
&& !pplayer
->is_alive
)
1039 page_conn(pconn
->self
, _("Demographics Report:"),
1040 _("Sorry, the Demographics report is unavailable."), "");
1045 fc_snprintf(civbuf
, sizeof(civbuf
), _("%s %s (%s)"),
1046 nation_adjective_for_player(pplayer
),
1047 government_name_for_player(pplayer
),
1054 for (i
= 0; i
< ARRAY_SIZE(rowtable
); i
++) {
1055 if (strchr(game
.server
.demography
, rowtable
[i
].key
)) {
1056 const char *name
= Q_(rowtable
[i
].name
);
1058 cat_snprintf(buffer
, sizeof(buffer
), "%s", name
);
1059 cat_snprintf(buffer
, sizeof(buffer
), "%*s",
1060 18 - (int) get_internal_string_length(name
), "");
1061 dem_line_item(buffer
, sizeof(buffer
), pplayer
, &rowtable
[i
], selcols
);
1062 sz_strlcat(buffer
, "\n");
1066 page_conn(pconn
->self
, _("Demographics Report:"), civbuf
, buffer
);
1069 /*************************************************************************
1070 Send achievements list
1071 *************************************************************************/
1072 void report_achievements(struct connection
*pconn
)
1076 struct player
*pplayer
= pconn
->playing
;
1078 if (pplayer
== NULL
) {
1082 fc_snprintf(civbuf
, sizeof(civbuf
), _("%s %s (%s)"),
1083 nation_adjective_for_player(pplayer
),
1084 government_name_for_player(pplayer
),
1089 achievements_iterate(pach
) {
1090 if (achievement_player_has(pach
, pplayer
)) {
1091 cat_snprintf(buffer
, sizeof(buffer
), "%s\n",
1092 achievement_name_translation(pach
));
1094 } achievements_iterate_end
;
1096 page_conn(pconn
->self
, _("Achievements List:"), civbuf
, buffer
);
1099 /**************************************************************************
1100 Allocate and initialize plrdata slot.
1101 **************************************************************************/
1102 static void plrdata_slot_init(struct plrdata_slot
*plrdata
,
1105 fc_assert_ret(plrdata
->name
== NULL
);
1107 plrdata
->name
= fc_calloc(MAX_LEN_NAME
, sizeof(plrdata
->name
));
1108 plrdata_slot_replace(plrdata
, name
);
1111 /**************************************************************************
1112 Replace plrdata slot with new one named according to input parameter.
1113 **************************************************************************/
1114 static void plrdata_slot_replace(struct plrdata_slot
*plrdata
,
1117 fc_assert_ret(plrdata
->name
!= NULL
);
1119 fc_strlcpy(plrdata
->name
, name
, MAX_LEN_NAME
);
1122 /**************************************************************************
1123 Free resources allocated for plrdata slot.
1124 **************************************************************************/
1125 static void plrdata_slot_free(struct plrdata_slot
*plrdata
)
1127 if (plrdata
->name
!= NULL
) {
1128 free(plrdata
->name
);
1129 plrdata
->name
= NULL
;
1133 /**************************************************************************
1134 Reads the whole file denoted by fp. Sets last_turn and id to the
1135 values contained in the file. Returns the player_names indexed by
1136 player_no at the end of the log file.
1138 Returns TRUE iff the file had read successfully.
1139 **************************************************************************/
1140 static bool scan_score_log(char *id
)
1142 int line_nr
, turn
, plr_no
, spaces
;
1143 struct plrdata_slot
*plrdata
;
1144 char plr_name
[MAX_LEN_NAME
], line
[80], *ptr
;
1146 fc_assert_ret_val(score_log
!= NULL
, FALSE
);
1147 fc_assert_ret_val(score_log
->fp
!= NULL
, FALSE
);
1149 score_log
->last_turn
= -1;
1152 for (line_nr
= 1;; line_nr
++) {
1153 if (!fgets(line
, sizeof(line
), score_log
->fp
)) {
1154 if (feof(score_log
->fp
) != 0) {
1157 log_error("[%s:-] Can't read scorelog file header!",
1158 game
.server
.scorefile
);
1162 ptr
= strchr(line
, '\n');
1164 log_error("[%s:%d] Line too long!", game
.server
.scorefile
, line_nr
);
1170 if (strncmp(line
, scorelog_magic
, strlen(scorelog_magic
)) != 0) {
1171 log_error("[%s:%d] Bad file magic!", game
.server
.scorefile
, line_nr
);
1176 if (strncmp(line
, "id ", strlen("id ")) == 0) {
1177 if (strlen(id
) > 0) {
1178 log_error("[%s:%d] Multiple ID entries!", game
.server
.scorefile
,
1182 fc_strlcpy(id
, line
+ strlen("id "), MAX_LEN_GAME_IDENTIFIER
);
1183 if (strcmp(id
, server
.game_identifier
) != 0) {
1184 log_error("[%s:%d] IDs don't match! game='%s' scorelog='%s'",
1185 game
.server
.scorefile
, line_nr
, server
.game_identifier
,
1191 if (strncmp(line
, "turn ", strlen("turn ")) == 0) {
1192 if (sscanf(line
+ strlen("turn "), "%d", &turn
) != 1) {
1193 log_error("[%s:%d] Bad line (turn)!", game
.server
.scorefile
,
1198 fc_assert_ret_val(turn
> score_log
->last_turn
, FALSE
);
1199 score_log
->last_turn
= turn
;
1202 if (strncmp(line
, "addplayer ", strlen("addplayer ")) == 0) {
1203 if (3 != sscanf(line
+ strlen("addplayer "), "%d %d %s",
1204 &turn
, &plr_no
, plr_name
)) {
1205 log_error("[%s:%d] Bad line (addplayer)!",
1206 game
.server
.scorefile
, line_nr
);
1210 /* Now get the complete player name if there are several parts. */
1211 ptr
= line
+ strlen("addplayer ");
1213 while (*ptr
!= '\0' && spaces
< 2) {
1219 fc_snprintf(plr_name
, sizeof(plr_name
), "%s", ptr
);
1220 log_debug("add player '%s' (from line %d: '%s')", plr_name
, line_nr
,
1223 if (0 > plr_no
|| plr_no
>= player_slot_count()) {
1224 log_error("[%s:%d] Invalid player number: %d!",
1225 game
.server
.scorefile
, line_nr
, plr_no
);
1229 plrdata
= score_log
->plrdata
+ plr_no
;
1230 if (plrdata
->name
!= NULL
) {
1231 log_error("[%s:%d] Two names for one player (id %d)!",
1232 game
.server
.scorefile
, line_nr
, plr_no
);
1236 plrdata_slot_init(plrdata
, plr_name
);
1239 if (strncmp(line
, "delplayer ", strlen("delplayer ")) == 0) {
1240 if (2 != sscanf(line
+ strlen("delplayer "), "%d %d",
1242 log_error("[%s:%d] Bad line (delplayer)!",
1243 game
.server
.scorefile
, line_nr
);
1247 if (!(plr_no
>= 0 && plr_no
< player_slot_count())) {
1248 log_error("[%s:%d] Invalid player number: %d!",
1249 game
.server
.scorefile
, line_nr
, plr_no
);
1253 plrdata
= score_log
->plrdata
+ plr_no
;
1254 if (plrdata
->name
== NULL
) {
1255 log_error("[%s:%d] Trying to remove undefined player (id %d)!",
1256 game
.server
.scorefile
, line_nr
, plr_no
);
1260 plrdata_slot_free(plrdata
);
1264 if (score_log
->last_turn
== -1) {
1265 log_error("[%s:-] Scorelog contains no turn!", game
.server
.scorefile
);
1269 if (strlen(id
) == 0) {
1270 log_error("[%s:-] Scorelog contains no ID!", game
.server
.scorefile
);
1274 if (score_log
->last_turn
+ 1 != game
.info
.turn
) {
1275 log_error("[%s:-] Scorelog doesn't match savegame!",
1276 game
.server
.scorefile
);
1283 /**************************************************************************
1284 Initialize score logging system
1285 **************************************************************************/
1286 void log_civ_score_init(void)
1288 if (score_log
!= NULL
) {
1292 score_log
= fc_calloc(1, sizeof(*score_log
));
1293 score_log
->fp
= NULL
;
1294 score_log
->last_turn
= -1;
1295 score_log
->plrdata
= fc_calloc(player_slot_count(),
1296 sizeof(*score_log
->plrdata
));
1297 player_slots_iterate(pslot
) {
1298 struct plrdata_slot
*plrdata
= score_log
->plrdata
1299 + player_slot_index(pslot
);
1300 plrdata
->name
= NULL
;
1301 } player_slots_iterate_end
;
1303 latest_history_report
.turn
= -2;
1306 /**************************************************************************
1307 Free resources allocated for score logging system
1308 **************************************************************************/
1309 void log_civ_score_free(void)
1316 if (score_log
->fp
) {
1317 fclose(score_log
->fp
);
1318 score_log
->fp
= NULL
;
1321 if (score_log
->plrdata
) {
1322 player_slots_iterate(pslot
) {
1323 struct plrdata_slot
*plrdata
= score_log
->plrdata
1324 + player_slot_index(pslot
);
1325 if (plrdata
->name
!= NULL
) {
1326 free(plrdata
->name
);
1328 } player_slots_iterate_end
;
1329 free(score_log
->plrdata
);
1336 /**************************************************************************
1337 Create a log file of the civilizations so you can see what was happening.
1338 **************************************************************************/
1339 void log_civ_score_now(void)
1341 enum { SL_CREATE
, SL_APPEND
, SL_UNSPEC
} oper
= SL_UNSPEC
;
1342 char id
[MAX_LEN_GAME_IDENTIFIER
];
1345 /* Add new tags only at end of this list. Maintaining the order of
1346 * old tags is critical. */
1347 static const struct {
1349 int (*get_value
) (const struct player
*);
1352 {"bnp", get_economics
},
1353 {"mfg", get_production
},
1354 {"cities", get_cities
},
1355 {"techs", get_techs
},
1356 {"munits", get_munits
},
1357 {"settlers", get_settlers
}, /* "original" tags end here */
1359 {"wonders", get_wonders
},
1360 {"techout", get_techout
},
1361 {"landarea", get_landarea
},
1362 {"settledarea", get_settledarea
},
1363 {"pollution", get_pollution
},
1364 {"literacy", get_literacy2
},
1365 {"spaceship", get_spaceship
}, /* new 1.8.2 tags end here */
1368 {"taxrate", get_taxrate
},
1369 {"scirate", get_scirate
},
1370 {"luxrate", get_luxrate
},
1371 {"riots", get_riots
},
1372 {"happypop", get_happypop
},
1373 {"contentpop", get_contentpop
},
1374 {"unhappypop", get_unhappypop
},
1375 {"specialists", get_specialists
},
1377 {"corruption", get_corruption
}, /* new 1.11.5 tags end here */
1379 {"score", get_total_score
}, /* New 2.1.10 tag end here. */
1381 {"unitsbuilt", get_units_built
}, /* New tags since 2.3.0. */
1382 {"unitskilled", get_units_killed
},
1383 {"unitslost", get_units_lost
},
1385 {"culture", get_culture
} /* New tag in 2.6.0. */
1388 if (!game
.server
.scorelog
) {
1396 if (!score_log
->fp
) {
1397 if (game
.info
.year
== GAME_START_YEAR
) {
1400 score_log
->fp
= fc_fopen(game
.server
.scorefile
, "r");
1401 if (!score_log
->fp
) {
1404 if (!scan_score_log(id
)) {
1405 goto log_civ_score_disable
;
1409 fclose(score_log
->fp
);
1410 score_log
->fp
= NULL
;
1416 score_log
->fp
= fc_fopen(game
.server
.scorefile
, "w");
1417 if (!score_log
->fp
) {
1418 log_error("Can't open scorelog file '%s' for creation!",
1419 game
.server
.scorefile
);
1420 goto log_civ_score_disable
;
1422 fprintf(score_log
->fp
, "%s%s\n", scorelog_magic
, VERSION_STRING
);
1423 fprintf(score_log
->fp
,
1425 "# For a specification of the format of this see doc/README.scorelog or \n"
1426 "# <http://svn.gna.org/viewcvs/freeciv/trunk/doc/README.scorelog?view=auto>.\n"
1429 fprintf(score_log
->fp
, "id %s\n", server
.game_identifier
);
1430 for (i
= 0; i
< ARRAY_SIZE(score_tags
); i
++) {
1431 fprintf(score_log
->fp
, "tag %d %s\n", i
, score_tags
[i
].name
);
1435 score_log
->fp
= fc_fopen(game
.server
.scorefile
, "a");
1436 if (!score_log
->fp
) {
1437 log_error("Can't open scorelog file '%s' for appending!",
1438 game
.server
.scorefile
);
1439 goto log_civ_score_disable
;
1443 log_error("[%s] bad operation %d", __FUNCTION__
, (int) oper
);
1444 goto log_civ_score_disable
;
1448 if (game
.info
.turn
> score_log
->last_turn
) {
1449 fprintf(score_log
->fp
, "turn %d %d %s\n", game
.info
.turn
, game
.info
.year
,
1451 score_log
->last_turn
= game
.info
.turn
;
1454 player_slots_iterate(pslot
) {
1455 struct plrdata_slot
*plrdata
= score_log
->plrdata
1456 + player_slot_index(pslot
);
1457 if (plrdata
->name
!= NULL
1458 && player_slot_is_used(pslot
)) {
1459 struct player
*pplayer
= player_slot_get_player(pslot
);
1461 if (!GOOD_PLAYER(pplayer
)) {
1462 fprintf(score_log
->fp
, "delplayer %d %d\n", game
.info
.turn
- 1,
1463 player_number(pplayer
));
1464 plrdata_slot_free(plrdata
);
1467 } player_slots_iterate_end
;
1469 players_iterate(pplayer
) {
1470 struct plrdata_slot
*plrdata
= score_log
->plrdata
+ player_index(pplayer
);
1471 if (plrdata
->name
== NULL
&& GOOD_PLAYER(pplayer
)) {
1472 switch (game
.server
.scoreloglevel
) {
1474 if (AI_PLAYER(pplayer
)) {
1478 fprintf(score_log
->fp
, "addplayer %d %d %s\n", game
.info
.turn
,
1479 player_number(pplayer
), player_name(pplayer
));
1480 plrdata_slot_init(plrdata
, player_name(pplayer
));
1483 } players_iterate_end
;
1485 players_iterate(pplayer
) {
1486 struct plrdata_slot
*plrdata
= score_log
->plrdata
+ player_index(pplayer
);
1488 if (GOOD_PLAYER(pplayer
)) {
1489 switch (game
.server
.scoreloglevel
) {
1491 if (AI_PLAYER(pplayer
) && plrdata
->name
== NULL
) {
1492 /* If a human player toggled into AI mode, don't break. */
1496 if (strcmp(plrdata
->name
, player_name(pplayer
)) != 0) {
1497 log_debug("player names does not match '%s' != '%s'", plrdata
->name
,
1498 player_name(pplayer
));
1499 fprintf(score_log
->fp
, "delplayer %d %d\n", game
.info
.turn
- 1,
1500 player_number(pplayer
));
1501 fprintf(score_log
->fp
, "addplayer %d %d %s\n", game
.info
.turn
,
1502 player_number(pplayer
), player_name(pplayer
));
1503 plrdata_slot_replace(plrdata
, player_name(pplayer
));
1507 } players_iterate_end
;
1509 for (i
= 0; i
< ARRAY_SIZE(score_tags
); i
++) {
1510 players_iterate(pplayer
) {
1511 if (!GOOD_PLAYER(pplayer
) ||
1512 (game
.server
.scoreloglevel
== SL_HUMANS
&& AI_PLAYER(pplayer
))) {
1516 fprintf(score_log
->fp
, "data %d %d %d %d\n", game
.info
.turn
, i
,
1517 player_number(pplayer
), score_tags
[i
].get_value(pplayer
));
1518 } players_iterate_end
;
1521 fflush(score_log
->fp
);
1525 log_civ_score_disable
:
1527 log_civ_score_free();
1530 /**************************************************************************
1531 Produce random history report if it's time for one.
1532 **************************************************************************/
1533 void make_history_report(void)
1535 if (player_count() == 1) {
1539 if (game
.server
.scoreturn
> game
.info
.turn
) {
1543 game
.server
.scoreturn
= (game
.info
.turn
+ GAME_DEFAULT_SCORETURN
1544 + fc_rand(GAME_DEFAULT_SCORETURN
));
1546 historian_generic(&latest_history_report
, game
.server
.scoreturn
% HISTORIAN_LAST
);
1547 send_current_history_report(game
.est_connections
);
1550 /**************************************************************************
1551 Inform clients about player scores and statistics when the game ends.
1552 Called only from server/srv_main.c srv_scores()
1553 **************************************************************************/
1554 void report_final_scores(struct conn_list
*dest
)
1556 static const struct {
1558 int (*score
) (const struct player
*);
1559 } score_categories
[] = {
1560 { N_("Population\n"), get_real_pop
},
1561 /* TRANS: "M goods" = million goods */
1562 { N_("Trade\n(M goods)"), get_economics
},
1563 /* TRANS: "M tons" = million tons */
1564 { N_("Production\n(M tons)"), get_production
},
1565 { N_("Cities\n"), get_cities
},
1566 { N_("Technologies\n"), get_techs
},
1567 { N_("Military Service\n(months)"), get_mil_service
},
1568 { N_("Wonders\n"), get_wonders
},
1569 { N_("Research Speed\n(bulbs)"), get_research
},
1570 /* TRANS: "sq. mi." is abbreviation for "square miles" */
1571 { N_("Land Area\n(sq. mi.)"), get_landarea
},
1572 /* TRANS: "sq. mi." is abbreviation for "square miles" */
1573 { N_("Settled Area\n(sq. mi.)"), get_settledarea
},
1574 { N_("Literacy\n(%)"), get_literacy
},
1575 { N_("Culture\n"), get_culture
},
1576 { N_("Spaceship\n"), get_spaceship
},
1577 { N_("Built Units\n"), get_units_built
},
1578 { N_("Killed Units\n"), get_units_killed
},
1579 { N_("Unit Losses\n"), get_units_lost
},
1581 const size_t score_categories_num
= ARRAY_SIZE(score_categories
);
1584 struct player_score_entry size
[player_count()];
1585 struct packet_endgame_report packet
;
1587 fc_assert(score_categories_num
<= ARRAY_SIZE(packet
.category_name
));
1590 dest
= game
.est_connections
;
1593 packet
.category_num
= score_categories_num
;
1594 for (j
= 0; j
< score_categories_num
; j
++) {
1595 sz_strlcpy(packet
.category_name
[j
], score_categories
[j
].name
);
1599 players_iterate(pplayer
) {
1600 if (GOOD_PLAYER(pplayer
)) {
1601 size
[i
].value
= pplayer
->score
.game
;
1602 size
[i
].player
= pplayer
;
1605 } players_iterate_end
;
1607 qsort(size
, i
, sizeof(size
[0]), secompare
);
1609 packet
.player_num
= i
;
1611 lsend_packet_endgame_report(dest
, &packet
);
1613 for (i
= 0; i
< packet
.player_num
; i
++) {
1614 struct packet_endgame_player ppacket
;
1615 const struct player
*pplayer
= size
[i
].player
;
1617 ppacket
.category_num
= score_categories_num
;
1618 ppacket
.player_id
= player_number(pplayer
);
1619 ppacket
.score
= size
[i
].value
;
1620 for (j
= 0; j
< score_categories_num
; j
++) {
1621 ppacket
.category_score
[j
] = score_categories
[j
].score(pplayer
);
1624 ppacket
.winner
= pplayer
->is_winner
;
1626 lsend_packet_endgame_player(dest
, &ppacket
);
1630 /**************************************************************************
1631 This function pops up a non-modal message dialog on the player's desktop
1632 **************************************************************************/
1633 void page_conn(struct conn_list
*dest
, const char *caption
,
1634 const char *headline
, const char *lines
) {
1635 page_conn_etype(dest
, caption
, headline
, lines
, E_REPORT
);
1639 /****************************************************************************
1640 This function pops up a non-modal message dialog on the player's desktop
1642 event == E_REPORT: message should not be ignored by clients watching
1643 AI players with ai_popup_windows off. Example:
1644 Server Options, Demographics Report, etc.
1646 event == E_BROADCAST_REPORT: message can safely be ignored by clients
1647 watching AI players with ai_popup_windows off. For
1648 example: Herodot's report... and similar messages.
1649 ****************************************************************************/
1650 static void page_conn_etype(struct conn_list
*dest
, const char *caption
,
1651 const char *headline
, const char *lines
,
1652 enum event_type event
)
1654 struct packet_page_msg packet
;
1658 sz_strlcpy(packet
.caption
, caption
);
1659 sz_strlcpy(packet
.headline
, headline
);
1660 packet
.event
= event
;
1661 len
= strlen(lines
);
1662 if ((len
% (MAX_LEN_CONTENT
- 1)) == 0) {
1663 packet
.parts
= len
/ (MAX_LEN_CONTENT
- 1);
1665 packet
.parts
= len
/ (MAX_LEN_CONTENT
- 1) + 1;
1669 lsend_packet_page_msg(dest
, &packet
);
1671 for (i
= 0; i
< packet
.parts
; i
++) {
1672 struct packet_page_msg_part part
;
1675 plen
= MIN(len
, (MAX_LEN_CONTENT
- 1));
1676 strncpy(part
.lines
, &(lines
[(MAX_LEN_CONTENT
- 1) * i
]), plen
);
1677 part
.lines
[plen
] = '\0';
1679 lsend_packet_page_msg_part(dest
, &part
);
1685 /**************************************************************************
1686 Return current history report
1687 **************************************************************************/
1688 struct history_report
*history_report_get(void)
1690 return &latest_history_report
;