Some miscellaneous updates to main help, for new features and otherwise.
[freeciv.git] / server / meta.c
blob6ae35d48b56240bd3c277d3407e11685665aad6c
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 <ctype.h>
19 #include <errno.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
24 #ifdef HAVE_NETINET_IN_H
25 #include <netinet/in.h>
26 #endif
27 #ifdef HAVE_SYS_SOCKET_H
28 #include <sys/socket.h>
29 #endif
30 #ifdef HAVE_SYS_TYPES_H
31 #include <sys/types.h>
32 #endif
33 #ifdef HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36 #ifdef HAVE_ARPA_INET_H
37 #include <arpa/inet.h>
38 #endif
39 #ifdef HAVE_WINSOCK
40 #include <winsock.h>
41 #endif
43 /* utility */
44 #include "fcintl.h"
45 #include "fcthread.h"
46 #include "log.h"
47 #include "mem.h"
48 #include "netintf.h"
49 #include "netfile.h"
50 #include "support.h"
51 #include "timing.h"
53 /* common */
54 #include "capstr.h"
55 #include "connection.h"
56 #include "dataio.h"
57 #include "game.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;
70 static char meta_patches[256] = "";
71 static char meta_message[256] = "";
73 static fc_thread *meta_srv_thread = NULL;
75 /*************************************************************************
76 the default metaserver patches for this server
77 *************************************************************************/
78 const char *default_meta_patches_string(void)
80 return "none";
83 /*************************************************************************
84 Return static string with default info line to send to metaserver.
85 *************************************************************************/
86 const char *default_meta_message_string(void)
88 #if IS_BETA_VERSION
89 return "unstable pre-" NEXT_STABLE_VERSION ": beware";
90 #else /* IS_BETA_VERSION */
91 #if IS_DEVEL_VERSION
92 return "development version: beware";
93 #else /* IS_DEVEL_VERSION */
94 return "-";
95 #endif /* IS_DEVEL_VERSION */
96 #endif /* IS_BETA_VERSION */
99 /*************************************************************************
100 the metaserver patches
101 *************************************************************************/
102 const char *get_meta_patches_string(void)
104 return meta_patches;
107 /*************************************************************************
108 the metaserver message
109 *************************************************************************/
110 const char *get_meta_message_string(void)
112 return meta_message;
115 /*************************************************************************
116 The metaserver message set by user
117 *************************************************************************/
118 const char *get_user_meta_message_string(void)
120 if (game.server.meta_info.user_message_set) {
121 return game.server.meta_info.user_message;
124 return NULL;
127 /*************************************************************************
128 Update meta message. Set it to user meta message, if it is available.
129 Otherwise use provided message.
130 It is ok to call this with NULL message. Then it only replaces current
131 meta message with user meta message if available.
132 *************************************************************************/
133 void maybe_automatic_meta_message(const char *automatic)
135 const char *user_message;
137 user_message = get_user_meta_message_string();
139 if (user_message == NULL) {
140 /* No user message */
141 if (automatic != NULL) {
142 set_meta_message_string(automatic);
144 return;
147 set_meta_message_string(user_message);
150 /*************************************************************************
151 set the metaserver patches string
152 *************************************************************************/
153 void set_meta_patches_string(const char *string)
155 sz_strlcpy(meta_patches, string);
158 /*************************************************************************
159 set the metaserver message string
160 *************************************************************************/
161 void set_meta_message_string(const char *string)
163 sz_strlcpy(meta_message, string);
166 /*************************************************************************
167 set user defined metaserver message string
168 *************************************************************************/
169 void set_user_meta_message_string(const char *string)
171 if (string != NULL && string[0] != '\0') {
172 sz_strlcpy(game.server.meta_info.user_message, string);
173 game.server.meta_info.user_message_set = TRUE;
174 set_meta_message_string(string);
175 } else {
176 /* Remove user meta message. We will use automatic messages instead */
177 game.server.meta_info.user_message[0] = '\0';
178 game.server.meta_info.user_message_set = FALSE;
179 set_meta_message_string(default_meta_message_string());
183 /*************************************************************************
184 Return string describing both metaserver name and port.
185 *************************************************************************/
186 char *meta_addr_port(void)
188 return srvarg.metaserver_addr;
191 /*************************************************************************
192 we couldn't find or connect to the metaserver.
193 *************************************************************************/
194 static void metaserver_failed(void)
196 con_puts(C_METAERROR, _("Not reporting to the metaserver in this game."));
197 con_flush();
199 server_close_meta();
202 /****************************************************************************
203 Insert a setting in the metaserver message. Return TRUE if it succeded.
204 ****************************************************************************/
205 static inline bool meta_insert_setting(struct netfile_post *post,
206 const char *set_name)
208 const struct setting *pset = setting_by_name(set_name);
209 char buf[256];
211 fc_assert_ret_val_msg(NULL != pset, FALSE,
212 "Setting \"%s\" not found!", set_name);
213 netfile_add_form_str(post, "vn[]", setting_name(pset));
214 netfile_add_form_str(post, "vv[]",
215 setting_value_name(pset, FALSE, buf, sizeof(buf)));
216 return TRUE;
219 /*************************************************************************
220 Send POST to metaserver. This runs in its own thread.
221 *************************************************************************/
222 static void send_metaserver_post(void *arg)
224 struct netfile_post *post = (struct netfile_post *) arg;
225 char *addr;
227 if (srvarg.bind_meta_addr != NULL) {
228 addr = srvarg.bind_meta_addr;
229 } else {
230 addr = srvarg.bind_addr;
233 if (!netfile_send_post(srvarg.metaserver_addr, post, NULL, addr)) {
234 con_puts(C_METAERROR, _("Error connecting to metaserver"));
235 metaserver_failed();
238 netfile_close_post(post);
241 /*************************************************************************
242 construct the POST message and send info to metaserver.
243 *************************************************************************/
244 static bool send_to_metaserver(enum meta_flag flag)
246 int players = 0;
247 int humans = 0;
248 char host[512];
249 char state[20];
250 struct netfile_post *post;
252 switch(server_state()) {
253 case S_S_INITIAL:
254 sz_strlcpy(state, "Pregame");
255 break;
256 case S_S_RUNNING:
257 sz_strlcpy(state, "Running");
258 break;
259 case S_S_OVER:
260 sz_strlcpy(state, "Game Ended");
261 break;
264 /* get hostname */
265 if (srvarg.metaserver_name[0] != '\0') {
266 sz_strlcpy(host, srvarg.metaserver_name);
267 } else if (fc_gethostname(host, sizeof(host)) != 0) {
268 sz_strlcpy(host, "unknown");
271 /* Freed in metaserver thread function send_metaserver_post() */
272 post = netfile_start_post();
274 netfile_add_form_str(post, "host", host);
275 netfile_add_form_int(post, "port", srvarg.port);
276 netfile_add_form_str(post, "state", state);
278 if (flag == META_GOODBYE) {
279 netfile_add_form_int(post, "bye", 1);
280 } else {
281 netfile_add_form_str(post, "version", VERSION_STRING);
282 netfile_add_form_str(post, "patches",
283 get_meta_patches_string());
284 netfile_add_form_str(post, "capability", our_capability);
286 netfile_add_form_str(post, "serverid", srvarg.serverid);
287 netfile_add_form_str(post, "message",
288 get_meta_message_string());
290 /* NOTE: send info for ALL players or none at all. */
291 if (normal_player_count() == 0) {
292 netfile_add_form_int(post, "dropplrs", 1);
293 } else {
294 players = 0; /* a counter for players_available */
295 humans = 0;
297 players_iterate(plr) {
298 bool is_player_available = TRUE;
299 char type[15];
300 struct connection *pconn = conn_by_user(plr->username);
302 if (!plr->is_alive) {
303 sz_strlcpy(type, "Dead");
304 } else if (is_barbarian(plr)) {
305 sz_strlcpy(type, "Barbarian");
306 } else if (plr->ai_controlled) {
307 sz_strlcpy(type, "A.I.");
308 } else {
309 sz_strlcpy(type, "Human");
312 netfile_add_form_str(post, "plu[]", plr->username);
313 netfile_add_form_str(post, "plt[]", type);
314 netfile_add_form_str(post, "pll[]", player_name(plr));
315 netfile_add_form_str(post, "pln[]",
316 plr->nation != NO_NATION_SELECTED
317 ? nation_plural_for_player(plr)
318 : "none");
319 netfile_add_form_str(post, "plh[]",
320 pconn ? pconn->addr : "");
322 /* is this player available to take?
323 * TODO: there's some duplication here with
324 * stdinhand.c:is_allowed_to_take() */
325 if (is_barbarian(plr) && !strchr(game.server.allow_take, 'b')) {
326 is_player_available = FALSE;
327 } else if (!plr->is_alive && !strchr(game.server.allow_take, 'd')) {
328 is_player_available = FALSE;
329 } else if (plr->ai_controlled
330 && !strchr(game.server.allow_take,
331 (game.info.is_new_game ? 'A' : 'a'))) {
332 is_player_available = FALSE;
333 } else if (!plr->ai_controlled
334 && !strchr(game.server.allow_take,
335 (game.info.is_new_game ? 'H' : 'h'))) {
336 is_player_available = FALSE;
339 if (pconn) {
340 is_player_available = FALSE;
343 if (is_player_available) {
344 players++;
347 if (!plr->ai_controlled && plr->is_alive) {
348 humans++;
350 } players_iterate_end;
352 /* send the number of available players. */
353 netfile_add_form_int(post, "available", players);
354 netfile_add_form_int(post, "humans", humans);
357 /* Send some variables: should be listed in inverted order? */
359 static const char *settings[] = {
360 "timeout", "endturn", "minplayers", "maxplayers",
361 "aifill", "allowtake", "generator"
363 int i;
365 for (i = 0; i < ARRAY_SIZE(settings); i++) {
366 meta_insert_setting(post, settings[i]);
369 /* HACK: send the most determinant setting for the map size. */
370 switch (map.server.mapsize) {
371 case MAPSIZE_FULLSIZE:
372 meta_insert_setting(post, "size");
373 break;
374 case MAPSIZE_PLAYER:
375 meta_insert_setting(post, "tilesperplayer");
376 break;
377 case MAPSIZE_XYSIZE:
378 meta_insert_setting(post, "xsize");
379 meta_insert_setting(post, "ysize");
380 break;
384 /* Turn and year. */
385 netfile_add_form_str(post, "vn[]", "turn");
386 netfile_add_form_int(post, "vv[]", game.info.turn);
387 netfile_add_form_str(post, "vn[]", "year");
389 if (server_state() != S_S_INITIAL) {
390 netfile_add_form_int(post, "vv[]", game.info.year);
391 } else {
392 netfile_add_form_str(post, "vv[]", "Calendar not set up");
396 if (meta_srv_thread != NULL) {
397 /* Previously started thread */
398 fc_thread_wait(meta_srv_thread);
399 } else {
400 meta_srv_thread = fc_malloc(sizeof(meta_srv_thread));
403 /* Send POST in new thread */
404 fc_thread_start(meta_srv_thread, &send_metaserver_post, post);
406 return TRUE;
409 /*************************************************************************
410 Stop sending updates to metaserver
411 *************************************************************************/
412 void server_close_meta(void)
414 server_is_open = FALSE;
417 /*************************************************************************
418 lookup the correct address for the metaserver.
419 *************************************************************************/
420 bool server_open_meta(void)
422 if (meta_patches[0] == '\0') {
423 set_meta_patches_string(default_meta_patches_string());
425 if (meta_message[0] == '\0') {
426 set_meta_message_string(default_meta_message_string());
429 server_is_open = TRUE;
431 return TRUE;
434 /**************************************************************************
435 are we sending info to the metaserver?
436 **************************************************************************/
437 bool is_metaserver_open(void)
439 return server_is_open;
442 /**************************************************************************
443 control when we send info to the metaserver.
444 **************************************************************************/
445 bool send_server_info_to_metaserver(enum meta_flag flag)
447 static struct timer *last_send_timer = NULL;
448 static bool want_update;
450 if (!server_is_open) {
451 return FALSE;
454 /* if we're bidding farewell, ignore all timers */
455 if (flag == META_GOODBYE) {
456 if (last_send_timer) {
457 timer_destroy(last_send_timer);
458 last_send_timer = NULL;
460 send_to_metaserver(flag);
462 /* Wait metaserver thread to finish */
463 fc_thread_wait(meta_srv_thread);
464 meta_srv_thread = NULL;
466 return TRUE;
469 /* don't allow the user to spam the metaserver with updates */
470 if (last_send_timer && (timer_read_seconds(last_send_timer)
471 < METASERVER_MIN_UPDATE_INTERVAL)) {
472 if (flag == META_INFO) {
473 want_update = TRUE; /* we couldn't update now, but update a.s.a.p. */
475 return FALSE;
478 /* if we're asking for a refresh, only do so if
479 * we've exceeded the refresh interval */
480 if ((flag == META_REFRESH) && !want_update && last_send_timer
481 && (timer_read_seconds(last_send_timer) < METASERVER_REFRESH_INTERVAL)) {
482 return FALSE;
485 /* start a new timer if we haven't already */
486 if (!last_send_timer) {
487 last_send_timer = timer_new(TIMER_USER, TIMER_ACTIVE);
490 timer_clear(last_send_timer);
491 timer_start(last_send_timer);
492 want_update = FALSE;
494 return send_to_metaserver(flag);