1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-10 22:31:40 +02:00

Squashing editor

This commit is contained in:
nordsoft
2022-09-18 03:23:17 +04:00
parent 12c71c154f
commit 0b9f601d2c
85 changed files with 12109 additions and 124 deletions

View File

@@ -5,6 +5,7 @@ on:
branches:
- features/*
- develop
- cpp-map-editor
pull_request:
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
@@ -46,7 +47,7 @@ jobs:
pack: 1
cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
extension: exe
cmake_args: -G Ninja
cmake_args: -G Ninja -DENABLE_EDITOR=0
- platform: msvc
os: windows-latest
test: 0

View File

@@ -41,7 +41,7 @@ addons:
notification_email: coverity@arseniyshestakov.com
build_command_prepend: cov-configure --compiler clang-3.6 --comptype clangcc &&
cov-configure --comptype clangcxx --compiler clang++-3.6 && cmake -G Ninja ..
-DCMAKE_BUILD_TYPE=DEBUG -DENABLE_LAUNCHER=0
-DCMAKE_BUILD_TYPE=DEBUG -DENABLE_LAUNCHER=0 -DENABLE_EDITOR=0
build_command: ninja -j 3
branch_pattern: coverity_scan

View File

@@ -44,6 +44,7 @@ set(VCMI_VERSION_PATCH 0)
option(ENABLE_ERM "Enable compilation of ERM scripting module" ON)
option(ENABLE_LUA "Enable compilation of LUA scripting module" ON)
option(ENABLE_LAUNCHER "Enable compilation of launcher" ON)
option(ENABLE_EDITOR "Enable compilation of map editor" ON)
option(ENABLE_TEST "Enable compilation of unit tests" ON)
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
option(ENABLE_PCH "Enable compilation using precompiled headers" ON)
@@ -247,7 +248,7 @@ if(TARGET SDL2_ttf::SDL2_ttf)
endif()
find_package(TBB REQUIRED)
if(ENABLE_LAUNCHER)
if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
# Widgets finds its own dependencies (QtGui and QtCore).
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network)
@@ -356,6 +357,9 @@ add_subdirectory_with_folder("AI" AI)
if(ENABLE_LAUNCHER)
add_subdirectory(launcher)
endif()
if(ENABLE_EDITOR)
add_subdirectory(mapeditor)
endif()
if(ENABLE_TEST)
enable_testing()
add_subdirectory(test)
@@ -390,7 +394,7 @@ if(WIN32)
set(debug_postfix d)
endif()
if(ENABLE_LAUNCHER)
if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
get_target_property(QtCore_location Qt${QT_VERSION_MAJOR}::Core LOCATION)
get_filename_component(Qtbin_folder ${QtCore_location} PATH)
file(GLOB dep_files

View File

@@ -388,7 +388,7 @@
},
"updateConfigUrl" : {
"type" : "string",
"default" : "https://raw.githubusercontent.com/vcmi/vcmi-updates/master/vcmi-updates.json"
"default" : "https://raw.githubusercontent.com/Nordsoft91/vcmi-autoupdate/main/autoUpdate.json"
}
}
}

View File

@@ -12,59 +12,11 @@
#include "../../lib/JsonNode.h"
#include "../../lib/filesystem/CFileInputStream.h"
#include "../../lib/GameConstants.h"
const int maxSections = 3; // versions consist from up to 3 sections, major.minor.patch
bool isCompatible(const QString & verMin, const QString & verMax)
{
QList<int> vcmiVersionList = {GameConstants::VCMI_VERSION_MAJOR,
GameConstants::VCMI_VERSION_MINOR,
GameConstants::VCMI_VERSION_PATCH};
if(!verMin.isEmpty())
{
QStringList verMinList = verMin.split(".");
assert(verMinList.size() == maxSections);
bool compatibleMin = true;
for(int i = 0; i < maxSections; i++)
{
if(verMinList[i].toInt() < vcmiVersionList[i])
{
break;
}
if(verMinList[i].toInt() > vcmiVersionList[i])
{
compatibleMin = false;
break;
}
}
if(!compatibleMin)
return false;
}
if(!verMax.isEmpty())
{
QStringList verMaxList = verMax.split(".");
assert(verMaxList.size() == maxSections);
for(int i = 0; i < maxSections; i++)
{
if(verMaxList[i].toInt() > vcmiVersionList[i])
{
return true;
}
if(verMaxList[i].toInt() < vcmiVersionList[i])
{
return false;
}
}
}
return true;
}
bool CModEntry::compareVersions(QString lesser, QString greater)
{
static const int maxSections = 3; // versions consist from up to 3 sections, major.minor.patch
QStringList lesserList = lesser.split(".");
QStringList greaterList = greater.split(".");
@@ -140,15 +92,6 @@ bool CModEntry::isUpdateable() const
return false;
}
bool CModEntry::isCompatible() const
{
if(!isInstalled())
return false;
auto compatibility = localData["compatibility"].toMap();
return ::isCompatible(compatibility["min"].toString(), compatibility["max"].toString());
}
bool CModEntry::isEssential() const
{
return getValue("storedLocaly").toBool();
@@ -159,11 +102,6 @@ bool CModEntry::isInstalled() const
return !localData.isEmpty();
}
bool CModEntry::isValid() const
{
return !localData.isEmpty() || !repository.isEmpty();
}
int CModEntry::getModStatus() const
{
int status = 0;
@@ -255,11 +193,7 @@ static QVariant getValue(QVariant input, QString path)
QString remainder = "/" + path.section('/', 2, -1);
entryName.remove(0, 1);
QMap<QString, QString> keyNormalize;
for(auto & key : input.toMap().keys())
keyNormalize[key.toLower()] = key;
return getValue(input.toMap().value(keyNormalize[entryName]), remainder);
return getValue(input.toMap().value(entryName), remainder);
}
else
{
@@ -269,7 +203,6 @@ static QVariant getValue(QVariant input, QString path)
CModEntry CModList::getMod(QString modname) const
{
modname = modname.toLower();
QVariantMap repo;
QVariantMap local = localModList[modname].toMap();
QVariantMap settings;
@@ -313,14 +246,14 @@ CModEntry CModList::getMod(QString modname) const
QVariant repoVal = getValue(entry, path);
if(repoVal.isValid())
{
auto repoValMap = repoVal.toMap();
auto compatibility = repoValMap["compatibility"].toMap();
if(isCompatible(compatibility["min"].toString(), compatibility["max"].toString()))
if(repo.empty())
{
if(repo.empty() || CModEntry::compareVersions(repo["version"].toString(), repoValMap["version"].toString()))
{
repo = repoValMap;
}
repo = repoVal.toMap();
}
else
{
if(CModEntry::compareVersions(repo["version"].toString(), repoVal.toMap()["version"].toString()))
repo = repoVal.toMap();
}
}
}
@@ -364,12 +297,12 @@ QVector<QString> CModList::getModList() const
{
for(auto it = repo.begin(); it != repo.end(); it++)
{
knownMods.insert(it.key().toLower());
knownMods.insert(it.key());
}
}
for(auto it = localModList.begin(); it != localModList.end(); it++)
{
knownMods.insert(it.key().toLower());
knownMods.insert(it.key());
}
for(auto entry : knownMods)

View File

@@ -51,10 +51,6 @@ public:
bool isInstalled() const;
// vcmi essential files
bool isEssential() const;
// checks if verison is compatible with vcmi
bool isCompatible() const;
// returns if has any data
bool isValid() const;
// see ModStatus enum
int getModStatus() const;

View File

@@ -245,7 +245,6 @@ bool CModFilterModel::filterMatchesThis(const QModelIndex & source) const
{
CModEntry mod = base->getMod(source.data(ModRoles::ModNameRole).toString());
return (mod.getModStatus() & filterMask) == filteredType &&
mod.isValid() &&
QSortFilterProxyModel::filterAcceptsRow(source.row(), source.parent());
}

View File

@@ -169,10 +169,6 @@ bool CModManager::canEnableMod(QString modname)
if(!mod.isInstalled())
return addError(modname, "Mod must be installed first");
//check for compatibility
if(!mod.isCompatible())
return addError(modname, "Mod is not compatible, please update VCMI and checkout latest mod revisions");
for(auto modEntry : mod.getValue("depends").toStringList())
{
if(!modList->hasMod(modEntry)) // required mod is not available

View File

@@ -544,14 +544,10 @@ CModInfo::Version CModInfo::Version::fromString(std::string from)
{
auto pointPos = from.find('.');
major = std::stoi(from.substr(0, pointPos));
if(pointPos != std::string::npos)
{
from = from.substr(pointPos + 1);
pointPos = from.find('.');
minor = std::stoi(from.substr(0, pointPos));
if(pointPos != std::string::npos)
patch = std::stoi(from.substr(pointPos + 1));
}
from = from.substr(pointPos);
pointPos = from.find('.');
minor = std::stoi(from.substr(0, pointPos));
patch = std::stoi(from.substr(pointPos));
}
catch(const std::invalid_argument & e)
{
@@ -654,11 +650,8 @@ void CModInfo::loadLocalData(const JsonNode & data)
}
//check compatibility
bool wasEnabled = enabled;
enabled &= vcmiCompatibleMin.isNull() || Version::GameVersion().compatible(vcmiCompatibleMin);
enabled &= vcmiCompatibleMax.isNull() || vcmiCompatibleMax.compatible(Version::GameVersion());
if(wasEnabled && !enabled)
logGlobal->warn("Mod %s is incompatible with current version of VCMI and cannot be enabled", name);
if (enabled)
validation = validated ? PASSED : PENDING;

View File

@@ -240,6 +240,19 @@ public:
static std::string getModDir(std::string name);
static std::string getModFile(std::string name);
//TODO: remove as soon as backward compatilibity for versions earlier 806 is not preserved.
template <typename Handler> void serialize(Handler &h, const int ver)
{
h & identifier;
h & description;
h & name;
h & dependencies;
h & conflicts;
h & config;
h & checksum;
h & validation;
h & enabled;
}
private:
void loadLocalData(const JsonNode & data);
};
@@ -361,33 +374,41 @@ public:
template <typename Handler> void serialize(Handler &h, const int version)
{
if(h.saving)
if(version < 806)
{
h & allMods; //don't serialize mods
h & activeMods;
for(auto & m : activeMods)
h & allMods[m].version;
}
else
{
std::vector<TModID> newActiveMods;
h & newActiveMods;
for(auto & m : newActiveMods)
if(h.saving)
{
if(!allMods.count(m))
throw Incompatibility(m + " unkown mod");
CModInfo::Version mver;
h & mver;
if(!allMods[m].version.isNull() && !mver.isNull() && !allMods[m].version.compatible(mver))
{
std::string err = allMods[m].name +
": version needed " + mver.toString() +
"but you have installed " + allMods[m].version.toString();
throw Incompatibility(err);
}
allMods[m].enabled = true;
h & activeMods;
for(auto & m : activeMods)
h & allMods[m].version;
}
else
{
std::vector<TModID> newActiveMods;
h & newActiveMods;
for(auto & m : newActiveMods)
{
if(!allMods.count(m))
throw Incompatibility(m + " unkown mod");
CModInfo::Version mver;
h & mver;
if(!allMods[m].version.isNull() && !mver.isNull() && !allMods[m].version.compatible(mver))
{
std::string err = allMods[m].name +
": version needed " + mver.toString() +
"but you have installed " + allMods[m].version.toString();
throw Incompatibility(err);
}
allMods[m].enabled = true;
}
std::swap(activeMods, newActiveMods);
}
std::swap(activeMods, newActiveMods);
}
h & settings;

780
mapeditor/Animation.cpp Normal file
View File

@@ -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<size_t, std::vector<JsonNode>> source_map;
//typedef std::map<size_t, IImage*> image_map;
//typedef std::map<size_t, image_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;
};
//offset[group][frame] - offset of frame data in file
std::map<size_t, std::vector <size_t> > offset;
std::unique_ptr<ui8[]> data;
std::unique_ptr<QVector<QRgb>> palette;
public:
DefFile(std::string Name);
~DefFile();
std::shared_ptr<QImage> loadFrame(size_t frame, size_t group) const;
const std::map<size_t, size_t> getEntries() const;
};
class ImageLoader
{
QImage * image;
ui8 * lineStart;
ui8 * position;
QPoint spriteSize, margins, fullSize;
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);
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<ui8[]> data;
std::unique_ptr<ui8[]> getCopy()
{
auto ret = std::unique_ptr<ui8[]>(new ui8[size]);
std::copy(data.get(), data.get() + size, ret.get());
return ret;
}
FileData(ResourceID name_, size_t size_, std::unique_ptr<ui8[]> data_):
name{std::move(name_)},
size{size_},
data{std::move(data_)}
{}
};
std::deque<FileData> cache;
public:
std::unique_ptr<ui8[]> 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)
{
#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::make_unique<QVector<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<DefType>(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<totalBlocks; i++)
{
size_t blockID = read_le_u32(data.get() + it);
it+=4;
size_t totalEntries = read_le_u32(data.get() + it);
it+=12;
//8 unknown bytes - skipping
//13 bytes for name of every frame in this block - not used, skipping
it+= 13 * (int)totalEntries;
for (ui32 j=0; j<totalEntries; j++)
{
size_t currOffset = read_le_u32(data.get() + it);
offset[blockID].push_back(currOffset);
it += 4;
}
}
}
std::shared_ptr<QImage> DefFile::loadFrame(size_t frame, size_t group) const
{
std::map<size_t, std::vector <size_t> >::const_iterator it;
it = offset.find(group);
assert (it != offset.end());
const ui8 * FDef = data.get()+it->second[frame];
const SSpriteDef sd = * reinterpret_cast<const SSpriteDef *>(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;
std::shared_ptr<QImage> img = std::make_shared<QImage>(sprite.fullWidth, sprite.fullHeight, QImage::Format_Indexed8);
if(!img)
throw std::runtime_error("Image memory cannot be allocated");
ImageLoader loader(img.get());
loader.init(QPoint(sprite.width, sprite.height),
QPoint(sprite.leftMargin, sprite.topMargin),
QPoint(sprite.fullWidth, sprite.fullHeight));
switch(sprite.format)
{
case 0:
{
//pixel data is not compressed, copy data to surface
for(ui32 i=0; i<sprite.height; i++)
{
loader.Load(sprite.width, FDef + currentOffset);
currentOffset += sprite.width;
loader.EndLine();
}
break;
}
case 1:
{
//for each line we have offset of pixel data
const ui32 * RWEntriesLoc = reinterpret_cast<const ui32 *>(FDef+currentOffset);
currentOffset += sizeof(ui32) * sprite.height;
for(ui32 i=0; i<sprite.height; i++)
{
//get position of the line
currentOffset=BaseOffset + read_le_u32(RWEntriesLoc + i);
ui32 TotalRowLength = 0;
while(TotalRowLength<sprite.width)
{
ui8 segmentType = FDef[currentOffset++];
ui32 length = FDef[currentOffset++] + 1;
if(segmentType==0xFF)//Raw data
{
loader.Load(length, FDef + currentOffset);
currentOffset+=length;
}
else// RLE
{
loader.Load(length, segmentType);
}
TotalRowLength += length;
}
loader.EndLine();
}
break;
}
case 2:
{
currentOffset = BaseOffset + read_le_u16(FDef + BaseOffset);
for(ui32 i=0; i<sprite.height; i++)
{
ui32 TotalRowLength=0;
while(TotalRowLength<sprite.width)
{
ui8 segment=FDef[currentOffset++];
ui8 code = segment / 32;
ui8 length = (segment & 31) + 1;
if(code==7)//Raw data
{
loader.Load(length, FDef + currentOffset);
currentOffset += length;
}
else//RLE
{
loader.Load(length, code);
}
TotalRowLength+=length;
}
loader.EndLine();
}
break;
}
case 3:
{
for(ui32 i=0; i<sprite.height; i++)
{
currentOffset = BaseOffset + read_le_u16(FDef + BaseOffset+i*2*(sprite.width/32));
ui32 TotalRowLength=0;
while(TotalRowLength<sprite.width)
{
ui8 segment = FDef[currentOffset++];
ui8 code = segment / 32;
ui8 length = (segment & 31) + 1;
if(code==7)//Raw data
{
loader.Load(length, FDef + currentOffset);
currentOffset += length;
}
else//RLE
{
loader.Load(length, code);
}
TotalRowLength += length;
}
loader.EndLine();
}
break;
}
default:
logGlobal->error("Error: unsupported format of def file: %d", sprite.format);
break;
}
img->setColorTable(*palette);
return img;
}
DefFile::~DefFile() = default;
const std::map<size_t, size_t > DefFile::getEntries() const
{
std::map<size_t, size_t > 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)
{
spriteSize = SpriteSize;
margins = Margins;
fullSize = FullSize;
memset((void *)image->bits(), 0, fullSize.y() * fullSize.x());
lineStart = image->bits();
lineStart += margins.y() * fullSize.x() + margins.x();
position = lineStart;
}
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 += fullSize.x();
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<QImage> 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] = defFile->loadFrame(frame, group);
return true;
}
}
return false;
// still here? image is missing
printError(frame, group, "LoadFrame");
images[group][frame] = std::make_shared<QImage>("DEFAULT");
}
else //load from separate file
{
auto img = getFromExtraDef(source[group][frame]["file"].String());
//if(!img)
//img = std::make_shared<QImage>(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<size_t, size_t> 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<ui8[]> 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<DefFile>(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<std::string>(sourceGroup)+":"+boost::lexical_cast<std::string>(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<QImage> 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));
}
}

87
mapeditor/Animation.h Normal file
View File

@@ -0,0 +1,87 @@
#ifndef ANIMATION_H
#define ANIMATION_H
#include "../lib/JsonNode.h"
#include "../lib/GameConstants.h"
#include <QRgb>
#include <QImage>
/*
* 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<size_t, std::vector<JsonNode>> source;
//bitmap[group][position], store objects with loaded bitmaps
std::map<size_t, std::map<size_t, std::shared_ptr<QImage> > > images;
//animation file name
std::string name;
bool preloaded;
std::shared_ptr<DefFile> 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<QImage> 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<QImage> 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

169
mapeditor/BitmapHandler.cpp Normal file
View File

@@ -0,0 +1,169 @@
//
// BitmapHandler.cpp
// vcmieditor
//
// Created by nordsoft on 29.08.2022.
//
#include "StdInc.h"
#include "BitmapHandler.h"
#include "../lib/filesystem/Filesystem.h"
#include <QBitmap>
#include <QImage>
#include <QPixmap>
namespace BitmapHandler
{
QImage loadH3PCX(ui8 * data, size_t size);
QImage loadBitmapFromDir(std::string path, std::string fname, bool setKey=true);
bool isPCX(const ui8 *header)//check whether file can be PCX according to header
{
ui32 fSize = read_le_u32(header + 0);
ui32 width = read_le_u32(header + 4);
ui32 height = read_le_u32(header + 8);
return fSize == width*height || fSize == width*height*3;
}
enum Epcxformat
{
PCX8B,
PCX24B
};
QImage loadH3PCX(ui8 * pcx, size_t size)
{
//SDL_Surface * ret;
Epcxformat format;
int it=0;
ui32 fSize = read_le_u32(pcx + it); it+=4;
ui32 width = read_le_u32(pcx + it); it+=4;
ui32 height = read_le_u32(pcx + it); it+=4;
if (fSize==width*height*3)
format=PCX24B;
else if (fSize==width*height)
format=PCX8B;
else
return QImage();
QSize qsize(width, height);
if (format==PCX8B)
{
it = 0xC;
//auto bitmap = QBitmap::fromData(qsize, pcx + it);
QImage image(pcx + it, width, height, QImage::Format_Indexed8);
//palette - last 256*3 bytes
QVector<QRgb> colorTable;
it = (int)size-256*3;
for (int i=0;i<256;i++)
{
char bytes[3];
bytes[0] = pcx[it++];
bytes[1] = pcx[it++];
bytes[2] = pcx[it++];
colorTable.append(qRgb(bytes[0], bytes[1], bytes[2]));
}
image.setColorTable(colorTable);
return image;
}
else
{
QImage image(pcx + it, width, height, QImage::Format_RGB32);
return image;
}
}
QImage loadBitmapFromDir(std::string path, std::string fname, bool setKey)
{
if(!fname.size())
{
logGlobal->warn("Call to loadBitmap with void fname!");
return QImage();
}
if (!CResourceHandler::get()->existsResource(ResourceID(path + fname, EResType::IMAGE)))
{
return QImage();
}
auto fullpath = CResourceHandler::get()->getResourceName(ResourceID(path + fname, EResType::IMAGE));
auto readFile = CResourceHandler::get()->load(ResourceID(path + fname, EResType::IMAGE))->readAll();
if (isPCX(readFile.first.get()))
{//H3-style PCX
auto image = BitmapHandler::loadH3PCX(readFile.first.get(), readFile.second);
if(!image.isNull())
{
if(image.bitPlaneCount() == 1 && setKey)
{
QVector<QRgb> colorTable = image.colorTable();
colorTable[0] = qRgba(255, 255, 255, 0);
image.setColorTable(colorTable);
}
}
else
{
logGlobal->error("Failed to open %s as H3 PCX!", fname);
}
return image;
}
else
{ //loading via SDL_Image
QImage image(QString::fromStdString(fullpath->make_preferred().string()));
if(!image.isNull())
{
if(image.bitPlaneCount() == 1)
{
//set correct value for alpha\unused channel
QVector<QRgb> colorTable = image.colorTable();
for(auto & c : colorTable)
c = qRgb(qRed(c), qGreen(c), qBlue(c));
image.setColorTable(colorTable);
}
}
else
{
logGlobal->error("Failed to open %s via QImage", fname);
return image;
}
}
return QImage();
// When modifying anything here please check two use cases:
// 1) Vampire mansion in Necropolis (not 1st color is transparent)
// 2) Battle background when fighting on grass/dirt, topmost sky part (NO transparent color)
// 3) New objects that may use 24-bit images for icons (e.g. witchking arts)
/*if (ret->format->palette)
{
CSDL_Ext::setDefaultColorKeyPresize(ret);
}
else if (ret->format->Amask)
{
SDL_SetSurfaceBlendMode(ret, SDL_BLENDMODE_BLEND);
}
else // always set
{
CSDL_Ext::setDefaultColorKey(ret);
}
return ret;*/
}
QImage loadBitmap(std::string fname, bool setKey)
{
QImage image = loadBitmapFromDir("DATA/", fname, setKey);
if(image.isNull())
{
image = loadBitmapFromDir("SPRITES/", fname, setKey);
if(image.isNull())
{
logGlobal->error("Error: Failed to find file %s", fname);
}
}
return image;
}
}

20
mapeditor/BitmapHandler.h Normal file
View File

@@ -0,0 +1,20 @@
//
// BitmapHandler.hpp
// vcmieditor
//
// Created by nordsoft on 29.08.2022.
//
#pragma once
#define read_le_u16(p) (* reinterpret_cast<const ui16 *>(p))
#define read_le_u32(p) (* reinterpret_cast<const ui32 *>(p))
#include <QImage>
namespace BitmapHandler
{
//Load file from /DATA or /SPRITES
QImage loadBitmap(std::string fname, bool setKey=true);
}

108
mapeditor/CGameInfo.cpp Normal file
View File

