diff --git a/mapeditor/Animation.cpp b/mapeditor/Animation.cpp new file mode 100644 index 000000000..4e0e2d7e4 --- /dev/null +++ b/mapeditor/Animation.cpp @@ -0,0 +1,780 @@ +/* + * CAnimation.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 "Animation.h" + +#include "BitmapHandler.h" + +#include "../lib/filesystem/Filesystem.h" +#include "../lib/filesystem/ISimpleResourceLoader.h" +#include "../lib/JsonNode.h" +#include "../lib/CRandomGenerator.h" + + +typedef std::map> source_map; +//typedef std::map image_map; +//typedef std::map group_map; + +/// Class for def loading +/// After loading will store general info (palette and frame offsets) and pointer to file itself +class DefFile +{ +private: + + struct SSpriteDef + { + ui32 size; + ui32 format; /// format in which pixel data is stored + ui32 fullWidth; /// full width and height of frame, including borders + ui32 fullHeight; + ui32 width; /// width and height of pixel data, borders excluded + ui32 height; + si32 leftMargin; + si32 topMargin; + } PACKED_STRUCT; + //offset[group][frame] - offset of frame data in file + std::map > offset; + + std::unique_ptr data; + std::unique_ptr palette; + +public: + DefFile(std::string Name); + ~DefFile(); + + QImage loadFrame(size_t frame, size_t group) const; + + const std::map getEntries() const; +}; + +class ImageLoader +{ + QImage * image; + ui8 * lineStart; + ui8 * position; +public: + //load size raw pixels from data + inline void Load(size_t size, const ui8 * data); + //set size pixels to color + inline void Load(size_t size, ui8 color=0); + inline void EndLine(); + //init image with these sizes and palette + inline void init(QPoint SpriteSize, QPoint Margins, QPoint FullSize, QRgb * pal); + + ImageLoader(QImage * Img); + ~ImageLoader(); +}; + +// Extremely simple file cache. TODO: smarter, more general solution +class FileCache +{ + static const int cacheSize = 50; //Max number of cached files + struct FileData + { + ResourceID name; + size_t size; + std::unique_ptr data; + + std::unique_ptr getCopy() + { + auto ret = std::unique_ptr(new ui8[size]); + std::copy(data.get(), data.get() + size, ret.get()); + return ret; + } + FileData(ResourceID name_, size_t size_, std::unique_ptr data_): + name{std::move(name_)}, + size{size_}, + data{std::move(data_)} + {} + }; + + std::deque cache; +public: + std::unique_ptr getCachedFile(ResourceID rid) + { + for(auto & file : cache) + { + if (file.name == rid) + return file.getCopy(); + } + // Still here? Cache miss + if (cache.size() > cacheSize) + cache.pop_front(); + + auto data = CResourceHandler::get()->load(rid)->readAll(); + + cache.emplace_back(std::move(rid), data.second, std::move(data.first)); + + return cache.back().getCopy(); + } +}; + +enum class DefType : uint32_t +{ + SPELL = 0x40, + SPRITE = 0x41, + CREATURE = 0x42, + MAP = 0x43, + MAP_HERO = 0x44, + TERRAIN = 0x45, + CURSOR = 0x46, + INTERFACE = 0x47, + SPRITE_FRAME = 0x48, + BATTLE_HERO = 0x49 +}; + +static FileCache animationCache; + +/************************************************************************* + * DefFile, class used for def loading * + *************************************************************************/ + +DefFile::DefFile(std::string Name): + data(nullptr), + palette(nullptr) +{ + + #if 0 + static QRgba H3_ORIG_PALETTE[8] = + { + { 0, 255, 255, SDL_ALPHA_OPAQUE}, + {255, 150, 255, SDL_ALPHA_OPAQUE}, + {255, 100, 255, SDL_ALPHA_OPAQUE}, + {255, 50, 255, SDL_ALPHA_OPAQUE}, + {255, 0, 255, SDL_ALPHA_OPAQUE}, + {255, 255, 0, SDL_ALPHA_OPAQUE}, + {180, 0, 255, SDL_ALPHA_OPAQUE}, + { 0, 255, 0, SDL_ALPHA_OPAQUE} + }; + #endif // 0 + + //First 8 colors in def palette used for transparency + static QRgb H3Palette[8] = + { + qRgba(0, 0, 0, 0), // 100% - transparency + qRgba(0, 0, 0, 32), // 75% - shadow border, + qRgba(0, 0, 0, 64), // TODO: find exact value + qRgba(0, 0, 0, 128), // TODO: for transparency + qRgba(0, 0, 0, 128), // 50% - shadow body + qRgba(0, 0, 0, 0), // 100% - selection highlight + qRgba(0, 0, 0, 128), // 50% - shadow body below selection + qRgba(0, 0, 0, 64) // 75% - shadow border below selection + }; + data = animationCache.getCachedFile(ResourceID(std::string("SPRITES/") + Name, EResType::ANIMATION)); + + palette = std::unique_ptr(new QRgb[256]); + int it = 0; + + ui32 type = read_le_u32(data.get() + it); + it+=4; + //int width = read_le_u32(data + it); it+=4;//not used + //int height = read_le_u32(data + it); it+=4; + it+=8; + ui32 totalBlocks = read_le_u32(data.get() + it); + it+=4; + + for (ui32 i= 0; i<256; i++) + { + ui8 c[3]; + c[0] = data[it++]; + c[1] = data[it++]; + c[2] = data[it++]; + palette[i] = qRgba(c[0], c[1], c[2], 255); + } + + switch(static_cast(type)) + { + case DefType::SPELL: + palette[0] = H3Palette[0]; + break; + case DefType::SPRITE: + case DefType::SPRITE_FRAME: + for(ui32 i= 0; i<8; i++) + palette[i] = H3Palette[i]; + break; + case DefType::CREATURE: + palette[0] = H3Palette[0]; + palette[1] = H3Palette[1]; + palette[4] = H3Palette[4]; + palette[5] = H3Palette[5]; + palette[6] = H3Palette[6]; + palette[7] = H3Palette[7]; + break; + case DefType::MAP: + case DefType::MAP_HERO: + palette[0] = H3Palette[0]; + palette[1] = H3Palette[1]; + palette[4] = H3Palette[4]; + //5 = owner flag, handled separately + break; + case DefType::TERRAIN: + palette[0] = H3Palette[0]; + palette[1] = H3Palette[1]; + palette[2] = H3Palette[2]; + palette[3] = H3Palette[3]; + palette[4] = H3Palette[4]; + break; + case DefType::CURSOR: + palette[0] = H3Palette[0]; + break; + case DefType::INTERFACE: + palette[0] = H3Palette[0]; + palette[1] = H3Palette[1]; + palette[4] = H3Palette[4]; + //player colors handled separately + //TODO: disallow colorizing other def types + break; + case DefType::BATTLE_HERO: + palette[0] = H3Palette[0]; + palette[1] = H3Palette[1]; + palette[4] = H3Palette[4]; + break; + default: + logAnim->error("Unknown def type %d in %s", type, Name); + break; + } + + + for (ui32 i=0; i >::const_iterator it; + it = offset.find(group); + assert (it != offset.end()); + + const ui8 * FDef = data.get()+it->second[frame]; + + const SSpriteDef sd = * reinterpret_cast(FDef); + SSpriteDef sprite; + + sprite.format = read_le_u32(&sd.format); + sprite.fullWidth = read_le_u32(&sd.fullWidth); + sprite.fullHeight = read_le_u32(&sd.fullHeight); + sprite.width = read_le_u32(&sd.width); + sprite.height = read_le_u32(&sd.height); + sprite.leftMargin = read_le_u32(&sd.leftMargin); + sprite.topMargin = read_le_u32(&sd.topMargin); + + ui32 currentOffset = sizeof(SSpriteDef); + + //special case for some "old" format defs (SGTWMTA.DEF and SGTWMTB.DEF) + + if(sprite.format == 1 && sprite.width > sprite.fullWidth && sprite.height > sprite.fullHeight) + { + sprite.leftMargin = 0; + sprite.topMargin = 0; + sprite.width = sprite.fullWidth; + sprite.height = sprite.fullHeight; + + currentOffset -= 16; + } + + const ui32 BaseOffset = currentOffset; + + + QImage img(sprite.fullWidth, sprite.height, QImage::Format_MonoLSB); + ImageLoader loader(&img); + //loader.init(QPoint(sprite.width, sprite.height), + // QPoint(sprite.leftMargin, sprite.topMargin), + // QPoint(sprite.fullWidth, sprite.fullHeight), palette.get()); + + switch(sprite.format) + { + case 0: + { + //pixel data is not compressed, copy data to surface + for(ui32 i=0; i(FDef+currentOffset); + currentOffset += sizeof(ui32) * sprite.height; + + for(ui32 i=0; ierror("Error: unsupported format of def file: %d", sprite.format); + break; + } + + + //TODO: SET PALETTE + return img; +} + +DefFile::~DefFile() = default; + +const std::map DefFile::getEntries() const +{ + std::map ret; + + for (auto & elem : offset) + ret[elem.first] = elem.second.size(); + return ret; +} + +/************************************************************************* + * Classes for image loaders - helpers for loading from def files * + *************************************************************************/ + +ImageLoader::ImageLoader(QImage * Img): + image(Img), + lineStart(Img->bits()), + position(Img->bits()) +{ +} + +void ImageLoader::init(QPoint SpriteSize, QPoint Margins, QPoint FullSize, QRgb * pal) +{ + //Init image + + //image->surf = SDL_CreateRGBSurface(0, SpriteSize.x, SpriteSize.y, 8, 0, 0, 0, 0); + //image->margins = Margins; + //image->fullSize = FullSize; + + //Prepare surface + //SDL_Palette * p = SDL_AllocPalette(SDLImage::DEFAULT_PALETTE_COLORS); + //SDL_SetPaletteColors(p, pal, 0, SDLImage::DEFAULT_PALETTE_COLORS); + //SDL_SetSurfacePalette(image->surf, p); + //SDL_FreePalette(p); + + //SDL_LockSurface(image->surf); + lineStart = position = image->bits(); +} + +inline void ImageLoader::Load(size_t size, const ui8 * data) +{ + if(size) + { + memcpy((void *)position, data, size); + position += size; + } +} + +inline void ImageLoader::Load(size_t size, ui8 color) +{ + if (size) + { + memset((void *)position, color, size); + position += size; + } +} + +inline void ImageLoader::EndLine() +{ + lineStart += 0;//image->surf->pitch; + position = lineStart; +} + +ImageLoader::~ImageLoader() +{ + //SDL_UnlockSurface(image->surf); + //SDL_SetColorKey(image->surf, SDL_TRUE, 0); + //TODO: RLE if compressed and bpp>1 +} + +/************************************************************************* + * Classes for images, support loading from file and drawing on surface * + *************************************************************************/ + +std::shared_ptr Animation::getFromExtraDef(std::string filename) +{ + size_t pos = filename.find(':'); + if (pos == -1) + return nullptr; + Animation anim(filename.substr(0, pos)); + pos++; + size_t frame = atoi(filename.c_str()+pos); + size_t group = 0; + pos = filename.find(':', pos); + if (pos != -1) + { + pos++; + group = frame; + frame = atoi(filename.c_str()+pos); + } + anim.load(frame ,group); + auto ret = anim.images[group][frame]; + anim.images.clear(); + return ret; +} + +bool Animation::loadFrame(size_t frame, size_t group) +{ + if(size(group) <= frame) + { + printError(frame, group, "LoadFrame"); + return false; + } + + auto image = getImage(frame, group, false); + if(image) + { + return true; + } + + //try to get image from def + if(source[group][frame].getType() == JsonNode::JsonType::DATA_NULL) + { + if(defFile) + { + auto frameList = defFile->getEntries(); + + if(vstd::contains(frameList, group) && frameList.at(group) > frame) // frame is present + { + images[group][frame] = std::make_shared(defFile->loadFrame(frame, group)); + return true; + } + } + // still here? image is missing + + printError(frame, group, "LoadFrame"); + images[group][frame] = std::make_shared("DEFAULT"); + } + else //load from separate file + { + auto img = getFromExtraDef(source[group][frame]["file"].String()); + //if(!img) + + //img = std::make_shared(source[group][frame]); + + images[group][frame] = img; + return true; + } + return false; +} + +bool Animation::unloadFrame(size_t frame, size_t group) +{ + auto image = getImage(frame, group, false); + if(image) + { + images[group].erase(frame); + + if(images[group].empty()) + images.erase(group); + return true; + } + return false; +} + +void Animation::init() +{ + if(defFile) + { + const std::map defEntries = defFile->getEntries(); + + for (auto & defEntry : defEntries) + source[defEntry.first].resize(defEntry.second); + } + + ResourceID resID(std::string("SPRITES/") + name, EResType::TEXT); + + //if(vstd::contains(graphics->imageLists, resID.getName())) + //initFromJson(graphics->imageLists[resID.getName()]); + + auto configList = CResourceHandler::get()->getResourcesWithName(resID); + + for(auto & loader : configList) + { + auto stream = loader->load(resID); + std::unique_ptr textData(new ui8[stream->getSize()]); + stream->read(textData.get(), stream->getSize()); + + const JsonNode config((char*)textData.get(), stream->getSize()); + + //initFromJson(config); + } +} + +void Animation::printError(size_t frame, size_t group, std::string type) const +{ + logGlobal->error("%s error: Request for frame not present in CAnimation! File name: %s, Group: %d, Frame: %d", type, name, group, frame); +} + +Animation::Animation(std::string Name): + name(Name), + preloaded(false), + defFile() +{ + size_t dotPos = name.find_last_of('.'); + if ( dotPos!=-1 ) + name.erase(dotPos); + std::transform(name.begin(), name.end(), name.begin(), toupper); + + ResourceID resource(std::string("SPRITES/") + name, EResType::ANIMATION); + + if(CResourceHandler::get()->existsResource(resource)) + defFile = std::make_shared(name); + + init(); + + if(source.empty()) + logAnim->error("Animation %s failed to load", Name); +} + +Animation::Animation(): + name(""), + preloaded(false), + defFile() +{ + init(); +} + +Animation::~Animation() = default; + +void Animation::duplicateImage(const size_t sourceGroup, const size_t sourceFrame, const size_t targetGroup) +{ + if(!source.count(sourceGroup)) + { + logAnim->error("Group %d missing in %s", sourceGroup, name); + return; + } + + if(source[sourceGroup].size() <= sourceFrame) + { + logAnim->error("Frame [%d %d] missing in %s", sourceGroup, sourceFrame, name); + return; + } + + //todo: clone actual loaded Image object + JsonNode clone(source[sourceGroup][sourceFrame]); + + if(clone.getType() == JsonNode::JsonType::DATA_NULL) + { + std::string temp = name+":"+boost::lexical_cast(sourceGroup)+":"+boost::lexical_cast(sourceFrame); + clone["file"].String() = temp; + } + + source[targetGroup].push_back(clone); + + size_t index = source[targetGroup].size() - 1; + + if(preloaded) + load(index, targetGroup); +} + +void Animation::setCustom(std::string filename, size_t frame, size_t group) +{ + if (source[group].size() <= frame) + source[group].resize(frame+1); + source[group][frame]["file"].String() = filename; + //FIXME: update image if already loaded +} + +std::shared_ptr Animation::getImage(size_t frame, size_t group, bool verbose) const +{ + auto groupIter = images.find(group); + if (groupIter != images.end()) + { + auto imageIter = groupIter->second.find(frame); + if (imageIter != groupIter->second.end()) + return imageIter->second; + } + if (verbose) + printError(frame, group, "GetImage"); + return nullptr; +} + +void Animation::load() +{ + for (auto & elem : source) + for (size_t image=0; image < elem.second.size(); image++) + loadFrame(image, elem.first); +} + +void Animation::unload() +{ + for (auto & elem : source) + for (size_t image=0; image < elem.second.size(); image++) + unloadFrame(image, elem.first); + +} + +void Animation::preload() +{ + if(!preloaded) + { + preloaded = true; + load(); + } +} + +void Animation::loadGroup(size_t group) +{ + if (vstd::contains(source, group)) + for (size_t image=0; image < source[group].size(); image++) + loadFrame(image, group); +} + +void Animation::unloadGroup(size_t group) +{ + if (vstd::contains(source, group)) + for (size_t image=0; image < source[group].size(); image++) + unloadFrame(image, group); +} + +void Animation::load(size_t frame, size_t group) +{ + loadFrame(frame, group); +} + +void Animation::unload(size_t frame, size_t group) +{ + unloadFrame(frame, group); +} + +size_t Animation::size(size_t group) const +{ + auto iter = source.find(group); + if (iter != source.end()) + return iter->second.size(); + return 0; +} + +void Animation::horizontalFlip() +{ + for(auto & group : images) + for(auto & image : group.second) + *image.second = image.second->transformed(QTransform::fromScale(-1, 1)); +} + +void Animation::verticalFlip() +{ + for(auto & group : images) + for(auto & image : group.second) + *image.second = image.second->transformed(QTransform::fromScale(1, -1)); +} + +void Animation::playerColored(PlayerColor player) +{ + //for(auto & group : images) + //for(auto & image : group.second) + //image.second->playerColored(player); +} + +void Animation::createFlippedGroup(const size_t sourceGroup, const size_t targetGroup) +{ + for(size_t frame = 0; frame < size(sourceGroup); ++frame) + { + duplicateImage(sourceGroup, frame, targetGroup); + + auto image = getImage(frame, targetGroup); + *image = image->transformed(QTransform::fromScale(1, -1)); + } +} diff --git a/mapeditor/Animation.h b/mapeditor/Animation.h new file mode 100644 index 000000000..133a8d26a --- /dev/null +++ b/mapeditor/Animation.h @@ -0,0 +1,88 @@ +#ifndef ANIMATION_H +#define ANIMATION_H + +#include "../lib/vcmi_endian.h" +#include "../lib/JsonNode.h" +#include "../lib/GameConstants.h" +#include +#include + +/* + * Base class for images, can be used for non-animation pictures as well + */ + +class DefFile; +/// Class for handling animation +class Animation +{ +private: + //source[group][position] - file with this frame, if string is empty - image located in def file + std::map> source; + + //bitmap[group][position], store objects with loaded bitmaps + std::map > > images; + + //animation file name + std::string name; + + bool preloaded; + + std::shared_ptr defFile; + + //loader, will be called by load(), require opened def file for loading from it. Returns true if image is loaded + bool loadFrame(size_t frame, size_t group); + + //unloadFrame, returns true if image has been unloaded ( either deleted or decreased refCount) + bool unloadFrame(size_t frame, size_t group); + + //initialize animation from file + //void initFromJson(const JsonNode & input); + void init(); + + //to get rid of copy-pasting error message :] + void printError(size_t frame, size_t group, std::string type) const; + + //not a very nice method to get image from another def file + //TODO: remove after implementing resource manager + std::shared_ptr getFromExtraDef(std::string filename); + +public: + Animation(std::string Name); + Animation(); + ~Animation(); + + //duplicates frame at [sourceGroup, sourceFrame] as last frame in targetGroup + //and loads it if animation is preloaded + void duplicateImage(const size_t sourceGroup, const size_t sourceFrame, const size_t targetGroup); + + // adjust the color of the animation, used in battle spell effects, e.g. Cloned objects + + //add custom surface to the selected position. + void setCustom(std::string filename, size_t frame, size_t group=0); + + std::shared_ptr getImage(size_t frame, size_t group=0, bool verbose=true) const; + + //all available frames + void load (); + void unload(); + void preload(); + + //all frames from group + void loadGroup (size_t group); + void unloadGroup(size_t group); + + //single image + void load (size_t frame, size_t group=0); + void unload(size_t frame, size_t group=0); + + //total count of frames in group (including not loaded) + size_t size(size_t group=0) const; + + void horizontalFlip(); + void verticalFlip(); + void playerColored(PlayerColor player); + + void createFlippedGroup(const size_t sourceGroup, const size_t targetGroup); +}; + +#endif // ANIMATION_H diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index 6c2564bda..f9ba1818c 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -5,6 +5,9 @@ set(editor_SRCS jsonutils.cpp mainwindow.cpp CGameInfo.cpp + BitmapHandler.cpp + maphandler.cpp + Animation.cpp ) set(editor_HEADERS @@ -13,6 +16,9 @@ set(editor_HEADERS jsonutils.h mainwindow.h CGameInfo.h + BitmapHandler.h + maphandler.h + Animation.h ) set(editor_FORMS diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index f9d4a710b..35de34db1 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -18,6 +18,7 @@ #include "CGameInfo.h" +#include "maphandler.h" static CBasicLogConfigurator * logConfig; @@ -113,11 +114,35 @@ void MainWindow::on_actionOpen_triggered() CMapService mapService; try { - auto cmap = mapService.loadMap(resId); + map = mapService.loadMap(resId); } catch(const std::exception & e) { QMessageBox::critical(this, "Failed to open map", e.what()); } + + const int tileSize = 32; + mapHandler.map = map.get(); + mapHandler.init(); + + + QPixmap pixmap(32 * map->width, 32 * map->height); + QPainter painter(&pixmap); + + for(int j = 0; j < map->height; ++j) + { + for(int i = 0; i < map->width; ++i) + { + auto img = mapHandler.drawTileTerrain(map->getTile(int3(1, 1, 0))); + if(!img) + continue; + + painter.drawImage(i * 32, j * 32, *img); + } + } + + scene->clear(); + scene->addPixmap(pixmap); + } diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index 153ccc709..59e1b1fd5 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -4,6 +4,10 @@ #include #include +#include "maphandler.h" + +class CMap; + namespace Ui { class MainWindow; } @@ -23,6 +27,10 @@ private: Ui::MainWindow *ui; QGraphicsScene * scene; + + std::unique_ptr map; + + MapHandler mapHandler; }; #endif // MAINWINDOW_H diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp new file mode 100644 index 000000000..5d3f2f21d --- /dev/null +++ b/mapeditor/maphandler.cpp @@ -0,0 +1,161 @@ +#include "StdInc.h" +#include "maphandler.h" +#include "../lib/mapping/CMap.h" + +MapHandler::MapHandler() +{ + +} + +void MapHandler::init() +{ + //sizes of terrain + sizes.x = map->width; + sizes.y = map->height; + sizes.z = map->twoLevel ? 2 : 1; + + initTerrainGraphics(); + logGlobal->info("\tPreparing terrain, roads, rivers, borders"); + initObjectRects(); + logGlobal->info("\tMaking object rects"); +} + +void MapHandler::initTerrainGraphics() +{ + static const std::map ROAD_FILES = + { + {ROAD_NAMES[1], "dirtrd"}, + {ROAD_NAMES[2], "gravrd"}, + {ROAD_NAMES[3], "cobbrd"} + }; + + static const std::map RIVER_FILES = + { + {RIVER_NAMES[1], "clrrvr"}, + {RIVER_NAMES[2], "icyrvr"}, + {RIVER_NAMES[3], "mudrvr"}, + {RIVER_NAMES[4], "lavrvr"} + }; + + + auto loadFlipped = [](TFlippedAnimations & animation, TFlippedCache & cache, const std::map & files) + { + //no rotation and basic setup + for(auto & type : files) + { + animation[type.first][0] = make_unique(type.second); + animation[type.first][0]->preload(); + const size_t views = animation[type.first][0]->size(0); + cache[type.first].resize(views); + + for(int j = 0; j < views; j++) + cache[type.first][j][0] = animation[type.first][0]->getImage(j); + } + + for(int rotation = 1; rotation < 4; rotation++) + { + for(auto & type : files) + { + animation[type.first][rotation] = make_unique(type.second); + animation[type.first][rotation]->preload(); + const size_t views = animation[type.first][rotation]->size(0); + + for(int j = 0; j < views; j++) + { + auto image = animation[type.first][rotation]->getImage(j); + + //if(rotation == 2 || rotation == 3) + //image->horizontalFlip(); + //if(rotation == 1 || rotation == 3) + //image->verticalFlip(); + + cache[type.first][j][rotation] = image; + } + } + } + }; + + std::map terrainFiles; + for(auto & terrain : Terrain::Manager::terrains()) + { + terrainFiles[terrain] = Terrain::Manager::getInfo(terrain).tilesFilename; + } + + loadFlipped(terrainAnimations, terrainImages, terrainFiles); + loadFlipped(roadAnimations, roadImages, ROAD_FILES); + loadFlipped(riverAnimations, riverImages, RIVER_FILES); +} + +std::shared_ptr MapHandler::drawTileTerrain(const TerrainTile & tinfo) const +{ + //Rect destRect(realTileRect); + + ui8 rotation = tinfo.extTileFlags % 4; + + if(terrainImages.at(tinfo.terType).size() <= tinfo.terView) + return nullptr; + + return terrainImages.at(tinfo.terType)[tinfo.terView][rotation]; +} + +void MapHandler::initObjectRects() +{ + //initializing objects / rects + /*for(auto & elem : map->objects) + { + const CGObjectInstance *obj = elem; + if( !obj + || (obj->ID==Obj::HERO && static_cast(obj)->inTownGarrison) //garrisoned hero + || (obj->ID==Obj::BOAT && static_cast(obj)->hero)) //boat with hero (hero graphics is used) + { + continue; + } + + std::shared_ptr animation = graphics->getAnimation(obj); + + //no animation at all + if(!animation) + continue; + + //empty animation + if(animation->size(0) == 0) + continue; + + auto image = animation->getImage(0,0); + + for(int fx=0; fx < obj->getWidth(); ++fx) + { + 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 = image->width() - fx * 32 - 32; + cr.y = image->height() - fy * 32 - 32; + TerrainTileObject toAdd(obj, cr, obj->visitableAt(currTile.x, currTile.y)); + + + 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 + ) + { + ttiles[currTile.x][currTile.y][currTile.z].objects.push_back(toAdd); + } + } + } + } + + for(int ix=0; ix + +class MapHandler +{ +public: + enum class EMapCacheType : char + { + TERRAIN, OBJECTS, ROADS, RIVERS, FOW, HEROES, HERO_FLAGS, FRAME, AFTER_LAST + }; + + void initObjectRects(); + void initTerrainGraphics(); + + int3 sizes; //map size (x = width, y = height, z = number of levels) + const CMap * map; + + //terrain graphics + + //FIXME: unique_ptr should be enough, but fails to compile in MSVS 2013 + typedef std::map, 4>> TFlippedAnimations; //[type, rotation] + typedef std::map, 4>>> TFlippedCache;//[type, view type, rotation] + + TFlippedAnimations terrainAnimations;//[terrain type, rotation] + TFlippedCache terrainImages;//[terrain type, view type, rotation] + + TFlippedAnimations roadAnimations;//[road type, rotation] + TFlippedCache roadImages;//[road type, view type, rotation] + + TFlippedAnimations riverAnimations;//[river type, rotation] + TFlippedCache riverImages;//[river type, view type, rotation] + + std::shared_ptr drawTileTerrain(const TerrainTile & tinfo) const; + + mutable std::map animationPhase; + + MapHandler(); + ~MapHandler() = default; + + //void getTerrainDescr(const int3 & pos, std::string & out, bool isRMB) const; // isRMB = whether Right Mouse Button is clicked + //bool printObject(const CGObjectInstance * obj, bool fadein = false); //puts appropriate things to tiles, so obj will be visible on map + //bool hideObject(const CGObjectInstance * obj, bool fadeout = false); //removes appropriate things from ttiles, so obj will be no longer visible on map (but still will exist) + //bool hasObjectHole(const int3 & pos) const; // Checks if TerrainTile2 tile has a pit remained after digging. + void init(); + + //EMapAnimRedrawStatus drawTerrainRectNew(SDL_Surface * targetSurface, const MapDrawingInfo * info, bool redrawOnlyAnim = false); + void updateWater(); + /// determines if the map is ready to handle new hero movement (not available during fading animations) + //bool canStartHeroMovement(); + + //void discardWorldViewCache(); + + static bool compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b); +}; + +#endif // MAPHANDLER_H