mirror of
https://github.com/wesnoth/wesnoth
synced 2025-04-27 11:50:45 +00:00
289 lines
9.0 KiB
C++
289 lines
9.0 KiB
C++
/*
|
||
Copyright (C) 2014 - 2022
|
||
by David White <dave@whitevine.net>
|
||
Part of the Battle for Wesnoth Project https://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.
|
||
*/
|
||
|
||
#pragma once
|
||
|
||
#include "game_events/pump.hpp" // for queued_event
|
||
#include "generic_event.hpp"
|
||
#include "mouse_handler_base.hpp"
|
||
#include "random.hpp"
|
||
#include "random_synced.hpp"
|
||
#include "replay.hpp"
|
||
#include "synced_checkup.hpp"
|
||
#include "synced_commands.hpp"
|
||
|
||
#include <deque>
|
||
#include <functional>
|
||
|
||
class config;
|
||
|
||
// only static methods.
|
||
class synced_context
|
||
{
|
||
public:
|
||
enum synced_state { UNSYNCED, SYNCED, LOCAL_CHOICE };
|
||
|
||
/**
|
||
* Sets the context to 'synced', initialises random context, and calls the given function.
|
||
* The plan is that in replays and in real game the same function is called.
|
||
* However, if you cannot call this function you can also use set_scontext_synced directly
|
||
* (use it like it's used in this method).
|
||
*
|
||
* Movement commands are currently treated specially because actions::move_unit returns a
|
||
* value and some function use that value. Maybe I should add a way to return a value here.
|
||
*
|
||
* AI attacks are also treated special because the ai wants to pass advancement_aspects.
|
||
*
|
||
* Redoing does normally not take place in a synced context, because we saved the dependent=true
|
||
* replay commands in the replay stack data. There are also no events of similar fired when
|
||
* redoing an action (in most cases).
|
||
*
|
||
* @param commandname The command to run.
|
||
* @param data The data to use with the command.
|
||
* @param use_undo This parameter is used to ignore undos during an ai move to optimize.
|
||
* @param show
|
||
* @param error_handler An error handler for the case that data contains invalid data.
|
||
*
|
||
* @return True if the action was successful.
|
||
*/
|
||
static bool run(const std::string& commandname,
|
||
const config& data,
|
||
bool use_undo = true,
|
||
bool show = true,
|
||
synced_command::error_handler_function error_handler = default_error_function);
|
||
|
||
static bool run_and_store(const std::string& commandname,
|
||
const config& data,
|
||
bool use_undo = true,
|
||
bool show = true,
|
||
synced_command::error_handler_function error_handler = default_error_function);
|
||
|
||
static bool run_and_throw(const std::string& commandname,
|
||
const config& data,
|
||
bool use_undo = true,
|
||
bool show = true,
|
||
synced_command::error_handler_function error_handler = default_error_function);
|
||
|
||
/**
|
||
* Checks whether we are currently running in a synced context, and if not we enters it.
|
||
* This is never called from so_replay_handle.
|
||
*/
|
||
static bool run_in_synced_context_if_not_already(const std::string& commandname,
|
||
const config& data,
|
||
bool use_undo = true,
|
||
bool show = true,
|
||
synced_command::error_handler_function error_handler = default_error_function);
|
||
|
||
/**
|
||
* @return 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 synced_state get_synced_state()
|
||
{
|
||
return state_;
|
||
}
|
||
|
||
/**
|
||
* @return 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 bool is_synced()
|
||
{
|
||
return get_synced_state() == SYNCED;
|
||
}
|
||
|
||
/**
|
||
* @return Whether we are not currently executing a synced action like recruit, start, recall, disband, movement,
|
||
* attack, init_side, end_turn, fire_event, lua_ai, auto_shroud or similar. and not doing a local choice.
|
||
*/
|
||
static bool is_unsynced()
|
||
{
|
||
return get_synced_state() == UNSYNCED;
|
||
}
|
||
|
||
/** Should only be called form set_scontext_synced, set_scontext_local_choice */
|
||
static void set_synced_state(synced_state newstate)
|
||
{
|
||
state_ = newstate;
|
||
}
|
||
|
||
/** Generates a new seed for a synced event, by asking the 'server' */
|
||
static std::string generate_random_seed();
|
||
|
||
/** called from get_user_choice while waiting for a remove user choice. */
|
||
static void pull_remote_user_input();
|
||
|
||
/**
|
||
* called from get_user_choice to send a recently made choice to the other clients.
|
||
* Does not receive any data from the network any sends data.
|
||
*/
|
||
static void send_user_choice();
|
||
|
||
/** 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);
|
||
|
||
/** A function to be passed to run_in_synced_context to log the error. */
|
||
static void just_log_error_function(const std::string& message);
|
||
|
||
/** A function to be passed to run_in_synced_context to ignore the error. */
|
||
static void ignore_error_function(const std::string& message);
|
||
|
||
/** @return A rng_deterministic if in determinsic mode otherwise a rng_synced. */
|
||
static std::shared_ptr<randomness::rng> get_rng_for_action();
|
||
|
||
/** @return whether we already sent data about the current action to other clients. which means we cannot undo it. */
|
||
static bool is_simultaneous()
|
||
{
|
||
return is_simultaneous_;
|
||
}
|
||
|
||
/** Sets is_simultaneous_ = false, called when entering the synced context. */
|
||
static void reset_is_simultaneous()
|
||
{
|
||
is_simultaneous_ = false;
|
||
}
|
||
|
||
/** Sets is_simultaneous_ = true, called using a user choice that is not the currently playing side. */
|
||
static void set_is_simultaneous();
|
||
|
||
/** @return Whether there were recently no methods called that prevent undoing. */
|
||
static bool can_undo();
|
||
|
||
static void set_last_unit_id(int id)
|
||
{
|
||
last_unit_id_ = id;
|
||
}
|
||
|
||
static int get_unit_id_diff();
|
||
|
||
class server_choice
|
||
{
|
||
public:
|
||
virtual ~server_choice()
|
||
{
|
||
}
|
||
|
||
/** We are in a game with no mp server and need to do this choice locally. */
|
||
virtual config local_choice() const = 0;
|
||
|
||
/** The request which is sent to the mp server. */
|
||
virtual config request() const = 0;
|
||
|
||
virtual const char* name() const = 0;
|
||
void send_request() const;
|
||
};
|
||
|
||
/** If we are in a mp game, ask the server, otherwise generate the answer ourselves. */
|
||
static config ask_server_choice(const server_choice&);
|
||
|
||
typedef std::deque<std::pair<config, game_events::queued_event>> 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 reset_undo_commands()
|
||
{
|
||
undo_commands_.clear();
|
||
}
|
||
|
||
private:
|
||
/** Weather we are in a synced move, in a user_choice, or none of them. */
|
||
static inline synced_state state_ = synced_context::UNSYNCED;
|
||
|
||
/**
|
||
* 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. It's impossible to undo data that has
|
||
* been sent over the network.
|
||
*
|
||
* false = we are on a local turn and haven't sent anything yet.
|
||
*
|
||
* TODO: it would be better if the following variable were not static.
|
||
*/
|
||
static inline bool is_simultaneous_ = false;
|
||
|
||
/** Used to restore the unit id manager when undoing. */
|
||
static inline int last_unit_id_ = 0;
|
||
|
||
/** Actions wml to be executed when the current action is undone. */
|
||
static inline event_list undo_commands_ {};
|
||
};
|
||
|
||
class set_scontext_synced_base
|
||
{
|
||
public:
|
||
set_scontext_synced_base();
|
||
~set_scontext_synced_base();
|
||
|
||
protected:
|
||
std::shared_ptr<randomness::rng> new_rng_;
|
||
randomness::rng* old_rng_;
|
||
};
|
||
|
||
/** A RAII object to enter the synced context, cannot be called if we are already in a synced context. */
|
||
class set_scontext_synced : set_scontext_synced_base
|
||
{
|
||
public:
|
||
set_scontext_synced();
|
||
|
||
/** Use this constructor if you have multiple synced_context but only one replay entry. */
|
||
set_scontext_synced(int num);
|
||
|
||
~set_scontext_synced();
|
||
|
||
int get_random_calls();
|
||
void do_final_checkup(bool dont_throw = false);
|
||
|
||
private:
|
||
// only called by constructors.
|
||
void init();
|
||
static checkup* generate_checkup(const std::string& tagname);
|
||
checkup* old_checkup_;
|
||
const std::unique_ptr<checkup> new_checkup_;
|
||
events::command_disabler disabler_;
|
||
bool did_final_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 leave_synced_context
|
||
{
|
||
public:
|
||
leave_synced_context();
|
||
~leave_synced_context();
|
||
|
||
private:
|
||
randomness::rng* old_rng_;
|
||
};
|
||
|
||
/**
|
||
* An object to leave the synced context during draw or unsynced wml items when we don’t know whether we are in a
|
||
* synced context or not. if we are in a synced context we leave the synced context, otherwise it has no effect. we need
|
||
* This because we might call lua's wesnoth.interface.game_display during draw and we don’t want to have that an effect
|
||
* on the gamestate in this case.
|
||
*/
|
||
class set_scontext_unsynced
|
||
{
|
||
public:
|
||
set_scontext_unsynced();
|
||
|
||
private:
|
||
const std::unique_ptr<leave_synced_context> leaver_;
|
||
};
|