@@ -0,0 +1,108 @@
/*
* CGameInfo.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 "CGameInfo.h"
#include "../lib/VCMI_Lib.h"
const CGameInfo * CGI;
CClientState * CCS = nullptr;
CServerHandler * CSH;
CGameInfo::CGameInfo()
{
generaltexth = nullptr;
mh = nullptr;
townh = nullptr;
globalServices = nullptr;
}
void CGameInfo::setFromLib()
{
globalServices = VLC;
modh = VLC->modh;
generaltexth = VLC->generaltexth;
creh = VLC->creh;
townh = VLC->townh;
heroh = VLC->heroh;
objh = VLC->objh;
spellh = VLC->spellh;
skillh = VLC->skillh;
objtypeh = VLC->objtypeh;
obstacleHandler = VLC->obstacleHandler;
battleFieldHandler = VLC->battlefieldsHandler;
}
const ArtifactService * CGameInfo::artifacts() const
{
return globalServices->artifacts();
}
const BattleFieldService * CGameInfo::battlefields() const
{
return globalServices->battlefields();
}
const CreatureService * CGameInfo::creatures() const
{
return globalServices->creatures();
}
const FactionService * CGameInfo::factions() const
{
return globalServices->factions();
}
const HeroClassService * CGameInfo::heroClasses() const
{
return globalServices->heroClasses();
}
const HeroTypeService * CGameInfo::heroTypes() const
{
return globalServices->heroTypes();
}
const scripting::Service * CGameInfo::scripts() const
{
return globalServices->scripts();
}
const spells::Service * CGameInfo::spells() const
{
return globalServices->spells();
}
const SkillService * CGameInfo::skills() const
{
return globalServices->skills();
}
const ObstacleService * CGameInfo::obstacles() const
{
return globalServices->obstacles();
}
void CGameInfo::updateEntity(Metatype metatype, int32_t index, const JsonNode & data)
{
logGlobal->error("CGameInfo::updateEntity call is not expected.");
}
spells::effects::Registry * CGameInfo::spellEffects()
{
return nullptr;
}
const spells::effects::Registry * CGameInfo::spellEffects() const
{
return globalServices->spellEffects();
}

93
mapeditor/CGameInfo.h Normal file
View File

@@ -0,0 +1,93 @@
/*
* CGameInfo.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 <vcmi/Services.h>
#include "../lib/ConstTransitivePtr.h"
class CModHandler;
class CMapHandler;
class CHeroHandler;
class CCreatureHandler;
class CSpellHandler;
class CSkillHandler;
class CBuildingHandler;
class CObjectHandler;
class CSoundHandler;
class CMusicHandler;
class CObjectClassesHandler;
class CTownHandler;
class CGeneralTextHandler;
class CConsoleHandler;
class CCursorHandler;
class CGameState;
class IMainVideoPlayer;
class CServerHandler;
class BattleFieldHandler;
class ObstacleHandler;
class CMap;
//a class for non-mechanical client GUI classes
class CClientState
{
public:
CSoundHandler * soundh;
CMusicHandler * musich;
CConsoleHandler * consoleh;
CCursorHandler * curh;
IMainVideoPlayer * videoh;
};
extern CClientState * CCS;
/// CGameInfo class
/// for allowing different functions for accessing game informations
class CGameInfo : public Services
{
public:
const ArtifactService * artifacts() const override;
const CreatureService * creatures() const override;
const FactionService * factions() const override;
const HeroClassService * heroClasses() const override;
const HeroTypeService * heroTypes() const override;
const scripting::Service * scripts() const override;
const spells::Service * spells() const override;
const SkillService * skills() const override;
const ObstacleService * obstacles() const override;
const BattleFieldService * battlefields() const override;
void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) override;
const spells::effects::Registry * spellEffects() const override;
spells::effects::Registry * spellEffects() override;
ConstTransitivePtr<CModHandler> modh; //public?
ConstTransitivePtr<BattleFieldHandler> battleFieldHandler;
ConstTransitivePtr<CHeroHandler> heroh;
ConstTransitivePtr<CCreatureHandler> creh;
ConstTransitivePtr<CSpellHandler> spellh;
ConstTransitivePtr<CSkillHandler> skillh;
ConstTransitivePtr<CObjectHandler> objh;
ConstTransitivePtr<CObjectClassesHandler> objtypeh;
ConstTransitivePtr<ObstacleHandler> obstacleHandler;
CGeneralTextHandler * generaltexth;
CMapHandler * mh;
CTownHandler * townh;
void setFromLib();
CGameInfo();
private:
const Services * globalServices;
};
extern const CGameInfo* CGI;

139
mapeditor/CMakeLists.txt Normal file
View File

@@ -0,0 +1,139 @@
set(editor_SRCS
StdInc.cpp
main.cpp
launcherdirs.cpp
jsonutils.cpp
mainwindow.cpp
CGameInfo.cpp
BitmapHandler.cpp
maphandler.cpp
Animation.cpp
graphics.cpp
spoiler.cpp
windownewmap.cpp
generatorprogress.cpp
mapview.cpp
radiopushbutton.cpp
objectbrowser.cpp
mapsettings.cpp
playersettings.cpp
playerparams.cpp
scenelayer.cpp
mapcontroller.cpp
validator.cpp
inspector/inspector.cpp
inspector/townbulidingswidget.cpp
inspector/armywidget.cpp
inspector/messagewidget.cpp
inspector/rewardswidget.cpp
)
set(editor_HEADERS
StdInc.h
launcherdirs.h
jsonutils.h
mainwindow.h
CGameInfo.h
BitmapHandler.h
maphandler.h
Animation.h
graphics.h
spoiler.h
windownewmap.h
generatorprogress.h
mapview.h
radiopushbutton.h
objectbrowser.h
mapsettings.h
playersettings.h
playerparams.h
scenelayer.h
mapcontroller.h
validator.h
inspector/inspector.h
inspector/townbulidingswidget.h
inspector/armywidget.h
inspector/messagewidget.h
inspector/rewardswidget.h
)
set(editor_FORMS
mainwindow.ui
windownewmap.ui
generatorprogress.ui
mapsettings.ui
playersettings.ui
playerparams.ui
validator.ui
inspector/townbulidingswidget.ui
inspector/armywidget.ui
inspector/messagewidget.ui
inspector/rewardswidget.ui
)
assign_source_group(${editor_SRCS} ${editor_HEADERS} mapeditor.rc)
# Tell CMake to run moc when necessary:
set(CMAKE_AUTOMOC ON)
if(POLICY CMP0071)
cmake_policy(SET CMP0071 NEW)
endif()
# As moc files are generated in the binary dir, tell CMake
# to always look for includes there:
set(CMAKE_INCLUDE_CURRENT_DIR ON)
if(TARGET Qt6::Core)
qt_wrap_ui(editor_UI_HEADERS ${editor_FORMS})
else()
qt5_wrap_ui(editor_UI_HEADERS ${editor_FORMS})
endif()
if(WIN32)
set(editor_ICON mapeditor.rc)
endif()
add_executable(vcmieditor WIN32 ${editor_SRCS} ${editor_HEADERS} ${editor_UI_HEADERS} ${editor_ICON})
if(WIN32)
set_target_properties(vcmieditor
PROPERTIES
OUTPUT_NAME "VCMI_mapeditor"
PROJECT_LABEL "VCMI_mapeditor"
)
# FIXME: Can't to get CMP0020 working with Vcpkg and CMake 3.8.2
# So far I tried:
# - cmake_minimum_required set to 2.8.11 globally and in this file
# - cmake_policy in all possible places
# - used NO_POLICY_SCOPE to make sure no other parts reset policies
# Still nothing worked, warning kept appearing and WinMain didn't link automatically
target_link_libraries(vcmieditor Qt${QT_VERSION_MAJOR}::WinMain)
endif()
if(APPLE)
# This makes Xcode project prettier by moving vcmilauncher_autogen directory into vcmiclient subfolder
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER vcmieditor)
endif()
target_link_libraries(vcmieditor vcmi Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network)
target_include_directories(vcmieditor
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
)
vcmi_set_output_dir(vcmieditor "")
enable_pch(vcmieditor)
# Copy to build directory for easier debugging
add_custom_command(TARGET vcmieditor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/mapeditor/icons
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/mapeditor/icons ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/mapeditor/icons
)
install(TARGETS vcmieditor DESTINATION ${BIN_DIR})
# copy whole directory
install(DIRECTORY icons DESTINATION ${DATA_DIR}/mapeditor)
# Install icons and desktop file on Linux
if(NOT WIN32 AND NOT APPLE)
install(FILES "vcmilauncher.desktop" DESTINATION share/applications)
endif()

1
mapeditor/StdInc.cpp Normal file
View File

@@ -0,0 +1 @@
#include "StdInc.h"

32
mapeditor/StdInc.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#include "../Global.h"
#define VCMI_EDITOR_VERSION "0.1"
#define VCMI_EDITOR_NAME "VCMI Map Editor"
#include <QtWidgets>
#include <QStringList>
#include <QSet>
#include <QVector>
#include <QList>
#include <QString>
#include <QFile>
inline QString pathToQString(const boost::filesystem::path & path)
{
#ifdef VCMI_WINDOWS
return QString::fromStdWString(path.wstring());
#else
return QString::fromStdString(path.string());
#endif
}
inline boost::filesystem::path qstringToPath(const QString & path)
{
#ifdef VCMI_WINDOWS
return boost::filesystem::path(path.toStdWString());
#else
return boost::filesystem::path(path.toUtf8().data());
#endif
}

View File

@@ -0,0 +1,38 @@
#include "StdInc.h"
#include "generatorprogress.h"
#include "ui_generatorprogress.h"
#include <thread>
#include <chrono>
GeneratorProgress::GeneratorProgress(Load::Progress & source, QWidget *parent) :
QDialog(parent),
ui(new Ui::GeneratorProgress),
source(source)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
setWindowFlags(Qt::Window);
show();
}
GeneratorProgress::~GeneratorProgress()
{
delete ui;
}
void GeneratorProgress::update()
{
while(!source.finished())
{
int status = float(source.get()) / 2.55f;
ui->progressBar->setValue(status);
qApp->processEvents();
}
//delete source;
close();
}

View File

@@ -0,0 +1,26 @@
#ifndef GENERATORPROGRESS_H
#define GENERATORPROGRESS_H
#include <QDialog>
#include "../lib/LoadProgress.h"
namespace Ui {
class GeneratorProgress;
}
class GeneratorProgress : public QDialog
{
Q_OBJECT
public:
explicit GeneratorProgress(Load::Progress & source, QWidget *parent = nullptr);
~GeneratorProgress();
void update();
private:
Ui::GeneratorProgress *ui;
Load::Progress & source;
};
#endif // GENERATORPROGRESS_H

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GeneratorProgress</class>
<widget class="QDialog" name="GeneratorProgress">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>60</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>60</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>64</height>
</size>
</property>
<property name="windowTitle">
<string>Generating map</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

378
mapeditor/graphics.cpp Normal file
View File

@@ -0,0 +1,378 @@
/*
* Graphics.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 "graphics.h"
#include <vcmi/Entity.h>
#include <vcmi/ArtifactService.h>
#include <vcmi/CreatureService.h>
#include <vcmi/FactionService.h>
#include <vcmi/HeroTypeService.h>
#include <vcmi/SkillService.h>
#include <vcmi/spells/Service.h>
#include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/CBinaryReader.h"
#include "Animation.h"
#include "../lib/CThreadHelper.h"
#include "../lib/CModHandler.h"
#include "../lib/VCMI_Lib.h"
#include "../CCallback.h"
#include "../lib/CGeneralTextHandler.h"
#include "BitmapHandler.h"
#include "../lib/CGameState.h"
#include "../lib/JsonNode.h"
#include "../lib/CStopWatch.h"
#include "../lib/mapObjects/CObjectClassesHandler.h"
#include "../lib/mapObjects/CObjectHandler.h"
#include "../lib/CHeroHandler.h"
#include "CGameInfo.h"
Graphics * graphics = nullptr;
void Graphics::loadPaletteAndColors()
{
auto textFile = CResourceHandler::get()->load(ResourceID("DATA/PLAYERS.PAL"))->readAll();
std::string pals((char*)textFile.first.get(), textFile.second);
playerColorPalette.resize(256);
playerColors.resize(PlayerColor::PLAYER_LIMIT_I);
int startPoint = 24; //beginning byte; used to read
for(int i=0; i<256; ++i)
{
QColor col;
col.setRed(pals[startPoint++]);
col.setGreen(pals[startPoint++]);
col.setBlue(pals[startPoint++]);
col.setAlpha(255);
startPoint++;
playerColorPalette[i] = col.rgba();
}
neutralColorPalette.resize(32);
auto stream = CResourceHandler::get()->load(ResourceID("config/NEUTRAL.PAL"));
CBinaryReader reader(stream.get());
for(int i=0; i<32; ++i)
{
QColor col;
col.setRed(reader.readUInt8());
col.setGreen(reader.readUInt8());
col.setBlue(reader.readUInt8());
col.setAlpha(255);
reader.readUInt8(); // this is "flags" entry, not alpha
neutralColorPalette[i] = col.rgba();
}
//colors initialization
QColor colors[] = {
{0xff,0, 0, 255},
{0x31,0x52,0xff,255},
{0x9c,0x73,0x52,255},
{0x42,0x94,0x29,255},
{0xff,0x84,0, 255},
{0x8c,0x29,0xa5,255},
{0x09,0x9c,0xa5,255},
{0xc6,0x7b,0x8c,255}};
for(int i=0;i<8;i++)
{
playerColors[i] = colors[i].rgba();
}
//gray
neutralColor = qRgba(0x84, 0x84, 0x84, 0xFF);
}
Graphics::Graphics()
{
#if 0
std::vector<Task> tasks; //preparing list of graphics to load
tasks += std::bind(&Graphics::loadFonts,this);
tasks += std::bind(&Graphics::loadPaletteAndColors,this);
tasks += std::bind(&Graphics::initializeBattleGraphics,this);
tasks += std::bind(&Graphics::loadErmuToPicture,this);
tasks += std::bind(&Graphics::initializeImageLists,this);
CThreadHelper th(&tasks,std::max((ui32)1,boost::thread::hardware_concurrency()));
th.run();
#else
loadPaletteAndColors();
initializeImageLists();
#endif
//(!) do not load any CAnimation here
}
Graphics::~Graphics()
{
}
void Graphics::load()
{
loadHeroAnimations();
loadHeroFlagAnimations();
}
void Graphics::loadHeroAnimations()
{
for(auto & elem : CGI->heroh->classes.objects)
{
for (auto templ : VLC->objtypeh->getHandlerFor(Obj::HERO, elem->getIndex())->getTemplates())
{
if (!heroAnimations.count(templ->animationFile))
heroAnimations[templ->animationFile] = loadHeroAnimation(templ->animationFile);
}
}
boatAnimations[0] = loadHeroAnimation("AB01_.DEF");
boatAnimations[1] = loadHeroAnimation("AB02_.DEF");
boatAnimations[2] = loadHeroAnimation("AB03_.DEF");
mapObjectAnimations["AB01_.DEF"] = boatAnimations[0];
mapObjectAnimations["AB02_.DEF"] = boatAnimations[1];
mapObjectAnimations["AB03_.DEF"] = boatAnimations[2];
}
void Graphics::loadHeroFlagAnimations()
{
static const std::vector<std::string> HERO_FLAG_ANIMATIONS =
{
"AF00", "AF01","AF02","AF03",
"AF04", "AF05","AF06","AF07"
};
static const std::vector< std::vector<std::string> > BOAT_FLAG_ANIMATIONS =
{
{
"ABF01L", "ABF01G", "ABF01R", "ABF01D",
"ABF01B", "ABF01P", "ABF01W", "ABF01K"
},
{
"ABF02L", "ABF02G", "ABF02R", "ABF02D",
"ABF02B", "ABF02P", "ABF02W", "ABF02K"
},
{
"ABF03L", "ABF03G", "ABF03R", "ABF03D",
"ABF03B", "ABF03P", "ABF03W", "ABF03K"
}
};
for(const auto & name : HERO_FLAG_ANIMATIONS)
heroFlagAnimations.push_back(loadHeroFlagAnimation(name));
for(int i = 0; i < BOAT_FLAG_ANIMATIONS.size(); i++)
for(const auto & name : BOAT_FLAG_ANIMATIONS[i])
boatFlagAnimations[i].push_back(loadHeroFlagAnimation(name));
}
std::shared_ptr<Animation> Graphics::loadHeroFlagAnimation(const std::string & name)
{
//first - group number to be rotated, second - group number after rotation
static const std::vector<std::pair<int,int> > rotations =
{
{6,10}, {7,11}, {8,12}, {1,13},
{2,14}, {3,15}
};
std::shared_ptr<Animation> anim = std::make_shared<Animation>(name);
anim->preload();
for(const auto & rotation : rotations)
{
const int sourceGroup = rotation.first;
const int targetGroup = rotation.second;
anim->createFlippedGroup(sourceGroup, targetGroup);
}
return anim;
}
std::shared_ptr<Animation> Graphics::loadHeroAnimation(const std::string &name)
{
//first - group number to be rotated, second - group number after rotation
static const std::vector<std::pair<int,int> > rotations =
{
{6,10}, {7,11}, {8,12}, {1,13},
{2,14}, {3,15}
};
std::shared_ptr<Animation> anim = std::make_shared<Animation>(name);
anim->preload();
for(const auto & rotation : rotations)
{
const int sourceGroup = rotation.first;
const int targetGroup = rotation.second;
anim->createFlippedGroup(sourceGroup, targetGroup);
}
return anim;
}
void Graphics::blueToPlayersAdv(QImage * sur, PlayerColor player)
{
if(sur->format() == QImage::Format_Indexed8)
{
auto palette = sur->colorTable();
if(player < PlayerColor::PLAYER_LIMIT)
{
for(int i = 0; i < 32; ++i)
palette[224 + i] = playerColorPalette[player.getNum() * 32 + i];
}
else if(player == PlayerColor::NEUTRAL)
{
palette = neutralColorPalette;
}
else
{
logGlobal->error("Wrong player id in blueToPlayersAdv (%s)!", player.getStr());
return;
}
//FIXME: not all player colored images have player palette at last 32 indexes
//NOTE: following code is much more correct but still not perfect (bugged with status bar)
sur->setColorTable(palette);
#if 0
SDL_Color * bluePalette = playerColorPalette + 32;
SDL_Palette * oldPalette = sur->format->palette;
SDL_Palette * newPalette = SDL_AllocPalette(256);
for(size_t destIndex = 0; destIndex < 256; destIndex++)
{
SDL_Color old = oldPalette->colors[destIndex];
bool found = false;
for(size_t srcIndex = 0; srcIndex < 32; srcIndex++)
{
if(old.b == bluePalette[srcIndex].b && old.g == bluePalette[srcIndex].g && old.r == bluePalette[srcIndex].r)
{
found = true;
newPalette->colors[destIndex] = palette[srcIndex];
break;
}
}
if(!found)
newPalette->colors[destIndex] = old;
}
SDL_SetSurfacePalette(sur, newPalette);
SDL_FreePalette(newPalette);
#endif // 0
}
else
{
//TODO: implement. H3 method works only for images with palettes.
// Add some kind of player-colored overlay?
// Or keep palette approach here and replace only colors of specific value(s)
// Or just wait for OpenGL support?
logGlobal->warn("Image must have palette to be player-colored!");
}
}
std::shared_ptr<Animation> Graphics::getAnimation(const CGObjectInstance* obj)
{
if(obj->ID == Obj::HERO)
return getHeroAnimation(obj->appearance);
return getAnimation(obj->appearance);
}
std::shared_ptr<Animation> Graphics::getHeroAnimation(const std::shared_ptr<const ObjectTemplate> info)
{
if(info->animationFile.empty())
{
logGlobal->warn("Def name for hero (%d,%d) is empty!", info->id, info->subid);
return std::shared_ptr<Animation>();
}
std::shared_ptr<Animation> ret = loadHeroAnimation(info->animationFile);
//already loaded
if(ret)
{
ret->preload();
return ret;
}
ret = std::make_shared<Animation>(info->animationFile);
heroAnimations[info->animationFile] = ret;
ret->preload();
return ret;
}
std::shared_ptr<Animation> Graphics::getAnimation(const std::shared_ptr<const ObjectTemplate> info)
{
if(info->animationFile.empty())
{
logGlobal->warn("Def name for obj (%d,%d) is empty!", info->id, info->subid);
return std::shared_ptr<Animation>();
}
std::shared_ptr<Animation> ret = mapObjectAnimations[info->animationFile];
//already loaded
if(ret)
{
ret->preload();
return ret;
}
ret = std::make_shared<Animation>(info->animationFile);
mapObjectAnimations[info->animationFile] = ret;
ret->preload();
return ret;
}
void Graphics::addImageListEntry(size_t index, const std::string & listName, const std::string & imageName)
{
if (!imageName.empty())
{
JsonNode entry;
entry["frame"].Integer() = index;
entry["file"].String() = imageName;
imageLists["SPRITES/" + listName]["images"].Vector().push_back(entry);
}
}
void Graphics::addImageListEntries(const EntityService * service)
{
auto cb = std::bind(&Graphics::addImageListEntry, this, _1, _2, _3);
auto loopCb = [&](const Entity * entity, bool & stop)
{
entity->registerIcons(cb);
};
service->forEachBase(loopCb);
}
void Graphics::initializeImageLists()
{
addImageListEntries(CGI->creatures());
addImageListEntries(CGI->heroTypes());
addImageListEntries(CGI->artifacts());
addImageListEntries(CGI->factions());
addImageListEntries(CGI->spells());
addImageListEntries(CGI->skills());
}

84
mapeditor/graphics.h Normal file
View File

@@ -0,0 +1,84 @@
/*
* Graphics.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/GameConstants.h"
#include <QImage>
class CGHeroInstance;
class CGTownInstance;
class CHeroClass;
struct InfoAboutHero;
struct InfoAboutTown;
class CGObjectInstance;
class ObjectTemplate;
class Animation;
class EntityService;
class JsonNode;
/// Handles fonts, hero images, town images, various graphics
class Graphics
{
void addImageListEntry(size_t index, const std::string & listName, const std::string & imageName);
void addImageListEntries(const EntityService * service);
void initializeBattleGraphics();
void loadPaletteAndColors();
void loadHeroAnimations();
//loads animation and adds required rotated frames
std::shared_ptr<Animation> loadHeroAnimation(const std::string &name);
void loadHeroFlagAnimations();
//loads animation and adds required rotated frames
std::shared_ptr<Animation> loadHeroFlagAnimation(const std::string &name);
void loadErmuToPicture();
void loadFogOfWar();
void loadFonts();
void initializeImageLists();
public:
//various graphics
QVector<QRgb> playerColors; //array [8]
QRgb neutralColor;
QVector<QRgb> playerColorPalette; //palette to make interface colors good - array of size [256]
QVector<QRgb> neutralColorPalette;
// [hero class def name] //added group 10: up - left, 11 - left and 12 - left down // 13 - up-left standing; 14 - left standing; 15 - left down standing
std::map< std::string, std::shared_ptr<Animation> > heroAnimations;
std::vector< std::shared_ptr<Animation> > heroFlagAnimations;
// [boat type: 0 .. 2] //added group 10: up - left, 11 - left and 12 - left down // 13 - up-left standing; 14 - left standing; 15 - left down standing
std::array< std::shared_ptr<Animation>, 3> boatAnimations;
std::array< std::vector<std::shared_ptr<Animation> >, 3> boatFlagAnimations;
//all other objects (not hero or boat)
std::map< std::string, std::shared_ptr<Animation> > mapObjectAnimations;
std::map<std::string, JsonNode> imageLists;
//functions
Graphics();
~Graphics();
void load();
void blueToPlayersAdv(QImage * sur, PlayerColor player); //replaces blue interface colour with a color of player
std::shared_ptr<Animation> getAnimation(const CGObjectInstance * obj);
std::shared_ptr<Animation> getAnimation(const std::shared_ptr<const ObjectTemplate> info);
std::shared_ptr<Animation> getHeroAnimation(const std::shared_ptr<const ObjectTemplate> info);
};
extern Graphics * graphics;

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,142 @@
#include "armywidget.h"
#include "ui_armywidget.h"
#include "CCreatureHandler.h"
ArmyWidget::ArmyWidget(CArmedInstance & a, QWidget *parent) :
QDialog(parent),
army(a),
ui(new Ui::ArmyWidget)
{
ui->setupUi(this);
uiCounts[0] = ui->count0; uiSlots[0] = ui->slot0;
uiCounts[1] = ui->count1; uiSlots[1] = ui->slot1;
uiCounts[2] = ui->count2; uiSlots[2] = ui->slot2;
uiCounts[3] = ui->count3; uiSlots[3] = ui->slot3;
uiCounts[4] = ui->count4; uiSlots[4] = ui->slot4;
uiCounts[5] = ui->count5; uiSlots[5] = ui->slot5;
uiCounts[6] = ui->count6; uiSlots[6] = ui->slot6;
for(int i = 0; i < TOTAL_SLOTS; ++i)
{
uiCounts[i]->setInputMask("d0000");
uiCounts[i]->setText("1");
uiSlots[i]->addItem("");
uiSlots[i]->setItemData(0, -1);
for(int c = 0; c < VLC->creh->objects.size(); ++c)
{
auto creature = VLC->creh->objects[c];
uiSlots[i]->insertItem(c + 1, tr(creature->getPluralName().c_str()));
uiSlots[i]->setItemData(c + 1, creature->getId().getNum());
}
}
ui->formationTight->setChecked(true);
}
int ArmyWidget::searchItemIndex(int slotId, CreatureID creId) const
{
for(int i = 0; i < uiSlots[slotId]->count(); ++i)
{
if(creId.getNum() == uiSlots[slotId]->itemData(i).toInt())
return i;
}
return 0;
}
void ArmyWidget::obtainData()
{
for(int i = 0; i < TOTAL_SLOTS; ++i)
{
if(army.hasStackAtSlot(SlotID(i)))
{
auto * creature = army.getCreature(SlotID(i));
uiSlots[i]->setCurrentIndex(searchItemIndex(i, creature->getId()));
uiCounts[i]->setText(QString::number(army.getStackCount(SlotID(i))));
}
}
if(army.formation)
ui->formationTight->setChecked(true);
else
ui->formationWide->setChecked(true);
}
bool ArmyWidget::commitChanges()
{
bool isArmed = false;
for(int i = 0; i < TOTAL_SLOTS; ++i)
{
CreatureID creId(uiSlots[i]->itemData(uiSlots[i]->currentIndex()).toInt());
if(creId == -1)
{
if(army.hasStackAtSlot(SlotID(i)))
army.eraseStack(SlotID(i));
}
else
{
isArmed = true;
int amount = uiCounts[i]->text().toInt();
if(amount)
{
army.setCreature(SlotID(i), creId, amount);
}
else
{
if(army.hasStackAtSlot(SlotID(i)))
army.eraseStack(SlotID(i));
army.putStack(SlotID(i), new CStackInstance(creId, amount, false));
}
}
}
army.setFormation(ui->formationTight->isChecked());
return isArmed;
}
ArmyWidget::~ArmyWidget()
{
delete ui;
}
ArmyDelegate::ArmyDelegate(CArmedInstance & t): army(t), QStyledItemDelegate()
{
}
QWidget * ArmyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
return new ArmyWidget(army, parent);
}
void ArmyDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
if(auto * ed = qobject_cast<ArmyWidget *>(editor))
{
ed->obtainData();
}
else
{
QStyledItemDelegate::setEditorData(editor, index);
}
}
void ArmyDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
if(auto * ed = qobject_cast<ArmyWidget *>(editor))
{
auto isArmed = ed->commitChanges();
model->setData(index, "dummy");
if(isArmed)
model->setData(index, "HAS ARMY");
else
model->setData(index, "");
}
else
{
QStyledItemDelegate::setModelData(editor, model, index);
}
}

View File

@@ -0,0 +1,49 @@
#ifndef ARMYWIDGET_H
#define ARMYWIDGET_H
#include <QDialog>
#include "../lib/mapObjects/CArmedInstance.h"
const int TOTAL_SLOTS = 7;
namespace Ui {
class ArmyWidget;
}
class ArmyWidget : public QDialog
{
Q_OBJECT
public:
explicit ArmyWidget(CArmedInstance &, QWidget *parent = nullptr);
~ArmyWidget();
void obtainData();
bool commitChanges();
private:
int searchItemIndex(int slotId, CreatureID creId) const;
Ui::ArmyWidget *ui;
CArmedInstance & army;
std::array<QLineEdit*, TOTAL_SLOTS> uiCounts;
std::array<QComboBox*, TOTAL_SLOTS> uiSlots;
};
class ArmyDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
using QStyledItemDelegate::QStyledItemDelegate;
ArmyDelegate(CArmedInstance &);
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
private:
CArmedInstance & army;
};
#endif // ARMYWIDGET_H

View File

@@ -0,0 +1,298 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ArmyWidget</class>
<widget class="QDialog" name="ArmyWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>318</width>
<height>314</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>318</width>
<height>314</height>
</size>
</property>
<property name="windowTitle">
<string>Army settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="6" column="0">
<widget class="QComboBox" name="slot6">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QComboBox" name="slot3">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QComboBox" name="slot2">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="count6">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="count2">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="count1">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="count5">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QRadioButton" name="formationWide">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Wide formation</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="count3">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="slot1">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="slot0">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QComboBox" name="slot4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="count4">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QComboBox" name="slot5">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="count0">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QRadioButton" name="formationTight">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Tight formation</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,735 @@
#include "StdInc.h"
#include "inspector.h"
#include "../lib/CArtHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CRandomGenerator.h"
#include "../lib/mapObjects/CObjectClassesHandler.h"
#include "../lib/mapping/CMap.h"
#include "townbulidingswidget.h"
#include "armywidget.h"
#include "messagewidget.h"
#include "rewardswidget.h"
//===============IMPLEMENT OBJECT INITIALIZATION FUNCTIONS================
Initializer::Initializer(CGObjectInstance * o, const PlayerColor & pl) : defaultPlayer(pl)
{
logGlobal->info("New object instance initialized");
///IMPORTANT! initialize order should be from base objects to derived objects
INIT_OBJ_TYPE(CGResource);
INIT_OBJ_TYPE(CGArtifact);
INIT_OBJ_TYPE(CArmedInstance);
INIT_OBJ_TYPE(CGShipyard);
INIT_OBJ_TYPE(CGGarrison);
INIT_OBJ_TYPE(CGMine);
INIT_OBJ_TYPE(CGDwelling);
INIT_OBJ_TYPE(CGTownInstance);
INIT_OBJ_TYPE(CGCreature);
INIT_OBJ_TYPE(CGHeroInstance);
INIT_OBJ_TYPE(CGSignBottle);
INIT_OBJ_TYPE(CGLighthouse);
//INIT_OBJ_TYPE(CGPandoraBox);
}
bool stringToBool(const QString & s)
{
if(s == "TRUE")
return true;
//if(s == "FALSE")
return false;
}
void Initializer::initialize(CArmedInstance * o)
{
if(!o) return;
}
void Initializer::initialize(CGSignBottle * o)
{
if(!o) return;
}
void Initializer::initialize(CGCreature * o)
{
if(!o) return;
o->character = CGCreature::Character::HOSTILE;
o->putStack(SlotID(0), new CStackInstance(CreatureID(o->subID), 0, false));
}
void Initializer::initialize(CGDwelling * o)
{
if(!o) return;
o->tempOwner = defaultPlayer;
switch(o->ID)
{
case Obj::RANDOM_DWELLING:
case Obj::RANDOM_DWELLING_LVL:
case Obj::RANDOM_DWELLING_FACTION:
o->initRandomObjectInfo();
}
}
void Initializer::initialize(CGGarrison * o)
{
if(!o) return;
o->tempOwner = defaultPlayer;
o->removableUnits = true;
}
void Initializer::initialize(CGShipyard * o)
{
if(!o) return;
o->tempOwner = defaultPlayer;
}
void Initializer::initialize(CGLighthouse * o)
{
if(!o) return;
o->tempOwner = defaultPlayer;
}
void Initializer::initialize(CGHeroInstance * o)
{
if(!o) return;
o->tempOwner = defaultPlayer;
if(o->ID == Obj::HERO)
{
for(auto t : VLC->heroh->objects)
{
if(t->heroClass == VLC->heroh->classes.objects[o->subID].get())
{
o->type = VLC->heroh->objects[o->subID];
break;
}
}
}
if(!o->type)
o->type = VLC->heroh->objects.at(o->subID);
o->name = o->type->getName();
o->sex = o->type->sex;
o->biography = o->type->biography;
o->portrait = o->type->imageIndex;
o->randomizeArmy(o->type->heroClass->faction);
}
void Initializer::initialize(CGTownInstance * o)
{
if(!o) return;
const std::vector<std::string> castleLevels{"village", "fort", "citadel", "castle", "capitol"};
int lvl = vstd::find_pos(castleLevels, o->appearance->stringID);
o->builtBuildings.insert(BuildingID::DEFAULT);
if(lvl > -1) o->builtBuildings.insert(BuildingID::TAVERN);
if(lvl > 0) o->builtBuildings.insert(BuildingID::FORT);
if(lvl > 1) o->builtBuildings.insert(BuildingID::CITADEL);
if(lvl > 2) o->builtBuildings.insert(BuildingID::CASTLE);
if(lvl > 3) o->builtBuildings.insert(BuildingID::CAPITOL);
for(auto spell : VLC->spellh->objects) //add all regular spells to town
{
if(!spell->isSpecial() && !spell->isCreatureAbility())
o->possibleSpells.push_back(spell->id);
}
}
void Initializer::initialize(CGArtifact * o)
{
if(!o) return;
if(o->ID == Obj::SPELL_SCROLL)
{
std::vector<SpellID> out;
for(auto spell : VLC->spellh->objects) //spellh size appears to be greater (?)
{
//if(map->isAllowedSpell(spell->id))
{
out.push_back(spell->id);
}
}
auto a = CArtifactInstance::createScroll(*RandomGeneratorUtil::nextItem(out, CRandomGenerator::getDefault()));
o->storedArtifact = a;
}
}
void Initializer::initialize(CGMine * o)
{
if(!o) return;
o->tempOwner = defaultPlayer;
o->producedResource = Res::ERes(o->subID);
o->producedQuantity = o->defaultResProduction();
}
void Initializer::initialize(CGResource * o)
{
if(!o) return;
o->amount = CGResource::RANDOM_AMOUNT;
}
//===============IMPLEMENT PROPERTIES SETUP===============================
void Inspector::updateProperties(CArmedInstance * o)
{
if(!o) return;
auto * delegate = new ArmyDelegate(*o);
addProperty("Army", PropertyEditorPlaceholder(), delegate, false);
}
void Inspector::updateProperties(CGDwelling * o)
{
if(!o) return;
addProperty("Owner", o->tempOwner, false);
}
void Inspector::updateProperties(CGLighthouse * o)
{
if(!o) return;
addProperty("Owner", o->tempOwner, false);
}
void Inspector::updateProperties(CGGarrison * o)
{
if(!o) return;
addProperty("Owner", o->tempOwner, false);
addProperty("Removable units", o->removableUnits, InspectorDelegate::boolDelegate(), false);
}
void Inspector::updateProperties(CGShipyard * o)
{
if(!o) return;
addProperty("Owner", o->tempOwner, false);
}
void Inspector::updateProperties(CGHeroInstance * o)
{
if(!o) return;
addProperty("Owner", o->tempOwner, o->ID == Obj::PRISON); //field is not editable for prison
addProperty<int>("Experience", o->exp, false);
addProperty("Hero class", o->type->heroClass->getName(), true);
{
auto * delegate = new InspectorDelegate;
delegate->options << "MALE" << "FEMALE";
addProperty<std::string>("Sex", (o->sex ? "FEMALE" : "MALE"), delegate , false);
}
addProperty("Name", o->name, false);
addProperty("Biography", o->biography, new MessageDelegate, false);
addProperty("Portrait", o->portrait, false);
{
auto * delegate = new InspectorDelegate;
for(int i = 0; i < VLC->heroh->objects.size(); ++i)
{
if(map->allowedHeroes.at(i))
{
if(o->ID == Obj::PRISON || (o->type && VLC->heroh->objects[i]->heroClass->getIndex() == o->type->heroClass->getIndex()))
delegate->options << QObject::tr(VLC->heroh->objects[i]->getName().c_str());
}
}
addProperty("Hero type", o->type->getName(), delegate, false);
}
}
void Inspector::updateProperties(CGTownInstance * o)
{
if(!o) return;
addProperty("Town name", o->name, false);
auto * delegate = new TownBuildingsDelegate(*o);
addProperty("Buildings", PropertyEditorPlaceholder(), delegate, false);
}
void Inspector::updateProperties(CGArtifact * o)
{
if(!o) return;
addProperty("Message", o->message, false);
CArtifactInstance * instance = o->storedArtifact;
if(instance)
{
SpellID spellId = instance->getGivenSpellID();
if(spellId != -1)
{
auto * delegate = new InspectorDelegate;
for(auto spell : VLC->spellh->objects)
{
//if(map->isAllowedSpell(spell->id))
delegate->options << QObject::tr(spell->name.c_str());
}
addProperty("Spell", VLC->spellh->objects[spellId]->name, delegate, false);
}
}
}
void Inspector::updateProperties(CGMine * o)
{
if(!o) return;
addProperty("Owner", o->tempOwner, false);
addProperty("Resource", o->producedResource);
addProperty("Productivity", o->producedQuantity, false);
}
void Inspector::updateProperties(CGResource * o)
{
if(!o) return;
addProperty("Amount", o->amount, false);
addProperty("Message", o->message, false);
}
void Inspector::updateProperties(CGSignBottle * o)
{
if(!o) return;
addProperty("Message", o->message, new MessageDelegate, false);
}
void Inspector::updateProperties(CGCreature * o)
{
if(!o) return;
addProperty("Message", o->message, false);
{
auto * delegate = new InspectorDelegate;
delegate->options << "COMPLIANT" << "FRIENDLY" << "AGRESSIVE" << "HOSTILE" << "SAVAGE";
addProperty<CGCreature::Character>("Character", (CGCreature::Character)o->character, delegate, false);
}
addProperty("Never flees", o->neverFlees, InspectorDelegate::boolDelegate(), false);
addProperty("Not growing", o->notGrowingTeam, InspectorDelegate::boolDelegate(), false);
addProperty("Artifact reward", o->gainedArtifact); //TODO: implement in setProperty
addProperty("Army", PropertyEditorPlaceholder(), true);
addProperty("Amount", o->stacks[SlotID(0)]->count, false);
//addProperty("Resources reward", o->resources); //TODO: implement in setProperty
}
void Inspector::updateProperties(CGPandoraBox * o)
{
if(!o) return;
auto * delegate = new RewardsPandoraDelegate(*map, *o);
addProperty("Reward", PropertyEditorPlaceholder(), delegate, false);
}
void Inspector::updateProperties(CGEvent * o)
{
if(!o) return;
addProperty("Remove after", o->removeAfterVisit, InspectorDelegate::boolDelegate(), false);
addProperty("Human trigger", o->humanActivate, InspectorDelegate::boolDelegate(), false);
addProperty("Cpu trigger", o->computerActivate, InspectorDelegate::boolDelegate(), false);
//ui8 availableFor; //players whom this event is available for
}
void Inspector::updateProperties()
{
if(!obj)
return;
table->setRowCount(0); //cleanup table
addProperty("Indentifier", obj);
addProperty("ID", obj->ID.getNum());
addProperty("SubID", obj->subID);
addProperty("InstanceName", obj->instanceName);
addProperty("TypeName", obj->typeName);
addProperty("SubTypeName", obj->subTypeName);
if(!dynamic_cast<CGHeroInstance*>(obj))
{
auto factory = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID);
addProperty("IsStatic", factory->isStaticObject());
}
auto * delegate = new InspectorDelegate();
delegate->options << "NEUTRAL";
for(int p = 0; p < map->players.size(); ++p)
if(map->players[p].canAnyonePlay())
delegate->options << QString("PLAYER %1").arg(p);
addProperty("Owner", obj->tempOwner, delegate, true);
UPDATE_OBJ_PROPERTIES(CArmedInstance);
UPDATE_OBJ_PROPERTIES(CGResource);
UPDATE_OBJ_PROPERTIES(CGArtifact);
UPDATE_OBJ_PROPERTIES(CGMine);
UPDATE_OBJ_PROPERTIES(CGGarrison);
UPDATE_OBJ_PROPERTIES(CGShipyard);
UPDATE_OBJ_PROPERTIES(CGDwelling);
UPDATE_OBJ_PROPERTIES(CGTownInstance);
UPDATE_OBJ_PROPERTIES(CGCreature);
UPDATE_OBJ_PROPERTIES(CGHeroInstance);
UPDATE_OBJ_PROPERTIES(CGSignBottle);
UPDATE_OBJ_PROPERTIES(CGLighthouse);
UPDATE_OBJ_PROPERTIES(CGPandoraBox);
UPDATE_OBJ_PROPERTIES(CGEvent);
table->show();
}
//===============IMPLEMENT PROPERTY UPDATE================================
void Inspector::setProperty(const QString & key, const QVariant & value)
{
if(!obj)
return;
if(key == "Owner")
{
PlayerColor owner(value.toString().mid(6).toInt()); //receiving PLAYER N, N has index 6
if(value == "NEUTRAL")
owner = PlayerColor::NEUTRAL;
if(value == "UNFLAGGABLE")
owner = PlayerColor::UNFLAGGABLE;
obj->tempOwner = owner;
}
SET_PROPERTIES(CArmedInstance);
SET_PROPERTIES(CGTownInstance);
SET_PROPERTIES(CGArtifact);
SET_PROPERTIES(CGMine);
SET_PROPERTIES(CGResource);
SET_PROPERTIES(CGDwelling);
SET_PROPERTIES(CGGarrison);
SET_PROPERTIES(CGCreature);
SET_PROPERTIES(CGHeroInstance);
SET_PROPERTIES(CGShipyard);
SET_PROPERTIES(CGSignBottle);
SET_PROPERTIES(CGLighthouse);
SET_PROPERTIES(CGPandoraBox);
SET_PROPERTIES(CGEvent);
}
void Inspector::setProperty(CArmedInstance * o, const QString & key, const QVariant & value)
{
if(!o) return;
}
void Inspector::setProperty(CGLighthouse * o, const QString & key, const QVariant & value)
{
if(!o) return;
}
void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVariant & value)
{
if(!o) return;
}
void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & value)
{
if(!o) return;
if("Remove after")
o->removeAfterVisit = stringToBool(value.toString());
if("Human trigger")
o->humanActivate = stringToBool(value.toString());
if("Cpu trigger")
o->computerActivate = stringToBool(value.toString());
}
void Inspector::setProperty(CGTownInstance * o, const QString & key, const QVariant & value)
{
if(!o) return;
if(key == "Town name")
o->name = value.toString().toStdString();
}
void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVariant & value)
{
if(!o) return;
if(key == "Message")
o->message = value.toString().toStdString();
}
void Inspector::setProperty(CGMine * o, const QString & key, const QVariant & value)
{
if(!o) return;
if(key == "Productivity")
o->producedQuantity = value.toString().toInt();
}
void Inspector::setProperty(CGArtifact * o, const QString & key, const QVariant & value)
{
if(!o) return;
if(key == "Message")
o->message = value.toString().toStdString();
if(o->storedArtifact && key == "Spell")
{
for(auto spell : VLC->spellh->objects)
{
if(spell->name == value.toString().toStdString())
{
o->storedArtifact = CArtifactInstance::createScroll(spell->getId());
break;
}
}
}
}
void Inspector::setProperty(CGDwelling * o, const QString & key, const QVariant & value)
{
if(!o) return;
}
void Inspector::setProperty(CGGarrison * o, const QString & key, const QVariant & value)
{
if(!o) return;
if(key == "Removable units")
o->removableUnits = stringToBool(value.toString());
}
void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVariant & value)
{
if(!o) return;
if(key == "Sex")
o->sex = value.toString() == "MALE" ? 0 : 1;
if(key == "Name")
o->name = value.toString().toStdString();
if(key == "Hero type")
{
for(auto t : VLC->heroh->objects)
{
if(t->getName() == value.toString().toStdString())
o->type = t.get();
}
o->name = o->type->getName();
o->sex = o->type->sex;
o->biography = o->type->biography;
o->portrait = o->type->imageIndex;
o->randomizeArmy(o->type->heroClass->faction);
updateProperties(); //updating other properties after change
}
}
void Inspector::setProperty(CGShipyard * o, const QString & key, const QVariant & value)
{
if(!o) return;
}
void Inspector::setProperty(CGResource * o, const QString & key, const QVariant & value)
{
if(!o) return;
if(key == "Amount")
o->amount = value.toString().toInt();
}
void Inspector::setProperty(CGCreature * o, const QString & key, const QVariant & value)
{
if(!o) return;
if(key == "Message")
o->message = value.toString().toStdString();
if(key == "Character")
{
//COMPLIANT = 0, FRIENDLY = 1, AGRESSIVE = 2, HOSTILE = 3, SAVAGE = 4
if(value == "COMPLIANT")
o->character = CGCreature::Character::COMPLIANT;
if(value == "FRIENDLY")
o->character = CGCreature::Character::FRIENDLY;
if(value == "AGRESSIVE")
o->character = CGCreature::Character::AGRESSIVE;
if(value == "HOSTILE")
o->character = CGCreature::Character::HOSTILE;
if(value == "SAVAGE")
o->character = CGCreature::Character::SAVAGE;
}
if(key == "Never flees")
o->neverFlees = stringToBool(value.toString());
if(key == "Not growing")
o->notGrowingTeam = stringToBool(value.toString());
if(key == "Amount")
o->stacks[SlotID(0)]->count = value.toString().toInt();
}
//===============IMPLEMENT PROPERTY VALUE TYPE============================
QTableWidgetItem * Inspector::addProperty(CGObjectInstance * value)
{
using NumericPointer = unsigned long long;
static_assert(sizeof(CGObjectInstance *) == sizeof(NumericPointer),
"Compilied for 64 bit arcitecture. Use NumericPointer = unsigned int");
return new QTableWidgetItem(QString::number(reinterpret_cast<NumericPointer>(value)));
}
QTableWidgetItem * Inspector::addProperty(Inspector::PropertyEditorPlaceholder value)
{
auto item = new QTableWidgetItem("");
item->setData(Qt::UserRole, QString("PropertyEditor"));
return item;
}
QTableWidgetItem * Inspector::addProperty(unsigned int value)
{
return new QTableWidgetItem(QString::number(value));
}
QTableWidgetItem * Inspector::addProperty(int value)
{
return new QTableWidgetItem(QString::number(value));
}
QTableWidgetItem * Inspector::addProperty(bool value)
{
return new QTableWidgetItem(value ? "TRUE" : "FALSE");
}
QTableWidgetItem * Inspector::addProperty(const std::string & value)
{
return addProperty(QString::fromStdString(value));
}
QTableWidgetItem * Inspector::addProperty(const QString & value)
{
return new QTableWidgetItem(value);
}
QTableWidgetItem * Inspector::addProperty(const int3 & value)
{
return new QTableWidgetItem(QString("(%1, %2, %3)").arg(value.x, value.y, value.z));
}
QTableWidgetItem * Inspector::addProperty(const PlayerColor & value)
{
auto str = QString("PLAYER %1").arg(value.getNum());
if(value == PlayerColor::NEUTRAL)
str = "NEUTRAL";
if(value == PlayerColor::UNFLAGGABLE)
str = "UNFLAGGABLE";
return new QTableWidgetItem(str);
}
QTableWidgetItem * Inspector::addProperty(const Res::ERes & value)
{
QString str;
switch (value) {
case Res::ERes::WOOD:
str = "WOOD";
break;
case Res::ERes::ORE:
str = "ORE";
break;
case Res::ERes::SULFUR:
str = "SULFUR";
break;
case Res::ERes::GEMS:
str = "GEMS";
break;
case Res::ERes::MERCURY:
str = "MERCURY";
break;
case Res::ERes::CRYSTAL:
str = "CRYSTAL";
break;
case Res::ERes::GOLD:
str = "GOLD";
break;
default:
break;
}
return new QTableWidgetItem(str);
}
QTableWidgetItem * Inspector::addProperty(CGCreature::Character value)
{
QString str;
switch (value) {
case CGCreature::Character::COMPLIANT:
str = "COMPLIANT";
break;
case CGCreature::Character::FRIENDLY:
str = "FRIENDLY";
break;
case CGCreature::Character::AGRESSIVE:
str = "AGRESSIVE";
break;
case CGCreature::Character::HOSTILE:
str = "HOSTILE";
break;
case CGCreature::Character::SAVAGE:
str = "SAVAGE";
break;
default:
break;
}
return new QTableWidgetItem(str);
}
//========================================================================
Inspector::Inspector(CMap * m, CGObjectInstance * o, QTableWidget * t): obj(o), table(t), map(m)
{
}
/*
* Delegates
*/
InspectorDelegate * InspectorDelegate::boolDelegate()
{
auto * d = new InspectorDelegate;
d->options << "TRUE" << "FALSE";
return d;
}
QWidget * InspectorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
return new QComboBox(parent);
}
void InspectorDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
if(QComboBox *ed = qobject_cast<QComboBox *>(editor))
{
ed->addItems(options);
}
else
{
QStyledItemDelegate::setEditorData(editor, index);
}
}
void InspectorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
if(QComboBox *ed = qobject_cast<QComboBox *>(editor))
{
if(!options.isEmpty())
{
QMap<int, QVariant> data;
data[0] = options[ed->currentIndex()];
model->setItemData(index, data);
}
}
else
{
QStyledItemDelegate::setModelData(editor, model, index);
}
}

