added basic multiplayer capabilities

This commit is contained in:
Dave White 2003-10-04 22:38:42 +00:00
parent 35f11b1cee
commit 8a8b84b4da
13 changed files with 641 additions and 27 deletions

View file

@ -5,10 +5,10 @@ ifndef CXXFLAGS
CXXFLAGS=-g -O2 -Wall
endif
SDL_CFLAGS=`sdl-config --cflags` `freetype-config --cflags`
SDL_LIBS=`sdl-config --libs` `freetype-config --libs` -lSDL_mixer -lSDL_ttf -lSDL_image
SDL_LIBS=`sdl-config --libs` `freetype-config --libs` -lSDL_mixer -lSDL_ttf -lSDL_image -lSDL_net
LIBS=${SDL_LIBS} -lstdc++
INCLUDES=-I. -Isrc -Isrc/tools -Isrc/widgets
OBJS=src/actions.o src/ai.o src/ai_attack.o src/ai_move.o src/config.o src/dialogs.o src/display.o src/filesystem.o src/font.o src/game.o src/game_config.o src/game_events.o src/gamestatus.o src/hotkeys.o src/intro.o src/key.o src/language.o src/log.o src/map.o src/menu.o src/multiplayer.o src/pathfind.o src/playlevel.o src/playturn.o src/preferences.o src/replay.o src/sdl_utils.o src/sound.o src/team.o src/terrain.o src/tooltips.o src/unit.o src/unit_types.o src/video.o src/widgets/button.o src/widgets/slider.o src/widgets/textbox.o
OBJS=src/actions.o src/ai.o src/ai_attack.o src/ai_move.o src/config.o src/dialogs.o src/display.o src/filesystem.o src/font.o src/game.o src/game_config.o src/game_events.o src/gamestatus.o src/hotkeys.o src/intro.o src/key.o src/language.o src/log.o src/map.o src/menu.o src/multiplayer.o src/network.o src/pathfind.o src/playlevel.o src/playturn.o src/preferences.o src/replay.o src/sdl_utils.o src/sound.o src/team.o src/terrain.o src/tooltips.o src/unit.o src/unit_types.o src/video.o src/widgets/button.o src/widgets/slider.o src/widgets/textbox.o
MAKE_TRANS_OBJS=src/tools/make_translation.o src/config.o src/filesystem.o src/log.o
MERGE_TRANS_OBJS=src/tools/merge_translations.o src/config.o src/filesystem.o src/log.o

View file

@ -124,6 +124,13 @@ start_game="Start Game!"
human_controlled="Human"
ai_controlled="AI"
network_controlled="Network Player"
remote_host="Choose host to connect to"
connection_failed="Could not connect to the remote host"
connection_timeout="Connection timed out"
host_game="Host Multiplayer Game"
join_game="Join Networked Multiplayer Game"
quit_message="Do you really want to quit?"

View file

@ -26,6 +26,7 @@
#include "language.hpp"
#include "menu.hpp"
#include "multiplayer.hpp"
#include "network.hpp"
#include "pathfind.hpp"
#include "playlevel.hpp"
#include "preferences.hpp"
@ -364,8 +365,19 @@ int play_game(int argc, char** argv)
state.campaign_type = "multiplayer";
state.scenario = 0;
std::vector<std::string> host_or_join;
host_or_join.push_back(string_table["host_game"]);
host_or_join.push_back(string_table["join_game"]);
const int res = gui::show_dialog(disp,NULL,"","",gui::MESSAGE,
&host_or_join);
try {
play_multiplayer(disp,units_data,game_config,state);
if(res == 0) {
play_multiplayer(disp,units_data,game_config,state);
} else if(res == 1) {
play_multiplayer_client(disp,units_data,game_config,state);
}
} catch(gamestatus::load_game_failed& e) {
std::cerr << "error loading the game: " << e.message
<< "\n";
@ -374,6 +386,8 @@ int play_game(int argc, char** argv)
std::cerr << "error while playing the game: "
<< e.message << "\n";
return 0;
} catch(network::error& e) {
gui::show_dialog(disp,NULL,"",e.message,gui::OK_ONLY);
}
continue;

View file

