Workaround to make custom C++ ais available to wesnoth

Discussion of all aspects of the game engine, including development of new and existing features.

Moderator: Forum Moderators

Post Reply
User avatar
iceiceice
Posts: 1056
Joined: August 23rd, 2013, 2:10 am

Workaround to make custom C++ ais available to wesnoth

Post by iceiceice »

If you just want to cut to the solution, jump to the last section.

The middle section is how I spent my saturday >< figuring out why wesnoth refuses to load custom ai's despite what is written here and in comments at ai/registry.cpp.

Basically you must modify various core files if you wish for wesnoth to recognize new values of "ai_algorithm".

***********************************************************************************************************************************************

Old title: Why does ai::holder assume it is holding a composite_ai?

In /src/ai/manager.cpp:

Code: Select all

namespace ai {

...
typedef boost::shared_ptr<ai_composite> composite_ai_ptr;
...

...
class holder{
public:
	...
	interface& get_ai_ref();
	...
private:
	...
	composite_ai_ptr ai_;
	side_context *side_context_;
	readonly_context *readonly_context_;
	readwrite_context *readwrite_context_;
	default_ai_context *default_ai_context_;
	side_number side_;
	config cfg_;
};
...

}

This doesn't make any sense. If interface is the base ai class, but composite_ai is the internal representation in a holder, then holders cannot hold anything except ai's derived from composite_ai ...? And holder will always cast it back down to an interface anyways apparently...? What is the point of this?

I'm having some issues where my ai is getting configured, and registered, but doesn't actually do anything or have its constructors or play turn called. I think this might have something to do with it.

*******************************************************************************************************************************************************

Edit: Why does ai::holder have this function?

Code: Select all

const std::string holder::get_ai_overview()
{
	if (!this->ai_) {
		get_ai_ref();
	}
	std::stringstream s;
	s << "advancements:  " << this->ai_->get_advancements().get_value() << std::endl;
	s << "aggression:  " << this->ai_->get_aggression() << std::endl;
	s << "attack_depth:  " << this->ai_->get_attack_depth() << std::endl;
	s << "caution:  " << this->ai_->get_caution() << std::endl;
	s << "grouping:  " << this->ai_->get_grouping() << std::endl;
	s << "leader_aggression:  " << this->ai_->get_leader_aggression() << std::endl;
	s << "leader_ignores_keep:  " << this->ai_->get_leader_ignores_keep() << std::endl;
	s << "leader_value:  " << this->ai_->get_leader_value() << std::endl;
	s << "number_of_possible_recruits_to_force_recruit:  " << this->ai_->get_number_of_possible_recruits_to_force_recruit() << std::endl;
	s << "passive_leader:  " << this->ai_->get_passive_leader() << std::endl;
	s << "passive_leader_shares_keep:  " << this->ai_->get_passive_leader_shares_keep() << std::endl;
	s << "recruitment_diversity:  " << this->ai_->get_recruitment_diversity() << std::endl;
	s << "recruitment_ignore_bad_combat:  " << this->ai_->get_recruitment_ignore_bad_combat() << std::endl;
	s << "recruitment_ignore_bad_movement:  " << this->ai_->get_recruitment_ignore_bad_movement() << std::endl;
	s << "recruitment_instructions:  " << std::endl << "----config begin----" << std::endl;
	s << this->ai_->get_recruitment_instructions() << "-----config end-----" << std::endl;
	s << "recruitment_more:  " << utils::join(this->ai_->get_recruitment_more()) << std::endl;
	s << "recruitment_pattern:  " << utils::join(this->ai_->get_recruitment_pattern()) << std::endl;
	s << "recruitment_randomness:  " << this->ai_->get_recruitment_randomness() << std::endl;
	s << "recruitment_save_gold:  " << std::endl << "----config begin----" << std::endl;
	s << this->ai_->get_recruitment_save_gold() << "-----config end-----" << std::endl;
	s << "scout_village_targeting:  " << this->ai_->get_scout_village_targeting() << std::endl;
	s << "simple_targeting:  " << this->ai_->get_simple_targeting() << std::endl;
	s << "support_villages:  " << this->ai_->get_support_villages() << std::endl;
	s << "village_value:  " << this->ai_->get_village_value() << std::endl;
	s << "villages_per_scout:  " << this->ai_->get_villages_per_scout() << std::endl;

	return s.str();
}
This information is not appropriate for an arbitrary custom ai! It should not be required. If it is determined that any ai should be able to give a report like this, then "get_overview" should become a new member function in ai::interface, and the implementation details above should be moved to ai_composite.