View File

@@ -0,0 +1,154 @@
#ifndef INSPECTOR_H
#define INSPECTOR_H
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QStyledItemDelegate>
#include "../lib/int3.h"
#include "../lib/GameConstants.h"
#include "../lib/mapObjects/MapObjects.h"
#include "../lib/ResourceSet.h"
#define DECLARE_OBJ_TYPE(x) void initialize(x*);
#define DECLARE_OBJ_PROPERTY_METHODS(x) \
void updateProperties(x*); \
void setProperty(x*, const QString &, const QVariant &);
#define INIT_OBJ_TYPE(x) initialize(dynamic_cast<x*>(o))
#define UPDATE_OBJ_PROPERTIES(x) updateProperties(dynamic_cast<x*>(obj))
#define SET_PROPERTIES(x) setProperty(dynamic_cast<x*>(obj), key, value)
class Initializer
{
public:
//===============DECLARE MAP OBJECTS======================================
DECLARE_OBJ_TYPE(CArmedInstance);
DECLARE_OBJ_TYPE(CGShipyard);
DECLARE_OBJ_TYPE(CGTownInstance);
DECLARE_OBJ_TYPE(CGArtifact);
DECLARE_OBJ_TYPE(CGMine);
DECLARE_OBJ_TYPE(CGResource);
DECLARE_OBJ_TYPE(CGDwelling);
DECLARE_OBJ_TYPE(CGGarrison);
DECLARE_OBJ_TYPE(CGHeroInstance);
DECLARE_OBJ_TYPE(CGCreature);
DECLARE_OBJ_TYPE(CGSignBottle);
DECLARE_OBJ_TYPE(CGLighthouse);
//DECLARE_OBJ_TYPE(CGEvent);
//DECLARE_OBJ_TYPE(CGPandoraBox);
Initializer(CGObjectInstance *, const PlayerColor &);
private:
PlayerColor defaultPlayer;
};
class Inspector
{
protected:
struct PropertyEditorPlaceholder {};
//===============DECLARE PROPERTIES SETUP=================================
DECLARE_OBJ_PROPERTY_METHODS(CArmedInstance);
DECLARE_OBJ_PROPERTY_METHODS(CGTownInstance);
DECLARE_OBJ_PROPERTY_METHODS(CGShipyard);
DECLARE_OBJ_PROPERTY_METHODS(CGArtifact);
DECLARE_OBJ_PROPERTY_METHODS(CGMine);
DECLARE_OBJ_PROPERTY_METHODS(CGResource);
DECLARE_OBJ_PROPERTY_METHODS(CGDwelling);
DECLARE_OBJ_PROPERTY_METHODS(CGGarrison);
DECLARE_OBJ_PROPERTY_METHODS(CGHeroInstance);
DECLARE_OBJ_PROPERTY_METHODS(CGCreature);
DECLARE_OBJ_PROPERTY_METHODS(CGSignBottle);
DECLARE_OBJ_PROPERTY_METHODS(CGLighthouse);
DECLARE_OBJ_PROPERTY_METHODS(CGPandoraBox);
DECLARE_OBJ_PROPERTY_METHODS(CGEvent);
//===============DECLARE PROPERTY VALUE TYPE==============================
QTableWidgetItem * addProperty(unsigned int value);
QTableWidgetItem * addProperty(int value);
QTableWidgetItem * addProperty(const std::string & value);
QTableWidgetItem * addProperty(const QString & value);
QTableWidgetItem * addProperty(const int3 & value);
QTableWidgetItem * addProperty(const PlayerColor & value);
QTableWidgetItem * addProperty(const Res::ERes & value);
QTableWidgetItem * addProperty(bool value);
QTableWidgetItem * addProperty(CGObjectInstance * value);
QTableWidgetItem * addProperty(CGCreature::Character value);
QTableWidgetItem * addProperty(PropertyEditorPlaceholder value);
//===============END OF DECLARATION=======================================
public:
Inspector(CMap *, CGObjectInstance *, QTableWidget *);
void setProperty(const QString & key, const QVariant & value);
void updateProperties();
protected:
template<class T>
void addProperty(const QString & key, const T & value, QAbstractItemDelegate * delegate, bool restricted)
{
auto * itemValue = addProperty(value);
if(restricted)
itemValue->setFlags(Qt::NoItemFlags);
QTableWidgetItem * itemKey = nullptr;
if(keyItems.contains(key))
{
itemKey = keyItems[key];
table->setItem(table->row(itemKey), 1, itemValue);
if(delegate)
table->setItemDelegateForRow(table->row(itemKey), delegate);
}
else
{
itemKey = new QTableWidgetItem(key);
itemKey->setFlags(Qt::NoItemFlags);
keyItems[key] = itemKey;
table->setRowCount(row + 1);
table->setItem(row, 0, itemKey);
table->setItem(row, 1, itemValue);
table->setItemDelegateForRow(row, delegate);
++row;
}
}
template<class T>
void addProperty(const QString & key, const T & value, bool restricted = true)
{
addProperty<T>(key, value, nullptr, restricted);
}
protected:
int row = 0;
QTableWidget * table;
CGObjectInstance * obj;
QMap<QString, QTableWidgetItem*> keyItems;
CMap * map;
};
class InspectorDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
static InspectorDelegate * boolDelegate();
using QStyledItemDelegate::QStyledItemDelegate;
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
QStringList options;
};
#endif // INSPECTOR_H

View File

@@ -0,0 +1,53 @@
#include "messagewidget.h"
#include "ui_messagewidget.h"
MessageWidget::MessageWidget(QWidget *parent) :
QDialog(parent),
ui(new Ui::MessageWidget)
{
ui->setupUi(this);
}
MessageWidget::~MessageWidget()
{
delete ui;
}
void MessageWidget::setMessage(const QString & m)
{
ui->messageEdit->setPlainText(m);
}
QString MessageWidget::getMessage() const
{
return ui->messageEdit->toPlainText();
}
QWidget * MessageDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
return new MessageWidget(parent);
}
void MessageDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
if(auto *ed = qobject_cast<MessageWidget *>(editor))
{
ed->setMessage(index.data().toString());
}
else
{
QStyledItemDelegate::setEditorData(editor, index);
}
}
void MessageDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
if(auto *ed = qobject_cast<MessageWidget *>(editor))
{
model->setData(index, ed->getMessage());
}
else
{
QStyledItemDelegate::setModelData(editor, model, index);
}
}

View File

