2
0
Эх сурвалжийг харах

[experiments2] Experiments with AI module, stubs of automation interfaces.

Michał W. Urbańczyk 12 жил өмнө
parent
commit
be242b3d2f

+ 132 - 53
AI/BattleAI/BattleAI.cpp

@@ -9,6 +9,18 @@
 
 using boost::optional;
 shared_ptr<CBattleCallback> cbc;
+const CBattleAI *ai;
+
+template<class ForwardRange, class ValueFunction>
+auto maxElementByFun(const ForwardRange& rng, ValueFunction vf) -> decltype(boost::begin(rng))
+{
+	std::map<double, decltype(boost::begin(rng))> elements;
+	for(auto i = boost::begin(rng); i != boost::end(rng); i++)
+		elements[vf(*i)] = i;
+
+	return (--(elements.end()))->second;
+}
+
 
 #define LOGL(text) print(text)
 #define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
@@ -107,12 +119,18 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 	LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName())	;
 
 	cbc = cb; //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too)
+	ai = this;
+
 	try
 	{
 		print("activeStack called for " + stack->nodeName());
 		if(stack->type->idNumber == CreatureID::CATAPULT)
 			return useCatapult(stack);
 
+		print("Evaluating tactic situation...");
+		tacticInfo = make_unique<TacticInfo>();
+		print("Done!");
+
 		if(cb->battleCanCastSpell())
 			attemptCastingSpell();
 
@@ -143,7 +161,6 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 		{
 			if(stack->waited())
 			{
-				ThreatMap threatsToUs(stack);
 				auto dists = cbc->battleGetDistances(stack);
 				const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, boost::bind(isCloser, _1, _2, boost::ref(dists)));
 				if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE)
@@ -385,6 +402,8 @@ struct CurrentOffensivePotential
 // 	}
 // }
 
+
+
 void CBattleAI::attemptCastingSpell()
 {
 	LOGL("Casting spells sounds like fun. Let's see...");
@@ -403,7 +422,7 @@ void CBattleAI::attemptCastingSpell()
 	LOGFL("I can cast %d spells.", possibleSpells.size());
 
 	vstd::erase_if(possibleSpells, [](const CSpell *s) 
-	{return spellType(s) == OTHER; });
+	{ return spellType(s) == OTHER; });
 	LOGFL("I know about workings of %d of them.", possibleSpells.size());
 
 	//Get possible spell-target pairs
@@ -419,14 +438,7 @@ void CBattleAI::attemptCastingSpell()
 	LOGFL("Found %d spell-target combinations.", possibleCasts.size());
 	if(possibleCasts.empty())
 		return;
-
-	std::map<const CStack*, int> valueOfStack;
-	BOOST_FOREACH(auto stack, cb->battleGetStacks())
-	{
-		PotentialTargets pt(stack);
-		valueOfStack[stack] = pt.bestActionValue();
-	}
-
+	
 	auto evaluateSpellcast = [&] (const PossibleSpellcast &ps) -> int
 	{
 		const int skillLevel = hero->getSpellSchoolLevel(ps.spell);
@@ -451,9 +463,9 @@ void CBattleAI::attemptCastingSpell()
 				{
 					const int dmg = cb->calculateSpellDmg(ps.spell, hero, stack, skillLevel, spellPower);
 					if(stack->owner == playerID)
-						damageReceived += dmg;
+						damageReceived += priorities.stackEvaluator(stack) * dmg;
 					else
-						damageDealt += dmg;
+						damageDealt += priorities.stackEvaluator(stack) * dmg;
 				}
 
 				const int damageDiff = damageDealt - damageReceived;
@@ -461,36 +473,52 @@ void CBattleAI::attemptCastingSpell()
 
 				LOGFL("Casting %s on hex %d would deal %d damage points among %d stacks.",
 					ps.spell->name % ps.dest % damageDiff % stacksSuffering.size());
-				//TODO tactic effect too
+
 				return damageDiff;
 			}
 		case TIMED_EFFECT:
 			{
-				StackWithBonuses swb;
-				swb.stack = cb->battleGetStackByPos(ps.dest);
-				if(!swb.stack)
-					return -1;
 
-				Bonus pseudoBonus;
-				pseudoBonus.sid = ps.spell->id;
-				pseudoBonus.val = skillLevel;
-				pseudoBonus.turnsRemain = 1; //TODO
-				CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
+				auto affectedCreatures = cb->getAffectedCreatures(ps.spell, skillLevel, playerID, ps.dest);
+				if(affectedCreatures.empty())
+					return -1;
 
-				HypotheticChangesToBattleState state;
-				state.bonusesOfStacks[swb.stack] = &swb;
+				int baseValue = 0;
+				BOOST_FOREACH(auto &affectedStack, affectedCreatures)
+				{
+					StackWithBonuses swb;
+					swb.stack = affectedStack;
+
+					Bonus pseudoBonus;
+					pseudoBonus.sid = ps.spell->id;
+					pseudoBonus.val = skillLevel;
+					pseudoBonus.turnsRemain = 1; //TODO
+					CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
+
+					HypotheticChangesToBattleState state;
+					state.bonusesOfStacks[swb.stack] = &swb;
+
+					PotentialTargets pt(swb.stack, state);
+					auto newValue = pt.bestActionValue();
+					auto oldValue = tacticInfo->targets[swb.stack].bestActionValue();
+					auto gain = newValue - oldValue;
+					if(swb.stack->owner != playerID) //enemy
+						gain = -gain;
+
+					baseValue += gain;
+					LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)",
+						ps.spell->name % swb.stack->nodeName() % gain % (oldValue) % (newValue));
+				}
 
