made it so quitting from a networked multiplayer game on a server...

...takes the player back to the lobby
This commit is contained in:
Dave White 2003-10-27 05:24:13 +00:00
parent d89efc6a73
commit df23cfa795
16 changed files with 263 additions and 302 deletions

View File

@ -167,7 +167,7 @@
[multiplayer_side]
name=Orcs
type=Orcish Warlord
recruit=Orcish Grunt,Troll Whelp,Wolf Rider,Orcish Archer
recruit=Orcish Grunt,Troll Whelp,Wolf Rider,Orcish Archer,Orcish Assassin
recruitment_pattern=fighter,fighter,archer,scout
[/multiplayer_side]
@ -181,6 +181,6 @@
[multiplayer_side]
name=Undead
type=Lich
recruit=Skeleton,Skeleton Archer,Walking Corpse,Ghost,Blood Bat,Dark Adept
recruit=Skeleton,Skeleton Archer,Walking Corpse,Ghost,Vampire Bat,Dark Adept
recruitment_pattern=scout,fighter,fighter,archer
[/multiplayer_side]

View File

@ -1,176 +1,3 @@
[test]
name=Isles Of The Damned
map=jzmap
turns=32
id=test
objectives="
Victory:
@Defeat both enemy leaders
@Resist until the end of the turns
Defeat:
#Death of Konrad
#Death of Habwa"
{DAWN}
{MORNING}
{AFTERNOON}
{DUSK}
{FIRST_WATCH}
{SECOND_WATCH}
[side]
type=Commander
description=Konrad
side=1
canrecruit=1
controller=human
gold=350
recruit=Elvish Fighter,Elvish Archer,Horseman,Mage,Elvish Shaman,Merman
enemy=2
[/side]
[side]
type=Lich
description=Urug-Telfar
side=2
canrecruit=1
recruit=Skeleton,Chocobone,Bone Shooter,Dark Adept,Mermen
recruitment_pattern=fighter,scout,fighter,fighter,scout
gold=200
enemy=1
[/side]
[side]
type=Lich
description=Ga'hal
side=3
canrecruit=1
recruit=Skeleton
recruitment_pattern=fighter
gold=50
enemy=1
[/side]
[item]
x=24
y=32
image=item-holywater.png
[/item]
[item]
x=28
y=2
image=merman-king.png
[/item]
[item]
x=28
y=2
image=misc/cage.png
[/item]
[event]
name=moveto
first_time_only=no
[filter]
side=1
x=24
y=32
[/filter]
[object]
id=object7_holywater
name=Holy Water
image=item-holywater.png
duration=level
description=This water will make close range weapons holy.
cannot_use_message=I am not suited to using this item! Let another take it.
[effect]
apply_to=attack
range=short
set_type=holy
[/effect]
[/object]
[/event]
[event]
name=moveto
[removeitem]
[/removeitem]
[filter]
side=1
x=28
y=2
[/filter]
[unit]
description=Habwa
side=1
type=Merman Lord
x=28
y=3
[/unit]
[unit]
side=1
type=Merman
x=28
y=1
[/unit]
[unit]
side=1
type=Merman
x=29
y=2
[/unit]
[message]
id=msgIOFD_1
description=Habwa
message="Thank you for freeing me. I have been here for so long, ever sence the Lichen took over my beutiful islands."
[/message]
[message]
id=msgIOFD_2
description=Konrad
message="Together we can reclaim your islands."
[/message]
[message]
id=msgIOFD_3
description=Habwa
message="If succesfull your h
elp will not go unrecognised."
[/message]
[/event]
[event]
name=time over
[message]
id=msgIOFD_4
description=Habwa
message="They are to strong for us, we must retreat."
[/message]
[message]
id=msgIOFD_5
description=Konrad
message="I fear you are right. Good luck in your future journies Habwa."
[/message]
[message]
id=msgIOFD_6
description=Habwa
message="And to you Konrad, may your travels be free from danger."
[/message]
[endlevel]
result=victory
[/endlevel]
[/event]
[event]
name=victory
[message]
id=msgIOFD_7
description=Habwa
message="They were strong, but I have my kingdom once again."
[/message]
[message]
id=msgIOFD_8
description=Konrad
message="I am gald to have been of service to you, Lord Habwa."
[/message]
[message]
id=msgIOFD_9
description=Habwa
message="As I had told you, your help has not gone unrecognized."
[/message]
# Konrad should get ring of regeneration
[/event]
[/test]
[a]
[test]
name=Scenario 1: The Elves Besieged
map=map-test
@ -205,4 +32,3 @@ elp will not go unrecognised."
enemy=1
[/side]
[/test]
[/a]

View File

@ -20,6 +20,9 @@ by their skill with both sword and bow.
The Elves are deft of foot, and fight best
in the forest."
get_hit_sound=groan.wav
[attack]
name=sword
type=blade

View File

@ -21,13 +21,11 @@ unit_description="The orcish crossbow tries to compensate his lack of skill with
time=-100
sound=firearrow.wav
[/sound]
[sound]
time=0
sound=arrow-hit.wav
sound_miss=arrow-miss.wav
[/sound]
[missile_frame]
begin=-100
end=0