@@ -0,0 +1,37 @@
#ifndef MESSAGEWIDGET_H
#define MESSAGEWIDGET_H
#include <QDialog>
namespace Ui {
class MessageWidget;
}
class MessageWidget : public QDialog
{
Q_OBJECT
public:
explicit MessageWidget(QWidget *parent = nullptr);
~MessageWidget();
void setMessage(const QString &);
QString getMessage() const;
private:
Ui::MessageWidget *ui;
};
class MessageDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
using QStyledItemDelegate::QStyledItemDelegate;
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
};
#endif // MESSAGEWIDGET_H

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MessageWidget</class>
<widget class="QDialog" name="MessageWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>306</width>
<height>201</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>306</width>
<height>201</height>
</size>
</property>
<property name="windowTitle">
<string>Message</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPlainTextEdit" name="messageEdit"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,329 @@
#include "rewardswidget.h"
#include "ui_rewardswidget.h"
#include "../lib/VCMI_Lib.h"
#include "../lib/CSkillHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CArtHandler.h"
#include "../lib/CCreatureHandler.h"
#include "../lib/StringConstants.h"
RewardsWidget::RewardsWidget(const CMap & m, CGPandoraBox & p, QWidget *parent) :
QDialog(parent),
map(m),
pandora(&p),
ui(new Ui::RewardsWidget)
{
ui->setupUi(this);
for(auto & type : rewardTypes)
ui->rewardType->addItem(QString::fromStdString(type));
}
RewardsWidget::~RewardsWidget()
{
delete ui;
}
QList<QString> RewardsWidget::getListForType(int typeId)
{
assert(typeId < rewardTypes.size());
QList<QString> result;
switch (typeId) {
case 4: //resources
//to convert string to index WOOD = 0, MERCURY, ORE, SULFUR, CRYSTAL, GEMS, GOLD, MITHRIL,
result.append("Wood");
result.append("Mercury");
result.append("Ore");
result.append("Sulfur");
result.append("Crystals");
result.append("Gems");
result.append("Gold");
break;
case 5:
for(auto s : PrimarySkill::names)
result.append(QString::fromStdString(s));
break;
case 6:
//abilities
for(int i = 0; i < map.allowedAbilities.size(); ++i)
{
if(map.allowedAbilities[i])
result.append(QString::fromStdString(VLC->skillh->objects.at(i)->getName()));
}
break;
case 7:
//arts
for(int i = 0; i < map.allowedArtifact.size(); ++i)
{
if(map.allowedArtifact[i])
result.append(QString::fromStdString(VLC->arth->objects.at(i)->getName()));
}
break;
case 8:
//spells
for(int i = 0; i < map.allowedSpell.size(); ++i)
{
if(map.allowedSpell[i])
result.append(QString::fromStdString(VLC->spellh->objects.at(i)->getName()));
}
break;
case 9:
//creatures
for(auto creature : VLC->creh->objects)
{
result.append(QString::fromStdString(creature->getName()));
}
break;
}
return result;
}
void RewardsWidget::on_rewardType_activated(int index)
{
ui->rewardList->clear();
ui->rewardList->setEnabled(true);
assert(index < rewardTypes.size());
auto l = getListForType(index);
if(l.empty())
ui->rewardList->setEnabled(false);
for(auto & s : l)
ui->rewardList->addItem(s);
}
void RewardsWidget::obtainData()
{
if(pandora)
{
if(pandora->gainedExp > 0)
addReward(0, 0, pandora->gainedExp);
if(pandora->manaDiff)
addReward(1, 0, pandora->manaDiff);
if(pandora->moraleDiff)
addReward(2, 0, pandora->moraleDiff);
if(pandora->luckDiff)
addReward(3, 0, pandora->luckDiff);
if(pandora->resources.nonZero())
{
for(Res::ResourceSet::nziterator resiter(pandora->resources); resiter.valid(); ++resiter)
addReward(4, resiter->resType, resiter->resVal);
}
for(int idx = 0; idx < pandora->primskills.size(); ++idx)
{
if(pandora->primskills[idx])
addReward(5, idx, pandora->primskills[idx]);
}
assert(pandora->abilities.size() == pandora->abilityLevels.size());
for(int idx = 0; idx < pandora->abilities.size(); ++idx)
{
addReward(6, pandora->abilities[idx].getNum(), pandora->abilityLevels[idx]);
}
for(auto art : pandora->artifacts)
{
addReward(7, art.getNum(), 1);
}
for(auto spell : pandora->spells)
{
addReward(8, spell.getNum(), 1);
}
for(int i = 0; i < pandora->creatures.Slots().size(); ++i)
{
if(auto c = pandora->creatures.getCreature(SlotID(i)))
addReward(9, c->getId(), pandora->creatures.getStackCount(SlotID(i)));
}
}
}
bool RewardsWidget::commitChanges()
{
bool haveRewards = false;
if(pandora)
{
pandora->abilities.clear();
pandora->abilityLevels.clear();
pandora->primskills.resize(GameConstants::PRIMARY_SKILLS, 0);
pandora->resources = Res::ResourceSet();
pandora->artifacts.clear();
pandora->spells.clear();
pandora->creatures.clear();
for(int row = 0; row < rewards; ++row)
{
haveRewards = true;
int typeId = ui->rewardsTable->item(row, 0)->data(Qt::UserRole).toInt();
int listId = ui->rewardsTable->item(row, 1) ? ui->rewardsTable->item(row, 1)->data(Qt::UserRole).toInt() : 0;
int amount = ui->rewardsTable->item(row, 2)->data(Qt::UserRole).toInt();
switch(typeId)
{
case 0:
pandora->gainedExp = amount;
break;
case 1:
pandora->manaDiff = amount;
break;
case 2:
pandora->moraleDiff = amount;
break;
case 3:
pandora->luckDiff = amount;
break;
case 4:
pandora->resources.at(listId) = amount;
break;
case 5:
pandora->primskills[listId] = amount;
break;
case 6:
pandora->abilities.push_back(SecondarySkill(listId));
pandora->abilityLevels.push_back(amount);
break;
case 7:
pandora->artifacts.push_back(ArtifactID(listId));
break;
case 8:
pandora->spells.push_back(SpellID(listId));
break;
case 9:
auto slot = pandora->creatures.getFreeSlot();
if(slot != SlotID() && amount > 0)
pandora->creatures.addToSlot(slot, CreatureID(listId), amount);
break;
}
}
}
return haveRewards;
}
void RewardsWidget::on_rewardList_activated(int index)
{
ui->rewardAmount->setText(QString::number(1));
}
void RewardsWidget::addReward(int typeId, int listId, int amount)
{
ui->rewardsTable->setRowCount(++rewards);
auto itemType = new QTableWidgetItem(QString::fromStdString(rewardTypes[typeId]));
itemType->setData(Qt::UserRole, typeId);
ui->rewardsTable->setItem(rewards - 1, 0, itemType);
auto l = getListForType(typeId);
if(!l.empty())
{
auto itemCurr = new QTableWidgetItem(getListForType(typeId)[listId]);
itemCurr->setData(Qt::UserRole, listId);
ui->rewardsTable->setItem(rewards - 1, 1, itemCurr);
}
QString am = QString::number(amount);
switch(ui->rewardType->currentIndex())
{
case 6:
if(amount <= 1)
am = "Basic";
if(amount == 2)
am = "Advanced";
if(amount >= 3)
am = "Expert";
break;
case 7:
case 8:
am = "";
amount = 1;
break;
}
auto itemCount = new QTableWidgetItem(am);
itemCount->setData(Qt::UserRole, amount);
ui->rewardsTable->setItem(rewards - 1, 2, itemCount);
}
void RewardsWidget::on_buttonAdd_clicked()
{
addReward(ui->rewardType->currentIndex(), ui->rewardList->currentIndex(), ui->rewardAmount->text().toInt());
}
void RewardsWidget::on_buttonRemove_clicked()
{
ui->rewardsTable->removeRow(ui->rewardsTable->currentRow());
--rewards;
}
void RewardsWidget::on_buttonClear_clicked()
{
ui->rewardsTable->clear();
rewards = 0;
}
void RewardsWidget::on_rewardsTable_itemSelectionChanged()
{
/*auto type = ui->rewardsTable->item(ui->rewardsTable->currentRow(), 0);
ui->rewardType->setCurrentIndex(type->data(Qt::UserRole).toInt());
ui->rewardType->activated(ui->rewardType->currentIndex());
type = ui->rewardsTable->item(ui->rewardsTable->currentRow(), 1);
ui->rewardList->setCurrentIndex(type->data(Qt::UserRole).toInt());
ui->rewardList->activated(ui->rewardList->currentIndex());
type = ui->rewardsTable->item(ui->rewardsTable->currentRow(), 2);
ui->rewardAmount->setText(QString::number(type->data(Qt::UserRole).toInt()));*/
}
RewardsPandoraDelegate::RewardsPandoraDelegate(const CMap & m, CGPandoraBox & t): map(m), pandora(t), QStyledItemDelegate()
{
}
QWidget * RewardsPandoraDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
return new RewardsWidget(map, pandora, parent);
}
void RewardsPandoraDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
if(auto * ed = qobject_cast<RewardsWidget *>(editor))
{
ed->obtainData();
}
else
{
QStyledItemDelegate::setEditorData(editor, index);
}
}
void RewardsPandoraDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
if(auto * ed = qobject_cast<RewardsWidget *>(editor))
{
auto isArmed = ed->commitChanges();
model->setData(index, "dummy");
if(isArmed)
model->setData(index, "HAS REWARD");
else
model->setData(index, "");
}
else
{
QStyledItemDelegate::setModelData(editor, model, index);
}
}

View File

@@ -0,0 +1,79 @@
#ifndef REWARDSWIDGET_H
#define REWARDSWIDGET_H
#include <QDialog>
#include "../lib/mapObjects/CGPandoraBox.h"
#include "../lib/mapping/CMap.h"
namespace Ui {
class RewardsWidget;
}
/*
ui32 gainedExp;
si32 manaDiff; //amount of gained / lost mana
si32 moraleDiff; //morale modifier
si32 luckDiff; //luck modifier
TResources resources;//gained / lost resources
std::vector<si32> primskills;//gained / lost prim skills
std::vector<SecondarySkill> abilities; //gained abilities
std::vector<si32> abilityLevels; //levels of gained abilities
std::vector<ArtifactID> artifacts; //gained artifacts
std::vector<SpellID> spells; //gained spells
CCreatureSet creatures; //gained creatures
*/
const std::array<std::string, 10> rewardTypes{"Experience", "Mana", "Morale", "Luck", "Resource", "Primary skill", "Secondary skill", "Artifact", "Spell", "Creature"};
class RewardsWidget : public QDialog
{
Q_OBJECT
public:
explicit RewardsWidget(const CMap &, CGPandoraBox &, QWidget *parent = nullptr);
~RewardsWidget();
void obtainData();
bool commitChanges();
private slots:
void on_rewardType_activated(int index);
void on_rewardList_activated(int index);
void on_buttonAdd_clicked();
void on_buttonRemove_clicked();
void on_buttonClear_clicked();
void on_rewardsTable_itemSelectionChanged();
private:
void addReward(int typeId, int listId, int amount);
QList<QString> getListForType(int typeId);
Ui::RewardsWidget *ui;
CGPandoraBox * pandora;
const CMap & map;
int rewards = 0;
};
class RewardsPandoraDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
using QStyledItemDelegate::QStyledItemDelegate;
RewardsPandoraDelegate(const CMap &, CGPandoraBox &);
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
private:
CGPandoraBox & pandora;
const CMap & map;
};
#endif // REWARDSWIDGET_H

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RewardsWidget</class>
<widget class="QDialog" name="RewardsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>645</width>
<height>335</height>
</rect>
</property>
<property name="windowTitle">
<string>Rewards</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QPushButton" name="buttonRemove">
<property name="text">
<string>Remove selected</string>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QLineEdit" name="rewardAmount">
<property name="maximumSize">
<size>
<width>80</width>
<height>16777215</height>
</size>
</property>
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="buttonClear">
<property name="text">
<string>Delete all</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="buttonAdd">
<property name="text">
<string>Add or change</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="5">
<widget class="QTableWidget" name="rewardsTable">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="columnCount">
<number>3</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<column/>
<column/>
<column/>
</widget>
</item>
<item row="2" column="1" colspan="3">
<widget class="QComboBox" name="rewardList"/>
</item>
<item row="2" column="0">
<widget class="QComboBox" name="rewardType"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,243 @@
#include "townbulidingswidget.h"
#include "ui_townbulidingswidget.h"
#include "../lib/CModHandler.h"
#include "../lib/CGeneralTextHandler.h"
std::string defaultBuildingIdConversion(BuildingID bId)
{
switch(bId)
{
case BuildingID::DEFAULT: return "DEFAULT";
case BuildingID::MAGES_GUILD_1: return "MAGES_GUILD_1";
case BuildingID::MAGES_GUILD_2: return "MAGES_GUILD_2";
case BuildingID::MAGES_GUILD_3: return "MAGES_GUILD_3";
case BuildingID::MAGES_GUILD_4: return "MAGES_GUILD_4";
case BuildingID::MAGES_GUILD_5: return "MAGES_GUILD_5";
case BuildingID::TAVERN: return "TAVERN";
case BuildingID::SHIPYARD: return "SHIPYARD";
case BuildingID::FORT: return "FORT";
case BuildingID::CITADEL: return "CITADEL";
case BuildingID::CASTLE: return "CASTLE";
case BuildingID::VILLAGE_HALL: return "VILLAGE_HALL";
case BuildingID::TOWN_HALL: return "TOWN_HALL";
case BuildingID::CITY_HALL: return "CITY_HALL";
case BuildingID::CAPITOL: return "CAPITOL";
case BuildingID::MARKETPLACE: return "MARKETPLACE";
case BuildingID::RESOURCE_SILO: return "RESOURCE_SILO";
case BuildingID::BLACKSMITH: return "BLACKSMITH";
case BuildingID::SPECIAL_1: return "SPECIAL_1";
case BuildingID::SPECIAL_2: return "SPECIAL_2";
case BuildingID::SPECIAL_3: return "SPECIAL_3";
case BuildingID::SPECIAL_4: return "SPECIAL_4";
case BuildingID::HORDE_1: return "HORDE_1";
case BuildingID::HORDE_1_UPGR: return "HORDE_1_UPGR";
case BuildingID::HORDE_2: return "HORDE_2";
case BuildingID::HORDE_2_UPGR: return "HORDE_2_UPGR";
case BuildingID::SHIP: return "SHIP";
case BuildingID::GRAIL: return "GRAIL";
case BuildingID::EXTRA_TOWN_HALL: return "EXTRA_TOWN_HALL";
case BuildingID::EXTRA_CITY_HALL: return "EXTRA_CITY_HALL";
case BuildingID::EXTRA_CAPITOL: return "EXTRA_CAPITOL";
case BuildingID::DWELL_LVL_1: return "DWELL_LVL_1";
case BuildingID::DWELL_LVL_2: return "DWELL_LVL_2";
case BuildingID::DWELL_LVL_3: return "DWELL_LVL_3";
case BuildingID::DWELL_LVL_4: return "DWELL_LVL_4";
case BuildingID::DWELL_LVL_5: return "DWELL_LVL_5";
case BuildingID::DWELL_LVL_6: return "DWELL_LVL_6";
case BuildingID::DWELL_LVL_7: return "DWELL_LVL_7";
case BuildingID::DWELL_LVL_1_UP: return "DWELL_LVL_1_UP";
case BuildingID::DWELL_LVL_2_UP: return "DWELL_LVL_2_UP";
case BuildingID::DWELL_LVL_3_UP: return "DWELL_LVL_3_UP";
case BuildingID::DWELL_LVL_4_UP: return "DWELL_LVL_4_UP";
case BuildingID::DWELL_LVL_5_UP: return "DWELL_LVL_5_UP";
case BuildingID::DWELL_LVL_6_UP: return "DWELL_LVL_6_UP";
case BuildingID::DWELL_LVL_7_UP: return "DWELL_LVL_7_UP";
default:
return "UNKNOWN";
}
}
TownBulidingsWidget::TownBulidingsWidget(CGTownInstance & t, QWidget *parent) :
town(t),
QDialog(parent),
ui(new Ui::TownBulidingsWidget)
{
ui->setupUi(this);
ui->treeView->setModel(&model);
//ui->treeView->setColumnCount(3);
model.setHorizontalHeaderLabels(QStringList() << QStringLiteral("Type") << QStringLiteral("Enabled") << QStringLiteral("Built"));
//setAttribute(Qt::WA_DeleteOnClose);
}
TownBulidingsWidget::~TownBulidingsWidget()
{
delete ui;
}
QStandardItem * TownBulidingsWidget::addBuilding(const CTown & ctown, int bId, std::set<si32> & remaining)
{
BuildingID buildingId(bId);
const CBuilding * building = ctown.buildings.at(buildingId);
if(!building)
{
remaining.erase(bId);
return nullptr;
}
QString name = tr(building->Name().c_str());
if(name.isEmpty())
name = QString::fromStdString(defaultBuildingIdConversion(buildingId));
QList<QStandardItem *> checks;
checks << new QStandardItem(name);
checks.back()->setData(bId, Qt::UserRole);
checks << new QStandardItem;
checks.back()->setCheckable(true);
checks.back()->setCheckState(town.forbiddenBuildings.count(buildingId) ? Qt::Unchecked : Qt::Checked);
checks.back()->setData(bId, Qt::UserRole);
checks << new QStandardItem;
checks.back()->setCheckable(true);
checks.back()->setCheckState(town.builtBuildings.count(buildingId) ? Qt::Checked : Qt::Unchecked);
checks.back()->setData(bId, Qt::UserRole);
if(building->getBase() == buildingId)
{
model.appendRow(checks);
}
else
{
QStandardItem * parent = nullptr;
std::vector<QModelIndex> stack;
stack.push_back(QModelIndex());
while(!parent && !stack.empty())
{
auto pindex = stack.back();
stack.pop_back();
for(int i = 0; i < model.rowCount(pindex); ++i)
{
QModelIndex index = model.index(i, 0, pindex);
if(building->upgrade == model.itemFromIndex(index)->data(Qt::UserRole).toInt())
{
parent = model.itemFromIndex(index);
break;
}
if(model.hasChildren(index))
stack.push_back(index);
}
}
if(!parent)
parent = addBuilding(ctown, building->upgrade.getNum(), remaining);
if(!parent)
{
remaining.erase(bId);
return nullptr;
}
parent->appendRow(checks);
}
remaining.erase(bId);
return checks.front();
}
void TownBulidingsWidget::addBuildings(const CTown & ctown)
{
auto buildings = ctown.getAllBuildings();
while(!buildings.empty())
{
addBuilding(ctown, *buildings.begin(), buildings);
}
ui->treeView->resizeColumnToContents(0);
ui->treeView->resizeColumnToContents(1);
ui->treeView->resizeColumnToContents(2);
}
std::set<BuildingID> TownBulidingsWidget::getForbiddenBuildings()
{
std::set<BuildingID> result;
for(int i = 0; i < model.rowCount(); ++i)
{
if(auto * item = model.item(i, 1))
if(item->checkState() == Qt::Unchecked)
result.emplace(item->data(Qt::UserRole).toInt());
}
return result;
}
std::set<BuildingID> TownBulidingsWidget::getBuiltBuildings()
{
std::set<BuildingID> result;
for(int i = 0; i < model.rowCount(); ++i)
{
if(auto * item = model.item(i, 2))
if(item->checkState() == Qt::Checked)
result.emplace(item->data(Qt::UserRole).toInt());
}
return result;
}
void TownBulidingsWidget::on_treeView_expanded(const QModelIndex &index)
{
ui->treeView->resizeColumnToContents(0);
}
void TownBulidingsWidget::on_treeView_collapsed(const QModelIndex &index)
{
ui->treeView->resizeColumnToContents(0);
}
TownBuildingsDelegate::TownBuildingsDelegate(CGTownInstance & t): town(t), QStyledItemDelegate()
{
}
QWidget * TownBuildingsDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
return new TownBulidingsWidget(town, parent);
}
void TownBuildingsDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
if(auto * ed = qobject_cast<TownBulidingsWidget *>(editor))
{
auto * ctown = town.town;
if(!ctown)
ctown = VLC->townh->randomTown;
if(!ctown)
throw std::runtime_error("No Town defined for type selected");
ed->addBuildings(*ctown);
}
else
{
QStyledItemDelegate::setEditorData(editor, index);
}
}
void TownBuildingsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
if(auto * ed = qobject_cast<TownBulidingsWidget *>(editor))
{
town.forbiddenBuildings = ed->getForbiddenBuildings();
town.builtBuildings = ed->getBuiltBuildings();
auto data = model->itemData(index);
model->setData(index, "dummy");
model->setItemData(index, data); //dummy change to trigger signal
}
else
{
QStyledItemDelegate::setModelData(editor, model, index);
}
}

View File

@@ -0,0 +1,53 @@
#ifndef TOWNBULIDINGSWIDGET_H
#define TOWNBULIDINGSWIDGET_H
#include <QDialog>
#include "../lib/mapObjects/CGTownInstance.h"
namespace Ui {
class TownBulidingsWidget;
}
class TownBulidingsWidget : public QDialog
{
Q_OBJECT
QStandardItem * addBuilding(const CTown & ctown, int bId, std::set<si32> & remaining);
public:
explicit TownBulidingsWidget(CGTownInstance &, QWidget *parent = nullptr);
~TownBulidingsWidget();
void addBuildings(const CTown & ctown);
std::set<BuildingID> getForbiddenBuildings();
std::set<BuildingID> getBuiltBuildings();
private slots:
void on_treeView_expanded(const QModelIndex &index);
void on_treeView_collapsed(const QModelIndex &index);
private:
Ui::TownBulidingsWidget *ui;
CGTownInstance & town;
mutable QStandardItemModel model;
};
class TownBuildingsDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
using QStyledItemDelegate::QStyledItemDelegate;
TownBuildingsDelegate(CGTownInstance &);
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
private:
CGTownInstance & town;
//std::set<BuildingID>
};
#endif // TOWNBULIDINGSWIDGET_H

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TownBulidingsWidget</class>
<widget class="QDialog" name="TownBulidingsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>280</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>480</width>
<height>280</height>
</size>
</property>
<property name="windowTitle">
<string>Buildings</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTreeView" name="treeView">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<attribute name="headerCascadingSectionResizes">
<bool>true</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

125
mapeditor/jsonutils.cpp Normal file
View File