-				PotentialTargets pt(swb.stack, state);
-				auto newValue = pt.bestActionValue();
-				auto oldValue = valueOfStack[swb.stack];
-				auto gain = newValue - oldValue;
-				if(swb.stack->owner != playerID) //enemy
-					gain = -gain;
 
-				LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)",
-					ps.spell->name % swb.stack->nodeName() % gain % (oldValue) % (newValue));
+				const int time = std::min(spellPower, tacticInfo->expectedLength());
+				int ret = 0;
+				for(int i = 0; i < time; i++)
+					ret += (baseValue>>i);
 
-				return gain;
+				LOGFL("Spell %s has base value of %d, will last %d turns of which %d is useful. Total value: %d.",
+					ps.spell->name % baseValue % spellPower % time % ret);
+				return ret;
 			}
 		default:
 			assert(0);
@@ -498,17 +526,28 @@ void CBattleAI::attemptCastingSpell()
 		}
 	};
 
-	auto castToPerform = *vstd::maxElementByFun(possibleCasts, evaluateSpellcast);
-	LOGFL("Best spell is %s. Will cast.", castToPerform.spell->name);
-
-	BattleAction spellcast;
-	spellcast.actionType = Battle::HERO_SPELL;
-	spellcast.additionalInfo = castToPerform.spell->id;
-	spellcast.destinationTile = castToPerform.dest;
-	spellcast.side = side;
-	spellcast.stackNumber = (!side) ? -1 : -2;
+	const auto castToPerform = *maxElementByFun(possibleCasts, evaluateSpellcast);
+	const double spellValue = evaluateSpellcast(castToPerform);
+	const double spellCost = priorities.manaValue * cb->battleGetSpellCost(castToPerform.spell, hero);
+	LOGFL("Best spell is %s. Value is %d, cost %s.", castToPerform.spell->name % spellValue % spellCost);
 
-	cb->battleMakeAction(&spellcast);
+	if(spellValue >= spellCost)
+	{
+		LOGL("Will cast the spell.");
+		BattleAction spellcast;
+		spellcast.actionType = Battle::HERO_SPELL;
+		spellcast.additionalInfo = castToPerform.spell->id;
+		spellcast.destinationTile = castToPerform.dest;
+		spellcast.side = side;
+		spellcast.stackNumber = (!side) ? -1 : -2;
+
+		cb->battleMakeAction(&spellcast);
+	}
+	else
+	{
+		LOGL("Won't cast the spell.");
+		return;
+	}
 }
 
 std::vector<BattleHex> CBattleAI::getTargetsToConsider( const CSpell *spell ) const
@@ -617,19 +656,21 @@ int AttackPossibility::damageDiff() const
 
 int AttackPossibility::attackValue() const
 {
-	return damageDiff() + tacticImpact;
+	return damageDiff() /*+ tacticImpact*/;
 }
 
 AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex)
 {
 	auto attacker = AttackInfo.attacker;
-	auto enemy = AttackInfo.defender;
+	auto defender = AttackInfo.defender;
 
-	const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacks);
-	const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION);
+	const int initialAttackerCount = getValOr(state.stackCount, attacker, attacker->count);
+	const int initialDefenderCount = getValOr(state.stackCount, defender, defender->count);
+	const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, defender, defender->counterAttacks);
+	const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || defender->hasBonusOfType(Bonus::NO_RETALIATION);
 	const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT) || Selector::effectRange(Bonus::ONLY_MELEE_FIGHT)))->totalValue();
 
-	AttackPossibility ap = {enemy, hex, AttackInfo, 0, 0, 0};
+	AttackPossibility ap = {defender, hex, AttackInfo, 0, 0/*, 0*/};
 
 	auto curBai = AttackInfo; //we'll modify here the stack counts
 	for(int i  = 0; i < totalAttacks; i++)
@@ -642,18 +683,20 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo
 		if(remainingCounterAttacks <= i || counterAttacksBlocked)
 			ap.damageReceived = 0;
 
-		curBai.attackerCount = attacker->count - attacker->countKilledByAttack(ap.damageReceived).first;
-		curBai.defenderCount = enemy->count - enemy->countKilledByAttack(ap.damageDealt).first;
+		curBai.attackerCount = initialAttackerCount - attacker->countKilledByAttack(ap.damageReceived).first;
+		curBai.defenderCount = initialDefenderCount - defender->countKilledByAttack(ap.damageDealt).first;
 		if(!curBai.attackerCount) 
 			break;
 		//TODO what about defender? should we break? but in pessimistic scenario defender might be alive
 	}
 
+	//TODO LUCK!!!!!!!!!!!!
+
 	//TODO other damage related to attack (eg. fire shield and other abilities)
 
 	//Limit damages by total stack health
-	vstd::amin(ap.damageDealt, enemy->count * enemy->MaxHealth() - (enemy->MaxHealth() - enemy->firstHPleft));
-	vstd::amin(ap.damageReceived, attacker->count * attacker->MaxHealth() - (attacker->MaxHealth() - attacker->firstHPleft));
+	vstd::amin(ap.damageDealt, initialDefenderCount * defender->MaxHealth() - (defender->MaxHealth() - defender->firstHPleft));
+	vstd::amin(ap.damageReceived, initialAttackerCount * attacker->MaxHealth() - (attacker->MaxHealth() - attacker->firstHPleft));
 
 	return ap;
 }
@@ -706,7 +749,7 @@ AttackPossibility PotentialTargets::bestAction() const
 	if(possibleAttacks.empty())
 		throw std::runtime_error("No best action, since we don't have any actions");
 
-	return *vstd::maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.attackValue(); } );
+	return *maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.attackValue(); } );
 }
 
 int PotentialTargets::bestActionValue() const
@@ -723,3 +766,39 @@ void EnemyInfo::calcDmg(const CStack * ourStack)
 	adi = (dmg.first + dmg.second) / 2;
 	adr = (retal.first + retal.second) / 2;
 }
+
+TacticInfo::TacticInfo(const HypotheticChangesToBattleState &state /*= HypotheticChangesToBattleState()*/)
+{
+	ourPotential = enemyPotential = ourHealth = enemyhealth = 0;
+
+	BOOST_FOREACH(const CStack * ourStack, cbc->battleGetStacks(CBattleInfoEssentials::ONLY_MINE))
+	{
+		if(getValOr(state.stackCount, ourStack, ourStack->count) <= 0) continue;
+		targets[ourStack] = PotentialTargets(ourStack, state);
+		ourPotential += targets[ourStack].bestActionValue();
+		ourHealth += (ourStack->count-1) * ourStack->MaxHealth() + ourStack->firstHPleft;
+	}
+	BOOST_FOREACH(const CStack * enemyStack, cbc->battleGetStacks(CBattleInfoEssentials::ONLY_ENEMY))
+	{
+		if(getValOr(state.stackCount, enemyStack, enemyStack->count) <= 0) continue;
+		targets[enemyStack] = PotentialTargets(enemyStack, state);
+		enemyPotential += targets[enemyStack].bestActionValue();
+		enemyhealth += (enemyStack->count-1) * enemyStack->MaxHealth() + enemyStack->firstHPleft;
+	}
+}
+
+double TacticInfo::totalValue() const
+{
+	return ourPotential - enemyPotential;
+}
+
+int TacticInfo::expectedLength() const
+{
+	double value = totalValue();
+	if(value > 0)
+		return std::ceil(enemyhealth / ourPotential);
+	else if(value < 0)
+		return std::ceil(ourHealth / enemyPotential);
+
+	return 10000; 
+}

+ 21 - 1
AI/BattleAI/BattleAI.h

@@ -64,6 +64,7 @@ struct HypotheticChangesToBattleState
 {
 	std::map<const CStack *, const IBonusBearer *> bonusesOfStacks;
 	std::map<const CStack *, int> counterAttacksLeft;
+	std::map<const CStack *, int> stackCount;
 };
 
 struct AttackPossibility
@@ -74,7 +75,7 @@ struct AttackPossibility
 
 	int damageDealt;
 	int damageReceived; //usually by counter-attack
-	int tacticImpact;
+	//int tacticImpact;
 
 	int damageDiff() const;
 	int attackValue() const;
@@ -107,6 +108,23 @@ struct PotentialTargets
 	int bestActionValue() const;
 };
 
+struct TacticInfo
+{
+	double ourPotential;
+	double enemyPotential;
+
+	double ourHealth;
+	double enemyhealth;
+
+	std::map<const CStack*, PotentialTargets> targets;
+
+	TacticInfo(const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState());
+
+	double totalValue() const;
+
+	int expectedLength() const;
+};
+
 class CBattleAI : public CBattleGameInterface
 {
 	int side;
@@ -115,6 +133,8 @@ class CBattleAI : public CBattleGameInterface
 	//Previous setting of cb 
 	bool wasWaitingForRealize, wasUnlockingGs;
 
+	unique_ptr<TacticInfo> tacticInfo;
+
 	void print(const std::string &text) const;
 public:
 	CBattleAI(void);

+ 120 - 0
CCallback.cpp

@@ -411,3 +411,123 @@ bool CBattleCallback::battleMakeTacticAction( BattleAction * action )
 	sendRequest(&ma);
 	return true;
 }
+
+bool CAutomationCallback::moveHero(const CGHeroInstance *h, int3 dst)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+bool CAutomationCallback::dismissHero(const CGHeroInstance * hero)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+void CAutomationCallback::dig(const CGObjectInstance *hero)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+void CAutomationCallback::castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos /*= int3(-1, -1, -1) */)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+void CAutomationCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+bool CAutomationCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+void CAutomationCallback::recruitCreatures(const CGObjectInstance *obj, CreatureID ID, ui32 amount, si32 level/*=-1*/)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+bool CAutomationCallback::upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID/*=CreatureID::NONE*/)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+void CAutomationCallback::swapGarrisonHero(const CGTownInstance *town)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+void CAutomationCallback::trade(const CGObjectInstance *market, EMarketMode::EMarketMode mode, int id1, int id2, int val1, const CGHeroInstance *hero /*= NULL*/)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+int CAutomationCallback::selectionMade(int selection, QueryID queryID)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+int CAutomationCallback::swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+int CAutomationCallback::mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+int CAutomationCallback::mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+int CAutomationCallback::splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+bool CAutomationCallback::swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+bool CAutomationCallback::assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+bool CAutomationCallback::dismissCreature(const CArmedInstance *obj, SlotID stackPos)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+void CAutomationCallback::endTurn()
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+void CAutomationCallback::buyArtifact(const CGHeroInstance *hero, ArtifactID aid)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+void CAutomationCallback::setFormation(const CGHeroInstance * hero, bool tight)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+void CAutomationCallback::setSelection(const CArmedInstance * obj)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+void CAutomationCallback::buildBoat(const IShipyard *obj)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+shared_ptr<CAutomationInterface> CAutomationCallback::getModule()
+{
+	return automationModule.lock();
+}

