The Battle for Wesnoth  1.17.23+dev
engine_lua.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2023
3  by Yurii Chernyi <terraninfo@terraninfo.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * LUA AI Support engine - creating specific ai components from config
18  * @file
19  */
20 
21 #include "ai/lua/engine_lua.hpp"
22 #include "ai/composite/ai.hpp"
23 #include "ai/composite/goal.hpp"
24 #include "ai/composite/rca.hpp"
25 #include "ai/composite/stage.hpp"
26 #include "ai/composite/aspect.hpp"
27 
29 
30 #include "log.hpp"
31 #include "resources.hpp"
32 #include "ai/lua/core.hpp"
33 #include "ai/lua/lua_object.hpp"
34 #include "game_board.hpp"
36 #include "units/unit.hpp"
37 #include "units/map.hpp"
38 #include "deprecation.hpp"
39 #include "game_version.hpp"
40 
41 namespace ai {
42 
43 static lg::log_domain log_ai_engine_lua("ai/engine/lua");
44 #define DBG_AI_LUA LOG_STREAM(debug, log_ai_engine_lua)
45 #define LOG_AI_LUA LOG_STREAM(info, log_ai_engine_lua)
46 #define WRN_AI_LUA LOG_STREAM(warn, log_ai_engine_lua)
47 #define ERR_AI_LUA LOG_STREAM(err, log_ai_engine_lua)
48 
49 #ifdef _MSC_VER
50 #pragma warning(push)
51 //silence "inherits via dominance" warnings
52 #pragma warning(disable:4250)
53 #endif
54 
56 
57 public:
59  : candidate_action(context, cfg),evaluation_action_handler_(),execution_action_handler_(),serialized_evaluation_state_(cfg.child_or_empty("args")),serialized_filterown_(cfg.child_or_empty("filter_own"))
60  {
61  // do nothing
62  }
63 
65 
66  virtual double evaluate()
67  {
68  auto l_obj = std::make_shared<lua_object<double>>();
69 
72  } else {
73  return BAD_SCORE;
74  }
75 
76  std::shared_ptr<double> result = l_obj->get();
77 
78  return result ? *result : 0.0;
79  }
80 
81  virtual void execute() {
83  lua_object_ptr nil;
85  }
86  }
87 
88  virtual config to_config() const {
91  cfg.add_child("filter_own",serialized_filterown_);
92  return cfg;
93  }
94 
95 protected:
96  std::shared_ptr<lua_ai_action_handler> evaluation_action_handler_;
97  std::shared_ptr<lua_ai_action_handler> execution_action_handler_;
100 };
101 
103 
104 public:
105  lua_candidate_action_wrapper( rca_context &context, const config &cfg, lua_ai_context &lua_ai_ctx)
106  : lua_candidate_action_wrapper_base(context,cfg),evaluation_(cfg["evaluation"]),execution_(cfg["execution"])
107  {
108  evaluation_action_handler_.reset(resources::lua_kernel->create_lua_ai_action_handler(evaluation_.c_str(),lua_ai_ctx));
109  execution_action_handler_.reset(resources::lua_kernel->create_lua_ai_action_handler(execution_.c_str(),lua_ai_ctx));
110  }
111 
113 
114  virtual config to_config() const
115  {
117  cfg["evaluation"] = evaluation_;
118  cfg["execution"] = execution_;
119  return cfg;
120  }
121 
122 private:
123  std::string evaluation_;
124  std::string execution_;
125 };
126 
128 public:
130  : lua_candidate_action_wrapper_base(context,cfg), location_(cfg["location"]), use_parms_(false)
131  {
132  if (cfg.has_attribute("exec_parms") || cfg.has_attribute("eval_parms")) {
133  deprecated_message("[candidate_action]eval_parms,exec_parms=", DEP_LEVEL::PREEMPTIVE, "1.17", "Use [args] instead - this data is passed to both the evaluation and the execution");
134  use_parms_ = true;
135  exec_parms_ = cfg["exec_parms"].str();
136  eval_parms_ = cfg["eval_parms"].str();
137  }
138  std::string eval_code;
139  std::string exec_code;
140  generate_code(eval_code, exec_code);
141 
142  evaluation_action_handler_.reset(resources::lua_kernel->create_lua_ai_action_handler(eval_code.c_str(),lua_ai_ctx));
143  execution_action_handler_.reset(resources::lua_kernel->create_lua_ai_action_handler(exec_code.c_str(),lua_ai_ctx));
144  }
145 
147 
148  virtual config to_config() const
149  {
151  cfg["location"] = location_;
152  if (use_parms_) {
153  cfg["eval_parms"] = eval_parms_;
154  cfg["exec_parms"] = exec_parms_;
155  }
156  return cfg;
157  }
158 
159 private:
160  std::string location_;
161  std::string eval_parms_;
162  std::string exec_parms_;
164 
165  void generate_code(std::string& eval, std::string& exec) {
166  std::string preamble = "local self, params, data, filter_own = ...\n";
167  std::string load = "wesnoth.require(\"" + location_ + "\")";
168  if (use_parms_) {
169  eval = preamble + "return " + load + ":evaluation(ai, {" + eval_parms_ + "}, {data = data})";
170  exec = preamble + load + ":execution(ai, {" + exec_parms_ + "}, {data = data})";
171  } else {
172  eval = preamble + "return " + load + ".evaluation(self, params, data, filter_own)";
173  exec = preamble + load + ".execution(self, params, data, filter_own)";
174  }
175  }
176 };
177 
179 public:
181  : lua_candidate_action_wrapper(context, cfg, lua_ai_ctx)
182  , bound_unit_()
183  {
184  map_location loc(cfg["unit_x"], cfg["unit_y"], wml_loc()); // lua and c++ coords differ by one
185  bound_unit_ = (*resources::gameboard->units().find(loc)).clone();
186  }
187 
188  virtual double evaluate()
189  {
190  if (resources::gameboard->units().find(bound_unit_->underlying_id()).valid())
191  {
193  }
194  else
195  {
196  this->set_to_be_removed();
197  return 0; // Is 0 what we return when we don't want the action to be executed?
198  }
199  }
200 
201  virtual void execute()
202  {
204  this->disable(); // we do not want to execute the same sticky CA twice -> will be moved out to Lua later
205  }
206 
207  virtual config to_config() const
208  {
210  cfg["sticky"] = true;
211  cfg["unit_x"] = bound_unit_->get_location().wml_x();
212  cfg["unit_y"] = bound_unit_->get_location().wml_y();
213  return cfg;
214  }
215 private:
217 
218 };
219 
220 class lua_stage_wrapper : public stage {
221 public:
222  lua_stage_wrapper( ai_context &context, const config &cfg, lua_ai_context &lua_ai_ctx )
223  : stage(context,cfg),action_handler_(),code_(cfg["code"]),serialized_evaluation_state_(cfg.child_or_empty("args"))
224  {
225  action_handler_.reset(resources::lua_kernel->create_lua_ai_action_handler(code_.c_str(),lua_ai_ctx));
226  }
227 
229  {
230  }
231 
232  virtual bool do_play_stage()
233  {
234  gamestate_observer gs_o;
235 
236  if (action_handler_) {
237  lua_object_ptr nil;
238  const config empty_cfg;
239  action_handler_->handle(serialized_evaluation_state_, empty_cfg, false, nil);
240  }
241 
242  return gs_o.is_gamestate_changed();
243  }
244 
245  virtual config to_config() const
246  {
247  config cfg = stage::to_config();
248  cfg["code"] = code_;
250  return cfg;
251  }
252 private:
253  std::shared_ptr<lua_ai_action_handler> action_handler_;
254  std::string code_;
256 };
257 
258 /**
259  * Note that initially we get access only to readonly context (engine is created rather early, when there's no way to move/attack.
260  * We inject full ai_context later.
261  */
263  : engine(context,cfg)
264  , code_(get_engine_code(cfg))
265  , lua_ai_context_(resources::lua_kernel->create_lua_ai_context(
266  get_engine_code(cfg).c_str(), this))
267 {
268  name_ = "lua";
269  config data(cfg.child_or_empty("data"));
270  config args(cfg.child_or_empty("args"));
271 
272  if (lua_ai_context_) { // The context might be nullptr if the config contains errors
273  lua_ai_context_->set_persistent_data(data);
274  lua_ai_context_->set_arguments(args);
275  lua_ai_context_->update_state();
276  }
277 }
278 
279 std::string engine_lua::get_engine_code(const config &cfg) const
280 {
281  if (cfg.has_attribute("code")) {
282  return cfg["code"].str();
283  }
284  // If there is no engine defined we create a dummy engine
285  std::string code = "wesnoth.require(\"ai/lua/dummy_engine_lua.lua\")";
286  return code;
287 }
288 
290 {
291 }
292 
293 bool engine_lua::is_ok() const
294 {
295  return lua_ai_context_ ? true : false;
296 }
297 
299 {
300  if (game_config::debug)
301  {
302  lua_ai_context_->push_ai_table();
303  }
304 }
305 
306 void engine_lua::do_parse_candidate_action_from_config( rca_context &context, const config &cfg, std::back_insert_iterator<std::vector< candidate_action_ptr > > b )
307 {
308  if (!lua_ai_context_) {
309  return;
310  }
311 
313  if (!cfg["sticky"].to_bool())
314  {
315  if (cfg.has_attribute("location")) {
317  } else {
318  ca_ptr.reset(new lua_candidate_action_wrapper(context,cfg,*lua_ai_context_));
319  }
320  }
321  else
322  {
324  }
325 
326  if (ca_ptr) {
327  *b = ca_ptr;
328  }
329 }
330 
331 void engine_lua::do_parse_stage_from_config( ai_context &context, const config &cfg, std::back_insert_iterator<std::vector< stage_ptr > > b )
332 {
333  if (!lua_ai_context_) {
334  return;
335  }
336 
337  if(auto st_ptr = std::make_shared<lua_stage_wrapper>(context, cfg, *lua_ai_context_)) {
338  st_ptr->on_create();
339  *b = st_ptr;
340  }
341 }
342 
343 void engine_lua::do_parse_aspect_from_config( const config &cfg, const std::string &id, std::back_insert_iterator<std::vector< aspect_ptr > > b )
344 {
345  const std::string aspect_factory_key = id+"*lua_aspect"; // @note: factory key for a lua_aspect
347 
348  if (f == lua_aspect_factory::get_list().end()){
349  ERR_AI_LUA << "side "<<ai_.get_side()<< " : UNKNOWN aspect["<<aspect_factory_key<<"]";
350  DBG_AI_LUA << "config snippet contains: " << std::endl << cfg;
351  return;
352  }
353  aspect_ptr new_aspect = f->second->get_new_instance(ai_,cfg,id,lua_ai_context_);
354  if (!new_aspect) {
355  ERR_AI_LUA << "side "<<ai_.get_side()<< " : UNABLE TO CREATE aspect, key=["<<aspect_factory_key<<"]";
356  DBG_AI_LUA << "config snippet contains: " << std::endl << cfg;
357  return;
358  }
359  *b = new_aspect;
360 }
361 
362 void engine_lua::do_parse_goal_from_config(const config &cfg, std::back_insert_iterator<std::vector< goal_ptr > > b )
363 {
365  if (f == goal_factory::get_list().end()){
366  ERR_AI_LUA << "side "<<ai_.get_side()<< " : UNKNOWN goal["<<cfg["name"]<<"]";
367  DBG_AI_LUA << "config snippet contains: " << std::endl << cfg;
368  return;
369  }
370  goal_ptr new_goal = f->second->get_new_instance(ai_,cfg);
371  new_goal->on_create(lua_ai_context_);
372  if (!new_goal || !new_goal->ok()) {
373  ERR_AI_LUA << "side "<<ai_.get_side()<< " : UNABLE TO CREATE goal["<<cfg["name"]<<"]";
374  DBG_AI_LUA << "config snippet contains: " << std::endl << cfg;
375  return;
376  }
377  *b = new_goal;
378 }
379 
380 std::string engine_lua::evaluate(const std::string &/*str*/)
381 {
382  // TODO: this is not mandatory, but if we want to allow lua to evaluate
383  // something 'in context' of this ai, this will be useful
384  return "";
385 }
386 
388 {
389  lua_ai_context_->apply_micro_ai(cfg);
390 }
391 
393 {
394  config cfg = engine::to_config();
395 
396  cfg["id"] = get_id();
397  cfg["code"] = this->code_;
398 
399  if (lua_ai_context_) {
400  config data = config();
401  lua_ai_context_->get_persistent_data(data);
402  cfg.add_child("data") = data;
403  }
404 
405  return cfg;
406 }
407 
408 #ifdef _MSC_VER
409 #pragma warning(pop)
410 #endif
411 
412 } //end of namespace ai
virtual void set_to_be_removed()
Definition: rca.cpp:119
void disable()
Disable the candidate action.
Definition: rca.cpp:70
static const double BAD_SCORE
Definition: rca.hpp:33
virtual config to_config() const
serialize
Definition: rca.cpp:103
void apply_micro_ai(const config &cfg)
Definition: engine_lua.cpp:387
std::string get_engine_code(const config &) const
Definition: engine_lua.cpp:279
virtual void do_parse_stage_from_config(ai_context &context, const config &cfg, std::back_insert_iterator< std::vector< stage_ptr > > b)
Taka a config (with engine=lua in it) and parse several (usually, 1) stages out of it.
Definition: engine_lua.cpp:331
bool is_ok() const
Definition: engine_lua.cpp:293
virtual void do_parse_aspect_from_config(const config &cfg, const std::string &id, std::back_insert_iterator< std::vector< aspect_ptr > > b)
Taka a config (with engine=lua in it) and parse several (usually, 1) aspects out of it.
Definition: engine_lua.cpp:343
virtual void do_parse_candidate_action_from_config(rca_context &context, const config &cfg, std::back_insert_iterator< std::vector< candidate_action_ptr > > b)
Taka a config (with engine=lua in it) and parse several (usually, 1) candidate actions out of it.
Definition: engine_lua.cpp:306
virtual config to_config() const
Serialize to config.
Definition: engine_lua.cpp:392
std::shared_ptr< lua_ai_context > lua_ai_context_
Definition: engine_lua.hpp:89
virtual std::string evaluate(const std::string &str)
Definition: engine_lua.cpp:380
engine_lua(readonly_context &context, const config &cfg)
Note that initially we get access only to readonly context (engine is created rather early,...
Definition: engine_lua.cpp:262
virtual ~engine_lua()
Definition: engine_lua.cpp:289
virtual void do_parse_goal_from_config(const config &cfg, std::back_insert_iterator< std::vector< goal_ptr > > b)
Definition: engine_lua.cpp:362
std::string code_
The underlying lua code.
Definition: engine_lua.hpp:86
virtual void push_ai_table()
Method that pushes the AI table of the lua_context on the stack for debugging purposes.
Definition: engine_lua.cpp:298
std::string name_
Definition: engine.hpp:102
virtual std::string get_id() const
Definition: engine.hpp:86
virtual config to_config() const
serialize
Definition: engine.cpp:134
readonly_context & ai_
Definition: engine.hpp:96
bool is_gamestate_changed()
Check if the gamestate has changed since last reset reset is done once on construction,...
static factory_map & get_list()
Definition: goal.hpp:159
Proxy table for the AI context.
Definition: core.hpp:34
static factory_map & get_list()
Definition: aspect.hpp:499
virtual void execute()
Execute the candidate action.
Definition: engine_lua.cpp:81
lua_candidate_action_wrapper_base(rca_context &context, const config &cfg)
Definition: engine_lua.cpp:58
virtual config to_config() const
serialize
Definition: engine_lua.cpp:88
std::shared_ptr< lua_ai_action_handler > evaluation_action_handler_
Definition: engine_lua.cpp:96
std::shared_ptr< lua_ai_action_handler > execution_action_handler_
Definition: engine_lua.cpp:97
virtual double evaluate()
Evaluate the candidate action, resetting the internal state of the action.
Definition: engine_lua.cpp:66
lua_candidate_action_wrapper_external(rca_context &context, const config &cfg, lua_ai_context &lua_ai_ctx)
Definition: engine_lua.cpp:129
void generate_code(std::string &eval, std::string &exec)
Definition: engine_lua.cpp:165
virtual config to_config() const
serialize
Definition: engine_lua.cpp:148
virtual config to_config() const
serialize
Definition: engine_lua.cpp:114
lua_candidate_action_wrapper(rca_context &context, const config &cfg, lua_ai_context &lua_ai_ctx)
Definition: engine_lua.cpp:105
virtual config to_config() const
serialize
Definition: engine_lua.cpp:245
lua_stage_wrapper(ai_context &context, const config &cfg, lua_ai_context &lua_ai_ctx)
Definition: engine_lua.cpp:222
virtual bool do_play_stage()
Play the turn - implementation.
Definition: engine_lua.cpp:232
virtual ~lua_stage_wrapper()
Definition: engine_lua.cpp:228
config serialized_evaluation_state_
Definition: engine_lua.cpp:255
std::shared_ptr< lua_ai_action_handler > action_handler_
Definition: engine_lua.cpp:253
lua_sticky_candidate_action_wrapper(rca_context &context, const config &cfg, lua_ai_context &lua_ai_ctx)
Definition: engine_lua.cpp:180
virtual double evaluate()
Evaluate the candidate action, resetting the internal state of the action.
Definition: engine_lua.cpp:188
virtual void execute()
Execute the candidate action.
Definition: engine_lua.cpp:201
virtual config to_config() const
serialize
Definition: engine_lua.cpp:207
virtual side_number get_side() const =0
Get the side number.
virtual config to_config() const
serialize
Definition: stage.cpp:67
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
const config & child_or_empty(config_key_type key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:399
bool has_attribute(config_key_type key) const
Definition: config.cpp:159
config & add_child(config_key_type key)
Definition: config.cpp:445
virtual const unit_map & units() const override
Definition: game_board.hpp:113
unit_iterator find(std::size_t id)
Definition: map.cpp:301
Composite AI with turn sequence which is a vector of stages.
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:30
#define ERR_AI_LUA
Definition: engine_lua.cpp:47
#define DBG_AI_LUA
Definition: engine_lua.cpp:44
LUA AI Support engine - creating specific ai components from config.
Interfaces for manipulating version numbers of engine, add-ons, etc.
A helper class to observe the game state.
Standard logging facilities (interface).
Lua object(value) wrapper implementation.
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:61
wfl::candidate_action_ptr ca_ptr
Definition: ai.cpp:74
std::shared_ptr< lua_object_base > lua_object_ptr
Definition: core.hpp:27
std::shared_ptr< aspect > aspect_ptr
Definition: game_info.hpp:95
std::shared_ptr< goal > goal_ptr
Definition: game_info.hpp:100
std::shared_ptr< candidate_action > candidate_action_ptr
Definition: rca.hpp:145
static lg::log_domain log_ai_engine_lua("ai/engine/lua")
const bool & debug
Definition: game_config.cpp:91
std::vector< game_tip > load(const config &cfg)
Loads the tips from a config.
Definition: tips.cpp:36
game_board * gameboard
Definition: resources.cpp:21
game_lua_kernel * lua_kernel
Definition: resources.cpp:26
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
std::string_view data
Definition: picture.cpp:199
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
candidate action framework
Composite AI stages.
Encapsulates the map of the game.
Definition: location.hpp:38
#define f
#define b