1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Large rewrite of adventure map objects:

- replaced CDefObjInfo with ObjectTemplate class
- ObjectTempate is a direct member of objects instead of pointer with
shared ownership across CMap, handler and game objects
- simplified handling of objects that can change appearance (e.g. towns)
- all object queries regarding object appearance/blockmaps use h3m pos
instead of relative positions
- removed need of modhandler::reload
- cleanup of some old code
This commit is contained in:
Ivan Savenko 2014-01-02 23:48:38 +00:00
parent 2b9e074d54
commit 2c4c964a45
27 changed files with 729 additions and 672 deletions

View File

@ -706,7 +706,7 @@ void VCAI::makeTurnInternal()
boost::sort (vec, isCloser);
for (auto obj : vec)
{
if(!obj || !obj->defInfo || !cb->getObj(obj->id))
if(!obj || !cb->getObj(obj->id))
{
logAi->errorStream() << "Error: there is wrong object on list for hero " << hero.first->name;
continue;

View File

@ -954,11 +954,6 @@ void endGame()
{
client->endGame();
vstd::clear_pointer(client);
delete CGI->dobjinfo.get();
const_cast<CGameInfo*>(CGI)->dobjinfo = new CDefObjInfoHandler;
const_cast<CGameInfo*>(CGI)->modh->reload(); //add info about new creatures to dobjinfo
}
void handleQuit()

View File

@ -335,12 +335,12 @@ void Graphics::loadFonts()
CDefEssential * Graphics::getDef( const CGObjectInstance * obj )
{
return advmapobjGraphics[obj->defInfo->name];
return advmapobjGraphics[obj->appearance.animationFile];
}
CDefEssential * Graphics::getDef( const CGDefInfo * info )
CDefEssential * Graphics::getDef( const ObjectTemplate & info )
{
return advmapobjGraphics[info->name];
return advmapobjGraphics[info.animationFile];
}
void Graphics::loadErmuToPicture()

View File

@ -25,7 +25,7 @@ struct SDL_Color;
struct InfoAboutHero;
struct InfoAboutTown;
class CGObjectInstance;
class CGDefInfo;
class ObjectTemplate;
enum EFonts
{
@ -60,7 +60,7 @@ public:
std::map<std::string, CDefEssential *> advmapobjGraphics;
CDefEssential * getDef(const CGObjectInstance * obj);
CDefEssential * getDef(const CGDefInfo * info);
CDefEssential * getDef(const ObjectTemplate & info);
//towns
std::map<int, std::string> ERMUtoPicture[GameConstants::F_NUMBER]; //maps building ID to it's picture's name for each town type
//for battles

View File

@ -410,14 +410,8 @@ void NewStructures::applyCl( CClient *cl )
CGTownInstance *town = GS(cl)->getTown(tid);
for(const auto & id : bid)
{
if(id == BuildingID::CAPITOL) //fort or capitol
{
town->defInfo = const_cast<CGDefInfo*>(CGI->dobjinfo->capitols.at(town->subID).get());
}
if(id == BuildingID::FORT)
{
town->defInfo = const_cast<CGDefInfo*>(CGI->dobjinfo->gobjs.at(Obj::TOWN).at(town->subID).get());
}
town->updateAppearance();
if(vstd::contains(cl->playerint,town->tempOwner))
cl->playerint[town->tempOwner]->buildChanged(town,id,1);
}
@ -427,10 +421,8 @@ void RazeStructures::applyCl (CClient *cl)
CGTownInstance *town = GS(cl)->getTown(tid);
for(const auto & id : bid)
{
if (id == BuildingID::CAPITOL) //fort or capitol
{
town->defInfo = const_cast<CGDefInfo*>(CGI->dobjinfo->gobjs.at(Obj::TOWN).at(town->subID).get());
}
town->updateAppearance();
if(vstd::contains (cl->playerint,town->tempOwner))
cl->playerint[town->tempOwner]->buildChanged (town,id,2);
}

View File

@ -1,6 +1,7 @@
#include "StdInc.h"
#include "mapHandler.h"
#include "CBitmapHandler.h"
#include "gui/SDL_Extensions.h"
#include "CGameInfo.h"
#include "../lib/CDefObjInfoHandler.h"
@ -229,26 +230,26 @@ void CMapHandler::borderAndTerrainBitmapInit()
delete bord;
}
static void processDef (const CGDefInfo* def)
static void processDef (const ObjectTemplate & objTempl)
{
if(def->id == Obj::EVENT)
if(objTempl.id == Obj::EVENT)
{
graphics->advmapobjGraphics[def->name] = nullptr;
graphics->advmapobjGraphics[objTempl.animationFile] = nullptr;
return;
}
CDefEssential * ourDef = graphics->getDef(def);
CDefEssential * ourDef = graphics->getDef(objTempl);
if(!ourDef) //if object has already set handler (eg. heroes) it should not be overwritten
{
if(def->name.size())
if(objTempl.animationFile.size())
{
graphics->advmapobjGraphics[def->name] = CDefHandler::giveDefEss(def->name);
graphics->advmapobjGraphics[objTempl.animationFile] = CDefHandler::giveDefEss(objTempl.animationFile);
}
else
{
logGlobal->warnStream() << "No def name for " << def->id << " " << def->subid;
logGlobal->warnStream() << "No def name for " << objTempl.id << " " << objTempl.subid;
return;
}
ourDef = graphics->getDef(def);
ourDef = graphics->getDef(objTempl);
}
//alpha transformation
@ -266,44 +267,40 @@ void CMapHandler::initObjectRects()
const CGObjectInstance *obj = elem;
if( !obj
|| (obj->ID==Obj::HERO && static_cast<const CGHeroInstance*>(obj)->inTownGarrison) //garrisoned hero
|| (obj->ID==Obj::BOAT && static_cast<const CGBoat*>(obj)->hero) //boat with hero (hero graphics is used)
|| !obj->defInfo )
|| (obj->ID==Obj::BOAT && static_cast<const CGBoat*>(obj)->hero)) //boat with hero (hero graphics is used)
{
continue;
}
if (!graphics->getDef(obj)) //try to load it
processDef(obj->defInfo);
processDef(obj->appearance);
if (!graphics->getDef(obj)) // stil no graphics? exit
continue;
const SDL_Surface *bitmap = graphics->getDef(obj)->ourImages[0].bitmap;
for(int fx=0; fx<bitmap->w>>5; ++fx) //bitmap->w/32
for(int fx=0; fx < obj->getWidth(); ++fx)
{
for(int fy=0; fy<bitmap->h>>5; ++fy) //bitmap->h/32
for(int fy=0; fy < obj->getHeight(); ++fy)
{
int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z);
SDL_Rect cr;
cr.w = 32;
cr.h = 32;
cr.x = fx<<5; //fx*32
cr.y = fy<<5; //fy*32
cr.x = bitmap->w - fx * 32 - 32;
cr.y = bitmap->h - fy * 32 - 32;
std::pair<const CGObjectInstance*,SDL_Rect> toAdd = std::make_pair(obj,cr);
if( (obj->pos.x + fx - bitmap->w/32+1) >= 0
&& (obj->pos.x + fx - bitmap->w/32+1) < ttiles.size() - frameW
&& (obj->pos.y + fy - bitmap->h/32+1) >= 0
&& (obj->pos.y + fy - bitmap->h/32+1) < ttiles[0].size() - frameH
if( map->isInTheMap(currTile) && // within map
cr.x + cr.w > 0 && // image has data on this tile
cr.y + cr.h > 0 &&
obj->coveringAt(currTile.x, currTile.y) // object is visible here
)
{
//TerrainTile2 & curt =
// ttiles
// [obj->pos.x + fx - bitmap->w/32]
//[obj->pos.y + fy - bitmap->h/32]
//[obj->pos.z];
ttiles[obj->pos.x + fx - bitmap->w/32+1][obj->pos.y + fy - bitmap->h/32+1][obj->pos.z].objects.push_back(toAdd);
ttiles[currTile.x][currTile.y][currTile.z].objects.push_back(toAdd);
}
}
}
}
} // for(int fy=0; fy<bitmap->h/32; ++fy)
} //for(int fx=0; fx<bitmap->w/32; ++fx)
} // for(int f=0; f<map->objects.size(); ++f)
for(int ix=0; ix<ttiles.size()-frameW; ++ix)
{
@ -319,7 +316,7 @@ void CMapHandler::initObjectRects()
void CMapHandler::initHeroDef(const CGHeroInstance * h)
{
graphics->advmapobjGraphics[h->defInfo->name] = graphics->flags1[0];
graphics->advmapobjGraphics[h->appearance.animationFile] = graphics->flags1[0];
}
void CMapHandler::init()
@ -367,9 +364,6 @@ void CMapHandler::init()
}
}
std::for_each(map->customDefs.begin(),map->customDefs.end(),processDef); //load h3m defs
logGlobal->infoStream()<<"\tUnpacking and handling defs: "<<th.getDiff();
prepareFOWDefs();
roadsRiverTerrainInit(); //road's and river's DefHandlers; and simple values initialization
borderAndTerrainBitmapInit();
@ -510,12 +504,16 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std::
{
const CGObjectInstance *obj = object.first;
if (!graphics->getDef(obj))
processDef(obj->defInfo);
processDef(obj->appearance);
if (!graphics->getDef(obj) && !obj->appearance.animationFile.empty())
{
logGlobal->errorStream() << "Failed to load image " << obj->appearance.animationFile;
}
PlayerColor color = obj->tempOwner;
//checking if object has non-empty graphic on this tile
if(obj->ID != Obj::HERO && !obj->coveringAt(top_tile.x + bx - obj->pos.x, top_tile.y + by - obj->pos.y))
if(obj->ID != Obj::HERO && !obj->coveringAt(top_tile.x + bx, top_tile.y + by))
continue;
static const int notBlittedInPuzzleMode[] = {124};
@ -640,10 +638,7 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std::
if(color < PlayerColor::PLAYER_LIMIT || color==PlayerColor::NEUTRAL)
CSDL_Ext::setPlayerColor(bitmap, color);
if( obj->hasShadowAt(top_tile.x + bx - obj->pos.x, top_tile.y + by - obj->pos.y) )
CSDL_Ext::blit8bppAlphaTo24bpp(bitmap,&pp,extSurf,&sr2);
else
CSDL_Ext::blitSurface(bitmap,&pp,extSurf,&sr2);
}
}
//objects blitted
@ -708,33 +703,41 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std::
//FoW blitted
// TODO: these should be activable by the console
#ifdef MARK_BLOCKED_POSITIONS
SDL_Rect tileRect = genRect(sr.h, sr.w, 0, 0);
if (settings["session"]["showBlock"].Bool())
{
if(map->getTile(int3(pos.x, pos.y, top_tile.z)).blocked) //temporary hiding blocked positions
{
static SDL_Surface * block = nullptr;
if (!block)
block = BitmapHandler::loadBitmap("blocked");
SDL_Rect sr;
sr.x=srx;
sr.y=sry;
sr.h=sr.w=32;
memset(rSurf->pixels, 128, rSurf->pitch * rSurf->h);
CSDL_Ext::blitSurface(rSurf,&genRect(sr.h, sr.w, 0, 0),extSurf,&sr);
CSDL_Ext::blitSurface(block, &tileRect, extSurf, &sr);
}
#endif
#ifdef MARK_VISITABLE_POSITIONS
}
if (settings["session"]["showVisit"].Bool())
{
if(map->getTile(int3(pos.x, pos.y, top_tile.z)).visitable) //temporary hiding visitable positions
{
static SDL_Surface * visit = nullptr;
if (!visit)
visit = BitmapHandler::loadBitmap("visitable");
SDL_Rect sr;
sr.x=srx;
sr.y=sry;
sr.h=sr.w=32;
memset(rSurf->pixels, 128, rSurf->pitch * rSurf->h);
CSDL_Ext::blitSurface(rSurf,&genRect(sr.h, sr.w, 0, 0),extSurf,&sr);
CSDL_Ext::blitSurface(visit, &tileRect, extSurf, &sr);
}
}
#endif
}
}
}
@ -849,7 +852,7 @@ std::pair<SDL_Surface *, bool> CMapHandler::getVisBitmap( const int3 & pos, cons
bool CMapHandler::printObject(const CGObjectInstance *obj)
{
if (!graphics->getDef(obj))
processDef(obj->defInfo);
processDef(obj->appearance);
const SDL_Surface *bitmap = graphics->getDef(obj)->ourImages[0].bitmap;
const int tilesW = bitmap->w/32;

View File

@ -657,6 +657,22 @@ void CArtHandler::afterLoadFinalization()
bonus->sid = art->id;
}
}
//Note: "10" is used here because H3 text files don't define any template for art with ID 0
ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::ARTIFACT, 10).front();
for (CArtifact * art : artifacts)
{
if (!art->advMapDef.empty())
{
base.animationFile = art->advMapDef;
base.subid = art->id;
// replace existing (if any) and add new template.
// Necessary for objects added via mods that don't have any templates in H3
VLC->dobjinfo->eraseAll(Obj::ARTIFACT, art->id);
VLC->dobjinfo->registerTemplate(base);
}
}
}
CArtifactInstance::CArtifactInstance()