View File

@ -1739,6 +1739,10 @@ bool display::unit_attack_ranged(const gamemap::location& a,
const std::vector<attack_type::sfx>& sounds = attack.sound_effects();
std::vector<attack_type::sfx>::const_iterator sfx_it = sounds.begin();
const std::string& hit_sound = def->second.type().get_hit_sound();
bool played_hit_sound = (hit_sound == "" || hit_sound == "null");
const int play_hit_sound_at = 0;
const bool hits = damage > 0;
const int begin_at = attack.get_first_frame();
const int end_at = maximum((damage+1)*time_resolution+missile_impact,
@ -1778,6 +1782,11 @@ bool display::unit_attack_ranged(const gamemap::location& a,
++sfx_it;
}
if(hits && !played_hit_sound && i >= play_hit_sound_at) {
sound::play_sound(hit_sound);
played_hit_sound = true;
}
const std::string* unit_image = attack.get_frame(i);
if(unit_image == NULL) {
@ -1869,6 +1878,14 @@ void display::unit_die(const gamemap::location& loc, SDL_Surface* image)
if(update_locked() || shrouded(loc.x,loc.y))
return;
const unit_map::const_iterator u = units_.find(loc);
assert(u != units_.end());
const std::string& die_sound = u->second.type().die_sound();
if(die_sound != "" && die_sound != "null") {
sound::play_sound(die_sound);
}
const int frame_time = 30;
int ticks = SDL_GetTicks();
@ -1953,6 +1970,10 @@ bool display::unit_attack(const gamemap::location& a,
const std::vector<attack_type::sfx>& sounds = attack.sound_effects();
std::vector<attack_type::sfx>::const_iterator sfx_it = sounds.begin();
const std::string& hit_sound = def->second.type().get_hit_sound();
bool played_hit_sound = (hit_sound == "" || hit_sound == "null");
const int play_hit_sound_at = 0;
const int time_resolution = 20;
const int acceleration = turbo() ? 5 : 1;
@ -1994,6 +2015,11 @@ bool display::unit_attack(const gamemap::location& a,
++sfx_it;
}
if(hits && !played_hit_sound && i >= play_hit_sound_at) {
sound::play_sound(hit_sound);
played_hit_sound = true;
}
for(int j = 0; j != 6; ++j) {
draw_tile(update_tiles[j].x,update_tiles[j].y);
}

View File

@ -26,6 +26,15 @@
#include <string>
#include <vector>
network_game_manager::~network_game_manager()
{
if(network::nconnections() > 0) {
config cfg;
cfg.add_child("leave_game");
network::send_data(cfg);
}
}
namespace {
class connection_acceptor : public gui::dialog_action
@ -232,6 +241,9 @@ void play_multiplayer(display& disp, game_data& units_data, config cfg,
{
log_scope("play multiplayer");
//ensure we send a close game message to the server when we are done
const network_game_manager game_manager = network_game_manager();
std::vector<std::string> options;
std::vector<config*>& levels = cfg.children["multiplayer"];
std::map<int,std::string> res_to_id;

View File

@ -19,6 +19,10 @@
#include "unit_types.hpp"
#include "video.hpp"
struct network_game_manager {
~network_game_manager();
};
void play_multiplayer_client(display& disp, game_data& units_data,
config& cfg, game_state& state);

View File

@ -99,6 +99,18 @@ void check_response(network::connection res, const config& data)
}
}
void receive_gamelist(config& data)
{
for(;;) {
const network::connection res = network::receive_data(data,0,3000);
check_response(res,data);
if(data.child("gamelist")) {
break;
}
}
}
class wait_for_start : public gui::dialog_action
{
public:
@ -167,6 +179,8 @@ void play_multiplayer_client(display& disp, game_data& units_data, config& cfg,
+ "' while you are using version'" + game_config::version);
}
bool logged_in = false;
//if we got a direction to login
if(data.child("mustlogin")) {
@ -196,133 +210,145 @@ void play_multiplayer_client(display& disp, game_data& units_data, config& cfg,
error = data.child("error");
} while(error != NULL);
logged_in = true;
}
//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;
const lobby::RESULT res = lobby::enter(disp,game_data);
switch(res) {
case lobby::QUIT: {
return;
}
case lobby::CREATE: {
std::cerr << "playing multiplayer...\n";
play_multiplayer(disp,units_data,cfg,state,false);
return;
}
case lobby::JOIN: {
break;
}
for(bool first_time = true; logged_in && network::nconnections() > 0;
first_time = false) {
if(!first_time) {
receive_gamelist(data);
}
for(;;) {
data_res = network::receive_data(sides,0,5000);
check_response(data_res,data);
//if we have got valid side data
if(sides.child("gamelist") == NULL) {
break;
//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;
const lobby::RESULT res = lobby::enter(disp,game_data);
switch(res) {
case lobby::QUIT: {
return;
}
case lobby::CREATE: {
play_multiplayer(disp,units_data,cfg,state,false);
continue;
}
case lobby::JOIN: {
break;
}
}
for(;;) {
data_res = network::receive_data(sides,0,5000);
check_response(data_res,data);
//if we have got valid side data
if(sides.child("gamelist") == NULL) {
break;
}
}
} else {
sides = data;
}
} else {
sides = data;
}
std::map<int,int> choice_map;
std::vector<std::string> choices;
choices.push_back(string_table["observer"]);
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];
const bool observer = choice == 0;
//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();
network::send_data(response);
}
wait_for_start waiter(sides);
const int dialog_res = gui::show_dialog(disp,NULL,"",
"Waiting for game to start...",
gui::CANCEL_ONLY,NULL,NULL,"",NULL,&waiter);
if(dialog_res != wait_for_start::START_GAME) {
return;
}
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
{
//ensure we send a close game message to the server when we are done
const network_game_manager game_manager = network_game_manager();
std::map<int,int> choice_map;
std::vector<std::string> choices;
choices.push_back(string_table["observer"]);
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";
}
}
//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);
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"]);
}
}
sides.children["replay"].clear();
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;
//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();
network::send_data(response);
}
wait_for_start waiter(sides);
const int dialog_res = gui::show_dialog(disp,NULL,"",
"Waiting for game to start...",
gui::CANCEL_ONLY,NULL,NULL,"",NULL,&waiter);
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
{
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";
}
}
//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.children["replay"].clear();
}
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();
}
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();
}