*************************************************************************************************************************************************

At some point in the past, it is suggested that to write an ai, you must
1) derive a class from ai::interface
2) register it in registry.cpp

I don't know because there's no up to date documentation, but it's looking like we are now intended to add custom ai's by deriving them from ai::ai_composite rather than ai::interface. However, play_turn is not a virtual member function of ai_composite so this won't work.

In fact, it looks like the ai factories in registry.cpp rely on these holder objects, so because of the above issues, any ai::interface that is registered will get cast to a composite_ai, which doesn't have a virtual play_turn function, so it will pretty much discard what ever you do. (See interface.hpp, registry.cpp.)

Can someone knowledgeable about the changes since then explain how C++ custom ai's are currently intended to be added to wesnoth?

Am I basically only intended to write a "stage" which can be added to composite_ai? If so, is there any easy way to prevent wesnoth from adding / get rid of the large number of default stages and aspects that I don't want to have?

If that is indeed the idea, let me suggest that as a newcomer, that seems like a far more daunting task, because it requires you to be familiar with the quite complicated architecture of composite_ai. ai::interface plays the logical role of separating the contract the ai must make with wesnoth from the implementation details; forcing everything to be derived from composite_ai is confusing and unnecessary. So I think the old way should continue to be supported.

****************************************************************************************************************************

Edit:

On closer inspection of how this all works, it turns out that the ai manager is actually completely disabled. As far as I can tell, the code that is supposed to instantiate registered ai's is actually unreachable:
Spoiler:
Instead, the manager code that is supposed to instantiate an AI from a config completely ignores the "ai_algorithm" field, checking only if it is "idle_ai" or not.

****************************************************************************************************************************************

Well life must go on so in the mean time I have found a hacky workaround.

If you want to get wesnoth load your custom C++ AI, the quickest way to get it into the game and running is

1) Add the "virtual" keyword to all public methods of composite_ai. in /src/ai/composite/ai.hpp
2) Derive your ai from class composite_ai in /ai/composite/ai.hpp, instead of from interface. If you override play_turn, none of the stages or any of that will have any effect.
3) Include your ai.hpp header files at the start of /ai/manager.cpp
4) In function holder::init() in manager.cpp @ line 83:

Code: Select all

...
        if (!this->ai_){
                ai_ = boost::shared_ptr<ai_composite>(new ai_composite(*default_ai_context_,cfg_));
        }
...
just add some code to manually case out on the value of ai_algorithm and register your ai's this way:

Code: Select all

...
	if (!this->ai_){
                //this case analysis added by iceiceice nov 23 2013, as the quickest hack that would work
                if (cfg_["ai_algorithm"] == "lp_1_ai") {
                    ai_ = boost::shared_ptr<ai_composite>(new lp_1_ai(*default_ai_context_, cfg_));
                }
                else if (cfg_["ai_algorithm"] == "lp_2_ai") {
                    ai_ = boost::shared_ptr<ai_composite>(new lp_2_ai(*default_ai_context_, cfg_));
                }
                else {
                    ai_ = boost::shared_ptr<ai_composite>(new ai_composite(*default_ai_context_,cfg_));
                }
	}
...
Also, make sure your wml [ai] tags in scenarios and in ai.cfg files have the field "version=11107" or some similarly large number, or your ai_algorithm field will be dropped, for reason of a different bug in src/ai/configuration.cpp. (see here)

~iceiceice~
Post Reply