View File

@ -1112,6 +1112,24 @@ void CCreatureHandler::buildBonusTreeForTiers()
b.attachTo(&allCreatures);
}
void CCreatureHandler::afterLoadFinalization()
{
ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::MONSTER, 0).front();
for (CCreature * crea : creatures)
{
if (!crea->advMapDef.empty())
{
base.animationFile = crea->advMapDef;
base.subid = crea->idNumber;
// replace existing (if any) and add new template.
// Necessary for objects added via mods that don't have any templates in H3
VLC->dobjinfo->eraseAll(Obj::MONSTER, crea->idNumber);
VLC->dobjinfo->registerTemplate(base);
}
}
}
void CCreatureHandler::deserializationFix()
{
buildBonusTreeForTiers();

View File

@ -198,6 +198,8 @@ public:
/// generates tier-specific bonus tree entries
void buildBonusTreeForTiers();
void afterLoadFinalization();
std::vector<JsonNode> loadLegacyData(size_t dataSize) override;
void loadObject(std::string scope, std::string name, const JsonNode & data) override;

View File

@ -2,9 +2,14 @@
#include "CDefObjInfoHandler.h"
#include "filesystem/Filesystem.h"
#include "../client/CGameInfo.h"
#include "filesystem/CBinaryReader.h"
//#include "../client/CGameInfo.h"
#include "../lib/VCMI_Lib.h"
#include "GameConstants.h"
#include "StringConstants.h"
#include "CGeneralTextHandler.h"
#include "CModHandler.h"
#include "JsonNode.h"
/*
* CDefObjInfoHandler.cpp, part of VCMI engine
@ -16,104 +21,11 @@
*
*/
bool CGDefInfo::isVisitable() const
static bool isVisitableFromTop(int identifier, int type)
{
for (auto & elem : visitMap)
{
if (elem)
if(type == 2 || type == 3 || type == 4 || type == 5) //creature, hero, artifact, resource
return true;
}
return false;
}
CGDefInfo::CGDefInfo()
{
visitDir = (8|16|32|64|128); //4,5,6,7,8 - any not-from-up direction
width = height = -1;
}
void CGDefInfo::fetchInfoFromMSK()
{
ResourceID resID("SPRITES/" + name, EResType::MASK);
if (CResourceHandler::get()->existsResource(resID))
{
auto msk = CResourceHandler::get()->load(resID)->readAll();
width = msk.first.get()[0];
height = msk.first.get()[1];
for(int i=0; i<6; ++i)
{
coverageMap[i] = msk.first.get()[i+2];
shadowCoverage[i] = msk.first.get()[i+8];
}
}
else
{
//maximum possible size of H3 object
//TODO: remove hardcode and move this data into modding system
width = 8;
height = 6;
}
}
CDefObjInfoHandler::CDefObjInfoHandler()
{
VLC->dobjinfo = this;
auto textFile = CResourceHandler::get()->load(ResourceID("DATA/OBJECTS.TXT"))->readAll();
std::istringstream inp(std::string((char*)textFile.first.get(), textFile.second));
int objNumber;
inp>>objNumber;
std::string mapStr;
for(int hh=0; hh<objNumber; ++hh)
{
auto nobj = new CGDefInfo();
std::string dump;
inp>>nobj->name;
std::transform(nobj->name.begin(), nobj->name.end(), nobj->name.begin(), (int(*)(int))toupper);
for(int o=0; o<6; ++o)
{
nobj->blockMap[o] = 0xff;
nobj->visitMap[o] = 0x00;
nobj->coverageMap[o] = 0x00;
nobj->shadowCoverage[o] = 0x00;
}
inp>>mapStr;
std::reverse(mapStr.begin(), mapStr.end());
for(int v=0; v<mapStr.size(); ++v)
{
if(mapStr[v]=='0')
{
nobj->blockMap[v/8] &= 255 - (128 >> (v%8));
}
}
inp>>mapStr;
std::reverse(mapStr.begin(), mapStr.end());
for(int v=0; v<mapStr.size(); ++v)
{
if(mapStr[v]=='1')
{
nobj->visitMap[v/8] |= (128 >> (v%8));
}
}
for(int yy=0; yy<2; ++yy) //first - on which types of terrain object can be placed;
inp>>dump; //second -in which terrains' menus object in the editor will be available (?)
si32 id; inp >> id; nobj->id = Obj(id);
inp>>nobj->subid;
inp>>nobj->type;
nobj->visitDir = (8|16|32|64|128); //disabled visiting from the top
if(nobj->type == 2 || nobj->type == 3 || nobj->type == 4 || nobj->type == 5) //creature, hero, artifact, resource
{
nobj->visitDir = 0xff;
}
else
{
static const Obj visitableFromTop[] =
{Obj::FLOTSAM,
Obj::SEA_CHEST,
@ -128,52 +40,368 @@ CDefObjInfoHandler::CDefObjInfoHandler()
Obj::BORDERGUARD,
Obj::BORDER_GATE,
Obj::QUEST_GUARD,
Obj::CORPSE};
for(auto & elem : visitableFromTop)
{
if(elem == nobj->id)
{
nobj->visitDir = 0xff;
break;
}
}
}
inp >> nobj->printPriority;
//coverageMap calculating
nobj->fetchInfoFromMSK();
auto dest = nobj->id.toDefObjInfo();
if (dest.find(nobj->subid) != dest.end() && dest[nobj->subid] != nullptr)
{
// there is just too many of these. Note that this data is almost unused
// exceptions are: town(village-capitol) and creation of new objects (holes, creatures, heroes, etc)
//logGlobal->warnStream() << "Warning: overwriting def info for " << dest[nobj->subid]->name << " with " << nobj->name;
dest[nobj->subid].dellNull(); // do not leak
}
nobj->id.toDefObjInfo()[nobj->subid] = nobj;
}
for (int i = 0; i < 8 ; i++)
{
static const char *holeDefs[] = {"AVLHOLD0.DEF", "AVLHLDS0.DEF", "AVLHOLG0.DEF", "AVLHLSN0.DEF",
"AVLHOLS0.DEF", "AVLHOLR0.DEF", "AVLHOLX0.DEF", "AVLHOLL0.DEF"};
if(i)
{
gobjs[Obj::HOLE][i] = new CGDefInfo(*gobjs[Obj::HOLE][0]);
}
gobjs[Obj::HOLE][i]->name = holeDefs[i];
}
Obj::CORPSE
};
if (vstd::find_pos(visitableFromTop, identifier) != -1)
return true;
return false;
}
CDefObjInfoHandler::~CDefObjInfoHandler()
ObjectTemplate::ObjectTemplate():
visitDir(8|16|32|64|128), // all but top
id(Obj::NO_OBJ),
subid(0),
printPriority(0)
{
for(auto & elem : gobjs)
for(auto j=elem.second.begin(); j!=elem.second.end(); j++)
j->second.dellNull();
}
void ObjectTemplate::readTxt(CLegacyConfigParser & parser)
{
std::string data = parser.readString();
std::vector<std::string> strings;
boost::split(strings, data, boost::is_any_of(" "));
assert(strings.size() == 9);
animationFile = strings[0];
std::string & blockStr = strings[1]; //block map, 0 = blocked, 1 = unblocked
std::string & visitStr = strings[2]; //visit map, 1 = visitable, 0 = not visitable
assert(blockStr.size() == 6*8);
assert(visitStr.size() == 6*8);
setSize(8, 6);
for (size_t i=0; i<6; i++) // 6 rows
{
for (size_t j=0; j<8; j++) // 8 columns
{
auto & tile = usedTiles[i][j];
tile |= VISIBLE; // assume that all tiles are visible
if (blockStr[i*8 + j] == '0')
tile |= BLOCKED;
if (visitStr[i*8 + j] == '1')
tile |= VISITABLE;
}
}
// strings[3] most likely - terrains on which this object can be placed in editor.
// e.g. Whirpool can be placed manually only on water while mines can be placed everywhere despite terrain-specific gfx
// so these two fields can be interpreted as "strong affinity" and "weak affinity" towards terrains
std::string & terrStr = strings[4]; // allowed terrains, 1 = object can be placed on this terrain
assert(terrStr.size() == 9); // all terrains but rock
for (size_t i=0; i<9; i++)
{
if (terrStr[8-i] == '1')
allowedTerrains.insert(ETerrainType(i));
}
id = Obj(boost::lexical_cast<int>(strings[5]));
subid = boost::lexical_cast<int>(strings[6]);
int type = boost::lexical_cast<int>(strings[7]);
printPriority = boost::lexical_cast<int>(strings[8]) * 100; // to have some space in future
if (isVisitableFromTop(id, type))
visitDir = 0xff;
else
visitDir = (8|16|32|64|128);
readMsk();
}
void ObjectTemplate::readMsk()
{
ResourceID resID("SPRITES/" + animationFile, EResType::MASK);
if (CResourceHandler::get()->existsResource(resID))
{
auto msk = CResourceHandler::get()->load(resID)->readAll();
setSize(msk.first.get()[0], msk.first.get()[1]);
}
else //maximum possible size of H3 object //TODO: remove hardcode and move this data into modding system
{
setSize(8, 6);
}
}
void ObjectTemplate::readMap(CBinaryReader & reader)
{
animationFile = reader.readString();
setSize(8, 6);
ui8 blockMask[6];
ui8 visitMask[6];
for(auto & byte : blockMask)
byte = reader.readUInt8();
for(auto & byte : visitMask)
byte = reader.readUInt8();
for (size_t i=0; i<6; i++) // 6 rows
{
for (size_t j=0; j<8; j++) // 8 columns
{
auto & tile = usedTiles[5 - i][7 - j];
tile |= VISIBLE; // assume that all tiles are visible
if (((blockMask[i] >> j) & 1 ) == 0)
tile |= BLOCKED;
if (((visitMask[i] >> j) & 1 ) != 0)
tile |= VISITABLE;
}
}
reader.readUInt16();
ui16 terrMask = reader.readUInt16();
for (size_t i=0; i<9; i++)
{
if (((terrMask >> i) & 1 ) != 0)
allowedTerrains.insert(ETerrainType(i));
}
id = Obj(reader.readUInt32());
subid = reader.readUInt32();
int type = reader.readUInt8();
printPriority = reader.readUInt8() * 100; // to have some space in future
if (isVisitableFromTop(id, type))
visitDir = 0xff;
else
visitDir = (8|16|32|64|128);
reader.skip(16);
readMsk();
if (id == Obj::EVENT)
{
setSize(1,1);
usedTiles[0][0] = VISITABLE;
}
}
void ObjectTemplate::readJson(const JsonNode &node)
{
id = Obj(node["basebase"].Float()); // temporary, should be replaced
subid = node["base"].Float();
animationFile = node["animation"].String();
const JsonVector & visitDirs = node["visitableFrom"].Vector();
if (!visitDirs.empty())
{
if (visitDirs[0].String()[0] == '+') visitDir |= 1;
if (visitDirs[0].String()[1] == '+') visitDir |= 2;
if (visitDirs[0].String()[2] == '+') visitDir |= 4;
if (visitDirs[1].String()[2] == '+') visitDir |= 8;
if (visitDirs[2].String()[2] == '+') visitDir |= 16;
if (visitDirs[2].String()[1] == '+') visitDir |= 32;
if (visitDirs[2].String()[0] == '+') visitDir |= 64;
if (visitDirs[1].String()[0] == '+') visitDir |= 128;
}
else
visitDir = 0xff;
for (auto & entry : node["allowedTerrains"].Vector())
allowedTerrains.insert(ETerrainType(vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.String())));
auto charToTile = [&](const char & ch) -> ui8
{
switch (ch)
{
case '0' : return 0;
case 'V' : return VISIBLE;
case 'B' : return VISIBLE | BLOCKED;
case 'H' : return BLOCKED;
case 'A' : return VISIBLE | BLOCKED | VISITABLE;
case 'T' : return VISIBLE | VISITABLE;
default:
logGlobal->errorStream() << "Unrecognized char " << ch << " in template mask";
return 0;
}
};
size_t maxHeight = 0, maxWidth = 0;
size_t width = node["mask"].Vector()[0].String().size();
size_t height = node["mask"].Vector().size();
setSize(width, height);
for (size_t i=0; i<height; i++)
{
const std::string & line = node["mask"].Vector()[i].String();
assert(line.size() == width);
for (size_t j=0; j < width; j++)
{
ui8 tile = charToTile(line[j]);
if (tile != 0)
{
vstd::amax(maxHeight, i);
vstd::amax(maxWidth, j);
usedTiles[i][j] = tile;
}
}
}
setSize(maxWidth, maxHeight);
printPriority = node["zIndex"].Float();
}
ui32 ObjectTemplate::getWidth() const
{
return usedTiles.empty() ? 0 : usedTiles.front().size();
}
ui32 ObjectTemplate::getHeight() const
{
return usedTiles.size();
}
void ObjectTemplate::setSize(ui32 width, ui32 height)
{
usedTiles.resize(height);
for (auto & line : usedTiles)
line.resize(width);
}
bool ObjectTemplate::isVisitable() const
{
for (auto & line : usedTiles)
for (auto & tile : line)
if (tile & VISITABLE)
return true;
return false;
}
bool ObjectTemplate::isWithin(si32 X, si32 Y) const
{
if (X < 0 || Y < 0)
return false;
if (X >= getWidth() || Y >= getHeight())
return false;
return true;
}
bool ObjectTemplate::isVisitableAt(si32 X, si32 Y) const
{
if (isWithin(X, Y))
return usedTiles[Y][X] & VISITABLE;
return false;
}
bool ObjectTemplate::isVisibleAt(si32 X, si32 Y) const
{
if (isWithin(X, Y))
return usedTiles[Y][X] & VISIBLE;
return false;
}
bool ObjectTemplate::isBlockedAt(si32 X, si32 Y) const
{
if (isWithin(X, Y))
return usedTiles[Y][X] & BLOCKED;
return false;
}
bool ObjectTemplate::isVisitableFrom(si8 X, si8 Y) const
{
// visitDir uses format
// 1 2 3
// 8 4
// 7 6 5
int dirMap[3][3] =
{
{ visitDir & 1, visitDir & 2, visitDir & 4 },
{ visitDir & 128, 1 , visitDir & 8 },
{ visitDir & 64, visitDir & 32, visitDir & 16 }
};
// map input values to range 0..2
int dx = X < 0 ? 0 : X == 0 ? 1 : 2;
int dy = Y < 0 ? 0 : Y == 0 ? 1 : 2;
return dirMap[dy][dx] != 0;
}
bool ObjectTemplate::canBePlacedAt(ETerrainType terrain) const
{
return allowedTerrains.count(terrain) != 0;
}
void CDefObjInfoHandler::readTextFile(std::string path)
{
CLegacyConfigParser parser(path);
size_t totalNumber = parser.readNumber(); // first line contains number of objects to read and nothing else
parser.endLine();
for (size_t i=0; i<totalNumber; i++)
{
ObjectTemplate templ;
templ.readTxt(parser);
parser.endLine();
objects.push_back(templ);
}
}
CDefObjInfoHandler::CDefObjInfoHandler()
{
readTextFile("Data/Objects.txt");
readTextFile("Data/Heroes.txt");
/*
const JsonNode node = JsonUtils::assembleFromFiles("config/objectTemplates.json");
std::vector<ObjectTemplate> newTemplates;
newTemplates.reserve(node.Struct().size());
// load all new templates
for (auto & entry : node.Struct())
{
ObjectTemplate templ;
templ.readJson(entry.second);
newTemplates.push_back(templ);
}
// erase old ones to avoid conflicts
for (auto & entry : newTemplates)
eraseAll(entry.id, entry.subid);
// merge new templates into storage
objects.insert(objects.end(), newTemplates.begin(), newTemplates.end());
*/
}
void CDefObjInfoHandler::eraseAll(Obj type, si32 subtype)
{
auto it = std::remove_if(objects.begin(), objects.end(), [&](const ObjectTemplate & obj)
{
return obj.id == type && obj.subid == subtype;
});
objects.erase(it, objects.end());
}
void CDefObjInfoHandler::registerTemplate(ObjectTemplate obj)
{
objects.push_back(obj);
}
std::vector<ObjectTemplate> CDefObjInfoHandler::pickCandidates(Obj type, si32 subtype) const
{
std::vector<ObjectTemplate> ret;
std::copy_if(objects.begin(), objects.end(), std::back_inserter(ret), [&](const ObjectTemplate & obj)
{
return obj.id == type && obj.subid == subtype;
});
if (ret.empty())
logGlobal->errorStream() << "Failed to find template for " << type << ":" << subtype;
assert(!ret.empty()); // Can't create object of this type/subtype
return ret;
}
std::vector<ObjectTemplate> CDefObjInfoHandler::pickCandidates(Obj type, si32 subtype, ETerrainType terrain) const
{
std::vector<ObjectTemplate> ret = pickCandidates(type, subtype);
std::vector<ObjectTemplate> filtered;
std::copy_if(ret.begin(), ret.end(), std::back_inserter(filtered), [&](const ObjectTemplate & obj)
{
return obj.canBePlacedAt(terrain);
});
// it is possible that there are no templates usable on specific terrain. In this case - return list before filtering
return filtered.empty() ? ret : filtered;
}