@@ -0,0 +1,125 @@
/*
* jsonutils.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 "jsonutils.h"
#include "../lib/filesystem/FileStream.h"
static QVariantMap JsonToMap(const JsonMap & json)
{
QVariantMap map;
for(auto & entry : json)
{
map.insert(QString::fromUtf8(entry.first.c_str()), JsonUtils::toVariant(entry.second));
}
return map;
}
static QVariantList JsonToList(const JsonVector & json)
{
QVariantList list;
for(auto & entry : json)
{
list.push_back(JsonUtils::toVariant(entry));
}
return list;
}
static JsonVector VariantToList(QVariantList variant)
{
JsonVector vector;
for(auto & entry : variant)
{
vector.push_back(JsonUtils::toJson(entry));
}
return vector;
}
static JsonMap VariantToMap(QVariantMap variant)
{
JsonMap map;
for(auto & entry : variant.toStdMap())
{
map[entry.first.toUtf8().data()] = JsonUtils::toJson(entry.second);
}
return map;
}
namespace JsonUtils
{
QVariant toVariant(const JsonNode & node)
{
switch(node.getType())
{
break;
case JsonNode::JsonType::DATA_NULL:
return QVariant();
break;
case JsonNode::JsonType::DATA_BOOL:
return QVariant(node.Bool());
break;
case JsonNode::JsonType::DATA_FLOAT:
return QVariant(node.Float());
break;
case JsonNode::JsonType::DATA_STRING:
return QVariant(QString::fromUtf8(node.String().c_str()));
break;
case JsonNode::JsonType::DATA_VECTOR:
return JsonToList(node.Vector());
break;
case JsonNode::JsonType::DATA_STRUCT:
return JsonToMap(node.Struct());
}
return QVariant();
}
QVariant JsonFromFile(QString filename)
{
QFile file(filename);
file.open(QFile::ReadOnly);
auto data = file.readAll();
if(data.size() == 0)
{
logGlobal->error("Failed to open file %s", filename.toUtf8().data());
return QVariant();
}
else
{
JsonNode node(data.data(), data.size());
return toVariant(node);
}
}
JsonNode toJson(QVariant object)
{
JsonNode ret;
if(object.canConvert<QVariantMap>())
ret.Struct() = VariantToMap(object.toMap());
else if(object.canConvert<QVariantList>())
ret.Vector() = VariantToList(object.toList());
else if(object.userType() == QMetaType::QString)
ret.String() = object.toString().toUtf8().data();
else if(object.userType() == QMetaType::Bool)
ret.Bool() = object.toBool();
else if(object.canConvert<double>())
ret.Float() = object.toFloat();
return ret;
}
void JsonToFile(QString filename, QVariant object)
{
FileStream file(qstringToPath(filename), std::ios::out | std::ios_base::binary);
file << toJson(object).toJson();
}
}

22
mapeditor/jsonutils.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* jsonutils.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 <QVariant>
#include "../lib/JsonNode.h"
namespace JsonUtils
{
QVariant toVariant(const JsonNode & node);
QVariant JsonFromFile(QString filename);
JsonNode toJson(QVariant object);
void JsonToFile(QString filename, QVariant object);
}

View File

@@ -0,0 +1,36 @@
/*
* launcherdirs.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 "launcherdirs.h"
#include "../lib/VCMIDirs.h"
static CLauncherDirs launcherDirsGlobal;
CLauncherDirs::CLauncherDirs()
{
QDir().mkdir(downloadsPath());
QDir().mkdir(modsPath());
}
CLauncherDirs & CLauncherDirs::get()
{
return launcherDirsGlobal;
}
QString CLauncherDirs::downloadsPath()
{
return pathToQString(VCMIDirs::get().userCachePath() / "downloads");
}
QString CLauncherDirs::modsPath()
{
return pathToQString(VCMIDirs::get().userDataPath() / "Mods");
}

22
mapeditor/launcherdirs.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* launcherdirs.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
/// similar to lib/VCMIDirs, controls where all launcher-related data will be stored
class CLauncherDirs
{
public:
CLauncherDirs();
static CLauncherDirs & get();
QString downloadsPath();
QString modsPath();
};

19
mapeditor/main.cpp Normal file
View File

@@ -0,0 +1,19 @@
/*
* main.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 <QApplication>
#include "StdInc.h"
#include "mainwindow.h"
int main(int argc, char * argv[])
{
QApplication vcmieditor(argc, argv);
MainWindow mainWindow;
return vcmieditor.exec();
}

1118
mapeditor/mainwindow.cpp Normal file

File diff suppressed because it is too large Load Diff

148
mapeditor/mainwindow.h Normal file
View File

@@ -0,0 +1,148 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsScene>
#include <QStandardItemModel>
#include "mapcontroller.h"
#include "../lib/Terrain.h"
class CMap;
class ObjectBrowser;
class CGObjectInstance;
namespace Ui
{
class MainWindow;
const QString teamName = "VCMI Team";
const QString appName = "VCMI Map Editor";
}
class MainWindow : public QMainWindow
{
Q_OBJECT
const QString mainWindowSizeSetting = "MainWindow/Size";
const QString mainWindowPositionSetting = "MainWindow/Position";
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void initializeMap(bool isNew);
void saveMap();
bool openMap(const QString &);
MapView * mapView();
void loadObjectsTree();
void setStatusMessage(const QString & status);
int getMapLevel() const {return mapLevel;}
MapController controller;
private slots:
void on_actionOpen_triggered();
void on_actionSave_as_triggered();
void on_actionNew_triggered();
void on_actionLevel_triggered();
void on_actionSave_triggered();
void on_actionErase_triggered();
void on_actionUndo_triggered();
void on_actionRedo_triggered();
void on_actionPass_triggered(bool checked);
void on_actionGrid_triggered(bool checked);
void on_toolBrush_clicked(bool checked);
void on_toolArea_clicked(bool checked);
void terrainButtonClicked(Terrain terrain);
void roadOrRiverButtonClicked(std::string type, bool isRoad);
void on_toolErase_clicked();
void on_treeView_activated(const QModelIndex &index);
void on_terrainFilterCombo_currentTextChanged(const QString &arg1);
void on_filter_textChanged(const QString &arg1);
void on_actionFill_triggered();
void on_toolBrush2_clicked(bool checked);
void on_toolBrush4_clicked(bool checked);
void on_inspectorWidget_itemChanged(QTableWidgetItem *item);
void on_actionMapSettings_triggered();
void on_actionPlayers_settings_triggered();
void on_actionValidate_triggered();
void on_actionUpdate_appearance_triggered();
void on_actionRecreate_obstacles_triggered();
void switchDefaultPlayer(const PlayerColor &);
public slots:
void treeViewSelected(const QModelIndex &selected, const QModelIndex &deselected);
void loadInspector(CGObjectInstance * obj, bool switchTab);
void mapChanged();
void enableUndo(bool enable);
void enableRedo(bool enable);
void onSelectionMade(int level, bool anythingSelected);
void onPlayersChanged();
void displayStatus(const QString& message, int timeout = 2000);
private:
void preparePreview(const QModelIndex &index, bool createNew);
void addGroupIntoCatalog(const std::string & groupName, bool staticOnly);
void addGroupIntoCatalog(const std::string & groupName, bool useCustomName, bool staticOnly, int ID);
QAction * getActionPlayer(const PlayerColor &);
void changeBrushState(int idx);
void setTitle();
void closeEvent(QCloseEvent *event) override;
bool getAnswerAboutUnsavedChanges();
void loadUserSettings();
void saveUserSettings();
private:
Ui::MainWindow *ui;
ObjectBrowser * objectBrowser = nullptr;
QGraphicsScene * scenePreview;
QString filename;
bool unsaved = false;
QStandardItemModel objectsModel;
int mapLevel = 0;
std::set<int> catalog;
};
#endif // MAINWINDOW_H

1120
mapeditor/mainwindow.ui Normal file

File diff suppressed because it is too large Load Diff

466
mapeditor/mapcontroller.cpp Normal file
View File

@@ -0,0 +1,466 @@
#include "mapcontroller.h"
#include "../lib/GameConstants.h"
#include "../lib/mapping/CMapService.h"
#include "../lib/mapping/CMap.h"
#include "../lib/mapping/CMapEditManager.h"
#include "../lib/Terrain.h"
#include "../lib/mapObjects/CObjectClassesHandler.h"
#include "../lib/rmg/ObstaclePlacer.h"
#include "../lib/CSkillHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CHeroHandler.h"
#include "mapview.h"
#include "scenelayer.h"
#include "maphandler.h"
#include "mainwindow.h"
#include "inspector/inspector.h"
MapController::MapController(MainWindow * m): main(m)
{
_scenes[0].reset(new MapScene(0));
_scenes[1].reset(new MapScene(1));
_miniscenes[0].reset(new MinimapScene(0));
_miniscenes[1].reset(new MinimapScene(1));
connectScenes();
}
void MapController::connectScenes()
{
for (int level = 0; level <= 1; level++)
{
//selections for both layers will be handled separately
QObject::connect(_scenes[level].get(), &MapScene::selected, [this, level](bool anythingSelected)
{
main->onSelectionMade(level, anythingSelected);
});
}
}
MapController::~MapController()
{
}
const std::unique_ptr<CMap> & MapController::getMapUniquePtr() const
{
return _map;
}
CMap * MapController::map()
{
return _map.get();
}
MapHandler * MapController::mapHandler()
{
return _mapHandler.get();
}
MapScene * MapController::scene(int level)
{
return _scenes[level].get();
}
MinimapScene * MapController::miniScene(int level)
{
return _miniscenes[level].get();
}
void MapController::repairMap()
{
//fix owners for objects
for(auto obj : _map->objects)
{
if(obj->getOwner() == PlayerColor::UNFLAGGABLE)
{
if(dynamic_cast<CGMine*>(obj.get()) ||
dynamic_cast<CGDwelling*>(obj.get()) ||
dynamic_cast<CGTownInstance*>(obj.get()) ||
dynamic_cast<CGGarrison*>(obj.get()) ||
dynamic_cast<CGShipyard*>(obj.get()) ||
dynamic_cast<CGHeroInstance*>(obj.get()))
obj->tempOwner = PlayerColor::NEUTRAL;
}
//fix hero instance
if(auto * nih = dynamic_cast<CGHeroInstance*>(obj.get()))
{
auto type = VLC->heroh->objects[nih->subID];
if(nih->ID == Obj::HERO)
nih->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
//fix spells
if(nih->spellbookContainsSpell(SpellID::PRESET))
{
nih->removeSpellFromSpellbook(SpellID::PRESET);
}
else
{
for(auto spellID : type->spells)
nih->addSpellToSpellbook(spellID);
}
//fix portrait
if(nih->portrait < 0 || nih->portrait == 255)
nih->portrait = type->imageIndex;
}
//fix town instance
if(auto * tnh = dynamic_cast<CGTownInstance*>(obj.get()))
{
if(tnh->getTown())
{
vstd::erase_if(tnh->builtBuildings, [tnh](BuildingID bid)
{
return !tnh->getTown()->buildings.count(bid);
});
vstd::erase_if(tnh->forbiddenBuildings, [tnh](BuildingID bid)
{
return !tnh->getTown()->buildings.count(bid);
});
}
}
}
//there might be extra skills, arts and spells not imported from map
if(VLC->skillh->getDefaultAllowed().size() > map()->allowedAbilities.size())
{
for(int i = map()->allowedAbilities.size(); i < VLC->skillh->getDefaultAllowed().size(); ++i)
map()->allowedAbilities.push_back(false);
}
if(VLC->arth->getDefaultAllowed().size() > map()->allowedArtifact.size())
{
for(int i = map()->allowedArtifact.size(); i < VLC->arth->getDefaultAllowed().size(); ++i)
map()->allowedArtifact.push_back(false);
}
if(VLC->spellh->getDefaultAllowed().size() > map()->allowedSpell.size())
{
for(int i = map()->allowedSpell.size(); i < VLC->spellh->getDefaultAllowed().size(); ++i)
map()->allowedSpell.push_back(false);
}
}
void MapController::setMap(std::unique_ptr<CMap> cmap)
{
_map = std::move(cmap);
repairMap();
_scenes[0].reset(new MapScene(0));
_scenes[1].reset(new MapScene(1));
_miniscenes[0].reset(new MinimapScene(0));
_miniscenes[1].reset(new MinimapScene(1));
resetMapHandler();
sceneForceUpdate();
connectScenes();
_map->getEditManager()->getUndoManager().setUndoCallback([this](bool allowUndo, bool allowRedo)
{
main->enableUndo(allowUndo);
main->enableRedo(allowRedo);
}
);
}
void MapController::sceneForceUpdate()
{
_scenes[0]->updateViews();
_miniscenes[0]->updateViews();
if(_map->twoLevel)
{
_scenes[1]->updateViews();
_miniscenes[1]->updateViews();
}
}
void MapController::sceneForceUpdate(int level)
{
_scenes[level]->updateViews();
_miniscenes[level]->updateViews();
}
void MapController::resetMapHandler()
{
if(!_mapHandler)
_mapHandler.reset(new MapHandler());
_mapHandler->reset(map());
_scenes[0]->initialize(*this);
_scenes[1]->initialize(*this);
_miniscenes[0]->initialize(*this);
_miniscenes[1]->initialize(*this);
}
void MapController::commitTerrainChange(int level, const Terrain & terrain)
{
std::vector<int3> v(_scenes[level]->selectionTerrainView.selection().begin(),
_scenes[level]->selectionTerrainView.selection().end());
if(v.empty())
return;
_scenes[level]->selectionTerrainView.clear();
_scenes[level]->selectionTerrainView.draw();
_map->getEditManager()->getTerrainSelection().setSelection(v);
_map->getEditManager()->drawTerrain(terrain, &CRandomGenerator::getDefault());
for(auto & t : v)
_scenes[level]->terrainView.setDirty(t);
_scenes[level]->terrainView.draw();
_miniscenes[level]->updateViews();
main->mapChanged();
}
void MapController::commitRoadOrRiverChange(int level, const std::string & type, bool isRoad)
{
std::vector<int3> v(_scenes[level]->selectionTerrainView.selection().begin(),
_scenes[level]->selectionTerrainView.selection().end());
if(v.empty())
return;
_scenes[level]->selectionTerrainView.clear();
_scenes[level]->selectionTerrainView.draw();
_map->getEditManager()->getTerrainSelection().setSelection(v);
if(isRoad)
_map->getEditManager()->drawRoad(type, &CRandomGenerator::getDefault());
else
_map->getEditManager()->drawRiver(type, &CRandomGenerator::getDefault());
for(auto & t : v)
_scenes[level]->terrainView.setDirty(t);
_scenes[level]->terrainView.draw();
_miniscenes[level]->updateViews();
main->mapChanged();
}
void MapController::commitObjectErase(int level)
{
auto selectedObjects = _scenes[level]->selectionObjectsView.getSelection();
if (selectedObjects.size() > 1)
{
//mass erase => undo in one operation
_map->getEditManager()->removeObjects(selectedObjects);
}
else if (selectedObjects.size() == 1)
{
_map->getEditManager()->removeObject(*selectedObjects.begin());
}
else //nothing to erase - shouldn't be here
{
return;
}
for (auto obj : selectedObjects)
{
//invalidate tiles under objects
_mapHandler->invalidate(_mapHandler->getTilesUnderObject(obj));
}
_scenes[level]->selectionObjectsView.clear();
_scenes[level]->objectsView.draw();
_scenes[level]->selectionObjectsView.draw();
_scenes[level]->passabilityView.update();
_miniscenes[level]->updateViews();
main->mapChanged();
}
bool MapController::discardObject(int level) const
{
_scenes[level]->selectionObjectsView.clear();
if(_scenes[level]->selectionObjectsView.newObject)
{
delete _scenes[level]->selectionObjectsView.newObject;
_scenes[level]->selectionObjectsView.newObject = nullptr;
_scenes[level]->selectionObjectsView.shift = QPoint(0, 0);
_scenes[level]->selectionObjectsView.selectionMode = 0;
_scenes[level]->selectionObjectsView.draw();
return true;
}
return false;
}
void MapController::createObject(int level, CGObjectInstance * obj) const
{
_scenes[level]->selectionObjectsView.newObject = obj;
_scenes[level]->selectionObjectsView.selectionMode = 2;
_scenes[level]->selectionObjectsView.draw();
}
void MapController::commitObstacleFill(int level)
{
auto selection = _scenes[level]->selectionTerrainView.selection();
if(selection.empty())
return;
//split by zones
std::map<Terrain, ObstacleProxy> terrainSelected;
for(auto & t : selection)
{
auto tl = _map->getTile(t);
if(tl.blocked || tl.visitable)
continue;
terrainSelected[tl.terType].blockedArea.add(t);
}
for(auto & sel : terrainSelected)
{
sel.second.collectPossibleObstacles(sel.first);
sel.second.placeObstacles(_map.get(), CRandomGenerator::getDefault());
}
_mapHandler->invalidateObjects();
_scenes[level]->selectionTerrainView.clear();
_scenes[level]->selectionTerrainView.draw();
_scenes[level]->objectsView.draw();
_scenes[level]->passabilityView.update();
_miniscenes[level]->updateViews();
main->mapChanged();
}
void MapController::commitObjectChange(int level)
{
//for( auto * o : _scenes[level]->selectionObjectsView.getSelection())
//_mapHandler->invalidate(o);
_scenes[level]->objectsView.draw();
_scenes[level]->selectionObjectsView.draw();
_scenes[level]->passabilityView.update();
_miniscenes[level]->updateViews();
main->mapChanged();
}
void MapController::commitChangeWithoutRedraw()
{
//DO NOT REDRAW
main->mapChanged();
}
void MapController::commitObjectShift(int level)
{
auto shift = _scenes[level]->selectionObjectsView.shift;
bool makeShift = !shift.isNull();
if(makeShift)
{
for(auto * obj : _scenes[level]->selectionObjectsView.getSelection())
{
int3 pos = obj->pos;
pos.z = level;
pos.x += shift.x(); pos.y += shift.y();
auto prevPositions = _mapHandler->getTilesUnderObject(obj);
_map->getEditManager()->moveObject(obj, pos);
_mapHandler->invalidate(prevPositions);
_mapHandler->invalidate(obj);
}
}
_scenes[level]->selectionObjectsView.newObject = nullptr;
_scenes[level]->selectionObjectsView.shift = QPoint(0, 0);
_scenes[level]->selectionObjectsView.selectionMode = 0;
if(makeShift)
{
_scenes[level]->objectsView.draw();
_scenes[level]->selectionObjectsView.draw();
_scenes[level]->passabilityView.update();
_miniscenes[level]->updateViews();
main->mapChanged();
}
}
void MapController::commitObjectCreate(int level)
{
auto * newObj = _scenes[level]->selectionObjectsView.newObject;
if(!newObj)
return;
auto shift = _scenes[level]->selectionObjectsView.shift;
int3 pos = newObj->pos;
pos.z = level;
pos.x += shift.x(); pos.y += shift.y();
newObj->pos = pos;
Initializer init(newObj, defaultPlayer);
_map->getEditManager()->insertObject(newObj);
_mapHandler->invalidate(newObj);
_scenes[level]->selectionObjectsView.newObject = nullptr;
_scenes[level]->selectionObjectsView.shift = QPoint(0, 0);
_scenes[level]->selectionObjectsView.selectionMode = 0;
_scenes[level]->objectsView.draw();
_scenes[level]->selectionObjectsView.draw();
_scenes[level]->passabilityView.update();
_miniscenes[level]->updateViews();
main->mapChanged();
}
bool MapController::canPlaceObject(int level, CGObjectInstance * newObj, QString & error) const
{
//need this because of possible limits
auto rmgInfo = VLC->objtypeh->getHandlerFor(newObj->ID, newObj->subID)->getRMGInfo();
//find all objects of such type
int objCounter = 0;
for(auto o : _map->objects)
{
if(o->ID == newObj->ID && o->subID == newObj->subID)
{
++objCounter;
}
}
if((rmgInfo.mapLimit && objCounter >= rmgInfo.mapLimit)
|| (newObj->ID == Obj::GRAIL && objCounter >= 1)) //special case for grail
{
auto typeName = QString::fromStdString(newObj->typeName);
auto subTypeName = QString::fromStdString(newObj->subTypeName);
error = QString("Reached map limit for object %1 - %2").arg(typeName, subTypeName);
return false; //maplimit reached
}
if(defaultPlayer == PlayerColor::NEUTRAL && (newObj->ID == Obj::HERO || newObj->ID == Obj::RANDOM_HERO))
{
error = "Hero cannot be created as NEUTRAL";
return false;
}
if(defaultPlayer != PlayerColor::NEUTRAL && newObj->ID == Obj::PRISON)
{
error = "Prison must be a NEUTRAL";
return false;
}
if(newObj->ID == Obj::ARTIFACT && !_map->allowedArtifact.at(newObj->subID))
{
error = "Artifact is not allowed. Check map settings.";
return false;
}
return true;
}
void MapController::undo()
{
_map->getEditManager()->getUndoManager().undo();
resetMapHandler();
sceneForceUpdate();
main->mapChanged();
}
void MapController::redo()
{
_map->getEditManager()->getUndoManager().redo();
resetMapHandler();
sceneForceUpdate();
main->mapChanged();
}

62
mapeditor/mapcontroller.h Normal file
View File

@@ -0,0 +1,62 @@
#ifndef MAPCONTROLLER_H
#define MAPCONTROLLER_H
#include "maphandler.h"
#include "mapview.h"
#include "../lib/mapping/CMap.h"
#include "../lib/Terrain.h"
class MainWindow;
class MapController
{
public:
MapController(MainWindow *);
MapController(const MapController &) = delete;
MapController(const MapController &&) = delete;
~MapController();
void setMap(std::unique_ptr<CMap>);
void repairMap();
const std::unique_ptr<CMap> & getMapUniquePtr() const; //to be used for map saving
CMap * map();
MapHandler * mapHandler();
MapScene * scene(int level);
MinimapScene * miniScene(int level);
void resetMapHandler();
void sceneForceUpdate();
void sceneForceUpdate(int level);
void commitTerrainChange(int level, const Terrain & terrain);
void commitRoadOrRiverChange(int level, const std::string & type, bool isRoad);
void commitObjectErase(const CGObjectInstance* obj);
void commitObjectErase(int level);
void commitObstacleFill(int level);
void commitChangeWithoutRedraw();
void commitObjectShift(int level);
void commitObjectCreate(int level);
void commitObjectChange(int level);
bool discardObject(int level) const;
void createObject(int level, CGObjectInstance * obj) const;
bool canPlaceObject(int level, CGObjectInstance * obj, QString & error) const;
void undo();
void redo();
PlayerColor defaultPlayer;
private:
std::unique_ptr<CMap> _map;
std::unique_ptr<MapHandler> _mapHandler;
MainWindow * main;
mutable std::array<std::unique_ptr<MapScene>, 2> _scenes;
mutable std::array<std::unique_ptr<MinimapScene>, 2> _miniscenes;
void connectScenes();
};
#endif // MAPCONTROLLER_H

BIN
mapeditor/mapeditor.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

1
mapeditor/mapeditor.rc Normal file
View File

@@ -0,0 +1 @@
IDI_ICON1 ICON "mapeditor.ico"

538
mapeditor/maphandler.cpp Normal file
View File

@@ -0,0 +1,538 @@
#include "StdInc.h"
#include "maphandler.h"
#include "graphics.h"
#include "../lib/mapping/CMap.h"
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/mapObjects/CObjectClassesHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/CModHandler.h"
#include "../lib/mapping/CMap.h"
#include "../lib/GameConstants.h"
#include "../lib/JsonDetail.h"
const int tileSize = 32;
static bool objectBlitOrderSorter(const TileObject & a, const TileObject & b)
{
return MapHandler::compareObjectBlitOrder(a.obj, b.obj);
}
int MapHandler::index(int x, int y, int z) const
{
return z * (sizes.x * sizes.y) + y * sizes.x + x;
}
int MapHandler::index(const int3 & p) const
{
return index(p.x, p.y, p.z);
}
MapHandler::MapHandler()
{
initTerrainGraphics();
logGlobal->info("\tPreparing terrain, roads, rivers, borders");
}
void MapHandler::reset(const CMap * Map)
{
ttiles.clear();
map = Map;
//sizes of terrain
sizes.x = map->width;
sizes.y = map->height;
sizes.z = map->twoLevel ? 2 : 1;
initObjectRects();
logGlobal->info("\tMaking object rects");
}
void MapHandler::initTerrainGraphics()
{
static const std::map<std::string, std::string> ROAD_FILES =
{
{ROAD_NAMES[1], "dirtrd"},
{ROAD_NAMES[2], "gravrd"},
{ROAD_NAMES[3], "cobbrd"}
};
static const std::map<std::string, std::string> 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<std::string, std::string> & files)
{
for(auto & type : files)
{
animation[type.first] = make_unique<Animation>(type.second);
animation[type.first]->preload();
const size_t views = animation[type.first]->size(0);
cache[type.first].resize(views);
for(int j = 0; j < views; j++)
cache[type.first][j] = animation[type.first]->getImage(j);
}
};
std::map<std::string, std::string> 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);
}
void MapHandler::drawTerrainTile(QPainter & painter, int x, int y, int z)
{
auto & tinfo = map->getTile(int3(x, y, z));
ui8 rotation = tinfo.extTileFlags % 4;
if(terrainImages.at(tinfo.terType).size() <= tinfo.terView)
return;
bool hflip = (rotation == 1 || rotation == 3), vflip = (rotation == 2 || rotation == 3);
painter.drawImage(x * tileSize, y * tileSize, terrainImages.at(tinfo.terType)[tinfo.terView]->mirrored(hflip, vflip));
}
void MapHandler::drawRoad(QPainter & painter, int x, int y, int z)
{
auto & tinfo = map->getTile(int3(x, y, z));
auto * tinfoUpper = map->isInTheMap(int3(x, y - 1, z)) ? &map->getTile(int3(x, y - 1, z)) : nullptr;
if (tinfoUpper && tinfoUpper->roadType != ROAD_NAMES[0])
{
QRect source(0, tileSize / 2, tileSize, tileSize / 2);
ui8 rotation = (tinfoUpper->extTileFlags >> 4) % 4;
bool hflip = (rotation == 1 || rotation == 3), vflip = (rotation == 2 || rotation == 3);
if(roadImages.at(tinfoUpper->roadType).size() > tinfoUpper->roadDir)
{
painter.drawImage(QPoint(x * tileSize, y * tileSize), roadImages.at(tinfoUpper->roadType)[tinfoUpper->roadDir]->mirrored(hflip, vflip), source);
}
}
if(tinfo.roadType != ROAD_NAMES[0]) //print road from this tile
{
QRect source(0, 0, tileSize, tileSize / 2);
ui8 rotation = (tinfo.extTileFlags >> 4) % 4;
bool hflip = (rotation == 1 || rotation == 3), vflip = (rotation == 2 || rotation == 3);
if(roadImages.at(tinfo.roadType).size() > tinfo.roadDir)
{
painter.drawImage(QPoint(x * tileSize, y * tileSize + tileSize / 2), roadImages.at(tinfo.roadType)[tinfo.roadDir]->mirrored(hflip, vflip), source);
}
}
}
void MapHandler::drawRiver(QPainter & painter, int x, int y, int z)
{
auto & tinfo = map->getTile(int3(x, y, z));
if(tinfo.riverType == RIVER_NAMES[0])
return;
if(riverImages.at(tinfo.riverType).size() <= tinfo.riverDir)
return;
ui8 rotation = (tinfo.extTileFlags >> 2) % 4;
bool hflip = (rotation == 1 || rotation == 3), vflip = (rotation == 2 || rotation == 3);
painter.drawImage(x * tileSize, y * tileSize, riverImages.at(tinfo.riverType)[tinfo.riverDir]->mirrored(hflip, vflip));
}
void setPlayerColor(QImage * sur, PlayerColor player)
{
if(player == PlayerColor::UNFLAGGABLE)
return;
if(sur->format() == QImage::Format_Indexed8)
{
QRgb color = graphics->neutralColor;
if(player != PlayerColor::NEUTRAL && player < PlayerColor::PLAYER_LIMIT)
color = graphics->playerColors.at(player.getNum());
sur->setColor(5, color);
}
else
logGlobal->warn("Warning, setPlayerColor called on not 8bpp surface!");
}
void MapHandler::initObjectRects()
{
ttiles.resize(sizes.x * sizes.y * sizes.z);
//initializing objects / rects
for(const CGObjectInstance * elem : map->objects)
{
CGObjectInstance *obj = const_cast<CGObjectInstance *>(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)
{
continue;
}
std::shared_ptr<Animation> animation = graphics->getAnimation(obj);
//no animation at all
if(!animation)
continue;
//empty animation
if(animation->size(0) == 0)
continue;
auto image = animation->getImage(0, obj->ID == Obj::HERO ? 2 : 0);
if(!image)
{
//workaound for prisons
image = animation->getImage(0, 0);
if(!image)
continue;
}
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);
QRect cr(image->width() - fx * tileSize - tileSize,
image->height() - fy * tileSize - tileSize,
image->width(),
image->height());
TileObject toAdd(obj, cr);
if( map->isInTheMap(currTile) && // within map
cr.x() + cr.width() > 0 && // image has data on this tile
cr.y() + cr.height() > 0 &&
obj->coveringAt(currTile.x, currTile.y) // object is visible here
)
{
ttiles[index(currTile)].push_back(toAdd);
}
}
}
}
for(auto & tt : ttiles)
{
stable_sort(tt.begin(), tt.end(), objectBlitOrderSorter);
}
}
bool MapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b)
{
if (!a)
return true;
if (!b)
return false;
if (a->appearance->printPriority != b->appearance->printPriority)
return a->appearance->printPriority > b->appearance->printPriority;
if(a->pos.y != b->pos.y)
return a->pos.y < b->pos.y;
if(b->ID==Obj::HERO && a->ID!=Obj::HERO)
return true;
if(b->ID!=Obj::HERO && a->ID==Obj::HERO)
return false;
if(!a->isVisitable() && b->isVisitable())
return true;
if(!b->isVisitable() && a->isVisitable())
return false;
if(a->pos.x < b->pos.x)
return true;
return false;
}
TileObject::TileObject(CGObjectInstance * obj_, QRect rect_)
: obj(obj_),
rect(rect_)
{
}
TileObject::~TileObject()
{
}
std::shared_ptr<QImage> MapHandler::findFlagBitmap(const CGHeroInstance * hero, int anim, const PlayerColor color, int group) const
{
if(!hero || hero->boat)
return std::shared_ptr<QImage>();
return findFlagBitmapInternal(graphics->heroFlagAnimations.at(color.getNum()), anim, group, hero->moveDir, !hero->isStanding);
}
std::shared_ptr<QImage> MapHandler::findFlagBitmapInternal(std::shared_ptr<Animation> animation, int anim, int group, ui8 dir, bool moving) const
{
size_t groupSize = animation->size(group);
if(groupSize == 0)
return nullptr;
if(moving)
return animation->getImage(anim % groupSize, group);
else
return animation->getImage((anim / 4) % groupSize, group);
}
MapHandler::AnimBitmapHolder MapHandler::findObjectBitmap(const CGObjectInstance * obj, int anim, int group) const
{
if(!obj)
return MapHandler::AnimBitmapHolder();
// normal object
std::shared_ptr<Animation> animation = graphics->getAnimation(obj);
size_t groupSize = animation->size(group);
if(groupSize == 0)
return MapHandler::AnimBitmapHolder();
animation->playerColored(obj->tempOwner);
auto bitmap = animation->getImage(anim % groupSize, group);
if(!bitmap)
return MapHandler::AnimBitmapHolder();
setPlayerColor(bitmap.get(), obj->tempOwner);
return MapHandler::AnimBitmapHolder(bitmap);
}
std::vector<TileObject> & MapHandler::getObjects(int x, int y, int z)
{
return ttiles[index(x, y, z)];
}
void MapHandler::drawObjects(QPainter & painter, int x, int y, int z)
{
for(auto & object : getObjects(x, y, z))
{
const CGObjectInstance * obj = object.obj;
if(!obj)
{
logGlobal->error("Stray map object that isn't fading");
return;
}
uint8_t animationFrame = 0;
auto objData = findObjectBitmap(obj, animationFrame, obj->ID == Obj::HERO ? 2 : 0);
if(obj->ID == Obj::HERO && obj->tempOwner.isValidPlayer())
objData.flagBitmap = findFlagBitmap(dynamic_cast<const CGHeroInstance*>(obj), 0, obj->tempOwner, 4);
if(objData.objBitmap)
{
auto pos = obj->getPosition();
painter.drawImage(QPoint(x * tileSize, y * tileSize), *objData.objBitmap, object.rect);
if(objData.flagBitmap)
{
if(x == pos.x && y == pos.y)
painter.drawImage(QPoint((x - 2) * tileSize, (y - 1) * tileSize), *objData.flagBitmap);
}
}
}
}
void MapHandler::drawObject(QPainter & painter, const TileObject & object)
{
const CGObjectInstance * obj = object.obj;
if (!obj)
{
logGlobal->error("Stray map object that isn't fading");
return;
}
uint8_t animationFrame = 0;
auto objData = findObjectBitmap(obj, animationFrame, obj->ID == Obj::HERO ? 2 : 0);
if(obj->ID == Obj::HERO && obj->tempOwner.isValidPlayer())
objData.flagBitmap = findFlagBitmap(dynamic_cast<const CGHeroInstance*>(obj), 0, obj->tempOwner, 0);
if (objData.objBitmap)
{
auto pos = obj->getPosition();
painter.drawImage(pos.x * tileSize - object.rect.x(), pos.y * tileSize - object.rect.y(), *objData.objBitmap);
if (objData.flagBitmap)
{
if(object.rect.x() == pos.x && object.rect.y() == pos.y)
painter.drawImage(pos.x * tileSize - object.rect.x(), pos.y * tileSize - object.rect.y(), *objData.flagBitmap);
}
}
}
void MapHandler::drawObjectAt(QPainter & painter, const CGObjectInstance * obj, int x, int y)
{
if (!obj)
{
logGlobal->error("Stray map object that isn't fading");
return;
}
uint8_t animationFrame = 0;
auto objData = findObjectBitmap(obj, animationFrame, obj->ID == Obj::HERO ? 2 : 0);
std::vector<std::shared_ptr<QImage>> debugFlagImages;
if(obj->ID == Obj::HERO && obj->tempOwner.isValidPlayer())
objData.flagBitmap = findFlagBitmap(dynamic_cast<const CGHeroInstance*>(obj), 0, obj->tempOwner, 4);
if (objData.objBitmap)
{
painter.drawImage(QPoint((x + 1) * 32 - objData.objBitmap->width(), (y + 1) * 32 - objData.objBitmap->height()), *objData.objBitmap);
if (objData.flagBitmap)
painter.drawImage(QPoint((x + 1) * 32 - objData.objBitmap->width(), (y + 1) * 32 - objData.objBitmap->height()), *objData.flagBitmap);
}
}
QRgb MapHandler::getTileColor(int x, int y, int z)
{
// if object at tile is owned - it will be colored as its owner
for(auto & object : getObjects(x, y, z))
{
if(!object.obj->getBlockedPos().count(int3(x, y, z)))
continue;
PlayerColor player = object.obj->getOwner();
if(player == PlayerColor::NEUTRAL)
return graphics->neutralColor;
else
if (player < PlayerColor::PLAYER_LIMIT)
return graphics->playerColors[player.getNum()];
}
// else - use terrain color (blocked version or normal)
auto & tile = map->getTile(int3(x, y, z));
auto color = Terrain::Manager::getInfo(tile.terType).minimapUnblocked;
if (tile.blocked && (!tile.visitable))
color = Terrain::Manager::getInfo(tile.terType).minimapBlocked;
return qRgb(color[0], color[1], color[2]);
}
void MapHandler::drawMinimapTile(QPainter & painter, int x, int y, int z)
{
painter.setPen(getTileColor(x, y, z));
painter.drawPoint(x, y);
}
void MapHandler::invalidate(int x, int y, int z)
{
auto & objects = getObjects(x, y, z);
for(auto obj = objects.begin(); obj != objects.end();)
{
//object was removed
if(std::find(map->objects.begin(), map->objects.end(), obj->obj) == map->objects.end())
{
obj = objects.erase(obj);
continue;
}
//object was moved
auto & pos = obj->obj->pos;
if(pos.z != z || pos.x < x || pos.y < y || pos.x - obj->obj->getWidth() >= x || pos.y - obj->obj->getHeight() >= y)
{
obj = objects.erase(obj);
continue;
}
++obj;
}
stable_sort(objects.begin(), objects.end(), objectBlitOrderSorter);
}
void MapHandler::invalidate(CGObjectInstance * obj)
{
std::shared_ptr<Animation> animation = graphics->getAnimation(obj);
//no animation at all or empty animation
if(!animation || animation->size(0) == 0)
return;
auto image = animation->getImage(0, obj->ID == Obj::HERO ? 2 : 0);
if(!image)
return;
for(int fx=0; fx < obj->getWidth(); ++fx)
{
for(int fy=0; fy < obj->getHeight(); ++fy)
{
//object presented on the tile
int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z);
QRect cr(image->width() - fx * tileSize - tileSize, image->height() - fy * tileSize - tileSize, image->width(), image->height());
if( map->isInTheMap(currTile) && // within map
cr.x() + cr.width() > 0 && // image has data on this tile
cr.y() + cr.height() > 0 &&
obj->coveringAt(currTile.x, currTile.y) // object is visible here
)
{
auto & objects = ttiles[index(currTile)];
bool found = false;
for(auto & o : objects)
{
if(o.obj == obj)
{
o.rect = cr;
found = true;
break;
}
}
if(!found)
objects.emplace_back(obj, cr);
stable_sort(objects.begin(), objects.end(), objectBlitOrderSorter);
}
}
}
}
std::vector<int3> MapHandler::getTilesUnderObject(CGObjectInstance * obj) const
{
std::vector<int3> result;
for(int fx=0; fx < obj->getWidth(); ++fx)
{
for(int fy=0; fy < obj->getHeight(); ++fy)
{
//object presented on the tile
int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z);
if(map->isInTheMap(currTile) && // within map
obj->coveringAt(currTile.x, currTile.y) // object is visible here
)
{
result.push_back(currTile);
}
}
}
return result;
}
void MapHandler::invalidateObjects()
{
for(auto obj : map->objects)
{
invalidate(obj);
}
}
void MapHandler::invalidate(const std::vector<int3> & tiles)
{
for(auto & currTile : tiles)
{
invalidate(currTile.x, currTile.y, currTile.z);
}
}

107
mapeditor/maphandler.h Normal file
View File

@@ -0,0 +1,107 @@
#ifndef MAPHANDLER_H
#define MAPHANDLER_H
#include "StdInc.h"
#include "../lib/mapping/CMap.h"
#include "Animation.h"
#include <QImage>
#include <QPixmap>
#include <QRect>
class CGObjectInstance;
class CGBoat;
class PlayerColor;
struct TileObject
{
CGObjectInstance *obj;
QRect rect;
TileObject(CGObjectInstance *obj_, QRect rect_);
~TileObject();
};
using TileObjects = std::vector<TileObject>; //pointers to objects being on this tile with rects to be easier to blit this tile on screen
class MapHandler
{
public:
struct AnimBitmapHolder
{
std::shared_ptr<QImage> objBitmap; // main object bitmap
std::shared_ptr<QImage> flagBitmap; // flag bitmap for the object (probably only for heroes and boats with heroes)
AnimBitmapHolder(std::shared_ptr<QImage> objBitmap_ = nullptr, std::shared_ptr<QImage> flagBitmap_ = nullptr)
: objBitmap(objBitmap_),
flagBitmap(flagBitmap_)
{}
};
private:
int index(int x, int y, int z) const;
int index(const int3 &) const;
std::shared_ptr<QImage> findFlagBitmapInternal(std::shared_ptr<Animation> animation, int anim, int group, ui8 dir, bool moving) const;
std::shared_ptr<QImage> findFlagBitmap(const CGHeroInstance * obj, int anim, const PlayerColor color, int group) const;
AnimBitmapHolder findObjectBitmap(const CGObjectInstance * obj, int anim, int group = 0) const;
//FIXME: unique_ptr should be enough, but fails to compile in MSVS 2013
typedef std::map<std::string, std::shared_ptr<Animation>> TFlippedAnimations; //[type, rotation]
typedef std::map<std::string, std::vector<std::shared_ptr<QImage>>> 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::vector<TileObjects> ttiles; //informations about map tiles
int3 sizes; //map size (x = width, y = height, z = number of levels)
const CMap * map;
enum class EMapCacheType : char
{
TERRAIN, OBJECTS, ROADS, RIVERS, FOW, HEROES, HERO_FLAGS, FRAME, AFTER_LAST
};
void initObjectRects();
void initTerrainGraphics();
QRgb getTileColor(int x, int y, int z);
public:
MapHandler();
~MapHandler() = default;
void reset(const CMap * Map);
void updateWater();
void drawTerrainTile(QPainter & painter, int x, int y, int z);
/// draws a river segment on current tile
void drawRiver(QPainter & painter, int x, int y, int z);
/// draws a road segment on current tile
void drawRoad(QPainter & painter, int x, int y, int z);
void invalidate(int x, int y, int z); //invalidates all objects in particular tile
void invalidate(CGObjectInstance *); //invalidates object rects
void invalidate(const std::vector<int3> &); //invalidates all tiles
void invalidateObjects(); //invalidates all objects on the map
std::vector<int3> getTilesUnderObject(CGObjectInstance *) const;
/// draws all objects on current tile (higher-level logic, unlike other draw*** methods)
void drawObjects(QPainter & painter, int x, int y, int z);
void drawObject(QPainter & painter, const TileObject & object);
void drawObjectAt(QPainter & painter, const CGObjectInstance * object, int x, int y);
std::vector<TileObject> & getObjects(int x, int y, int z);
void drawMinimapTile(QPainter & painter, int x, int y, int z);
static bool compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b);
};
#endif // MAPHANDLER_H

102
mapeditor/mapsettings.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "mapsettings.h"
#include "ui_mapsettings.h"
#include "mainwindow.h"
#include "../lib/CSkillHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CArtHandler.h"
#include "../lib/CHeroHandler.h"
MapSettings::MapSettings(MapController & ctrl, QWidget *parent) :
QDialog(parent),
ui(new Ui::MapSettings),
controller(ctrl)
{
ui->setupUi(this);
assert(controller.map());
ui->mapNameEdit->setText(tr(controller.map()->name.c_str()));
ui->mapDescriptionEdit->setPlainText(tr(controller.map()->description.c_str()));
show();
for(int i = 0; i < controller.map()->allowedAbilities.size(); ++i)
{
auto * item = new QListWidgetItem(QString::fromStdString(VLC->skillh->objects[i]->getName()));
item->setData(Qt::UserRole, QVariant::fromValue(i));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(controller.map()->allowedAbilities[i] ? Qt::Checked : Qt::Unchecked);
ui->listAbilities->addItem(item);
}
for(int i = 0; i < controller.map()->allowedSpell.size(); ++i)
{
auto * item = new QListWidgetItem(QString::fromStdString(VLC->spellh->objects[i]->getName()));
item->setData(Qt::UserRole, QVariant::fromValue(i));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(controller.map()->allowedSpell[i] ? Qt::Checked : Qt::Unchecked);
ui->listSpells->addItem(item);
}
for(int i = 0; i < controller.map()->allowedArtifact.size(); ++i)
{
auto * item = new QListWidgetItem(QString::fromStdString(VLC->arth->objects[i]->getName()));
item->setData(Qt::UserRole, QVariant::fromValue(i));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(controller.map()->allowedArtifact[i] ? Qt::Checked : Qt::Unchecked);
ui->listArts->addItem(item);
}
for(int i = 0; i < controller.map()->allowedHeroes.size(); ++i)
{
auto * item = new QListWidgetItem(QString::fromStdString(VLC->heroh->objects[i]->getName()));
item->setData(Qt::UserRole, QVariant::fromValue(i));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(controller.map()->allowedHeroes[i] ? Qt::Checked : Qt::Unchecked);
ui->listHeroes->addItem(item);
}
//ui8 difficulty;
//ui8 levelLimit;
//std::string victoryMessage;
//std::string defeatMessage;
//ui16 victoryIconIndex;
//ui16 defeatIconIndex;
//std::vector<PlayerInfo> players; /// The default size of the vector is PlayerColor::PLAYER_LIMIT.
}
MapSettings::~MapSettings()
{
delete ui;
}
void MapSettings::on_pushButton_clicked()
{
controller.map()->name = ui->mapNameEdit->text().toStdString();
controller.map()->description = ui->mapDescriptionEdit->toPlainText().toStdString();
controller.commitChangeWithoutRedraw();
for(int i = 0; i < controller.map()->allowedAbilities.size(); ++i)
{
auto * item = ui->listAbilities->item(i);
controller.map()->allowedAbilities[i] = item->checkState() == Qt::Checked;
}
for(int i = 0; i < controller.map()->allowedSpell.size(); ++i)
{
auto * item = ui->listSpells->item(i);
controller.map()->allowedSpell[i] = item->checkState() == Qt::Checked;
}
for(int i = 0; i < controller.map()->allowedArtifact.size(); ++i)
{
auto * item = ui->listArts->item(i);
controller.map()->allowedArtifact[i] = item->checkState() == Qt::Checked;
}
for(int i = 0; i < controller.map()->allowedHeroes.size(); ++i)
{
auto * item = ui->listHeroes->item(i);
controller.map()->allowedHeroes[i] = item->checkState() == Qt::Checked;
}
close();
}

27
mapeditor/mapsettings.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef MAPSETTINGS_H
#define MAPSETTINGS_H
#include <QDialog>
#include "mapcontroller.h"
namespace Ui {
class MapSettings;
}
class MapSettings : public QDialog
{
Q_OBJECT
public:
explicit MapSettings(MapController & controller, QWidget *parent = nullptr);
~MapSettings();
private slots:
void on_pushButton_clicked();
private:
Ui::MapSettings *ui;
MapController & controller;
};
#endif // MAPSETTINGS_H

175
mapeditor/mapsettings.ui Normal file
View File

@@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MapSettings</class>
<widget class="QDialog" name="MapSettings">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>454</width>
<height>480</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Map settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>4</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>General</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Map name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="mapNameEdit"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Map description</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="mapDescriptionEdit"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Abilities</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QListWidget" name="listAbilities">
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerItem</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
<property name="resizeMode">
<enum>QListView::Adjust</enum>
</property>
<property name="layoutMode">
<enum>QListView::Batched</enum>
</property>
<property name="batchSize">
<number>30</number>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>Spells</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QListWidget" name="listSpells">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerItem</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
<property name="layoutMode">
<enum>QListView::Batched</enum>
</property>
<property name="batchSize">
<number>30</number>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_4">
<attribute name="title">
<string>Artifacts</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QListWidget" name="listArts">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerItem</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
<property name="layoutMode">
<enum>QListView::Batched</enum>
</property>
<property name="batchSize">
<number>30</number>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_5">
<attribute name="title">
<string>Heroes</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QListWidget" name="listHeroes">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerItem</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
<property name="layoutMode">
<enum>QListView::Batched</enum>
</property>
<property name="batchSize">
<number>30</number>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Ok</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

448
mapeditor/mapview.cpp Normal file
View File

@@ -0,0 +1,448 @@
#include "StdInc.h"
#include "mapview.h"
#include "mainwindow.h"
#include <QGraphicsSceneMouseEvent>
#include "mapcontroller.h"
MinimapView::MinimapView(QWidget * parent):
QGraphicsView(parent)
{
// Disable scrollbars
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
void MinimapView::dimensions()
{
fitInView(0, 0, controller->map()->width, controller->map()->height, Qt::KeepAspectRatio);
}
void MinimapView::setController(MapController * ctrl)
{
controller = ctrl;
}
void MinimapView::mouseMoveEvent(QMouseEvent *mouseEvent)
{
this->update();
auto * sc = static_cast<MinimapScene*>(scene());
if(!sc)
return;
int w = sc->viewport.viewportWidth();
int h = sc->viewport.viewportHeight();
auto pos = mapToScene(mouseEvent->pos());
pos.setX(pos.x() - w / 2);
pos.setY(pos.y() - h / 2);
QPointF point = pos * 32;
emit cameraPositionChanged(point);
}
void MinimapView::mousePressEvent(QMouseEvent* event)
{
mouseMoveEvent(event);
}
MapView::MapView(QWidget * parent):
QGraphicsView(parent),
selectionTool(MapView::SelectionTool::None)
{
}
void MapView::cameraChanged(const QPointF & pos)
{
//ui->mapView->translate(pos.x(), pos.y());
horizontalScrollBar()->setValue(pos.x());
verticalScrollBar()->setValue(pos.y());
}
void MapView::setController(MapController * ctrl)
{
controller = ctrl;
}
void MapView::mouseMoveEvent(QMouseEvent *mouseEvent)
{
this->update();
auto * sc = static_cast<MapScene*>(scene());
if(!sc || !controller->map())
return;
auto pos = mapToScene(mouseEvent->pos()); //TODO: do we need to check size?
int3 tile(pos.x() / 32, pos.y() / 32, sc->level);
if(tile == tilePrev) //do not redraw
return;
tilePrev = tile;
//main->setStatusMessage(QString("x: %1 y: %2").arg(QString::number(pos.x()), QString::number(pos.y())));
switch(selectionTool)
{
case MapView::SelectionTool::Brush:
if(mouseEvent->buttons() & Qt::RightButton)
sc->selectionTerrainView.erase(tile);
else if(mouseEvent->buttons() == Qt::LeftButton)
sc->selectionTerrainView.select(tile);
sc->selectionTerrainView.draw();
break;
case MapView::SelectionTool::Brush2:
{
std::array<int3, 4> extra{ int3{0, 0, 0}, int3{1, 0, 0}, int3{0, 1, 0}, int3{1, 1, 0} };
for(auto & e : extra)
{
if(mouseEvent->buttons() & Qt::RightButton)
sc->selectionTerrainView.erase(tile + e);
else if(mouseEvent->buttons() == Qt::LeftButton)
sc->selectionTerrainView.select(tile + e);
}
}
sc->selectionTerrainView.draw();
break;
case MapView::SelectionTool::Brush4:
{
std::array<int3, 16> extra{
int3{-1, -1, 0}, int3{0, -1, 0}, int3{1, -1, 0}, int3{2, -1, 0},
int3{-1, 0, 0}, int3{0, 0, 0}, int3{1, 0, 0}, int3{2, 0, 0},
int3{-1, 1, 0}, int3{0, 1, 0}, int3{1, 1, 0}, int3{2, 1, 0},
int3{-1, 2, 0}, int3{0, 2, 0}, int3{1, 2, 0}, int3{2, 2, 0}
};
for(auto & e : extra)
{
if(mouseEvent->buttons() & Qt::RightButton)
sc->selectionTerrainView.erase(tile + e);
else if(mouseEvent->buttons() == Qt::LeftButton)
sc->selectionTerrainView.select(tile + e);
}
}
sc->selectionTerrainView.draw();
break;
case MapView::SelectionTool::Area:
if(mouseEvent->buttons() & Qt::RightButton || !mouseEvent->buttons() & Qt::LeftButton)
break;
sc->selectionTerrainView.clear();
for(int j = std::min(tile.y, tileStart.y); j < std::max(tile.y, tileStart.y); ++j)
{
for(int i = std::min(tile.x, tileStart.x); i < std::max(tile.x, tileStart.x); ++i)
{
sc->selectionTerrainView.select(int3(i, j, sc->level));
}
}
sc->selectionTerrainView.draw();
break;
case MapView::SelectionTool::None:
if(mouseEvent->buttons() & Qt::RightButton)
break;
auto sh = tile - tileStart;
sc->selectionObjectsView.shift = QPoint(sh.x, sh.y);
if(sh.x || sh.y)
{
if(sc->selectionObjectsView.newObject)
{
sc->selectionObjectsView.shift = QPoint(tile.x, tile.y);
sc->selectionObjectsView.selectObject(sc->selectionObjectsView.newObject);
sc->selectionObjectsView.selectionMode = 2;
}
else if(mouseEvent->buttons() & Qt::LeftButton)
{
if(sc->selectionObjectsView.selectionMode == 1)
{
sc->selectionObjectsView.clear();
sc->selectionObjectsView.selectObjects(tileStart.x, tileStart.y, tile.x, tile.y);
}
}
}
sc->selectionObjectsView.draw();
break;
}
}
void MapView::mousePressEvent(QMouseEvent *event)
{
this->update();
auto * sc = static_cast<MapScene*>(scene());
if(!sc || !controller->map())
return;
mouseStart = mapToScene(event->pos());
tileStart = tilePrev = int3(mouseStart.x() / 32, mouseStart.y() / 32, sc->level);
if(sc->selectionTerrainView.selection().count(tileStart))
pressedOnSelected = true;
else
pressedOnSelected = false;
switch(selectionTool)
{
case MapView::SelectionTool::Brush:
sc->selectionObjectsView.clear();
sc->selectionObjectsView.draw();
if(event->button() == Qt::RightButton)
sc->selectionTerrainView.erase(tileStart);
else if(event->button() == Qt::LeftButton)
sc->selectionTerrainView.select(tileStart);
sc->selectionTerrainView.draw();
break;
case MapView::SelectionTool::Brush2:
sc->selectionObjectsView.clear();
sc->selectionObjectsView.draw();
{
std::array<int3, 4> extra{ int3{0, 0, 0}, int3{1, 0, 0}, int3{0, 1, 0}, int3{1, 1, 0} };
for(auto & e : extra)
{
if(event->button() == Qt::RightButton)
sc->selectionTerrainView.erase(tileStart + e);
else if(event->button() == Qt::LeftButton)
sc->selectionTerrainView.select(tileStart + e);
}
}
sc->selectionTerrainView.draw();
break;
case MapView::SelectionTool::Brush4:
sc->selectionObjectsView.clear();
sc->selectionObjectsView.draw();
{
std::array<int3, 16> extra{
int3{-1, -1, 0}, int3{0, -1, 0}, int3{1, -1, 0}, int3{2, -1, 0},
int3{-1, 0, 0}, int3{0, 0, 0}, int3{1, 0, 0}, int3{2, 0, 0},
int3{-1, 1, 0}, int3{0, 1, 0}, int3{1, 1, 0}, int3{2, 1, 0},
int3{-1, 2, 0}, int3{0, 2, 0}, int3{1, 2, 0}, int3{2, 2, 0}
};
for(auto & e : extra)
{
if(event->button() == Qt::RightButton)
sc->selectionTerrainView.erase(tileStart + e);
else if(event->button() == Qt::LeftButton)
sc->selectionTerrainView.select(tileStart + e);
}
}
sc->selectionTerrainView.draw();
break;
case MapView::SelectionTool::Area:
if(event->button() == Qt::RightButton)
break;
sc->selectionTerrainView.clear();
sc->selectionTerrainView.draw();
sc->selectionObjectsView.clear();
sc->selectionObjectsView.draw();
break;
case MapView::SelectionTool::None:
sc->selectionTerrainView.clear();
sc->selectionTerrainView.draw();
if(sc->selectionObjectsView.newObject && sc->selectionObjectsView.isSelected(sc->selectionObjectsView.newObject))
{
if(event->button() == Qt::RightButton)
controller->discardObject(sc->level);
}
else
{
if(event->button() == Qt::LeftButton)
{
auto * obj = sc->selectionObjectsView.selectObjectAt(tileStart.x, tileStart.y);
if(obj)
{
if(sc->selectionObjectsView.isSelected(obj))
{
if(qApp->keyboardModifiers() & Qt::ControlModifier)
{
sc->selectionObjectsView.deselectObject(obj);
sc->selectionObjectsView.selectionMode = 1;
}
else
sc->selectionObjectsView.selectionMode = 2;
}
else
{
if(!(qApp->keyboardModifiers() & Qt::ControlModifier))
sc->selectionObjectsView.clear();
sc->selectionObjectsView.selectionMode = 2;
sc->selectionObjectsView.selectObject(obj);
}
}
else
{
sc->selectionObjectsView.clear();
sc->selectionObjectsView.selectionMode = 1;
}
}
sc->selectionObjectsView.shift = QPoint(0, 0);
sc->selectionObjectsView.draw();
}
break;
}
//main->setStatusMessage(QString("x: %1 y: %2").arg(QString::number(event->pos().x()), QString::number(event->pos().y())));
}
void MapView::mouseReleaseEvent(QMouseEvent *event)
{
this->update();
auto * sc = static_cast<MapScene*>(scene());
if(!sc || !controller->map())
return;
switch(selectionTool)
{
case MapView::SelectionTool::None:
if(event->button() == Qt::RightButton)
break;
//switch position
bool tab = false;
if(sc->selectionObjectsView.selectionMode == 2)
{
if(sc->selectionObjectsView.newObject)
{
QString errorMsg;
if(controller->canPlaceObject(sc->level, sc->selectionObjectsView.newObject, errorMsg))
controller->commitObjectCreate(sc->level);
else
{
QMessageBox::information(this, "Can't place object", errorMsg);
break;
}
}
else
controller->commitObjectShift(sc->level);
}
else
{
sc->selectionObjectsView.selectionMode = 0;
sc->selectionObjectsView.shift = QPoint(0, 0);
sc->selectionObjectsView.draw();
tab = true;
//check if we have only one object
}
auto selection = sc->selectionObjectsView.getSelection();
if(selection.size() == 1)
{
emit openObjectProperties(*selection.begin(), tab);
}
break;
}
}
bool MapView::viewportEvent(QEvent *event)
{
if(auto * sc = static_cast<MapScene*>(scene()))
{
//auto rect = sceneRect();
auto rect = mapToScene(viewport()->geometry()).boundingRect();
controller->miniScene(sc->level)->viewport.setViewport(rect.x() / 32, rect.y() / 32, rect.width() / 32, rect.height() / 32);
}
return QGraphicsView::viewportEvent(event);
}
MapSceneBase::MapSceneBase(int lvl):
QGraphicsScene(nullptr),
level(lvl)
{
}
void MapSceneBase::initialize(MapController & controller)
{
for(auto * layer : getAbstractLayers())
layer->initialize(controller);
}
void MapSceneBase::updateViews()
{
for(auto * layer : getAbstractLayers())
layer->update();
}
MapScene::MapScene(int lvl):
MapSceneBase(lvl),
gridView(this),
passabilityView(this),
selectionTerrainView(this),
terrainView(this),
objectsView(this),
selectionObjectsView(this),
isTerrainSelected(false),
isObjectSelected(false)
{
connect(&selectionTerrainView, &SelectionTerrainLayer::selectionMade, this, &MapScene::terrainSelected);
connect(&selectionObjectsView, &SelectionObjectsLayer::selectionMade, this, &MapScene::objectSelected);
}
std::list<AbstractLayer *> MapScene::getAbstractLayers()
{
//sequence is important because it defines rendering order
return {
&terrainView,
&objectsView,
&gridView,
&passabilityView,
&selectionTerrainView,
&selectionObjectsView
};
}
void MapScene::updateViews()
{
MapSceneBase::updateViews();
terrainView.show(true);
objectsView.show(true);
selectionTerrainView.show(true);
selectionObjectsView.show(true);
}
void MapScene::terrainSelected(bool anythingSelected)
{
isTerrainSelected = anythingSelected;
emit selected(isTerrainSelected || isObjectSelected);
}
void MapScene::objectSelected(bool anythingSelected)
{
isObjectSelected = anythingSelected;
emit selected(isTerrainSelected || isObjectSelected);
}
MinimapScene::MinimapScene(int lvl):
MapSceneBase(lvl),
minimapView(this),
viewport(this)
{
}
std::list<AbstractLayer *> MinimapScene::getAbstractLayers()
{
//sequence is important because it defines rendering order
return {
&minimapView,
&viewport
};
}
void MinimapScene::updateViews()
{
MapSceneBase::updateViews();
minimapView.show(true);
viewport.show(true);
}

133
mapeditor/mapview.h Normal file
View File

@@ -0,0 +1,133 @@
#ifndef MAPVIEW_H
#define MAPVIEW_H
#include <QGraphicsScene>
#include <QGraphicsView>
#include "scenelayer.h"
#include "../lib/int3.h"
class CGObjectInstance;
class MainWindow;
class MapController;
class MapSceneBase : public QGraphicsScene
{
Q_OBJECT;
public:
MapSceneBase(int lvl);
const int level;
virtual void updateViews();
virtual void initialize(MapController &);
protected:
virtual std::list<AbstractLayer *> getAbstractLayers() = 0;
};
class MinimapScene : public MapSceneBase
{
public:
MinimapScene(int lvl);
void updateViews() override;
MinimapLayer minimapView;
MinimapViewLayer viewport;
protected:
std::list<AbstractLayer *> getAbstractLayers() override;
};
class MapScene : public MapSceneBase
{
Q_OBJECT
public:
MapScene(int lvl);
void updateViews() override;
GridLayer gridView;
PassabilityLayer passabilityView;
SelectionTerrainLayer selectionTerrainView;
TerrainLayer terrainView;
ObjectsLayer objectsView;
SelectionObjectsLayer selectionObjectsView;
signals:
void selected(bool anything);
public slots:
void terrainSelected(bool anything);
void objectSelected(bool anything);
protected:
std::list<AbstractLayer *> getAbstractLayers() override;
bool isTerrainSelected;
bool isObjectSelected;
};
class MapView : public QGraphicsView
{
Q_OBJECT
public:
enum class SelectionTool
{
None, Brush, Brush2, Brush4, Area, Lasso
};
public:
MapView(QWidget * parent);
void setController(MapController *);
SelectionTool selectionTool;
public slots:
void mouseMoveEvent(QMouseEvent * mouseEvent) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void cameraChanged(const QPointF & pos);
signals:
void openObjectProperties(CGObjectInstance *, bool switchTab);
//void viewportChanged(const QRectF & rect);
protected:
bool viewportEvent(QEvent *event) override;
private:
MapController * controller = nullptr;
QPointF mouseStart;
int3 tileStart;
int3 tilePrev;
bool pressedOnSelected;
};
class MinimapView : public QGraphicsView
{
Q_OBJECT
public:
MinimapView(QWidget * parent);
void setController(MapController *);
void dimensions();
public slots:
void mouseMoveEvent(QMouseEvent * mouseEvent) override;
void mousePressEvent(QMouseEvent* event) override;
signals:
void cameraPositionChanged(const QPointF & newPosition);
private:
MapController * controller = nullptr;
int displayWidth = 192;
int displayHeight = 192;
};
#endif // MAPVIEW_H

View File

@@ -0,0 +1,68 @@
#include "objectbrowser.h"
#include "../lib/mapObjects/CObjectClassesHandler.h"
ObjectBrowser::ObjectBrowser(QObject *parent)
: QSortFilterProxyModel{parent}, terrain(Terrain::ANY)
{
}
bool ObjectBrowser::filterAcceptsRow(int source_row, const QModelIndex & source_parent) const
{
bool result = QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
QModelIndex currentIndex = sourceModel()->index(source_row, 0, source_parent);
int childCount = currentIndex.model()->rowCount(currentIndex);
if(childCount)
return false;
auto item = dynamic_cast<QStandardItemModel*>(sourceModel())->itemFromIndex(currentIndex);
if(!item)
return result;
if(!filterAcceptsRowText(source_row, source_parent))
return false;
if(terrain == Terrain::ANY)
return result;
auto data = item->data().toJsonObject();
if(data.empty())
return result;
auto objIdJson = data["id"];
if(objIdJson == QJsonValue::Undefined)
return result;
auto objId = data["id"].toInt();
auto objSubId = data["subid"].toInt();
auto templateId = data["template"].toInt();
auto factory = VLC->objtypeh->getHandlerFor(objId, objSubId);
auto templ = factory->getTemplates()[templateId];
result = result & templ->canBePlacedAt(terrain);
//text filter
return result;
}
bool ObjectBrowser::filterAcceptsRowText(int source_row, const QModelIndex &source_parent) const
{
if(source_parent.isValid())
{
if(filterAcceptsRowText(source_parent.row(), source_parent.parent()))
return true;
}
QModelIndex index = sourceModel()->index(source_row, 0 ,source_parent);
if(!index.isValid())
return false;
auto item = dynamic_cast<QStandardItemModel*>(sourceModel())->itemFromIndex(index);
if(!item)
return false;
return (filter.isNull() || filter.isEmpty() || item->text().contains(filter, Qt::CaseInsensitive));
}

20
mapeditor/objectbrowser.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef OBJECTBROWSER_H
#define OBJECTBROWSER_H
#include <QSortFilterProxyModel>
#include "../lib/Terrain.h"
class ObjectBrowser : public QSortFilterProxyModel
{
public:
explicit ObjectBrowser(QObject *parent = nullptr);
Terrain terrain;
QString filter;
protected:
bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override;
bool filterAcceptsRowText(int source_row, const QModelIndex &source_parent) const;
};
#endif // OBJECTBROWSER_H

139
mapeditor/playerparams.cpp Normal file
View File

@@ -0,0 +1,139 @@
#include "StdInc.h"
#include "playerparams.h"
#include "ui_playerparams.h"
#include "../lib/CTownHandler.h"
PlayerParams::PlayerParams(MapController & ctrl, int playerId, QWidget *parent) :
QWidget(parent),
ui(new Ui::PlayerParams),
controller(ctrl)
{
ui->setupUi(this);
playerColor = playerId;
assert(controller.map()->players.size() > playerColor);
playerInfo = controller.map()->players[playerColor];
//load factions
for(auto idx : VLC->townh->getAllowedFactions())
{
CFaction * faction = VLC->townh->objects.at(idx);
auto * item = new QListWidgetItem(QString::fromStdString(faction->name));
item->setData(Qt::UserRole, QVariant::fromValue(idx));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
ui->allowedFactions->addItem(item);
if(playerInfo.allowedFactions.count(idx))
item->setCheckState(Qt::Checked);
else
item->setCheckState(Qt::Unchecked);
}
QObject::connect(ui->allowedFactions, SIGNAL(itemChanged(QListWidgetItem*)),
this, SLOT(allowedFactionsCheck(QListWidgetItem*)));
//load checks
bool canHumanPlay = playerInfo.canHumanPlay; //need variable to restore after signal received
playerInfo.canComputerPlay = true; //computer always can play
ui->radioCpu->setChecked(true);
if(canHumanPlay)
ui->radioHuman->setChecked(true);
if(playerInfo.isFactionRandom)
ui->randomFaction->setChecked(true);
if(playerInfo.generateHeroAtMainTown)
ui->generateHero->setChecked(true);
//load towns
int foundMainTown = -1;
for(int i = 0, townIndex = 0; i < controller.map()->objects.size(); ++i)
{
if(auto town = dynamic_cast<CGTownInstance*>(controller.map()->objects[i].get()))
{
auto * ctown = town->town;
if(!ctown)
ctown = VLC->townh->randomTown;
if(ctown && town->getOwner().getNum() == playerColor)
{
if(playerInfo.hasMainTown && playerInfo.posOfMainTown == town->pos)
foundMainTown = townIndex;
auto name = town->name + ", (random)";
if(ctown->faction)
name = town->getObjectName();
ui->mainTown->addItem(tr(name.c_str()), QVariant::fromValue(i));
++townIndex;
}
}
}
if(foundMainTown > -1)
{
ui->mainTown->setCurrentIndex(foundMainTown + 1);
}
else
{
ui->generateHero->setEnabled(false);
playerInfo.hasMainTown = false;
playerInfo.generateHeroAtMainTown = false;
playerInfo.posOfMainTown = int3(-1, -1, -1);
}
ui->playerColor->setTitle(QString("PlayerID: %1").arg(playerId));
show();
}
PlayerParams::~PlayerParams()
{
delete ui;
}
void PlayerParams::on_radioHuman_toggled(bool checked)
{
if(checked)
playerInfo.canHumanPlay = true;
}
void PlayerParams::on_radioCpu_toggled(bool checked)
{
if(checked)
playerInfo.canHumanPlay = false;
}
void PlayerParams::on_generateHero_stateChanged(int arg1)
{
playerInfo.generateHeroAtMainTown = ui->generateHero->isChecked();
}
void PlayerParams::on_randomFaction_stateChanged(int arg1)
{
playerInfo.isFactionRandom = ui->randomFaction->isChecked();
}
void PlayerParams::allowedFactionsCheck(QListWidgetItem * item)
{
if(item->checkState() == Qt::Checked)
playerInfo.allowedFactions.insert(item->data(Qt::UserRole).toInt());
else
playerInfo.allowedFactions.erase(item->data(Qt::UserRole).toInt());
}
void PlayerParams::on_mainTown_activated(int index)
{
if(index == 0) //default
{
ui->generateHero->setEnabled(false);
ui->generateHero->setChecked(false);
playerInfo.hasMainTown = false;
playerInfo.posOfMainTown = int3(-1, -1, -1);
}
else
{
ui->generateHero->setEnabled(true);
auto town = controller.map()->objects.at(ui->mainTown->currentData().toInt());
playerInfo.hasMainTown = true;
playerInfo.posOfMainTown = town->pos;
}
}

42
mapeditor/playerparams.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef PLAYERPARAMS_H
#define PLAYERPARAMS_H
#include <QWidget>
#include "../lib/mapping/CMap.h"
#include "mapcontroller.h"
namespace Ui {
class PlayerParams;
}
class PlayerParams : public QWidget
{
Q_OBJECT
public:
explicit PlayerParams(MapController & controller, int playerId, QWidget *parent = nullptr);
~PlayerParams();
PlayerInfo playerInfo;
int playerColor;
private slots:
void on_radioHuman_toggled(bool checked);
void on_radioCpu_toggled(bool checked);
void on_mainTown_activated(int index);
void on_generateHero_stateChanged(int arg1);
void on_randomFaction_stateChanged(int arg1);
void allowedFactionsCheck(QListWidgetItem *);
private:
Ui::PlayerParams *ui;
MapController & controller;
};
#endif // PLAYERPARAMS_H

142
mapeditor/playerparams.ui Normal file
View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PlayerParams</class>
<widget class="QWidget" name="PlayerParams">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>505</width>
<height>160</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>160</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="playerColor">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>160</height>
</size>
</property>
<property name="title">
<string>GroupBox</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QComboBox" name="teamId">
<item>
<property name="text">
<string>No team</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0">
<widget class="QRadioButton" name="radioHuman">
<property name="text">
<string>Human/CPU</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="radioCpu">
<property name="text">
<string>CPU only</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Team</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Main town</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QCheckBox" name="randomFaction">
<property name="text">
<string>Random faction</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="generateHero">
<property name="text">
<string>Generate hero at main</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="mainTown">
<item>
<property name="text">
<string>(default)</string>
</property>
</item>
</widget>
</item>
<item row="0" column="2" rowspan="3">
<widget class="QListWidget" name="allowedFactions">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,77 @@
#include "StdInc.h"
#include "playersettings.h"
#include "ui_playersettings.h"
#include "playerparams.h"
#include "mainwindow.h"
PlayerSettings::PlayerSettings(MapController & ctrl, QWidget *parent) :
QDialog(parent),
ui(new Ui::PlayerSettings),
controller(ctrl)
{
ui->setupUi(this);
show();
int players = 0;
for(auto & p : controller.map()->players)
{
if(p.canAnyonePlay())
{
paramWidgets.push_back(new PlayerParams(controller, players));
ui->playersLayout->addWidget(paramWidgets.back());
++players;
}
}
if(players < 2)
ui->playersCount->setCurrentText("");
else
ui->playersCount->setCurrentIndex(players - 2);
setAttribute(Qt::WA_DeleteOnClose);
}
PlayerSettings::~PlayerSettings()
{
delete ui;
}
void PlayerSettings::on_playersCount_currentIndexChanged(int index)
{
assert(index + 2 <= controller.map()->players.size());
for(int i = 0; i < index + 2; ++i)
{
if(i < paramWidgets.size())
continue;
auto & p = controller.map()->players[i];
p.canComputerPlay = true;
paramWidgets.push_back(new PlayerParams(controller, i));
ui->playersLayout->addWidget(paramWidgets.back());
}
assert(!paramWidgets.empty());
for(int i = paramWidgets.size() - 1; i >= index + 2; --i)
{
auto & p = controller.map()->players[i];
p.canComputerPlay = false;
p.canHumanPlay = false;
ui->playersLayout->removeWidget(paramWidgets[i]);
delete paramWidgets[i];
paramWidgets.pop_back();
}
}
void PlayerSettings::on_pushButton_clicked()
{
for(auto * w : paramWidgets)
{
controller.map()->players[w->playerColor] = w->playerInfo;
}
controller.commitChangeWithoutRedraw();
close();
}

View File

@@ -0,0 +1,34 @@
#ifndef PLAYERSETTINGS_H
#define PLAYERSETTINGS_H
#include "StdInc.h"
#include <QDialog>
#include "playerparams.h"
namespace Ui {
class PlayerSettings;
}
class PlayerSettings : public QDialog
{
Q_OBJECT
public:
explicit PlayerSettings(MapController & controller, QWidget *parent = nullptr);
~PlayerSettings();
private slots:
void on_playersCount_currentIndexChanged(int index);
void on_pushButton_clicked();
private:
Ui::PlayerSettings *ui;
std::vector<PlayerParams*> paramWidgets;
MapController & controller;
};
#endif // PLAYERSETTINGS_H

117
mapeditor/playersettings.ui Normal file
View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PlayerSettings</class>
<widget class="QDialog" name="PlayerSettings">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>654</width>
<height>283</height>
</rect>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="windowTitle">
<string>Player settings</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="8">
<widget class="QScrollArea" name="players">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="playerscomon">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>628</width>
<height>187</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="playersLayout"/>
</item>
</layout>
</widget>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Players</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="playersCount">
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>3</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>5</string>
</property>
</item>
<item>
<property name="text">
<string>6</string>
</property>
</item>
<item>
<property name="text">
<string>7</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
</widget>
</item>
<item row="2" column="6" colspan="2">
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Ok</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,7 @@
#include "StdInc.h"
#include "radiopushbutton.h"
RadioPushButton::RadioPushButton()
{
}

View File

@@ -0,0 +1,12 @@
#ifndef RADIOPUSHBUTTON_H
#define RADIOPUSHBUTTON_H
#include <QPushButton>
class RadioPushButton : public QPushButton
{
public:
RadioPushButton();
};
#endif // RADIOPUSHBUTTON_H

564
mapeditor/scenelayer.cpp Normal file
View File

@@ -0,0 +1,564 @@
#include "StdInc.h"
#include "scenelayer.h"
#include "mainwindow.h"
#include "../lib/mapping/CMapEditManager.h"
#include "inspector/inspector.h"
#include "mapview.h"
#include "mapcontroller.h"
AbstractLayer::AbstractLayer(MapSceneBase * s): scene(s)
{
}
void AbstractLayer::initialize(MapController & controller)
{
map = controller.map();
handler = controller.mapHandler();
}
void AbstractLayer::show(bool show)
{
if(isShown == show)
return;
isShown = show;
redraw();
}
void AbstractLayer::redraw()
{
if(item)
{
if(pixmap && isShown)
item->setPixmap(*pixmap);
else
item->setPixmap(emptyPixmap);
}
else
{
if(pixmap && isShown)
item.reset(scene->addPixmap(*pixmap));
else
item.reset(scene->addPixmap(emptyPixmap));
}
}
GridLayer::GridLayer(MapSceneBase * s): AbstractLayer(s)
{
}
void GridLayer::update()
{
if(!map)
return;
pixmap.reset(new QPixmap(map->width * 32, map->height * 32));
pixmap->fill(QColor(0, 0, 0, 0));
QPainter painter(pixmap.get());
painter.setPen(QColor(0, 0, 0, 190));
for(int j = 0; j < map->height; ++j)
{
painter.drawLine(0, j * 32, map->width * 32 - 1, j * 32);
}
for(int i = 0; i < map->width; ++i)
{
painter.drawLine(i * 32, 0, i * 32, map->height * 32 - 1);
}
redraw();
}
PassabilityLayer::PassabilityLayer(MapSceneBase * s): AbstractLayer(s)
{
}
void PassabilityLayer::update()
{
if(!map)
return;
pixmap.reset(new QPixmap(map->width * 32, map->height * 32));
pixmap->fill(QColor(0, 0, 0, 0));
if(scene->level == 0 || map->twoLevel)
{
QPainter painter(pixmap.get());
for(int j = 0; j < map->height; ++j)
{
for(int i = 0; i < map->width; ++i)
{
auto tl = map->getTile(int3(i, j, scene->level));
if(tl.blocked || tl.visitable)
{
painter.fillRect(i * 32, j * 32, 31, 31, tl.visitable ? QColor(200, 200, 0, 64) : QColor(255, 0, 0, 64));
}
}
}
}
redraw();
}
SelectionTerrainLayer::SelectionTerrainLayer(MapSceneBase * s): AbstractLayer(s)
{
}
void SelectionTerrainLayer::update()
{
if(!map)
return;
area.clear();
areaAdd.clear();
areaErase.clear();
onSelection();
pixmap.reset(new QPixmap(map->width * 32, map->height * 32));
pixmap->fill(QColor(0, 0, 0, 0));
redraw();
}
void SelectionTerrainLayer::draw()
{
if(!pixmap)
return;
QPainter painter(pixmap.get());
painter.setCompositionMode(QPainter::CompositionMode_Source);
for(auto & t : areaAdd)
{
painter.fillRect(t.x * 32, t.y * 32, 31, 31, QColor(128, 128, 128, 96));
}
for(auto & t : areaErase)
{
painter.fillRect(t.x * 32, t.y * 32, 31, 31, QColor(0, 0, 0, 0));
}
areaAdd.clear();
areaErase.clear();
redraw();
}
void SelectionTerrainLayer::select(const int3 & tile)
{
if(!map || !map->isInTheMap(tile))
return;
if(!area.count(tile))
{
area.insert(tile);
areaAdd.insert(tile);
areaErase.erase(tile);
}
onSelection();
}
void SelectionTerrainLayer::erase(const int3 & tile)
{
if(!map || !map->isInTheMap(tile))
return;
if(area.count(tile))
{
area.erase(tile);
areaErase.insert(tile);
areaAdd.erase(tile);
}
onSelection();
}
void SelectionTerrainLayer::clear()
{
areaErase = area;
areaAdd.clear();
area.clear();
onSelection();
}
const std::set<int3> & SelectionTerrainLayer::selection() const
{
return area;
}
void SelectionTerrainLayer::onSelection()
{
emit selectionMade(!area.empty());
}
TerrainLayer::TerrainLayer(MapSceneBase * s): AbstractLayer(s)
{
}
void TerrainLayer::update()
{
if(!map)
return;
pixmap.reset(new QPixmap(map->width * 32, map->height * 32));
draw(false);
}
void TerrainLayer::setDirty(const int3 & tile)
{
dirty.insert(tile);
}
void TerrainLayer::draw(bool onlyDirty)
{
if(!pixmap)
return;
if(!map)
return;
QPainter painter(pixmap.get());
//painter.setCompositionMode(QPainter::CompositionMode_Source);
if(onlyDirty)
{
std::set<int3> forRedrawing(dirty), neighbours;
for(auto & t : dirty)
{
for(auto & tt : int3::getDirs())
{
if(map->isInTheMap(t + tt))
neighbours.insert(t + tt);
}
}
for(auto & t : neighbours)
{
for(auto & tt : int3::getDirs())
{
forRedrawing.insert(t);
if(map->isInTheMap(t + tt))
forRedrawing.insert(t + tt);
}
}
for(auto & t : forRedrawing)
{
handler->drawTerrainTile(painter, t.x, t.y, scene->level);
handler->drawRiver(painter, t.x, t.y, scene->level);
handler->drawRoad(painter, t.x, t.y, scene->level);
}
}
else
{
for(int j = 0; j < map->height; ++j)
{
for(int i = 0; i < map->width; ++i)
{
handler->drawTerrainTile(painter, i, j, scene->level);
handler->drawRiver(painter, i, j, scene->level);
handler->drawRoad(painter, i, j, scene->level);
}
}
}
dirty.clear();
redraw();
}
ObjectsLayer::ObjectsLayer(MapSceneBase * s): AbstractLayer(s)
{
}
void ObjectsLayer::update()
{
if(!map)
return;
pixmap.reset(new QPixmap(map->width * 32, map->height * 32));
pixmap->fill(QColor(0, 0, 0, 0));
draw(false);
}
void ObjectsLayer::draw(bool onlyDirty)
{
if(!pixmap)
return;
if(!map)
return;
pixmap->fill(QColor(0, 0, 0, 0));
QPainter painter(pixmap.get());
std::set<const CGObjectInstance *> drawen;
for(int j = 0; j < map->height; ++j)
{
for(int i = 0; i < map->width; ++i)
{
handler->drawObjects(painter, i, j, scene->level);
/*auto & objects = main->getMapHandler()->getObjects(i, j, scene->level);
for(auto & object : objects)
{
if(!object.obj || drawen.count(object.obj))
continue;
if(!onlyDirty || dirty.count(object.obj))
{
main->getMapHandler()->drawObject(painter, object);
drawen.insert(object.obj);
}
}*/
}
}
dirty.clear();
redraw();
}
void ObjectsLayer::setDirty(int x, int y)
{
/*auto & objects = main->getMapHandler()->getObjects(x, y, scene->level);
for(auto & object : objects)
{
if(object.obj)
dirty.insert(object.obj);
}*/
}
void ObjectsLayer::setDirty(const CGObjectInstance * object)
{
}
SelectionObjectsLayer::SelectionObjectsLayer(MapSceneBase * s): AbstractLayer(s), newObject(nullptr)
{
}
void SelectionObjectsLayer::update()
{
if(!map)
return;
selectedObjects.clear();
onSelection();
shift = QPoint();
if(newObject)
delete newObject;
newObject = nullptr;
pixmap.reset(new QPixmap(map->width * 32, map->height * 32));
//pixmap->fill(QColor(0, 0, 0, 0));
draw();
}
void SelectionObjectsLayer::draw()
{
if(!pixmap)
return;
pixmap->fill(QColor(0, 0, 0, 0));
QPainter painter(pixmap.get());
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.setPen(QColor(255, 255, 255));
for(auto * obj : selectedObjects)
{
if(obj != newObject)
{
QRect bbox(obj->getPosition().x, obj->getPosition().y, 1, 1);
for(auto & t : obj->getBlockedPos())
{
QPoint topLeft(std::min(t.x, bbox.topLeft().x()), std::min(t.y, bbox.topLeft().y()));
bbox.setTopLeft(topLeft);
QPoint bottomRight(std::max(t.x, bbox.bottomRight().x()), std::max(t.y, bbox.bottomRight().y()));
bbox.setBottomRight(bottomRight);
}
painter.setOpacity(1.0);
painter.drawRect(bbox.x() * 32, bbox.y() * 32, bbox.width() * 32, bbox.height() * 32);
}
//show translation
if(selectionMode == 2 && (shift.x() || shift.y()))
{
painter.setOpacity(0.5);
auto newPos = QPoint(obj->getPosition().x, obj->getPosition().y) + shift;
handler->drawObjectAt(painter, obj, newPos.x(), newPos.y());
}
}
redraw();
}
CGObjectInstance * SelectionObjectsLayer::selectObjectAt(int x, int y) const
{
if(!map || !map->isInTheMap(int3(x, y, scene->level)))
return nullptr;
auto & objects = handler->getObjects(x, y, scene->level);
//visitable is most important
for(auto & object : objects)
{
if(!object.obj)
continue;
if(object.obj->visitableAt(x, y))
{
return object.obj;
}
}
//if not visitable tile - try to get blocked
for(auto & object : objects)
{
if(!object.obj)
continue;
if(object.obj->blockingAt(x, y))
{
return object.obj;
}
}
//finally, we can take any object
for(auto & object : objects)
{
if(!object.obj)
continue;
if(object.obj->coveringAt(x, y))
{
return object.obj;
}
}
return nullptr;
}
void SelectionObjectsLayer::selectObjects(int x1, int y1, int x2, int y2)
{
if(!map)
return;
if(x1 > x2)
std::swap(x1, x2);
if(y1 > y2)
std::swap(y1, y2);
for(int j = y1; j < y2; ++j)
{
for(int i = x1; i < x2; ++i)
{
for(auto & o : handler->getObjects(i, j, scene->level))
selectObject(o.obj, false); //do not inform about each object added
}
}
onSelection();
}
void SelectionObjectsLayer::selectObject(CGObjectInstance * obj, bool inform /* = true */)
{
selectedObjects.insert(obj);
if (inform)
{
onSelection();
}
}
void SelectionObjectsLayer::deselectObject(CGObjectInstance * obj)
{
selectedObjects.erase(obj);
}
bool SelectionObjectsLayer::isSelected(const CGObjectInstance * obj) const
{
return selectedObjects.count(const_cast<CGObjectInstance*>(obj));
}
std::set<CGObjectInstance*> SelectionObjectsLayer::getSelection() const
{
return selectedObjects;
}
void SelectionObjectsLayer::clear()
{
selectedObjects.clear();
onSelection();
shift.setX(0);
shift.setY(0);
}
void SelectionObjectsLayer::onSelection()
{
emit selectionMade(!selectedObjects.empty());
}
MinimapLayer::MinimapLayer(MapSceneBase * s): AbstractLayer(s)
{
}
void MinimapLayer::update()
{
if(!map)
return;
pixmap.reset(new QPixmap(map->width, map->height));
QPainter painter(pixmap.get());
//coordinate transfomation
for(int j = 0; j < map->height; ++j)
{
for(int i = 0; i < map->width; ++i)
{
handler->drawMinimapTile(painter, i, j, scene->level);
}
}
redraw();
}
MinimapViewLayer::MinimapViewLayer(MapSceneBase * s): AbstractLayer(s)
{
}
void MinimapViewLayer::update()
{
if(!map)
return;
pixmap.reset(new QPixmap(map->width, map->height));
pixmap->fill(QColor(0, 0, 0, 0));
QPainter painter(pixmap.get());
painter.setPen(QColor(255, 255, 255));
painter.drawRect(x, y, w, h);
redraw();
}
void MinimapViewLayer::draw()
{
if(!map)
return;
pixmap->fill(QColor(0, 0, 0, 0));
//maybe not optimal but ok
QPainter painter(pixmap.get());
painter.setPen(QColor(255, 255, 255));
painter.drawRect(x, y, w, h);
redraw();
}
void MinimapViewLayer::setViewport(int _x, int _y, int _w, int _h)
{
x = _x;
y = _y;
w = _w;
h = _h;
draw();
}