+ 41 - 0
CCallback.h

@@ -29,6 +29,7 @@ struct CPathsInfo;
 struct CPack;
 class IBattleEventsReceiver;
 class IGameEventsReceiver;
+class CAutomationInterface;
 
 class IBattleCallback
 {
@@ -155,3 +156,43 @@ public:
 //friends
 	friend class CClient;
 };
+
+class CAutomationCallback : public CCallback
+{
+	std::weak_ptr<CAutomationInterface> automationModule;
+
+	shared_ptr<CAutomationInterface> getModule();
+
+	bool canAccessHero(const CGHeroInstance *h);
+	bool canRecruitAt(const CGDwelling *dwelling);
+
+public:
+	//overloads
+	virtual bool moveHero(const CGHeroInstance *h, int3 dst);
+	virtual bool dismissHero(const CGHeroInstance * hero);
+	virtual void dig(const CGObjectInstance *hero);
+	virtual void castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos = int3(-1, -1, -1) );
+	virtual void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero);
+	virtual bool buildBuilding(const CGTownInstance *town, BuildingID buildingID);
+	virtual void recruitCreatures(const CGObjectInstance *obj, CreatureID ID, ui32 amount, si32 level=-1);
+	virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE);
+	virtual void swapGarrisonHero(const CGTownInstance *town);
+	virtual void trade(const CGObjectInstance *market, EMarketMode::EMarketMode mode, int id1, int id2, int val1, const CGHeroInstance *hero = NULL);
+	virtual int selectionMade(int selection, QueryID queryID);
+	virtual int swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2);
+	virtual int mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2);
+	virtual int mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2);
+	virtual int splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val);
+	virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2);
+	virtual bool assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo);
+	virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos);
+	virtual void endTurn();
+	virtual void buyArtifact(const CGHeroInstance *hero, ArtifactID aid);
+	virtual void setFormation(const CGHeroInstance * hero, bool tight);
+	virtual void setSelection(const CArmedInstance * obj);
+	virtual void buildBoat(const IShipyard *obj);
+
+
+	//new, automation specific
+	void sendMessage(const boost::any &msg);
+};

+ 5 - 0
lib/CGameInterface.cpp

@@ -250,3 +250,8 @@ void CBattleGameInterface::saveGame(COSer<CSaveFile> &h, const int version)
 void CBattleGameInterface::loadGame(CISer<CLoadFile> &h, const int version)
 {
 }
+
+void CAutomationModule::receivedMessage(const boost::any &msg)
+{
+
+}

+ 33 - 0
lib/CGameInterface.h

@@ -52,6 +52,8 @@ template <typename Serializer> class CISer;
 template <typename Serializer> class COSer;
 struct ArtifactLocation;
 class CScriptingModule;
+class CAutomationModule;
+
 
 class DLL_LINKAGE CBattleGameInterface : public IBattleEventsReceiver
 {
@@ -143,3 +145,34 @@ public:
 	virtual void saveGame(COSer<CSaveFile> &h, const int version); //saving
 	virtual void loadGame(CISer<CLoadFile> &h, const int version); //loading
 };
+
+struct ReceivedObjects
+{
+	std::vector<const CGObjectInstance *> objects;
+
+	bool hasObject(const CGObjectInstance *obj) const
+	{
+		return vstd::contains(objects, obj);
+	}
+	bool remove(const CGObjectInstance *obj)
+	{
+		auto it = range::find(objects, obj);
+		if(it != objects.end())
+			objects.erase(it);
+	}
+	bool add(const CGObjectInstance *obj)
+	{
+		objects.push_back(obj);
+	}
+};
+
+class DLL_LINKAGE CAutomationModule : public CGameInterface
+{
+	ReceivedObjects receivedObjects;
+
+public:
+
+	virtual void receivedMessage(const boost::any &msg);
+
+	friend class CAutomationModule;
+};