View File

@ -13,52 +13,96 @@
*
*/
class CDefEssential;
class DLL_LINKAGE CGDefInfo
{
public:
std::string name;
class CBinaryReader;
class CLegacyConfigParser;
class JsonNode;
ui8 visitMap[6];
ui8 blockMap[6];
ui8 coverageMap[6], shadowCoverage[6]; //to determine which tiles are covered by picture of this object
ui8 visitDir; //directions from which object can be entered, format same as for moveDir in CGHeroInstance(but 0 - 7)
Obj id;
si32 subid; //of object described by this defInfo
si32 terrainAllowed, //on which terrain it is possible to place object
terrainMenu; //in which menus in map editor object will be showed
si32 width, height; //tiles
si32 type; //(0- ground, 1- towns, 2-creatures, 3- heroes, 4-artifacts, 5- resources)
si32 printPriority;
bool isVisitable() const;
bool operator<(const CGDefInfo& por) const
class DLL_LINKAGE ObjectTemplate
{
enum EBlockMapBits
{
if(id!=por.id)
return id<por.id;
else
return subid<por.subid;
}
VISIBLE = 1,
VISITABLE = 2,
BLOCKED = 4
};
/// tiles that are covered by this object, uses EBlockMapBits enum as flags
std::vector<std::vector<ui8>> usedTiles;
/// directions from which object can be entered, format same as for moveDir in CGHeroInstance(but 0 - 7)
ui8 visitDir;
/// list of terrains on which this object can be placed
std::set<ETerrainType> allowedTerrains;
public:
/// H3 ID/subID of this object
Obj id;
si32 subid;
/// print priority, objects with higher priority will be print first, below everything else
si32 printPriority;
/// animation file that should be used to display object
std::string animationFile;
ui32 getWidth() const;
ui32 getHeight() const;
void setSize(ui32 width, ui32 height);
bool isVisitable() const;
// Checks object used tiles
// Position is relative to bottom-right corner of the object, can not be negative
bool isWithin(si32 X, si32 Y) const;
bool isVisitableAt(si32 X, si32 Y) const;
bool isVisibleAt(si32 X, si32 Y) const;
bool isBlockedAt(si32 X, si32 Y) const;
// Checks if object is visitable from certain direction. X and Y must be between -1..+1
bool isVisitableFrom(si8 X, si8 Y) const;
// Checks if object can be placed on specific terrain
bool canBePlacedAt(ETerrainType terrain) const;
ObjectTemplate();
void readTxt(CLegacyConfigParser & parser);
void readMsk();
void readMap(CBinaryReader & reader);
void readJson(const JsonNode & node);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & name & visitMap & blockMap & visitDir & id & subid &terrainAllowed
& terrainMenu & width & height & type & printPriority & coverageMap & shadowCoverage;
h & usedTiles & allowedTerrains & animationFile;
h & id & subid & printPriority & visitDir;
}
CGDefInfo();
void fetchInfoFromMSK();
};
class DLL_LINKAGE CDefObjInfoHandler
{
public:
std::map<int, std::map<int, ConstTransitivePtr<CGDefInfo> > > gobjs;
/// list of all object templates loaded from text files
/// actual object have ObjectTemplate as member "appearance"
std::vector<ObjectTemplate> objects;
std::map<TFaction, ConstTransitivePtr<CGDefInfo> > capitols;
std::map<TFaction, ConstTransitivePtr<CGDefInfo> > villages;
/// reads one of H3 text files that contain object templates description
void readTextFile(std::string path);
public:
CDefObjInfoHandler();
~CDefObjInfoHandler();
/// Erases all templates with given type/subtype
void eraseAll(Obj type, si32 subtype);
/// Add new template into the list
void registerTemplate(ObjectTemplate obj);
/// picks all possible candidates for specific pair <type, subtype>
std::vector<ObjectTemplate> pickCandidates(Obj type, si32 subtype) const;
/// picks all candidates for <type, subtype> and of possible - also filters them by terrain
std::vector<ObjectTemplate> pickCandidates(Obj type, si32 subtype, ETerrainType terrain) const;
// TODO: as above, but also filters out templates that are not applicable according to accepted test
// std::vector<ObjectTemplate> pickCandidates(Obj type, si32 subtype, ETerrainType terrain, std::function<bool(ObjectTemplate &)> accept) const;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & gobjs & capitols & villages;
h & objects;
}
};

View File

@ -343,36 +343,23 @@ static CGObjectInstance * createObject(Obj id, int subid, int3 pos, PlayerColor
switch(id)
{
case Obj::HERO:
{
auto nobj = new CGHeroInstance();
nobj->pos = pos;
nobj->tempOwner = owner;
nobj->subID = subid;
//nobj->initHero(ran);
return nobj;
}
nobj = new CGHeroInstance();
nobj->appearance = VLC->dobjinfo->pickCandidates(id, VLC->heroh->heroes[subid]->heroClass->id).front();
break;
case Obj::TOWN:
nobj = new CGTownInstance;
break;
default: //rest of objects
nobj = new CGObjectInstance;
nobj->defInfo = id.toDefObjInfo()[subid];
break;
}
nobj->ID = id;
nobj->subID = subid;
if(!nobj->defInfo)
logGlobal->warnStream() <<"No def declaration for " <<id <<" "<<subid;
nobj->pos = pos;
//nobj->state = nullptr;//new CLuaObjectScript();
nobj->tempOwner = owner;
nobj->defInfo->id = id;
nobj->defInfo->subid = subid;
if (id != Obj::HERO)
nobj->appearance = VLC->dobjinfo->pickCandidates(id, subid).front();
//assigning defhandler
if(nobj->ID==Obj::HERO || nobj->ID==Obj::TOWN)
return nobj;
nobj->defInfo = id.toDefObjInfo()[subid];
return nobj;
}
@ -641,14 +628,11 @@ void CGameState::randomizeObject(CGObjectInstance *cur)
{
if(cur->ID==Obj::TOWN) //town - set def
{
const TerrainTile &tile = map->getTile(cur->pos);
CGTownInstance *t = dynamic_cast<CGTownInstance*>(cur);
t->town = VLC->townh->factions[t->subID]->town;
if(t->hasCapitol())
t->defInfo = VLC->dobjinfo->capitols[t->subID];
else if(t->hasFort())
t->defInfo = VLC->dobjinfo->gobjs[Obj::TOWN][t->subID];
else
t->defInfo = VLC->dobjinfo->villages[t->subID];
t->appearance = VLC->dobjinfo->pickCandidates(Obj::TOWN, t->subID, tile.terType).front();
t->updateAppearance();
}
return;
}
@ -666,33 +650,33 @@ void CGameState::randomizeObject(CGObjectInstance *cur)
}
else if(ran.first==Obj::TOWN)//special code for town
{
const TerrainTile &tile = map->getTile(cur->pos);
CGTownInstance *t = dynamic_cast<CGTownInstance*>(cur);
if(!t) {logGlobal->warnStream()<<"Wrong random town at "<<cur->pos; return;}
cur->ID = ran.first;
cur->subID = ran.second;
//FIXME: copy-pasted from above
t->town = VLC->townh->factions[t->subID]->town;
if(t->hasCapitol())
t->defInfo = VLC->dobjinfo->capitols[t->subID];
else if(t->hasFort())
t->defInfo = VLC->dobjinfo->gobjs[Obj::TOWN][t->subID];
else
t->defInfo = VLC->dobjinfo->villages[t->subID];
t->appearance = VLC->dobjinfo->pickCandidates(Obj::TOWN,t->subID, tile.terType).front();
t->updateAppearance();
t->randomizeArmy(t->subID);
map->towns.push_back(t);
return;
}
else
{
if (ran.first != cur->appearance.id ||
ran.second != cur->appearance.subid)
{
const TerrainTile &tile = map->getTile(cur->pos);
cur->appearance = VLC->dobjinfo->pickCandidates(Obj(ran.first),ran.second, tile.terType).front();
}
}
//we have to replace normal random object
cur->ID = ran.first;
cur->subID = ran.second;
map->removeBlockVisTiles(cur); //recalculate blockvis tiles - picked object might have different than random placeholder
map->customDefs.push_back(cur->defInfo = ran.first.toDefObjInfo()[ran.second]);
if(!cur->defInfo)
{
logGlobal->errorStream()<<"*BIG* WARNING: Missing def declaration for "<<cur->ID<<" "<<cur->subID;
return;
}
map->removeBlockVisTiles(cur, true); //recalculate blockvis tiles - picked object might have different than random placeholder
map->addBlockVisTiles(cur);
}
@ -1189,7 +1173,6 @@ void CGameState::placeStartingHero(PlayerColor playerColor, HeroTypeID heroTypeI
townPos.x += 1;
CGHeroInstance * hero = static_cast<CGHeroInstance*>(createObject(Obj::HERO, heroTypeId.getNum(), townPos, playerColor));
hero->initHeroDefInfo();
map->getEditManager()->insertObject(hero, townPos);
}
@ -2205,14 +2188,15 @@ bool CGameState::isVisible( const CGObjectInstance *obj, boost::optional<PlayerC
if(*player == PlayerColor::NEUTRAL) //-> TODO ??? needed?
return false;
//object is visible when at least one blocked tile is visible
for(int fx=0; fx<8; ++fx)
for(int fy=0; fy < obj->getHeight(); ++fy)
{
for(int fy=0; fy<6; ++fy)
for(int fx=0; fx < obj->getWidth(); ++fx)
{
int3 pos = obj->pos + int3(fx-7,fy-5,0);
if(map->isInTheMap(pos)
&& !((obj->defInfo->blockMap[fy] >> (7 - fx)) & 1)
&& isVisible(pos, *player) )
int3 pos = obj->pos + int3(-fx, -fy, 0);
if ( map->isInTheMap(pos) &&
obj->coveringAt(pos.x, pos.y) &&
isVisible(pos, *player))
return true;
}
}
@ -2232,40 +2216,11 @@ bool CGameState::checkForVisitableDir( const int3 & src, const TerrainTile *pom,
if(!vstd::contains(pom->blockingObjects, pom->visitableObjects[b])) //this visitable object is not blocking, ignore
continue;
CGDefInfo * di = pom->visitableObjects[b]->defInfo;
if( (dst.x == src.x-1 && dst.y == src.y-1) && !(di->visitDir & (1<<4)) )
{
const CGObjectInstance * obj = pom->visitableObjects[b];
if (!obj->appearance.isVisitableFrom(src.x - dst.x, src.y - dst.y))
return false;
}
if( (dst.x == src.x && dst.y == src.y-1) && !(di->visitDir & (1<<5)) )
{
return false;
}
if( (dst.x == src.x+1 && dst.y == src.y-1) && !(di->visitDir & (1<<6)) )
{
return false;
}
if( (dst.x == src.x+1 && dst.y == src.y) && !(di->visitDir & (1<<7)) )
{
return false;
}
if( (dst.x == src.x+1 && dst.y == src.y+1) && !(di->visitDir & (1<<0)) )
{
return false;
}
if( (dst.x == src.x && dst.y == src.y+1) && !(di->visitDir & (1<<1)) )
{
return false;
}
if( (dst.x == src.x-1 && dst.y == src.y+1) && !(di->visitDir & (1<<2)) )
{
return false;
}
if( (dst.x == src.x-1 && dst.y == src.y) && !(di->visitDir & (1<<3)) )
{
return false;
}
}
return true;
}

