wesnothd: update current turn in init_side instead of end_turn
This matches the behavior on the client where the events that happen after end_turn (the "turn end" wml events) happen during the last sides turn not the next sides turn. In particular this fixes #2563. It also fixes #3899 because the new logic automaticially skips empty sides already in init_side and not on end_turn. It also changes how description is updated, since carrying that return value arround specifying whether it was updated was becoming annoying. With a few code improvements from soliton.
This commit is contained in:
parent
f2bcc997b8
commit
e64283534f
4 changed files with 153 additions and 80 deletions
|
@ -21,6 +21,7 @@
|
|||
#include "serialization/chrono.hpp"
|
||||
#include "server/wesnothd/player_network.hpp"
|
||||
#include "server/wesnothd/server.hpp"
|
||||
#include "utils/math.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
@ -97,8 +98,10 @@ game::game(wesnothd::server& server, player_connections& player_connections,
|
|||
, history_()
|
||||
, chat_history_()
|
||||
, description_(nullptr)
|
||||
, description_updated_(false)
|
||||
, current_turn_(0)
|
||||
, current_side_index_(0)
|
||||
, next_side_index_(0)
|
||||
, num_turns_(0)
|
||||
, all_observers_muted_(false)
|
||||
, bans_()
|
||||
|
@ -306,15 +309,24 @@ void game::start_game(player_iterator starter)
|
|||
DBG_GAME << "Number of sides: " << nsides_;
|
||||
int turn = 1;
|
||||
int side = 0;
|
||||
int next_side = 0;
|
||||
|
||||
// Savegames have a snapshot that tells us which side starts.
|
||||
if(const simple_wml::node* snapshot = level_.root().child("snapshot")) {
|
||||
turn = lexical_cast_default<int>((*snapshot)["turn_at"], 1);
|
||||
side = lexical_cast_default<int>((*snapshot)["playing_team"], 0);
|
||||
LOG_GAME << "Reload from turn: " << turn << ". Current side is: " << side + 1 << ".";
|
||||
if((*snapshot)["init_side_done"].to_bool(false)) {
|
||||
next_side = -1;
|
||||
} else {
|
||||
next_side = side;
|
||||
}
|
||||
LOG_GAME << "Reload from turn: " << turn << ". Current side is: " << side + 1 << ". Next side is: " << next_side + 1;
|
||||
}
|
||||
|
||||
current_turn_ = turn;
|
||||
current_side_index_ = side;
|
||||
next_side_index_ = next_side;
|
||||
|
||||
num_turns_ = lexical_cast_default<int>((*starting_pos(level_.root()))["turns"], -1);
|
||||
|
||||
update_turn_data();
|
||||
|
@ -632,10 +644,10 @@ void game::notify_new_host()
|
|||
send_and_record_server_message(message);
|
||||
}
|
||||
|
||||
bool game::describe_slots()
|
||||
void game::describe_slots()
|
||||
{
|
||||
if(started_ || description_ == nullptr) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
int available_slots = 0;
|
||||
|
@ -652,15 +664,10 @@ bool game::describe_slots()
|
|||
++i;
|
||||
}
|
||||
|
||||
simple_wml::node* slots_cfg = description_->child("slot_data");
|
||||
if(!slots_cfg) {
|
||||
slots_cfg = &description_->add_child("slot_data");
|
||||
}
|
||||
simple_wml::node& slots_cfg = description_for_writing()->child_or_add("slot_data");
|
||||
|
||||
slots_cfg->set_attr_int("vacant", available_slots);
|
||||
slots_cfg->set_attr_int("max", num_sides);
|
||||
|
||||
return true;
|
||||
slots_cfg.set_attr_int("vacant", available_slots);
|
||||
slots_cfg.set_attr_int("max", num_sides);
|
||||
}
|
||||
|
||||
bool game::player_is_banned(player_iterator player, const std::string& name) const
|
||||
|
@ -902,11 +909,16 @@ bool game::is_legal_command(const simple_wml::node& command, player_iterator use
|
|||
return false;
|
||||
}
|
||||
|
||||
if(from_side_index >= sides_.size() || sides_[from_side_index] != user) {
|
||||
if(get_side_player(from_side_index) != user) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(command.child("init_side")) {
|
||||
// If side init was already done, the rhs returns nullopt so this also return fale in this case.
|
||||
return get_side_player(get_next_side_index()) == user;
|
||||
}
|
||||
|
||||
if(is_current) {
|
||||
return true;
|
||||
}
|
||||
|
@ -929,7 +941,7 @@ bool game::is_legal_command(const simple_wml::node& command, player_iterator use
|
|||
}
|
||||
|
||||
std::size_t side_number = sn.to_int();
|
||||
if(side_number >= sides_.size() || sides_[side_number] != user) {
|
||||
if(get_side_player(side_number) != user) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
|
@ -1052,7 +1064,10 @@ bool game::process_turn(simple_wml::document& data, player_iterator user)
|
|||
send_and_record_server_message(username(user) + " has surrendered.");
|
||||
} else if(is_current_player(user) && (*command).child("end_turn")) {
|
||||
simple_wml::node& endturn = *(*command).child("end_turn");
|
||||
turn_ended = end_turn(endturn["next_player_number"].to_int());
|
||||
end_turn(endturn["next_player_number"].to_int());
|
||||
} else if(command->child("init_side")) {
|
||||
//["side_number"]
|
||||
init_turn();
|
||||
}
|
||||
|
||||
++index;
|
||||
|
@ -1295,46 +1310,34 @@ void game::process_change_turns_wml(simple_wml::document& data, player_iterator
|
|||
|
||||
assert(static_cast<int>(this->current_turn()) == current_turn);
|
||||
|
||||
simple_wml::node* turns_cfg = description_->child("turn_data");
|
||||
if(!turns_cfg) {
|
||||
turns_cfg = &description_->add_child("turn_data");
|
||||
}
|
||||
simple_wml::node& turns_cfg = description_for_writing()->child_or_add("turn_data");
|
||||
|
||||
ctw_node.copy_into(*turns_cfg);
|
||||
ctw_node.copy_into(turns_cfg);
|
||||
|
||||
// Don't send or store this change, all players should have gotten it by wml.
|
||||
}
|
||||
|
||||
bool game::end_turn(int new_side)
|
||||
void game::end_turn(int new_side)
|
||||
{
|
||||
if(new_side > 0) {
|
||||
current_side_index_ = new_side - 1;
|
||||
next_side_index_ = new_side - 1;
|
||||
} else {
|
||||
next_side_index_ = current_side_index_ + 1;
|
||||
}
|
||||
else {
|
||||
++current_side_index_;
|
||||
}
|
||||
|
||||
void game::init_turn()
|
||||
{
|
||||
int new_side = get_next_side_index();
|
||||
if(new_side > current_side_index_) {
|
||||
++current_turn_;
|
||||
}
|
||||
|
||||
// Skip over empty sides.
|
||||
for(int i = 0; i < nsides_ && side_controllers_[current_side()] == side_controller::type::none; ++i) {
|
||||
++current_side_index_;
|
||||
}
|
||||
|
||||
auto res = std::div(current_side_index_, nsides_ > 0 ? nsides_ : 1);
|
||||
|
||||
if(res.quot == 0) {
|
||||
return false;
|
||||
}
|
||||
current_side_index_ = res.rem;
|
||||
current_turn_ += res.quot;
|
||||
|
||||
if(description_ == nullptr) {
|
||||
// TODO: why do we need this?
|
||||
return false;
|
||||
}
|
||||
current_side_index_ = new_side;
|
||||
next_side_index_ = -1;
|
||||
|
||||
update_turn_data();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void game::update_turn_data()
|
||||
|
@ -1343,13 +1346,10 @@ void game::update_turn_data()
|
|||
return;
|
||||
}
|
||||
|
||||
simple_wml::node* turns_cfg = description_->child("turn_data");
|
||||
if(!turns_cfg) {
|
||||
turns_cfg = &description_->add_child("turn_data");
|
||||
}
|
||||
simple_wml::node& turns_cfg = description_for_writing()->child_or_add("turn_data");
|
||||
|
||||
turns_cfg->set_attr_int("current", current_turn());
|
||||
turns_cfg->set_attr_int("max", num_turns_);
|
||||
turns_cfg.set_attr_int("current", current_turn());
|
||||
turns_cfg.set_attr_int("max", num_turns_);
|
||||
}
|
||||
|
||||
bool game::add_player(player_iterator player, bool observer)
|
||||
|
@ -1998,4 +1998,26 @@ bool game::is_reload() const
|
|||
return multiplayer.has_attr("savegame") && multiplayer["savegame"].to_bool();
|
||||
}
|
||||
|
||||
int game::get_next_side_index() const
|
||||
{
|
||||
return get_next_nonempty(next_side_index_);
|
||||
}
|
||||
|
||||
int game::get_next_nonempty(int side_index) const
|
||||
{
|
||||
if(side_index == -1) {
|
||||
return -1;
|
||||
}
|
||||
if(nsides_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
for(int i = 0; i < nsides_; ++i) {
|
||||
int res = modulo(side_index + i, nsides_, 0);
|
||||
if(side_controllers_[res] != side_controller::type::none) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace wesnothd
|
||||
|
|
|
@ -363,10 +363,8 @@ public:
|
|||
|
||||
/**
|
||||
* Set the description to the number of available slots.
|
||||
*
|
||||
* @returns True if the number of slots has changed.
|
||||
*/
|
||||
bool describe_slots();
|
||||
void describe_slots();
|
||||
|
||||
/**
|
||||
* Sends a message to all players in this game that aren't excluded.
|
||||
|
@ -482,6 +480,27 @@ public:
|
|||
return description_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The node containing the game's current description. and remembers that it was changed.
|
||||
*/
|
||||
simple_wml::node* description_for_writing()
|
||||
{
|
||||
description_updated_ = true;
|
||||
return description_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The node containing the game's current description if it was changed.
|
||||
*/
|
||||
simple_wml::node* changed_description()
|
||||
{
|
||||
if(description_updated_) {
|
||||
description_updated_ = false;
|
||||
return description_;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password required to access the game.
|
||||
*
|
||||
|
@ -602,7 +621,19 @@ private:
|
|||
*/
|
||||
std::size_t current_side() const
|
||||
{
|
||||
return nsides_ != 0 ? (current_side_index_ % nsides_) : 0;
|
||||
// At the start of the game it can happen that current_side_index_ is 0,
|
||||
// but the first side is empty. It's better to do this than to skip empty
|
||||
// sides in start_game() in case the controller changes during start events.
|
||||
return get_next_nonempty(current_side_index_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The player who owns the side at index @a index.
|
||||
* nullopt if wither index is invalid or the side is not owned.
|
||||
*/
|
||||
utils::optional<player_iterator> get_side_player(size_t index) const
|
||||
{
|
||||
return index >= sides_.size() ? utils::optional<player_iterator>() : sides_[index];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -610,7 +641,8 @@ private:
|
|||
*/
|
||||
utils::optional<player_iterator> current_player() const
|
||||
{
|
||||
return sides_[current_side()];
|
||||
// sides_ should never be empty but just to be sure.
|
||||
return get_side_player(current_side());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -742,13 +774,17 @@ private:
|
|||
|
||||
/**
|
||||
* Function which should be called every time a player ends their turn
|
||||
* (i.e. [end_turn] received). This will update the 'turn' attribute for
|
||||
* the game's description when appropriate.
|
||||
* (i.e. [end_turn] received).
|
||||
*
|
||||
* @param new_side The side number whose turn to move it has become.
|
||||
* @return True if the current side and-or current turn values have been updated, false otherwise.
|
||||
*/
|
||||
bool end_turn(int new_side);
|
||||
void end_turn(int new_side);
|
||||
/**
|
||||
* Function which should be called every time a player starts their turn
|
||||
* (i.e. [init_side] received). This will update the 'turn' attribute for
|
||||
* the game's description when appropriate.
|
||||
*/
|
||||
void init_turn();
|
||||
|
||||
/**
|
||||
* Set or update the current and max turn values in the game's description.
|
||||
|
@ -792,6 +828,13 @@ private:
|
|||
*/
|
||||
std::string debug_sides_info() const;
|
||||
|
||||
/// @return the side index for which we accept [init_side]
|
||||
int get_next_side_index() const;
|
||||
/**
|
||||
* finds the first side starting at @a side_index that is non empty.
|
||||
*/
|
||||
int get_next_nonempty(int side_index) const;
|
||||
|
||||
/** The wesnothd server instance this game exists on. */
|
||||
wesnothd::server& server;
|
||||
player_connections& player_connections_;
|
||||
|
@ -869,10 +912,18 @@ private:
|
|||
/** Pointer to the game's description in the games_and_users_list_. */
|
||||
simple_wml::node* description_;
|
||||
|
||||
/** Set to true whenever description_ was changed that an update needs to be sent to clients. */
|
||||
bool description_updated_;
|
||||
|
||||
/** The game's current turn. */
|
||||
int current_turn_;
|
||||
/** The index of the current side. The side number is current_side_index_+1. */
|
||||
int current_side_index_;
|
||||
/**
|
||||
* after [end_turn] was received, this contains the side for who we accept [init_side].
|
||||
* -1 if we currently don't accept [init_side] because the current player didn't end his turn yet.
|
||||
**/
|
||||
int next_side_index_;
|
||||
/** The maximum number of turns before the game ends. */
|
||||
int num_turns_;
|
||||
/** Whether all observers should be treated as muted. */
|
||||
|
|
|
@ -1506,7 +1506,7 @@ void server::handle_join_game(player_iterator player, simple_wml::node& join)
|
|||
|
||||
// send notification of changes to the game and user
|
||||
simple_wml::document diff;
|
||||
bool diff1 = make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g->description(), diff);
|
||||
bool diff1 = make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g->changed_description(), diff);
|
||||
bool diff2 = make_change_diff(games_and_users_list_.root(), nullptr, "user",
|
||||
player->info().config_address(), diff);
|
||||
|
||||
|
@ -1572,7 +1572,7 @@ void server::handle_player_in_game(player_iterator p, simple_wml::document& data
|
|||
|
||||
assert(games_and_users_list_.child("gamelist")->children("game").empty() == false);
|
||||
|
||||
simple_wml::node& desc = *g.description();
|
||||
simple_wml::node& desc = *g.description_for_writing();
|
||||
|
||||
// Update the game's description.
|
||||
// If there is no shroud, then tell players in the lobby
|
||||
|
@ -1659,7 +1659,7 @@ void server::handle_player_in_game(player_iterator p, simple_wml::document& data
|
|||
return;
|
||||
}
|
||||
|
||||
simple_wml::node& desc = *g.description();
|
||||
simple_wml::node& desc = *g.description_for_writing();
|
||||
|
||||
// Update the game's description.
|
||||
if(const simple_wml::node* m = scenario->child("multiplayer")) {
|
||||
|
@ -1790,7 +1790,8 @@ void server::handle_player_in_game(player_iterator p, simple_wml::document& data
|
|||
if(g.remove_player(p)) {
|
||||
delete_game(g.id());
|
||||
} else {
|
||||
auto description = g.description();
|
||||
bool has_diff = false;
|
||||
simple_wml::document diff;
|
||||
|
||||
// After this line, the game object may be destroyed. Don't use `g`!
|
||||
player_connections_.modify(p, std::bind(&player_record::enter_lobby, std::placeholders::_1));
|
||||
|
@ -1798,14 +1799,14 @@ void server::handle_player_in_game(player_iterator p, simple_wml::document& data
|
|||
// Only run this if the game object is still valid
|
||||
if(auto gStrong = g_ptr.lock()) {
|
||||
gStrong->describe_slots();
|
||||
//Don't update the game if it no longer exists.
|
||||
has_diff |= make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", gStrong->description(), diff);
|
||||
}
|
||||
|
||||
// Send all other players in the lobby the update to the gamelist.
|
||||
simple_wml::document diff;
|
||||
bool diff1 = make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", description, diff);
|
||||
bool diff2 = make_change_diff(games_and_users_list_.root(), nullptr, "user", player.config_address(), diff);
|
||||
has_diff |= make_change_diff(games_and_users_list_.root(), nullptr, "user", player.config_address(), diff);
|
||||
|
||||
if(diff1 || diff2) {
|
||||
if(has_diff) {
|
||||
send_to_lobby(diff, p);
|
||||
}
|
||||
|
||||
|
@ -1827,9 +1828,8 @@ void server::handle_player_in_game(player_iterator p, simple_wml::document& data
|
|||
g.update_side_data();
|
||||
}
|
||||
|
||||
if(g.describe_slots()) {
|
||||
update_game_in_lobby(g);
|
||||
}
|
||||
g.describe_slots();
|
||||
update_game_in_lobby(g);
|
||||
|
||||
g.send_data(data, p);
|
||||
return;
|
||||
|
@ -1840,9 +1840,8 @@ void server::handle_player_in_game(player_iterator p, simple_wml::document& data
|
|||
// If the owner of a side is changing the controller.
|
||||
} else if(const simple_wml::node* change = data.child("change_controller")) {
|
||||
g.transfer_side_control(p, *change);
|
||||
if(g.describe_slots()) {
|
||||
update_game_in_lobby(g);
|
||||
}
|
||||
g.describe_slots();
|
||||
update_game_in_lobby(g);
|
||||
|
||||
return;
|
||||
// If all observers should be muted. (toggles)
|
||||
|
@ -1871,9 +1870,9 @@ void server::handle_player_in_game(player_iterator p, simple_wml::document& data
|
|||
|
||||
if(user) {
|
||||
player_connections_.modify(*user, std::bind(&player_record::enter_lobby, std::placeholders::_1));
|
||||
if(g.describe_slots()) {
|
||||
update_game_in_lobby(g, user);
|
||||
}
|
||||
g.describe_slots();
|
||||
|
||||
update_game_in_lobby(g, user);
|
||||
|
||||
// Send all other players in the lobby the update to the gamelist.
|
||||
simple_wml::document gamelist_diff;
|
||||
|
@ -1911,9 +1910,8 @@ void server::handle_player_in_game(player_iterator p, simple_wml::document& data
|
|||
// Notify the game of the commands, and if it changes
|
||||
// the description, then sync the new description
|
||||
// to players in the lobby.
|
||||
if(g.process_turn(data, p)) {
|
||||
update_game_in_lobby(g);
|
||||
}
|
||||
g.process_turn(data, p);
|
||||
update_game_in_lobby(g);
|
||||
|
||||
return;
|
||||
} else if(data.child("whiteboard")) {
|
||||
|
@ -2996,11 +2994,13 @@ void server::delete_game(int gameid, const std::string& reason)
|
|||
}
|
||||
}
|
||||
|
||||
void server::update_game_in_lobby(const wesnothd::game& g, utils::optional<player_iterator> exclude)
|
||||
void server::update_game_in_lobby(wesnothd::game& g, utils::optional<player_iterator> exclude)
|
||||
{
|
||||
simple_wml::document diff;
|
||||
if(make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", g.description(), diff)) {
|
||||
send_to_lobby(diff, exclude);
|
||||
if(auto p_desc = g.changed_description()) {
|
||||
if(make_change_diff(*games_and_users_list_.child("gamelist"), "gamelist", "game", p_desc, diff)) {
|
||||
send_to_lobby(diff, exclude);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -222,7 +222,7 @@ private:
|
|||
|
||||
void delete_game(int, const std::string& reason="");
|
||||
|
||||
void update_game_in_lobby(const game& g, utils::optional<player_iterator> exclude = {});
|
||||
void update_game_in_lobby(game& g, utils::optional<player_iterator> exclude = {});
|
||||
|
||||
void start_new_server();
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue