Minor changes: next_filename() is now in filesystem.cpp and more robust
against unexpected filenames, and the seg fault that Boucman found when
watching MP games is fixed.

http://stats.wesnoth.org is starting to look more useful, but the graphs
are going to need more love, eg. HttT has too many scenarios to fit the
full graph on the screen:
http://stats.wesnoth.org/?W_PLAYER=1&W_VERSION=1.1-svn

You'll need a browser that understands SVG (or a plugin).

Cheers!
Rusty.
Index: src/upload_log.cpp
===================================================================
--- src/upload_log.cpp  (revision 0)
+++ src/upload_log.cpp  (revision 0)
@@ -0,0 +1,288 @@
+/* $Id$ */
+/*
+   Copyright (C) 2005 by Rusty Russell <[EMAIL PROTECTED]>
+   Part of the Battle for Wesnoth Project http://www.wesnoth.org/
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License.
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY.
+
+   See the COPYING file for more details.
+*/
+#include "global.hpp"
+
+#define GETTEXT_DOMAIN "wesnoth"
+
+#include "gettext.hpp"
+#include "filesystem.hpp"
+#include "preferences.hpp"
+#include "serialization/parser.hpp"
+#include "show_dialog.hpp"
+#include "upload_log.hpp"
+#include "wesconfig.h"
+#include "wml_separators.hpp"
+
+#include "SDL_net.h"
+
+#include <vector>
+#include <string>
+
+#define TARGET_HOST "stats.wesnoth.org"
+#define TARGET_URL "/upload.cgi"
+#define TARGET_PORT 80
+
+struct upload_log::thread_info upload_log::thread_;
+
+// On exit, kill the upload thread if it's still going.
+upload_log::manager::~manager()
+{
+       threading::thread *t = thread_.t;
+       if (t)
+               t->kill();
+}
+
+static void send_string(TCPsocket sock, const std::string &str)
+{
+       if (SDLNet_TCP_Send(sock, (void *)str.c_str(), str.length())
+               != str.length()) {
+               throw network::error("");
+       }
+}
+
+// Function which runs in a background thread to upload logs to server.
+// Uses http POST to port 80 for maximum firewall penetration & other-end
+// compatibility.
+static int upload_logs(void *_ti)
+{
+       TCPsocket sock = NULL;
+       upload_log::thread_info *ti = (upload_log::thread_info *)_ti;
+
+       const char *header =
+               "POST " TARGET_URL " HTTP/1.1\n"
+               "Host: " TARGET_HOST "\n"
+               "User-Agent: Wesnoth " VERSION "\n"
+               "Content-Type: text/plain\n";
+
+       try {
+               std::vector<std::string> files;
+
+               // These are sorted: send them one at a time until we get to 
lastfile.
+               get_files_in_dir(get_upload_dir(), &files, NULL, 
ENTIRE_FILE_PATH);
+
+               IPaddress ip;
+               if (SDLNet_ResolveHost(&ip, TARGET_HOST, TARGET_PORT) == 0) {
+                       std::vector<std::string>::iterator i;
+                       for (i = files.begin(); i!=files.end() && 
*i!=ti->lastfile; i++) {
+                               std::string contents;
+                               int resplen;
+                               char response[strlen("HTTP/1.1 2")];
+
+                               contents = read_file(*i);
+
+                               sock = SDLNet_TCP_Open(&ip);
+                               if (!sock)
+                                       break;
+                               send_string(sock, header);
+                               send_string(sock, "Content-length: ");
+                               send_string(sock, 
lexical_cast<std::string>(contents.length()));
+                               send_string(sock, "\n\n");
+                               send_string(sock, contents.c_str());
+
+                               if (SDLNet_TCP_Recv(sock, response, 
sizeof(response))
+                                       != sizeof(response))
+                                       break;
+                               // Must be version 1.x, must start with 2 (eg. 
200) for success
+                               if (memcmp(response, "HTTP/1.", 
strlen("HTTP/1.")) != 0)
+                                       break;
+                               if (memcmp(response+8, " 2", strlen(" 2")) != 0)
+                                       break;
+
+                               delete_directory(*i);
+                               SDLNet_TCP_Close(sock);
+                               sock = NULL;
+                       }
+               }
+       } catch(...) { }
+
+       if (sock)
+               SDLNet_TCP_Close(sock);
+       ti->t = NULL;
+       return 0;
+}
+
+// Currently only enabled when playing campaigns.
+upload_log::upload_log(bool enable) : game_(NULL), enabled_(enable)
+{
+       filename_ = next_filename(get_upload_dir()); 
+       if (preferences::upload_log() && !thread_.t) {
+               // Thread can outlive us; it uploads everything up to the next
+               // filename, and unsets thread_.t when it's finished.
+               thread_.lastfile = filename_;
+               thread_.t = new threading::thread(upload_logs, &thread_);
+       }
+
+       config_["version"] = VERSION;
+       config_["format_version"] = "1";
+       config_["id"] = preferences::upload_id();
+}
+
+upload_log::~upload_log()
+{
+       // If last game has a conclusion, add it.
+       if (game_finished(game_))
+               config_.add_child("game", *game_);
+
+       if (enabled_ && !config_.empty()) {
+               std::ostream *out = ostream_file(filename_);
+               write(*out, config_);
+               delete out;
+
+               // Try to upload latest log before exit.
+               if (preferences::upload_log() && !thread_.t) {
+                       thread_.lastfile = next_filename(get_upload_dir());
+                       thread_.t = new threading::thread(upload_logs, 
&thread_);
+               }
+       }
+}
+
+bool upload_log::game_finished(config *game)
+{
+       if (!game)
+               return false;
+       
+       return game->child("victory") || game->child("defeat") || 
game->child("quit");
+}
+
+config &upload_log::add_game_result(const std::string &str, int turn)
+{
+       config &child = game_->add_child(str);
+       child["time"] = lexical_cast<std::string>(SDL_GetTicks() / 1000);
+       child["end_turn"] = lexical_cast<std::string>(turn);
+       return child;
+}
+
+// User starts a game (may be new campaign or saved).
+void upload_log::start(game_state &state, const team &team,
+                                          int team_number,
+                                          const unit_map &units,
+                                          const t_string &turn,
+                                          int num_turns)
+{
+       const config *player_conf;
+       std::vector<const unit*> all_units;
+
+       // If we have a previous game which is finished, add it.
+       if (game_finished(game_))
+               config_.add_child("game", *game_);
+
+       game_ = new config();
+       (*game_)["time"] = lexical_cast<std::string>(SDL_GetTicks() / 1000);
+       (*game_)["campaign"] = state.campaign_define;
+       (*game_)["difficulty"] = state.difficulty;
+       (*game_)["scenario"] = state.scenario;
+       if (!state.version.empty())
+               (*game_)["version"] = state.version;
+       if (!turn.empty())
+               (*game_)["start_turn"] = turn;
+       (*game_)["gold"] = lexical_cast<std::string>(team.gold());
+       (*game_)["num_turns"] = lexical_cast<std::string>(num_turns);
+
+       // We seem to have to walk the map to find some units, and the player's
+       // available_units for the rest.
+       for (unit_map::const_iterator un = units.begin(); un != units.end(); 
++un){
+               if (un->second.side() == team_number) {
+                       all_units.push_back(&un->second);
+               }
+       }
+
+       // FIXME: Assumes first player is "us"; is that valid?
+       player_info &player = state.players.begin()->second;
+       for (std::vector<unit>::iterator it = player.available_units.begin();
+                it != player.available_units.end();
+                ++it) {
+               all_units.push_back(&*it);
+       }
+
+       // Record details of any special units.
+       std::vector<const unit*>::const_iterator i;
+       for (i = all_units.begin(); i != all_units.end(); ++i) {
+               if ((*i)->can_recruit()) {
+                       config &sp = game_->add_child("special-unit");
+                       sp["name"] = (*i)->name();
+                       sp["level"] = 
lexical_cast<std::string>((*i)->type().level());
+                       sp["experience"] = 
lexical_cast<std::string>((*i)->experience());
+               }
+       }
+
+       // Record summary of all units.
+       config &summ = game_->add_child("units-by-level");
+       bool higher_units = true;
+       for (int level = 0; higher_units; level++) {
+               std::map<std::string, int> tally;
+
+               higher_units = false;
+               for (i = all_units.begin(); i != all_units.end(); ++i) {
+                       if ((*i)->type().level() > level)
+                               higher_units = true;
+                       else if ((*i)->type().level() == level) {
+                               if (tally.find((*i)->type().id()) == 
tally.end())
+                                       tally[(*i)->type().id()] = 1;
+                               else
+                                       tally[(*i)->type().id()]++;
+                       }
+               }
+               if (!tally.empty()) {
+                       config &tc = 
summ.add_child(lexical_cast<std::string>(level));
+                       for (std::map<std::string, int>::iterator t = 
tally.begin();
+                                t != tally.end();
+                                t++) {
+                               config &uc = tc.add_child(t->first);
+                               uc["count"] = 
lexical_cast<std::string>(t->second);
+                       }
+               }
+       }
+}
+
+// User finishes a scenario.
+void upload_log::defeat(int turn)
+{
+       add_game_result("defeat", turn);
+}
+
+void upload_log::victory(int turn, int gold)
+{
+       config &e = add_game_result("victory", turn);
+       e["gold"] = lexical_cast<std::string>(gold);
+}
+
+void upload_log::quit(int turn)
+{
+       std::string turnstr = lexical_cast<std::string>(turn);
+
+       // We only record the quit if they've actually played a turn.
+       if (!game_ || game_->get_attribute("start_turn") == turnstr || turn == 
1)
+               return;
+
+       add_game_result("quit", turn);
+}
+
+void upload_log_dialog::show_beg_dialog(display& disp)
+{
+       std::vector<gui::check_item> options;
+
+       options.push_back(gui::check_item("Enable summary uploads",
+                                                                         
preferences::upload_log()));
+
+       std::string msg = std::string(_("Wesnoth relies on volunteers like 
yourself for feedback, especially beginners and new players.  Wesnoth keeps 
summaries of your games: you can help us improve game play by giving permission 
to send these summaries (anonymously) to wesnoth.org.\n"))
+               + _("You can see the summaries to be sent in ")
+               + get_upload_dir() + "\n"
+               + _("You can view the results at http://stats.wesnoth.org.\n";);
+
+       gui::show_dialog(disp, NULL,
+                                        _("Help us make Wesnoth better for 
you!"),
+                                        msg,
+                                        gui::OK_ONLY,
+                                        NULL, NULL, "", NULL, 0, NULL, 
&options);
+       preferences::set_upload_log(options.front().checked);
+}
Index: src/playlevel.cpp
===================================================================
--- src/playlevel.cpp   (revision 9403)
+++ src/playlevel.cpp   (working copy)
@@ -19,7 +19,7 @@
 //#include "dialogs.hpp"
 //#include "events.hpp"
 #include "filesystem.hpp"
-//#include "game_errors.hpp"
+#include "game_errors.hpp"
 //#include "gamestatus.hpp"
 #include "gettext.hpp"
 #include "game_events.hpp"
@@ -42,6 +42,7 @@
 #include "statistics.hpp"
 #include "tooltips.hpp"
 //#include "unit_display.hpp"
+#include "upload_log.hpp"
 //#include "util.hpp"
 //#include "video.hpp"
 
@@ -118,7 +119,8 @@
 LEVEL_RESULT play_level(const game_data& gameinfo, const config& game_config,
                config const* level, CVideo& video,
                game_state& state_of_game,
-               const std::vector<config*>& story)
+               const std::vector<config*>& story,
+               upload_log &log)
 {
        //if the recorder has no event, adds an "game start" event to the
        //recorder, whose only goal is to initialize the RNG
@@ -304,6 +306,14 @@
                //instead of starting a fresh one
                const bool loading_game = lvl["playing_team"].empty() == false;
 
+               // log before prestart events: they do weird things.
+               if (first_human_team != -1) {
+                       log.start(state_of_game, teams[first_human_team],
+                                         first_human_team+1, units,
+                                         loading_game ? 
state_of_game.get_variable("turn_number") : "",
+                                         num_turns);
+               }
+
                //pre-start events must be executed before any GUI operation,
                //as those may cause the display to be refreshed.
                if(!loading_game) {
@@ -584,6 +594,10 @@
                        }
                } //end for loop
 
+       } catch(game::load_game_exception& e) {
+               // Loading a new game is effectively a quit.
+               log.quit(status.turn());
+               throw;
        } catch(end_level_exception& end_level) {
                bool obs = team_manager.is_observer();
                if (end_level.result == DEFEAT || end_level.result == VICTORY) {
@@ -603,8 +617,10 @@
                }
 
                if(end_level.result == QUIT) {
+                       log.quit(status.turn());
                        return end_level.result;
                } else if(end_level.result == DEFEAT) {
+                       log.defeat(status.turn());
                        try {
                                game_events::fire("defeat");
                        } catch(end_level_exception&) {
@@ -623,6 +639,10 @@
                        } catch(end_level_exception&) {
                        }
 
+                       if (end_level.result == VICTORY && first_human_team != 
-1) {
+                               log.victory(status.turn(), 
teams[first_human_team].gold());
+                       }
+
                        if(state_of_game.scenario == (*level)["id"]) {
                                state_of_game.scenario = 
(*level)["next_scenario"];
                        }
Index: src/filesystem.cpp
===================================================================
--- src/filesystem.cpp  (revision 9403)
+++ src/filesystem.cpp  (working copy)
@@ -54,6 +54,8 @@
 #include <algorithm>
 #include <fstream>
 #include <iostream>
+#include <iomanip>
+#include <sstream>
 #include <set>
 
 #include "wesconfig.h"
@@ -68,7 +70,6 @@
 #define ERR_FS LOG_STREAM(err, filesystem)
 
 #ifdef USE_ZIPIOS
-#include <sstream>
 #include <zipios++/collcoll.h>
 #include <zipios++/dircoll.h>
 #include <zipios++/zipfile.h>
@@ -303,6 +304,12 @@
        return get_dir(dir_path);
 }
 
+std::string get_upload_dir()
+{
+       const std::string dir_path = get_user_data_dir() + "/upload";
+       return get_dir(dir_path);
+}
+
 std::string get_dir(const std::string& dir_path)
 {
 #ifdef _WIN32
@@ -617,6 +624,32 @@
        return buf.st_mtime;
 }
 
+//return the next ordered full filename within this directory
+std::string next_filename(const std::string &dirname)
+{
+       std::vector<std::string> files;
+       std::stringstream fname;
+       unsigned int num = 1;
+
+       // These are sorted, so we can simply add one to last one.
+       get_files_in_dir(dirname, &files);
+
+       // Make sure we skip over any files we didn't create ourselves.
+       std::vector<std::string>::reverse_iterator i;
+       for (i = files.rbegin(); i != files.rend(); ++i) {
+               if (i->length() == 8) {
+                       try {
+                               num = lexical_cast<int>(*i)+1;
+                               break;
+                       } catch (bad_lexical_cast &c) {
+                       }
+               }
+       }
+
+       fname << std::setw(8) << std::setfill('0') << num;
+       return dirname + "/" + fname.str();
+}
+
 file_tree_checksum::file_tree_checksum()
     : nfiles(0), sum_size(0), modified(0)
 {}
Index: src/upload_log.hpp
===================================================================
--- src/upload_log.hpp  (revision 0)
+++ src/upload_log.hpp  (revision 0)
@@ -0,0 +1,69 @@
+/* $Id$ */
+/*
+   Copyright (C) 2005 by Rusty Russell <[EMAIL PROTECTED]>
+   Part of the Battle for Wesnoth Project http://www.wesnoth.org/
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License.
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY.
+
+   See the COPYING file for more details.
+*/
+
+#ifndef UPLOAD_LOG_H_INCLUDED
+#define UPLOAD_LOG_H_INCLUDED
+#include "config.hpp"
+#include "display.hpp"
+#include "gamestatus.hpp"
+#include "team.hpp"
+#include "thread.hpp"
+#include "tstring.hpp"
+#include "unit.hpp"
+
+struct upload_log
+{
+       struct manager {
+               manager() { };
+               ~manager();
+       };
+
+       // We only enable logging when playing campaigns.
+       upload_log(bool enable);
+       ~upload_log();
+
+       // User starts a game (may be new campaign or saved).
+       void start(game_state &state, const team &team,
+                          int team_number, const unit_map &map, const t_string 
&turn,
+                          int num_turns);
+
+       // User finishes a level.
+       void defeat(int turn);
+       void victory(int turn, int gold);
+       void quit(int turn);
+
+       // Argument passed to upload thread.
+       struct thread_info {
+               threading::thread *t;
+               std::string lastfile;
+       };
+
+private:
+       config &add_game_result(const std::string &str, int turn);
+       bool game_finished(config *game);
+
+       static struct thread_info thread_;
+
+       config config_;
+       config *game_;
+       std::string filename_;
+       bool enabled_;
+};
+
+namespace upload_log_dialog
+{
+       // Please please please upload stats?
+       void show_beg_dialog(display& disp);
+};
+
+#endif // UPLOAD_LOG_H_INCLUDED
Index: src/playlevel.hpp
===================================================================
--- src/playlevel.hpp   (revision 9403)
+++ src/playlevel.hpp   (working copy)
@@ -16,6 +16,7 @@
 class config;
 class CVideo;
 struct game_state;
+class upload_log;
 
 #include "game_config.hpp"
 #include "unit_types.hpp"
@@ -40,7 +41,8 @@
 LEVEL_RESULT play_level(const game_data& gameinfo, const config& 
terrain_config,
                config const* level, CVideo& video,
                game_state& state_of_game,
-               const std::vector<config*>& story);
+               const std::vector<config*>& story,
+               upload_log &log);
 
 namespace play{
        void place_sides_in_preferred_locations(gamemap& map, const 
config::child_list& sides);
Index: src/titlescreen.cpp
===================================================================
--- src/titlescreen.cpp (revision 9403)
+++ src/titlescreen.cpp (working copy)
@@ -203,14 +203,16 @@
                                               N_("TitleScreen button^Load"),
                                               N_("TitleScreen 
button^Language"),
                                               N_("TitleScreen 
button^Preferences"),
+                                                  N_("TitleScreen button^Help 
Wesnoth"),
                                               N_("About"),
-                                              N_("TitleScreen button^Quit") };
+                                                  N_("TitleScreen 
button^Quit") };
        static const char* help_button_labels[] = { N_("Start a tutorial to 
familiarize yourself with the game"),
                                                    N_("Start a new single 
player campaign"),
                                                    N_("Play multiplayer 
(hotseat, LAN, or Internet), or a single scenario against the AI"),
                                                    N_("Load a single player 
saved game"),
                                                    N_("Change the language"),
                                                    N_("Configure the game's 
settings"),
+                                                       N_("Help Wesnoth by 
sending us information"),
                                                    N_("View the credits"),
                                                    N_("Quit the game") };
 
@@ -222,7 +224,7 @@
 #ifdef USE_TINY_GUI
        const int menu_yincr = 15;
 #else
-       const int menu_yincr = 40;
+       const int menu_yincr = 35;
 #endif
        const int padding = game_config::title_buttons_padding;
 
Index: src/Makefile.am
===================================================================
--- src/Makefile.am     (revision 9403)
+++ src/Makefile.am     (working copy)
@@ -103,6 +103,7 @@
        unit.cpp \
        unit_display.cpp \
        unit_types.cpp \
+       upload_log.cpp \
        variable.cpp \
        video.cpp \
        serialization/binary_or_text.cpp \
Index: src/playcampaign.cpp
===================================================================
--- src/playcampaign.cpp        (revision 9403)
+++ src/playcampaign.cpp        (working copy)
@@ -120,6 +120,7 @@
 
 LEVEL_RESULT play_game(display& disp, game_state& state, const config& 
game_config,
                const game_data& units_data, CVideo& video,
+               upload_log &log,
                io_type_t io_type)
 {
        std::string type = state.campaign_type;
@@ -200,7 +201,7 @@
                        if (state.label.empty())
                                state.label = (*scenario)["name"];
 
-                       LEVEL_RESULT res = 
play_level(units_data,game_config,scenario,video,state,story);
+                       LEVEL_RESULT res = 
play_level(units_data,game_config,scenario,video,state,story,log);
                        //LEVEL_RESULT res = 
play_scenario(units_data,game_config,scenario,video,state,story);
 
                        state.snapshot = config();
Index: src/multiplayer.cpp
===================================================================
--- src/multiplayer.cpp (revision 9403)
+++ src/multiplayer.cpp (working copy)
@@ -30,6 +30,7 @@
 #include "video.hpp"
 #include "statistics.hpp"
 #include "serialization/string_utils.hpp"
+#include "upload_log.hpp"
 
 #define LOG_NW LOG_STREAM(info, network)
 
@@ -236,6 +237,7 @@
        mp::ui::result res;
        game_state state;
        network_game_manager m;
+       upload_log nolog(false);
 
        gamelist.clear();
        statistics::fresh_stats();
@@ -257,7 +259,7 @@
 
        switch (res) {
        case mp::ui::PLAY:
-               play_game(disp, state, game_config, data, disp.video(), 
IO_CLIENT);
+               play_game(disp, state, game_config, data, disp.video(), nolog, 
IO_CLIENT);
                recorder.clear();
 
                break;
@@ -278,6 +280,7 @@
                        network::server_manager::TRY_CREATE_SERVER :
                        network::server_manager::NO_SERVER);
        network_game_manager m;
+       upload_log nolog(false);
 
        gamelist.clear();
        statistics::fresh_stats();
@@ -298,7 +301,7 @@
 
        switch (res) {
        case mp::ui::PLAY:
-               play_game(disp, state, game_config, data, disp.video(), 
IO_SERVER);
+               play_game(disp, state, game_config, data, disp.video(), nolog, 
IO_SERVER);
                recorder.clear();
 
                break;
Index: src/game.cpp
===================================================================
--- src/game.cpp        (revision 9403)
+++ src/game.cpp        (working copy)
@@ -51,6 +51,7 @@
 #include "titlescreen.hpp"
 #include "util.hpp"
 #include "unit_types.hpp"
+#include "upload_log.hpp"
 #include "unit.hpp"
 #include "video.hpp"
 #include "wassert.hpp"
@@ -106,6 +107,7 @@
        bool change_language();
 
        void show_preferences();
+       void show_upload_begging();
 
        enum RELOAD_GAME_DATA { RELOAD_DATA, NO_RELOAD_DATA };
        void play_game(RELOAD_GAME_DATA reload=RELOAD_DATA);
@@ -139,6 +141,7 @@
        const image::manager image_manager_;
        const events::event_context main_event_context_;
        const hotkey::manager hotkey_manager_;
+       const upload_log::manager upload_log_manager_;
        binary_paths_manager paths_manager_;
 
        bool test_mode_, multiplayer_mode_, no_gui_;
@@ -417,7 +420,8 @@
        state_.scenario = "test";
 
        try {
-               ::play_game(disp(),state_,game_config_,units_data_,video_);
+               upload_log nolog(false);
+               
::play_game(disp(),state_,game_config_,units_data_,video_,nolog);
        } catch(game::load_game_exception& e) {
                loaded_game_ = e.game;
                loaded_game_show_replay_ = e.show_replay;
@@ -576,8 +580,9 @@
        }
 
        try {
+               upload_log nolog(false);
                state_.snapshot = level;
-               ::play_game(disp(),state_,game_config_,units_data_,video_);
+               
::play_game(disp(),state_,game_config_,units_data_,video_,nolog);
                
//play_level(units_data_,game_config_,&level,video_,state_,story);
        } catch(game::error& e) {
                std::cerr << "caught error: '" << e.message << "'\n";
@@ -1242,6 +1247,13 @@
        disp().redraw_everything();
 }
 
+void game_controller::show_upload_begging()
+{
+       upload_log_dialog::show_beg_dialog(disp());
+
+       disp().redraw_everything();
+}
+
 //this function reads the game configuration, searching for valid cached 
copies first
 void game_controller::read_game_cfg(const preproc_map& defines, config& cfg, 
bool use_cache)
 {
@@ -1454,7 +1466,12 @@
        const binary_paths_manager bin_paths_manager(game_config_);
 
        try {
-               const LEVEL_RESULT result = 
::play_game(disp(),state_,game_config_,units_data_,video_);
+               // Only record log for single-player games & tutorial.
+               upload_log log(state_.campaign_type.empty()
+                                          || state_.campaign_type == "scenario"
+                                          || state_.campaign_type == 
"tutorial");
+
+               const LEVEL_RESULT result = 
::play_game(disp(),state_,game_config_,units_data_,video_, log);
                // don't show The End for multiplayer scenario
                // change this if MP campaigns are implemented
                if(result == VICTORY && (state_.campaign_type.empty() || 
state_.campaign_type != "multiplayer")) {
@@ -1731,6 +1748,9 @@
                } else if(res == gui::SHOW_ABOUT) {
                        about::show_about(game.disp());
                        continue;
+               } else if(res == gui::BEG_FOR_UPLOAD) {
+                       game.show_upload_begging();
+                       continue;
                }
 
                if (recorder.at_end()){
Index: src/filesystem.hpp
===================================================================
--- src/filesystem.hpp  (revision 9403)
+++ src/filesystem.hpp  (working copy)
@@ -51,6 +51,7 @@
 std::string get_cache_dir();
 std::string get_intl_dir();
 std::string get_screenshot_dir();
+std::string get_upload_dir();
 std::string get_user_data_dir();
 
 std::string get_cwd();
@@ -78,6 +79,9 @@
 //function to get the creation time of a file
 time_t file_create_time(const std::string& fname);
 
+//return the next ordered full filename within this directory
+std::string next_filename(const std::string &dirname);
+
 struct file_tree_checksum
 {
        file_tree_checksum();
Index: src/preferences.cpp
===================================================================
--- src/preferences.cpp (revision 9403)
+++ src/preferences.cpp (working copy)
@@ -769,6 +769,29 @@
        fps = value;
 }
 
+bool upload_log()
+{
+       return prefs["upload_log"] == "yes";
+}
+
+void set_upload_log(bool value)
+{
+       prefs["upload_log"] = value ? "yes" : "no";
+}
+
+const std::string &upload_id()
+{
+       // We create a unique id for each person, *when asked for* to increase
+       // randomness.
+       if (prefs["upload_id"] == "") {
+               srand(time(NULL));
+               prefs["upload_id"]
+                       = lexical_cast<std::string>(rand())
+                       + lexical_cast<std::string>(SDL_GetTicks());
+       }
+       return prefs["upload_id"];
+}
+
 bool compress_saves()
 {
        return prefs["compress_saves"] != "no";
Index: src/titlescreen.hpp
===================================================================
--- src/titlescreen.hpp (revision 9403)
+++ src/titlescreen.hpp (working copy)
@@ -20,7 +20,7 @@
 namespace gui {
 
 enum TITLE_RESULT { TUTORIAL = 0, NEW_CAMPAIGN, MULTIPLAYER, LOAD_GAME,
-                    CHANGE_LANGUAGE, EDIT_PREFERENCES, SHOW_ABOUT, QUIT_GAME, 
TITLE_CONTINUE };
+                    CHANGE_LANGUAGE, EDIT_PREFERENCES, BEG_FOR_UPLOAD, 
SHOW_ABOUT, QUIT_GAME, TITLE_CONTINUE };
 
 TITLE_RESULT show_title(display& screen, config& tips_of_day, int* ntip);
 
Index: src/playcampaign.hpp
===================================================================
--- src/playcampaign.hpp        (revision 9403)
+++ src/playcampaign.hpp        (working copy)
@@ -22,6 +22,7 @@
 class config;
 struct game_data;
 class CVideo;
+class upload_log;
 
 enum io_type_t {
        IO_NONE,
@@ -31,6 +32,7 @@
 
 LEVEL_RESULT play_game(display& disp, game_state& state, const config& 
game_config,
                const game_data& units_data, CVideo& video,
+               upload_log &log,
                io_type_t io_type=IO_NONE);
 
 
Index: src/preferences.hpp
===================================================================
--- src/preferences.hpp (revision 9403)
+++ src/preferences.hpp (working copy)
@@ -178,6 +178,10 @@
        bool flip_time();
        void set_flip_time(bool value);
 
+       bool upload_log();
+       void set_upload_log(bool value);
+       const std::string &upload_id();
+
        // Multiplayer functions
        bool chat_timestamp();
        void set_chat_timestamp(bool value);
Index: data/tips.cfg
===================================================================
--- data/tips.cfg       (revision 9403)
+++ data/tips.cfg       (working copy)
@@ -35,3 +35,4 @@
 Gaining an AMLA becomes progressively harder for each AMLA the unit
 receives, however. Thus, it is often more useful to try to advance
 your lower level units."
+tip_of_day_34= _ "You can send the Wesnoth Project an anonymous summary of 
your progress using the Help Wesnoth button.  This information is vital so we 
can adjust campaign difficulty."

-- 
 ccontrol: http://ozlabs.org/~rusty/ccontrol


Reply via email to