View File

@ -223,6 +223,18 @@ void CHeroClassHandler::afterLoadFinalization()
heroClass->selectionProbability[faction->index] = static_cast<int>(sqrt(chance) + 0.5); //FIXME: replace with std::round once MVS supports it
}
}
ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::HERO, 0).front();
for (CHeroClass * hc : heroClasses)
{
base.animationFile = hc->imageMapMale;
base.subid = hc->id;
// replace existing (if any) and add new template.
// Necessary for objects added via mods that don't have any templates in H3
VLC->dobjinfo->eraseAll(Obj::HERO, hc->id);
VLC->dobjinfo->registerTemplate(base);
}
}
std::vector<bool> CHeroClassHandler::getDefaultAllowed() const

View File

@ -789,91 +789,4 @@ void CModHandler::afterLoad()
std::ofstream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::trunc);
file << modSettings;
reload();
}
void CModHandler::reload()
{
{
//recreate adventure map defs
assert(!VLC->dobjinfo->gobjs[Obj::MONSTER].empty()); //make sure that at least some def info was found
const CGDefInfo * baseInfo = VLC->dobjinfo->gobjs[Obj::MONSTER].begin()->second;
for(auto & crea : VLC->creh->creatures)
{
if (!vstd::contains(VLC->dobjinfo->gobjs[Obj::MONSTER], crea->idNumber)) // no obj info for this type
{
auto info = new CGDefInfo(*baseInfo);
info->subid = crea->idNumber;
info->name = crea->advMapDef;
VLC->dobjinfo->gobjs[Obj::MONSTER][crea->idNumber] = info;
}
}
}
{
assert(!VLC->dobjinfo->gobjs[Obj::ARTIFACT].empty());
const CGDefInfo * baseInfo = VLC->dobjinfo->gobjs[Obj::ARTIFACT].begin()->second;
for(auto & art : VLC->arth->artifacts)
{
if (!vstd::contains(VLC->dobjinfo->gobjs[Obj::ARTIFACT], art->id)) // no obj info for this type
{
auto info = new CGDefInfo(*baseInfo);
info->subid = art->id;
info->name = art->advMapDef;
VLC->dobjinfo->gobjs[Obj::ARTIFACT][art->id] = info;
}
}
}
{
assert(!VLC->dobjinfo->gobjs[Obj::TOWN].empty()); //make sure that at least some def info was found
const CGDefInfo * baseInfo = VLC->dobjinfo->gobjs[Obj::TOWN].begin()->second;
auto & townInfos = VLC->dobjinfo->gobjs[Obj::TOWN];
for(auto & faction : VLC->townh->factions)
{
TFaction index = faction->index;
CTown * town = faction->town;
if (town)
{
auto & cientInfo = town->clientInfo;
if (!vstd::contains(VLC->dobjinfo->gobjs[Obj::TOWN], index)) // no obj info for this type
{
auto info = new CGDefInfo(*baseInfo);
info->subid = index;
townInfos[index] = info;
}
townInfos[index]->name = cientInfo.advMapCastle;
VLC->dobjinfo->villages[index] = new CGDefInfo(*townInfos[index]);
VLC->dobjinfo->villages[index]->name = cientInfo.advMapVillage;
VLC->dobjinfo->capitols[index] = new CGDefInfo(*townInfos[index]);
VLC->dobjinfo->capitols[index]->name = cientInfo.advMapCapitol;
for (int i = 0; i < town->dwellings.size(); ++i)
{
const CGDefInfo * baseInfo = VLC->dobjinfo->gobjs[Obj::CREATURE_GENERATOR1][i]; //get same blockmap as first dwelling of tier i
for (auto cre : town->creatures[i]) //both unupgraded and upgraded get same dwelling
{
auto info = new CGDefInfo(*baseInfo);
info->subid = cre;
info->name = town->dwellings[i];
VLC->dobjinfo->gobjs[Obj::CREATURE_GENERATOR1][cre] = info;
VLC->objh->cregens[cre] = cre; //map of dwelling -> creature id
}
}
}
}
}
}

View File

