1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

Merge pull request #465 from vcmi/AIMapObjectEvaluation

Okay let's see where this gets us.
This commit is contained in:
DjWarmonger
2018-08-04 15:07:08 +02:00
committed by GitHub
16 changed files with 397 additions and 31 deletions

View File

@@ -11,6 +11,7 @@ set(VCAI_SRCS
AIUtility.cpp
AIhelper.cpp
ResourceManager.cpp
MapObjectsEvaluator.cpp
Fuzzy.cpp
Goals.cpp
main.cpp
@@ -23,6 +24,7 @@ set(VCAI_HEADERS
AIUtility.h
AIhelper.h
ResourceManager.h
MapObjectsEvaluator.h
Fuzzy.h
Goals.h
VCAI.h

View File

@@ -19,6 +19,7 @@
#include "../../lib/VCMI_Lib.h"
#include "../../CCallback.h"
#include "VCAI.h"
#include "MapObjectsEvaluator.h"
#define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
@@ -97,6 +98,8 @@ FuzzyHelper::FuzzyHelper()
ta.configure();
initVisitTile();
vt.configure();
initWanderTarget();
wanderTarget.configure();
}
@@ -199,6 +202,20 @@ void FuzzyHelper::initTacticalAdvantage()
}
}
float FuzzyHelper::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const
{
float turns = 0.0f;
float distance = CPathfinderHelper::getMovementCost(h, tile);
if(distance)
{
if(distance < h->movement) //we can move there within one turn
turns = (fl::scalar)distance / h->movement;
else
turns = 1 + (fl::scalar)(distance - h->movement) / h->maxMovePoints(true); //bool on land?
}
return turns;
}
ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
{
//this one is not fuzzy anymore, just calculate weighted average
@@ -272,6 +289,38 @@ float FuzzyHelper::getTacticalAdvantage(const CArmedInstance * we, const CArmedI
return output;
}
float FuzzyHelper::getWanderTargetObjectValue(const CGHeroInstance & h, const ObjectIdRef & obj)
{
float distFromObject = calculateTurnDistanceInputValue(&h, obj->pos);
boost::optional<int> objValueKnownByAI = MapObjectsEvaluator::getInstance().getObjectValue(obj->ID, obj->subID);
int objValue = 0;
if(objValueKnownByAI != boost::none) //consider adding value manipulation based on object instances on map
{
objValue = std::min(std::max(objValueKnownByAI.get(), 0), 20000);
}
else
{
MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0);
logGlobal->warn("AI met object type it doesn't know - ID: " + std::to_string(obj->ID) + ", subID: " + std::to_string(obj->subID) + " - adding to database with value " + std::to_string(objValue));
}
float output = -1.0f;
try
{
wanderTarget.distance->setValue(distFromObject);
wanderTarget.objectValue->setValue(objValue);
wanderTarget.engine.process();
output = wanderTarget.visitGain->getValue();
}
catch (fl::Exception & fe)
{
logAi->error("evaluate getWanderTargetObjectValue: %s", fe.getWhat());
}
assert(output >= 0.0f);
return output;
}
FuzzyHelper::TacticalAdvantage::~TacticalAdvantage()
{
//TODO: smart pointers?
@@ -413,6 +462,55 @@ void FuzzyHelper::initVisitTile()
}
}
void FuzzyHelper::initWanderTarget()
{
try
{
wanderTarget.distance = new fl::InputVariable("distance"); //distance on map from object
wanderTarget.objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI
wanderTarget.visitGain = new fl::OutputVariable("visitGain");
wanderTarget.visitGain->setMinimum(0);
wanderTarget.visitGain->setMaximum(10);
wanderTarget.engine.addInputVariable(wanderTarget.distance);
wanderTarget.engine.addInputVariable(wanderTarget.objectValue);
wanderTarget.engine.addOutputVariable(wanderTarget.visitGain);
//for now distance variable same as in as VisitTile
wanderTarget.distance->addTerm(new fl::Ramp("SHORT", 0.5, 0));
wanderTarget.distance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8));
wanderTarget.distance->addTerm(new fl::Ramp("LONG", 0.5, 3));
wanderTarget.distance->setRange(0, 3.0);
//objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges
wanderTarget.objectValue->addTerm(new fl::Ramp("LOW", 3000, 0)); //I have feeling that concave shape might work well instead of ramp for objectValue FL terms
wanderTarget.objectValue->addTerm(new fl::Triangle("MEDIUM", 2500, 6000));
wanderTarget.objectValue->addTerm(new fl::Ramp("HIGH", 5000, 20000));
wanderTarget.objectValue->setRange(0, 20000); //relic artifact value is border value by design, even better things are scaled down.
wanderTarget.visitGain->addTerm(new fl::Ramp("LOW", 5, 0));
wanderTarget.visitGain->addTerm(new fl::Triangle("MEDIUM", 4, 6));
wanderTarget.visitGain->addTerm(new fl::Ramp("HIGH", 5, 10));
wanderTarget.visitGain->setRange(0, 10);
wanderTarget.addRule("if distance is LONG and objectValue is HIGH then visitGain is MEDIUM");
wanderTarget.addRule("if distance is MEDIUM and objectValue is HIGH then visitGain is somewhat HIGH");
wanderTarget.addRule("if distance is SHORT and objectValue is HIGH then visitGain is HIGH");
wanderTarget.addRule("if distance is LONG and objectValue is MEDIUM then visitGain is somewhat LOW");
wanderTarget.addRule("if distance is MEDIUM and objectValue is MEDIUM then visitGain is MEDIUM");
wanderTarget.addRule("if distance is SHORT and objectValue is MEDIUM then visitGain is somewhat HIGH");
wanderTarget.addRule("if distance is LONG and objectValue is LOW then visitGain is very LOW");
wanderTarget.addRule("if distance is MEDIUM and objectValue is LOW then visitGain is LOW");
wanderTarget.addRule("if distance is SHORT and objectValue is LOW then visitGain is MEDIUM");
}
catch(fl::Exception & fe)
{
logAi->error("FindWanderTarget: %s", fe.getWhat());
}
}
float FuzzyHelper::evaluate(Goals::VisitTile & g)
{
//we assume that hero is already set and we want to choose most suitable one for the mission
@@ -420,20 +518,7 @@ float FuzzyHelper::evaluate(Goals::VisitTile & g)
return 0;
//assert(cb->isInTheMap(g.tile));
float turns = 0;
float distance = CPathfinderHelper::getMovementCost(g.hero.h, g.tile);
if(!distance) //we stand on that tile
{
turns = 0;
}
else
{
if(distance < g.hero->movement) //we can move there within one turn
turns = (fl::scalar)distance / g.hero->movement;
else
turns = 1 + (fl::scalar)(distance - g.hero->movement) / g.hero->maxMovePoints(true); //bool on land?
}
float turns = calculateTurnDistanceInputValue(g.hero.h, g.tile);
float missionImportance = 0;
if(vstd::contains(ai->lockedHeroes, g.hero))
missionImportance = ai->lockedHeroes[g.hero]->priority;
@@ -549,3 +634,10 @@ void FuzzyHelper::setPriority(Goals::TSubgoal & g) //calls evaluate - Visitor pa
{
g->setpriority(g->accept(this)); //this enforces returned value is set
}
FuzzyHelper::EvalWanderTargetObject::~EvalWanderTargetObject()
{
delete distance;
delete objectValue;
delete visitGain;
}

View File

@@ -55,7 +55,17 @@ class FuzzyHelper
~EvalVisitTile();
} vt;
class EvalWanderTargetObject : public engineBase //designed for use with VCAI::wander()
{
public:
fl::InputVariable * distance;
fl::InputVariable * objectValue;
fl::OutputVariable * visitGain;
~EvalWanderTargetObject();
} wanderTarget;
private:
float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const;
public:
enum RuleBlocks {BANK_DANGER, TACTICAL_ADVANTAGE, VISIT_TILE};
@@ -64,6 +74,7 @@ public:
FuzzyHelper();
void initTacticalAdvantage();
void initVisitTile();
void initWanderTarget();
float evaluate(Goals::Explore & g);
float evaluate(Goals::RecruitHero & g);
@@ -82,6 +93,7 @@ public:
ui64 estimateBankDanger(const CBank * bank);
float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
float getWanderTargetObjectValue(const CGHeroInstance & h, const ObjectIdRef & obj);
Goals::TSubgoal chooseSolution(Goals::TGoalVec vec);
//std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec);

View File

@@ -0,0 +1,63 @@
#include "StdInc.h"
#include "MapObjectsEvaluator.h"
#include "../../lib/GameConstants.h"
#include "../../lib/VCMI_Lib.h"
MapObjectsEvaluator & MapObjectsEvaluator::getInstance()
{
static std::unique_ptr<MapObjectsEvaluator> singletonInstance;
if(singletonInstance == nullptr)
singletonInstance.reset(new MapObjectsEvaluator());
return *(singletonInstance.get());
}
MapObjectsEvaluator::MapObjectsEvaluator()
{
for(auto primaryID : VLC->objtypeh->knownObjects())
{
for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
{
auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
if(!handler->isStaticObject())
{
if(handler->getAiValue() != boost::none)
{
objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = handler->getAiValue().get();
}
else if(VLC->objtypeh->getObjGroupAiValue(primaryID) != boost::none) //if value is not initialized - fallback to default value for this object family if it exists
{
objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = VLC->objtypeh->getObjGroupAiValue(primaryID).get();
}
else
{
objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0; //some default handling when aiValue not found
}
}
}
}
}
boost::optional<int> MapObjectsEvaluator::getObjectValue(int primaryID, int secondaryID) const
{
CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID);
auto object = objectDatabase.find(internalIdentifier);
if(object != objectDatabase.end())
return object->second;
logGlobal->trace("Unknown object for AI, ID: " + std::to_string(primaryID) + ", SubID: " + std::to_string(secondaryID));
return boost::optional<int>();
}
void MapObjectsEvaluator::addObjectData(int primaryID, int secondaryID, int value) //by current design it updates value if already in AI database
{
CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID);
objectDatabase[internalIdentifier] = value;
}
void MapObjectsEvaluator::removeObjectData(int primaryID, int secondaryID)
{
CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID);
vstd::erase_if_present(objectDatabase, internalIdentifier);
}

View File

@@ -0,0 +1,25 @@
/*
* MapObjectsEvaluator.h, 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
*
*/
#pragma once
#include "../../lib/mapObjects/CObjectClassesHandler.h"
class MapObjectsEvaluator
{
private:
std::map<CompoundMapObjectID, int> objectDatabase; //value for each object type
public:
MapObjectsEvaluator();
static MapObjectsEvaluator & getInstance();
boost::optional<int> getObjectValue(int primaryID, int secondaryID) const;
void addObjectData(int primaryID, int secondaryID, int value);
void removeObjectData(int primaryID, int secondaryID);
};

View File

@@ -1592,7 +1592,12 @@ void VCAI::wander(HeroPtr h)
if(dests.size()) //performance improvement
{
const ObjectIdRef & dest = *boost::min_element(dests, CDistanceSorter(h.get())); //find next closest one
auto fuzzyLogicSorter = [h](const ObjectIdRef & l, const ObjectIdRef & r) -> bool //TODO: create elementar GetObj goal usable for goal decomposition and Wander based on VisitTile logic and object value on top of it
{
return fh->getWanderTargetObjectValue( *h.get(), l) < fh->getWanderTargetObjectValue(*h.get(), r);
};
const ObjectIdRef & dest = *boost::max_element(dests, fuzzyLogicSorter); //find best object to visit based on fuzzy logic evaluation, TODO: use elementar version of GetObj here in future
//wander should not cause heroes to be reserved - they are always considered free
logAi->debug("Of all %d destinations, object oid=%d seems nice", dests.size(), dest.id.getNum());

View File

@@ -141,6 +141,7 @@
<ClCompile Include="Fuzzy.cpp" />
<ClCompile Include="Goals.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="MapObjectsEvaluator.cpp" />
<ClCompile Include="StdInc.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
@@ -153,6 +154,7 @@
<ClInclude Include="AIUtility.h" />
<ClInclude Include="Fuzzy.h" />
<ClInclude Include="Goals.h" />
<ClInclude Include="MapObjectsEvaluator.h" />
<ClInclude Include="StdInc.h" />
<ClInclude Include="VCAI.h" />
</ItemGroup>

View File

@@ -12,6 +12,7 @@
{
"index" : 0,
"name" : "Cyclops Stockpile",
"aiValue" : 3000,
"sounds" : {
"ambient" : ["LOOPCAVE"]
},
@@ -120,6 +121,7 @@
"index" : 1,
"resetDuration" : 0,
"name" : "Dwarven Treasury",
"aiValue" : 2000,
"sounds" : {
"ambient" : ["LOOPDWAR"]
},
@@ -210,6 +212,7 @@
"index" : 2,
"resetDuration" : 0,
"name" : "Griffin Conservatory",
"aiValue" : 9000,
"sounds" : {
"ambient" : ["LOOPGRIF"]
},
@@ -284,6 +287,7 @@
"index" : 3,
"resetDuration" : 0,
"name" : "Imp Cache",
"aiValue" : 1500,
"sounds" : {
"ambient" : ["LOOPFIRE"]
},
@@ -373,6 +377,7 @@
"index" : 4,
"resetDuration" : 0,
"name" : "Medusa Stores",
"aiValue" : 1500,
"sounds" : {
"ambient" : ["LOOPMEDU"]
},
@@ -463,6 +468,7 @@
"index" : 5,
"resetDuration" : 0,
"name" : "Naga Bank",
"aiValue" : 3000,
"sounds" : {
"ambient" : ["LOOPNAGA"]
},
@@ -553,6 +559,7 @@
"index" : 6,
"resetDuration" : 0,
"name" : "Dragon Fly Hive",
"aiValue" : 9000,
"sounds" : {
"ambient" : ["LOOPLEAR"]
},
@@ -633,6 +640,7 @@
"index" : 0,
"resetDuration" : 0,
"name" : "Shipwreck",
"aiValue" : 2000,
"rmg" : {
"value" : 2000,
"rarity" : 100
@@ -724,6 +732,7 @@
"index" : 0,
"resetDuration" : 0,
"name" : "Derelict Ship",
"aiValue" : 4000,
"rmg" : {
"value" : 4000,
"rarity" : 20
@@ -822,6 +831,7 @@
"index" : 0,
"resetDuration" : 0,
"name" : "Crypt",
"aiValue" : 1500,
"rmg" : {
"value" : 1000,
"rarity" : 100
@@ -917,6 +927,7 @@
"index" : 0,
"resetDuration" : 0,
"name" : "Dragon Utopia",
"aiValue" : 11000,
"rmg" : {
"value" : 10000,
"rarity" : 100
@@ -1031,6 +1042,7 @@
"index" : 0,
"resetDuration" : 0,
"name" : "Pyramid",
"aiValue" : 8000,
"rmg" : {
"value" : 5000,
"rarity" : 20

View File

@@ -10,7 +10,7 @@
}
},
"types" : {
"prison" : { "index" : 0 }
"prison" : { "index" : 0, "aiValue" : 5000 }
}
},
@@ -25,6 +25,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -45,6 +46,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -65,6 +67,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -79,6 +82,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -100,6 +104,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 8000,
"rmg" : {
"value" : 8000,
"rarity" : 20
@@ -120,6 +125,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 10000,
"templates" : {
"normal" : { "animation" : "ava0128.def", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] }
},
@@ -151,6 +157,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 750,
"templates" :
{
"base" : { "animation" : "avxredw.def", "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VV", "VV", "VA"], "allowedTerrains":["grass", "swamp", "dirt", "sand", "lava", "rough"] },
@@ -176,6 +183,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 750,
"rmg" : {
"zoneLimit" : 1,
"value" : 750,
@@ -195,6 +203,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
}
}
@@ -246,6 +255,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 2000,
"rmg" : {
"value" : 5000,
"rarity" : 20
@@ -265,6 +275,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"zoneLimit" : 1,
"value" : 1500,
@@ -285,6 +296,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 500,
"rmg" : {
"value" : 500,
"rarity" : 100
@@ -304,6 +316,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 2000,
"rmg" : {
"value" : 2000,
"rarity" : 100
@@ -323,6 +336,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 3000,
"rmg" : {
"value" : 3000,
"rarity" : 100
@@ -344,6 +358,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 0,
"rmg" : {
}
}
@@ -360,6 +375,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 750,
"rmg" : {
}
}
@@ -376,6 +392,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 500,
"rmg" : {
}
}
@@ -392,6 +409,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 350,
"rmg" : {
"mapLimit" : 48,
"value" : 3500,
@@ -411,6 +429,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 0,
"rmg" : {
}
}
@@ -428,6 +447,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"value" : 1500,
"rarity" : 100
@@ -447,6 +467,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1000,
"rmg" : {
}
}
@@ -463,6 +484,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 0,
"rmg" : {
}
}
@@ -480,6 +502,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"mapLimit" : 32,
"value" : 100,
@@ -500,6 +523,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"templates" :
{
"green" : { "animation" : "avxdent.def", "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VV", "BA"], "allowedTerrains":["grass", "swamp", "dirt"] },
@@ -524,6 +548,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 2500,
"rmg" : {
"value" : 2500,
"rarity" : 20
@@ -542,6 +567,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"zoneLimit" : 3,
"value" : 1500,
@@ -565,6 +591,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 10000,
"rmg" : {
}
}
@@ -581,6 +608,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 250,
"rmg" : {
"zoneLimit" : 1,
"value" : 250,
@@ -819,6 +847,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 7000,
"rmg" : {
"zoneLimit" : 1,
"value" : 7000,
@@ -833,6 +862,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 0,
"rmg" : {
}
}
@@ -850,6 +880,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -870,6 +901,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,

View File

@@ -23,6 +23,7 @@
"hero" : {
"index" :34,
"handler": "hero",
"defaultAiValue" : 5000,
"base" : {
"base" : {
"visitableFrom" : [ "+++", "+-+", "+++" ],
@@ -63,14 +64,14 @@
}
},
"types" : {
"wood" : { "index" : 0, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTwood0.def" } } },
"mercury" : { "index" : 1, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTmerc0.def" } } },
"ore" : { "index" : 2, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTore0.def" } } },
"sulfur" : { "index" : 3, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTsulf0.def" } } },
"crystal" : { "index" : 4, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTcrys0.def" } } },
"gems" : { "index" : 5, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgems0.def" } } },
"gold" : { "index" : 6, "rmg" : { "value" : 750, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgold0.def" } } },
"mithril" : { "index" : 7 } // TODO: move to WoG?
"wood" : { "index" : 0, "aiValue" : 1400, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTwood0.def" } } },
"mercury" : { "index" : 1, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTmerc0.def" } } },
"ore" : { "index" : 2, "aiValue" : 1400, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTore0.def" } } },
"sulfur" : { "index" : 3, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTsulf0.def" } } },
"crystal" : { "index" : 4, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTcrys0.def" } } },
"gems" : { "index" : 5, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgems0.def" } } },
"gold" : { "index" : 6, "aiValue" : 750, "rmg" : { "value" : 750, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgold0.def" } } },
"mithril" : { "index" : 7, "aiValue" : 3500 } // TODO: move to WoG?
}
},
@@ -78,6 +79,7 @@
"town" : {
"index" :98,
"handler": "town",
"defaultAiValue" : 20000,
"base" : {
"filters" : {
// village image - fort not present
@@ -107,6 +109,7 @@
"boat" : {
"index" :8,
"handler": "boat",
"defaultAiValue" : 0,
"base" : {
"base" : {
"visitableFrom" : [ "+++", "+-+", "+++" ],
@@ -124,6 +127,7 @@
"borderGuard" : {
"index" :9,
"handler": "borderGuard",
"defaultAiValue" : 0,
"base" : {
"sounds" : {
"visit" : ["CAVEHEAD"],
@@ -144,6 +148,7 @@
"borderGate" : {
"index" :212,
"handler": "borderGate",
"defaultAiValue" : 0,
"base" : {
"sounds" : {
"visit" : ["CAVEHEAD"]
@@ -163,6 +168,7 @@
"keymasterTent" : {
"index" :10,
"handler": "keymaster",
"defaultAiValue" : 10000,
"base" : {
"sounds" : {
"visit" : ["CAVEHEAD"]
@@ -183,6 +189,7 @@
"seerHut" : {
"index" :83,
"handler": "seerHut",
"defaultAiValue" : 10000,
"base" : {
"base" : {
"visitableFrom" : [ "---", "+++", "+++" ],
@@ -209,9 +216,9 @@
}
},
"types" : {
"water" : { "index" : 0, "rmg" : { "zoneLimit" : 1, "value" : 5000, "rarity" : 20 } },
"land" : { "index" : 1, "rmg" : { "zoneLimit" : 1, "value" : 10000, "rarity" : 20 } },
"subterra" : { "index" : 2, "rmg" : { "zoneLimit" : 1, "value" : 7500, "rarity" : 20 } }
"water" : { "index" : 0, "aiValue" : 5000, "rmg" : { "zoneLimit" : 1, "value" : 5000, "rarity" : 20 } },
"land" : { "index" : 1, "aiValue": 10000, "rmg" : { "zoneLimit" : 1, "value" : 10000, "rarity" : 20 } },
"subterra" : { "index" : 2, "aiValue" : 7500, "rmg" : { "zoneLimit" : 1, "value" : 7500, "rarity" : 20 } }
}
},
@@ -227,6 +234,7 @@
"types" : {
"sawmill" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"value" : 1500
},
@@ -236,6 +244,7 @@
},
"alchemistLab" : {
"index" : 1,
"aiValue" : 3500,
"rmg" : {
"value" : 3500
},
@@ -245,6 +254,7 @@
},
"orePit" : {
"index" : 2,
"aiValue" : 1500,
"rmg" : {
"value" : 1500
},
@@ -254,6 +264,7 @@
},
"sulfurDune" : {
"index" : 3,
"aiValue" : 3500,
"rmg" : {
"value" : 3500
},
@@ -263,6 +274,7 @@
},
"crystalCavern" : {
"index" : 4,
"aiValue" : 3500,
"rmg" : {
"value" : 3500
},
@@ -272,6 +284,7 @@
},
"gemPond" : {
"index" : 5,
"aiValue" : 3500,
"rmg" : {
"value" : 3500
},
@@ -281,6 +294,7 @@
},
"goldMine" : {
"index" : 6,
"aiValue" : 7000,
"rmg" : {
"value" : 7000
},
@@ -290,6 +304,7 @@
},
"abandoned" : {
"index" : 7,
"aiValue" : 3500,
"sounds" : {
"ambient" : ["LOOPCAVE"],
"visit" : ["MYSTERY"]
@@ -300,6 +315,7 @@
"abandonedMine" : {
"index" :220,
"handler": "mine",
"defaultAiValue" : 3500,
"base" : {
"sounds" : {
"ambient" : ["LOOPCAVE"]
@@ -313,6 +329,7 @@
"garrisonHorizontal": {
"index" :33,
"handler": "garrison",
"defaultAiValue" : 0,
"base" : {
"sounds" : {
"visit" : ["MILITARY"]
@@ -336,6 +353,7 @@
"garrisonVertical" : {
"index" :219,
"handler": "garrison",
"defaultAiValue" : 0,
"base" : {
"sounds" : {
"visit" : ["MILITARY"]
@@ -454,6 +472,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 500,
"templates" : {
"normal" : {
"visitableFrom" : [ "+++", "+-+", "+++" ],

View File

@@ -12,7 +12,8 @@
},
"types" : {
"object" : {
"index" : 0//,
"index" : 0,
"aiValue" : 500//,
//"rmg" : {
// "zoneLimit" : 1,
// "value" : 500,
@@ -35,6 +36,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 500,
"rmg" : {
"value" : 500,
"rarity" : 50
@@ -54,6 +56,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"value" : 1500,
"rarity" : 80
@@ -73,6 +76,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 750,
"rmg" : {
"value" : 750,
"rarity" : 50
@@ -92,6 +96,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 500,
"rmg" : {
"value" : 500,
"rarity" : 100
@@ -110,6 +115,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 500,
"rmg" : {
"value" : 500,
"rarity" : 100
@@ -128,6 +134,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 500,
"rmg" : {
"value" : 500,
"rarity" : 50
@@ -146,6 +153,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 6000,
"rmg" : {
"value" : 6000,
"rarity" : 20
@@ -167,6 +175,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 2000,
"rmg" : {
"value" : 2000,
"rarity" : 500
@@ -186,6 +195,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 2000,
"rmg" : {
"value" : 2000,
"rarity" : 100
@@ -205,6 +215,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"value" : 1500,
"rarity" : 500
@@ -224,6 +235,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"value" : 1500,
"rarity" : 50
@@ -243,6 +255,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"value" : 1500,
"rarity" : 1000
@@ -263,6 +276,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 3000,
"rmg" : {
"value" : 3000,
"rarity" : 50
@@ -282,6 +296,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"value" : 1500,
"rarity" : 100
@@ -301,6 +316,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"value" : 1500,
"rarity" : 100
@@ -319,6 +335,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 12000,
"rmg" : {
"value" : 12000,
"rarity" : 20
@@ -338,6 +355,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"value" : 1500,
"rarity" : 100
@@ -357,6 +375,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"value" : 1500,
"rarity" : 100
@@ -375,6 +394,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 2500,
"rmg" : {
"mapLimit" : 100,
"value" : 2500,
@@ -395,6 +415,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1000,
"rmg" : {
"value" : 1000,
"rarity" : 50
@@ -414,6 +435,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1000,
"rmg" : {
"value" : 1000,
"rarity" : 50
@@ -433,6 +455,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"value" : 1500,
"rarity" : 200
@@ -454,6 +477,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"value" : 100,
"rarity" : 100
@@ -472,6 +496,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -492,6 +517,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -512,6 +538,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -532,6 +559,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -551,6 +579,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -573,6 +602,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"value" : 100,
"rarity" : 20
@@ -591,6 +621,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -611,6 +642,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 200,
"rmg" : {
"zoneLimit" : 1,
"value" : 200,
@@ -631,6 +663,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -651,6 +684,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
"zoneLimit" : 1,
"value" : 100,
@@ -670,6 +704,7 @@
"types" : {
"object" : {
"index" : 0,
"aiValue" : 500,
"rmg" : {
"zoneLimit" : 1,
"value" : 500,

View File

@@ -13,6 +13,9 @@
"name": {
"type":"string",
},
"defaultAiValue": {
"type":"number",
},
"handler": {
"type":"string",

View File

@@ -13,6 +13,9 @@
"name": {
"type":"string",
},
"aiValue": {
"type":"number",
},
"sounds": {
"type":"object",

View File

@@ -206,6 +206,11 @@ CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(co
obj->handlerName = json["handler"].String();
obj->base = json["base"];
obj->id = selectNextID(json["index"], objects, 256);
if(json["defaultAiValue"].isNull())
obj->groupDefaultAiValue = boost::none;
else
obj->groupDefaultAiValue = json["defaultAiValue"].Integer();
for (auto entry : json["types"].Struct())
{
loadObjectEntry(entry.first, entry.second, obj);
@@ -285,6 +290,11 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string type, std::s
throw std::runtime_error("Object type handler not found");
}
TObjectTypeHandler CObjectClassesHandler::getHandlerFor(CompoundMapObjectID compoundIdentifier) const
{
return getHandlerFor(compoundIdentifier.primaryID, compoundIdentifier.secondaryID);
}
std::set<si32> CObjectClassesHandler::knownObjects() const
{
std::set<si32> ret;
@@ -382,6 +392,11 @@ std::string CObjectClassesHandler::getObjectHandlerName(si32 type) const
return objects.at(type)->handlerName;
}
boost::optional<si32> CObjectClassesHandler::getObjGroupAiValue(si32 primaryID) const
{
return objects.at(primaryID)->groupDefaultAiValue;
}
AObjectTypeHandler::AObjectTypeHandler():
type(-1), subtype(-1)
{
@@ -452,6 +467,11 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optional<std::strin
for(const JsonNode & node : input["sounds"]["removal"].Vector())
sounds.removal.push_back(node.String());
if(input["aiValue"].isNull())
aiValue = boost::none;
else
aiValue = input["aiValue"].Integer();
initTypeData(input);
}
@@ -540,6 +560,11 @@ const RandomMapInfo & AObjectTypeHandler::getRMGInfo()
return rmgInfo;
}
boost::optional<si32> AObjectTypeHandler::getAiValue() const
{
return aiValue;
}
bool AObjectTypeHandler::isStaticObject()
{
return false; // most of classes are not static

View File

@@ -65,6 +65,27 @@ struct DLL_LINKAGE RandomMapInfo
}
};
struct DLL_LINKAGE CompoundMapObjectID
{
si32 primaryID;
si32 secondaryID;
CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {};
bool operator<(const CompoundMapObjectID& other) const
{
if(this->primaryID != other.primaryID)
return this->primaryID < other.primaryID;
else
return this->secondaryID < other.secondaryID;
}
bool operator==(const CompoundMapObjectID& other) const
{
return (this->primaryID == other.primaryID) && (this->secondaryID == other.secondaryID);
}
};
class DLL_LINKAGE IObjectInfo
{
public:
@@ -125,6 +146,8 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable
std::vector<ObjectTemplate> templates;
SObjectSounds sounds;
boost::optional<si32> aiValue;
protected:
void preInitObject(CGObjectInstance * obj) const;
virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const;
@@ -163,6 +186,8 @@ public:
const RandomMapInfo & getRMGInfo();
boost::optional<si32> getAiValue() const;
virtual bool isStaticObject();
virtual void afterLoadFinalization();
@@ -194,6 +219,10 @@ public:
{
h & sounds;
}
if(version >= 789)
{
h & aiValue;
}
}
};
@@ -216,6 +245,8 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
SObjectSounds sounds;
boost::optional<si32> groupDefaultAiValue;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & name;
@@ -231,6 +262,10 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
{
h & sounds;
}
if(version >= 789)
{
h & groupDefaultAiValue;
}
}
};
@@ -274,6 +309,7 @@ public:
/// returns handler for specified object (ID-based). ObjectHandler keeps ownership
TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const;
TObjectTypeHandler getHandlerFor(std::string type, std::string subtype) const;
TObjectTypeHandler getHandlerFor(CompoundMapObjectID compoundIdentifier) const;
std::string getObjectName(si32 type) const;
std::string getObjectName(si32 type, si32 subtype) const;
@@ -284,7 +320,7 @@ public:
/// Returns handler string describing the handler (for use in client)
std::string getObjectHandlerName(si32 type) const;
boost::optional<si32> getObjGroupAiValue(si32 primaryID) const; //default AI value of objects belonging to particular primaryID
template <typename Handler> void serialize(Handler &h, const int version)
{

View File

@@ -12,7 +12,7 @@
#include "../ConstTransitivePtr.h"
#include "../GameConstants.h"
const ui32 SERIALIZATION_VERSION = 788;
const ui32 SERIALIZATION_VERSION = 789;
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
const std::string SAVEGAME_MAGIC = "VCMISVG";