Merge pull request #106 from cbeck88/idle_controller

Idle controller
This commit is contained in:
cbeck88 2014-02-21 21:49:57 -05:00
commit 37b0b8dd69
10 changed files with 159 additions and 5 deletions

View file

@ -78,6 +78,12 @@ Version 1.11.9+dev:
to work correctly in subsequent scenarios of an mp campaign.
* Partial fix for bug #21405: The abort option presented when a player
disconnects from a networked game is now a "save and abort" option.
* Partial fix for bug #21405: Sides may now be set in an "idle" state when
a player disconnects from a network game. This does not give any player
control or vision. To proceed with the game, the host must reassign the
controller using :control, :droid, or :give_control as usual.
Related to this, there are new commands :controller which query the
controller status, and :idle which toggles the idle status.
Version 1.11.9:
* Add-ons client:

View file

@ -177,7 +177,7 @@ public:
for(std::vector<team>::const_iterator it = resources::teams->begin();
it != resources::teams->end();
++it) {
if(!it->is_ai() && !it->is_empty() && !it->current_player().empty())
if(!it->is_ai() && !it->is_idle() && !it->is_empty() && !it->current_player().empty())
nicks.insert(it->current_player());
}

View file

@ -1962,8 +1962,10 @@ class console_handler : public map_command_handler<console_handler>, private cha
void do_refresh();
void do_droid();
void do_idle();
void do_theme();
void do_control();
void do_controller();
void do_clear();
void do_sunset();
void do_foreground();
@ -2041,9 +2043,13 @@ class console_handler : public map_command_handler<console_handler>, private cha
_("Refresh gui."));
register_command("droid", &console_handler::do_droid,
_("Switch a side to/from AI control."), _("do not translate the on/off^[<side> [on/off]]"));
register_command("idle", &console_handler::do_idle,
_("Switch a side to/from idle state."), _("do not translate the on/off^[<side> [on/off]]"));
register_command("theme", &console_handler::do_theme);
register_command("control", &console_handler::do_control,
_("Assign control of a side to a different player or observer."), _("<side> <nickname>"), "N");
register_command("controller", &console_handler::do_controller,
_("Query the controller status of a side."), _("<side>"));
register_command("clear", &console_handler::do_clear,
_("Clear chat history."));
register_command("sunset", &console_handler::do_sunset,
@ -2659,7 +2665,7 @@ void console_handler::do_droid() {
symbols["side"] = lexical_cast<std::string>(side);
command_failed(vgettext("Can't droid networked side: '$side'.", symbols));
return;
} else if (menu_handler_.teams_[side - 1].is_human() && action != " off") {
} else if ((menu_handler_.teams_[side - 1].is_human() || menu_handler_.teams_[side - 1].is_idle()) && action != " off") {
//this is our side, so give it to AI
menu_handler_.teams_[side - 1].make_human_ai();
menu_handler_.change_controller(lexical_cast<std::string>(side),"human_ai");
@ -2675,6 +2681,49 @@ void console_handler::do_droid() {
menu_handler_.textbox_info_.close(*menu_handler_.gui_);
}
void console_handler::do_idle() {
// :idle [<side> [on/off]]
const std::string side_s = get_arg(1);
const std::string action = get_arg(2);
// default to the current side if empty
const unsigned int side = side_s.empty() ?
team_num_ : lexical_cast_default<unsigned int>(side_s);
if (side < 1 || side > menu_handler_.teams_.size()) {
utils::string_map symbols;
symbols["side"] = side_s;
command_failed(vgettext("Can't idle invalid side: '$side'.", symbols));
return;
} else if (menu_handler_.teams_[side - 1].is_network()) {
utils::string_map symbols;
symbols["side"] = lexical_cast<std::string>(side);
command_failed(vgettext("Can't droid networked side: '$side'.", symbols));
return;
} else if (menu_handler_.teams_[side - 1].is_human() && action != " off") {
//this is our side, so give it to idle
menu_handler_.teams_[side - 1].make_idle();
menu_handler_.change_controller(lexical_cast<std::string>(side),"idle");
if(team_num_ == side) {
//if it is our turn at the moment, we have to indicate to the
//play_controller, that we are no longer in control
throw end_turn_exception(side);
}
} else if (menu_handler_.teams_[side - 1].is_ai() && action != " off") {
//this is our side, so give it to idle
menu_handler_.teams_[side - 1].make_human_ai();
menu_handler_.change_controller(lexical_cast<std::string>(side),"human_ai");
} else if (menu_handler_.teams_[side - 1].is_idle() && action != " on") {
menu_handler_.teams_[side - 1].make_human();
menu_handler_.change_controller(lexical_cast<std::string>(side),"human");
if(team_num_ == side) {
//if it is our turn at the moment, we have to indicate to the
//play_controller, that idle should no longer be in control
throw end_turn_exception(side);
}
}
menu_handler_.textbox_info_.close(*menu_handler_.gui_);
}
void console_handler::do_theme() {
preferences::show_theme_dialog(*menu_handler_.gui_);
}
@ -2707,6 +2756,27 @@ void console_handler::do_control() {
menu_handler_.request_control_change(side_num,player);
menu_handler_.textbox_info_.close(*(menu_handler_.gui_));
}
void console_handler::do_controller()
{
const std::string side = get_arg(1);
unsigned int side_num;
try {
side_num = lexical_cast<unsigned int>(side);
} catch(bad_lexical_cast&) {
utils::string_map symbols;
symbols["side"] = side;
command_failed(vgettext("Can't query control of invalid side: '$side'.", symbols));
return;
}
if (side_num < 1 || side_num > menu_handler_.teams_.size()) {
utils::string_map symbols;
symbols["side"] = side;
command_failed(vgettext("Can't query control of out-of-bounds side: '$side'.", symbols));
return;
}
print(get_cmd(), menu_handler_.teams_[side_num - 1].controller_string());
}
void console_handler::do_clear() {
menu_handler_.gui_->clear_chat_messages();
}
@ -3202,6 +3272,18 @@ void menu_handler::request_control_change ( int side_num, const std::string& pla
if (player == preferences::login())
return;
change_side_controller(side,player);
} else if (teams_[side_num - 1].is_idle()) { //if this is our side and it is idle, make human and throw an end turn exception
LOG_NG << " *** Got an idle side with requested control change " << std::endl;
teams_[side_num - 1].make_human();
change_controller(lexical_cast<std::string>(side_num),"human");
if (player == preferences::login()) {
LOG_NG << " *** It's us, throwing end turn exception " << std::endl;
} else {
LOG_NG << " *** It's not us, changing sides now as usual, then throwing end_turn " << std::endl;
change_side_controller(side,player);
}
throw end_turn_exception(side_num);
} else {
//it is not our side, the server will decide if we can change the
//controller (that is if we are host of the game)

View file

@ -549,3 +549,10 @@ bool playmp_controller::can_execute_command(const hotkey::hotkey_command& cmd, i
}
return res;
}
void playmp_controller::do_idle_notification()
{
resources::screen->add_chat_message(time(NULL), "Wesnoth", 0,
"This side is in an idle state. To proceed with the game, the host must assign it to another controller.",
events::chat_handler::MESSAGE_PUBLIC, false);
}

View file

@ -58,6 +58,7 @@ protected:
virtual void after_human_turn();
virtual void finish_side_turn();
virtual void play_network_turn();
virtual void do_idle_notification();
void init_turn_data();
turn_info* turn_data_;

View file

@ -629,6 +629,13 @@ void playsingle_controller::play_turn(bool save)
check_time_over();
}
void playsingle_controller::play_idle_loop()
{
play_slice();
gui_->draw();
SDL_Delay(10);
}
void playsingle_controller::play_side(const unsigned int side_number, bool save)
{
//check for team-specific items in the scenario
@ -681,6 +688,29 @@ void playsingle_controller::play_side(const unsigned int side_number, bool save)
} else if(current_team().is_network()) {
play_network_turn();
} else if(current_team().is_idle()) {
try{
end_turn_enable(false);
do_idle_notification();
before_human_turn(save);
while(1) {
play_idle_loop();
}
} catch(end_turn_exception& end_turn) {
LOG_NG << "Escaped from idle state with exception!" << std::endl;
if (end_turn.redo == side_number) {
player_type_changed_ = true;
// If new controller is not human,
// reset gui to prev human one
if (!teams_[side_number-1].is_human()) {
browse_ = true;
int s = find_human_team_before(side_number);
if (s <= 0)
s = gui_->playing_side();
update_gui_to_player(s-1);
}
}
}
}
// Else current_team().is_empty(), so do nothing.
@ -884,6 +914,16 @@ void playsingle_controller::play_ai_turn(){
}
/**
* Will handle sending a networked notification in descendent classes.
*/
void playsingle_controller::do_idle_notification()
{
resources::screen->add_chat_message(time(NULL), "Wesnoth", 0,
"This side is in an idle state. To proceed with the game, the host must assign it to another controller.",
events::chat_handler::MESSAGE_PUBLIC, false);
}
/**
* Will handle networked turns in descendent classes.
*/

View file

@ -82,6 +82,8 @@ protected:
void end_turn_enable(bool enable);
virtual hotkey::ACTION_STATE get_action_state(hotkey::HOTKEY_COMMAND command, int index) const;
void play_ai_turn();
void play_idle_loop();
virtual void do_idle_notification();
virtual void play_network_turn();
virtual void init_gui();
void check_time_over();

View file

@ -243,6 +243,7 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
}
int action = 0;
int first_observer_option_idx = 0;
std::vector<std::string> observers;
std::vector<team*> allies;
@ -253,8 +254,11 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
utils::string_map t_vars;
options.push_back(_("Replace with AI"));
options.push_back(_("Replace with local player"));
options.push_back(_("Set side to idle"));
options.push_back(_("Save and Abort game"));
first_observer_option_idx = options.size();
//get all observers in as options to transfer control
BOOST_FOREACH(const std::string &ob, resources::screen->observers())
{
@ -309,12 +313,19 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
case 2:
tm.make_idle();
tm.set_current_player("idle" + side_drop);
if (have_leader) leader->rename("idle" + side_drop);
return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
case 3:
//The user pressed "end game". Don't throw a network error here or he will get
//thrown back to the title screen.
do_save();
throw end_level_exception(QUIT);
default:
if (action > 2) {
if (action > 3) {
{
// Server thinks this side is ours now so in case of error transferring side we have to make local state to same as what server thinks it is.
@ -323,7 +334,7 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
if (have_leader) leader->rename("human"+side_drop);
}
const size_t index = static_cast<size_t>(action - 3);
const size_t index = static_cast<size_t>(action - first_observer_option_idx);
if (index < observers.size()) {
change_side_controller(side_drop, observers[index]);
} else if (index < options.size() - 1) {

View file

@ -227,6 +227,7 @@ char const *team::team_info::controller_string() const
case HUMAN_AI: return "human_ai";
case NETWORK: return "network";
case NETWORK_AI: return "network_ai";
case IDLE: return "idle";
case EMPTY: return "null";
default: assert(false); return NULL;
}
@ -498,6 +499,8 @@ void team::change_controller(const std::string& controller)
cid = team::NETWORK_AI;
else if (controller == "null")
cid = team::EMPTY;
else if (controller == "idle")
cid = team::IDLE;
else
cid = team::AI;

View file

@ -32,7 +32,7 @@ namespace wb {
class team : public savegame::savegame_config
{
public:
enum CONTROLLER { HUMAN, HUMAN_AI, AI, NETWORK, NETWORK_AI, EMPTY };
enum CONTROLLER { HUMAN, HUMAN_AI, AI, NETWORK, NETWORK_AI, IDLE, EMPTY };
private:
class shroud_map {
@ -210,6 +210,7 @@ public:
bool is_network_human() const { return info_.controller == NETWORK; }
bool is_network_ai() const { return info_.controller == NETWORK_AI; }
bool is_ai() const { return info_.controller == AI || is_human_ai(); }
bool is_idle() const { return info_.controller == IDLE; }
bool is_empty() const { return info_.controller == EMPTY; }
bool is_local() const { return is_human() || is_ai(); }
@ -220,6 +221,7 @@ public:
void make_network() { info_.controller = NETWORK; }
void make_network_ai() { info_.controller = NETWORK_AI; }
void make_ai() { info_.controller = AI; }
void make_idle() { info_.controller = IDLE; }
void change_controller(const std::string& controller);
void change_controller(CONTROLLER controller) { info_.controller = controller; }