1 /*
2 Copyright (C) 2008 - 2018 by David White <dave@whitevine.net>
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13 */
14
15 /**
16 * @file
17 * Defines formula ai candidate actions - headers
18 */
19
20 #include "ai/formula/ai.hpp"
21
22 #include "ai/formula/callable_objects.hpp" // for unit_callable, etc
23 #include "chat_events.hpp" // for chat_handler, etc
24 #include "display_chat_manager.hpp"
25 #include "formula/function.hpp" // for formula_expression
26 #include "game_board.hpp" // for game_board
27 #include "game_display.hpp" // for game_display
28 #include "log.hpp" // for LOG_STREAM, logger, etc
29 #include "map/map.hpp" // for gamemap
30 #include "pathfind/pathfind.hpp" // for plain_route, etc
31 #include "pathfind/teleport.hpp" // for get_teleport_locations, etc
32 #include "recall_list_manager.hpp" // for recall_list_manager
33 #include "resources.hpp" // for gameboard, teams, units, etc
34 #include "serialization/string_utils.hpp" // for split
35 #include "team.hpp" // for team
36 #include "terrain/filter.hpp" // for terrain_filter
37 #include "time_of_day.hpp" // for time_of_day
38 #include "tod_manager.hpp" // for tod_manager
39 #include "tstring.hpp" // for t_string, operator+
40 #include "units/unit.hpp" // for unit
41 #include "units/formula_manager.hpp" // for unit_formula_manager
42 #include "units/ptr.hpp" // for unit_ptr
43 #include "units/types.hpp"
44 #include "formula/formula.hpp" // for formula_error, formula, etc
45 #include "map/location.hpp" // for map_location, etc
46 #include "ai/actions.hpp" // for recall_result, etc
47 #include "ai/manager.hpp" // for manager
48 #include "ai/composite/contexts.hpp"
49 #include "ai/composite/stage.hpp" // for stage
50 #include "ai/default/contexts.hpp" // for attack_analysis
51 #include "ai/formula/function_table.hpp" // for ai_function_symbol_table
52 #include "ai/game_info.hpp" // for move_result_ptr, move_map, etc
53 #include "ai/formula/candidates.hpp" // for base_candidate_action, etc
54
55 #include <cassert> // for assert
56 #include <ctime> // for time
57 #include <map> // for multimap<>::const_iterator, etc
58 #include <sstream> // for operator<<, basic_ostream, etc
59 #include <stack> // for stack
60 #include <vector> // for vector, allocator, etc
61
62 static lg::log_domain log_formula_ai("ai/engine/fai");
63 #define DBG_AI LOG_STREAM(debug, log_formula_ai)
64 #define LOG_AI LOG_STREAM(info, log_formula_ai)
65 #define WRN_AI LOG_STREAM(warn, log_formula_ai)
66 #define ERR_AI LOG_STREAM(err, log_formula_ai)
67
68
69 using namespace wfl;
70
71 namespace ai {
72
73 using ca_ptr = wfl::candidate_action_ptr;
74
load_candidate_action_from_config(const config & rc_action)75 ca_ptr formula_ai::load_candidate_action_from_config(const config& rc_action)
76 {
77 ca_ptr new_ca;
78 const t_string &name = rc_action["name"];
79 try {
80 const t_string &type = rc_action["type"];
81
82 if( type == "movement") {
83 new_ca = std::make_shared<move_candidate_action>(name, type, rc_action, &function_table_);
84 } else if( type == "attack") {
85 new_ca = std::make_shared<attack_candidate_action>(name, type, rc_action, &function_table_);
86 } else {
87 ERR_AI << "Unknown candidate action type: " << type << std::endl;
88 }
89 } catch(const formula_error& e) {
90 handle_exception(e, "Error while registering candidate action '" + name + "'");
91 }
92 return new_ca;
93 }
94
get_recursion_count() const95 int formula_ai::get_recursion_count() const{
96 return recursion_counter_.get_count();
97 }
98
99
formula_ai(readonly_context & context,const config & cfg)100 formula_ai::formula_ai(readonly_context &context, const config &cfg)
101 :
102 readonly_context_proxy(),
103 formula_callable(),
104 ai_ptr_(nullptr),
105 cfg_(cfg),
106 recursion_counter_(context.get_recursion_count()),
107 keeps_cache_(),
108 attacks_callable(*this, resources::gameboard->units()),
109 // infinite_loop_guardian_(),
110 vars_(),
111 function_table_(*this)
112 {
113 init_readonly_context_proxy(context);
114 LOG_AI << "creating new formula ai"<< std::endl;
115 }
116
handle_exception(const formula_error & e) const117 void formula_ai::handle_exception(const formula_error& e) const
118 {
119 handle_exception(e, "Error while parsing formula");
120 }
121
handle_exception(const formula_error & e,const std::string & failed_operation) const122 void formula_ai::handle_exception(const formula_error& e, const std::string& failed_operation) const
123 {
124 LOG_AI << failed_operation << ": " << e.formula << std::endl;
125 display_message(failed_operation + ": " + e.formula);
126 //if line number = 0, don't display info about filename and line number
127 if (e.line != 0) {
128 LOG_AI << e.type << " in " << e.filename << ":" << e.line << std::endl;
129 display_message(e.type + " in " + e.filename + ":" + std::to_string(e.line));
130 } else {
131 LOG_AI << e.type << std::endl;
132 display_message(e.type);
133 }
134 }
135
display_message(const std::string & msg) const136 void formula_ai::display_message(const std::string& msg) const
137 {
138 game_display::get_singleton()->get_chat_manager().add_chat_message(time(nullptr), "wfl", get_side(), msg,
139 events::chat_handler::MESSAGE_PUBLIC, false);
140
141 }
142
create_optional_formula(const std::string & formula_string) const143 formula_ptr formula_ai::create_optional_formula(const std::string& formula_string) const {
144 try{
145 return formula::create_optional_formula(formula_string, &function_table_);
146 }
147 catch(const formula_error& e) {
148 handle_exception(e);
149 return wfl::formula_ptr();
150 }
151 }
152
153
set_ai_context(ai_context * context)154 void formula_ai::set_ai_context(ai_context *context)
155 {
156 ai_ptr_ = context;
157 }
158
159
evaluate(const std::string & formula_str)160 std::string formula_ai::evaluate(const std::string& formula_str)
161 {
162 try{
163
164 formula f(formula_str, &function_table_);
165
166 map_formula_callable callable(fake_ptr());
167
168 //formula_debugger fdb;
169 const variant v = f.evaluate(callable,nullptr);
170
171 if (ai_ptr_) {
172 variant var = variant(this->fake_ptr()).execute_variant(v);
173
174 if ( !var.is_empty() ) {
175 return "Made move: " + var.to_debug_string();
176 }
177 }
178
179 return v.to_debug_string();
180 }
181 catch(formula_error& e) {
182 e.line = 0;
183 handle_exception(e);
184 throw;
185 }
186 }
187
make_action(wfl::const_formula_ptr formula_,const wfl::formula_callable & variables)188 wfl::variant formula_ai::make_action(wfl::const_formula_ptr formula_, const wfl::formula_callable& variables)
189 {
190 if (!formula_) {
191 throw formula_error("null formula passed to make_action","","formula",0);
192 }
193 LOG_AI << "do move...\n";
194 const variant var = formula_->evaluate(variables);///@todo 1.9 add formula_debugger
195 variant res;
196
197 if (ai_ptr_) {
198 res = variant(this->fake_ptr()).execute_variant(var);
199 } else {
200 ERR_AI << "skipped execution of action because ai context is not set correctly" << std::endl;
201 }
202
203 return res;
204 }
205
shortest_path_calculator(const map_location & src,const map_location & dst,unit_map::iterator & unit_it,pathfind::teleport_map & allowed_teleports) const206 pathfind::plain_route formula_ai::shortest_path_calculator(const map_location &src,
207 const map_location &dst, unit_map::iterator &unit_it,
208 pathfind::teleport_map& allowed_teleports) const
209 {
210 map_location destination = dst;
211
212 unit_map &units_ = resources::gameboard->units();
213 pathfind::shortest_path_calculator calc(*unit_it, current_team(), resources::gameboard->teams(), resources::gameboard->map());
214
215 unit_map::const_iterator dst_un = units_.find(destination);
216
217 map_location res;
218
219 if( dst_un != units_.end() ) {
220 //there is unit standing at dst, let's try to find free hex to move to
221 const map_location::DIRECTION preferred = destination.get_relative_dir(src);
222
223 int best_rating = 100;//smaller is better
224 adjacent_loc_array_t adj;
225 get_adjacent_tiles(destination,adj.data());
226
227 for(size_t n = 0; n < adj.size(); ++n) {
228 if(resources::gameboard->map().on_board(adj[n]) == false) {
229 continue;
230 }
231
232 if(units_.find(adj[n]) != units_.end()) {
233 continue;
234 }
235
236 static const size_t NDIRECTIONS = map_location::NDIRECTIONS;
237 unsigned int difference = std::abs(int(preferred - n));
238 if(difference > NDIRECTIONS/2) {
239 difference = NDIRECTIONS - difference;
240 }
241
242 const int rating = difference * 2;
243 if(rating < best_rating || res.valid() == false) {
244 best_rating = rating;
245 res = adj[n];
246 }
247 }
248 }
249
250 if( res != map_location() ) {
251 destination = res;
252 }
253
254 pathfind::plain_route route = pathfind::a_star_search(src, destination, 1000.0, calc,
255 resources::gameboard->map().w(), resources::gameboard->map().h(), &allowed_teleports);
256
257 return route;
258 }
259
get_allowed_teleports(unit_map::iterator & unit_it) const260 pathfind::teleport_map formula_ai::get_allowed_teleports(unit_map::iterator& unit_it) const
261 {
262 return pathfind::get_teleport_locations(*unit_it, current_team(), true);
263 }
264
add_formula_function(const std::string & name,const_formula_ptr formula,const_formula_ptr precondition,const std::vector<std::string> & args)265 void formula_ai::add_formula_function(const std::string& name, const_formula_ptr formula, const_formula_ptr precondition, const std::vector<std::string>& args)
266 {
267 formula_function_ptr fcn(new user_formula_function(name,formula,precondition,args));
268 function_table_.add_function(name, std::move(fcn));
269 }
270
271 namespace {
272 template<typename Container>
villages_from_set(const Container & villages,const std::set<map_location> * exclude=nullptr)273 variant villages_from_set(const Container& villages,
274 const std::set<map_location>* exclude=nullptr) {
275 std::vector<variant> vars;
276 for(const map_location& loc : villages) {
277 if(exclude && exclude->count(loc)) {
278 continue;
279 }
280 vars.emplace_back(std::make_shared<location_callable>(loc));
281 }
282
283 return variant(vars);
284 }
285 }
286
get_value(const std::string & key) const287 variant formula_ai::get_value(const std::string& key) const
288 {
289 const unit_map& units = resources::gameboard->units();
290
291 if(key == "aggression")
292 {
293 return variant(get_aggression()*1000,variant::DECIMAL_VARIANT);
294
295 } else if(key == "attack_depth")
296 {
297 return variant(get_attack_depth());
298
299 } else if(key == "avoid")
300 {
301 std::set<map_location> av_locs;
302 get_avoid().get_locations(av_locs);
303 return villages_from_set(av_locs);
304
305 } else if(key == "caution")
306 {
307 return variant(get_caution()*1000,variant::DECIMAL_VARIANT);
308
309 } else if(key == "grouping")
310 {
311 return variant(get_grouping());
312
313 } else if(key == "leader_aggression")
314 {
315 return variant(get_leader_aggression()*1000,variant::DECIMAL_VARIANT);
316
317 } else if(key == "leader_ignores_keep")
318 {
319 return variant(get_leader_ignores_keep());
320
321 } else if(key == "leader_value")
322 {
323 return variant(get_leader_value()*1000,variant::DECIMAL_VARIANT);
324
325 } else if(key == "passive_leader")
326 {
327 return variant(get_passive_leader());
328
329 } else if(key == "passive_leader_shares_keep")
330 {
331 return variant(get_passive_leader_shares_keep());
332
333 } else if(key == "recruitment_pattern")
334 {
335 const std::vector<std::string> &rp = get_recruitment_pattern();
336 std::vector<variant> vars;
337 for(const std::string &i : rp) {
338 vars.emplace_back(i);
339 }
340 return variant(vars);
341
342 } else if(key == "scout_village_targeting")
343 {
344 return variant(get_scout_village_targeting()*1000,variant::DECIMAL_VARIANT);
345
346 } else if(key == "support_villages")
347 {
348 return variant(get_support_villages());
349
350 } else if(key == "village_value")
351 {
352 return variant(get_village_value()*1000,variant::DECIMAL_VARIANT);
353
354 } else if(key == "villages_per_scout")
355 {
356 return variant(get_villages_per_scout());
357
358 } else if(key == "attacks")
359 {
360 return get_attacks_as_variant();
361
362 } else if(key == "turn")
363 {
364 return variant(resources::tod_manager->turn());
365
366 } else if(key == "time_of_day")
367 {
368 return variant(resources::tod_manager->get_time_of_day().id);
369
370 } else if(key == "my_side")
371 {
372 return variant(std::make_shared<team_callable>(resources::gameboard->get_team(get_side())));
373
374 } else if(key == "my_side_number")
375 {
376 return variant(get_side()-1);
377
378 } else if(key == "teams")
379 {
380 std::vector<variant> vars;
381 for(std::vector<team>::const_iterator i = resources::gameboard->teams().begin(); i != resources::gameboard->teams().end(); ++i) {
382 vars.emplace_back(std::make_shared<team_callable>(*i));
383 }
384 return variant(vars);
385
386 } else if(key == "allies")
387 {
388 std::vector<variant> vars;
389 for( size_t i = 0; i < resources::gameboard->teams().size(); ++i) {
390 if ( !current_team().is_enemy( i+1 ) )
391 vars.emplace_back(i);
392 }
393 return variant(vars);
394
395 } else if(key == "enemies")
396 {
397 std::vector<variant> vars;
398 for( size_t i = 0; i < resources::gameboard->teams().size(); ++i) {
399 if ( current_team().is_enemy( i+1 ) )
400 vars.emplace_back(i);
401 }
402 return variant(vars);
403
404 } else if(key == "my_recruits")
405 {
406 std::vector<variant> vars;
407
408 unit_types.build_all(unit_type::FULL);
409
410 const std::set<std::string>& recruits = current_team().recruits();
411 if(recruits.empty()) {
412 return variant(vars);
413 }
414 for(std::set<std::string>::const_iterator i = recruits.begin(); i != recruits.end(); ++i)
415 {
416 const unit_type *ut = unit_types.find(*i);
417 if (ut)
418 {
419 vars.emplace_back(std::make_shared<unit_type_callable>(*ut));
420 }
421 }
422 return variant(vars);
423
424 } else if(key == "recruits_of_side")
425 {
426 std::vector<variant> vars;
427 std::vector< std::vector< variant>> tmp;
428
429 unit_types.build_all(unit_type::FULL);
430
431 for( size_t i = 0; i<resources::gameboard->teams().size(); ++i)
432 {
433 std::vector<variant> v;
434 tmp.push_back( v );
435
436 const std::set<std::string>& recruits = resources::gameboard->teams()[i].recruits();
437 if(recruits.empty()) {
438 continue;
439 }
440 for(std::set<std::string>::const_iterator str_it = recruits.begin(); str_it != recruits.end(); ++str_it)
441 {
442 const unit_type *ut = unit_types.find(*str_it);
443 if (ut)
444 {
445 tmp[i].emplace_back(std::make_shared<unit_type_callable>(*ut));
446 }
447 }
448 }
449
450 for( size_t i = 0; i<tmp.size(); ++i)
451 vars.emplace_back(tmp[i]);
452 return variant(vars);
453
454 } else if(key == "units")
455 {
456 std::vector<variant> vars;
457 for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
458 vars.emplace_back(std::make_shared<unit_callable>(*i));
459 }
460 return variant(vars);
461
462 } else if(key == "units_of_side")
463 {
464 std::vector<variant> vars;
465 std::vector< std::vector< variant>> tmp;
466 for( size_t i = 0; i<resources::gameboard->teams().size(); ++i)
467 {
468 std::vector<variant> v;
469 tmp.push_back( v );
470 }
471 for(const unit &u : units) {
472 tmp[u.side() - 1].emplace_back(std::make_shared<unit_callable>(u));
473 }
474 for( size_t i = 0; i<tmp.size(); ++i)
475 vars.emplace_back(tmp[i]);
476 return variant(vars);
477
478 } else if(key == "my_units")
479 {
480 std::vector<variant> vars;
481 for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
482 if (i->side() == get_side()) {
483 vars.emplace_back(std::make_shared<unit_callable>(*i));
484 }
485 }
486 return variant(vars);
487
488 } else if(key == "enemy_units")
489 {
490 std::vector<variant> vars;
491 for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
492 if (current_team().is_enemy(i->side())) {
493 if (!i->incapacitated()) {
494 vars.emplace_back(std::make_shared<unit_callable>(*i));
495 }
496 }
497 }
498 return variant(vars);
499
500 } else if(key == "my_moves")
501 {
502 return variant(std::make_shared<move_map_callable>(get_srcdst(), get_dstsrc(), units));
503
504 } else if(key == "my_attacks")
505 {
506 return variant(attacks_callable.fake_ptr());
507 } else if(key == "enemy_moves")
508 {
509 return variant(std::make_shared<move_map_callable>(get_enemy_srcdst(), get_enemy_dstsrc(), units));
510
511 } else if(key == "my_leader")
512 {
513 unit_map::const_iterator i = units.find_leader(get_side());
514 if(i == units.end()) {
515 return variant();
516 }
517 return variant(std::make_shared<unit_callable>(*i));
518
519 } else if(key == "recall_list")
520 {
521 std::vector<variant> tmp;
522
523 for(std::vector<unit_ptr >::const_iterator i = current_team().recall_list().begin(); i != current_team().recall_list().end(); ++i) {
524 tmp.emplace_back(std::make_shared<unit_callable>(**i));
525 }
526
527 return variant(tmp);
528
529 } else if(key == "vars")
530 {
531 return variant(vars_.fake_ptr());
532 } else if(key == "keeps")
533 {
534 return get_keeps();
535 } else if(key == "map")
536 {
537 return variant(std::make_shared<gamemap_callable>(*resources::gameboard));
538 } else if(key == "villages")
539 {
540 return villages_from_set(resources::gameboard->map().villages());
541 } else if(key == "villages_of_side")
542 {
543 std::vector<variant> vars;
544 for(size_t i = 0; i<resources::gameboard->teams().size(); ++i)
545 {
546 vars.emplace_back();
547 }
548 for(size_t i = 0; i<vars.size(); ++i)
549 {
550 vars[i] = villages_from_set(resources::gameboard->teams()[i].villages());
551 }
552 return variant(vars);
553
554 } else if(key == "my_villages")
555 {
556 return villages_from_set(current_team().villages());
557
558 } else if(key == "enemy_and_unowned_villages")
559 {
560 return villages_from_set(resources::gameboard->map().villages(), ¤t_team().villages());
561 }
562
563 return variant();
564 }
565
get_inputs(formula_input_vector & inputs) const566 void formula_ai::get_inputs(formula_input_vector& inputs) const
567 {
568 add_input(inputs, "aggression");
569 add_input(inputs, "leader_aggression");
570 add_input(inputs, "caution");
571 add_input(inputs, "attacks");
572 add_input(inputs, "my_side");
573 add_input(inputs, "teams");
574 add_input(inputs, "turn");
575 add_input(inputs, "time_of_day");
576 add_input(inputs, "keeps");
577 add_input(inputs, "vars");
578 add_input(inputs, "allies");
579 add_input(inputs, "enemies");
580 add_input(inputs, "map");
581 add_input(inputs, "my_attacks");
582 add_input(inputs, "enemy_moves");
583 add_input(inputs, "my_leader");
584 add_input(inputs, "my_recruits");
585 //add_input(inputs, "recall_list");
586 add_input(inputs, "recruits_of_side");
587 add_input(inputs, "units");
588 add_input(inputs, "units_of_side");
589 add_input(inputs, "my_units");
590 add_input(inputs, "enemy_units");
591 add_input(inputs, "villages");
592 add_input(inputs, "my_villages");
593 add_input(inputs, "villages_of_side");
594 add_input(inputs, "enemy_and_unowned_villages");
595 }
596
set_value(const std::string & key,const variant & value)597 void formula_ai::set_value(const std::string& key, const variant& value) {
598 vars_.mutate_value(key, value);
599 }
600
get_keeps() const601 variant formula_ai::get_keeps() const
602 {
603 if(keeps_cache_.is_null()) {
604 std::vector<variant> vars;
605 for(size_t x = 0; x != size_t(resources::gameboard->map().w()); ++x) {
606 for(size_t y = 0; y != size_t(resources::gameboard->map().h()); ++y) {
607 const map_location loc(x,y);
608 if(resources::gameboard->map().is_keep(loc)) {
609 adjacent_loc_array_t adj;
610 get_adjacent_tiles(loc,adj.data());
611 for(size_t n = 0; n < adj.size(); ++n) {
612 if(resources::gameboard->map().is_castle(adj[n])) {
613 vars.emplace_back(std::make_shared<location_callable>(loc));
614 break;
615 }
616 }
617 }
618 }
619 }
620 keeps_cache_ = variant(vars);
621 }
622
623 return keeps_cache_;
624 }
625
can_reach_unit(map_location unit_A,map_location unit_B) const626 bool formula_ai::can_reach_unit(map_location unit_A, map_location unit_B) const {
627 if (tiles_adjacent(unit_A,unit_B)) {
628 return true;
629 }
630 move_map::const_iterator i;
631 std::pair<move_map::const_iterator,
632 move_map::const_iterator> unit_moves;
633
634 unit_moves = get_srcdst().equal_range(unit_A);
635 for(i = unit_moves.first; i != unit_moves.second; ++i) {
636 if (tiles_adjacent((*i).second,unit_B)) {
637 return true;
638 }
639 }
640 return false;
641 }
642
on_create()643 void formula_ai::on_create(){
644 //make sure we don't run out of refcount
645
646 for(const config &func : cfg_.child_range("function"))
647 {
648 const t_string &name = func["name"];
649 const t_string &inputs = func["inputs"];
650 const t_string &formula_str = func["formula"];
651
652 std::vector<std::string> args = utils::split(inputs);
653 try {
654 add_formula_function(name,
655 create_optional_formula(formula_str),
656 create_optional_formula(func["precondition"]),
657 args);
658 }
659 catch(const formula_error& e) {
660 handle_exception(e, "Error while registering function '" + name + "'");
661 }
662 }
663
664
665 vars_ = map_formula_callable();
666 if (const config &ai_vars = cfg_.child("vars"))
667 {
668 variant var;
669 for(const config::attribute &i : ai_vars.attribute_range()) {
670 var.serialize_from_string(i.second);
671 vars_.add(i.first, var);
672 }
673 }
674
675
676 }
677
678
evaluate_candidate_action(ca_ptr fai_ca)679 void formula_ai::evaluate_candidate_action(ca_ptr fai_ca)
680 {
681 fai_ca->evaluate(this,resources::gameboard->units());
682
683 }
684
execute_candidate_action(ca_ptr fai_ca)685 bool formula_ai::execute_candidate_action(ca_ptr fai_ca)
686 {
687 map_formula_callable callable(fake_ptr());
688 fai_ca->update_callable_map( callable );
689 const_formula_ptr move_formula(fai_ca->get_action());
690 return !make_action(move_formula, callable).is_empty();
691 }
692
693 #if 0
694 formula_ai::gamestate_change_observer::gamestate_change_observer() :
695 set_var_counter_(), set_unit_var_counter_(), continue_counter_()
696 {
697 ai::manager::get_singleton().add_gamestate_observer(this);
698 }
699
700 formula_ai::gamestate_change_observer::~gamestate_change_observer() {
701 ai::manager::get_singleton().remove_gamestate_observer(this);
702 }
703
704 void formula_ai::gamestate_change_observer::handle_generic_event(const std::string& /*event_name*/) {
705 set_var_counter_ = 0;
706 set_unit_var_counter_ = 0;
707 continue_counter_ = 0;
708 }
709
710 //return false if number of calls exceeded MAX_CALLS
711 bool formula_ai::gamestate_change_observer::set_var_check() {
712 if(set_var_counter_ >= MAX_CALLS)
713 return false;
714
715 set_var_counter_++;
716 return true;
717 }
718
719 bool formula_ai::gamestate_change_observer::set_unit_var_check() {
720 if(set_unit_var_counter_ >= MAX_CALLS)
721 return false;
722
723 set_unit_var_counter_++;
724 return true;
725 }
726
727 bool formula_ai::gamestate_change_observer::continue_check() {
728 if(continue_counter_ >= MAX_CALLS)
729 return false;
730
731 continue_counter_++;
732 return true;
733 }
734 #endif
735
to_config() const736 config formula_ai::to_config() const
737 {
738 if (!cfg_)
739 {
740 return config();
741 }
742 DBG_AI << "formula_ai::to_config(): "<< cfg_<<std::endl;
743 config cfg = cfg_;
744
745 //formula AI variables
746 cfg.clear_children("vars");
747 if (vars_.empty() == false) {
748 config &ai_vars = cfg.add_child("vars");
749
750 std::string str;
751 for(map_formula_callable::const_iterator i = vars_.begin(); i != vars_.end(); ++i)
752 {
753 try {
754 str = i->second.serialize_to_string();
755 } catch(const type_error&) {
756 WRN_AI << "variable ["<< i->first <<"] is not serializable - it will not be persisted across savegames"<<std::endl;
757 continue;
758 }
759 if (!str.empty())
760 {
761 ai_vars[i->first] = str;
762 str.clear();
763 }
764 }
765 }
766
767 return cfg;
768 }
769
770 } // end of namespace ai
771