add synced_context class
the intention is to fix #20871 and implement #21697, to sync prestart/start events, and to implement http://forums.wesnoth.org/viewtopic.php?f=6&t=39611 in the next patches, the intention of synced_checkup is to replace random .. set/get_random_results. Because set/get_random_results didn't have much to do with random, since it was only used to compare unit checksums and attacker damage to replays. the intention of synced_commands is to move code out of do_replay_handle and make it callable from other places too so that we can just call the same function taht was called from replay in the simple cases (with synced_context::run_in_synced_command). the object set_scontext_synced can be used to enter the synced context which enables synced_checkup and make the random calls synced. Or we can use run_in_synced_context which is normaly easier than set_scontext_synced. we can check wether we are in a synced context with get_syced_state to make addidional checks to detect oos (the other intention of that is to implement #21697 ). this commit is part of pf 121.
This commit is contained in:
parent
8f00786fb6
commit
fc8c15e46a
8 changed files with 1316 additions and 1 deletions
|
@ -868,6 +868,9 @@ set(wesnoth-main_SRC
|
|||
storyscreen/part.cpp
|
||||
storyscreen/render.cpp
|
||||
strftime.cpp
|
||||
synced_checkup.cpp
|
||||
synced_context.cpp
|
||||
synced_commands.cpp
|
||||
team.cpp
|
||||
terrain_filter.cpp
|
||||
tod_manager.cpp
|
||||
|
|
|
@ -501,7 +501,10 @@ wesnoth_sources = Split("""
|
|||
storyscreen/interface.cpp
|
||||
storyscreen/part.cpp
|
||||
storyscreen/render.cpp
|
||||
strftime.cpp
|
||||
strftime.cpp
|
||||
synced_checkup.cpp
|
||||
synced_context.cpp
|
||||
synced_commands.cpp
|
||||
team.cpp
|
||||
terrain_filter.cpp
|
||||
tod_manager.cpp
|
||||
|
|
133
src/synced_checkup.cpp
Normal file
133
src/synced_checkup.cpp
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "synced_checkup.hpp"
|
||||
#include "log.hpp"
|
||||
#include "map_location.hpp"
|
||||
#include "unit_map.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "resources.hpp"
|
||||
#include "game_display.hpp"
|
||||
static lg::log_domain log_replay("replay");
|
||||
#define DBG_REPLAY LOG_STREAM(debug, log_replay)
|
||||
#define LOG_REPLAY LOG_STREAM(info, log_replay)
|
||||
#define WRN_REPLAY LOG_STREAM(warn, log_replay)
|
||||
#define ERR_REPLAY LOG_STREAM(err, log_replay)
|
||||
|
||||
ignored_checkup default_instnce;
|
||||
|
||||
checkup* checkup_instance = &default_instnce;
|
||||
|
||||
checkup::checkup()
|
||||
{
|
||||
}
|
||||
|
||||
checkup::~checkup()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void checkup::unit_checksum(const map_location& loc, bool local)
|
||||
{
|
||||
unit_map::iterator u = resources::units->find(loc);
|
||||
bool equals;
|
||||
config real;
|
||||
config expected;
|
||||
|
||||
if (!u.valid()) {
|
||||
std::stringstream message;
|
||||
message << "non existent unit to checksum at " << loc.x+1 << "," << loc.y+1 << "!";
|
||||
resources::screen->add_chat_message(time(NULL), "verification", 1, message.str(),
|
||||
events::chat_handler::MESSAGE_PRIVATE, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
expected["checksum"] = get_checksum(*u);
|
||||
}
|
||||
|
||||
if(local)
|
||||
{
|
||||
equals = local_checkup(expected, real);
|
||||
}
|
||||
else
|
||||
{
|
||||
equals = this->networked_checkup(expected, real);
|
||||
}
|
||||
|
||||
if(!equals && ((game_config::mp_debug && !local) || local))
|
||||
{
|
||||
std::stringstream message;
|
||||
message << "checksum mismatch at " << loc.x+1 << "," << loc.y+1 << "!";
|
||||
resources::screen->add_chat_message(time(NULL), "verification", 1, message.str(),
|
||||
events::chat_handler::MESSAGE_PRIVATE, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ignored_checkup::ignored_checkup()
|
||||
{
|
||||
}
|
||||
|
||||
ignored_checkup::~ignored_checkup()
|
||||
{
|
||||
}
|
||||
|
||||
bool ignored_checkup::local_checkup(const config& /*expected_data*/, config& real_data)
|
||||
{
|
||||
assert(real_data.empty());
|
||||
LOG_REPLAY << "ignored_checkup::local_checkup called\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ignored_checkup::networked_checkup(const config& /*expected_data*/, config& real_data)
|
||||
{
|
||||
assert(real_data.empty());
|
||||
|
||||
LOG_REPLAY << "ignored_checkup::networked_checkup called\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
synced_checkup::synced_checkup(config& buffer)
|
||||
: buffer_(buffer), pos_(0)
|
||||
{
|
||||
}
|
||||
|
||||
synced_checkup::~synced_checkup()
|
||||
{
|
||||
}
|
||||
|
||||
bool synced_checkup::local_checkup(const config& expected_data, config& real_data)
|
||||
{
|
||||
assert(real_data.empty());
|
||||
if(buffer_.child_count("checkup") > pos_)
|
||||
{
|
||||
//copying objects :o
|
||||
real_data = buffer_.child("checkup",pos_);
|
||||
pos_ ++;
|
||||
return real_data == expected_data;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(buffer_.child_count("checkup") == pos_);
|
||||
buffer_.add_child("checkup", expected_data);
|
||||
pos_++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool synced_checkup::networked_checkup(const config& /*expected_data*/, config& real_data)
|
||||
{
|
||||
assert(real_data.empty());
|
||||
throw "not implemented";
|
||||
//TODO: something with get_user_choice :).
|
||||
}
|
81
src/synced_checkup.hpp
Normal file
81
src/synced_checkup.hpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#ifndef SYNCED_CHECKUP_H_INCLUDED
|
||||
#define SYNCED_CHECKUP_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
struct map_location;
|
||||
/*
|
||||
a class to check whether calculated ingame results match the results calculated during the original game.
|
||||
note, that you shouldn't add new checkups to existent user actions or you might break replay compability by bringing the [checkups] tag of older saves in unorder.
|
||||
|
||||
so if you really want to add new checkups, you should wrap your checkup_instance->... call in a if(resources::state_of_game->classification().version ....) or similar.
|
||||
*/
|
||||
class checkup
|
||||
{
|
||||
public:
|
||||
checkup();
|
||||
virtual ~checkup();
|
||||
/*
|
||||
does only compares data in replays.
|
||||
returns wether the two config objects are equal.
|
||||
*/
|
||||
virtual bool local_checkup(const config& expected_data, config& real_data) = 0;
|
||||
/*
|
||||
compares data on all clients in a networked game, the disadvantage is,
|
||||
that the clients have to communicate more which might be not wanted if some persons have laggy inet.
|
||||
returns whether the two config objects are equal.
|
||||
|
||||
this is currently not used.
|
||||
*/
|
||||
virtual bool networked_checkup(const config& expected_data, config& real_data) = 0;
|
||||
/*
|
||||
we cannot use the replay.add_checksum anymore without risks because we might send the unit_checksum after it has been recorded at the other client.
|
||||
this is a helper function for that.
|
||||
*/
|
||||
void unit_checksum(const map_location& loc, bool local = true);
|
||||
};
|
||||
|
||||
class synced_checkup : public checkup
|
||||
{
|
||||
public:
|
||||
synced_checkup(config& buffer);
|
||||
virtual ~synced_checkup();
|
||||
virtual bool local_checkup(const config& expected_data, config& real_data);
|
||||
virtual bool networked_checkup(const config& expected_data, config& real_data);
|
||||
private:
|
||||
config& buffer_;
|
||||
unsigned int pos_;
|
||||
};
|
||||
|
||||
/*
|
||||
the only purpose of these function isto thro OOS erros, because they should never be called.
|
||||
*/
|
||||
|
||||
class ignored_checkup : public checkup
|
||||
{
|
||||
public:
|
||||
ignored_checkup();
|
||||
virtual ~ignored_checkup();
|
||||
virtual bool local_checkup(const config& expected_data, config& real_data);
|
||||
virtual bool networked_checkup(const config& expected_data, config& real_data);
|
||||
};
|
||||
|
||||
/*
|
||||
this is a synced_checkup during a synced context otherwise a invalid_checkup object.
|
||||
*/
|
||||
|
||||
extern checkup* checkup_instance;
|
||||
|
||||
#endif
|
475
src/synced_commands.cpp
Normal file
475
src/synced_commands.cpp
Normal file
|
@ -0,0 +1,475 @@
|
|||
/*
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#include "synced_commands.hpp"
|
||||
#include <cassert>
|
||||
|
||||
#include "log.hpp"
|
||||
#include "resources.hpp"
|
||||
#include "map_location.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "team.hpp"
|
||||
#include "play_controller.hpp"
|
||||
#include "actions/create.hpp"
|
||||
#include "actions/attack.hpp"
|
||||
#include "actions/move.hpp"
|
||||
#include "actions/undo.hpp"
|
||||
#include "preferences.hpp"
|
||||
#include "game_preferences.hpp"
|
||||
#include "game_events/pump.hpp"
|
||||
#include "dialogs.hpp"
|
||||
#include "unit_helper.hpp"
|
||||
#include "replay.hpp" //user choice
|
||||
#include "resources.hpp"
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
static lg::log_domain log_replay("replay");
|
||||
#define DBG_REPLAY LOG_STREAM(debug, log_replay)
|
||||
#define LOG_REPLAY LOG_STREAM(info, log_replay)
|
||||
#define WRN_REPLAY LOG_STREAM(warn, log_replay)
|
||||
#define ERR_REPLAY LOG_STREAM(err, log_replay)
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param[in] tag The replay tag for this action.
|
||||
* @param[in] function The callback for this action.
|
||||
*/
|
||||
synced_command::synced_command(const std::string & tag, handler function)
|
||||
{
|
||||
assert(registry().find( tag ) == registry().end());
|
||||
registry()[tag] = function;
|
||||
}
|
||||
|
||||
synced_command::map& synced_command::registry()
|
||||
{
|
||||
static map* instance = new map();
|
||||
return *instance;
|
||||
}
|
||||
|
||||
|
||||
SYNCED_COMMAND_HANDLER_FUNCTION(recruit, child, use_undo, show, error_handler)
|
||||
{
|
||||
int current_team_num = resources::controller->current_side();
|
||||
team ¤t_team = (*resources::teams)[current_team_num - 1];
|
||||
|
||||
map_location loc(child, resources::gamedata);
|
||||
map_location from(child.child_or_empty("from"), resources::gamedata);
|
||||
// Validate "from".
|
||||
if ( !from.valid() ) {
|
||||
// This will be the case for AI recruits in replays saved
|
||||
// before 1.11.2, so it is not more severe than a warning.
|
||||
// EDIT: we borke compability with 1.11.2 anyway so we should give an error.
|
||||
error_handler("Missing leader location for recruitment.\n", false);
|
||||
}
|
||||
else if ( resources::units->find(from) == resources::units->end() ) {
|
||||
// Sync problem?
|
||||
std::stringstream errbuf;
|
||||
errbuf << "Recruiting leader not found at " << from << ".\n";
|
||||
error_handler(errbuf.str(), false);
|
||||
}
|
||||
|
||||
// Get the unit_type ID.
|
||||
std::string type_id = child["type"];
|
||||
if ( type_id.empty() ) {
|
||||
error_handler("Recruitment is missing a unit type.", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
const unit_type *u_type = unit_types.find(type_id);
|
||||
if (!u_type) {
|
||||
std::stringstream errbuf;
|
||||
errbuf << "Recruiting illegal unit: '" << type_id << "'.\n";
|
||||
error_handler(errbuf.str(), true);
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string res = actions::find_recruit_location(current_team_num, loc, from, type_id);
|
||||
if(!res.empty())
|
||||
{
|
||||
std::stringstream errbuf;
|
||||
errbuf << "cannot recruit unit: " << res << "\n";
|
||||
error_handler(errbuf.str(), true);
|
||||
return false;
|
||||
//we are already oos because the unit wasn't created, no need to keep the bookkeeping right...
|
||||
}
|
||||
const int beginning_gold = current_team.gold();
|
||||
|
||||
|
||||
|
||||
if ( u_type->cost() > beginning_gold ) {
|
||||
std::stringstream errbuf;
|
||||
errbuf << "unit '" << type_id << "' is too expensive to recruit: "
|
||||
<< u_type->cost() << "/" << beginning_gold << "\n";
|
||||
error_handler(errbuf.str(), false);
|
||||
}
|
||||
|
||||
actions::recruit_unit(*u_type, current_team_num, loc, from, show, use_undo, false);
|
||||
|
||||
LOG_REPLAY << "recruit: team=" << current_team_num << " '" << type_id << "' at (" << loc
|
||||
<< ") cost=" << u_type->cost() << " from gold=" << beginning_gold << ' '
|
||||
<< "-> " << current_team.gold() << "\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
SYNCED_COMMAND_HANDLER_FUNCTION(recall, child, use_undo, show, error_handler)
|
||||
{
|
||||
|
||||
int current_team_num = resources::controller->current_side();
|
||||
team ¤t_team = (*resources::teams)[current_team_num - 1];
|
||||
|
||||
const std::string& unit_id = child["value"];
|
||||
map_location loc(child, resources::gamedata);
|
||||
map_location from(child.child_or_empty("from"), resources::gamedata);
|
||||
|
||||
if ( !actions::recall_unit(unit_id, current_team, loc, from, show, use_undo, false) ) {
|
||||
error_handler("illegal recall: unit_id '" + unit_id + "' could not be found within the recall list.\n", true);
|
||||
//when recall_unit returned false nothing happend so we can safety return false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
||||
class unit_advancement_choice : public mp_sync::user_choice
|
||||
{
|
||||
public:
|
||||
unit_advancement_choice(const map_location& loc, int total_opt, int side_num)
|
||||
: loc_ (loc), nb_options_(total_opt), side_num_(side_num)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~unit_advancement_choice()
|
||||
{
|
||||
}
|
||||
|
||||
virtual config query_user() const
|
||||
{
|
||||
int res = 0;
|
||||
team t = (*resources::teams)[side_num_ - 1];
|
||||
//note, that the advancements for networked sides are also determined on the current playing side.
|
||||
if(t.is_ai() || t.is_network_ai() || t.is_empty() || t.is_idle())
|
||||
{
|
||||
//TODO: if ai, call something like ai::choose_uni_advancement
|
||||
// To make the ai_advancement_aspect work again.
|
||||
res = rand() % nb_options_;
|
||||
}
|
||||
else if (t.is_local())
|
||||
{
|
||||
res = rand() % nb_options_;
|
||||
assert(t.is_human());
|
||||
res = dialogs::advance_unit_dialog(loc_);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(t.is_network_human());
|
||||
res = 0;
|
||||
}
|
||||
LOG_REPLAY << "unit at position " << loc_ << "choose advancement number " << res << "\n";
|
||||
config retv;
|
||||
retv["value"] = res;
|
||||
return retv;
|
||||
|
||||
}
|
||||
virtual config random_choice() const
|
||||
{
|
||||
config retv;
|
||||
retv["value"] = 0;
|
||||
return retv;
|
||||
}
|
||||
private:
|
||||
const map_location loc_;
|
||||
int nb_options_;
|
||||
int side_num_;
|
||||
};
|
||||
|
||||
void advance_unit_internal(const map_location& loc)
|
||||
{
|
||||
//i just don't want infinite loops...
|
||||
for(int advacment_number = 0; advacment_number < 20; advacment_number++)
|
||||
{
|
||||
unit_map::iterator u = resources::units->find(loc);
|
||||
//this implies u.valid()
|
||||
if(!unit_helper::will_certainly_advance(u)) {
|
||||
return;
|
||||
}
|
||||
config selected = mp_sync::get_user_choice("choose",
|
||||
unit_advancement_choice(loc, unit_helper::number_of_possible_advances(*u),u->side()));
|
||||
dialogs::animate_unit_advancement(loc, selected["value"], true, true); //or pass show with the last argument?
|
||||
|
||||
//i want to remove the next few lines...
|
||||
u = resources::units->find(loc);
|
||||
// level 10 unit gives 80 XP and the highest mainline is level 5
|
||||
if (u.valid() && u->experience() > 80)
|
||||
{
|
||||
ERR_REPLAY << "Unit has too many (" << u->experience() << ") XP left; cascade leveling disabled.\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
ERR_REPLAY << "unit at " << loc << "tried to adcance more than 21 times\n";
|
||||
}
|
||||
}
|
||||
|
||||
SYNCED_COMMAND_HANDLER_FUNCTION(attack, child, /*use_undo*/, show, error_handler)
|
||||
{
|
||||
|
||||
const config &destination = child.child("destination");
|
||||
const config &source = child.child("source");
|
||||
//check_checksums(*cfg);
|
||||
|
||||
if (!destination || !source) {
|
||||
error_handler("no destination/source found in attack\n", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
//we must get locations by value instead of by references, because the iterators
|
||||
//may become invalidated later
|
||||
const map_location src(source, resources::gamedata);
|
||||
const map_location dst(destination, resources::gamedata);
|
||||
|
||||
int weapon_num = child["weapon"];
|
||||
int def_weapon_num = child["defender_weapon"].to_int(-2);
|
||||
if (def_weapon_num == -2) {
|
||||
// Let's not gratuitously destroy backwards compatibility.
|
||||
WRN_REPLAY << "Old data, having to guess weapon\n";
|
||||
def_weapon_num = -1;
|
||||
}
|
||||
|
||||
unit_map::iterator u = resources::units->find(src);
|
||||
if (!u.valid()) {
|
||||
error_handler("unfound location for source of attack\n", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string &att_type_id = child["attacker_type"];
|
||||
if (u->type_id() != att_type_id) {
|
||||
WRN_REPLAY << "unexpected attacker type: " << att_type_id << "(game_state gives: " << u->type_id() << ")\n";
|
||||
}
|
||||
|
||||
if (size_t(weapon_num) >= u->attacks().size()) {
|
||||
error_handler("illegal weapon type in attack\n", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
unit_map::const_iterator tgt = resources::units->find(dst);
|
||||
|
||||
if (!tgt.valid()) {
|
||||
std::stringstream errbuf;
|
||||
errbuf << "unfound defender for attack: " << src << " -> " << dst << '\n';
|
||||
error_handler(errbuf.str(), true);
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string &def_type_id = child["defender_type"];
|
||||
if (tgt->type_id() != def_type_id) {
|
||||
WRN_REPLAY << "unexpected defender type: " << def_type_id << "(game_state gives: " << tgt->type_id() << ")\n";
|
||||
}
|
||||
|
||||
if (def_weapon_num >= static_cast<int>(tgt->attacks().size())) {
|
||||
|
||||
error_handler("illegal defender weapon type in attack\n", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
DBG_REPLAY << "Attacker XP (before attack): " << u->experience() << "\n";
|
||||
|
||||
|
||||
|
||||
resources::undo_stack->clear();
|
||||
try
|
||||
{
|
||||
DBG_REPLAY << "Attacking NOW!: attacker: " << src << " defender: "<< dst <<"\n";
|
||||
attack_unit(src, dst, weapon_num, def_weapon_num, show);
|
||||
}
|
||||
catch(end_level_exception&)
|
||||
{
|
||||
|
||||
unit_map::const_iterator atku = resources::units->find(src);
|
||||
// i think this check is not needed but i'm not sure.
|
||||
if (atku != resources::units->end()) {
|
||||
advance_unit_internal(src);
|
||||
}
|
||||
|
||||
unit_map::const_iterator defu = resources::units->find(dst);
|
||||
if (defu != resources::units->end()) {
|
||||
advance_unit_internal(dst);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
unit_map::const_iterator atku = resources::units->find(src);
|
||||
if (atku != resources::units->end()) {
|
||||
advance_unit_internal(src);
|
||||
}
|
||||
|
||||
unit_map::const_iterator defu = resources::units->find(dst);
|
||||
if (defu != resources::units->end()) {
|
||||
advance_unit_internal(dst);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SYNCED_COMMAND_HANDLER_FUNCTION(disband, child, /*use_undo*/, /*show*/, error_handler)
|
||||
{
|
||||
|
||||
int current_team_num = resources::controller->current_side();
|
||||
team ¤t_team = (*resources::teams)[current_team_num - 1];
|
||||
|
||||
const std::string& unit_id = child["value"];
|
||||
std::vector<unit>::iterator disband_unit =
|
||||
find_if_matches_id(current_team.recall_list(), unit_id);
|
||||
|
||||
if(disband_unit != current_team.recall_list().end()) {
|
||||
current_team.recall_list().erase(disband_unit);
|
||||
} else {
|
||||
error_handler("illegal disband\n", true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SYNCED_COMMAND_HANDLER_FUNCTION(move, child, /*use_undo*/, show, error_handler)
|
||||
{
|
||||
int current_team_num = resources::controller->current_side();
|
||||
team ¤t_team = (*resources::teams)[current_team_num - 1];
|
||||
|
||||
const std::string& x = child["x"];
|
||||
const std::string& y = child["y"];
|
||||
const std::vector<map_location> steps = parse_location_range(x,y);
|
||||
|
||||
if(steps.empty())
|
||||
{
|
||||
WRN_REPLAY << "Warning: Missing path data found in [move]\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
const map_location& src = steps.front();
|
||||
const map_location& dst = steps.back();
|
||||
|
||||
if (src == dst) {
|
||||
WRN_REPLAY << "Warning: Move with identical source and destination. Skipping...\n";
|
||||
return false;
|
||||
}
|
||||
//storign the early stope
|
||||
map_location early_stop(child["stop_x"].to_int(-999) - 1,
|
||||
child["stop_y"].to_int(-999) - 1);
|
||||
if ( !early_stop.valid() )
|
||||
early_stop = dst; // Not really "early", but we need a valid stopping point.
|
||||
|
||||
// The nominal destination should appear to be unoccupied.
|
||||
unit_map::iterator u = find_visible_unit(dst, current_team);
|
||||
if ( u.valid() ) {
|
||||
WRN_REPLAY << "Warning: Move destination " << dst << " appears occupied.\n";
|
||||
// We'll still proceed with this movement, though, since
|
||||
// an event might intervene.
|
||||
// for a player it is NOT POSSIBLE to give the command to move a unit to a blocked hex,
|
||||
// and it doesnt matter whether the units still stands there when the unit reaches the destination
|
||||
// so this is an OOS.
|
||||
}
|
||||
|
||||
u = resources::units->find(src);
|
||||
if (!u.valid()) {
|
||||
std::stringstream errbuf;
|
||||
errbuf << "unfound location for source of movement: "
|
||||
<< src << " -> " << dst << '\n';
|
||||
error_handler(errbuf.str(), true);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool show_move = show;
|
||||
if ( current_team.is_ai() || current_team.is_network_ai() )
|
||||
show_move = show_move && preferences::show_ai_moves();
|
||||
//const int num_steps =
|
||||
//todo
|
||||
actions::move_unit(steps, NULL, resources::undo_stack, true,
|
||||
show_move, NULL, NULL, NULL);
|
||||
|
||||
// Verify our destination.
|
||||
/*
|
||||
const map_location& actual_stop = steps[num_steps];
|
||||
if ( actual_stop != early_stop ) {
|
||||
std::stringstream errbuf;
|
||||
errbuf << "Failed to complete movement to "
|
||||
<< early_stop << ".\n";
|
||||
replay::process_error(errbuf.str());
|
||||
return;
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
SYNCED_COMMAND_HANDLER_FUNCTION(fire_event, child, /*use_undo*/, /*show*/, /*error_handler*/)
|
||||
{
|
||||
//i don't know the reason for the following three lines.
|
||||
//TODO: find out wheter we can delete them. I think this code was introduced in bbfdfcf9ed6ca44f01da32bf74c39d5fa9a75c37
|
||||
BOOST_FOREACH(const config &v, child.child_range("set_variable")) {
|
||||
resources::gamedata->set_variable(v["name"], v["value"]);
|
||||
}
|
||||
bool undoable = true;
|
||||
|
||||
if(const config &source = child.child("source"))
|
||||
{
|
||||
//the select event cannot clear he undo stack.
|
||||
game_events::fire("select", map_location(source, resources::gamedata));
|
||||
}
|
||||
const std::string &event = child["raise"];
|
||||
if (const config &source = child.child("source")) {
|
||||
undoable = undoable & !game_events::fire(event, map_location(source, resources::gamedata));
|
||||
} else {
|
||||
undoable = undoable & !game_events::fire(event);
|
||||
}
|
||||
if ( !undoable)
|
||||
resources::undo_stack->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
SYNCED_COMMAND_HANDLER_FUNCTION(lua_ai, child, /*use_undo*/, /*show*/, /*error_handler*/)
|
||||
{
|
||||
const std::string &lua_code = child["code"];
|
||||
game_events::run_lua_commands(lua_code.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
SYNCED_COMMAND_HANDLER_FUNCTION(auto_shroud, child, /*use_undo*/, /*show*/, /*error_handler*/)
|
||||
{
|
||||
int current_team_num = resources::controller->current_side();
|
||||
team ¤t_team = (*resources::teams)[current_team_num - 1];
|
||||
|
||||
bool active = child["active"].to_bool();
|
||||
// Turning on automatic shroud causes vision to be updated.
|
||||
if ( active )
|
||||
resources::undo_stack->commit_vision(true);
|
||||
|
||||
current_team.set_auto_shroud_updates(active);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** from resources::undo_stack->commit_vision(bool is_replay):
|
||||
* Updates fog/shroud based on the undo stack, then updates stack as needed.
|
||||
* Call this when "updating shroud now".
|
||||
* This may fire events and change the game state.
|
||||
* @param[in] is_replay Set to true when this is called during a replay.
|
||||
*
|
||||
* this means it ia synced command liek any other.
|
||||
*/
|
||||
|
||||
SYNCED_COMMAND_HANDLER_FUNCTION(update_shroud, /*child*/, /*use_undo*/, /*show*/, /*error_handler*/)
|
||||
{
|
||||
resources::undo_stack->commit_vision(true);
|
||||
return true;
|
||||
}
|
55
src/synced_commands.hpp
Normal file
55
src/synced_commands.hpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef SYNCED_COMMANDS_H_INCLUDED
|
||||
#define SYNCED_COMMANDS_H_INCLUDED
|
||||
|
||||
#include <map>
|
||||
#include <exception>
|
||||
#include "config.hpp"
|
||||
|
||||
#include <boost/function.hpp>
|
||||
class synced_command {
|
||||
public:
|
||||
/*
|
||||
the parameters or error handlers are
|
||||
1) the message of the error
|
||||
2) a boolean that indicates whether the error is heavy enough to make proceeding impossible.
|
||||
TODO: remove the second argument because it isn't used.
|
||||
|
||||
*/
|
||||
typedef boost::function2<void, const std::string&, bool> error_handler_function;
|
||||
/*
|
||||
returns: true if the action succeeded correclty,
|
||||
|
||||
*/
|
||||
typedef bool (*handler)(const config &, bool use_undo, bool show, error_handler_function error_handler);
|
||||
typedef std::map<std::string, handler> map;
|
||||
|
||||
|
||||
synced_command(const std::string & tag, handler function);
|
||||
|
||||
/// using static function variable instead of static member variable to prevent static initialization fiasco when used in other files.
|
||||
static map& registry();
|
||||
};
|
||||
|
||||
/*
|
||||
this is currently only used in "synced_commands.cpp" and there is no reason to use it anywhere else.
|
||||
but if you have a good reason feel free to do so.
|
||||
*/
|
||||
|
||||
#define SYNCED_COMMAND_HANDLER_FUNCTION(pname, pcfg, use_undo, show, error_handler) \
|
||||
static bool synced_command_func_##pname(const config & pcfg, bool use_undo, bool show, synced_command::error_handler_function error_handler ); \
|
||||
static synced_command synced_command_action_##pname(#pname, &synced_command_func_##pname); \
|
||||
static bool synced_command_func_##pname(const config & pcfg, bool use_undo, bool show, synced_command::error_handler_function error_handler)
|
||||
|
||||
#endif
|
374
src/synced_context.cpp
Normal file
374
src/synced_context.cpp
Normal file
|
@ -0,0 +1,374 @@
|
|||
#include "synced_context.hpp"
|
||||
#include "synced_commands.hpp"
|
||||
|
||||
#include "actions/undo.hpp"
|
||||
#include "ai/manager.hpp"
|
||||
#include "global.hpp"
|
||||
#include "config.hpp"
|
||||
#include "config_assign.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "random_new.hpp"
|
||||
#include "random_new_synced.hpp"
|
||||
#include "random_new_deterministic.hpp"
|
||||
#include "resources.hpp"
|
||||
#include "synced_checkup.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "network.hpp"
|
||||
#include "log.hpp"
|
||||
#include "play_controller.hpp"
|
||||
#include "actions/undo.hpp"
|
||||
#include "game_end_exceptions.hpp"
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <stdlib.h>
|
||||
static lg::log_domain log_replay("replay");
|
||||
#define DBG_REPLAY LOG_STREAM(debug, log_replay)
|
||||
#define LOG_REPLAY LOG_STREAM(info, log_replay)
|
||||
#define WRN_REPLAY LOG_STREAM(warn, log_replay)
|
||||
#define ERR_REPLAY LOG_STREAM(err, log_replay)
|
||||
|
||||
|
||||
synced_context::syced_state synced_context::state_ = synced_context::UNSYNCED;
|
||||
bool synced_context::is_simultaneously_ = false;
|
||||
|
||||
bool synced_context::run_in_synced_context(const std::string& commandname, const config& data, bool use_undo, bool show, bool store_in_replay, synced_command::error_handler_function error_handler)
|
||||
{
|
||||
DBG_REPLAY << "run_in_synced_context:" << commandname << "\n";
|
||||
assert(use_undo || (!resources::undo_stack->can_redo() && !resources::undo_stack->can_undo()));
|
||||
if(store_in_replay)
|
||||
{
|
||||
recorder.add_synced_command(commandname, data);
|
||||
}
|
||||
/*
|
||||
use this after recorder.add_synced_command
|
||||
because set_scontext_synced sets the checkup to the last added command
|
||||
*/
|
||||
set_scontext_synced sco(commandname);
|
||||
synced_command::map::iterator it = synced_command::registry().find(commandname);
|
||||
if(it == synced_command::registry().end())
|
||||
{
|
||||
error_handler("commandname not found", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool success = it->second(data, use_undo, show, error_handler);
|
||||
if(!success && store_in_replay)
|
||||
{
|
||||
//remove it from replay if we weren't sucessful.
|
||||
recorder.undo();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// this might also be a good point to call resources::controller->check_victory();
|
||||
// because before for example if someone kills all units during a moveto event they don't loose.
|
||||
resources::controller->check_victory();
|
||||
DBG_REPLAY << "run_in_synced_context end\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
void synced_context::default_error_function(const std::string& message, bool /*heavy*/)
|
||||
{
|
||||
ERR_REPLAY << "Very strange Error during synced execution " << message;
|
||||
assert(false && "Very strange Error during synced execution");
|
||||
}
|
||||
|
||||
void synced_context::just_log_error_function(const std::string& message, bool /*heavy*/)
|
||||
{
|
||||
ERR_REPLAY << "Error during synced execution: " << message;
|
||||
}
|
||||
|
||||
void synced_context::ignore_error_function(const std::string& message, bool /*heavy*/)
|
||||
{
|
||||
DBG_REPLAY << "Ignored during synced execution: " << message;
|
||||
}
|
||||
|
||||
synced_context::syced_state synced_context::get_syced_state()
|
||||
{
|
||||
return state_;
|
||||
}
|
||||
|
||||
void synced_context::set_syced_state(syced_state newstate)
|
||||
{
|
||||
state_ = newstate;
|
||||
}
|
||||
|
||||
int synced_context::generate_random_seed()
|
||||
{
|
||||
random_seed_choice cho;
|
||||
config retv_c = synced_context::ask_server("random_seed", cho);
|
||||
return retv_c["new_seed"];
|
||||
}
|
||||
|
||||
void synced_context::pull_remote_user_input()
|
||||
{
|
||||
//code copied form persist_var, feels strange to call ai::.. functions for something where the ai isn't involved....
|
||||
//note that ai::manager::raise_sync_network isn't called by the ai at all anymore (one more reason to put it somehwere else)
|
||||
try
|
||||
{
|
||||
ai::manager::raise_user_interact();
|
||||
}
|
||||
catch(end_turn_exception&)
|
||||
{
|
||||
//ignore, since it will be thwown throw again.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ai::manager::raise_sync_network();
|
||||
}
|
||||
catch(end_turn_exception&)
|
||||
{
|
||||
//ignore, since it will be thwown again.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// in some cases network::receive_data only returns the wanted result on the second try.
|
||||
ai::manager::raise_sync_network();
|
||||
}
|
||||
catch(end_turn_exception&)
|
||||
{
|
||||
//ignore, since it will throw again.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
boost::shared_ptr<random_new::rng> synced_context::get_rng_for(const std::string& commandname)
|
||||
{
|
||||
const std::string/*&*/ mode= ""; // = resources ... gamestate ... get_random_mode()
|
||||
if(mode == "deterministic")
|
||||
{
|
||||
return boost::shared_ptr<random_new::rng>(new random_new::rng_deterministic(resources::gamedata->rng()));
|
||||
}
|
||||
else if (mode == "only_attacks")
|
||||
{
|
||||
/*
|
||||
this is how is was before, it does make sense because random calculation in non attack actions
|
||||
might be not important enough to pay the lag you get with asking the server for a new sed.
|
||||
*/
|
||||
if(commandname == "attack")
|
||||
{
|
||||
return boost::shared_ptr<random_new::rng>(new random_new::synced_rng(generate_random_seed));
|
||||
}
|
||||
else
|
||||
{
|
||||
return boost::shared_ptr<random_new::rng>(new random_new::rng_deterministic(resources::gamedata->rng()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return boost::shared_ptr<random_new::rng>(new random_new::synced_rng(generate_random_seed));
|
||||
}
|
||||
}
|
||||
|
||||
config synced_context::ask_server(const std::string &name, const mp_sync::user_choice &uch)
|
||||
{
|
||||
assert(get_syced_state() == synced_context::SYNCED);
|
||||
|
||||
int current_side = resources::controller->current_side();
|
||||
int side = current_side;
|
||||
bool is_mp_game = network::nconnections() != 0;
|
||||
const int max_side = static_cast<int>(resources::teams->size());
|
||||
bool did_require = false;
|
||||
|
||||
|
||||
if ((*resources::teams)[side-1].is_empty())
|
||||
{
|
||||
/*
|
||||
|
||||
*/
|
||||
DBG_REPLAY << "MP synchronization: side 1 being null-controlled in get_user_choice.\n";
|
||||
side = 1;
|
||||
while ( side <= max_side && (*resources::teams)[side-1].is_empty() )
|
||||
side++;
|
||||
assert(side <= max_side);
|
||||
}
|
||||
|
||||
|
||||
|
||||
assert(1 <= side && side <= max_side);
|
||||
assert(1 <= current_side && current_side <= max_side);
|
||||
|
||||
DBG_REPLAY << "ask_server for :" << name << "\n";
|
||||
/*
|
||||
as soon as random or similar is involved, undoing is impossible.
|
||||
*/
|
||||
resources::undo_stack->clear();
|
||||
/*
|
||||
there might be speak or similar commands in the replay before the user input.
|
||||
*/
|
||||
while(true){
|
||||
|
||||
do_replay_handle(current_side, name);
|
||||
// the current_side on the server is a lie because it can happen on one client we are already executing side 2
|
||||
bool is_local_side = (*resources::teams)[side-1].is_local();
|
||||
bool is_replay_end = get_replay_source().at_end();
|
||||
|
||||
if (is_replay_end && !is_mp_game)
|
||||
{
|
||||
/* The decision is ours, and it will be inserted
|
||||
into the replay. */
|
||||
DBG_REPLAY << "MP synchronization: local server choice\n";
|
||||
config cfg = uch.query_user();
|
||||
//-1 for "server" todo: change that.
|
||||
recorder.user_input(name, cfg, -1);
|
||||
return cfg;
|
||||
|
||||
}
|
||||
else if(is_replay_end && is_mp_game)
|
||||
{
|
||||
DBG_REPLAY << "MP synchronization: remote server choice\n";
|
||||
|
||||
//here we can get into the situation that the decision has already been made but not received yet.
|
||||
synced_context::pull_remote_user_input();
|
||||
|
||||
/*
|
||||
we don't want to send multiple "require_random" to the server.
|
||||
*/
|
||||
if(is_local_side && !did_require)
|
||||
{
|
||||
config data;
|
||||
data.add_child("require_random");
|
||||
network::send_data(data,0);
|
||||
did_require = true;
|
||||
}
|
||||
SDL_Delay(10);
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
else if (!is_replay_end)
|
||||
{
|
||||
/* The decision has already been made, and must
|
||||
be extracted from the replay. */
|
||||
DBG_REPLAY << "MP synchronization: replay server choice\n";
|
||||
do_replay_handle(resources::controller->current_side(), name);
|
||||
const config *action = get_replay_source().get_next_action();
|
||||
if (!action)
|
||||
{
|
||||
replay::process_error("[" + name + "] expected but none found\n");
|
||||
return config();
|
||||
}
|
||||
if (!action->has_child(name))
|
||||
{
|
||||
replay::process_error("[" + name + "] expected but none found, found instead:\n " + action->debug() + "\n");
|
||||
return config();
|
||||
}
|
||||
return action->child(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_scontext_synced::set_scontext_synced(const std::string& commandname)
|
||||
: new_rng_(synced_context::get_rng_for(commandname)), new_checkup_(recorder.get_last_real_command().child_or_add("checkup"))
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
set_scontext_synced::set_scontext_synced()
|
||||
: new_rng_(synced_context::get_rng_for("")), new_checkup_(recorder.get_last_real_command().child_or_add("checkup"))
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
set_scontext_synced::set_scontext_synced(int number)
|
||||
: new_rng_(synced_context::get_rng_for("")), new_checkup_(recorder.get_last_real_command().child_or_add("checkup" + boost::lexical_cast<std::string>(number)))
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
/*
|
||||
so we dont have to write the same code 3 times.
|
||||
*/
|
||||
void set_scontext_synced::init()
|
||||
{
|
||||
|
||||
LOG_REPLAY << "set_scontext_synced::set_scontext_synced\n";
|
||||
assert(synced_context::get_syced_state() == synced_context::UNSYNCED);
|
||||
|
||||
synced_context::set_syced_state(synced_context::SYNCED);
|
||||
|
||||
old_checkup_ = checkup_instance;
|
||||
checkup_instance = & new_checkup_;
|
||||
old_rng_ = random_new::generator;
|
||||
random_new::generator = new_rng_.get();
|
||||
}
|
||||
set_scontext_synced::~set_scontext_synced()
|
||||
{
|
||||
LOG_REPLAY << "set_scontext_synced:: destructor\n";
|
||||
assert(synced_context::get_syced_state() == synced_context::SYNCED);
|
||||
assert(checkup_instance == &new_checkup_);
|
||||
config co;
|
||||
if(!checkup_instance->local_checkup(config_of("random_calls", new_rng_->get_random_calls()), co))
|
||||
{
|
||||
//if we really get -999 we have a very serious OOS.
|
||||
ERR_REPLAY << "We called random " << new_rng_->get_random_calls() << " times, but the original game called random " << co["random_calls"].to_int(-99) << " times.\n";
|
||||
ERR_REPLAY << co.debug() << "\n";
|
||||
}
|
||||
|
||||
random_new::generator = old_rng_;
|
||||
synced_context::set_syced_state(synced_context::UNSYNCED);
|
||||
|
||||
checkup_instance = old_checkup_;
|
||||
}
|
||||
|
||||
int set_scontext_synced::get_random_calls()
|
||||
{
|
||||
return new_rng_->get_random_calls();
|
||||
}
|
||||
|
||||
|
||||
set_scontext_local_choice::set_scontext_local_choice()
|
||||
{
|
||||
|
||||
assert(synced_context::get_syced_state() == synced_context::SYNCED);
|
||||
synced_context::set_syced_state(synced_context::LOCAL_CHOICE);
|
||||
|
||||
|
||||
old_rng_ = random_new::generator;
|
||||
//calling the synced rng form inside a local_choice would cause oos.
|
||||
//TODO use a member variable instead if new/delete
|
||||
random_new::generator = new random_new::rng();
|
||||
}
|
||||
set_scontext_local_choice::~set_scontext_local_choice()
|
||||
{
|
||||
assert(synced_context::get_syced_state() == synced_context::LOCAL_CHOICE);
|
||||
synced_context::set_syced_state(synced_context::SYNCED);
|
||||
delete random_new::generator;
|
||||
random_new::generator = old_rng_;
|
||||
}
|
||||
|
||||
|
||||
|
||||
random_seed_choice::random_seed_choice()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
random_seed_choice::~random_seed_choice()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
config random_seed_choice::query_user() const
|
||||
{
|
||||
//getting here means we are in a sp game
|
||||
|
||||
|
||||
config retv;
|
||||
retv["new_seed"] = rand();
|
||||
return retv;
|
||||
};
|
||||
config random_seed_choice::random_choice() const
|
||||
{
|
||||
//it obviously doesn't make sense to call the uninitialized random generator to generatoe a seed ofr the same random generator;
|
||||
//this shoud never happen
|
||||
assert(false && "random_seed_choice::random_choice called");
|
||||
|
||||
config retv;
|
||||
retv["new_seed"] = 0;
|
||||
return retv;
|
||||
}
|
191
src/synced_context.hpp
Normal file
191
src/synced_context.hpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
|
||||
/*
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
#ifndef SYNCED_CONTEXT_H_INCLUDED
|
||||
#define SYNCED_CONTEXT_H_INCLUDED
|
||||
|
||||
#include <boost/function.hpp>
|
||||
#include "synced_commands.hpp"
|
||||
#include "synced_checkup.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "random_new.hpp"
|
||||
#include "random_new_synced.hpp"
|
||||
#include "generic_event.hpp"
|
||||
#include <boost/shared_ptr.hpp>
|
||||
class config;
|
||||
|
||||
//only static methods.
|
||||
class synced_context
|
||||
{
|
||||
public:
|
||||
enum syced_state {UNSYNCED, SYNCED, LOCAL_CHOICE};
|
||||
|
||||
typedef boost::function1<void, config&> ftype;
|
||||
/**
|
||||
|
||||
Sets the context to 'synced', initialises random context, and calls the given function.
|
||||
The plan is that in replay and in real game the same function is called.
|
||||
|
||||
|
||||
when redoing an action it does not matter whether we run them i synced context because it's save to assume that they do not have any mean effect (otherwise they couldn’t be undone)
|
||||
|
||||
TODO: call clear undo stack in the right places (get_user_choice / ask_server) DONE
|
||||
|
||||
|
||||
|
||||
TODO: implement the "Deterministic SP Mode".
|
||||
TODO: implement access to get_syced_state() in lua. DONE.
|
||||
TODO: repair checkups in attacks. DONE
|
||||
TODO: handle layer leaving during get_user_choice correctly DONE
|
||||
|
||||
|
||||
TODO: move_unit currently ignores when the unit moves further than it can,
|
||||
it would be good to give an oos error especially to notice cheating in mp.
|
||||
|
||||
TODO: movement commands are currently treated specially,
|
||||
thats because actions::move_unit returns a value and some function use that value.
|
||||
maybe i should add a way here to return a value.
|
||||
|
||||
TODO: move_unit tried to preserve the replay at all costs, the disadvantage is
|
||||
1)we notice OOS later,
|
||||
2)we don't notice another player cheating in MP,
|
||||
3)i forgot
|
||||
4)we make the code more complicated
|
||||
my proposed solution would be to execute the command normally
|
||||
and then just set the unit to the replay destination after we gave an OOS error.
|
||||
|
||||
TODO: undos are crrently recorded AFTER the action takes place,
|
||||
that means you cannot disallow an action during it's execution by calling undo_stack->clear();
|
||||
it would make things easier if that was possible.
|
||||
|
||||
TODO: the ai can currently not decide how units advance.
|
||||
TODO: don't change comments in this header everytime i do something because that causes rebuild of other files ...
|
||||
|
||||
@param save_in_replay whether data should be saved in replay.
|
||||
@param use_undo this parameter is used to ignore undos during an ai move to optimize.
|
||||
@param store_in_replay only true if called by do_replay_handle
|
||||
@param error_handler an error handler for the case that data contains invalid data.
|
||||
|
||||
@return true if the action was successful.
|
||||
|
||||
|
||||
|
||||
*/
|
||||
static bool run_in_synced_context(const std::string& commandname,const config& data, bool use_undo = true, bool show = true , bool store_in_replay = true , synced_command::error_handler_function error_handler = default_error_function);
|
||||
/*
|
||||
Returns whether we are currently executing a synced action like recruit, start, recall, disband, movement, attack, init_side, end_turn, fire_event, lua_ai, auto_shroud or similar.
|
||||
*/
|
||||
static syced_state get_syced_state();
|
||||
/*
|
||||
should only be called form set_scontext_synced, set_scontext_local_choice
|
||||
*/
|
||||
static void set_syced_state(syced_state newstate);
|
||||
/*
|
||||
Generates a new seed for a synced event, by asking the 'server'
|
||||
*/
|
||||
static int generate_random_seed();
|
||||
/*
|
||||
called from get_user_choice;
|
||||
*/
|
||||
static void pull_remote_user_input();
|
||||
/*
|
||||
a function to be passed to run_in_synced_context to assert false on error (the default).
|
||||
*/
|
||||
static void default_error_function(const std::string& message, bool heavy);
|
||||
/*
|
||||
a function to be passed to run_in_synced_context to log the error.
|
||||
*/
|
||||
static void just_log_error_function(const std::string& message, bool heavy);
|
||||
/*
|
||||
a function to be passed to run_in_synced_context to ignore the error.
|
||||
*/
|
||||
static void ignore_error_function(const std::string& message, bool heavy);
|
||||
/*
|
||||
returns a rng_deterministic if in determinsic mode otherwise a rng_synced.
|
||||
*/
|
||||
static boost::shared_ptr<random_new::rng> get_rng_for(const std::string& commandname);
|
||||
private:
|
||||
/*
|
||||
similar to get_user_choice but asks the server instead of a user.
|
||||
*/
|
||||
static config ask_server(const std::string &name, const mp_sync::user_choice &uch);
|
||||
/*
|
||||
weather we are in a synced move, in a user_choice, or none of them
|
||||
*/
|
||||
static syced_state state_;
|
||||
/*
|
||||
as soon as get_user_choice is used with side != current_side (for example in generate_random_seed) other sides execute the command simultaneously and is_simultaneously is set to true
|
||||
is impossible to undo that.
|
||||
|
||||
also during the execution of networked turns this should always be true.
|
||||
false = we are on a local turn and havent sended anything yet.
|
||||
|
||||
this variable is currently not used, the original plan was to use it to send data as soon as possible if is_simultaneously_= true.
|
||||
*/
|
||||
static bool is_simultaneously_;
|
||||
/*
|
||||
TODO: replace ai::manager::raise_sync_network with this event because ai::manager::raise_sync_network has nothing to do with ai anymore.
|
||||
*/
|
||||
static events::generic_event remote_user_input_required_;
|
||||
};
|
||||
|
||||
/*
|
||||
a RAII object to enter the synced context, cannot be called if we are already in a synced context.
|
||||
*/
|
||||
class set_scontext_synced
|
||||
{
|
||||
public:
|
||||
set_scontext_synced(const std::string& commanname);
|
||||
set_scontext_synced();
|
||||
/*
|
||||
use this if you have multiple synced_context but only one replay entry.
|
||||
*/
|
||||
set_scontext_synced(int num);
|
||||
~set_scontext_synced();
|
||||
int get_random_calls();
|
||||
private:
|
||||
//only called by contructors.
|
||||
void init();
|
||||
random_new::rng* old_rng_;
|
||||
boost::shared_ptr<random_new::rng> new_rng_;
|
||||
checkup* old_checkup_;
|
||||
synced_checkup new_checkup_;
|
||||
};
|
||||
|
||||
/*
|
||||
a RAII object to temporary leave the synced context like in wesnoth.synchronize_choice. Can only be used from inside a synced context.
|
||||
*/
|
||||
|
||||
class set_scontext_local_choice
|
||||
{
|
||||
public:
|
||||
set_scontext_local_choice();
|
||||
~set_scontext_local_choice();
|
||||
private:
|
||||
random_new::rng* old_rng_;
|
||||
};
|
||||
|
||||
/*
|
||||
a helper object to server random seed generation.
|
||||
*/
|
||||
class random_seed_choice : public mp_sync::user_choice
|
||||
{
|
||||
public:
|
||||
random_seed_choice();
|
||||
virtual ~random_seed_choice();
|
||||
virtual config query_user() const;
|
||||
virtual config random_choice() const;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue