407 lines
11 KiB
C++
407 lines
11 KiB
C++
#include "language.hpp"
|
|
#include "log.hpp"
|
|
#include "multiplayer.hpp"
|
|
#include "multiplayer_client.hpp"
|
|
#include "multiplayer_lobby.hpp"
|
|
#include "playlevel.hpp"
|
|
#include "preferences.hpp"
|
|
#include "replay.hpp"
|
|
#include "show_dialog.hpp"
|
|
|
|
#include <sstream>
|
|
|
|
namespace {
|
|
|
|
class gamelist_manager : public gui::dialog_action
|
|
{
|
|
public:
|
|
int do_action() {
|
|
const network::connection res = network::receive_data(cfg);
|
|
if(res != NULL && get_gamelist() != NULL) {
|
|
return UPDATED_GAMELIST;
|
|
} else {
|
|
return CONTINUE_DIALOG;
|
|
}
|
|
}
|
|
|
|
const config* get_gamelist() const {
|
|
return cfg.child("gamelist");
|
|
}
|
|
|
|
enum { UPDATED_GAMELIST = -3 };
|
|
|
|
private:
|
|
config cfg;
|
|
};
|
|
|
|
enum GAME_LIST_RESULT { QUIT_GAME, CREATE_GAME, JOIN_GAME };
|
|
|
|
GAME_LIST_RESULT manage_game_list(display& disp, const config* gamelist)
|
|
{
|
|
gamelist_manager manager;
|
|
for(;;) {
|
|
std::vector<std::string> options;
|
|
options.push_back(string_table["create_new_game"]);
|
|
for(config::const_child_itors i = gamelist->child_range("game");
|
|
i.first != i.second; ++i.first) {
|
|
options.push_back((**i.first)["name"]);
|
|
}
|
|
|
|
options.push_back(string_table["quit_button"]);
|
|
|
|
const int res = gui::show_dialog(disp,NULL,"","Choose game to join",
|
|
gui::MESSAGE,&options,NULL,"",NULL,&manager);
|
|
if(res == gamelist_manager::UPDATED_GAMELIST) {
|
|
gamelist = manager.get_gamelist();
|
|
} else if(res == 0) {
|
|
std::string name;
|
|
const int res = gui::show_dialog(disp,NULL,"","Name your game:",
|
|
gui::OK_CANCEL,NULL,NULL,"Name:",&name);
|
|
if(res == 0) {
|
|
config response;
|
|
config& create_game = response.add_child("create_game");;
|
|
create_game["name"] = name;
|
|
|
|
network::send_data(response);
|
|
|
|
return CREATE_GAME;
|
|
}
|
|
} else if(size_t(res) == options.size()-1) {
|
|
return QUIT_GAME;
|
|
} else if(res > 0 && size_t(res) < options.size()) {
|
|
const config::const_child_itors i = gamelist->child_range("game");
|
|
const size_t index = size_t(res)-1;
|
|
assert(i.second - i.first > int(index));
|
|
const std::string& id = (**(i.first+index))["id"];
|
|
|
|
config response;
|
|
config& join = response.add_child("join");
|
|
join["id"] = id;
|
|
|
|
network::send_data(response);
|
|
|
|
return JOIN_GAME;
|
|
}
|
|
}
|
|
|
|
return QUIT_GAME;
|
|
}
|
|
|
|
void check_response(network::connection res, const config& data)
|
|
{
|
|
if(!res) {
|
|
throw network::error(string_table["connection_timeout"]);
|
|
}
|
|
|
|
const config* err = data.child("error");
|
|
if(err != NULL) {
|
|
throw network::error((*err)["message"]);
|
|
}
|
|
}
|
|
|
|
void receive_gamelist(display& disp, config& data)
|
|
{
|
|
for(;;) {
|
|
|
|
const network::connection res = gui::network_data_dialog(disp,string_table["receive_game_list"],data);
|
|
check_response(res,data);
|
|
|
|
if(data.child("gamelist")) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
class wait_for_start : public gui::dialog_action
|
|
{
|
|
public:
|
|
wait_for_start(config& cfg) : got_side(false), sides_(cfg) {}
|
|
|
|
int do_action() {
|
|
config reply;
|
|
const network::connection res = network::receive_data(reply);
|
|
if(res) {
|
|
std::cerr << "received data while waiting: " << reply.write() << "\n";
|
|
if(reply.values["failed"] == "yes") {
|
|
got_side = false;
|
|
throw network::error("Side chosen is unavailable");
|
|
} else if(reply["side_secured"].empty() == false) {
|
|
got_side = true;
|
|
} else if(reply.child("start_game")) {
|
|
std::cerr << "received start_game message\n";
|
|
//break out of dialog
|
|
return START_GAME;
|
|
} else if(reply.child("leave_game")) {
|
|
return GAME_CANCELLED;
|
|
} else {
|
|
sides_ = reply;
|
|
}
|
|
}
|
|
|
|
return CONTINUE_DIALOG;
|
|
}
|
|
|
|
bool got_side;
|
|
|
|
enum { START_GAME = 1, GAME_CANCELLED = 2 };
|
|
|
|
private:
|
|
config& sides_;
|
|
};
|
|
}
|
|
|
|
void play_multiplayer_client(display& disp, game_data& units_data, config& cfg,
|
|
game_state& state)
|
|
{
|
|
log_scope("playing multiplayer client");
|
|
|
|
const network::manager net_manager;
|
|
|
|
std::string host = preferences::network_host();
|
|
const int res = gui::show_dialog(disp,NULL,"","",
|
|
gui::OK_CANCEL,NULL,NULL,
|
|
string_table["remote_host"] + ": ",&host);
|
|
if(res != 0 || host.empty()) {
|
|
return;
|
|
}
|
|
|
|
network::connection sock;
|
|
|
|
sock = network::connect(host);
|
|
config sides, data;
|
|
|
|
|
|
network::connection data_res = gui::network_data_dialog(disp,string_table["connecting_remote"],data);
|
|
check_response(data_res,data);
|
|
|
|
preferences::set_network_host(host);
|
|
|
|
//if response contained a version number
|
|
const std::string& version = data["version"];
|
|
if(version.empty() == false && version != game_config::version) {
|
|
throw network::error("The server requires version '" + version
|
|
+ "' while you are using version'" + game_config::version + "'");
|
|
}
|
|
|
|
bool logged_in = false;
|
|
|
|
//if we got a direction to login
|
|
if(data.child("mustlogin")) {
|
|
|
|
config* error = NULL;
|
|
|
|
do {
|
|
if(error != NULL) {
|
|
gui::show_dialog(disp,NULL,"",(*error)["message"],gui::OK_ONLY);
|
|
}
|
|
|
|
std::string login = preferences::login();
|
|
const int res = gui::show_dialog(disp,NULL,"",
|
|
string_table["must_login"],gui::OK_CANCEL,
|
|
NULL,NULL,string_table["login"] + ": ",&login);
|
|
if(res != 0 || login.empty()) {
|
|
return;
|
|
}
|
|
|
|
preferences::set_login(login);
|
|
|
|
config response;
|
|
response.add_child("login")["username"] = login;
|
|
network::send_data(response);
|
|
|
|
data_res = network::receive_data(data,0,5000);
|
|
if(!data_res) {
|
|
throw network::error(string_table["connection_timeout"]);
|
|
}
|
|
|
|
error = data.child("error");
|
|
} while(error != NULL);
|
|
|
|
logged_in = true;
|
|
}
|
|
|
|
for(bool first_time = true;
|
|
(first_time || logged_in) && network::nconnections() > 0;
|
|
first_time = false) {
|
|
|
|
if(!first_time) {
|
|
receive_gamelist(disp,data);
|
|
}
|
|
|
|
//if we got a gamelist back - otherwise we have
|
|
//got a description of the game back
|
|
const config* const gamelist = data.child("gamelist");
|
|
if(gamelist != NULL) {
|
|
config game_data = data;
|
|
int status = -1;
|
|
while(status == -1) {
|
|
const lobby::RESULT res = lobby::enter(disp,game_data);
|
|
switch(res) {
|
|
case lobby::QUIT: {
|
|
status = 1;
|
|
return;
|
|
}
|
|
case lobby::CREATE: {
|
|
status = play_multiplayer(disp,units_data,cfg,state,false);
|
|
break;
|
|
}
|
|
case lobby::JOIN: {
|
|
status = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(;;) {
|
|
data_res = gui::network_data_dialog(disp,string_table["getting_game_data"],data);
|
|
check_response(data_res,data);
|
|
|
|
//if we have got valid side data
|
|
if(sides.child("gamelist") == NULL) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
sides = data;
|
|
}
|
|
|
|
//ensure we send a close game message to the server when we are done
|
|
network_game_manager game_manager;
|
|
|
|
std::map<int,int> choice_map;
|
|
std::vector<std::string> choices, race_names;
|
|
std::vector<bool> changes_allowed;
|
|
choices.push_back(string_table["observer"]);
|
|
|
|
const config::child_list& sides_list = sides.get_children("side");
|
|
for(config::child_list::const_iterator s = sides_list.begin(); s != sides_list.end(); ++s) {
|
|
if((**s)["controller"] == "network" &&
|
|
(**s)["taken"] != "yes") {
|
|
choice_map[choices.size()] = 1 + s - sides_list.begin();
|
|
choices.push_back((**s)["name"] + " - " + (**s)["type"]);
|
|
}
|
|
|
|
race_names.push_back((**s)["name"]);
|
|
changes_allowed.push_back((**s)["allow_changes"] != "no");
|
|
}
|
|
|
|
const int choice = gui::show_dialog(disp,NULL,"","Choose side:",
|
|
gui::OK_CANCEL,&choices);
|
|
if(choice < 0) {
|
|
continue;
|
|
}
|
|
|
|
const int team_num = choice_map[choice];
|
|
const bool observer = choice == 0;
|
|
|
|
const std::string& default_race = race_names[team_num-1];
|
|
const bool allow_changes = changes_allowed[team_num-1];
|
|
|
|
//send our choice of team to the server
|
|
if(!observer) {
|
|
|
|
config response;
|
|
std::stringstream stream;
|
|
stream << team_num;
|
|
response["side"] = stream.str();
|
|
response["description"] = preferences::login();
|
|
|
|
const config::child_list& possible_sides = cfg.get_children("multiplayer_side");
|
|
if(possible_sides.empty()) {
|
|
std::cerr << "no multiplayer sides found\n";
|
|
return;
|
|
}
|
|
|
|
size_t choice = 0;
|
|
|
|
std::vector<std::string> choices;
|
|
for(config::child_list::const_iterator side =
|
|
possible_sides.begin(); side != possible_sides.end(); ++side) {
|
|
choices.push_back((**side)["name"]);
|
|
|
|
if(choices.back() == default_race)
|
|
choice = side - possible_sides.begin();
|
|
}
|
|
|
|
//if the client is allowed to choose their team, instead of having
|
|
//it set by the server, do that here.
|
|
if(allow_changes) {
|
|
choice = size_t(gui::show_dialog(disp,NULL,"",
|
|
string_table["client_choose_side"],gui::OK_ONLY,&choices));
|
|
}
|
|
|
|
assert(choice < possible_sides.size());
|
|
|
|
const config& chosen_side = *possible_sides[choice];
|
|
response["name"] = chosen_side["name"];
|
|
response["type"] = chosen_side["type"];
|
|
response["recruit"] = chosen_side["recruit"];
|
|
response["music"] = chosen_side["music"];
|
|
|
|
network::send_data(response);
|
|
}
|
|
|
|
wait_for_start waiter(sides);
|
|
const int dialog_res = gui::show_dialog(disp,NULL,"",
|
|
string_table["waiting_start"],
|
|
gui::CANCEL_ONLY,NULL,NULL,"",NULL,&waiter);
|
|
if(dialog_res == wait_for_start::GAME_CANCELLED) {
|
|
gui::show_dialog(disp,NULL,"",string_table["game_cancelled"],
|
|
gui::OK_ONLY);
|
|
continue;
|
|
} else if(dialog_res != wait_for_start::START_GAME) {
|
|
continue;
|
|
}
|
|
|
|
if(!observer && !waiter.got_side) {
|
|
throw network::error("Choice of team unavailable.");
|
|
}
|
|
|
|
//we want to make the network/human players look right from our
|
|
//perspective
|
|
{
|
|
const config::child_list& sides_list = sides.get_children("side");
|
|
for(config::child_list::const_iterator side = sides_list.begin(); side != sides_list.end(); ++side) {
|
|
if(team_num-1 == side - sides_list.begin())
|
|
(**side)["controller"] = preferences::client_type();
|
|
else
|
|
(**side)["controller"] = "network";
|
|
}
|
|
}
|
|
|
|
//any replay data is only temporary and should be removed from
|
|
//the level data in case we want to save the game later
|
|
config* const replay_data = sides.child("replay");
|
|
config replay_data_store;
|
|
if(replay_data != NULL) {
|
|
replay_data_store = *replay_data;
|
|
std::cerr << "setting replay\n";
|
|
recorder = replay(replay_data_store);
|
|
if(!recorder.empty()) {
|
|
const int res = gui::show_dialog(disp,NULL,
|
|
"", string_table["replay_game_message"],
|
|
gui::YES_NO);
|
|
//if yes, then show the replay, otherwise
|
|
//skip showing the replay
|
|
if(res == 0) {
|
|
recorder.set_skip(0);
|
|
} else {
|
|
std::cerr << "skipping...\n";
|
|
recorder.set_skip(-1);
|
|
}
|
|
}
|
|
|
|
sides.clear_children("replay");
|
|
}
|
|
|
|
std::cerr << "starting game\n";
|
|
|
|
state.starting_pos = sides;
|
|
|
|
recorder.set_save_info(state);
|
|
|
|
std::vector<config*> story;
|
|
play_level(units_data,cfg,&sides,disp.video(),state,story);
|
|
recorder.clear();
|
|
}
|
|
}
|