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:
mattsc 2013-12-04 06:49:55 -08:00
parent cfee347a38
commit 3aa9cdc99c
10 changed files with 200 additions and 4 deletions

View file

@ -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;
}

View file

@ -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

View file

@ -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)
{

View file

@ -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();

View file

@ -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;

View file

@ -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 },
//{ "",},

View file

@ -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() )
{

View file

@ -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.
*/

View file

@ -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") )

View file

@ -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);