Refactor undo stack
This commit splits undo actions into multiple smaller steps. The main advantages: - Its allows us always undo the parts of an action in the reverse order of which they originally happen, preventing bugs that could be caused if the parts interact with each other in a way where the order matters (like for example an [on_undo] resetting a units moves, when the event happend in a move action which would then also reset the units moves on undoing). - It's easier to add (c++) undo steps for specific steps even if they are used from wml, like spending gold. - [do_command] no longer confused the undo stack (in fact it's by default now undoable). - All actions are put onto the undo stack in the same way, previously it was often unclear whether the undo stack is empty during a specific step during the execution of an action, in particular when people tried to use `undo_stack->empty()` to check whether an action could still be undone.
This commit is contained in:
parent
d8086942df
commit
8350f48c88
19 changed files with 426 additions and 456 deletions
|
@ -54,60 +54,6 @@ static lg::log_domain log_engine("engine");
|
|||
namespace actions {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates an undo_action based on a config.
|
||||
* @return a pointer that must be deleted, or nullptr if the @a cfg could not be parsed.
|
||||
*/
|
||||
std::unique_ptr<undo_action_base> undo_list::create_action(const config & cfg)
|
||||
{
|
||||
const std::string str = cfg["type"];
|
||||
// The general division of labor in this function is that the various
|
||||
// constructors will parse the "unit" child config, while this function
|
||||
// parses everything else.
|
||||
|
||||
if ( str == "move" ) {
|
||||
return std::make_unique<undo::move_action>(cfg, cfg.child_or_empty("unit"),
|
||||
cfg["starting_moves"].to_int(),
|
||||
map_location::parse_direction(cfg["starting_direction"]));
|
||||
}
|
||||
|
||||
else if ( str == "recruit" ) {
|
||||
// Validate the unit type.
|
||||
const config & child = cfg.mandatory_child("unit");
|
||||
const unit_type * u_type = unit_types.find(child["type"]);
|
||||
|
||||
if ( !u_type ) {
|
||||
// Bad data.
|
||||
ERR_NG << "Invalid recruit found in [undo] or [redo]; unit type '"
|
||||
<< child["type"] << "' was not found.\n";
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<undo::recruit_action>(cfg, *u_type, map_location(cfg.child_or_empty("leader"), nullptr));
|
||||
}
|
||||
|
||||
else if ( str == "recall" )
|
||||
return std::make_unique<undo::recall_action>(cfg, map_location(cfg.child_or_empty("leader"), nullptr));
|
||||
|
||||
else if ( str == "dismiss" )
|
||||
return std::make_unique<undo::dismiss_action>(cfg, cfg.mandatory_child("unit"));
|
||||
|
||||
else if ( str == "auto_shroud" )
|
||||
return std::make_unique<undo::auto_shroud_action>(cfg["active"].to_bool());
|
||||
|
||||
else if ( str == "update_shroud" )
|
||||
return std::make_unique<undo::update_shroud_action>();
|
||||
else if ( str == "dummy" )
|
||||
return std::make_unique<undo_dummy_action>(cfg);
|
||||
else
|
||||
{
|
||||
// Unrecognized type.
|
||||
ERR_NG << "Unrecognized undo action type: " << str << ".";
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* The config is allowed to be invalid.
|
||||
|
@ -135,10 +81,6 @@ void undo_list::add_auto_shroud(bool turned_on)
|
|||
add(std::make_unique<undo::auto_shroud_action>(turned_on));
|
||||
}
|
||||
|
||||
void undo_list::add_dummy()
|
||||
{
|
||||
add(std::make_unique<undo_dummy_action>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dismissal to the undo stack.
|
||||
|
@ -178,16 +120,6 @@ void undo_list::add_recruit(const unit_const_ptr u, const map_location& loc,
|
|||
add(std::make_unique<undo::recruit_action>(u, loc, from, orig_village_owner, time_bonus));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a shroud update to the undo stack.
|
||||
* This is called from within commit_vision(), so there should be no need
|
||||
* for this to be publicly visible.
|
||||
*/
|
||||
void undo_list::add_update_shroud()
|
||||
{
|
||||
add(std::make_unique<undo::update_shroud_action>());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clears the stack of undoable (and redoable) actions.
|
||||
|
@ -267,39 +199,33 @@ void undo_list::new_side_turn(int side)
|
|||
void undo_list::read(const config & cfg)
|
||||
{
|
||||
// Merge header data.
|
||||
// TODO: remove side parameter, its already stored in [snapshot].
|
||||
side_ = cfg["side"].to_int(side_);
|
||||
committed_actions_ = committed_actions_ || cfg["committed"].to_bool();
|
||||
|
||||
// Build the undo stack.
|
||||
for (const config & child : cfg.child_range("undo")) {
|
||||
try {
|
||||
if(auto action = create_action(child)) {
|
||||
undos_.push_back(std::move(action));
|
||||
}
|
||||
} catch (const bad_lexical_cast &) {
|
||||
ERR_NG << "Error when parsing undo list from config: bad lexical cast.";
|
||||
ERR_NG << "config was: " << child.debug();
|
||||
ERR_NG << "Skipping this undo action...";
|
||||
} catch (const config::error& e) {
|
||||
ERR_NG << "Error when parsing undo list from config: " << e.what();
|
||||
ERR_NG << "config was: " << child.debug();
|
||||
ERR_NG << "Skipping this undo action...";
|
||||
try {
|
||||
for(const config& child : cfg.child_range("undo")) {
|
||||
undos_.push_back(std::make_unique<undo_action_container>());
|
||||
undos_.back()->read(child);
|
||||
}
|
||||
} catch(const bad_lexical_cast&) {
|
||||
//It ddoenst make sense to "skip" actions in the undo stakc since that would just result in errors later.
|
||||
ERR_NG << "Error when parsing undo list from config: bad lexical cast.";
|
||||
ERR_NG << "config was: " << cfg.debug();
|
||||
ERR_NG << "discardind undo stack...";
|
||||
undos_.clear();
|
||||
} catch(const config::error& e) {
|
||||
ERR_NG << "Error when parsing undo list from config: " << e.what();
|
||||
ERR_NG << "config was: " << cfg.debug();
|
||||
ERR_NG << "discardind undo stack...";
|
||||
undos_.clear();
|
||||
}
|
||||
|
||||
|
||||
// Build the redo stack.
|
||||
for (const config & child : cfg.child_range("redo")) {
|
||||
try {
|
||||
redos_.emplace_back(new config(child));
|
||||
} catch (const bad_lexical_cast &) {
|
||||
ERR_NG << "Error when parsing redo list from config: bad lexical cast.";
|
||||
ERR_NG << "config was: " << child.debug();
|
||||
ERR_NG << "Skipping this redo action...";
|
||||
} catch (const config::error& e) {
|
||||
ERR_NG << "Error when parsing redo list from config: " << e.what();
|
||||
ERR_NG << "config was: " << child.debug();
|
||||
ERR_NG << "Skipping this redo action...";
|
||||
}
|
||||
redos_.emplace_back(new config(child));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,6 +246,32 @@ void undo_list::write(config & cfg) const
|
|||
}
|
||||
|
||||
|
||||
void undo_list::init_action()
|
||||
{
|
||||
current_ = std::make_unique<undo_action_container>();
|
||||
redos_.clear();
|
||||
}
|
||||
|
||||
|
||||
void undo_list::finish_action(bool can_undo)
|
||||
{
|
||||
if(current_) {
|
||||
current_->set_unit_id_diff(synced_context::get_unit_id_diff());
|
||||
undos_.emplace_back(std::move(current_));
|
||||
if(!can_undo) {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void undo_list::cleanup_action()
|
||||
{
|
||||
// This in particular makes sure no commands that do nothing stay on the undo stack but also on the recorder
|
||||
// in particular so that menu items that did nothing because the user aborted in a custom menu dont persist on the replay.
|
||||
if(!undos_.empty() && undos_.back()->empty()) {
|
||||
undo();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Undoes the top action on the undo stack.
|
||||
*/
|
||||
|
@ -330,49 +282,25 @@ void undo_list::undo()
|
|||
|
||||
const events::command_disabler disable_commands;
|
||||
|
||||
game_display & gui = *game_display::get_singleton();
|
||||
|
||||
// Get the action to undo. (This will be placed on the redo stack, but
|
||||
// only if the undo is successful.)
|
||||
auto action = std::move(undos_.back());
|
||||
if (undo_action* undoable_action = dynamic_cast<undo_action*>(action.get()))
|
||||
{
|
||||
int last_unit_id = resources::gameboard->unit_id_manager().get_save_id();
|
||||
if ( !undoable_action->undo(side_) ) {
|
||||
return;
|
||||
}
|
||||
if(last_unit_id - undoable_action->unit_id_diff < 0) {
|
||||
ERR_NG << "Next unit id is below 0 after undoing";
|
||||
}
|
||||
resources::gameboard->unit_id_manager().set_save_id(last_unit_id - undoable_action->unit_id_diff);
|
||||
|
||||
// Bookkeeping.
|
||||
undos_.pop_back();
|
||||
redos_.emplace_back(new config());
|
||||
resources::recorder->undo_cut(*redos_.back());
|
||||
|
||||
resources::whiteboard->on_gamestate_change();
|
||||
|
||||
// Screen updates.
|
||||
gui.invalidate_unit();
|
||||
gui.invalidate_game_status();
|
||||
gui.redraw_minimap();
|
||||
}
|
||||
else
|
||||
{
|
||||
//ignore this action, and undo the previous one.
|
||||
undos_.pop_back();
|
||||
config replay_data;
|
||||
resources::recorder->undo_cut(replay_data);
|
||||
undo();
|
||||
resources::recorder->redo(replay_data);
|
||||
undos_.emplace_back(std::move(action));
|
||||
}
|
||||
if(std::all_of(undos_.begin(), undos_.end(), [](const action_ptr_t& action){ return dynamic_cast<undo_action*>(action.get()) == nullptr; }))
|
||||
{
|
||||
//clear the undo stack if it only contains dsu related actions, this in particular makes sure loops like `while(can_undo()) { undo(); }`always stop.
|
||||
undos_.clear();
|
||||
if(!action->undo(side_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bookkeeping.
|
||||
undos_.pop_back();
|
||||
redos_.emplace_back(new config());
|
||||
resources::recorder->undo_cut(*redos_.back());
|
||||
|
||||
resources::whiteboard->on_gamestate_change();
|
||||
|
||||
// Screen updates.
|
||||
game_display& gui = *game_display::get_singleton();
|
||||
gui.invalidate_unit();
|
||||
gui.invalidate_game_status();
|
||||
gui.redraw_minimap();
|
||||
}
|
||||
|
||||
|
||||
|
@ -441,17 +369,20 @@ bool undo_list::apply_shroud_changes() const
|
|||
|
||||
// Loop through the list of undo_actions.
|
||||
for( std::size_t i = 0; i != list_size; ++i ) {
|
||||
if (const shroud_clearing_action* action = dynamic_cast<const shroud_clearing_action*>(undos_[i].get())) {
|
||||
LOG_NG << "Turning an undo...";
|
||||
// Loop through the staps of the action.
|
||||
for(auto& step_ptr : undos_[i]->steps()) {
|
||||
if(const shroud_clearing_action* action = dynamic_cast<const shroud_clearing_action*>(step_ptr.get())) {
|
||||
LOG_NG << "Turning an undo...";
|
||||
|
||||
// Clear the hexes this unit can see from each hex occupied during
|
||||
// the action.
|
||||
std::vector<map_location>::const_iterator step;
|
||||
for (step = action->route.begin(); step != action->route.end(); ++step) {
|
||||
// Clear the shroud, collecting new sighted events.
|
||||
// (This can be made gradual by changing "true" to "false".)
|
||||
if ( clearer.clear_unit(*step, tm, action->view_info, true) ) {
|
||||
cleared_shroud = true;
|
||||
// Clear the hexes this unit can see from each hex occupied during
|
||||
// the action.
|
||||
std::vector<map_location>::const_iterator step;
|
||||
for(step = action->route.begin(); step != action->route.end(); ++step) {
|
||||
// Clear the shroud, collecting new sighted events.
|
||||
// (This can be made gradual by changing "true" to "false".)
|
||||
if(clearer.clear_unit(*step, tm, action->view_info, true)) {
|
||||
cleared_shroud = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace actions {
|
|||
/** Class to store the actions that a player can undo and redo. */
|
||||
class undo_list {
|
||||
|
||||
typedef std::unique_ptr<undo_action_base> action_ptr_t;
|
||||
typedef std::unique_ptr<undo_action_container> action_ptr_t;
|
||||
typedef std::vector<action_ptr_t> action_list;
|
||||
typedef std::vector<std::unique_ptr<config>> redos_list;
|
||||
|
||||
|
@ -44,18 +44,10 @@ public:
|
|||
undo_list();
|
||||
~undo_list();
|
||||
|
||||
/**
|
||||
* Creates an undo_action based on a config.
|
||||
* Throws bad_lexical_cast or config::error if it cannot parse the config properly.
|
||||
*/
|
||||
static std::unique_ptr<undo_action_base> create_action(const config & cfg);
|
||||
|
||||
// Functions related to managing the undo stack:
|
||||
|
||||
/** Adds an auto-shroud toggle to the undo stack. */
|
||||
void add_auto_shroud(bool turned_on);
|
||||
/** Adds an auto-shroud toggle to the undo stack. */
|
||||
void add_dummy();
|
||||
/** Adds a dismissal to the undo stack. */
|
||||
void add_dismissal(const unit_const_ptr u);
|
||||
/** Adds a move to the undo stack. */
|
||||
|
@ -70,8 +62,12 @@ public:
|
|||
/** Adds a recruit to the undo stack. */
|
||||
void add_recruit(const unit_const_ptr u, const map_location& loc,
|
||||
const map_location& from, int orig_village_owner, bool time_bonus);
|
||||
/** Adds a shroud update to the undo stack. */
|
||||
void add_update_shroud();
|
||||
|
||||
template<class T, class... Args>
|
||||
void add_custom(Args&&... args)
|
||||
{
|
||||
add(std::make_unique<T>(std::forward<Args>(args)...));
|
||||
}
|
||||
private:
|
||||
public:
|
||||
/** Clears the stack of undoable (and redoable) actions. */
|
||||
|
@ -99,19 +95,36 @@ public:
|
|||
bool can_undo() const { return !undos_.empty(); }
|
||||
/** True if there are actions that can be redone. */
|
||||
bool can_redo() const { return !redos_.empty(); }
|
||||
|
||||
/** called before a user action, starts collecting undo steps for the new action. */
|
||||
void init_action();
|
||||
/** called after a user action, pushes the collected undo steps on the undo stack. */
|
||||
void finish_action(bool can_undo);
|
||||
/** called after a user action, removes empty actions */
|
||||
void cleanup_action();
|
||||
/** Undoes the top action on the undo stack. */
|
||||
void undo();
|
||||
/** Redoes the top action on the redo stack. */
|
||||
void redo();
|
||||
|
||||
private: // functions
|
||||
/** Adds an action to the undo stack. */
|
||||
void add(std::unique_ptr<undo_action_base>&& action)
|
||||
{ undos_.push_back(std::move(action)); redos_.clear(); }
|
||||
undo_action_container* get_current()
|
||||
{
|
||||
return current_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
/** Adds an undo step to the current action. */
|
||||
void add(std::unique_ptr<undo_action>&& action)
|
||||
{
|
||||
if(current_) {
|
||||
current_->add(std::move(action));
|
||||
}
|
||||
}
|
||||
/** Applies the pending fog/shroud changes from the undo stack. */
|
||||
bool apply_shroud_changes() const;
|
||||
|
||||
private: // data
|
||||
action_ptr_t current_;
|
||||
action_list undos_;
|
||||
redos_list redos_;
|
||||
|
||||
|
|
|
@ -15,20 +15,78 @@
|
|||
|
||||
#include "actions/undo_action.hpp"
|
||||
#include "game_board.hpp"
|
||||
#include "log.hpp" // for LOG_STREAM, logger, etc
|
||||
#include "scripting/game_lua_kernel.hpp"
|
||||
#include "resources.hpp"
|
||||
#include "variable.hpp" // vconfig
|
||||
#include "game_data.hpp"
|
||||
#include "units/unit.hpp"
|
||||
#include "utils/ranges.hpp"
|
||||
#include "sound.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
|
||||
static lg::log_domain log_engine("engine");
|
||||
#define ERR_NG LOG_STREAM(err, log_engine)
|
||||
#define LOG_NG LOG_STREAM(info, log_engine)
|
||||
|
||||
|
||||
namespace actions
|
||||
{
|
||||
|
||||
|
||||
undo_action_container::undo_action_container()
|
||||
: steps_()
|
||||
, unit_id_diff_(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool undo_action_container::undo(int side)
|
||||
{
|
||||
int last_unit_id = resources::gameboard->unit_id_manager().get_save_id();
|
||||
for(auto& p_step : utils::reversed_view(steps_)) {
|
||||
p_step->undo(side);
|
||||
}
|
||||
if(last_unit_id - unit_id_diff_ < 0) {
|
||||
ERR_NG << "Next unit id is below 0 after undoing";
|
||||
}
|
||||
resources::gameboard->unit_id_manager().set_save_id(last_unit_id - unit_id_diff_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void undo_action_container::add(t_step_ptr&& action)
|
||||
{
|
||||
steps_.emplace_back(std::move(action));
|
||||
}
|
||||
|
||||
|
||||
void undo_action_container::read(const config& cfg)
|
||||
{
|
||||
for(const config& step : cfg.child_range("step")) {
|
||||
auto& factory = get_factories()[step["type"]];
|
||||
add(factory(step));
|
||||
}
|
||||
}
|
||||
void undo_action_container::write(config& cfg)
|
||||
{
|
||||
for(auto& p_step : steps_) {
|
||||
p_step->write(cfg.add_child("step"));
|
||||
}
|
||||
}
|
||||
|
||||
undo_action_container::t_factory_map& undo_action_container::get_factories()
|
||||
{
|
||||
static t_factory_map res;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
undo_event::undo_event(int fcn_idx, const config& args, const game_events::queued_event& ctx)
|
||||
: lua_idx(fcn_idx)
|
||||
, commands(args)
|
||||
|
@ -84,125 +142,101 @@ undo_event::undo_event(const config& first, const config& second, const config&
|
|||
{
|
||||
}
|
||||
|
||||
undo_action::undo_action()
|
||||
: undo_action_base()
|
||||
, unit_id_diff(synced_context::get_unit_id_diff())
|
||||
undo_event::undo_event(const config& cfg)
|
||||
: undo_event(cfg.child_or_empty("filter"),
|
||||
cfg.child_or_empty("filter_second"),
|
||||
cfg.child_or_empty("data"),
|
||||
cfg.child_or_empty("command"))
|
||||
{
|
||||
auto& undo = synced_context::get_undo_commands();
|
||||
auto command_transformer = [](const synced_context::event_info& p) {
|
||||
if(p.lua_.has_value()) {
|
||||
return undo_event(*p.lua_, p.cmds_, p.evt_);
|
||||
} else {
|
||||
return undo_event(p.cmds_, p.evt_);
|
||||
}
|
||||
};
|
||||
std::transform(undo.begin(), undo.end(), std::back_inserter(umc_commands_undo), command_transformer);
|
||||
undo.clear();
|
||||
}
|
||||
|
||||
undo_action::undo_action(const config& cfg)
|
||||
: undo_action_base()
|
||||
, unit_id_diff(cfg["unit_id_diff"].to_int())
|
||||
{
|
||||
read_event_vector(umc_commands_undo, cfg, "undo_actions");
|
||||
}
|
||||
|
||||
namespace {
|
||||
unit_ptr get_unit(std::size_t uid, const std::string& id) {
|
||||
assert(resources::gameboard);
|
||||
auto iter = resources::gameboard->units().find(uid);
|
||||
if(!iter.valid() || iter->id() != id) {
|
||||
return nullptr;
|
||||
}
|
||||
return iter.get_shared_ptr();
|
||||
}
|
||||
void execute_event(const undo_event& e, std::string tag) {
|
||||
assert(resources::lua_kernel);
|
||||
assert(resources::gamedata);
|
||||
|
||||
config::attribute_value& x1 = resources::gamedata->get_variable("x1");
|
||||
config::attribute_value& y1 = resources::gamedata->get_variable("y1");
|
||||
config::attribute_value& x2 = resources::gamedata->get_variable("x2");
|
||||
config::attribute_value& y2 = resources::gamedata->get_variable("y2");
|
||||
int oldx1 = x1.to_int(), oldy1 = y1.to_int(), oldx2 = x2.to_int(), oldy2 = y2.to_int();
|
||||
x1 = e.filter_loc1.wml_x(); y1 = e.filter_loc1.wml_y();
|
||||
x2 = e.filter_loc2.wml_x(); y2 = e.filter_loc2.wml_y();
|
||||
|
||||
std::unique_ptr<scoped_xy_unit> u1, u2;
|
||||
if(unit_ptr who = get_unit(e.uid1, e.id1)) {
|
||||
u1.reset(new scoped_xy_unit("unit", who->get_location(), resources::gameboard->units()));
|
||||
}
|
||||
if(unit_ptr who = get_unit(e.uid2, e.id2)) {
|
||||
u2.reset(new scoped_xy_unit("unit", who->get_location(), resources::gameboard->units()));
|
||||
}
|
||||
|
||||
scoped_weapon_info w1("weapon", e.data.optional_child("first"));
|
||||
scoped_weapon_info w2("second_weapon", e.data.optional_child("second"));
|
||||
|
||||
game_events::queued_event q(tag, "", map_location(x1, y1, wml_loc()), map_location(x2, y2, wml_loc()), e.data);
|
||||
if(e.lua_idx.has_value()) {
|
||||
resources::lua_kernel->run_wml_event(*e.lua_idx, vconfig(e.commands), q);
|
||||
} else {
|
||||
resources::lua_kernel->run_wml_action("command", vconfig(e.commands), q);
|
||||
}
|
||||
sound::commit_music_changes();
|
||||
|
||||
x1 = oldx1; y1 = oldy1;
|
||||
x2 = oldx2; y2 = oldy2;
|
||||
namespace
|
||||
{
|
||||
unit_ptr get_unit(std::size_t uid, const std::string& id)
|
||||
{
|
||||
assert(resources::gameboard);
|
||||
auto iter = resources::gameboard->units().find(uid);
|
||||
if(!iter.valid() || iter->id() != id) {
|
||||
return nullptr;
|
||||
}
|
||||
return iter.get_shared_ptr();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void undo_action::execute_undo_umc_wml()
|
||||
bool undo_event::undo(int)
|
||||
{
|
||||
for(const undo_event& e : umc_commands_undo)
|
||||
{
|
||||
execute_event(e, "undo");
|
||||
undo_event& e = *this;
|
||||
std::string tag = "undo";
|
||||
assert(resources::lua_kernel);
|
||||
assert(resources::gamedata);
|
||||
|
||||
config::attribute_value& x1 = resources::gamedata->get_variable("x1");
|
||||
config::attribute_value& y1 = resources::gamedata->get_variable("y1");
|
||||
config::attribute_value& x2 = resources::gamedata->get_variable("x2");
|
||||
config::attribute_value& y2 = resources::gamedata->get_variable("y2");
|
||||
int oldx1 = x1.to_int(), oldy1 = y1.to_int(), oldx2 = x2.to_int(), oldy2 = y2.to_int();
|
||||
x1 = e.filter_loc1.wml_x();
|
||||
y1 = e.filter_loc1.wml_y();
|
||||
x2 = e.filter_loc2.wml_x();
|
||||
y2 = e.filter_loc2.wml_y();
|
||||
|
||||
std::unique_ptr<scoped_xy_unit> u1, u2;
|
||||
if(unit_ptr who = get_unit(e.uid1, e.id1)) {
|
||||
u1.reset(new scoped_xy_unit("unit", who->get_location(), resources::gameboard->units()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void undo_action::write(config & cfg) const
|
||||
{
|
||||
cfg["unit_id_diff"] = unit_id_diff;
|
||||
write_event_vector(umc_commands_undo, cfg, "undo_actions");
|
||||
undo_action_base::write(cfg);
|
||||
}
|
||||
|
||||
void undo_action::read_event_vector(event_vector& vec, const config& cfg, const std::string& tag)
|
||||
{
|
||||
for(auto c : cfg.child_range(tag)) {
|
||||
vec.emplace_back(c.child_or_empty("filter"), c.child_or_empty("filter_second"), c.child_or_empty("data"), c.child_or_empty("command"));
|
||||
if(unit_ptr who = get_unit(e.uid2, e.id2)) {
|
||||
u2.reset(new scoped_xy_unit("unit", who->get_location(), resources::gameboard->units()));
|
||||
}
|
||||
}
|
||||
|
||||
void undo_action::write_event_vector(const event_vector& vec, config& cfg, const std::string& tag)
|
||||
{
|
||||
for(const auto& evt : vec)
|
||||
{
|
||||
if(evt.lua_idx.has_value()) {
|
||||
// TODO: Log warning that this cannot be serialized
|
||||
continue;
|
||||
}
|
||||
config& entry = cfg.add_child(tag);
|
||||
config& first = entry.add_child("filter");
|
||||
config& second = entry.add_child("filter_second");
|
||||
entry.add_child("data", evt.data);
|
||||
entry.add_child("command", evt.commands);
|
||||
// First location
|
||||
first["filter_x"] = evt.filter_loc1.wml_x();
|
||||
first["filter_y"] = evt.filter_loc1.wml_y();
|
||||
first["underlying_id"] = evt.uid1;
|
||||
first["id"] = evt.id1;
|
||||
first["x"] = evt.loc1.wml_x();
|
||||
first["y"] = evt.loc1.wml_y();
|
||||
// Second location
|
||||
second["filter_x"] = evt.filter_loc2.wml_x();
|
||||
second["filter_y"] = evt.filter_loc2.wml_y();
|
||||
second["underlying_id"] = evt.uid2;
|
||||
second["id"] = evt.id2;
|
||||
second["x"] = evt.loc2.wml_x();
|
||||
second["y"] = evt.loc2.wml_y();
|
||||
scoped_weapon_info w1("weapon", e.data.optional_child("first"));
|
||||
scoped_weapon_info w2("second_weapon", e.data.optional_child("second"));
|
||||
|
||||
game_events::queued_event q(tag, "", map_location(x1, y1, wml_loc()), map_location(x2, y2, wml_loc()), e.data);
|
||||
if(e.lua_idx.has_value()) {
|
||||
resources::lua_kernel->run_wml_event(*e.lua_idx, vconfig(e.commands), q);
|
||||
} else {
|
||||
resources::lua_kernel->run_wml_action("command", vconfig(e.commands), q);
|
||||
}
|
||||
sound::commit_music_changes();
|
||||
|
||||
x1 = oldx1;
|
||||
y1 = oldy1;
|
||||
x2 = oldx2;
|
||||
y2 = oldy2;
|
||||
return true;
|
||||
}
|
||||
|
||||
void undo_event::write(config& cfg) const
|
||||
{
|
||||
undo_action::write(cfg);
|
||||
auto& evt = *this;
|
||||
if(evt.lua_idx.has_value()) {
|
||||
// TODO: Log warning that this cannot be serialized
|
||||
return;
|
||||
}
|
||||
config& entry = cfg;
|
||||
config& first = entry.add_child("filter");
|
||||
config& second = entry.add_child("filter_second");
|
||||
entry.add_child("data", evt.data);
|
||||
entry.add_child("command", evt.commands);
|
||||
// First location
|
||||
first["filter_x"] = evt.filter_loc1.wml_x();
|
||||
first["filter_y"] = evt.filter_loc1.wml_y();
|
||||
first["underlying_id"] = evt.uid1;
|
||||
first["id"] = evt.id1;
|
||||
first["x"] = evt.loc1.wml_x();
|
||||
first["y"] = evt.loc1.wml_y();
|
||||
// Second location
|
||||
second["filter_x"] = evt.filter_loc2.wml_x();
|
||||
second["filter_y"] = evt.filter_loc2.wml_y();
|
||||
second["underlying_id"] = evt.uid2;
|
||||
second["id"] = evt.id2;
|
||||
second["x"] = evt.loc2.wml_x();
|
||||
second["y"] = evt.loc2.wml_y();
|
||||
}
|
||||
|
||||
|
||||
static auto red_undo_event = undo_action_container::subaction_factory<undo_event>();
|
||||
|
||||
} // namespace actions
|
||||
|
|
|
@ -22,99 +22,102 @@
|
|||
|
||||
namespace actions {
|
||||
|
||||
struct undo_event {
|
||||
utils::optional<int> lua_idx;
|
||||
config commands, data;
|
||||
map_location loc1, loc2, filter_loc1, filter_loc2;
|
||||
std::size_t uid1, uid2;
|
||||
std::string id1, id2;
|
||||
undo_event(int fcn_idx, const config& args, const game_events::queued_event& ctx);
|
||||
undo_event(const config& cmds, const game_events::queued_event& ctx);
|
||||
undo_event(const config& first, const config& second, const config& weapons, const config& cmds);
|
||||
struct undo_action;
|
||||
|
||||
/*
|
||||
* Contains all steps that are needed to undo a user action.
|
||||
*/
|
||||
class undo_action_container
|
||||
{
|
||||
using t_step_ptr = std::unique_ptr<undo_action>;
|
||||
using t_steps = std::vector<t_step_ptr>;
|
||||
|
||||
t_steps steps_;
|
||||
int unit_id_diff_;
|
||||
|
||||
public:
|
||||
|
||||
undo_action_container();
|
||||
|
||||
bool empty() const { return steps_.empty(); }
|
||||
t_steps& steps() { return steps_; }
|
||||
bool undo(int side);
|
||||
void write(config& cfg);
|
||||
/**
|
||||
* Creates the list of undo steps based on a config.
|
||||
* Throws bad_lexical_cast or config::error if it cannot parse the config properly.
|
||||
*/
|
||||
void read(const config& cfg);
|
||||
void add(t_step_ptr&& action);
|
||||
void set_unit_id_diff(int id_diff)
|
||||
{
|
||||
unit_id_diff_ = id_diff;
|
||||
}
|
||||
|
||||
using t_factory = std::function<t_step_ptr(const config&)>;
|
||||
using t_factory_map = std::map<std::string, t_factory>;
|
||||
static t_factory_map& get_factories();
|
||||
|
||||
template<typename T>
|
||||
struct subaction_factory
|
||||
{
|
||||
subaction_factory()
|
||||
{
|
||||
std::string name = T::get_type_impl();
|
||||
get_factories()[name] = [](const config& cfg) {
|
||||
auto res = std::make_unique<T>(cfg);
|
||||
return res;
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Records information to be able to undo an action.
|
||||
* Each type of action gets its own derived type.
|
||||
* Base class for all entries in the undo stack, also contains non undoable actions like update_shroud or auto_shroud.
|
||||
* Each type of step gets its own derived type.
|
||||
*/
|
||||
struct undo_action_base
|
||||
struct undo_action
|
||||
{
|
||||
undo_action_base(const undo_action_base&) = delete;
|
||||
undo_action_base& operator=(const undo_action_base&) = delete;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
* This is the only way to get nullptr view_info.
|
||||
*/
|
||||
undo_action_base()
|
||||
{ }
|
||||
// Virtual destructor to support derived classes.
|
||||
virtual ~undo_action_base() {}
|
||||
|
||||
/** Writes this into the provided config. */
|
||||
virtual void write(config & cfg) const
|
||||
{
|
||||
cfg["type"] = this->get_type();
|
||||
}
|
||||
|
||||
virtual const char* get_type() const = 0;
|
||||
};
|
||||
|
||||
/** actions that are undoable (this does not include update_shroud and auto_shroud) */
|
||||
struct undo_action : undo_action_base
|
||||
{
|
||||
/**
|
||||
* Default constructor.
|
||||
* It is assumed that undo actions are constructed after the action is performed
|
||||
* so that the unit id diff does not change after this constructor
|
||||
*/
|
||||
undo_action();
|
||||
undo_action(const config& cfg);
|
||||
undo_action() {}
|
||||
// Virtual destructor to support derived classes.
|
||||
virtual ~undo_action() {}
|
||||
|
||||
/** Writes this into the provided config. */
|
||||
virtual void write(config & cfg) const;
|
||||
|
||||
/**
|
||||
* Undoes this action.
|
||||
* @return true on success; false on an error.
|
||||
*/
|
||||
virtual bool undo(int side) = 0;
|
||||
/**
|
||||
* the difference in the unit ids
|
||||
* TODO: does it really make sense to allow undoing if the unit id counter has changed?
|
||||
*/
|
||||
int unit_id_diff;
|
||||
/** actions wml (specified by wml) that should be executed when undoing this command. */
|
||||
typedef std::vector<undo_event> event_vector;
|
||||
event_vector umc_commands_undo;
|
||||
void execute_undo_umc_wml();
|
||||
|
||||
static void read_event_vector(event_vector& vec, const config& cfg, const std::string& tag);
|
||||
static void write_event_vector(const event_vector& vec, config& cfg, const std::string& tag);
|
||||
/** Writes this into the provided config. */
|
||||
virtual void write(config& cfg) const
|
||||
{
|
||||
cfg["type"] = this->get_type();
|
||||
}
|
||||
virtual const char* get_type() const = 0;
|
||||
};
|
||||
|
||||
/** entry for player actions that do not need any special code to be performed when undoing such as right-click menu items. */
|
||||
struct undo_dummy_action : undo_action
|
||||
class undo_event : public undo_action
|
||||
{
|
||||
undo_dummy_action ()
|
||||
: undo_action()
|
||||
{
|
||||
}
|
||||
explicit undo_dummy_action (const config & cfg)
|
||||
: undo_action(cfg)
|
||||
{
|
||||
}
|
||||
virtual const char* get_type() const { return "dummy"; }
|
||||
virtual ~undo_dummy_action () {}
|
||||
/** Undoes this action. */
|
||||
virtual bool undo(int)
|
||||
{
|
||||
execute_undo_umc_wml();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
public:
|
||||
|
||||
undo_event(int fcn_idx, const config& args, const game_events::queued_event& ctx);
|
||||
undo_event(const config& cmds, const game_events::queued_event& ctx);
|
||||
|
||||
undo_event(const config& cfg);
|
||||
|
||||
virtual bool undo(int side);
|
||||
virtual void write(config& cfg) const;
|
||||
|
||||
|
||||
static const char* get_type_impl() { return "event"; }
|
||||
virtual const char* get_type() const { return get_type_impl(); }
|
||||
|
||||
private:
|
||||
undo_event(const config& first, const config& second, const config& weapons, const config& cmds);
|
||||
|
||||
utils::optional<int> lua_idx;
|
||||
config commands, data;
|
||||
map_location loc1, loc2, filter_loc1, filter_loc2;
|
||||
std::size_t uid1, uid2;
|
||||
std::string id1, id2;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -26,9 +26,9 @@ dismiss_action::dismiss_action(const unit_const_ptr dismissed)
|
|||
{
|
||||
}
|
||||
|
||||
dismiss_action::dismiss_action(const config& cfg, const config& unit_cfg)
|
||||
: undo_action(cfg)
|
||||
, dismissed_unit(unit::create(unit_cfg))
|
||||
dismiss_action::dismiss_action(const config& cfg)
|
||||
: undo_action()
|
||||
, dismissed_unit(unit::create(cfg.mandatory_child("unit")))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -50,8 +50,9 @@ bool dismiss_action::undo(int side)
|
|||
team ¤t_team = resources::gameboard->get_team(side);
|
||||
|
||||
current_team.recall_list().add(dismissed_unit);
|
||||
execute_undo_umc_wml();
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto red_undo_dismiss = undo_action_container::subaction_factory<dismiss_action>();
|
||||
|
||||
}
|
||||
|
|
|
@ -23,9 +23,11 @@ struct dismiss_action : undo_action
|
|||
unit_ptr dismissed_unit;
|
||||
|
||||
explicit dismiss_action(const unit_const_ptr dismissed);
|
||||
explicit dismiss_action(const config& cfg, const config& unit_cfg);
|
||||
explicit dismiss_action(const config& cfg);
|
||||
|
||||
static const char* get_type_impl() { return "dismiss"; }
|
||||
virtual const char* get_type() const { return get_type_impl(); }
|
||||
|
||||
virtual const char* get_type() const { return "dismiss"; }
|
||||
virtual ~dismiss_action() {}
|
||||
|
||||
/** Writes this into the provided config. */
|
||||
|
|
|
@ -123,8 +123,8 @@ bool move_action::undo(int)
|
|||
u->anim_comp().set_standing();
|
||||
|
||||
gui.invalidate_unit_after_move(rev_route.front(), rev_route.back());
|
||||
execute_undo_umc_wml();
|
||||
return true;
|
||||
}
|
||||
static auto reg_undo_move = undo_action_container::subaction_factory<move_action>();
|
||||
|
||||
}
|
||||
|
|
|
@ -31,17 +31,20 @@ struct move_action : undo_action, shroud_clearing_action
|
|||
const std::vector<map_location>::const_iterator & begin,
|
||||
const std::vector<map_location>::const_iterator & end,
|
||||
int sm, int timebonus, int orig, const map_location::direction dir);
|
||||
move_action(const config & cfg, const config & unit_cfg,
|
||||
int sm, const map_location::direction dir)
|
||||
: undo_action(cfg)
|
||||
move_action(const config & cfg)
|
||||
: undo_action()
|
||||
, shroud_clearing_action(cfg)
|
||||
, starting_moves(sm)
|
||||
, starting_dir(dir)
|
||||
, goto_hex(unit_cfg["goto_x"].to_int(-999),
|
||||
unit_cfg["goto_y"].to_int(-999), wml_loc())
|
||||
, starting_moves(cfg["starting_moves"].to_int())
|
||||
, starting_dir(map_location::parse_direction(cfg["starting_direction"]))
|
||||
, goto_hex(cfg.child_or_empty("unit")["goto_x"].to_int(-999),
|
||||
cfg.child_or_empty("unit")["goto_y"].to_int(-999),
|
||||
wml_loc())
|
||||
{
|
||||
}
|
||||
virtual const char* get_type() const { return "move"; }
|
||||
|
||||
static const char* get_type_impl() { return "move"; }
|
||||
virtual const char* get_type() const { return get_type_impl(); }
|
||||
|
||||
virtual ~move_action() {}
|
||||
|
||||
/** Writes this into the provided config. */
|
||||
|
|
|
@ -40,11 +40,11 @@ recall_action::recall_action(const unit_const_ptr recalled, const map_location&
|
|||
, recall_from(from)
|
||||
{}
|
||||
|
||||
recall_action::recall_action(const config & cfg, const map_location & from)
|
||||
: undo_action(cfg)
|
||||
recall_action::recall_action(const config & cfg)
|
||||
: undo_action()
|
||||
, shroud_clearing_action(cfg)
|
||||
, id(cfg["id"])
|
||||
, recall_from(from)
|
||||
, recall_from(map_location(cfg.child_or_empty("leader"), nullptr))
|
||||
{}
|
||||
|
||||
/**
|
||||
|
@ -98,8 +98,8 @@ bool recall_action::undo(int side)
|
|||
resources::whiteboard->on_kill_unit();
|
||||
un->anim_comp().clear_haloes();
|
||||
this->return_village();
|
||||
execute_undo_umc_wml();
|
||||
return true;
|
||||
}
|
||||
static auto reg_undo_recall = undo_action_container::subaction_factory<recall_action>();
|
||||
|
||||
}
|
||||
|
|
|
@ -28,8 +28,11 @@ struct recall_action : undo_action, shroud_clearing_action
|
|||
|
||||
recall_action(const unit_const_ptr recalled, const map_location& loc,
|
||||
const map_location& from, int orig_village_owner, bool time_bonus);
|
||||
recall_action(const config & cfg, const map_location & from);
|
||||
virtual const char* get_type() const { return "recall"; }
|
||||
recall_action(const config & cfg);
|
||||
|
||||
static const char* get_type_impl() { return "recall"; }
|
||||
virtual const char* get_type() const { return get_type_impl(); }
|
||||
|
||||
virtual ~recall_action() {}
|
||||
|
||||
/** Writes this into the provided config. */
|
||||
|
|
|
@ -39,12 +39,24 @@ recruit_action::recruit_action(const unit_const_ptr recruited, const map_locatio
|
|||
, recruit_from(from)
|
||||
{}
|
||||
|
||||
recruit_action::recruit_action(const config & cfg, const unit_type & type, const map_location& from)
|
||||
: undo_action(cfg)
|
||||
static const unit_type& get_unit_type(const config& cfg)
|
||||
{
|
||||
const config& child = cfg.mandatory_child("unit");
|
||||
const unit_type* u_type = unit_types.find(child["type"]);
|
||||
|
||||
if(!u_type) {
|
||||
// Bad data.
|
||||
throw config::error("Invalid recruit; unit type '" + child["type"].str() + "' was not found.\n");
|
||||
}
|
||||
return *u_type;
|
||||
}
|
||||
recruit_action::recruit_action(const config & cfg)
|
||||
: undo_action()
|
||||
, shroud_clearing_action(cfg)
|
||||
, u_type(type)
|
||||
, recruit_from(from)
|
||||
{}
|
||||
, u_type(get_unit_type(cfg))
|
||||
, recruit_from(map_location(cfg.child_or_empty("leader"), nullptr))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes this into the provided config.
|
||||
|
@ -87,8 +99,8 @@ bool recruit_action::undo(int side)
|
|||
gui.invalidate(recruit_loc);
|
||||
units.erase(recruit_loc);
|
||||
this->return_village();
|
||||
execute_undo_umc_wml();
|
||||
return true;
|
||||
}
|
||||
static auto reg_undo_recruit = undo_action_container::subaction_factory<recruit_action>();
|
||||
|
||||
}
|
||||
|
|
|
@ -32,8 +32,11 @@ struct recruit_action : undo_action, shroud_clearing_action
|
|||
|
||||
recruit_action(const unit_const_ptr recruited, const map_location& loc,
|
||||
const map_location& from, int orig_village_owner, bool time_bonus);
|
||||
recruit_action(const config & cfg, const unit_type & type, const map_location& from);
|
||||
virtual const char* get_type() const { return "recruit"; }
|
||||
recruit_action(const config & cfg);
|
||||
|
||||
static const char* get_type_impl() { return "recruit"; }
|
||||
virtual const char* get_type() const { return get_type_impl(); }
|
||||
|
||||
virtual ~recruit_action() {}
|
||||
|
||||
/** Writes this into the provided config. */
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
|
||||
#include "actions/undo_update_shroud_action.hpp"
|
||||
|
||||
#include "play_controller.hpp"
|
||||
#include "resources.hpp" // for screen, teams, units, etc
|
||||
#include "synced_context.hpp" // for set_scontext_synced
|
||||
#include "team.hpp" // for team
|
||||
|
||||
namespace actions::undo
|
||||
{
|
||||
/**
|
||||
|
@ -21,16 +26,16 @@ namespace actions::undo
|
|||
*/
|
||||
void auto_shroud_action::write(config & cfg) const
|
||||
{
|
||||
undo_action_base::write(cfg);
|
||||
undo_action::write(cfg);
|
||||
cfg["active"] = active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes this into the provided config.
|
||||
*/
|
||||
void update_shroud_action::write(config & cfg) const
|
||||
bool auto_shroud_action::undo(int)
|
||||
{
|
||||
undo_action_base::write(cfg);
|
||||
resources::controller->current_team().set_auto_shroud_updates(!active);
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto reg_auto_shroud = undo_action_container::subaction_factory<auto_shroud_action>();
|
||||
|
||||
}
|
||||
|
|
|
@ -18,28 +18,28 @@
|
|||
|
||||
namespace actions::undo
|
||||
{
|
||||
struct auto_shroud_action : undo_action_base {
|
||||
struct auto_shroud_action : undo_action {
|
||||
bool active;
|
||||
|
||||
explicit auto_shroud_action(bool turned_on)
|
||||
: undo_action_base()
|
||||
: undo_action()
|
||||
, active(turned_on)
|
||||
{}
|
||||
virtual const char* get_type() const { return "auto_shroud"; }
|
||||
virtual ~auto_shroud_action() {}
|
||||
{
|
||||
}
|
||||
explicit auto_shroud_action(const config& cfg)
|
||||
: undo_action()
|
||||
, active(cfg["active"].to_bool())
|
||||
{
|
||||
}
|
||||
|
||||
/** Writes this into the provided config. */
|
||||
virtual void write(config & cfg) const;
|
||||
};
|
||||
static const char* get_type_impl() { return "auto_shroud"; }
|
||||
virtual const char* get_type() const { return get_type_impl(); }
|
||||
|
||||
struct update_shroud_action : undo_action_base {
|
||||
// No additional data.
|
||||
virtual bool undo(int);
|
||||
|
||||
update_shroud_action()
|
||||
: undo_action_base()
|
||||
{}
|
||||
virtual const char* get_type() const { return "update_shroud"; }
|
||||
virtual ~update_shroud_action() {}
|
||||
virtual ~auto_shroud_action()
|
||||
{
|
||||
}
|
||||
|
||||
/** Writes this into the provided config. */
|
||||
virtual void write(config & cfg) const;
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
#include "actions/advancement.hpp" // for advance_unit_at, etc
|
||||
#include "actions/move.hpp" // for clear_shroud
|
||||
#include "actions/vision.hpp" // for clear_shroud and create_jamming_map
|
||||
#include "actions/undo.hpp" // for clear_shroud and create_jamming_map
|
||||
#include "actions/undo_action.hpp" // for clear_shroud and create_jamming_map
|
||||
#include "ai/composite/ai.hpp" // for ai_composite
|
||||
#include "ai/composite/component.hpp" // for component, etc
|
||||
#include "ai/composite/contexts.hpp" // for ai_context
|
||||
|
@ -4212,10 +4214,10 @@ int game_lua_kernel::intf_add_undo_actions(lua_State *L)
|
|||
{
|
||||
config cfg;
|
||||
if(luaW_toconfig(L, 1, cfg)) {
|
||||
synced_context::add_undo_commands(cfg, get_event_info());
|
||||
game_state_.undo_stack_->add_custom<actions::undo_event>(cfg, get_event_info());
|
||||
} else {
|
||||
luaW_toconfig(L, 2, cfg);
|
||||
synced_context::add_undo_commands(save_wml_event(1), cfg, get_event_info());
|
||||
game_state_.undo_stack_->add_custom<actions::undo_event>(save_wml_event(1), cfg, get_event_info());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -4318,7 +4320,7 @@ int game_lua_kernel::cfun_undoable_event(lua_State* L)
|
|||
lua_pushvalue(L, lua_upvalueindex(1));
|
||||
lua_push(L, 1);
|
||||
luaW_pcall(L, 1, 0);
|
||||
synced_context::add_undo_commands(lua_upvalueindex(2), get_event_info());
|
||||
game_state_.undo_stack_->add_custom<actions::undo_event>(lua_upvalueindex(2), config(), get_event_info());
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -321,12 +321,6 @@ SYNCED_COMMAND_HANDLER_FUNCTION(fire_event, child, /*spectator*/)
|
|||
synced_context::block_undo(std::get<0>(resources::game_events->pump().fire(event_name)));
|
||||
}
|
||||
|
||||
// Not clearing the undo stack here causes OOS because we added an entry to the replay but no entry to the undo stack.
|
||||
if(synced_context::undo_blocked()) {
|
||||
synced_context::block_undo();
|
||||
} else {
|
||||
resources::undo_stack->add_dummy();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -335,11 +329,6 @@ SYNCED_COMMAND_HANDLER_FUNCTION(custom_command, child, /*spectator*/)
|
|||
assert(resources::lua_kernel);
|
||||
resources::lua_kernel->custom_command(child["name"], child.child_or_empty("data"));
|
||||
|
||||
if(synced_context::undo_blocked()) {
|
||||
synced_context::block_undo();
|
||||
} else {
|
||||
resources::undo_stack->add_dummy();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -369,7 +358,6 @@ SYNCED_COMMAND_HANDLER_FUNCTION(update_shroud, /*child*/, spectator)
|
|||
spectator.error("Team has DSU disabled but we found an explicit shroud update");
|
||||
}
|
||||
bool res = resources::undo_stack->commit_vision();
|
||||
resources::undo_stack->add_update_shroud();
|
||||
if(res) {
|
||||
synced_context::block_undo();
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "synced_checkup.hpp"
|
||||
#include "syncmp_handler.hpp"
|
||||
#include "units/id.hpp"
|
||||
#include "utils/general.hpp"
|
||||
#include "whiteboard/manager.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
@ -51,12 +52,13 @@ bool synced_context::run(const std::string& commandname, const config& data, act
|
|||
// use this after resources::recorder->add_synced_command
|
||||
// because set_scontext_synced sets the checkup to the last added command
|
||||
set_scontext_synced sync;
|
||||
resources::undo_stack->init_action();
|
||||
|
||||
synced_command::map::iterator it = synced_command::registry().find(commandname);
|
||||
if(it == synced_command::registry().end()) {
|
||||
auto p_handler = utils::find(synced_command::registry(), commandname);
|
||||
if(!p_handler) {
|
||||
spectator.error("commandname [" + commandname + "] not found");
|
||||
} else {
|
||||
bool success = it->second(data, spectator);
|
||||
bool success = p_handler->second(data, spectator);
|
||||
if(!success) {
|
||||
return false;
|
||||
}
|
||||
|
@ -66,12 +68,10 @@ bool synced_context::run(const std::string& commandname, const config& data, act
|
|||
|
||||
sync.do_final_checkup();
|
||||
|
||||
// TODO: It would be nice if this could automaticially detect that
|
||||
// no entry was pushed to the undo stack for this action
|
||||
// and always clear the undo stack in that case.
|
||||
resources::undo_stack->finish_action(!undo_blocked());
|
||||
|
||||
if(undo_blocked()) {
|
||||
// This in particular helps the networking code to make sure this command is sent.
|
||||
resources::undo_stack->clear();
|
||||
send_user_choice();
|
||||
}
|
||||
|
||||
|
@ -91,8 +91,9 @@ bool synced_context::run_and_store(const std::string& commandname, const config&
|
|||
bool success = run(commandname, data, spectator);
|
||||
if(!success) {
|
||||
resources::recorder->undo();
|
||||
} else {
|
||||
resources::undo_stack->cleanup_action();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@ -344,21 +345,6 @@ config synced_context::ask_server_choice(const server_choice& sch)
|
|||
}
|
||||
}
|
||||
|
||||
void synced_context::add_undo_commands(const config& commands, const game_events::queued_event& ctx)
|
||||
{
|
||||
undo_commands_.emplace_front(commands, ctx);
|
||||
}
|
||||
|
||||
void synced_context::add_undo_commands(int idx, const game_events::queued_event& ctx)
|
||||
{
|
||||
undo_commands_.emplace_front(idx, ctx);
|
||||
}
|
||||
|
||||
void synced_context::add_undo_commands(int idx, const config& args, const game_events::queued_event& ctx)
|
||||
{
|
||||
undo_commands_.emplace_front(idx, args, ctx);
|
||||
}
|
||||
|
||||
bool synced_context::ignore_undo()
|
||||
{
|
||||
auto& ct = resources::controller->current_team();
|
||||
|
@ -378,7 +364,6 @@ set_scontext_synced_base::set_scontext_synced_base()
|
|||
synced_context::set_synced_state(synced_context::SYNCED);
|
||||
synced_context::reset_block_undo();
|
||||
synced_context::set_last_unit_id(resources::gameboard->unit_id_manager().get_save_id());
|
||||
synced_context::reset_undo_commands();
|
||||
|
||||
old_rng_ = randomness::generator;
|
||||
randomness::generator = new_rng_.get();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "random.hpp"
|
||||
#include "synced_checkup.hpp"
|
||||
#include "synced_commands.hpp"
|
||||
#include "actions/undo_action.hpp"
|
||||
|
||||
#include <deque>
|
||||
|
||||
|
@ -152,30 +153,6 @@ public:
|
|||
/** If we are in a mp game, ask the server, otherwise generate the answer ourselves. */
|
||||
static config ask_server_choice(const server_choice&);
|
||||
|
||||
struct event_info {
|
||||
config cmds_;
|
||||
utils::optional<int> lua_;
|
||||
game_events::queued_event evt_;
|
||||
event_info(const config& cmds, game_events::queued_event evt) : cmds_(cmds), evt_(evt) {}
|
||||
event_info(int lua, game_events::queued_event evt) : lua_(lua), evt_(evt) {}
|
||||
event_info(int lua, const config& args, game_events::queued_event evt) : cmds_(args), lua_(lua), evt_(evt) {}
|
||||
};
|
||||
|
||||
typedef std::deque<event_info> event_list;
|
||||
static event_list& get_undo_commands()
|
||||
{
|
||||
return undo_commands_;
|
||||
}
|
||||
|
||||
static void add_undo_commands(const config& commands, const game_events::queued_event& ctx);
|
||||
static void add_undo_commands(int fcn_idx, const game_events::queued_event& ctx);
|
||||
static void add_undo_commands(int fcn_idx, const config& args, const game_events::queued_event& ctx);
|
||||
|
||||
static void reset_undo_commands()
|
||||
{
|
||||
undo_commands_.clear();
|
||||
}
|
||||
|
||||
static bool ignore_undo();
|
||||
private:
|
||||
/** Weather we are in a synced move, in a user_choice, or none of them. */
|
||||
|
@ -192,9 +169,6 @@ private:
|
|||
|
||||
/** Used to restore the unit id manager when undoing. */
|
||||
static inline int last_unit_id_ = 0;
|
||||
|
||||
/** Actions to be executed when the current action is undone. */
|
||||
static inline event_list undo_commands_ {};
|
||||
};
|
||||
|
||||
class set_scontext_synced_base
|
||||
|
|
|
@ -126,4 +126,15 @@ void sort_if(Container& container, const Predicate& predicate)
|
|||
std::sort(container.begin(), container.end(), predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience wrapper for using find on a container without needing to comare to end()
|
||||
*
|
||||
*/
|
||||
template<typename Container, typename Value>
|
||||
auto* find(Container& container, const Value& value)
|
||||
{
|
||||
auto res = container.find(value);
|
||||
return (res == container.end()) ? nullptr : &*res;
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
|
|
Loading…
Add table
Reference in a new issue