178
mapeditor/scenelayer.h Normal file
View File

@@ -0,0 +1,178 @@
#ifndef SCENELAYER_H
#define SCENELAYER_H
#include "../lib/int3.h"
class MapSceneBase;
class MapScene;
class CGObjectInstance;
class MapController;
class CMap;
class MapHandler;
class AbstractLayer : public QObject
{
Q_OBJECT
public:
AbstractLayer(MapSceneBase * s);
virtual void update() = 0;
void show(bool show);
void redraw();
void initialize(MapController & controller);
protected:
MapSceneBase * scene;
CMap * map = nullptr;
MapHandler * handler = nullptr;
bool isShown = false;
std::unique_ptr<QPixmap> pixmap;
QPixmap emptyPixmap;
private:
std::unique_ptr<QGraphicsPixmapItem> item;
};
class GridLayer: public AbstractLayer
{
Q_OBJECT
public:
GridLayer(MapSceneBase * s);
void update() override;
};
class PassabilityLayer: public AbstractLayer
{
Q_OBJECT
public:
PassabilityLayer(MapSceneBase * s);
void update() override;
};
class SelectionTerrainLayer: public AbstractLayer
{
Q_OBJECT
public:
SelectionTerrainLayer(MapSceneBase* s);
void update() override;
void draw();
void select(const int3 & tile);
void erase(const int3 & tile);
void clear();
const std::set<int3> & selection() const;
signals:
void selectionMade(bool anythingSlected);
private:
std::set<int3> area, areaAdd, areaErase;
void onSelection();
};
class TerrainLayer: public AbstractLayer
{
Q_OBJECT
public:
TerrainLayer(MapSceneBase * s);
void update() override;
void draw(bool onlyDirty = true);
void setDirty(const int3 & tile);
private:
std::set<int3> dirty;
};
class ObjectsLayer: public AbstractLayer
{
Q_OBJECT
public:
ObjectsLayer(MapSceneBase * s);
void update() override;
void draw(bool onlyDirty = true); //TODO: implement dirty
void setDirty(int x, int y);
void setDirty(const CGObjectInstance * object);
private:
std::set<const CGObjectInstance *> objDirty;
std::set<int3> dirty;
};
class SelectionObjectsLayer: public AbstractLayer
{
Q_OBJECT
public:
SelectionObjectsLayer(MapSceneBase* s);
void update() override;
void draw();
CGObjectInstance * selectObjectAt(int x, int y) const;
void selectObjects(int x1, int y1, int x2, int y2);
void selectObject(CGObjectInstance *, bool inform = true);
void deselectObject(CGObjectInstance *);
bool isSelected(const CGObjectInstance *) const;
std::set<CGObjectInstance*> getSelection() const;
void moveSelection(int x, int y);
void clear();
QPoint shift;
CGObjectInstance * newObject;
//FIXME: magic number
int selectionMode = 0; //0 - nothing, 1 - selection, 2 - movement
signals:
void selectionMade(bool anythingSlected);
private:
std::set<CGObjectInstance *> selectedObjects;
void onSelection();
};
class MinimapLayer: public AbstractLayer
{
public:
MinimapLayer(MapSceneBase * s);
void update() override;
};
class MinimapViewLayer: public AbstractLayer
{
public:
MinimapViewLayer(MapSceneBase * s);
void setViewport(int x, int y, int w, int h);
void draw();
void update() override;
int viewportX() const {return x;}
int viewportY() const {return y;}
int viewportWidth() const {return w;}
int viewportHeight() const {return h;}
private:
int x = 0, y = 0, w = 1, h = 1;
};
#endif // SCENELAYER_H

