add anonymous data logging, thanks, rusty, for the hard work
This commit is contained in:
parent
8674b86082
commit
eab9c1533b
18 changed files with 490 additions and 12 deletions
|
@ -20,6 +20,9 @@ SVN trunk (1.1+svn):
|
|||
use any attack of the same range. Official name for current ranges are
|
||||
"melee" and "ranged"
|
||||
* starting_villages macro updated for added village types
|
||||
* Miscelleneous
|
||||
* Added anonymous data logging for single player campaigns, see
|
||||
stats.wesnoth.org
|
||||
|
||||
Version 1.1:
|
||||
* campaign server
|
||||
|
|
|
@ -35,3 +35,4 @@ tip_of_day_33= _ "Units can gain After Maximum Level Advancements
|
|||
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."
|
||||
|
|
|
@ -103,6 +103,7 @@ wesnoth_SOURCES = \
|
|||
unit.cpp \
|
||||
unit_display.cpp \
|
||||
unit_types.cpp \
|
||||
upload_log.cpp \
|
||||
variable.cpp \
|
||||
video.cpp \
|
||||
serialization/binary_or_text.cpp \
|
||||
|
|
|
@ -353,6 +353,7 @@ std::vector<std::string> get_text() {
|
|||
"- Frédéric Wagner",
|
||||
"- Jeff Breidenbach (jab)",
|
||||
"- Dominique Bolin (Xan)",
|
||||
"- Rusty Russell (rusty)",
|
||||
|
||||
"_" N_("+Bots"),
|
||||
"- wesbot",
|
||||
|
|
|
@ -54,6 +54,8 @@ BPath be_path;
|
|||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
|
||||
#include "wesconfig.h"
|
||||
|
@ -68,7 +70,6 @@ BPath be_path;
|
|||
#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 @@ std::string get_screenshot_dir()
|
|||
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 @@ time_t file_create_time(const std::string& fname)
|
|||
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)
|
||||
{}
|
||||
|
|
|
@ -51,6 +51,7 @@ std::string get_saves_dir();
|
|||
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 @@ bool file_exists(const std::string& name);
|
|||
//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();
|
||||
|
|
26
src/game.cpp
26
src/game.cpp
|
@ -40,6 +40,7 @@
|
|||
#include "thread.hpp"
|
||||
#include "titlescreen.hpp"
|
||||
#include "util.hpp"
|
||||
#include "upload_log.hpp"
|
||||
#include "wassert.hpp"
|
||||
#include "wml_separators.hpp"
|
||||
#include "serialization/binary_or_text.hpp"
|
||||
|
@ -90,6 +91,7 @@ public:
|
|||
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);
|
||||
|
@ -123,6 +125,7 @@ private:
|
|||
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_;
|
||||
|
@ -401,7 +404,8 @@ bool game_controller::play_test()
|
|||
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;
|
||||
|
@ -560,8 +564,9 @@ bool game_controller::play_multiplayer_mode()
|
|||
}
|
||||
|
||||
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";
|
||||
|
@ -1226,6 +1231,13 @@ void game_controller::show_preferences()
|
|||
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)
|
||||
{
|
||||
|
@ -1438,7 +1450,12 @@ void game_controller::play_game(RELOAD_GAME_DATA reload)
|
|||
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")) {
|
||||
|
@ -1715,6 +1732,9 @@ int play_game(int argc, char** argv)
|
|||
} 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()){
|
||||
|
|
|
@ -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 @@ void enter_wait_mode(display& disp, const config& game_config, game_data& data,
|
|||
mp::ui::result res;
|
||||
game_state state;
|
||||
network_game_manager m;
|
||||
upload_log nolog(false);
|
||||
|
||||
gamelist.clear();
|
||||
statistics::fresh_stats();
|
||||
|
@ -257,7 +259,7 @@ void enter_wait_mode(display& disp, const config& game_config, game_data& data,
|
|||
|
||||
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 @@ void enter_connect_mode(display& disp, const config& game_config, game_data& dat
|
|||
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 @@ void enter_connect_mode(display& disp, const config& game_config, game_data& dat
|
|||
|
||||
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;
|
||||
|
|
|
@ -120,6 +120,7 @@ void play_replay(display& disp, game_state& state, const config& game_config,
|
|||
|
||||
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 @@ LEVEL_RESULT play_game(display& disp, game_state& state, const config& game_conf
|
|||
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();
|
||||
|
|
|
@ -22,6 +22,7 @@ struct game_state;
|
|||
class config;
|
||||
struct game_data;
|
||||
class CVideo;
|
||||
class upload_log;
|
||||
|
||||
enum io_type_t {
|
||||
IO_NONE,
|
||||
|
@ -31,6 +32,7 @@ enum io_type_t {
|
|||
|
||||
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);
|
||||
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
#include "sound.hpp"
|
||||
#include "statistics.hpp"
|
||||
#include "tooltips.hpp"
|
||||
#include "upload_log.hpp"
|
||||
#include "game_errors.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
|
@ -107,7 +109,8 @@ namespace play{
|
|||
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
|
||||
|
@ -293,6 +296,14 @@ LEVEL_RESULT play_level(const game_data& gameinfo, const config& game_config,
|
|||
//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) {
|
||||
|
@ -573,6 +584,10 @@ redo_turn:
|
|||
}
|
||||
} //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) {
|
||||
|
@ -592,8 +607,10 @@ redo_turn:
|
|||
}
|
||||
|
||||
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&) {
|
||||
|
@ -612,6 +629,10 @@ redo_turn:
|
|||
} 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"];
|
||||
}
|
||||
|
|
|
@ -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 @@ struct end_turn_exception {
|
|||
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);
|
||||
|
|
|
@ -769,6 +769,29 @@ void set_show_fps(bool value)
|
|||
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";
|
||||
|
|
|
@ -178,6 +178,10 @@ namespace preferences {
|
|||
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);
|
||||
|
|
|
@ -203,14 +203,16 @@ TITLE_RESULT show_title(display& screen, config& tips_of_day, int* ntip)
|
|||
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 @@ TITLE_RESULT show_title(display& screen, config& tips_of_day, int* ntip)
|
|||
#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;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ class display;
|
|||
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);
|
||||
|
||||
|
|
288
src/upload_log.cpp
Normal file
288
src/upload_log.cpp
Normal file
|
@ -0,0 +1,288 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2005 by Rusty Russell <rusty@rustcorp.com.au>
|
||||
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);
|
||||
}
|
69
src/upload_log.hpp
Normal file
69
src/upload_log.hpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2005 by Rusty Russell <rusty@rustcorp.com.au>
|
||||
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
|
Loading…
Add table
Reference in a new issue