Fix division by zero when unit activity rate is zero
[freeciv.git] / server / meta.c
blob111e132bfbbe45096f31cbe3d2f6c1456d079565
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)
6 any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
18 #include "fc_prehdrs.h"
20 #include <ctype.h>
21 #include <errno.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
26 #ifdef HAVE_NETINET_IN_H
27 #include <netinet/in.h>
28 #endif
29 #ifdef HAVE_SYS_SOCKET_H
30 #include <sys/socket.h>
31 #endif
32 #ifdef HAVE_SYS_TYPES_H
33 #include <sys/types.h>
34 #endif
35 #ifdef HAVE_UNISTD_H
36 #include <unistd.h>
37 #endif
38 #ifdef HAVE_ARPA_INET_H
39 #include <arpa/inet.h>
40 #endif
42 /* utility */
43 #include "fcintl.h"
44 #include "fcthread.h"
45 #include "log.h"
46 #include "mem.h"
47 #include "netintf.h"
48 #include "netfile.h"
49 #include "support.h"
50 #include "timing.h"
52 /* common */
53 #include "capstr.h"
54 #include "connection.h"
55 #include "dataio.h"
56 #include "game.h"
57 #include "map.h"
58 #include "version.h"
60 /* server */
61 #include "console.h"
62 #include "plrhand.h"
63 #include "settings.h"
64 #include "srv_main.h"
66 #include "meta.h"
68 static bool server_is_open = FALSE;
69 static bool persistent_meta_connection = FALSE;
70 static int meta_retry_wait = 0;
72 static char meta_patches[256] = "";
73 static char meta_message[256] = "";
75 static fc_thread *meta_srv_thread = NULL;
77 /*************************************************************************
78 the default metaserver patches for this server
79 *************************************************************************/
80 const char *default_meta_patches_string(void)
82 return "none";
85 /*************************************************************************
86 Return static string with default info line to send to metaserver.
87 *************************************************************************/
88 const char *default_meta_message_string(void)
90 #if IS_BETA_VERSION
91 return "unstable pre-" NEXT_STABLE_VERSION ": beware";
92 #else /* IS_BETA_VERSION */
93 #if IS_DEVEL_VERSION
94 return "development version: beware";
95 #else /* IS_DEVEL_VERSION */
96 return "-";
97 #endif /* IS_DEVEL_VERSION */
98 #endif /* IS_BETA_VERSION */
101 /*************************************************************************
102 the metaserver patches
103 *************************************************************************/
104 const char *get_meta_patches_string(void)
106 return meta_patches;
109 /*************************************************************************
110 the metaserver message
111 *************************************************************************/
112 const char *get_meta_message_string(void)
114 return meta_message;
117 /*************************************************************************
118 The server metaserver type
119 *************************************************************************/
120 static const char *get_meta_type_string(void)
122 if (game.server.meta_info.type[0] != '\0') {
123 return game.server.meta_info.type;
126 return NULL;
129 /*************************************************************************
130 The metaserver message set by user
131 *************************************************************************/
132 const char *get_user_meta_message_string(void)
134 if (game.server.meta_info.user_message_set) {
135 return game.server.meta_info.user_message;
138 return NULL;
141 /*************************************************************************
142 Update meta message. Set it to user meta message, if it is available.
143 Otherwise use provided message.
144 It is ok to call this with NULL message. Then it only replaces current
145 meta message with user meta message if available.
146 *************************************************************************/
147 void maybe_automatic_meta_message(const char *automatic)
149 const char *user_message;
151 user_message = get_user_meta_message_string();
153 if (user_message == NULL) {
154 /* No user message */
155 if (automatic != NULL) {
156 set_meta_message_string(automatic);
158 return;
161 set_meta_message_string(user_message);
164 /*************************************************************************
165 set the metaserver patches string
166 *************************************************************************/
167 void set_meta_patches_string(const char *string)
169 sz_strlcpy(meta_patches, string);
172 /*************************************************************************
173 set the metaserver message string
174 *************************************************************************/
175 void set_meta_message_string(const char *string)
177 sz_strlcpy(meta_message, string);
180 /*************************************************************************
181 set user defined metaserver message string
182 *************************************************************************/
183 void set_user_meta_message_string(const char *string)
185 if (string != NULL && string[0] != '\0') {
186 sz_strlcpy(game.server.meta_info.user_message, string);
187 game.server.meta_info.user_message_set = TRUE;
188 set_meta_message_string(string);
189 } else {
190 /* Remove user meta message. We will use automatic messages instead */
191 game.server.meta_info.user_message[0] = '\0';
192 game.server.meta_info.user_message_set = FALSE;
193 set_meta_message_string(default_meta_message_string());
197 /*************************************************************************
198 Return string describing both metaserver name and port.
199 *************************************************************************/
200 char *meta_addr_port(void)
202 return srvarg.metaserver_addr;
205 /*************************************************************************
206 we couldn't find or connect to the metaserver.
207 *************************************************************************/
208 static void metaserver_failed(void)
210 if (!persistent_meta_connection) {
211 con_puts(C_METAERROR, _("Not reporting to the metaserver in this game."));
212 con_flush();
214 server_close_meta();
215 } else {
216 con_puts(C_METAERROR, _("Metaserver connection currently failing."));
217 meta_retry_wait = 1;
221 /****************************************************************************
222 Insert a setting in the metaserver message. Return TRUE if it succeded.
223 ****************************************************************************/
224 static inline bool meta_insert_setting(struct netfile_post *post,
225 const char *set_name)
227 const struct setting *pset = setting_by_name(set_name);
228 char buf[256];
230 fc_assert_ret_val_msg(NULL != pset, FALSE,
231 "Setting \"%s\" not found!", set_name);
232 netfile_add_form_str(post, "vn[]", setting_name(pset));
233 netfile_add_form_str(post, "vv[]",
234 setting_value_name(pset, FALSE, buf, sizeof(buf)));
235 return TRUE;
238 /*************************************************************************
239 Send POST to metaserver. This runs in its own thread.
240 *************************************************************************/
241 static void send_metaserver_post(void *arg)
243 struct netfile_post *post = (struct netfile_post *) arg;
244 char *addr;
246 if (srvarg.bind_meta_addr != NULL) {
247 addr = srvarg.bind_meta_addr;
248 } else {
249 addr = srvarg.bind_addr;
252 if (!netfile_send_post(srvarg.metaserver_addr, post, NULL, NULL, addr)) {
253 con_puts(C_METAERROR, _("Error connecting to metaserver"));
254 metaserver_failed();
257 netfile_close_post(post);
260 /*************************************************************************
261 construct the POST message and send info to metaserver.
262 *************************************************************************/
263 static bool send_to_metaserver(enum meta_flag flag)
265 int players = 0;
266 int humans = 0;
267 char host[512];
268 char state[20];
269 char rs[256];
270 struct netfile_post *post;
272 switch(server_state()) {
273 case S_S_INITIAL:
274 sz_strlcpy(state, "Pregame");
275 break;
276 case S_S_RUNNING:
277 sz_strlcpy(state, "Running");
278 break;
279 case S_S_OVER:
280 sz_strlcpy(state, "Game Ended");
281 break;
284 /* get hostname */
285 if (srvarg.identity_name[0] != '\0') {
286 sz_strlcpy(host, srvarg.identity_name);
287 } else if (fc_gethostname(host, sizeof(host)) != 0) {
288 sz_strlcpy(host, "unknown");
291 if (game.control.version[0] != '\0') {
292 fc_snprintf(rs, sizeof(rs), "%s %s", game.control.name, game.control.version);
293 } else {
294 sz_strlcpy(rs, game.control.name);
297 /* Freed in metaserver thread function send_metaserver_post() */
298 post = netfile_start_post();
300 netfile_add_form_str(post, "host", host);
301 netfile_add_form_int(post, "port", srvarg.port);
302 netfile_add_form_str(post, "state", state);
303 netfile_add_form_str(post, "ruleset", rs);
305 if (flag == META_GOODBYE) {
306 netfile_add_form_int(post, "bye", 1);
307 } else {
308 const char *srvtype = get_meta_type_string();
310 if (srvtype != NULL) {
311 netfile_add_form_str(post, "type", srvtype);
313 netfile_add_form_str(post, "version", VERSION_STRING);
314 netfile_add_form_str(post, "patches",
315 get_meta_patches_string());
316 netfile_add_form_str(post, "capability", our_capability);
318 netfile_add_form_str(post, "serverid", srvarg.serverid);
319 netfile_add_form_str(post, "message",
320 get_meta_message_string());
322 /* NOTE: send info for ALL players or none at all. */
323 if (normal_player_count() == 0) {
324 netfile_add_form_int(post, "dropplrs", 1);
325 } else {
326 players = 0; /* a counter for players_available */
327 humans = 0;
329 players_iterate(plr) {
330 bool is_player_available = TRUE;
331 char type[15];
332 struct connection *pconn = conn_by_user(plr->username);
334 if (!plr->is_alive) {
335 sz_strlcpy(type, "Dead");
336 } else if (is_barbarian(plr)) {
337 sz_strlcpy(type, "Barbarian");
338 } else if (plr->ai_controlled) {
339 sz_strlcpy(type, "A.I.");
340 } else {
341 sz_strlcpy(type, "Human");
344 netfile_add_form_str(post, "plu[]", plr->username);
345 netfile_add_form_str(post, "plt[]", type);
346 netfile_add_form_str(post, "pll[]", player_name(plr));
347 netfile_add_form_str(post, "pln[]",
348 plr->nation != NO_NATION_SELECTED
349 ? nation_plural_for_player(plr)
350 : "none");
351 netfile_add_form_str(post, "plf[]",
352 plr->nation != NO_NATION_SELECTED
353 ? nation_of_player(plr)->flag_graphic_str
354 : "none");
355 netfile_add_form_str(post, "plh[]",
356 pconn ? pconn->addr : "");
358 /* is this player available to take?
359 * TODO: there's some duplication here with
360 * stdinhand.c:is_allowed_to_take() */
361 if (is_barbarian(plr) && !strchr(game.server.allow_take, 'b')) {
362 is_player_available = FALSE;
363 } else if (!plr->is_alive && !strchr(game.server.allow_take, 'd')) {
364 is_player_available = FALSE;
365 } else if (plr->ai_controlled
366 && !strchr(game.server.allow_take,
367 (game.info.is_new_game ? 'A' : 'a'))) {
368 is_player_available = FALSE;
369 } else if (!plr->ai_controlled
370 && !strchr(game.server.allow_take,
371 (game.info.is_new_game ? 'H' : 'h'))) {
372 is_player_available = FALSE;
375 if (pconn) {
376 is_player_available = FALSE;
379 if (is_player_available) {
380 players++;
383 if (!plr->ai_controlled && plr->is_alive) {
384 humans++;
386 } players_iterate_end;
388 /* send the number of available players. */
389 netfile_add_form_int(post, "available", players);
390 netfile_add_form_int(post, "humans", humans);
393 /* Send some variables: should be listed in inverted order? */
395 static const char *settings[] = {
396 "timeout", "endturn", "minplayers", "maxplayers",
397 "aifill", "allowtake", "generator"
399 int i;
401 for (i = 0; i < ARRAY_SIZE(settings); i++) {
402 meta_insert_setting(post, settings[i]);
405 /* HACK: send the most determinant setting for the map size. */
406 switch (game.map.server.mapsize) {
407 case MAPSIZE_FULLSIZE:
408 meta_insert_setting(post, "size");
409 break;
410 case MAPSIZE_PLAYER:
411 meta_insert_setting(post, "tilesperplayer");
412 break;
413 case MAPSIZE_XYSIZE:
414 meta_insert_setting(post, "xsize");
415 meta_insert_setting(post, "ysize");
416 break;
420 /* Turn and year. */
421 netfile_add_form_str(post, "vn[]", "turn");
422 netfile_add_form_int(post, "vv[]", game.info.turn);
423 netfile_add_form_str(post, "vn[]", "year");
425 if (server_state() != S_S_INITIAL) {
426 netfile_add_form_int(post, "vv[]", game.info.year);
427 } else {
428 netfile_add_form_str(post, "vv[]", "Calendar not set up");
432 if (meta_srv_thread != NULL) {
433 /* Previously started thread */
434 fc_thread_wait(meta_srv_thread);
435 } else {
436 meta_srv_thread = fc_malloc(sizeof(meta_srv_thread));
439 /* Send POST in new thread */
440 fc_thread_start(meta_srv_thread, &send_metaserver_post, post);
442 return TRUE;
445 /*************************************************************************
446 Stop sending updates to metaserver
447 *************************************************************************/
448 void server_close_meta(void)
450 server_is_open = FALSE;
451 persistent_meta_connection = FALSE;
454 /*************************************************************************
455 Lookup the correct address for the metaserver.
456 *************************************************************************/
457 bool server_open_meta(bool persistent)
459 if (meta_patches[0] == '\0') {
460 set_meta_patches_string(default_meta_patches_string());
462 if (meta_message[0] == '\0') {
463 set_meta_message_string(default_meta_message_string());
466 server_is_open = TRUE;
467 persistent_meta_connection = persistent;
468 meta_retry_wait = 0;
470 return TRUE;
473 /**************************************************************************
474 Are we sending info to the metaserver?
475 **************************************************************************/
476 bool is_metaserver_open(void)
478 return server_is_open;
481 /**************************************************************************
482 Control when we send info to the metaserver.
483 **************************************************************************/
484 bool send_server_info_to_metaserver(enum meta_flag flag)
486 static struct timer *last_send_timer = NULL;
487 static bool want_update;
489 if (!server_is_open) {
490 return FALSE;
493 /* Persistent connection temporary failures handling */
494 if (meta_retry_wait > 0) {
495 if (meta_retry_wait++ > 5) {
496 meta_retry_wait = 0;
497 } else {
498 return FALSE;
502 /* if we're bidding farewell, ignore all timers */
503 if (flag == META_GOODBYE) {
504 if (last_send_timer) {
505 timer_destroy(last_send_timer);
506 last_send_timer = NULL;
508 send_to_metaserver(flag);
510 /* Wait metaserver thread to finish */
511 fc_thread_wait(meta_srv_thread);
512 free(meta_srv_thread);
513 meta_srv_thread = NULL;
515 return TRUE;
518 /* don't allow the user to spam the metaserver with updates */
519 if (last_send_timer && (timer_read_seconds(last_send_timer)
520 < METASERVER_MIN_UPDATE_INTERVAL)) {
521 if (flag == META_INFO) {
522 want_update = TRUE; /* we couldn't update now, but update a.s.a.p. */
524 return FALSE;
527 /* if we're asking for a refresh, only do so if
528 * we've exceeded the refresh interval */
529 if ((flag == META_REFRESH) && !want_update && last_send_timer
530 && (timer_read_seconds(last_send_timer) < METASERVER_REFRESH_INTERVAL)) {
531 return FALSE;
534 /* start a new timer if we haven't already */
535 if (!last_send_timer) {
536 last_send_timer = timer_new(TIMER_USER, TIMER_ACTIVE);
539 timer_clear(last_send_timer);
540 timer_start(last_send_timer);
541 want_update = FALSE;
543 return send_to_metaserver(flag);