59
mapeditor/spoiler.cpp Normal file
View File

@@ -0,0 +1,59 @@
#include "StdInc.h"
#include <QPropertyAnimation>
#include "spoiler.h"
Spoiler::Spoiler(const QString & title, const int animationDuration, QWidget *parent) : QWidget(parent), animationDuration(animationDuration)
{
toggleButton.setStyleSheet("QToolButton { border: none; }");
toggleButton.setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toggleButton.setArrowType(Qt::ArrowType::RightArrow);
toggleButton.setText(title);
toggleButton.setCheckable(true);
toggleButton.setChecked(false);
headerLine.setFrameShape(QFrame::HLine);
headerLine.setFrameShadow(QFrame::Sunken);
headerLine.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
contentArea.setStyleSheet("QScrollArea { background-color: white; border: none; }");
contentArea.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
// start out collapsed
contentArea.setMaximumHeight(0);
contentArea.setMinimumHeight(0);
// let the entire widget grow and shrink with its content
toggleAnimation.addAnimation(new QPropertyAnimation(this, "minimumHeight"));
toggleAnimation.addAnimation(new QPropertyAnimation(this, "maximumHeight"));
toggleAnimation.addAnimation(new QPropertyAnimation(&contentArea, "maximumHeight"));
// don't waste space
mainLayout.setVerticalSpacing(0);
mainLayout.setContentsMargins(0, 0, 0, 0);
int row = 0;
mainLayout.addWidget(&toggleButton, row, 0, 1, 1, Qt::AlignLeft);
mainLayout.addWidget(&headerLine, row++, 2, 1, 1);
mainLayout.addWidget(&contentArea, row, 0, 1, 3);
setLayout(&mainLayout);
QObject::connect(&toggleButton, &QToolButton::clicked, [this](const bool checked) {
toggleButton.setArrowType(checked ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow);
toggleAnimation.setDirection(checked ? QAbstractAnimation::Forward : QAbstractAnimation::Backward);
toggleAnimation.start();
});
}
void Spoiler::setContentLayout(QLayout & contentLayout)
{
delete contentArea.layout();
contentArea.setLayout(&contentLayout);
const auto collapsedHeight = sizeHint().height() - contentArea.maximumHeight();
auto contentHeight = contentLayout.sizeHint().height();
for (int i = 0; i < toggleAnimation.animationCount() - 1; ++i) {
QPropertyAnimation * spoilerAnimation = static_cast<QPropertyAnimation *>(toggleAnimation.animationAt(i));
spoilerAnimation->setDuration(animationDuration);
spoilerAnimation->setStartValue(collapsedHeight);
spoilerAnimation->setEndValue(collapsedHeight + contentHeight);
}
QPropertyAnimation * contentAnimation = static_cast<QPropertyAnimation *>(toggleAnimation.animationAt(toggleAnimation.animationCount() - 1));
contentAnimation->setDuration(animationDuration);
contentAnimation->setStartValue(0);
contentAnimation->setEndValue(contentHeight);
}