@ -213,10 +213,6 @@ public:
void load();
void afterLoad();
/// actions that should be triggered on map restart
/// TODO: merge into appropriate handlers?
void reload();
struct DLL_LINKAGE hardcodedFeatures
{
JsonNode data;

View File

@ -33,11 +33,6 @@
using namespace boost::assign;
// It looks that we can't rely on shadowCoverage correctness (Mantis #866). This may result
// in notable performance decrease (SDL blit with custom alpha blit) not notable on my system (Ivan)
#define USE_COVERAGE_MAP 0
std::map<Obj, std::map<int, std::vector<ObjectInstanceID> > > CGTeleport::objs;
std::vector<std::pair<ObjectInstanceID, ObjectInstanceID> > CGTeleport::gates;
IGameCallback * IObjectInterface::cb = nullptr;
@ -298,7 +293,6 @@ CGObjectInstance::CGObjectInstance():
pos(-1,-1,-1),
ID(Obj::NO_OBJ),
subID(-1),
defInfo(nullptr),
tempOwner(PlayerColor::UNFLAGGABLE),
blockVisit(false)
{
@ -323,62 +317,24 @@ void CGObjectInstance::setOwner(PlayerColor ow)
}
int CGObjectInstance::getWidth() const//returns width of object graphic in tiles
{
return defInfo->width;
return appearance.getWidth();
}
int CGObjectInstance::getHeight() const //returns height of object graphic in tiles
{
return defInfo->height;
return appearance.getHeight();
}
bool CGObjectInstance::visitableAt(int x, int y) const //returns true if object is visitable at location (x, y) form left top tile of image (x, y in tiles)
{
if(defInfo==nullptr)
{
logGlobal->warnStream() << "Warning: VisitableAt for obj "<< id.getNum() <<": nullptr defInfo!";
return false;
}
if((defInfo->visitMap[y] >> (7-x) ) & 1)
{
return true;
}
return false;
return appearance.isVisitableAt(pos.x - x, pos.y - y);
}
bool CGObjectInstance::blockingAt(int x, int y) const
{
if(x<0 || y<0 || x>=getWidth() || y>=getHeight() || defInfo==nullptr)
return false;
if((defInfo->blockMap[y+6-getHeight()] >> (7-(8-getWidth()+x) )) & 1)
return false;
return true;
return appearance.isBlockedAt(pos.x - x, pos.y - y);
}
bool CGObjectInstance::coveringAt(int x, int y) const
{
//input coordinates are always negative
x = -x;
y = -y;
#if USE_COVERAGE_MAP
//NOTE: this code may be broken
if((defInfo->coverageMap[y] >> (7-(x) )) & 1
|| (defInfo->shadowCoverage[y] >> (7-(x) )) & 1)
return true;
return false;
#else
return x >= 0 && y >= 0 && x < getWidth() && y < getHeight();
#endif
}
bool CGObjectInstance::hasShadowAt( int x, int y ) const
{
#if USE_COVERAGE_MAP
//NOTE: this code may be broken
if( (defInfo->shadowCoverage[y] >> (7-(x) )) & 1 )
return true;
return false;
#else
return coveringAt(x,y);// ignore unreliable shadowCoverage map
#endif
return appearance.isVisibleAt(pos.x - x, pos.y - y);
}
std::set<int3> CGObjectInstance::getBlockedPos() const
@ -388,8 +344,8 @@ std::set<int3> CGObjectInstance::getBlockedPos() const
{
for(int h=0; h<getHeight(); ++h)
{
if(blockingAt(w, h))
ret.insert(int3(pos.x - getWidth() + w + 1, pos.y - getHeight() + h + 1, pos.z));
if (appearance.isBlockedAt(-w, -h))
ret.insert(int3(pos.x - w, pos.y - h, pos.z));
}
}
return ret;
@ -397,21 +353,20 @@ std::set<int3> CGObjectInstance::getBlockedPos() const
bool CGObjectInstance::operator<(const CGObjectInstance & cmp) const //screen printing priority comparing
{
if(defInfo->printPriority==1 && cmp.defInfo->printPriority==0)
return true;
if(cmp.defInfo->printPriority==1 && defInfo->printPriority==0)
return false;
if(this->pos.y<cmp.pos.y)
return true;
if(this->pos.y>cmp.pos.y)
return false;
if (appearance.printPriority != cmp.appearance.printPriority)
return appearance.printPriority > cmp.appearance.printPriority;
if(pos.y != cmp.pos.y)
return pos.y < cmp.pos.y;
if(cmp.ID==Obj::HERO && ID!=Obj::HERO)
return true;
if(cmp.ID!=Obj::HERO && ID==Obj::HERO)
return false;
if(!defInfo->isVisitable() && cmp.defInfo->isVisitable())
if(!isVisitable() && cmp.isVisitable())
return true;
if(!cmp.defInfo->isVisitable() && defInfo->isVisitable())
if(!cmp.isVisitable() && isVisitable())
return false;
if(this->pos.x<cmp.pos.x)
return true;
@ -490,13 +445,13 @@ void CGObjectInstance::hideTiles(PlayerColor ourplayer, int radius) const
}
int3 CGObjectInstance::getVisitableOffset() const
{
for(int y = 0; y < 6; y++)
for (int x = 0; x < 8; x++)
if((defInfo->visitMap[5-y] >> x) & 1)
for(int y = 0; y < appearance.getHeight(); y++)
for (int x = 0; x < appearance.getWidth(); x++)
if (appearance.isVisitableAt(x, y))
return int3(x,y,0);
logGlobal->warnStream() << "Warning: getVisitableOffset called on non-visitable obj!";
return int3(-1,-1,-1);
return int3(0,0,0);
}
void CGObjectInstance::getNameVis( std::string &hname ) const
@ -556,14 +511,7 @@ int3 CGObjectInstance::visitablePos() const
bool CGObjectInstance::isVisitable() const
{
for(int g=0; g<ARRAY_COUNT(defInfo->visitMap); ++g)
{
if(defInfo->visitMap[g] != 0)
{
return true;
}
}
return false;
return appearance.isVisitable();
}
bool CGObjectInstance::passableFor(PlayerColor color) const
@ -766,11 +714,12 @@ void CGHeroInstance::initHero(HeroTypeID SUBID)
void CGHeroInstance::initHero()
{
assert(validTypes(true));
if(ID == Obj::HERO)
initHeroDefInfo();
if(!type)
type = VLC->heroh->heroes[subID];
if (ID == Obj::HERO)
appearance = VLC->dobjinfo->pickCandidates(Obj::HERO, type->heroClass->id).front();
if(!vstd::contains(spells, SpellID::PRESET)) //hero starts with a spell
{
for(auto spellID : type->spells)
@ -888,27 +837,7 @@ void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= nullptr*/)
dst->setCreature(SlotID(stackNo-warMachinesGiven), stack.creature, count);
}
}
void CGHeroInstance::initHeroDefInfo()
{
if(!defInfo || defInfo->id != Obj::HERO)
{
defInfo = new CGDefInfo();
defInfo->id = Obj::HERO;
defInfo->subid = subID;
defInfo->printPriority = 0;
defInfo->visitDir = 0xff;
}
for(int i=0;i<6;i++)
{
defInfo->blockMap[i] = 255;
defInfo->visitMap[i] = 0;
defInfo->coverageMap[i] = 0;
defInfo->shadowCoverage[i] = 0;
}
defInfo->blockMap[5] = 253;
defInfo->visitMap[5] = 2;
defInfo->coverageMap[4] = defInfo->coverageMap[5] = 224;
}
CGHeroInstance::~CGHeroInstance()
{
commander.dellNull();
@ -1780,8 +1709,8 @@ void CGDwelling::initObj()
if (subID >= VLC->generaltexth->creGens.size()) //very messy workaround
{
auto & dwellingNames = VLC->townh->factions[crs->faction]->town->dwellingNames;
assert (!dwellingNames.empty());
hoverName = dwellingNames[VLC->creh->creatures[subID]->level - 1];
assert (dwellingNames.size() > crs->level - 1);
hoverName = dwellingNames[crs->level - 1];
}
else
hoverName = VLC->generaltexth->creGens[subID];
@ -2537,6 +2466,16 @@ std::vector<int> CGTownInstance::availableItemsIds(EMarketMode::EMarketMode mode
return IMarket::availableItemsIds(mode);
}
void CGTownInstance::updateAppearance()
{
if (!hasFort())
appearance.animationFile = town->clientInfo.advMapVillage;
else if(hasCapitol())
appearance.animationFile = town->clientInfo.advMapCapitol;
else
appearance.animationFile = town->clientInfo.advMapCastle;
}
std::string CGTownInstance::nodeName() const
{
return "Town (" + (town ? town->faction->name : "unknown") + ") of " + name;
@ -5378,7 +5317,7 @@ std::vector<int3> CGMagicSpring::getVisitableOffsets() const
for(int y = 0; y < 6; y++)
for (int x = 0; x < 8; x++) //starting from left
if((defInfo->visitMap[5-y] >> x) & 1)
if (appearance.isVisitableAt(x, y))
visitableTiles.push_back (int3(x, y , 0));
return visitableTiles;

View File

@ -2,6 +2,7 @@
#include "../lib/CCreatureSet.h"
#include "../lib/CTownHandler.h"
#include "../lib/CDefObjInfoHandler.h"
#include "CArtHandler.h"
#include "../lib/ConstTransitivePtr.h"
#include "int3.h"
@ -36,7 +37,6 @@ class CSpell;
class CGTownInstance;
class CGTownBuilding;
class CArtifact;
class CGDefInfo;
class CSpecObjInfo;
class CCastleEvent;
struct TerrainTile;
@ -180,7 +180,7 @@ public:
Obj ID;
si32 subID; //normal subID (this one from OH3 maps ;])
ObjectInstanceID id;//number of object in map's vector
CGDefInfo * defInfo;
ObjectTemplate appearance;
PlayerColor tempOwner;
bool blockVisit; //if non-zero then blocks the tile but is visitable from neighbouring tile
@ -194,12 +194,11 @@ public:
void setOwner(PlayerColor ow);
int getWidth() const; //returns width of object graphic in tiles
int getHeight() const; //returns height of object graphic in tiles
virtual bool visitableAt(int x, int y) const; //returns true if object is visitable at location (x, y) form left top tile of image (x, y in tiles)
virtual bool visitableAt(int x, int y) const; //returns true if object is visitable at location (x, y) (h3m pos)
virtual int3 getVisitableOffset() const; //returns (x,y,0) offset to first visitable tile from bottom right obj tile (0,0,0) (h3m pos)
int3 visitablePos() const;
bool blockingAt(int x, int y) const; //returns true if object is blocking location (x, y) form left top tile of image (x, y in tiles)
bool coveringAt(int x, int y) const; //returns true if object covers with picture location (x, y) form left top tile of maximal possible image (8 x 6 tiles) (x, y in tiles)
bool hasShadowAt(int x, int y) const;//returns true if object covers with shadow location (x, y) form left top tile of maximal possible image (8 x 6 tiles) (x, y in tiles)
bool blockingAt(int x, int y) const; //returns true if object is blocking location (x, y) (h3m pos)
bool coveringAt(int x, int y) const; //returns true if object covers with picture location (x, y) (h3m pos)
std::set<int3> getBlockedPos() const; //returns set of positions blocked by this object
bool isVisitable() const; //returns true if object is visitable
bool operator<(const CGObjectInstance & cmp) const; //screen printing priority comparing
@ -220,7 +219,7 @@ public:
template <typename Handler> void serialize(Handler &h, const int version)
{
h & hoverName & pos & ID & subID & id & tempOwner & blockVisit & defInfo;
h & hoverName & pos & ID & subID & id & tempOwner & blockVisit & appearance;
//definfo is handled by map serializer
}
protected:
@ -446,7 +445,6 @@ public:
void initExp();
void initArmy(IArmyDescriptor *dst = nullptr);
//void giveArtifact (ui32 aid);
void initHeroDefInfo();
void pushPrimSkill(PrimarySkill::PrimarySkill which, int val);
ui8 maxlevelsToMagicSchool() const;
ui8 maxlevelsToWisdom() const;
@ -688,6 +686,7 @@ public:
bool allowsTrade(EMarketMode::EMarketMode mode) const;
std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const;
void updateAppearance();
//////////////////////////////////////////////////////////////////////////

View File

@ -11,6 +11,8 @@
#include "CArtHandler.h"
#include "CSpellHandler.h"
#include "filesystem/Filesystem.h"
#include "CDefObjInfoHandler.h"
#include "CObjectHandler.h"
/*
* CTownHandler.cpp, part of VCMI engine
@ -709,6 +711,38 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
void CTownHandler::afterLoadFinalization()
{
initializeRequirements();
ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::TOWN, 0).front();
for (CFaction * fact : factions)
{
if (fact->town)
{
base.animationFile = fact->town->clientInfo.advMapCastle;
base.subid = fact->index;
// replace existing (if any) and add new template.
// Necessary for objects added via mods that don't have any templates in H3
VLC->dobjinfo->eraseAll(Obj::TOWN, fact->index);
VLC->dobjinfo->registerTemplate(base);
assert(fact->town->dwellings.size() == fact->town->dwellingNames.size());
for (size_t i=0; i<fact->town->dwellings.size(); i++)
{
ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::CREATURE_GENERATOR1, 0).front();
//both unupgraded and upgraded get same dwelling
for (auto cre : fact->town->creatures[i])
{
base.subid = 80 + cre;
base.animationFile = fact->town->dwellings[i];
if (VLC->objh->cregens.count(cre) == 0)
{
VLC->dobjinfo->registerTemplate(base);
VLC->objh->cregens[80 + cre] = cre; //map of dwelling -> creature id
}
}
}
}
}
}
void CTownHandler::initializeRequirements()

View File

@ -75,12 +75,6 @@ ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID)
ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType)
std::map<int, ConstTransitivePtr<CGDefInfo> > & Obj::toDefObjInfo() const
{
return VLC->dobjinfo->gobjs[*this];
}
CArtifact * ArtifactID::toArtifact() const
{
return VLC->arth->artifacts[*this];

View File

@ -55,7 +55,6 @@ namespace GameConstants
class CArtifact;
class CArtifactInstance;
class CGDefInfo;
class CCreature;
class CSpell;
class CGameInfoCallback;
@ -598,8 +597,6 @@ public:
ID_LIKE_CLASS_COMMON(Obj, EObj)
std::map<int, ConstTransitivePtr<CGDefInfo> > & toDefObjInfo() const;
EObj num;
};

View File

@ -311,10 +311,7 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs )
CGObjectInstance *obj = gs->getObjInstance(id);
logGlobal->debugStream() << "removing object id=" << id << "; address=" << (intptr_t)obj << "; name=" << obj->getHoverText();
//unblock tiles
if(obj->defInfo)
{
gs->map->removeBlockVisTiles(obj);
}
if(obj->ID==Obj::HERO)
{
@ -534,7 +531,6 @@ DLL_LINKAGE void HeroRecruited::applyGs( CGameState *gs )
else
gs->map->objects[h->id.getNum()] = h;
h->initHeroDefInfo();
gs->map->heroesOnMap.push_back(h);
p->heroes.push_back(h);
h->attachTo(p);
@ -558,7 +554,6 @@ DLL_LINKAGE void GiveHero::applyGs( CGameState *gs )
gs->map->removeBlockVisTiles(h,true);
h->setOwner(player);
h->movement = h->maxMovePoints(true);
h->initHeroDefInfo();
gs->map->heroesOnMap.push_back(h);
gs->getPlayer(h->getOwner())->heroes.push_back(h);
gs->map->addBlockVisTiles(h);
@ -594,27 +589,14 @@ DLL_LINKAGE void NewObject::applyGs( CGameState *gs )
o->ID = ID;
o->subID = subID;
o->pos = pos;
o->defInfo = VLC->dobjinfo->gobjs[ID][subID];
const TerrainTile &t = gs->map->getTile(pos);
o->appearance = VLC->dobjinfo->pickCandidates(o->ID, o->subID, t.terType).front();
id = o->id = ObjectInstanceID(gs->map->objects.size());
o->hoverName = VLC->generaltexth->names[ID];
switch(ID)
{
case Obj::MONSTER:
o->defInfo = VLC->dobjinfo->gobjs[ID][subID];
assert(o->defInfo);
break;
case Obj::HOLE:
const TerrainTile &t = gs->map->getTile(pos);
o->defInfo = VLC->dobjinfo->gobjs[ID][t.terType];
assert(o->defInfo);
break;
}
gs->map->objects.push_back(o);
gs->map->addBlockVisTiles(o);
o->initObj();
assert(o->defInfo);
logGlobal->debugStream() << "added object id=" << id << "; address=" << (intptr_t)o << "; name=" << o->getHoverText();
}

View File

@ -215,22 +215,22 @@ CMap::~CMap()
void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
{
for(int fx=0; fx<8; ++fx)
for(int fx=0; fx<obj->getWidth(); ++fx)
{
for(int fy=0; fy<6; ++fy)
for(int fy=0; fy<obj->getHeight(); ++fy)
{
int xVal = obj->pos.x + fx - 7;
int yVal = obj->pos.y + fy - 5;
int xVal = obj->pos.x - fx;
int yVal = obj->pos.y - fy;
int zVal = obj->pos.z;
if(xVal>=0 && xVal<width && yVal>=0 && yVal<height)
{
TerrainTile & curt = terrain[xVal][yVal][zVal];
if(total || ((obj->defInfo->visitMap[fy] >> (7 - fx)) & 1))
if(total || obj->visitableAt(xVal, yVal))
{
curt.visitableObjects -= obj;
curt.visitable = curt.visitableObjects.size();
}
if(total || !((obj->defInfo->blockMap[fy] >> (7 - fx)) & 1))
if(total || obj->blockingAt(xVal, yVal))
{
curt.blockingObjects -= obj;
curt.blocked = curt.blockingObjects.size();
@ -242,22 +242,22 @@ void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
void CMap::addBlockVisTiles(CGObjectInstance * obj)
{
for(int fx=0; fx<8; ++fx)
for(int fx=0; fx<obj->getWidth(); ++fx)
{
for(int fy=0; fy<6; ++fy)
for(int fy=0; fy<obj->getHeight(); ++fy)
{
int xVal = obj->pos.x + fx - 7;
int yVal = obj->pos.y + fy - 5;
int xVal = obj->pos.x - fx;
int yVal = obj->pos.y - fy;
int zVal = obj->pos.z;
if(xVal>=0 && xVal<width && yVal>=0 && yVal<height)
{
TerrainTile & curt = terrain[xVal][yVal][zVal];
if(((obj->defInfo->visitMap[fy] >> (7 - fx)) & 1))
if( obj->visitableAt(xVal, yVal))
{
curt.visitableObjects.push_back(obj);
curt.visitable = true;
}
if(!((obj->defInfo->blockMap[fy] >> (7 - fx)) & 1))
if( obj->blockingAt(xVal, yVal))
{
curt.blockingObjects.push_back(obj);
curt.blocked = true;

View File

@ -19,7 +19,6 @@
#include "../LogicalExpression.h"
class CArtifactInstance;
class CGDefInfo;
class CGObjectInstance;
class CGHeroInstance;
class CCommanderInstance;
@ -408,7 +407,6 @@ public:
std::vector<Rumor> rumors;
std::vector<DisposedHero> disposedHeroes;
std::vector<ConstTransitivePtr<CGHeroInstance> > predefinedHeroes;
std::vector<ConstTransitivePtr<CGDefInfo> > customDefs;
std::vector<bool> allowedSpell;
std::vector<bool> allowedArtifact;
std::vector<bool> allowedAbilities;
@ -484,7 +482,7 @@ public:
}
}
h & customDefs & objects;
h & objects;
h & heroesOnMap & towns & artInstances;
// static members

View File

@ -124,7 +124,6 @@ void CMapLoaderH3M::init()
// Calculate blocked / visitable positions
for(auto & elem : map->objects)
{
if(!elem->defInfo) continue;
map->addBlockVisTiles(elem);
}
times.push_back(MapLoadingTime("blocked/visitable tiles", sw.getDiff()));
@ -945,78 +944,14 @@ void CMapLoaderH3M::readDefInfo()
{
int defAmount = reader.readUInt32();
map->customDefs.reserve(defAmount + 8);
templates.reserve(defAmount);
// Read custom defs
for(int idd = 0; idd < defAmount; ++idd)
{
auto defInfo = new CGDefInfo();
defInfo->name = reader.readString();
std::transform(defInfo->name.begin(),defInfo->name.end(),defInfo->name.begin(),(int(*)(int))toupper);
ui8 bytes[12];
for(auto & byte : bytes)
{
byte = reader.readUInt8();
}
defInfo->terrainAllowed = reader.readUInt16();
defInfo->terrainMenu = reader.readUInt16();
defInfo->id = Obj(reader.readUInt32());
defInfo->subid = reader.readUInt32();
defInfo->type = reader.readUInt8();
defInfo->printPriority = reader.readUInt8();
for(int zi = 0; zi < 6; ++zi)
{
defInfo->blockMap[zi] = reverse(bytes[zi]);
}
for(int zi = 0; zi < 6; ++zi)
{
defInfo->visitMap[zi] = reverse(bytes[6 + zi]);
}
reader.skip(16);
if(defInfo->id != Obj::HERO && defInfo->id != Obj::RANDOM_HERO)
{
CGDefInfo * h = VLC->dobjinfo->gobjs[defInfo->id][defInfo->subid];
if(!h)
{
//remove fake entry
VLC->dobjinfo->gobjs[defInfo->id].erase(defInfo->subid);
if(VLC->dobjinfo->gobjs[defInfo->id].size())
{
VLC->dobjinfo->gobjs.erase(defInfo->id);
}
logGlobal->warnStream() << "\t\tWarning: no defobjinfo entry for object ID="
<< defInfo->id << " subID=" << defInfo->subid;
}
else
{
defInfo->visitDir = VLC->dobjinfo->gobjs[defInfo->id][defInfo->subid]->visitDir;
}
}
else
{
defInfo->visitDir = 0xff;
}
if(defInfo->id == Obj::EVENT)
{
std::memset(defInfo->blockMap, 255, 6);
}
//calculating coverageMap
defInfo->fetchInfoFromMSK();
map->customDefs.push_back(defInfo);
}
//add holes - they always can appear
for(int i = 0; i < 8 ; ++i)
{
map->customDefs.push_back(VLC->dobjinfo->gobjs[Obj::HOLE][i]);
ObjectTemplate tmpl;
tmpl.readMap(reader);
templates.push_back(tmpl);
}
}
@ -1033,10 +968,10 @@ void CMapLoaderH3M::readObjects()
int defnum = reader.readUInt32();
ObjectInstanceID idToBeGiven = ObjectInstanceID(map->objects.size());
CGDefInfo * defInfo = map->customDefs.at(defnum);
ObjectTemplate & objTempl = templates.at(defnum);
reader.skip(5);
switch(defInfo->id)
switch(objTempl.id)
{
case Obj::EVENT:
{
@ -1142,7 +1077,7 @@ void CMapLoaderH3M::readObjects()
break;
}
case Obj::TREASURE_CHEST:
if(defInfo->subid == 0)
if(objTempl.subid == 0)
{
nobj = new CGPickable();
}
@ -1315,15 +1250,15 @@ void CMapLoaderH3M::readObjects()
readMessageAndGuards(art->message, art);
if(defInfo->id == Obj::SPELL_SCROLL)
if(objTempl.id == Obj::SPELL_SCROLL)
{
spellID = reader.readUInt32();
artID = 1;
}
else if(defInfo->id == Obj::ARTIFACT)
else if(objTempl.id == Obj::ARTIFACT)
{
//specific artifact
artID = defInfo->subid;
artID = objTempl.subid;
}
art->storedArtifact = createArtifact(artID, spellID);
@ -1338,7 +1273,7 @@ void CMapLoaderH3M::readObjects()
readMessageAndGuards(res->message, res);
res->amount = reader.readUInt32();
if(defInfo->subid == Res::GOLD)
if(objTempl.subid == Res::GOLD)
{
// Gold is multiplied by 100.
res->amount *= 100;
@ -1349,7 +1284,7 @@ void CMapLoaderH3M::readObjects()
case Obj::RANDOM_TOWN:
case Obj::TOWN:
{
nobj = readTown(defInfo->subid);
nobj = readTown(objTempl.subid);
break;
}
case Obj::MINE:
@ -1455,7 +1390,7 @@ void CMapLoaderH3M::readObjects()
{
nobj = new CGDwelling();
CSpecObjInfo * spec = nullptr;
switch(defInfo->id)
switch(objTempl.id)
{
break; case Obj::RANDOM_DWELLING: spec = new CCreGenLeveledCastleInfo();
break; case Obj::RANDOM_DWELLING_LVL: spec = new CCreGenAsCastleInfo();
@ -1607,7 +1542,7 @@ void CMapLoaderH3M::readObjects()
}
case Obj::PYRAMID: //Pyramid of WoG object
{
if(defInfo->subid == 0)
if(objTempl.subid == 0)
{
nobj = new CGPyramid();
}
@ -1671,13 +1606,13 @@ void CMapLoaderH3M::readObjects()
}
nobj->pos = objPos;
nobj->ID = defInfo->id;
nobj->ID = objTempl.id;
nobj->id = idToBeGiven;
if(nobj->ID != Obj::HERO && nobj->ID != Obj::HERO_PLACEHOLDER && nobj->ID != Obj::PRISON)
{
nobj->subID = defInfo->subid;
nobj->subID = objTempl.subid;
}
nobj->defInfo = defInfo;
nobj->appearance = objTempl;
assert(idToBeGiven == ObjectInstanceID(map->objects.size()));
map->objects.push_back(nobj);
if(nobj->ID == Obj::TOWN)

View File

@ -14,6 +14,7 @@
#include "CMapService.h"
#include "../GameConstants.h"
#include "../ResourceSet.h"
#include "../CDefObjInfoHandler.h"
#include "../int3.h"
@ -250,6 +251,10 @@ private:
return p;
}
/** List of templates loaded from the map, used on later stage to create
* objects but not needed for fully functional CMap */
std::vector<ObjectTemplate> templates;
/** ptr to the map object which gets filled by data from the buffer */
CMap * map;

View File

@ -161,7 +161,7 @@ void CMapGenerator::genTowns()
if(townId == CMapGenOptions::CPlayerSettings::RANDOM_TOWN) townId = gen.getInteger(0, 8); // Default towns
town->subID = townId;
town->tempOwner = owner;
town->defInfo = VLC->dobjinfo->gobjs[town->ID][town->subID];
town->appearance = VLC->dobjinfo->pickCandidates(town->ID, town->subID, map->getTile(townPos[side]).terType).front();
town->builtBuildings.insert(BuildingID::FORT);
town->builtBuildings.insert(BuildingID::DEFAULT);
editManager->insertObject(town, int3(townPos[side].x, townPos[side].y + (i / 2) * 5, 0));

View File

@ -5221,7 +5221,7 @@ bool CGameHandler::dig( const CGHeroInstance *h )
NewObject no;
no.ID = Obj::HOLE;
no.pos = h->getPosition();
no.subID = getTile(no.pos)->terType;
no.subID = 0;
sendAndApply(&no);
//take MPs