Lua AI: add new replay-safe action ai.synced_command()
This enables the execution of Lua commands from within the AI. The commands are synced to be in the replay file as well, using a new [command][lua_ai] tag. Function call: ai.synced_command(command, x1, y1), where command is a string containing the lua code and x1,y1 is an optional map location that is the only allowed external variable used in the code string.
This commit is contained in:
parent
cfee347a38
commit
3aa9cdc99c
10 changed files with 200 additions and 4 deletions
|
@ -42,6 +42,7 @@
|
|||
#include "../game_end_exceptions.hpp"
|
||||
#include "../game_preferences.hpp"
|
||||
#include "../log.hpp"
|
||||
#include "../scripting/lua.hpp"
|
||||
#include "../mouse_handler_base.hpp"
|
||||
#include "../pathfind/teleport.hpp"
|
||||
#include "../play_controller.hpp"
|
||||
|
@ -868,6 +869,57 @@ void stopunit_result::do_init_for_execution()
|
|||
}
|
||||
|
||||
|
||||
// synced_command_result
|
||||
synced_command_result::synced_command_result( side_number side, const std::string& lua_code, const map_location& location )
|
||||
: action_result(side), lua_code_(lua_code), location_(location)
|
||||
{
|
||||
}
|
||||
|
||||
void synced_command_result::do_check_before()
|
||||
{
|
||||
LOG_AI_ACTIONS << " check_before " << *this << std::endl;
|
||||
}
|
||||
|
||||
void synced_command_result::do_check_after()
|
||||
{
|
||||
}
|
||||
|
||||
std::string synced_command_result::do_describe() const
|
||||
{
|
||||
std::stringstream s;
|
||||
s <<" synced_command by side ";
|
||||
s << get_side();
|
||||
s <<std::endl;
|
||||
return s.str();
|
||||
}
|
||||
|
||||
void synced_command_result::do_execute()
|
||||
{
|
||||
LOG_AI_ACTIONS << "start of execution of: " << *this << std::endl;
|
||||
assert(is_success());
|
||||
|
||||
std::stringstream s;
|
||||
if (location_ != map_location::null_location){
|
||||
s << "local x1 = " << location_.x << " local y1 = " << location_.y << " ";
|
||||
}
|
||||
s << lua_code_;
|
||||
|
||||
resources::lua_kernel->run(s.str().c_str());
|
||||
try {
|
||||
recorder.add_lua_ai(s.str());
|
||||
set_gamestate_changed();
|
||||
manager::raise_gamestate_changed();
|
||||
} catch (...) {
|
||||
is_ok(); //Silences "unchecked result" warning
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void synced_command_result::do_init_for_execution()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// =======================================================================
|
||||
// STATELESS INTERFACE TO AI ACTIONS
|
||||
// =======================================================================
|
||||
|
@ -930,6 +982,16 @@ stopunit_result_ptr actions::execute_stopunit_action( side_number side,
|
|||
return action;
|
||||
}
|
||||
|
||||
synced_command_result_ptr actions::execute_synced_command_action( side_number side,
|
||||
bool execute,
|
||||
const std::string& lua_code,
|
||||
const map_location& location)
|
||||
{
|
||||
synced_command_result_ptr action(new synced_command_result(side,lua_code,location));
|
||||
execute ? action->execute() : action->check_before();
|
||||
return action;
|
||||
}
|
||||
|
||||
const std::string& actions::get_error_name(int error_code)
|
||||
{
|
||||
if (error_names_.empty()){
|
||||
|
@ -1012,3 +1074,8 @@ std::ostream &operator<<(std::ostream &s, ai::stopunit_result const &r) {
|
|||
s << r.do_describe();
|
||||
return s;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &s, ai::synced_command_result const &r) {
|
||||
s << r.do_describe();
|
||||
return s;
|
||||
}
|
||||
|
|
|
@ -290,6 +290,23 @@ private:
|
|||
const bool remove_attacks_;
|
||||
};
|
||||
|
||||
class synced_command_result : public action_result {
|
||||
public:
|
||||
synced_command_result( side_number side,
|
||||
const std::string& lua_code,
|
||||
const map_location& location );
|
||||
|
||||
virtual std::string do_describe() const;
|
||||
protected:
|
||||
virtual void do_check_before();
|
||||
virtual void do_check_after();
|
||||
virtual void do_execute();
|
||||
virtual void do_init_for_execution();
|
||||
private:
|
||||
const std::string& lua_code_;
|
||||
const map_location& location_;
|
||||
};
|
||||
|
||||
|
||||
class actions {
|
||||
|
||||
|
@ -399,6 +416,22 @@ static stopunit_result_ptr execute_stopunit_action( side_number side,
|
|||
bool remove_attacks );
|
||||
|
||||
|
||||
/**
|
||||
* Ask the game to run Lua code
|
||||
* @param side the side which tries to execute the move
|
||||
* @param execute should move be actually executed or not
|
||||
* @param lua_code the code to be run
|
||||
* @param location location to be passed to the code as x1/y1
|
||||
* @retval possible result: ok
|
||||
* @retval possible_result: something wrong
|
||||
* @retval possible_result: nothing to do
|
||||
*/
|
||||
static synced_command_result_ptr execute_synced_command_action( side_number side,
|
||||
bool execute,
|
||||
const std::string& lua_code,
|
||||
const map_location& location );
|
||||
|
||||
|
||||
/**
|
||||
* get human-readable name of the error by code.
|
||||
* @param error_code error code.
|
||||
|
@ -423,5 +456,6 @@ std::ostream &operator<<(std::ostream &s, ai::move_result const &r);
|
|||
std::ostream &operator<<(std::ostream &s, ai::recall_result const &r);
|
||||
std::ostream &operator<<(std::ostream &s, ai::recruit_result const &r);
|
||||
std::ostream &operator<<(std::ostream &s, ai::stopunit_result const &r);
|
||||
std::ostream &operator<<(std::ostream &s, ai::synced_command_result const &r);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -143,6 +143,16 @@ stopunit_result_ptr readonly_context_impl::check_stopunit_action(const map_locat
|
|||
}
|
||||
|
||||
|
||||
synced_command_result_ptr readwrite_context_impl::execute_synced_command_action(const std::string& lua_code, const map_location& location){
|
||||
return actions::execute_synced_command_action(get_side(),true,lua_code,location);
|
||||
}
|
||||
|
||||
|
||||
synced_command_result_ptr readonly_context_impl::check_synced_command_action(const std::string& lua_code, const map_location& location){
|
||||
return actions::execute_synced_command_action(get_side(),false,lua_code,location);
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
void readonly_context_impl::add_known_aspect(const std::string &name, boost::shared_ptr< typesafe_aspect <T> > &where)
|
||||
{
|
||||
|
|
|
@ -187,6 +187,7 @@ public:
|
|||
virtual recall_result_ptr check_recall_action(const std::string& id, const map_location &where = map_location::null_location, const map_location &from = map_location::null_location) = 0;
|
||||
virtual recruit_result_ptr check_recruit_action(const std::string& unit_name, const map_location &where = map_location::null_location, const map_location &from = map_location::null_location) = 0;
|
||||
virtual stopunit_result_ptr check_stopunit_action(const map_location& unit_location, bool remove_movement = true, bool remove_attacks = false) = 0;
|
||||
virtual synced_command_result_ptr check_synced_command_action(const std::string& lua_code, const map_location& location = map_location::null_location) = 0;
|
||||
virtual void calculate_possible_moves(std::map<map_location,pathfind::paths>& possible_moves,
|
||||
move_map& srcdst, move_map& dstsrc, bool enemy,
|
||||
bool assume_full_movement=false,
|
||||
|
@ -444,6 +445,9 @@ public:
|
|||
virtual stopunit_result_ptr execute_stopunit_action(const map_location& unit_location, bool remove_movement = true, bool remove_attacks = false) = 0;
|
||||
|
||||
|
||||
virtual synced_command_result_ptr execute_synced_command_action(const std::string& lua_code, const map_location& location = map_location::null_location) = 0;
|
||||
|
||||
|
||||
virtual team& current_team_w() = 0;
|
||||
|
||||
|
||||
|
@ -580,6 +584,11 @@ public:
|
|||
return target_->check_stopunit_action(unit_location, remove_movement, remove_attacks);
|
||||
}
|
||||
|
||||
virtual synced_command_result_ptr check_synced_command_action(const std::string& lua_code, const map_location& location = map_location::null_location)
|
||||
{
|
||||
return target_->check_synced_command_action(lua_code, location);
|
||||
}
|
||||
|
||||
virtual void calculate_possible_moves(std::map<map_location,pathfind::paths>& possible_moves,
|
||||
move_map& srcdst, move_map& dstsrc, bool enemy,
|
||||
bool assume_full_movement=false,
|
||||
|
@ -1073,6 +1082,12 @@ public:
|
|||
}
|
||||
|
||||
|
||||
virtual synced_command_result_ptr execute_synced_command_action(const std::string& lua_code, const map_location& location = map_location::null_location)
|
||||
{
|
||||
return target_->execute_synced_command_action(lua_code,location);
|
||||
}
|
||||
|
||||
|
||||
virtual team& current_team_w()
|
||||
{
|
||||
return target_->current_team_w();
|
||||
|
@ -1255,6 +1270,17 @@ public:
|
|||
stopunit_result_ptr check_stopunit_action(const map_location& unit_location, bool remove_movement = true, bool remove_attacks = false);
|
||||
|
||||
|
||||
/**
|
||||
* Check if it is possible to run Lua code
|
||||
* @param lua_code the code to be run
|
||||
* @param location location to be passed to the code as x1/y1
|
||||
* @retval possible result: ok
|
||||
* @retval possible_result: something wrong
|
||||
* @retval possible_result: nothing to do
|
||||
*/
|
||||
synced_command_result_ptr check_synced_command_action(const std::string& lua_code, const map_location& location = map_location::null_location);
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the moves units may possibly make.
|
||||
*
|
||||
|
@ -1649,6 +1675,17 @@ public:
|
|||
virtual stopunit_result_ptr execute_stopunit_action(const map_location& unit_location, bool remove_movement = true, bool remove_attacks = false);
|
||||
|
||||
|
||||
/**
|
||||
* Ask the game to run Lua code
|
||||
* @param lua_code the code to be run
|
||||
* @param location location to be passed to the code as x1/y1
|
||||
* @retval possible result: ok
|
||||
* @retval possible_result: something wrong
|
||||
* @retval possible_result: nothing to do
|
||||
*/
|
||||
virtual synced_command_result_ptr execute_synced_command_action(const std::string& lua_code, const map_location& location = map_location::null_location);
|
||||
|
||||
|
||||
/** Return a reference to the 'team' object for the AI. */
|
||||
virtual team& current_team_w();
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ class recruit_result;
|
|||
class move_result;
|
||||
class move_and_attack_result;
|
||||
class stopunit_result;
|
||||
class synced_command_result;
|
||||
|
||||
typedef boost::shared_ptr<action_result> action_result_ptr;
|
||||
typedef boost::shared_ptr<attack_result> attack_result_ptr;
|
||||
|
@ -103,6 +104,7 @@ typedef boost::shared_ptr<recruit_result> recruit_result_ptr;
|
|||
typedef boost::shared_ptr<move_result> move_result_ptr;
|
||||
typedef boost::shared_ptr<move_and_attack_result> move_and_attack_result_ptr;
|
||||
typedef boost::shared_ptr<stopunit_result> stopunit_result_ptr;
|
||||
typedef boost::shared_ptr<synced_command_result> synced_command_result_ptr;
|
||||
|
||||
typedef boost::shared_ptr< aspect > aspect_ptr;
|
||||
typedef boost::shared_ptr< candidate_action > candidate_action_ptr;
|
||||
|
|
|
@ -281,6 +281,30 @@ static int cfun_ai_check_stopunit(lua_State *L)
|
|||
return ai_stopunit_select(L, false, true, true);
|
||||
}
|
||||
|
||||
static int ai_synced_command(lua_State *L, bool exec)
|
||||
{
|
||||
const char *lua_code = luaL_checkstring(L, 1);
|
||||
int side = get_readonly_context(L).get_side();
|
||||
map_location location;
|
||||
if (!lua_isnoneornil(L, 2)) {
|
||||
location.x = lua_tonumber(L, 2);
|
||||
location.y = lua_tonumber(L, 3);
|
||||
}
|
||||
|
||||
ai::synced_command_result_ptr synced_command_result = ai::actions::execute_synced_command_action(side,exec,std::string(lua_code),location);
|
||||
return transform_ai_action(L,synced_command_result);
|
||||
}
|
||||
|
||||
static int cfun_ai_execute_synced_command(lua_State *L)
|
||||
{
|
||||
return ai_synced_command(L, true);
|
||||
}
|
||||
|
||||
static int cfun_ai_check_synced_command(lua_State *L)
|
||||
{
|
||||
return ai_synced_command(L, false);
|
||||
}
|
||||
|
||||
static int ai_recruit(lua_State *L, bool exec)
|
||||
{
|
||||
const char *unit_name = luaL_checkstring(L, 1);
|
||||
|
@ -870,10 +894,12 @@ static void generate_and_push_ai_table(lua_State* L, ai::engine_lua* engine) {
|
|||
{ "stopunit_all", &cfun_ai_execute_stopunit_all },
|
||||
{ "stopunit_attacks", &cfun_ai_execute_stopunit_attacks },
|
||||
{ "stopunit_moves", &cfun_ai_execute_stopunit_moves },
|
||||
{ "synced_command", &cfun_ai_execute_synced_command },
|
||||
{ "suitable_keep", &cfun_ai_get_suitable_keep },
|
||||
{ "check_recall", &cfun_ai_check_recall },
|
||||
{ "check_move", &cfun_ai_check_move },
|
||||
{ "check_stopunit", &cfun_ai_check_stopunit },
|
||||
{ "check_synced_command", &cfun_ai_check_synced_command },
|
||||
{ "check_attack", &cfun_ai_check_attack },
|
||||
{ "check_recruit", &cfun_ai_check_recruit },
|
||||
//{ "",},
|
||||
|
|
|
@ -401,6 +401,10 @@ void put_wml_message(const std::string& logger, const std::string& message)
|
|||
}
|
||||
}
|
||||
|
||||
void run_lua_commands(char const *lua_code)
|
||||
{
|
||||
resources::lua_kernel->run(lua_code);
|
||||
}
|
||||
|
||||
void handle_event_commands(const queued_event& event_info, const vconfig &cfg)
|
||||
{
|
||||
|
@ -476,7 +480,7 @@ bool pump()
|
|||
wb::real_map real_unit_map;
|
||||
|
||||
pump_manager pump_instance;
|
||||
|
||||
|
||||
// Loop through the events we need to process.
|
||||
while ( !pump_instance.done() )
|
||||
{
|
||||
|
|
|
@ -99,6 +99,11 @@ namespace game_events
|
|||
/// really be pushed into the wml_messages_stream, and does it.
|
||||
void put_wml_message(const std::string& logger, const std::string& message);
|
||||
|
||||
/**
|
||||
* Directly runs the lua command(s) @a lua_code
|
||||
*/
|
||||
void run_lua_commands(char const *lua_code);
|
||||
|
||||
/**
|
||||
* Runs the action handler associated to the command sequence @a cfg.
|
||||
*/
|
||||
|
|
|
@ -463,6 +463,13 @@ void replay::add_event(const std::string& name, const map_location& loc)
|
|||
(*cmd)["undo"] = false;
|
||||
}
|
||||
|
||||
void replay::add_lua_ai(const std::string& lua_code)
|
||||
{
|
||||
config* const cmd = add_command();
|
||||
config& child = cmd->add_child("lua_ai");
|
||||
child["code"] = lua_code;
|
||||
}
|
||||
|
||||
void replay::add_log_data(const std::string &key, const std::string &var)
|
||||
{
|
||||
config& ulog = cfg_.child_or_add("upload_log");
|
||||
|
@ -1287,14 +1294,17 @@ bool do_replay_handle(int side_num, const std::string &do_untill)
|
|||
} else {
|
||||
game_events::fire(event);
|
||||
}
|
||||
|
||||
}
|
||||
else if (const config &child = cfg->child("lua_ai"))
|
||||
{
|
||||
const std::string &lua_code = child["code"];
|
||||
game_events::run_lua_commands(lua_code.c_str());
|
||||
}
|
||||
else if (const config &child = cfg->child("advance_unit"))
|
||||
{
|
||||
const map_location loc(child, resources::gamedata);
|
||||
get_replay_source().add_expected_advancement(loc);
|
||||
DBG_REPLAY << "got an explicit advance\n";
|
||||
|
||||
}
|
||||
else if (cfg->child("global_variable"))
|
||||
{
|
||||
|
@ -1305,7 +1315,7 @@ bool do_replay_handle(int side_num, const std::string &do_untill)
|
|||
// Turning on automatic shroud causes vision to be updated.
|
||||
if ( active )
|
||||
resources::undo_stack->commit_vision(true);
|
||||
|
||||
|
||||
current_team.set_auto_shroud_updates(active);
|
||||
}
|
||||
else if ( cfg->child("update_shroud") )
|
||||
|
|
|
@ -83,6 +83,7 @@ public:
|
|||
void end_turn();
|
||||
void add_event(const std::string& name,
|
||||
const map_location& loc=map_location::null_location);
|
||||
void add_lua_ai(const std::string& lua_code);
|
||||
void add_unit_checksum(const map_location& loc,config* const cfg);
|
||||
void add_checksum_check(const map_location& loc);
|
||||
void add_log_data(const std::string &key, const std::string &var);
|
||||
|
|
Loading…
Add table
Reference in a new issue