27
mapeditor/spoiler.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef SPOILER_H
#define SPOILER_H
#include <QFrame>
#include <QGridLayout>
#include <QParallelAnimationGroup>
#include <QScrollArea>
#include <QToolButton>
#include <QWidget>
class Spoiler : public QWidget {
Q_OBJECT
private:
QGridLayout mainLayout;
QToolButton toggleButton;
QFrame headerLine;
QParallelAnimationGroup toggleAnimation;
QScrollArea contentArea;
int animationDuration{300};
public:
explicit Spoiler(const QString & title = "", const int animationDuration = 300, QWidget *parent = 0);
void setContentLayout(QLayout & contentLayout);
};
#endif // SPOILER_H

159
mapeditor/validator.cpp Normal file
View File

@@ -0,0 +1,159 @@
#include "StdInc.h"
#include "validator.h"
#include "ui_validator.h"
#include "../lib/mapObjects/MapObjects.h"
#include "../lib/CHeroHandler.h"
Validator::Validator(const CMap * map, QWidget *parent) :
QDialog(parent),
ui(new Ui::Validator)
{
ui->setupUi(this);
show();
setAttribute(Qt::WA_DeleteOnClose);
std::array<QString, 2> icons{"mapeditor/icons/mod-update.png", "mapeditor/icons/mod-delete.png"};
for(auto & issue : Validator::validate(map))
{
auto * item = new QListWidgetItem(QIcon(icons[issue.critical ? 1 : 0]), issue.message);
ui->listWidget->addItem(item);
}
}
Validator::~Validator()
{
delete ui;
}
std::list<Validator::Issue> Validator::validate(const CMap * map)
{
std::list<Validator::Issue> issues;
if(!map)
{
issues.emplace_back("Map is not loaded", true);
return issues;
}
try
{
//check player settings
int hplayers = 0;
int cplayers = 0;
std::map<int, int> amountOfCastles;
for(int i = 0; i < map->players.size(); ++i)
{
auto & p = map->players[i];
if(p.canAnyonePlay())
amountOfCastles[i] = 0;
if(p.canComputerPlay)
++cplayers;
if(p.canHumanPlay)
++hplayers;
if(p.allowedFactions.empty())
issues.emplace_back(QString("No factions allowed for player %1").arg(i), true);
}
if(hplayers + cplayers == 0)
issues.emplace_back("No players allowed to play this map", true);
if(hplayers + cplayers == 1)
issues.emplace_back("Map is allowed for one player and cannot be started", true);
if(!hplayers)
issues.emplace_back("No human players allowed to play this map", true);
//checking all objects in the map
for(auto o : map->objects)
{
//owners for objects
if(o->getOwner() == PlayerColor::UNFLAGGABLE)
{
if(dynamic_cast<CGMine*>(o.get()) ||
dynamic_cast<CGDwelling*>(o.get()) ||
dynamic_cast<CGTownInstance*>(o.get()) ||
dynamic_cast<CGGarrison*>(o.get()) ||
dynamic_cast<CGHeroInstance*>(o.get()))
{
issues.emplace_back(QString("Armored instance %1 is UNFLAGGABLE but must have NEUTRAL or player owner").arg(o->instanceName.c_str()), true);
}
}
//checking towns
if(auto * ins = dynamic_cast<CGTownInstance*>(o.get()))
{
bool has = amountOfCastles.count(ins->getOwner().getNum());
if(!has && ins->getOwner() != PlayerColor::NEUTRAL)
issues.emplace_back(QString("Town %1 has undefined owner %s").arg(ins->instanceName.c_str(), ins->getOwner().getStr().c_str()), true);
if(has)
++amountOfCastles[ins->getOwner().getNum()];
}
//checking heroes and prisons
if(auto * ins = dynamic_cast<CGHeroInstance*>(o.get()))
{
if(ins->ID == Obj::PRISON)
{
if(ins->getOwner() != PlayerColor::NEUTRAL)
issues.emplace_back(QString("Prison %1 must be a NEUTRAL").arg(ins->instanceName.c_str()), true);
}
else
{
bool has = amountOfCastles.count(ins->getOwner().getNum());
if(!has)
issues.emplace_back(QString("Hero %1 must have an owner").arg(ins->instanceName.c_str()), true);
else
issues.emplace_back(QString("Hero %1: heroes on map are not supported in current version").arg(ins->instanceName.c_str()), false);
}
if(ins->type)
{
if(!map->allowedHeroes[ins->type->getId().getNum()])
issues.emplace_back(QString("Hero %1 is prohibited by map settings").arg(ins->instanceName.c_str()), false);
}
else
issues.emplace_back(QString("Hero %1 has an empty type and must be removed").arg(ins->instanceName.c_str()), true);
}
//checking for arts
if(auto * ins = dynamic_cast<CGArtifact*>(o.get()))
{
if(ins->ID == Obj::SPELL_SCROLL)
{
if(ins->storedArtifact)
{
if(!map->allowedSpell[ins->storedArtifact->id.getNum()])
issues.emplace_back(QString("Spell scroll %1 is prohibited by map settings").arg(ins->instanceName.c_str()), false);
}
else
issues.emplace_back(QString("Spell scroll %1 doesn't have instance assigned and must be removed").arg(ins->instanceName.c_str()), true);
}
else
{
if(ins->ID == Obj::ARTIFACT && !map->allowedArtifact[ins->subID])
{
issues.emplace_back(QString("Artifact %1 is prohibited by map settings").arg(ins->instanceName.c_str()), false);
}
}
}
}
//verification of starting towns
for(auto & mp : amountOfCastles)
if(mp.second == 0)
issues.emplace_back(QString("Player %1 doesn't have any starting town").arg(mp.first), false);
//verification of map name and description
if(map->name.empty())
issues.emplace_back("Map name is not specified", false);
if(map->description.empty())
issues.emplace_back("Map description is not specified", false);
}
catch(const std::exception & e)
{
issues.emplace_back(QString("Exception occurs during validation: %1").arg(e.what()), true);
}
catch(...)
{
issues.emplace_back("Unknown exception occurs during validation", true);
}
return issues;
}

33
mapeditor/validator.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef VALIDATOR_H
#define VALIDATOR_H
#include <QDialog>
#include "../lib/mapping/CMap.h"
namespace Ui {
class Validator;
}
class Validator : public QDialog
{
Q_OBJECT
public:
struct Issue
{
QString message;
bool critical;
Issue(const QString & m, bool c): message(m), critical(c) {}
};
public:
explicit Validator(const CMap * map, QWidget *parent = nullptr);
~Validator();
static std::list<Issue> validate(const CMap * map);
private:
Ui::Validator *ui;
};
#endif // VALIDATOR_H

72
mapeditor/validator.ui Normal file
View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Validator</class>
<widget class="QDialog" name="Validator">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>482</width>
<height>178</height>
</rect>
</property>
<property name="windowTitle">
<string>Map validation results</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QListWidget" name="listWidget">
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="lineWidth">
<number>1</number>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="resizeMode">
<enum>QListView::Adjust</enum>
</property>
<property name="gridSize">
<size>
<width>0</width>
<height>32</height>
</size>
</property>
<property name="viewMode">
<enum>QListView::ListMode</enum>
</property>
<property name="uniformItemSizes">
<bool>false</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

405
mapeditor/windownewmap.cpp Normal file
View File

@@ -0,0 +1,405 @@
#include "StdInc.h"
#include "../lib/mapping/CMap.h"
#include "../lib/rmg/CRmgTemplateStorage.h"
#include "../lib/rmg/CRmgTemplate.h"
#include "../lib/rmg/CMapGenerator.h"
#include "../lib/VCMI_Lib.h"
#include "../lib/mapping/CMapEditManager.h"
#include "../lib/CGeneralTextHandler.h"
#include "windownewmap.h"
#include "ui_windownewmap.h"
#include "mainwindow.h"
#include "generatorprogress.h"
WindowNewMap::WindowNewMap(QWidget *parent) :
QDialog(parent),
ui(new Ui::WindowNewMap)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
setWindowModality(Qt::ApplicationModal);
loadUserSettings();
ui->widthTxt->setInputMask("d00");
ui->heightTxt->setInputMask("d00");
//setup initial parameters - can depend on loaded settings
mapGenOptions.setWidth(ui->widthTxt->text().toInt());
mapGenOptions.setHeight(ui->heightTxt->text().toInt());
bool twoLevel = ui->twoLevelCheck->isChecked();
mapGenOptions.setHasTwoLevels(twoLevel);
updateTemplateList();
loadLastTemplate();
show();
}
WindowNewMap::~WindowNewMap()
{
saveUserSettings();
delete ui;
}
void WindowNewMap::loadUserSettings()
{
//load last saved settings
QSettings s(Ui::teamName, Ui::appName);
auto width = s.value(newMapWidth);
if (width.isValid())
{
ui->widthTxt->setText(width.toString());
}
auto height = s.value(newMapHeight);
if (height.isValid())
{
ui->heightTxt->setText(height.toString());
}
auto twoLevel = s.value(newMapTwoLevel);
if (twoLevel.isValid())
{
ui->twoLevelCheck->setChecked(twoLevel.toBool());
}
auto generateRandom = s.value(newMapGenerateRandom);
if (generateRandom.isValid())
{
ui->randomMapCheck->setChecked(generateRandom.toBool());
}
auto players = s.value(newMapPlayers);
if (players.isValid())
{
ui->humanCombo->setCurrentIndex(players.toInt());
}
auto cpuPlayers = s.value(newMapCpuPlayers);
if (cpuPlayers.isValid())
{
ui->cpuCombo->setCurrentIndex(cpuPlayers.toInt());
}
//TODO: teams when implemented
auto waterContent = s.value(newMapWaterContent);
if (waterContent.isValid())
{
switch (waterContent.toInt())
{
case EWaterContent::RANDOM:
ui->waterOpt1->setChecked(true); break;
case EWaterContent::NONE:
ui->waterOpt2->setChecked(true); break;
case EWaterContent::NORMAL:
ui->waterOpt3->setChecked(true); break;
case EWaterContent::ISLANDS:
ui->waterOpt4->setChecked(true); break;
}
}
auto monsterStrength = s.value(newMapMonsterStrength);
if (monsterStrength.isValid())
{
switch (monsterStrength.toInt())
{
case EMonsterStrength::RANDOM:
ui->monsterOpt1->setChecked(true); break;
case EMonsterStrength::GLOBAL_WEAK:
ui->monsterOpt2->setChecked(true); break;
case EMonsterStrength::GLOBAL_NORMAL:
ui->monsterOpt3->setChecked(true); break;
case EMonsterStrength::GLOBAL_STRONG:
ui->monsterOpt4->setChecked(true); break;
}
}
}
void WindowNewMap::loadLastTemplate()
{
//this requires already loaded template list
QSettings s(Ui::teamName, Ui::appName);
auto templateName = s.value(newMapTemplate);
if (templateName.isValid())
{
auto qstr = templateName.toString();
//Template might have been removed, then silently comboBox will be set to empty string
auto index = ui->templateCombo->findText(qstr);
ui->templateCombo->setCurrentIndex(index);
on_templateCombo_activated(index);
}
}
void WindowNewMap::saveUserSettings()
{
QSettings s(Ui::teamName, Ui::appName);
s.setValue(newMapWidth, ui->widthTxt->text().toInt());
s.setValue(newMapHeight, ui->heightTxt->text().toInt());
s.setValue(newMapTwoLevel, ui->twoLevelCheck->isChecked());
s.setValue(newMapGenerateRandom, ui->randomMapCheck->isChecked());
s.setValue(newMapPlayers,ui->humanCombo->currentIndex());
s.setValue(newMapCpuPlayers,ui->cpuCombo->currentIndex());
//TODO: teams when implemented
EWaterContent::EWaterContent water = EWaterContent::RANDOM;
if(ui->waterOpt1->isChecked())
water = EWaterContent::RANDOM;
else if(ui->waterOpt2->isChecked())
water = EWaterContent::NONE;
else if(ui->waterOpt3->isChecked())
water = EWaterContent::NORMAL;
else if(ui->waterOpt4->isChecked())
water = EWaterContent::ISLANDS;
s.setValue(newMapWaterContent, static_cast<int>(water));
EMonsterStrength::EMonsterStrength monster = EMonsterStrength::RANDOM;
if(ui->monsterOpt1->isChecked())
monster = EMonsterStrength::RANDOM;
else if(ui->monsterOpt2->isChecked())
monster = EMonsterStrength::GLOBAL_WEAK;
else if(ui->monsterOpt3->isChecked())
monster = EMonsterStrength::GLOBAL_NORMAL;
else if(ui->monsterOpt4->isChecked())
monster = EMonsterStrength::GLOBAL_STRONG;
s.setValue(newMapMonsterStrength, static_cast<int>(monster));
auto templateName = ui->templateCombo->currentText();
if (templateName.size() && templateName != defaultTemplate)
{
s.setValue(newMapTemplate, templateName);
}
else
{
s.setValue(newMapTemplate, "");
}
}
void WindowNewMap::on_cancelButton_clicked()
{
close();
}
void generateRandomMap(CMapGenerator & gen, MainWindow * window)
{
window->controller.setMap(gen.generate());
}
std::unique_ptr<CMap> generateEmptyMap(CMapGenOptions & options)
{
std::unique_ptr<CMap> map(new CMap);
map->version = EMapFormat::VCMI;
map->width = options.getWidth();
map->height = options.getHeight();
map->twoLevel = options.getHasTwoLevels();
map->initTerrain();
map->getEditManager()->clearTerrain(&CRandomGenerator::getDefault());
return std::move(map);
}
void WindowNewMap::on_okButtong_clicked()
{
EWaterContent::EWaterContent water = EWaterContent::RANDOM;
EMonsterStrength::EMonsterStrength monster = EMonsterStrength::RANDOM;
if(ui->waterOpt1->isChecked())
water = EWaterContent::RANDOM;
if(ui->waterOpt2->isChecked())
water = EWaterContent::NONE;
if(ui->waterOpt3->isChecked())
water = EWaterContent::NORMAL;
if(ui->waterOpt4->isChecked())
water = EWaterContent::ISLANDS;
if(ui->monsterOpt1->isChecked())
monster = EMonsterStrength::RANDOM;
if(ui->monsterOpt2->isChecked())
monster = EMonsterStrength::GLOBAL_WEAK;
if(ui->monsterOpt3->isChecked())
monster = EMonsterStrength::GLOBAL_NORMAL;
if(ui->monsterOpt4->isChecked())
monster = EMonsterStrength::GLOBAL_STRONG;
mapGenOptions.setWaterContent(water);
mapGenOptions.setMonsterStrength(monster);
std::unique_ptr<CMap> nmap;
if(ui->randomMapCheck->isChecked())
{
//verify map template
if(mapGenOptions.getPossibleTemplates().empty())
{
QMessageBox::warning(this, "No template", "No template for parameters scecified. Random map cannot be generated.");
return;
}
CMapGenerator generator(mapGenOptions);
auto progressBarWnd = new GeneratorProgress(generator, this);
progressBarWnd->show();
try
{
auto f = std::async(std::launch::async, &CMapGenerator::generate, &generator);
progressBarWnd->update();
nmap = f.get();
}
catch(const std::exception & e)
{
QMessageBox::critical(this, "RMG failure", e.what());
}
}
else
{
auto f = std::async(std::launch::async, &::generateEmptyMap, std::ref(mapGenOptions));
nmap = f.get();
}
static_cast<MainWindow*>(parent())->controller.setMap(std::move(nmap));
static_cast<MainWindow*>(parent())->initializeMap(true);
close();
}
void WindowNewMap::on_sizeCombo_activated(int index)
{
std::map<int, std::pair<int, int>> sizes
{
{0, {36, 36}},
{1, {72, 72}},
{2, {108, 108}},
{3, {144, 144}},
};
ui->widthTxt->setText(QString::number(sizes[index].first));
ui->heightTxt->setText(QString::number(sizes[index].second));
}
void WindowNewMap::on_twoLevelCheck_stateChanged(int arg1)
{
bool twoLevel = ui->twoLevelCheck->isChecked();
mapGenOptions.setHasTwoLevels(twoLevel);
updateTemplateList();
}
void WindowNewMap::on_humanCombo_activated(int index)
{
int humans = players.at(index);
if(humans > playerLimit)
{
humans = playerLimit;
ui->humanCombo->setCurrentIndex(humans);
return;
}
mapGenOptions.setPlayerCount(humans);
int teams = mapGenOptions.getTeamCount();
if(teams > humans - 1)
{
teams = humans - 1;
//TBD
}
int cpu = mapGenOptions.getCompOnlyPlayerCount();
if(cpu > playerLimit - humans)
{
cpu = playerLimit - humans;
ui->cpuCombo->setCurrentIndex(cpu + 1);
}
int cpuTeams = mapGenOptions.getCompOnlyTeamCount(); //comp only players - 1
if(cpuTeams > cpu - 1)
{
cpuTeams = cpu - 1;
//TBD
}
//void setMapTemplate(const CRmgTemplate * value);
updateTemplateList();
}
void WindowNewMap::on_cpuCombo_activated(int index)
{
int humans = mapGenOptions.getPlayerCount();
int cpu = cpuPlayers.at(index);
if(cpu > playerLimit - humans)
{
cpu = playerLimit - humans;
ui->cpuCombo->setCurrentIndex(cpu + 1);
return;
}
mapGenOptions.setCompOnlyPlayerCount(cpu);
updateTemplateList();
}
void WindowNewMap::on_randomMapCheck_stateChanged(int arg1)
{
randomMap = ui->randomMapCheck->isChecked();
ui->templateCombo->setEnabled(randomMap);
updateTemplateList();
}
void WindowNewMap::on_templateCombo_activated(int index)
{
if(index == 0)
{
mapGenOptions.setMapTemplate(nullptr);
return;
}
auto * templ = VLC->tplh->getTemplateByName(ui->templateCombo->currentText().toStdString());
mapGenOptions.setMapTemplate(templ);
}
void WindowNewMap::on_widthTxt_textChanged(const QString &arg1)
{
int sz = arg1.toInt();
if(sz > 1)
{
mapGenOptions.setWidth(arg1.toInt());
updateTemplateList();
}
}
void WindowNewMap::on_heightTxt_textChanged(const QString &arg1)
{
int sz = arg1.toInt();
if(sz > 1)
{
mapGenOptions.setHeight(arg1.toInt());
updateTemplateList();
}
}
void WindowNewMap::updateTemplateList()
{
ui->templateCombo->clear();
ui->templateCombo->setCurrentIndex(-1);
if(!randomMap)
return;
mapGenOptions.setMapTemplate(nullptr);
auto templates = mapGenOptions.getPossibleTemplates();
if(templates.empty())
return;
ui->templateCombo->addItem(defaultTemplate);
for(auto * templ : templates)
{
ui->templateCombo->addItem(QString::fromStdString(templ->getName()));
}
ui->templateCombo->setCurrentIndex(0);
}

96
mapeditor/windownewmap.h Normal file
View File

@@ -0,0 +1,96 @@
#ifndef WINDOWNEWMAP_H
#define WINDOWNEWMAP_H
#include <QDialog>
#include "../lib/rmg/CMapGenOptions.h"
namespace Ui
{
class WindowNewMap;
}
class WindowNewMap : public QDialog
{
Q_OBJECT
const QString newMapWidth = "NewMapWindow/Width";
const QString newMapHeight = "NewMapWindow/Height";
const QString newMapTwoLevel = "NewMapWindow/TwoLevel";
const QString newMapGenerateRandom = "NewMapWindow/GenerateRandom";
const QString newMapPlayers = "NewMapWindow/Players"; //map index
const QString newMapCpuPlayers = "NewMapWindow/CpuPlayers"; //map index
const QString newMapWaterContent = "NewMapWindow/WaterContent";
const QString newMapMonsterStrength = "NewMapWindow/MonsterStrength";
const QString newMapTemplate = "NewMapWindow/Template";
const QString defaultTemplate = "[default]";
const int playerLimit = 8;
const std::map<int, int> players
{
{0, CMapGenOptions::RANDOM_SIZE},
{1, 1},
{2, 2},
{3, 3},
{4, 4},
{5, 5},
{6, 6},
{7, 7},
{8, 8}
};
const std::map<int, int> cpuPlayers
{
{0, CMapGenOptions::RANDOM_SIZE},
{1, 0},
{2, 1},
{3, 2},
{4, 3},
{5, 4},
{6, 5},
{7, 6},
{8, 7}
};
public:
explicit WindowNewMap(QWidget *parent = nullptr);
~WindowNewMap();
private slots:
void on_cancelButton_clicked();
void on_okButtong_clicked();
void on_sizeCombo_activated(int index);
void on_twoLevelCheck_stateChanged(int arg1);
void on_humanCombo_activated(int index);
void on_cpuCombo_activated(int index);
void on_randomMapCheck_stateChanged(int arg1);
void on_templateCombo_activated(int index);
void on_widthTxt_textChanged(const QString &arg1);
void on_heightTxt_textChanged(const QString &arg1);
private:
void updateTemplateList();
void loadUserSettings();
void loadLastTemplate();
void saveUserSettings();
private:
Ui::WindowNewMap *ui;
CMapGenOptions mapGenOptions;
bool randomMap = false;
};
#endif // WINDOWNEWMAP_H

784
mapeditor/windownewmap.ui Normal file
View File

@@ -0,0 +1,784 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WindowNewMap</class>
<widget class="QDialog" name="WindowNewMap">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>448</width>
<height>379</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>390</width>
<height>351</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>448</width>
<height>379</height>
</size>
</property>
<property name="windowTitle">
<string>Create new map</string>
</property>
<property name="modal">
<bool>false</bool>
</property>
<widget class="QGroupBox" name="groupBox">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>291</width>
<height>81</height>
</rect>
</property>
<property name="title">
<string>Map size</string>
</property>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>20</y>
<width>261</width>
<height>68</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="1,0,0">
<item row="1" column="0">
<widget class="QCheckBox" name="twoLevelCheck">
<property name="text">
<string>Two level map</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="widthTxt">
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
<property name="text">
<string>36</string>
</property>
<property name="maxLength">
<number>3</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLineEdit" name="heightTxt">
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
<property name="text">
<string>36</string>
</property>
<property name="maxLength">
<number>3</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>48</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>96</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QComboBox" name="sizeCombo">
<property name="minimumSize">
<size>
<width>96</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<item>
<property name="text">
<string>S (36x36)</string>
</property>
</item>
<item>
<property name="text">
<string>M (72x72)</string>
</property>
</item>
<item>
<property name="text">
<string>L (108x108)</string>
</property>
</item>
<item>
<property name="text">
<string>XL (144x144)</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox_5">
<property name="geometry">
<rect>
<x>10</x>
<y>120</y>
<width>431</width>
<height>251</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Random map</string>
</property>
<widget class="QGroupBox" name="groupBox_2">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>411</width>
<height>91</height>
</rect>
</property>
<property name="title">
<string>Players</string>
</property>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>391</width>
<height>68</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0,0,0,1">
<item row="0" column="3">
<widget class="QComboBox" name="humanTeamsCombo">
<property name="minimumSize">
<size>
<width>48</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>64</width>
<height>16777215</height>
</size>
</property>
<item>
<property name="text">
<string>0</string>
</property>
</item>
</widget>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="cpuTeamsCombo">
<item>
<property name="text">
<string>0</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="minimumSize">
<size>
<width>96</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Human/Computer</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="humanCombo">
<property name="minimumSize">
<size>
<width>96</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<item>
<property name="text">
<string>Random</string>
</property>
</item>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>3</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>5</string>
</property>
</item>
<item>
<property name="text">
<string>6</string>
</property>
</item>
<item>
<property name="text">
<string>7</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Computer only</string>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="cpuCombo">
<item>
<property name="text">
<string>Random</string>
</property>
</item>
<item>
<property name="text">
<string>0</string>
</property>
</item>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>3</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>5</string>
</property>
</item>
<item>
<property name="text">
<string>6</string>
</property>
</item>
<item>
<property name="text">
<string>7</string>
</property>
</item>
</widget>
</item>
<item row="1" column="4">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox_4">
<property name="geometry">
<rect>
<x>10</x>
<y>170</y>
<width>411</width>
<height>41</height>
</rect>
</property>
<property name="title">
<string>Monster strength</string>
</property>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>20</y>
<width>411</width>
<height>26</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1,1,1,1">
<item>
<widget class="QRadioButton" name="monsterOpt1">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Random</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="monsterOpt2">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Weak</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="monsterOpt3">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Normal</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="monsterOpt4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Strong</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox_3">
<property name="geometry">
<rect>
<x>10</x>
<y>120</y>
<width>411</width>
<height>41</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>480</width>
<height>96</height>
</size>
</property>
<property name="title">
<string>Water content</string>
</property>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>20</y>
<width>411</width>
<height>26</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,1,1,1,1">
<item>
<widget class="QRadioButton" name="waterOpt1">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>144</width>
<height>96</height>
</size>
</property>
<property name="text">
<string>Random</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="waterOpt2">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>144</width>
<height>96</height>
</size>
</property>
<property name="text">
<string>None</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="waterOpt3">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>144</width>
<height>96</height>
</size>
</property>
<property name="text">
<string>Normal</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="waterOpt4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>144</width>
<height>96</height>
</size>
</property>
<property name="text">
<string>Islands</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>220</y>
<width>411</width>
<height>32</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Template</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="templateCombo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QCheckBox" name="randomMapCheck">
<property name="geometry">
<rect>
<x>10</x>
<y>100</y>
<width>291</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>Generate random map</string>
</property>
</widget>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>310</x>
<y>20</y>
<width>111</width>
<height>101</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="okButtong">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>36</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>36</height>
</size>
</property>
<property name="text">
<string>Ok</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>36</height>
</size>
</property>
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>