@ -10,9 +10,12 @@
See the COPYING file for more details.
*/
#include "language.hpp"
#include "log.hpp"
#include "menu.hpp"
#include "multiplayer.hpp"
#include "network.hpp"
#include "playlevel.hpp"
#include "replay.hpp"
@ -21,6 +24,203 @@
#include <string>
#include <vector>
namespace {
bool accept_network_connections(config& players)
{
log_scope("accept network connections");
typedef std::map<config*,network::connection> positions_map;
positions_map positions;
std::vector<config*>& sides = players.children["side"];
for(std::vector<config*>::const_iterator i = sides.begin();
i != sides.end(); ++i) {
if((*i)->values["controller"] == "network") {
positions[*i] = 0;
}
}
if(positions.empty())
return true;
std::cerr << "waiting for connections...\n";
for(;;) {
network::connection sock = network::accept_connection();
if(sock) {
std::cerr << "Received connection\n";
network::send_data(players,sock);
}
config cfg;
sock = network::receive_data(cfg);
if(sock) {
const int side_taken = atoi(cfg.values["side"].c_str())-1;
if(side_taken >= 0 && side_taken < int(sides.size())) {
positions_map::iterator pos = positions.find(sides[side_taken]);
if(pos != positions.end()) {
if(!pos->second) {
std::cerr << "client has taken a valid position\n";
//broadcast to everyone the new game status
pos->first->values["taken"] = "yes";
positions[sides[side_taken]] = sock;
network::send_data(players);
std::cerr << "sent player data\n";
//send a reply telling the client they have secured
//the side they asked for
std::stringstream side;
side << (side_taken+1);
config reply;
reply.values["side_secured"] = side.str();
std::cerr << "going to send data...\n";
network::send_data(reply,sock);
//see if all positions are now filled
bool unclaimed = false;
for(positions_map::const_iterator p = positions.begin();
p != positions.end(); ++p) {
if(!p->second) {
unclaimed = true;
break;
}
}
if(!unclaimed) {
std::cerr << "starting game now...\n";
config start_game;
start_game.children["start_game"].
push_back(new config());
network::send_data(start_game);
return true;
}
} else {
config response;
response.values["failed"] = "yes";
network::send_data(response,sock);
}
} else {
std::cerr << "tried to take illegal side: " << side_taken
<< "\n";
}
} else {
std::cerr << "tried to take unknown side: " << side_taken
<< "\n";
}
}
std::cerr << "pump\n";
pump_events();
SDL_Delay(50);
}
}
}
void play_multiplayer_client(display& disp, game_data& units_data, config& cfg,
game_state& state)
{
const network::manager net_manager;
std::string 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;
network::connection data_res = network::receive_data(sides,0,3000);
if(!data_res) {
throw network::error(string_table["connection_timeout"]);
}
std::map<int,int> choice_map;
std::vector<std::string> choices;
std::vector<config*>& sides_list = sides.children["side"];
for(std::vector<config*>::iterator s = sides_list.begin();
s != sides_list.end(); ++s) {
if((*s)->values["controller"] == "network" &&
(*s)->values["taken"] != "yes") {
choice_map[choices.size()] = 1 + s - sides_list.begin();
choices.push_back((*s)->values["name"] + " - " +
(*s)->values["type"]);
}
}
const int choice = gui::show_dialog(disp,NULL,"","Choose side:",
gui::OK_CANCEL,&choices);
if(choice < 0) {
return;
}
const int team_num = choice_map[choice];
//send our choice of team to the server
{
config response;
std::stringstream stream;
stream << team_num;
response.values["side"] = stream.str();
network::send_data(response);
}
bool got_side = false;
for(;;) {
config reply;
data_res = network::receive_data(reply,0,100);
if(data_res) {
if(reply.values["failed"] == "yes") {
got_side = false;
break;
} else if(reply.values["side_secured"].size() > 0) {
got_side = true;
} else if(reply.children["start_game"].empty() == false) {
break;
} else {
sides = reply;
}
}
}
if(!got_side) {
throw network::error("Choice of team unavailable.");
}
//we want to make the network/human players look right from our
//perspective
{
std::vector<config*>& sides_list = sides.children["side"];
for(std::vector<config*>::iterator side = sides_list.begin();
side != sides_list.end(); ++side) {
string_map& values = (*side)->values;
if(team_num-1 == side - sides_list.begin())
values["controller"] = "human";
else
values["controller"] = "network";
}
}
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();
}
void play_multiplayer(display& disp, game_data& units_data, config& cfg,
game_state& state)
{
@ -66,10 +266,16 @@ void play_multiplayer(display& disp, game_data& units_data, config& cfg,
sd != sides.end(); ++sd) {
std::stringstream details;
details << (*sd)->values["side"] << ","
<< (*sd)->values["name"] << ","
<< ((*sd)->values["controller"] == "human" ?
string_table["human_controlled"] :
string_table["ai_controlled"]);
<< (*sd)->values["name"] << ",";
const std::string& controller = (*sd)->values["controller"];
if(controller == "human")
details << string_table["human_controlled"];
else if(controller == "network")
details << string_table["network_controlled"];
else
details << string_table["ai_controlled"];
sides_list.push_back(details.str());
}
@ -81,13 +287,21 @@ void play_multiplayer(display& disp, game_data& units_data, config& cfg,
if(size_t(res) < sides.size()) {
std::vector<std::string> choices;
for(int n = 0; n != 2; ++n) {
for(int n = 0; n != 3; ++n) {
for(std::vector<config*>::iterator i = possible_sides.begin();
i != possible_sides.end(); ++i) {
std::stringstream choice;
choice << (*i)->values["name"] << " - "
<< (n == 0 ? string_table["human_controlled"] :
string_table["ai_controlled"]);
choice << (*i)->values["name"] << " - ";
switch(n) {
case 0: choice << string_table["human_controlled"];
break;
case 1: choice << string_table["ai_controlled"];
break;
case 2: choice << string_table["network_controlled"];
break;
default: assert(false);
}
choices.push_back(choice.str());
}
}
@ -96,10 +310,18 @@ void play_multiplayer(display& disp, game_data& units_data, config& cfg,
string_table["choose_side"],
gui::MESSAGE,&choices);
if(result >= 0) {
sides[res]->values["controller"] =
(result >= int(choices.size())/2) ? "ai" : "human";
if(result >= int(choices.size())/2)
result -= choices.size()/2;
std::string controller = "network";
if(result < int(choices.size())/3) {
controller = "human";
} else if(result < int(choices.size()/3)*2) {
controller = "ai";
result -= choices.size()/3;
} else {
controller = "network";
result -= (choices.size()/3)*2;
}
sides[res]->values["controller"] = controller;
assert(result < int(possible_sides.size()));
@ -112,6 +334,13 @@ void play_multiplayer(display& disp, game_data& units_data, config& cfg,
}
}
const network::manager net_manager;
const network::server_manager server_man;
const bool network_state = accept_network_connections(level);
if(network_state == false)
return;
state.starting_pos = level;
recorder.set_save_info(state);

View file

@ -19,6 +19,9 @@
#include "unit_types.hpp"
#include "video.hpp"
void play_multiplayer_client(display& disp, game_data& units_data,
config& cfg, game_state& state);
void play_multiplayer(display& disp, game_data& units_data,
config& cfg, game_state& state);

230
src/network.cpp Normal file
View file

@ -0,0 +1,230 @@
#include "log.hpp"
#include "network.hpp"
#include "SDL_net.h"
#include <algorithm>
#include <vector>
namespace {
SDLNet_SocketSet socket_set = SDLNet_AllocSocketSet(64);
typedef std::vector<network::connection> sockets_list;
sockets_list sockets;
std::map<network::connection,std::string> received_data;
TCPsocket server_socket;
}
namespace network {
manager::manager()
{
if(SDLNet_Init() == -1) {
throw error(SDL_GetError());
}
}
manager::~manager()
{
disconnect();
SDLNet_FreeSocketSet(socket_set);
SDLNet_Quit();
}
server_manager::server_manager(int port, bool create_server)
{
if(create_server) {
server_socket = connect("",port);
std::cerr << "server socket initialized: " << server_socket << "\n";
}
}
server_manager::~server_manager()
{
if(server_socket) {
SDLNet_TCP_Close(server_socket);
server_socket = 0;
}
}
size_t nconnections()
{
return sockets.size();
}
connection connect(const std::string& host, int port)
{
char* const hostname = host.empty() ? NULL:const_cast<char*>(host.c_str());
IPaddress ip;
if(SDLNet_ResolveHost(&ip,hostname,port) == -1) {
throw error(SDLNet_GetError());
}
TCPsocket sock = SDLNet_TCP_Open(&ip);
if(!sock) {
throw error(SDLNet_GetError());
}
//if this is not a server socket, then add it to the list
//of sockets we listen to
if(hostname != NULL) {
const int res = SDLNet_TCP_AddSocket(socket_set,sock);
if(res == -1) {
SDLNet_TCP_Close(sock);
throw network::error("Could not add socket to socket set");
}
assert(sock != server_socket);
sockets.push_back(sock);
}
return sock;
}
connection accept_connection()
{
assert(server_socket);
const connection sock = SDLNet_TCP_Accept(server_socket);
if(sock) {
const int res = SDLNet_TCP_AddSocket(socket_set,sock);
if(res == -1) {
SDLNet_TCP_Close(sock);
throw network::error("Could not add socket to socket set");
}
assert(sock != server_socket);
sockets.push_back(sock);
std::cerr << "new socket: " << sock << "\n";
std::cerr << "server socket: " << server_socket << "\n";
}
return sock;
}
void disconnect(connection s)
{
if(!s) {
while(sockets.empty() == false) {
disconnect(sockets.back());
}
return;
}
received_data.erase(s);
const sockets_list::iterator i = std::find(sockets.begin(),sockets.end(),s);
if(i != sockets.end()) {
sockets.erase(i);
SDLNet_TCP_DelSocket(socket_set,s);
SDLNet_TCP_Close(s);
}
}
connection receive_data(config& cfg, connection connection_num, int timeout)
{
if(sockets.empty()) {
return 0;
}
const int starting_ticks = SDL_GetTicks();
const int res = SDLNet_CheckSockets(socket_set,timeout);
if(res <= 0) {
return 0;
}
for(sockets_list::const_iterator i = sockets.begin();
i != sockets.end(); ++i) {
if(SDLNet_SocketReady(*i)) {
std::string buffer;
for(;;) {
char c;
const int len = SDLNet_TCP_Recv(*i,&c,1);
if(len == 0) {
break;
} else if(len < 0) {
disconnect(*i);
throw error(std::string("error receiving data: ") +
SDLNet_GetError());
}
buffer.resize(buffer.size()+1);
buffer[buffer.size()-1] = c;
if(c == 0) {
break;
}
}
if(buffer == "") {
disconnect(*i);
throw error("error receiving data");
}
std::cerr << "received: " << buffer << "\n";
if(buffer[buffer.size()-1] != 0) {
received_data[*i] += buffer;
const int ticks_taken = SDL_GetTicks() - starting_ticks;
if(ticks_taken < timeout) {
return receive_data(cfg,connection_num,timeout-ticks_taken);
} else {
return 0;
}
} else {
const std::map<connection,std::string>::iterator it =
received_data.find(*i);
if(it != received_data.end()) {
buffer = it->second + buffer;
received_data.erase(it);
}
cfg.read(buffer);
return *i;
}
}
}
assert(false);
return 0;
}
void send_data(config& cfg, connection connection_num)
{
log_scope("sending data");
if(!connection_num) {
std::cerr << "sockets: " << sockets.size() << "\n";
for(sockets_list::const_iterator i = sockets.begin();
i != sockets.end(); ++i) {
std::cerr << "server socket: " << server_socket << "\n";
std::cerr << "current socket: " << *i << "\n";
assert(*i && *i != server_socket);
send_data(cfg,*i);
}
return;
}
std::cerr << "a\n";
assert(connection_num != server_socket);
std::string value = cfg.write();
std::cerr << "sending " << (value.size()+1) << " bytes\n";
const int res = SDLNet_TCP_Send(connection_num,
const_cast<char*>(value.c_str()),
value.size()+1);
if(res < int(value.size()+1)) {
std::cerr << "sending data failed: " << res << "/" << value.size() << ": " << SDL_GetError() << "\n";
disconnect(connection_num);
throw error("Could not send data over socket");
}
}
}

42
src/network.hpp Normal file
View file

@ -0,0 +1,42 @@
#ifndef NETWORK_HPP_INCLUDED
#define NETWORK_HPP_INCLUDED
#include "config.hpp"
#include "SDL_net.h"
#include <string>
namespace network {
struct manager {
manager();
~manager();
};
struct server_manager {
server_manager(int port=15000, bool create_server=true);
~server_manager();
};
typedef TCPsocket connection;
size_t nconnections();
connection connect(const std::string& host, int port=15000);
connection accept_connection();
void disconnect(connection connection_num=0);
connection receive_data(config& cfg, connection connection_num=0, int tout=0);
void send_data(config& cfg, connection connection_num=0);
struct error
{
error(const std::string& msg) : message(msg) {}
std::string message;
};
}
#endif

View file

@ -13,6 +13,7 @@
#include "game_events.hpp"
#include "intro.hpp"
#include "language.hpp"
#include "network.hpp"
#include "playlevel.hpp"
#include "playturn.hpp"
#include "preferences.hpp"
@ -162,22 +163,64 @@ LEVEL_RESULT play_level(game_data& gameinfo, config& terrain_config,
dialogs::show_objectives(gui,*level);
}
play_turn(gameinfo,state_of_game,status,terrain_config,
level, video, key, gui, events_manager, map,
teams, player_number, units);
try {
play_turn(gameinfo,state_of_game,status,terrain_config,
level, video, key, gui, events_manager, map,
teams, player_number, units);
} catch(end_level_exception& e) {
if(network::nconnections() > 0) {
config cfg;
cfg.children["turn"].push_back(
new config(recorder.get_last_turn(1)));
network::send_data(cfg);
}
throw e;
}
if(game_config::debug)
display::clear_debug_highlights();
} else if(!replaying) {
if(network::nconnections() > 0) {
config cfg;
cfg.children["turn"].push_back(
new config(recorder.get_last_turn(2)));
network::send_data(cfg);
}
} else if(!replaying && team_it->is_ai()) {
ai::do_move(gui,map,gameinfo,units,teams,
player_number,status);
if(network::nconnections() > 0) {
config cfg;
cfg.children["turn"].push_back(
new config(recorder.get_last_turn(2)));
network::send_data(cfg);
}
gui.invalidate_unit();
gui.invalidate_game_status();
gui.invalidate_all();
gui.draw();
SDL_Delay(1000);
SDL_Delay(500);
} else if(!replaying && team_it->is_network()) {
config cfg;
for(;;) {
network::connection res=network::receive_data(cfg,0,50);
if(res && cfg.children["turn"].empty() == false) {
break;
}
pump_events();
}
std::cerr << "replay: '" << cfg.children["turn"].front()->write() << "'\n";
replay replay_obj(*cfg.children["turn"].front());
replay_obj.start_replay();
do_replay(gui,map,gameinfo,units,teams,
player_number,status,state_of_game,&replay_obj);
}
for(unit_map::iterator uit = units.begin();

View file

@ -181,6 +181,32 @@ void replay::end_turn()
cmd->children["end_turn"].push_back(new config());
}
config replay::get_last_turn(int num_turns)
{
log_scope("get_last_turn\n");
//back up from the end, finding the position we're looking for
std::vector<config*>& cmd = commands();
std::vector<config*>::reverse_iterator i;
for(i = cmd.rbegin(); i != cmd.rend() && num_turns > 0; ++i) {
if((*i)->children["end_turn"].size() > 0) {
--num_turns;
}
}
//if we reached an end turn, we have to move one step forward so
//that we don't include the end turn
const int adjust = num_turns == 0 ? 1 : 0;
config res;
for(std::vector<config*>::const_iterator fi = i.base()+adjust;
fi != cmd.end(); ++fi) {
res.children["command"].push_back(new config(**fi));
}
return res;
}
void replay::undo()
{
std::vector<config*>& cmd = commands();
@ -263,8 +289,10 @@ bool replay::empty()
bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo,
std::map<gamemap::location,unit>& units,
std::vector<team>& teams, int team_num, const gamestatus& state,
game_state& state_of_game)
game_state& state_of_game, replay* obj)
{
replay& recorder = obj != NULL ? *obj : recorder;
update_locker lock_update(disp,recorder.skipping());
//a list of units that have promoted from the last attack

View file

@ -42,6 +42,8 @@ public:
void choose_option(int index);
void end_turn();
config get_last_turn(int num_turns=1);
void undo();
int get_random();
@ -80,6 +82,6 @@ extern replay recorder;
bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo,
std::map<gamemap::location,unit>& units,
std::vector<team>& teams, int team_num, const gamestatus& state,
game_state& state_of_game);
game_state& state_of_game, replay* obj=NULL);
#endif

View file

@ -43,9 +43,11 @@ team::team_info::team_info(config& cfg)
}
if(cfg.values["controller"] == "human")
human = true;
controller = HUMAN;
else if(cfg.values["controller"] == "network")
controller = NETWORK;
else
human = false;
controller = AI;
const std::string& scouts_val = cfg.values["villages_per_scout"];
if(scouts_val.empty()) {
@ -171,7 +173,17 @@ double team::aggression() const
bool team::is_human() const
{
return info_.human;
return info_.controller == team_info::HUMAN;
}
bool team::is_ai() const
{
return info_.controller == team_info::AI;
}
bool team::is_network() const
{
return info_.controller == team_info::NETWORK;
}
double team::leader_value() const

View file

@ -40,7 +40,9 @@ public:
std::vector<std::string> recruitment_pattern;
double aggression;
std::vector<int> enemies;
bool human;
enum CONTROLLER { HUMAN, AI, NETWORK };
CONTROLLER controller;
int villages_per_scout;
double leader_value, village_value;
@ -69,6 +71,8 @@ public:
double aggression() const;
bool is_human() const;
bool is_network() const;
bool is_ai() const;
double leader_value() const;
double village_value() const;

View file

@ -29,7 +29,7 @@ struct tooltip
std::string message;
};
static const int font_size = 12;
static const int font_size = 14;
std::vector<tooltip> tips;