View File

@ -41,10 +41,12 @@ manager::manager() : free_(true)
manager::~manager()
{
disconnect();
SDLNet_FreeSocketSet(socket_set);
socket_set = 0;
SDLNet_Quit();
if(free_) {
disconnect();
SDLNet_FreeSocketSet(socket_set);
socket_set = 0;
SDLNet_Quit();
}
}
server_manager::server_manager(int port, bool create_server)

View File

@ -212,6 +212,10 @@ LEVEL_RESULT play_level(game_data& gameinfo, config& terrain_config,
for(;;) {
network::connection res =
network::receive_data(cfg);
if(res && cfg.child("leave_game")) {
throw network::error("");
}
if(res && cfg.children["turn"].empty() == false) {
break;
}
@ -352,6 +356,10 @@ LEVEL_RESULT play_level(game_data& gameinfo, config& terrain_config,
return QUIT;
}
catch(network::error& e) {
if(e.socket) {
e.disconnect();
}
std::string label = string_table["multiplayer_save_name"];
const int res = gui::show_dialog(gui,NULL,"",
string_table["save_game_error"],

View File

@ -132,3 +132,9 @@ config* game::description()
{
return description_;
}
void game::add_players(const game& other_game)
{
players_.insert(players_.end(),
other_game.players_.begin(),other_game.players_.end());
}

View File

@ -38,6 +38,8 @@ public:
void set_description(config* desc);
config* description();
void add_players(const game& other_game);
private:
static int id_num;
int id_;

View File

@ -217,8 +217,44 @@ int main()
}
if(data.child("start_game")) {
std::cerr << "!! start_game: " << data.write() << "\n";
g->start_game();
} else if(data.child("leave_game")) {
const bool needed = g->is_needed(sock);
g->remove_player(sock);
lobby_players.add_player(sock);
if(needed) {
//tell all other players the game is over,
//because a needed player has left
config cfg;
cfg.add_child("leave_game");
g->send_data(cfg);
//delete the game's description
config* const gamelist =
initial_response.child("gamelist");
assert(gamelist != NULL);
std::vector<config*>& vg =
gamelist->children["game"];
std::vector<config*>::iterator desc =
std::find(vg.begin(),vg.end(),g->description());
if(desc != vg.end()) {
delete *desc;
vg.erase(desc);
}
//put the players back in the lobby and send
//them the game list and user list again
g->send_data(initial_response);
lobby_players.add_players(*g);
games.erase(g);
}
//send the player who has quit the new game list
network::send_data(initial_response,sock);
continue;
} else if(data["side_secured"].empty() == false) {
continue;
} else if(data["failed"].empty() == false) {

View File

@ -40,7 +40,7 @@ manager::manager()
Mix_OpenAudio(MIX_DEFAULT_FREQUENCY,MIX_DEFAULT_FORMAT,2,1024);
if(res >= 0) {
mix_ok = true;
Mix_AllocateChannels(8);
Mix_AllocateChannels(16);
} else {
mix_ok = false;
std::cerr << "Could not initialize audio: " << SDL_GetError() << "\n";

View File

@ -456,6 +456,16 @@ const std::string& unit_type::unit_description() const
return desc;
}
const std::string& unit_type::get_hit_sound() const
{
return cfg_["get_hit_sound"];
}
const std::string& unit_type::die_sound() const
{
return cfg_["die_sound"];
}
int unit_type::hitpoints() const
{
return atoi(cfg_["hitpoints"].c_str());

View File

@ -132,6 +132,8 @@ public:
const std::string& image_fighting(attack_type::RANGE range) const;
const std::string& image_defensive(attack_type::RANGE range) const;
const std::string& unit_description() const;
const std::string& get_hit_sound() const;
const std::string& die_sound() const;
int hitpoints() const;
std::vector<attack_type> attacks() const;
const unit_movement_type& movement_type() const;