2018-08-10 18:27:57 +02:00
/*
* FuzzyHelper . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
# include "FuzzyHelper.h"
2018-12-01 10:30:37 +02:00
# include "Goals/Goals.h"
2018-08-10 18:27:57 +02:00
# include "VCAI.h"
2023-06-02 21:00:44 +02:00
# include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
# include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
2023-06-06 17:32:53 +02:00
# include "../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
2023-06-02 21:00:44 +02:00
# include "../../lib/mapObjects/CBank.h"
2023-06-17 14:21:42 +02:00
# include "../../lib/mapObjects/CGCreature.h"
2023-06-02 21:00:44 +02:00
# include "../../lib/mapObjects/CGDwelling.h"
2023-06-23 17:02:48 +02:00
# include "../../lib/gameState/InfoAboutArmy.h"
2023-06-02 20:47:37 +02:00
2018-08-10 18:27:57 +02:00
FuzzyHelper * fh ;
Goals : : TSubgoal FuzzyHelper : : chooseSolution ( Goals : : TGoalVec vec )
{
2018-09-11 12:09:14 +02:00
if ( vec . empty ( ) )
{
logAi - > debug ( " FuzzyHelper found no goals. Returning Goals::Invalid. " ) ;
//no possibilities found
2018-08-10 18:27:57 +02:00
return sptr ( Goals : : Invalid ( ) ) ;
2018-09-11 12:09:14 +02:00
}
2018-08-10 18:27:57 +02:00
//a trick to switch between heroes less often - calculatePaths is costly
auto sortByHeroes = [ ] ( const Goals : : TSubgoal & lhs , const Goals : : TSubgoal & rhs ) - > bool
{
return lhs - > hero . h < rhs - > hero . h ;
} ;
boost : : sort ( vec , sortByHeroes ) ;
for ( auto g : vec )
{
setPriority ( g ) ;
}
auto compareGoals = [ ] ( const Goals : : TSubgoal & lhs , const Goals : : TSubgoal & rhs ) - > bool
{
return lhs - > priority < rhs - > priority ;
} ;
2018-09-11 12:09:14 +02:00
for ( auto goal : vec )
{
2018-12-16 16:46:48 +02:00
logAi - > trace ( " FuzzyHelper evaluated goal %s, priority=%.4f " , goal - > name ( ) , goal - > priority ) ;
2018-09-11 12:09:14 +02:00
}
Goals : : TSubgoal result = * boost : : max_element ( vec , compareGoals ) ;
2018-12-16 16:46:48 +02:00
logAi - > debug ( " FuzzyHelper returned goal %s, priority=%.4f " , result - > name ( ) , result - > priority ) ;
2018-09-11 12:09:14 +02:00
return result ;
2018-08-10 18:27:57 +02:00
}
ui64 FuzzyHelper : : estimateBankDanger ( const CBank * bank )
{
//this one is not fuzzy anymore, just calculate weighted average
2023-10-28 11:27:10 +02:00
auto objectInfo = bank - > getObjectHandler ( ) - > getObjectInfo ( bank - > appearance ) ;
2018-08-10 18:27:57 +02:00
CBankInfo * bankInfo = dynamic_cast < CBankInfo * > ( objectInfo . get ( ) ) ;
ui64 totalStrength = 0 ;
ui8 totalChance = 0 ;
2024-01-01 16:37:48 +02:00
for ( auto config : bankInfo - > getPossibleGuards ( bank - > cb ) )
2018-08-10 18:27:57 +02:00
{
totalStrength + = config . second . totalStrength * config . first ;
totalChance + = config . first ;
}
return totalStrength / std : : max < ui8 > ( totalChance , 1 ) ; //avoid division by zero
}
float FuzzyHelper : : evaluate ( Goals : : VisitTile & g )
{
2018-12-24 15:19:04 +02:00
if ( g . parent )
{
g . parent - > accept ( this ) ;
}
2018-08-10 18:27:57 +02:00
return visitTileEngine . evaluate ( g ) ;
}
2018-10-09 21:31:44 +02:00
float FuzzyHelper : : evaluate ( Goals : : BuildBoat & g )
{
const float buildBoatPenalty = 0.25 ;
if ( ! g . parent )
{
return 0 ;
}
return g . parent - > accept ( this ) - buildBoatPenalty ;
}
2019-01-07 23:33:31 +02:00
float FuzzyHelper : : evaluate ( Goals : : AdventureSpellCast & g )
{
if ( ! g . parent )
{
return 0 ;
}
const CSpell * spell = g . getSpell ( ) ;
const float spellCastPenalty = ( float ) g . hero - > getSpellCost ( spell ) / g . hero - > mana ;
return g . parent - > accept ( this ) - spellCastPenalty ;
}
2018-12-09 15:20:46 +02:00
float FuzzyHelper : : evaluate ( Goals : : CompleteQuest & g )
{
// TODO: How to evaluate quest complexity?
2020-10-01 10:38:06 +02:00
const float questPenalty = 0.2f ;
2018-12-09 15:20:46 +02:00
if ( ! g . parent )
{
return 0 ;
}
return g . parent - > accept ( this ) * questPenalty ;
}
2018-08-10 18:27:57 +02:00
float FuzzyHelper : : evaluate ( Goals : : VisitObj & g )
{
2018-12-24 15:19:04 +02:00
if ( g . parent )
{
g . parent - > accept ( this ) ;
}
2018-08-10 20:36:42 +02:00
return visitObjEngine . evaluate ( g ) ;
2018-08-10 18:27:57 +02:00
}
2019-01-07 23:33:31 +02:00
2018-08-10 18:27:57 +02:00
float FuzzyHelper : : evaluate ( Goals : : VisitHero & g )
{
auto obj = ai - > myCb - > getObj ( ObjectInstanceID ( g . objid ) ) ; //we assume for now that these goals are similar
if ( ! obj )
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
{
2018-08-10 18:27:57 +02:00
return - 100 ; //hero died in the meantime
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
}
2018-08-22 12:32:48 +02:00
else
{
2018-10-09 21:31:44 +02:00
g . setpriority ( Goals : : VisitTile ( obj - > visitablePos ( ) ) . sethero ( g . hero ) . accept ( this ) ) ;
2018-08-22 12:32:48 +02:00
}
2018-08-10 18:27:57 +02:00
return g . priority ;
}
float FuzzyHelper : : evaluate ( Goals : : GatherArmy & g )
{
//the more army we need, the more important goal
//the more army we lack, the less important goal
2020-10-01 10:38:06 +02:00
float army = static_cast < float > ( g . hero - > getArmyStrength ( ) ) ;
2018-08-10 18:27:57 +02:00
float ratio = g . value / std : : max ( g . value - army , 2000.0f ) ; //2000 is about the value of hero recruited from tavern
return 5 * ( ratio / ( ratio + 2 ) ) ; //so 50% army gives 2.5, asymptotic 5
}
float FuzzyHelper : : evaluate ( Goals : : ClearWayTo & g )
{
2018-08-21 08:40:47 +02:00
if ( ! g . hero . h )
return 0 ; //lowest priority
2018-08-10 18:27:57 +02:00
2018-08-12 13:31:31 +02:00
return g . whatToDoToAchieve ( ) - > accept ( this ) ;
2018-08-10 18:27:57 +02:00
}
float FuzzyHelper : : evaluate ( Goals : : BuildThis & g )
{
return g . priority ; //TODO
}
float FuzzyHelper : : evaluate ( Goals : : DigAtTile & g )
{
return 0 ;
}
float FuzzyHelper : : evaluate ( Goals : : CollectRes & g )
{
return g . priority ; //handled by ResourceManager
}
float FuzzyHelper : : evaluate ( Goals : : Build & g )
{
return 0 ;
}
float FuzzyHelper : : evaluate ( Goals : : BuyArmy & g )
{
return g . priority ;
}
float FuzzyHelper : : evaluate ( Goals : : Explore & g )
{
return 1 ;
}
float FuzzyHelper : : evaluate ( Goals : : RecruitHero & g )
{
return 1 ;
}
float FuzzyHelper : : evaluate ( Goals : : Invalid & g )
{
return - 1e10 ;
}
float FuzzyHelper : : evaluate ( Goals : : AbstractGoal & g )
{
logAi - > warn ( " Cannot evaluate goal %s " , g . name ( ) ) ;
return g . priority ;
}
void FuzzyHelper : : setPriority ( Goals : : TSubgoal & g ) //calls evaluate - Visitor pattern
{
g - > setpriority ( g - > accept ( this ) ) ; //this enforces returned value is set
}
2019-02-10 15:25:17 +02:00
ui64 FuzzyHelper : : evaluateDanger ( crint3 tile , const CGHeroInstance * visitor )
{
2023-07-31 16:00:37 +02:00
return evaluateDanger ( tile , visitor , ai ) ;
2019-02-10 15:25:17 +02:00
}
2019-02-16 10:33:24 +02:00
ui64 FuzzyHelper : : evaluateDanger ( crint3 tile , const CGHeroInstance * visitor , const VCAI * ai )
2019-02-10 15:25:17 +02:00
{
2019-02-16 10:33:24 +02:00
auto cb = ai - > myCb ;
2019-02-10 15:25:17 +02:00
const TerrainTile * t = cb - > getTile ( tile , false ) ;
if ( ! t ) //we can know about guard but can't check its tile (the edge of fow)
return 190000000 ; //MUCH
ui64 objectDanger = 0 ;
ui64 guardDanger = 0 ;
auto visitableObjects = cb - > getVisitableObjs ( tile ) ;
// in some scenarios hero happens to be "under" the object (eg town). Then we consider ONLY the hero.
if ( vstd : : contains_if ( visitableObjects , objWithID < Obj : : HERO > ) )
{
vstd : : erase_if ( visitableObjects , [ ] ( const CGObjectInstance * obj )
{
return ! objWithID < Obj : : HERO > ( obj ) ;
} ) ;
}
if ( const CGObjectInstance * dangerousObject = vstd : : backOrNull ( visitableObjects ) )
{
2019-02-16 10:33:24 +02:00
objectDanger = evaluateDanger ( dangerousObject , ai ) ; //unguarded objects can also be dangerous or unhandled
2019-02-10 15:25:17 +02:00
if ( objectDanger )
{
//TODO: don't downcast objects AI shouldn't know about!
auto armedObj = dynamic_cast < const CArmedInstance * > ( dangerousObject ) ;
if ( armedObj )
{
float tacticalAdvantage = tacticalAdvantageEngine . getTacticalAdvantage ( visitor , armedObj ) ;
2020-10-01 10:38:06 +02:00
objectDanger = static_cast < ui64 > ( objectDanger * tacticalAdvantage ) ; //this line tends to go infinite for allied towns (?)
2019-02-10 15:25:17 +02:00
}
}
if ( dangerousObject - > ID = = Obj : : SUBTERRANEAN_GATE )
{
//check guard on the other side of the gate
auto it = ai - > knownSubterraneanGates . find ( dangerousObject ) ;
if ( it ! = ai - > knownSubterraneanGates . end ( ) )
{
auto guards = cb - > getGuardingCreatures ( it - > second - > visitablePos ( ) ) ;
for ( auto cre : guards )
{
2019-02-16 10:33:24 +02:00
float tacticalAdvantage = tacticalAdvantageEngine . getTacticalAdvantage ( visitor , dynamic_cast < const CArmedInstance * > ( cre ) ) ;
vstd : : amax ( guardDanger , evaluateDanger ( cre , ai ) * tacticalAdvantage ) ;
2019-02-10 15:25:17 +02:00
}
}
}
}
auto guards = cb - > getGuardingCreatures ( tile ) ;
for ( auto cre : guards )
{
2019-02-16 10:33:24 +02:00
float tacticalAdvantage = tacticalAdvantageEngine . getTacticalAdvantage ( visitor , dynamic_cast < const CArmedInstance * > ( cre ) ) ;
vstd : : amax ( guardDanger , evaluateDanger ( cre , ai ) * tacticalAdvantage ) ; //we are interested in strongest monster around
2019-02-10 15:25:17 +02:00
}
//TODO mozna odwiedzic blockvis nie ruszajac straznika
return std : : max ( objectDanger , guardDanger ) ;
}
2019-02-16 10:33:24 +02:00
ui64 FuzzyHelper : : evaluateDanger ( const CGObjectInstance * obj , const VCAI * ai )
2019-02-10 15:25:17 +02:00
{
2019-02-16 10:33:24 +02:00
auto cb = ai - > myCb ;
2023-08-27 00:35:38 +02:00
if ( obj - > tempOwner . isValidPlayer ( ) & & cb - > getPlayerRelations ( obj - > tempOwner , ai - > playerID ) ! = PlayerRelations : : ENEMIES ) //owned or allied objects don't pose any threat
2019-02-10 15:25:17 +02:00
return 0 ;
switch ( obj - > ID )
{
case Obj : : HERO :
{
InfoAboutHero iah ;
cb - > getHeroInfo ( obj , iah ) ;
return iah . army . getStrength ( ) ;
}
case Obj : : TOWN :
case Obj : : GARRISON :
case Obj : : GARRISON2 :
{
InfoAboutTown iat ;
cb - > getTownInfo ( obj , iat ) ;
return iat . army . getStrength ( ) ;
}
case Obj : : MONSTER :
{
//TODO!!!!!!!!
const CGCreature * cre = dynamic_cast < const CGCreature * > ( obj ) ;
return cre - > getArmyStrength ( ) ;
}
case Obj : : CREATURE_GENERATOR1 :
case Obj : : CREATURE_GENERATOR4 :
{
const CGDwelling * d = dynamic_cast < const CGDwelling * > ( obj ) ;
return d - > getArmyStrength ( ) ;
}
case Obj : : MINE :
case Obj : : ABANDONED_MINE :
{
const CArmedInstance * a = dynamic_cast < const CArmedInstance * > ( obj ) ;
return a - > getArmyStrength ( ) ;
}
case Obj : : CRYPT : //crypt
case Obj : : CREATURE_BANK : //crebank
case Obj : : DRAGON_UTOPIA :
case Obj : : SHIPWRECK : //shipwreck
case Obj : : DERELICT_SHIP : //derelict ship
case Obj : : PYRAMID :
2023-10-28 11:27:10 +02:00
return estimateBankDanger ( dynamic_cast < const CBank * > ( obj ) ) ;
2019-02-10 15:25:17 +02:00
default :
return 0 ;
}
2023-06-02 20:47:37 +02:00
}