Tell other clients about controller type changes from :droid

If one client changes the controller type of a side it controls from human -> AI or vice versa, the other clients need to know about it in case there's WML/lua that uses the controller type to determine what to do. This also means that droiding a side you own can now only be done when it's your turn.

Fixes #4996
This commit is contained in:
Pentarctagon 2020-12-10 13:29:41 -06:00 committed by Pentarctagon
parent 77c60a11f4
commit 204357c6d2
6 changed files with 94 additions and 26 deletions

View file

@ -214,11 +214,20 @@ void game_board::side_drop_to(int side_num, team::CONTROLLER ctrl, team::PROXY_C
if (leader.valid()) leader->rename(ctrl.to_string() + std::to_string(side_num));
}
void game_board::side_change_controller(int side_num, bool is_local, const std::string& pname) {
void game_board::side_change_controller(int side_num, bool is_local, const std::string& pname, const std::string& controller_type) {
team &tm = get_team(side_num);
tm.set_local(is_local);
// only changing the type of controller
if(controller_type == team::CONTROLLER::enum_to_string(team::CONTROLLER::AI) && !tm.is_ai()) {
tm.make_ai();
return;
} else if(controller_type == team::CONTROLLER::enum_to_string(team::CONTROLLER::HUMAN) && !tm.is_human()) {
tm.make_human();
return;
}
if (pname.empty() || !tm.is_human()) {
return;
}

View file

@ -155,7 +155,7 @@ public:
// Manipulator from playturn
void side_drop_to (int side_num, team::CONTROLLER ctrl, team::PROXY_CONTROLLER proxy = team::PROXY_CONTROLLER::PROXY_HUMAN);
void side_change_controller (int side_num, bool is_local, const std::string& pname = "");
void side_change_controller (int side_num, bool is_local, const std::string& pname, const std::string& controller_type);
// Manipulator from actionwml

View file

@ -1485,6 +1485,7 @@ void console_handler::do_droid()
std::transform(action.begin(), action.end(), action.begin(), tolower);
// default to the current side if empty
const unsigned int side = side_s.empty() ? team_num_ : lexical_cast_default<unsigned int>(side_s);
const bool is_your_turn = resources::controller->current_side() == static_cast<int>(display::get_singleton()->viewing_side());
utils::string_map symbols;
symbols["side"] = std::to_string(side);
@ -1498,43 +1499,81 @@ void console_handler::do_droid()
} else if(menu_handler_.board().get_team(side).is_local()) {
bool changed = false;
const bool is_human = menu_handler_.board().get_team(side).is_human();
const bool is_droid = menu_handler_.board().get_team(side).is_droid();
const bool is_proxy_human = menu_handler_.board().get_team(side).is_proxy_human();
const bool is_ai = menu_handler_.board().get_team(side).is_ai();
const std::string human = team::CONTROLLER::enum_to_string(team::CONTROLLER::HUMAN);
const std::string ai = team::CONTROLLER::enum_to_string(team::CONTROLLER::AI);
if(action == "on") {
if(!menu_handler_.board().get_team(side).is_human() || !menu_handler_.board().get_team(side).is_droid()) {
if(is_ai && !is_your_turn) {
command_failed(_("It is not allowed to change a side from AI to human control when it's not your turn."));
return;
}
if(!is_human || !is_droid) {
menu_handler_.board().get_team(side).make_human();
menu_handler_.board().get_team(side).make_droid();
changed = true;
if(is_ai) {
menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", preferences::login(), "to", human}});
}
print(get_cmd(), VGETTEXT("Side '$side' controller is now controlled by: AI.", symbols));
} else {
print(get_cmd(), VGETTEXT("Side '$side' is already droided.", symbols));
}
} else if(action == "off") {
if(!menu_handler_.board().get_team(side).is_human() || !menu_handler_.board().get_team(side).is_proxy_human()) {
if(is_ai && !is_your_turn) {
command_failed(_("It is not allowed to change a side from AI to human control when it's not your turn."));
return;
}
if(!is_human || !is_proxy_human) {
menu_handler_.board().get_team(side).make_human();
menu_handler_.board().get_team(side).make_proxy_human();
changed = true;
if(is_ai) {
menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", preferences::login(), "to", human}});
}
print(get_cmd(), VGETTEXT("Side '$side' controller is now controlled by: human.", symbols));
} else {
print(get_cmd(), VGETTEXT("Side '$side' is already not droided.", symbols));
}
} else if(action == "full") {
if(!menu_handler_.board().get_team(side).is_ai() || !menu_handler_.board().get_team(side).is_droid()) {
if(!is_your_turn) {
command_failed(_("It is not allowed to change a side from human to AI control when it's not your turn."));
return;
}
if(!is_ai || !is_droid) {
menu_handler_.board().get_team(side).make_ai();
menu_handler_.board().get_team(side).make_droid();
changed = true;
if(is_human || is_proxy_human) {
menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", preferences::login(), "to", ai}});
}
print(get_cmd(), VGETTEXT("Side '$side' controller is now fully controlled by: AI.", symbols));
} else {
print(get_cmd(), VGETTEXT("Side '$side' is already fully AI controlled.", symbols));
}
} else if(action == "") {
if(menu_handler_.board().get_team(side).is_ai() || menu_handler_.board().get_team(side).is_droid()) {
if(is_ai && !is_your_turn) {
command_failed(_("It is not allowed to change a side from AI to human control when it's not your turn."));
return;
}
if(is_ai || is_droid) {
menu_handler_.board().get_team(side).make_human();
menu_handler_.board().get_team(side).make_proxy_human();
changed = true;
if(is_ai) {
menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", preferences::login(), "to", human}});
}
print(get_cmd(), VGETTEXT("Side '$side' controller is now controlled by: human.", symbols));
} else {
menu_handler_.board().get_team(side).make_human();
menu_handler_.board().get_team(side).make_droid();
changed = true;
if(is_ai) {
menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", preferences::login(), "to", human}});
}
print(get_cmd(), VGETTEXT("Side '$side' controller is now controlled by: AI.", symbols));
}
} else {

View file

@ -178,6 +178,7 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
const int side = change["side"].to_int();
const bool is_local = change["is_local"].to_bool();
const std::string player = change["player"];
const std::string controller_type = change["controller"];
const std::size_t index = side - 1;
if(index >= resources::gameboard->teams().size()) {
ERR_NW << "Bad [change_controller] signal from server, side out of bounds: " << change.debug() << std::endl;
@ -187,7 +188,7 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
const team & tm = resources::gameboard->teams().at(index);
const bool was_local = tm.is_local();
resources::gameboard->side_change_controller(side, is_local, player);
resources::gameboard->side_change_controller(side, is_local, player, controller_type);
if (!was_local && tm.is_local()) {
resources::controller->on_not_observer();

View file

@ -545,6 +545,7 @@ void game::transfer_side_control(const socket_ptr& sock, const simple_wml::node&
const simple_wml::string_span& newplayer_name = cfg["player"];
const socket_ptr old_player = sides_[side_num - 1];
const auto oldplayer = player_connections_.find(old_player);
const std::string& controller_type = cfg["to"].to_string();
if(oldplayer == player_connections_.end()) {
missing_user(old_player, __func__);
}
@ -579,10 +580,18 @@ void game::transfer_side_control(const socket_ptr& sock, const simple_wml::node&
}
if(newplayer == old_player) {
std::stringstream msg;
msg << "Side " << side_num << " is already controlled by " << newplayer_name << ".";
send_server_message(msg.str(), sock);
return;
// if the player is unchanged and the controller type (human or ai) is also unchanged then nothing to do
// else only need to change the controller type rather than the player who controls the side
if(CONTROLLER::string_to_enum(controller_type) == side_controllers_[side_num - 1]) {
std::stringstream msg;
msg << "Side " << side_num << " is already controlled by " << newplayer_name << ".";
send_server_message(msg.str(), sock);
return;
} else {
side_controllers_[side_num - 1] = CONTROLLER::string_to_enum(controller_type);
change_controller_type(side_num - 1, newplayer, player_connections_.find(newplayer)->info().name());
return;
}
}
sides_[side_num - 1].reset();
@ -631,32 +640,39 @@ void game::change_controller(
}
}
simple_wml::document response;
simple_wml::node& change = response.root().add_child("change_controller");
auto response = change_controller_type(side_index, sock, player_name);
change.set_attr_dup("side", side.c_str());
change.set_attr_dup("player", player_name.c_str());
// Tell everyone but the new player that this side's controller changed.
change.set_attr_dup("controller", side_controllers_[side_index].to_cstring());
change.set_attr("is_local", "no");
send_data(response, sock);
if(started_) { // this is added instead of the if (started_) {...} below
if(started_) {
// the purpose of these records is so that observers, replay viewers, get controller updates correctly
record_data(response.clone());
record_data(response->clone());
}
// Tell the new player that he controls this side now.
// Just don't send it when the player left the game. (The host gets the
// side_drop already.)
if(!player_left) {
change.set_attr("is_local", "yes");
async_send_doc_queued(sock, response);
response->root().child("change_controller")->set_attr("is_local", "yes");
async_send_doc_queued(sock, *response.get());
}
}
std::unique_ptr<simple_wml::document> game::change_controller_type(const std::size_t side_index, const socket_ptr& sock, const std::string& player_name)
{
const std::string& side = std::to_string(side_index + 1);
simple_wml::document response;
simple_wml::node& change = response.root().add_child("change_controller");
change.set_attr_dup("side", side.c_str());
change.set_attr_dup("player", player_name.c_str());
// Tell everyone but the source player that this side's controller changed.
change.set_attr_dup("controller", side_controllers_[side_index].to_cstring());
change.set_attr("is_local", "no");
send_data(response, sock);
return std::unique_ptr<simple_wml::document>(response.clone());
}
void game::notify_new_host()
{
const std::string owner_name = username(owner_);

View file

@ -387,6 +387,9 @@ private:
const socket_ptr& sock,
const std::string& player_name,
const bool player_left = true);
std::unique_ptr<simple_wml::document> change_controller_type(const std::size_t side_num,
const socket_ptr& sock,
const std::string& player_name);
void transfer_ai_sides(const socket_ptr& player);
void send_leave_game(const socket_ptr& user) const;