mirror of
https://github.com/vcmi/vcmi.git
synced 2025-07-17 01:32:21 +02:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
@ -140,10 +140,10 @@
|
||||
|
||||
"vcmi.client.errors.invalidMap" : "{Błędna mapa lub kampania}\n\nNie udało się stworzyć gry! Wybrana mapa lub kampania jest niepoprawna lub uszkodzona. Powód:\n%s",
|
||||
"vcmi.client.errors.missingCampaigns" : "{Brakujące pliki gry}\n\nPliki kampanii nie zostały znalezione! Możliwe że używasz niekompletnych lub uszkodzonych plików Heroes 3. Spróbuj ponownej instalacji plików gry.",
|
||||
"vcmi.server.errors.disconnected" : "{Błąd sieciowy}\n\nUtracono połączenie z serwerem!",
|
||||
"vcmi.server.errors.existingProcess" : "Inny proces 'vcmiserver' został już uruchomiony, zakończ go nim przejdziesz dalej",
|
||||
"vcmi.server.errors.modsToEnable" : "{Następujące mody są wymagane do wczytania gry}",
|
||||
"vcmi.server.errors.modsToDisable" : "{Następujące mody muszą zostać wyłączone}",
|
||||
"vcmi.server.confirmReconnect" : "Połączyć ponownie z ostatnią sesją?",
|
||||
"vcmi.server.errors.modNoDependency" : "Nie udało się wczytać moda {'%s'}!\n Jest on zależny od moda {'%s'} który nie jest aktywny!\n",
|
||||
"vcmi.server.errors.modConflict" : "Nie udało się wczytać moda {'%s'}!\n Konflikty z aktywnym modem {'%s'}!\n",
|
||||
"vcmi.server.errors.unknownEntity" : "Nie udało się wczytać zapisu! Nieznany element '%s' znaleziony w pliku zapisu! Zapis może nie być zgodny z aktualnie zainstalowaną wersją modów!",
|
||||
@ -162,6 +162,38 @@
|
||||
"vcmi.systemOptions.otherGroup" : "Inne ustawienia", // unused right now
|
||||
"vcmi.systemOptions.townsGroup" : "Ekran miasta",
|
||||
|
||||
"vcmi.statisticWindow.statistics" : "Statystyki",
|
||||
"vcmi.statisticWindow.tsvCopy" : "Kopiuj do schowka",
|
||||
"vcmi.statisticWindow.selectView" : "Tryb widoku",
|
||||
"vcmi.statisticWindow.value" : "Wartość",
|
||||
"vcmi.statisticWindow.title.overview" : "Przegląd",
|
||||
"vcmi.statisticWindow.title.resources" : "Surowce",
|
||||
"vcmi.statisticWindow.title.income" : "Przychód",
|
||||
"vcmi.statisticWindow.title.numberOfHeroes" : "Lb. bohaterów",
|
||||
"vcmi.statisticWindow.title.numberOfTowns" : "Lb. miast",
|
||||
"vcmi.statisticWindow.title.numberOfArtifacts" : "Lb. artefaktów",
|
||||
"vcmi.statisticWindow.title.numberOfDwellings" : "Lb. siedlisk",
|
||||
"vcmi.statisticWindow.title.numberOfMines" : "Lb. kopalni",
|
||||
"vcmi.statisticWindow.title.armyStrength" : "Siła armii",
|
||||
"vcmi.statisticWindow.title.experience" : "Doświadczenie",
|
||||
"vcmi.statisticWindow.title.resourcesSpentArmy" : "Koszty armii",
|
||||
"vcmi.statisticWindow.title.resourcesSpentBuildings" : "Koszty budowy",
|
||||
"vcmi.statisticWindow.title.mapExplored" : "Odkrycie mapy",
|
||||
"vcmi.statisticWindow.param.playerName" : "Imię",
|
||||
"vcmi.statisticWindow.param.daysSurvived" : "Przeżytych dni",
|
||||
"vcmi.statisticWindow.param.maxHeroLevel" : "Maks. poziom bohatera",
|
||||
"vcmi.statisticWindow.param.battleWinRatioHero" : "Zwycięstw (vs. bohaterom)",
|
||||
"vcmi.statisticWindow.param.battleWinRatioNeutral" : "Zwycięstw (vs. neutralnym)",
|
||||
"vcmi.statisticWindow.param.battlesHero" : "Walk (vs. bohaterom)",
|
||||
"vcmi.statisticWindow.param.battlesNeutral" : "Walk (vs. neutralnym)",
|
||||
"vcmi.statisticWindow.param.maxArmyStrength" : "Maks. siła armii",
|
||||
"vcmi.statisticWindow.param.tradeVolume" : "Wielkość handlu",
|
||||
"vcmi.statisticWindow.param.obeliskVisited" : "Lb. obelisków",
|
||||
"vcmi.statisticWindow.icon.townCaptured" : "Miasto zdobyte",
|
||||
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "Najsilniejszy bohater przeciwnika pokonany",
|
||||
"vcmi.statisticWindow.icon.grailFound" : "Gral znaleziony",
|
||||
"vcmi.statisticWindow.icon.defeated" : "Pokonany",
|
||||
|
||||
"vcmi.systemOptions.fullscreenBorderless.hover" : "Pełny ekran (bez ramek)",
|
||||
"vcmi.systemOptions.fullscreenBorderless.help" : "{Pełny ekran w trybie okna}\n\nVCMI będzie działać w trybie okna pełnoekranowego. W tym trybie gra będzie zawsze używać rozdzielczości pulpitu, ignorując wybraną rozdzielczość.",
|
||||
"vcmi.systemOptions.fullscreenExclusive.hover" : "Pełny ekran (tradycyjny)",
|
||||
@ -628,5 +660,7 @@
|
||||
"core.bonus.WATER_IMMUNITY.name": "Odporność: Woda",
|
||||
"core.bonus.WATER_IMMUNITY.description": "Odporny na wszystkie czary szkoły wody",
|
||||
"core.bonus.WIDE_BREATH.name": "Szerokie zionięcie",
|
||||
"core.bonus.WIDE_BREATH.description": "Szeroki atak zionięciem (wiele heksów)"
|
||||
"core.bonus.WIDE_BREATH.description": "Szeroki atak zionięciem (wiele heksów)",
|
||||
"core.bonus.DISINTEGRATE.name": "Rozpadanie",
|
||||
"core.bonus.DISINTEGRATE.description": "Po śmierci nie pozostaje żaden trup"
|
||||
}
|
||||
|
@ -532,7 +532,10 @@ void CServerHandler::sendGuiAction(ui8 action) const
|
||||
|
||||
void CServerHandler::sendRestartGame() const
|
||||
{
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>();
|
||||
if(si->campState && !si->campState->getLoadingBackground().empty())
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>(si->campState->getLoadingBackground());
|
||||
else
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>();
|
||||
|
||||
LobbyRestartGame endGame;
|
||||
sendLobbyPack(endGame);
|
||||
@ -576,7 +579,12 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const
|
||||
verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
|
||||
|
||||
if(!settings["session"]["headless"].Bool())
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>();
|
||||
{
|
||||
if(si->campState && !si->campState->getLoadingBackground().empty())
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>(si->campState->getLoadingBackground());
|
||||
else
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>();
|
||||
}
|
||||
|
||||
LobbyPrepareStartGame lpsg;
|
||||
sendLobbyPack(lpsg);
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include "../lib/CConfigHandler.h"
|
||||
#include "../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../lib/serializer/Connection.h"
|
||||
#include "../lib/campaign/CampaignState.h"
|
||||
|
||||
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack)
|
||||
{
|
||||
@ -203,7 +204,10 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState &
|
||||
if(!lobby->bonusSel && handler.si->campState && handler.getState() == EClientState::LOBBY_CAMPAIGN)
|
||||
{
|
||||
lobby->bonusSel = std::make_shared<CBonusSelection>();
|
||||
GH.windows().pushWindow(lobby->bonusSel);
|
||||
if(!handler.si->campState->conqueredScenarios().size() && !handler.si->campState->getIntroVideo().empty())
|
||||
GH.windows().createAndPushWindow<CampaignIntroVideo>(handler.si->campState->getIntroVideo(), handler.si->campState->getIntroVideoRim().empty() ? ImagePath::builtin("INTRORIM") : handler.si->campState->getIntroVideoRim(), lobby->bonusSel);
|
||||
else
|
||||
GH.windows().pushWindow(lobby->bonusSel);
|
||||
}
|
||||
|
||||
if(lobby->bonusSel)
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "../widgets/MiscWidgets.h"
|
||||
#include "../widgets/ObjectLists.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../widgets/VideoWidget.h"
|
||||
#include "../windows/GUIClasses.h"
|
||||
#include "../windows/InfoWindows.h"
|
||||
#include "../render/IImage.h"
|
||||
@ -58,6 +59,41 @@
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
|
||||
|
||||
CampaignIntroVideo::CampaignIntroVideo(VideoPath video, ImagePath rim, std::shared_ptr<CBonusSelection> bonusSel)
|
||||
: CWindowObject(BORDERED), bonusSel(bonusSel)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
addUsedEvents(LCLICK | KEYBOARD);
|
||||
|
||||
pos = center(Rect(0, 0, 800, 600));
|
||||
|
||||
videoPlayer = std::make_shared<VideoWidgetOnce>(Point(80, 186), video, true, [this](){ exit(); });
|
||||
setBackground(rim);
|
||||
|
||||
CCS->musich->stopMusic();
|
||||
}
|
||||
|
||||
void CampaignIntroVideo::exit()
|
||||
{
|
||||
close();
|
||||
|
||||
if (!CSH->si->campState->getMusic().empty())
|
||||
CCS->musich->playMusic(CSH->si->campState->getMusic(), true, false);
|
||||
|
||||
GH.windows().pushWindow(bonusSel);
|
||||
}
|
||||
|
||||
void CampaignIntroVideo::clickPressed(const Point & cursorPosition)
|
||||
{
|
||||
exit();
|
||||
}
|
||||
|
||||
void CampaignIntroVideo::keyPressed(EShortcut key)
|
||||
{
|
||||
exit();
|
||||
}
|
||||
|
||||
std::shared_ptr<CampaignState> CBonusSelection::getCampaign()
|
||||
{
|
||||
return CSH->si->campState;
|
||||
@ -93,7 +129,9 @@ CBonusSelection::CBonusSelection()
|
||||
labelCampaignDescription = std::make_shared<CLabel>(481, 63, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
|
||||
campaignDescription = std::make_shared<CTextBox>(getCampaign()->getDescriptionTranslated(), Rect(480, 86, 286, 117), 1);
|
||||
|
||||
mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getNameTranslated(), 285);
|
||||
bool videoButtonActive = CSH->getState() == EClientState::GAMEPLAY;
|
||||
int availableSpace = videoButtonActive ? 225 : 285;
|
||||
mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getNameTranslated(), availableSpace );
|
||||
labelMapDescription = std::make_shared<CLabel>(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
|
||||
mapDescription = std::make_shared<CTextBox>("", Rect(480, 278, 292, 108), 1);
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "../windows/CWindowObject.h"
|
||||
|
||||
#include "../lib/campaign/CampaignConstants.h"
|
||||
#include "../lib/filesystem/ResourcePath.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -28,6 +29,22 @@ class CLabel;
|
||||
class CFlagBox;
|
||||
class ISelectionScreenInfo;
|
||||
class ExtraOptionsTab;
|
||||
class VideoWidgetOnce;
|
||||
class CBonusSelection;
|
||||
|
||||
|
||||
class CampaignIntroVideo : public CWindowObject
|
||||
{
|
||||
std::shared_ptr<VideoWidgetOnce> videoPlayer;
|
||||
std::shared_ptr<CBonusSelection> bonusSel;
|
||||
|
||||
void exit();
|
||||
public:
|
||||
CampaignIntroVideo(VideoPath video, ImagePath rim, std::shared_ptr<CBonusSelection> bonusSel);
|
||||
|
||||
void clickPressed(const Point & cursorPosition) override;
|
||||
void keyPressed(EShortcut key) override;
|
||||
};
|
||||
|
||||
/// Campaign screen where you can choose one out of three starting bonuses
|
||||
class CBonusSelection : public CWindowObject
|
||||
|
@ -601,6 +601,12 @@ void RandomMapTab::loadOptions()
|
||||
{
|
||||
w->setItem(mapGenOptions->getMapTemplate());
|
||||
}
|
||||
} else
|
||||
{
|
||||
// Default settings
|
||||
mapGenOptions->setRoadEnabled(RoadId(Road::DIRT_ROAD), true);
|
||||
mapGenOptions->setRoadEnabled(RoadId(Road::GRAVEL_ROAD), true);
|
||||
mapGenOptions->setRoadEnabled(RoadId(Road::COBBLESTONE_ROAD), true);
|
||||
}
|
||||
updateMapInfoByHost();
|
||||
|
||||
|
@ -787,6 +787,8 @@ bool SelectionTab::isMapSupported(const CMapInfo & info)
|
||||
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["supported"].Bool();
|
||||
case EMapFormat::SOD:
|
||||
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH)["supported"].Bool();
|
||||
case EMapFormat::CHR:
|
||||
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES)["supported"].Bool();
|
||||
case EMapFormat::WOG:
|
||||
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["supported"].Bool();
|
||||
case EMapFormat::HOTA:
|
||||
|
@ -629,7 +629,12 @@ void CSimpleJoinScreen::startConnection(const std::string & addr, ui16 port)
|
||||
}
|
||||
|
||||
CLoadingScreen::CLoadingScreen()
|
||||
: CWindowObject(BORDERED, getBackground())
|
||||
: CLoadingScreen(getBackground())
|
||||
{
|
||||
}
|
||||
|
||||
CLoadingScreen::CLoadingScreen(ImagePath background)
|
||||
: CWindowObject(BORDERED, background)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
|
@ -192,6 +192,7 @@ class CLoadingScreen : virtual public CWindowObject, virtual public Load::Progre
|
||||
|
||||
public:
|
||||
CLoadingScreen();
|
||||
CLoadingScreen(ImagePath background);
|
||||
~CLoadingScreen();
|
||||
|
||||
void tick(uint32_t msPassed) override;
|
||||
|
@ -16,15 +16,17 @@
|
||||
#include "../render/Colors.h"
|
||||
#include "../render/IScreenHandler.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/Rect.h"
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../lib/filesystem/Filesystem.h"
|
||||
#include "../../lib/modding/CModHandler.h"
|
||||
#include "../../lib/texts/Languages.h"
|
||||
#include "../../lib/texts/TextOperations.h"
|
||||
#include "../../lib/vcmi_endian.h"
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
|
||||
#include <SDL_surface.h>
|
||||
#include <SDL_image.h>
|
||||
|
||||
struct AtlasLayout
|
||||
{
|
||||
@ -199,10 +201,20 @@ CBitmapFont::CBitmapFont(const std::string & filename):
|
||||
|
||||
if (GH.screenHandler().getScalingFactor() != 1)
|
||||
{
|
||||
auto scaledSurface = CSDL_Ext::scaleSurfaceIntegerFactor(atlasImage, GH.screenHandler().getScalingFactor());
|
||||
static const std::map<std::string, EScalingAlgorithm> filterNameToEnum = {
|
||||
{ "nearest", EScalingAlgorithm::NEAREST},
|
||||
{ "bilinear", EScalingAlgorithm::BILINEAR},
|
||||
{ "xbrz", EScalingAlgorithm::XBRZ}
|
||||
};
|
||||
|
||||
auto filterName = settings["video"]["fontUpscalingFilter"].String();
|
||||
EScalingAlgorithm algorithm = filterNameToEnum.at(filterName);
|
||||
auto scaledSurface = CSDL_Ext::scaleSurfaceIntegerFactor(atlasImage, GH.screenHandler().getScalingFactor(), algorithm);
|
||||
SDL_FreeSurface(atlasImage);
|
||||
atlasImage = scaledSurface;
|
||||
}
|
||||
|
||||
IMG_SavePNG(atlasImage, ("/home/ivan/font_" + filename).c_str());
|
||||
}
|
||||
|
||||
CBitmapFont::~CBitmapFont()
|
||||
|
@ -27,19 +27,25 @@ std::pair<std::unique_ptr<ui8[]>, ui64> CTrueTypeFont::loadData(const JsonNode &
|
||||
return CResourceHandler::get()->load(ResourcePath(filename, EResType::TTF_FONT))->readAll();
|
||||
}
|
||||
|
||||
int CTrueTypeFont::getPointSize(const JsonNode & config) const
|
||||
{
|
||||
int scalingFactor = getScalingFactor();
|
||||
|
||||
if (config.isNumber())
|
||||
return config.Integer() * scalingFactor;
|
||||
else
|
||||
return config[scalingFactor-1].Integer();
|
||||
}
|
||||
|
||||
TTF_Font * CTrueTypeFont::loadFont(const JsonNode &config)
|
||||
{
|
||||
int pointSizeBase = static_cast<int>(config["size"].Float());
|
||||
int scalingFactor = getScalingFactor();
|
||||
int pointSize = pointSizeBase * scalingFactor;
|
||||
|
||||
if(!TTF_WasInit() && TTF_Init()==-1)
|
||||
throw std::runtime_error(std::string("Failed to initialize true type support: ") + TTF_GetError() + "\n");
|
||||
|
||||
return TTF_OpenFontRW(SDL_RWFromConstMem(data.first.get(), (int)data.second), 1, pointSize);
|
||||
return TTF_OpenFontRW(SDL_RWFromConstMem(data.first.get(), data.second), 1, getPointSize(config["size"]));
|
||||
}
|
||||
|
||||
int CTrueTypeFont::getFontStyle(const JsonNode &config)
|
||||
int CTrueTypeFont::getFontStyle(const JsonNode &config) const
|
||||
{
|
||||
const JsonVector & names = config["style"].Vector();
|
||||
int ret = 0;
|
||||
|
@ -30,7 +30,8 @@ class CTrueTypeFont final : public IFont
|
||||
|
||||
std::pair<std::unique_ptr<ui8[]>, ui64> loadData(const JsonNode & config);
|
||||
TTF_Font * loadFont(const JsonNode & config);
|
||||
int getFontStyle(const JsonNode & config);
|
||||
int getPointSize(const JsonNode & config) const;
|
||||
int getFontStyle(const JsonNode & config) const;
|
||||
|
||||
void renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const override;
|
||||
public:
|
||||
|
@ -270,30 +270,10 @@ std::shared_ptr<ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palet
|
||||
if (factor <= 0)
|
||||
throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor));
|
||||
|
||||
if (palette && surf->format->palette)
|
||||
if (palette && surf && surf->format->palette)
|
||||
SDL_SetSurfacePalette(surf, palette);
|
||||
|
||||
/// Convert current surface to ARGB format suitable for xBRZ
|
||||
/// TODO: skip its creation if this is format matches current surface (even if unlikely)
|
||||
SDL_Surface * intermediate = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ARGB8888, 0);
|
||||
SDL_Surface * scaled = CSDL_Ext::newSurface(Point(surf->w * factor, surf->h * factor), intermediate);
|
||||
|
||||
assert(intermediate->pitch == intermediate->w * 4);
|
||||
assert(scaled->pitch == scaled->w * 4);
|
||||
|
||||
const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels);
|
||||
uint32_t * dstPixels = static_cast<uint32_t*>(scaled->pixels);
|
||||
|
||||
// avoid excessive granulation - xBRZ prefers at least 8-16 lines per task
|
||||
// TODO: compare performance and size of images, recheck values for potentially better parameters
|
||||
const int granulation = std::clamp(surf->h / 64 * 8, 8, 64);
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate](const tbb::blocked_range<size_t> & r)
|
||||
{
|
||||
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, xbrz::ColorFormat::ARGB, {}, r.begin(), r.end());
|
||||
});
|
||||
|
||||
SDL_FreeSurface(intermediate);
|
||||
SDL_Surface * scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ);
|
||||
|
||||
auto ret = std::make_shared<SDLImageShared>(scaled);
|
||||
|
||||
@ -307,7 +287,7 @@ std::shared_ptr<ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palet
|
||||
// erase our own reference
|
||||
SDL_FreeSurface(scaled);
|
||||
|
||||
if (surf->format->palette)
|
||||
if (surf && surf->format->palette)
|
||||
SDL_SetSurfacePalette(surf, originalPalette);
|
||||
|
||||
return ret;
|
||||
|
@ -638,8 +638,8 @@ SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface * surf, int width, int height)
|
||||
if(!surf || !width || !height)
|
||||
return nullptr;
|
||||
|
||||
if (surf->w * 2 == width && surf->h * 2 == height)
|
||||
return scaleSurfaceIntegerFactor(surf, 2);
|
||||
// TODO: use xBRZ if possible? E.g. when scaling to 150% do 100% -> 200% via xBRZ and then linear downscale 200% -> 150%?
|
||||
// Need to investigate which is optimal for performance and for visuals
|
||||
|
||||
SDL_Surface * intermediate = SDL_ConvertSurface(surf, screen->format, 0);
|
||||
SDL_Surface * ret = newSurface(Point(width, height), intermediate);
|
||||
@ -654,7 +654,7 @@ SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface * surf, int width, int height)
|
||||
return ret;
|
||||
}
|
||||
|
||||
SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor)
|
||||
SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor, EScalingAlgorithm algorithm)
|
||||
{
|
||||
if(surf == nullptr || factor == 0)
|
||||
return nullptr;
|
||||
@ -662,7 +662,7 @@ SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor
|
||||
int newWidth = surf->w * factor;
|
||||
int newHight = surf->h * factor;
|
||||
|
||||
SDL_Surface * intermediate = SDL_ConvertSurface(surf, screen->format, 0);
|
||||
SDL_Surface * intermediate = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ARGB8888, 0);
|
||||
SDL_Surface * ret = newSurface(Point(newWidth, newHight), intermediate);
|
||||
|
||||
assert(intermediate->pitch == intermediate->w * 4);
|
||||
@ -675,10 +675,23 @@ SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor
|
||||
// TODO: compare performance and size of images, recheck values for potentially better parameters
|
||||
const int granulation = std::clamp(surf->h / 64 * 8, 8, 64);
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate](const tbb::blocked_range<size_t> & r)
|
||||
switch (algorithm)
|
||||
{
|
||||
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, xbrz::ColorFormat::ARGB, {}, r.begin(), r.end());
|
||||
});
|
||||
case EScalingAlgorithm::NEAREST:
|
||||
xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
|
||||
break;
|
||||
case EScalingAlgorithm::BILINEAR:
|
||||
xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
|
||||
break;
|
||||
case EScalingAlgorithm::XBRZ:
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate](const tbb::blocked_range<size_t> & r)
|
||||
{
|
||||
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, xbrz::ColorFormat::ARGB, {}, r.begin(), r.end());
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("invalid scaling algorithm!");
|
||||
}
|
||||
|
||||
SDL_FreeSurface(intermediate);
|
||||
|
||||
|
@ -27,6 +27,13 @@ class Point;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
enum class EScalingAlgorithm : int8_t
|
||||
{
|
||||
NEAREST,
|
||||
BILINEAR,
|
||||
XBRZ
|
||||
};
|
||||
|
||||
namespace CSDL_Ext
|
||||
{
|
||||
|
||||
@ -92,7 +99,7 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &,
|
||||
|
||||
// bilinear filtering. Always returns rgba surface
|
||||
SDL_Surface * scaleSurface(SDL_Surface * surf, int width, int height);
|
||||
SDL_Surface * scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor);
|
||||
SDL_Surface * scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor, EScalingAlgorithm scaler);
|
||||
|
||||
template<int bpp>
|
||||
void convertToGrayscaleBpp(SDL_Surface * surf, const Rect & rect);
|
||||
|
@ -73,20 +73,19 @@ std::vector<std::string> CMessage::breakText(std::string text, size_t maxLineWid
|
||||
// each iteration generates one output line
|
||||
while(text.length())
|
||||
{
|
||||
ui32 lineWidth = 0; //in characters or given char metric
|
||||
ui32 wordBreak = -1; //last position for line break (last space character)
|
||||
ui32 currPos = 0; //current position in text
|
||||
bool opened = false; //set to true when opening brace is found
|
||||
std::string color; //color found
|
||||
|
||||
size_t symbolSize = 0; // width of character, in bytes
|
||||
size_t glyphWidth = 0; // width of printable glyph, pixels
|
||||
|
||||
std::string printableString;
|
||||
|
||||
// loops till line is full or end of text reached
|
||||
while(currPos < text.length() && text[currPos] != 0x0a && lineWidth < maxLineWidth)
|
||||
while(currPos < text.length() && text[currPos] != 0x0a)
|
||||
{
|
||||
symbolSize = TextOperations::getUnicodeCharacterSize(text[currPos]);
|
||||
glyphWidth = graphics->fonts[font]->getGlyphWidth(text.data() + currPos);
|
||||
|
||||
// candidate for line break
|
||||
if(ui8(text[currPos]) <= ui8(' '))
|
||||
@ -116,7 +115,14 @@ std::vector<std::string> CMessage::breakText(std::string text, size_t maxLineWid
|
||||
color = "";
|
||||
}
|
||||
else
|
||||
lineWidth += glyphWidth;
|
||||
{
|
||||
std::string newPrintableString = printableString;
|
||||
newPrintableString.append(text.data() + currPos, symbolSize);
|
||||
if (graphics->fonts[font]->getStringWidth(newPrintableString) < maxLineWidth)
|
||||
printableString.append(text.data() + currPos, symbolSize);
|
||||
else
|
||||
break;
|
||||
}
|
||||
currPos += symbolSize;
|
||||
}
|
||||
|
||||
|
254
config/campaignOverrides.json
Normal file
254
config/campaignOverrides.json
Normal file
@ -0,0 +1,254 @@
|
||||
{
|
||||
"MAPS/HC1_MAIN" : { // Heroes Chronicles 1
|
||||
"regions":
|
||||
{
|
||||
"background": "chronicles_1/CamBkHc",
|
||||
"prefix": "chronicles_1/HcSc",
|
||||
"suffix": ["1", "2", "3"],
|
||||
"color_suffix_length": 0,
|
||||
"desc": [
|
||||
{ "infix": "1", "x": 27, "y": 43 },
|
||||
{ "infix": "2", "x": 231, "y": 43 },
|
||||
{ "infix": "3", "x": 27, "y": 178 },
|
||||
{ "infix": "4", "x": 231, "y": 178 },
|
||||
{ "infix": "5", "x": 27, "y": 312 },
|
||||
{ "infix": "6", "x": 231, "y": 312 },
|
||||
{ "infix": "7", "x": 27, "y": 447 },
|
||||
{ "infix": "8", "x": 231, "y": 447 }
|
||||
]
|
||||
},
|
||||
"scenarioCount": 8,
|
||||
"scenarios": [
|
||||
{ "voiceProlog": "chronicles_1/ABVOFL4" },
|
||||
{ "voiceProlog": "chronicles_1/H3X2UAE" },
|
||||
{ "voiceProlog": "chronicles_1/H3X2BBA" },
|
||||
{ "voiceProlog": "chronicles_1/H3X2RND" },
|
||||
{ "voiceProlog": "chronicles_1/G1C" },
|
||||
{ "voiceProlog": "chronicles_1/G2C" },
|
||||
{ "voiceProlog": "chronicles_1/ABVOFL3" },
|
||||
{ "voiceProlog": "chronicles_1/H3X2BBF", "voiceEpilog": "chronicles_1/N1C_D" }
|
||||
],
|
||||
"loadingBackground": "chronicles_1/LoadBar",
|
||||
"introVideoRim": "chronicles_1/INTRORIM",
|
||||
"introVideo": "chronicles_1/Intro"
|
||||
},
|
||||
"MAPS/HC2_MAIN" : { // Heroes Chronicles 2
|
||||
"regions":
|
||||
{
|
||||
"background": "chronicles_2/CamBkHc",
|
||||
"prefix": "chronicles_2/HcSc",
|
||||
"suffix": ["1", "2", "3"],
|
||||
"color_suffix_length": 0,
|
||||
"desc": [
|
||||
{ "infix": "1", "x": 27, "y": 43 },
|
||||
{ "infix": "2", "x": 231, "y": 43 },
|
||||
{ "infix": "3", "x": 27, "y": 178 },
|
||||
{ "infix": "4", "x": 231, "y": 178 },
|
||||
{ "infix": "5", "x": 27, "y": 312 },
|
||||
{ "infix": "6", "x": 231, "y": 312 },
|
||||
{ "infix": "7", "x": 27, "y": 447 },
|
||||
{ "infix": "8", "x": 231, "y": 447 }
|
||||
]
|
||||
},
|
||||
"scenarioCount": 8,
|
||||
"scenarios": [
|
||||
{ "voiceProlog": "chronicles_2/H3X2ELB" },
|
||||
{ "voiceProlog": "chronicles_2/H3X2NBA" },
|
||||
{ "voiceProlog": "chronicles_2/H3X2RNA" },
|
||||
{ "voiceProlog": "chronicles_2/ABVOAB8" },
|
||||
{ "voiceProlog": "chronicles_2/H3X2UAL" },
|
||||
{ "voiceProlog": "chronicles_2/E1A" },
|
||||
{ "voiceProlog": "chronicles_2/ABVOAB2" },
|
||||
{ "voiceProlog": "chronicles_2/G1A", "voiceEpilog": "chronicles_2/S1C" }
|
||||
],
|
||||
"loadingBackground": "chronicles_2/LoadBar",
|
||||
"introVideoRim": "chronicles_2/INTRORIM",
|
||||
"introVideo": "chronicles_2/Intro"
|
||||
},
|
||||
"MAPS/HC3_MAIN" : { // Heroes Chronicles 3
|
||||
"regions":
|
||||
{
|
||||
"background": "chronicles_3/CamBkHc",
|
||||
"prefix": "chronicles_3/HcSc",
|
||||
"suffix": ["1", "2", "3"],
|
||||
"color_suffix_length": 0,
|
||||
"desc": [
|
||||
{ "infix": "1", "x": 27, "y": 43 },
|
||||
{ "infix": "2", "x": 231, "y": 43 },
|
||||
{ "infix": "3", "x": 27, "y": 178 },
|
||||
{ "infix": "4", "x": 231, "y": 178 },
|
||||
{ "infix": "5", "x": 27, "y": 312 },
|
||||
{ "infix": "6", "x": 231, "y": 312 },
|
||||
{ "infix": "7", "x": 27, "y": 447 },
|
||||
{ "infix": "8", "x": 231, "y": 447 }
|
||||
]
|
||||
},
|
||||
"scenarioCount": 8,
|
||||
"scenarios": [
|
||||
{ "voiceProlog": "chronicles_3/G2C" },
|
||||
{ "voiceProlog": "chronicles_3/ABVOAB1" },
|
||||
{ "voiceProlog": "chronicles_3/G2D" },
|
||||
{ "voiceProlog": "chronicles_3/E1B" },
|
||||
{ "voiceProlog": "chronicles_3/ABVOAB2" },
|
||||
{ "voiceProlog": "chronicles_3/ABVOAB4" },
|
||||
{ "voiceProlog": "chronicles_3/ABVOAB6" },
|
||||
{ "voiceProlog": "chronicles_3/G3B", "voiceEpilog": "chronicles_3/ABVOFL2" }
|
||||
],
|
||||
"loadingBackground": "chronicles_3/LoadBar",
|
||||
"introVideoRim": "chronicles_3/INTRORIM",
|
||||
"introVideo": "chronicles_3/Intro"
|
||||
},
|
||||
"MAPS/HC4_MAIN" : { // Heroes Chronicles 4
|
||||
"regions":
|
||||
{
|
||||
"background": "chronicles_4/CamBkHc",
|
||||
"prefix": "chronicles_4/HcSc",
|
||||
"suffix": ["1", "2", "3"],
|
||||
"color_suffix_length": 0,
|
||||
"desc": [
|
||||
{ "infix": "1", "x": 27, "y": 43 },
|
||||
{ "infix": "2", "x": 231, "y": 43 },
|
||||
{ "infix": "3", "x": 27, "y": 178 },
|
||||
{ "infix": "4", "x": 231, "y": 178 },
|
||||
{ "infix": "5", "x": 27, "y": 312 },
|
||||
{ "infix": "6", "x": 231, "y": 312 },
|
||||
{ "infix": "7", "x": 27, "y": 447 },
|
||||
{ "infix": "8", "x": 231, "y": 447 }
|
||||
]
|
||||
},
|
||||
"scenarioCount": 8,
|
||||
"scenarios": [
|
||||
{ "voiceProlog": "chronicles_4/ABVOAB1" },
|
||||
{ "voiceProlog": "chronicles_4/ABVODB4" },
|
||||
{ "voiceProlog": "chronicles_4/H3X2ELC" },
|
||||
{ "voiceProlog": "chronicles_4/ABVODS2" },
|
||||
{ "voiceProlog": "chronicles_4/ABVODS1" },
|
||||
{ "voiceProlog": "chronicles_4/ABVODS3" },
|
||||
{ "voiceProlog": "chronicles_4/ABVODS4" },
|
||||
{ "voiceProlog": "chronicles_4/H3X2NBD", "voiceEpilog": "chronicles_4/S1C" }
|
||||
],
|
||||
"loadingBackground": "chronicles_4/LoadBar",
|
||||
"introVideoRim": "chronicles_4/INTRORIM",
|
||||
"introVideo": "chronicles_4/Intro"
|
||||
},
|
||||
"MAPS/HC5_MAIN" : { // Heroes Chronicles 5
|
||||
"regions":
|
||||
{
|
||||
"background": "chronicles_5/CamBkHc",
|
||||
"prefix": "chronicles_5/HcSc",
|
||||
"suffix": ["1", "2", "3"],
|
||||
"color_suffix_length": 0,
|
||||
"desc": [
|
||||
{ "infix": "1", "x": 34, "y": 184 },
|
||||
{ "infix": "2", "x": 235, "y": 184 },
|
||||
{ "infix": "3", "x": 34, "y": 320 },
|
||||
{ "infix": "4", "x": 235, "y": 320 },
|
||||
{ "infix": "5", "x": 129, "y": 459 }
|
||||
]
|
||||
},
|
||||
"scenarioCount": 5,
|
||||
"scenarios": [
|
||||
{ "voiceProlog": "chronicles_5/ABVOAB1" },
|
||||
{ "voiceProlog": "chronicles_5/H3X2RNA" },
|
||||
{ "voiceProlog": "chronicles_5/ABVOFL2" },
|
||||
{ "voiceProlog": "chronicles_5/ABVOFL4" },
|
||||
{ "voiceProlog": "chronicles_5/H3X2UAH", "voiceEpilog": "chronicles_5/N1C_D" }
|
||||
],
|
||||
"loadingBackground": "chronicles_5/LoadBar",
|
||||
"introVideoRim": "chronicles_5/INTRORIM",
|
||||
"introVideo": "chronicles_5/Intro"
|
||||
},
|
||||
"MAPS/HC6_MAIN" : { // Heroes Chronicles 6
|
||||
"regions":
|
||||
{
|
||||
"background": "chronicles_6/CamBkHc",
|
||||
"prefix": "chronicles_6/HcSc",
|
||||
"suffix": ["1", "2", "3"],
|
||||
"color_suffix_length": 0,
|
||||
"desc": [
|
||||
{ "infix": "1", "x": 34, "y": 184 },
|
||||
{ "infix": "2", "x": 235, "y": 184 },
|
||||
{ "infix": "3", "x": 34, "y": 320 },
|
||||
{ "infix": "4", "x": 235, "y": 320 },
|
||||
{ "infix": "5", "x": 129, "y": 459 }
|
||||
]
|
||||
},
|
||||
"scenarioCount": 5,
|
||||
"scenarios": [
|
||||
{ "voiceProlog": "chronicles_6/H3X2ELB" },
|
||||
{ "voiceProlog": "chronicles_6/E1A" },
|
||||
{ "voiceProlog": "chronicles_6/H3X2BBA" },
|
||||
{ "voiceProlog": "chronicles_6/ABVOAB2" },
|
||||
{ "voiceProlog": "chronicles_6/ABVOAB5", "voiceEpilog": "chronicles_6/ABVODB2" }
|
||||
],
|
||||
"loadingBackground": "chronicles_6/LoadBar",
|
||||
"introVideoRim": "chronicles_6/INTRORIM",
|
||||
"introVideo": "chronicles_6/Intro"
|
||||
},
|
||||
"MAPS/HC7_MAIN" : { // Heroes Chronicles 7
|
||||
"regions":
|
||||
{
|
||||
"background": "chronicles_7/CamBkHc",
|
||||
"prefix": "chronicles_7/HcSc",
|
||||
"suffix": ["1", "2", "3"],
|
||||
"color_suffix_length": 0,
|
||||
"desc": [
|
||||
{ "infix": "1", "x": 27, "y": 43 },
|
||||
{ "infix": "2", "x": 231, "y": 43 },
|
||||
{ "infix": "3", "x": 27, "y": 178 },
|
||||
{ "infix": "4", "x": 231, "y": 178 },
|
||||
{ "infix": "5", "x": 27, "y": 312 },
|
||||
{ "infix": "6", "x": 231, "y": 312 },
|
||||
{ "infix": "7", "x": 27, "y": 447 },
|
||||
{ "infix": "8", "x": 231, "y": 447 }
|
||||
]
|
||||
},
|
||||
"scenarioCount": 8,
|
||||
"scenarios": [
|
||||
{ "voiceProlog": "chronicles_7/ABVOFL2" },
|
||||
{ "voiceProlog": "chronicles_7/ABVOFL3" },
|
||||
{ "voiceProlog": "chronicles_7/N1C_D" },
|
||||
{ "voiceProlog": "chronicles_7/S1C" },
|
||||
{ "voiceProlog": "chronicles_7/H3X2UAB" },
|
||||
{ "voiceProlog": "chronicles_7/E2C" },
|
||||
{ "voiceProlog": "chronicles_7/H3X2NBE" },
|
||||
{ "voiceProlog": "chronicles_7/ABVOFW4", "voiceEpilog": "chronicles_7/ABVOAB1" }
|
||||
],
|
||||
"loadingBackground": "chronicles_7/LoadBar",
|
||||
"introVideoRim": "chronicles_7/INTRORIM",
|
||||
"introVideo": "chronicles_7/Intro5"
|
||||
},
|
||||
"MAPS/HC8_MAIN" : { // Heroes Chronicles 8
|
||||
"regions":
|
||||
{
|
||||
"background": "chronicles_8/CamBkHc",
|
||||
"prefix": "chronicles_8/HcSc",
|
||||
"suffix": ["1", "2", "3"],
|
||||
"color_suffix_length": 0,
|
||||
"desc": [
|
||||
{ "infix": "1", "x": 27, "y": 43 },
|
||||
{ "infix": "2", "x": 231, "y": 43 },
|
||||
{ "infix": "3", "x": 27, "y": 178 },
|
||||
{ "infix": "4", "x": 231, "y": 178 },
|
||||
{ "infix": "5", "x": 27, "y": 312 },
|
||||
{ "infix": "6", "x": 231, "y": 312 },
|
||||
{ "infix": "7", "x": 27, "y": 447 },
|
||||
{ "infix": "8", "x": 231, "y": 447 }
|
||||
]
|
||||
},
|
||||
"scenarioCount": 8,
|
||||
"scenarios": [
|
||||
{ "voiceProlog": "chronicles_8/H3X2RNB" },
|
||||
{ "voiceProlog": "chronicles_8/ABVOAB9" },
|
||||
{ "voiceProlog": "chronicles_8/H3X2BBB" },
|
||||
{ "voiceProlog": "chronicles_8/ABVODS1" },
|
||||
{ "voiceProlog": "chronicles_8/H3X2ELA" },
|
||||
{ "voiceProlog": "chronicles_8/E1B" },
|
||||
{ "voiceProlog": "chronicles_8/H3X2BBD" },
|
||||
{ "voiceProlog": "chronicles_8/H3X2ELE", "voiceEpilog": "chronicles_8/ABVOAB7" }
|
||||
],
|
||||
"loadingBackground": "chronicles_8/LoadBar",
|
||||
"introVideoRim": "chronicles_8/INTRORIM",
|
||||
"introVideo": "chronicles_8/Intro6"
|
||||
}
|
||||
}
|
@ -19,7 +19,11 @@
|
||||
// Should be in format:
|
||||
// <replaced bitmap font name, case-sensetive> : <true type font description>
|
||||
// "file" - file to load font from, must be in data/ directory
|
||||
// "size" - point size of font
|
||||
// "size" - point size of font. Can be defined in two forms:
|
||||
// a) single number, e.g. 10. In this case, game will automatically multiply font size by upscaling factor when xBRZ is in use,
|
||||
// so xbrz 2x will use 20px, xbrz 3x will use 30px, etc
|
||||
// b) list of scaling factors for each scaling mode, e.g. [ 10, 16, 22, 26]. In this case game will select point size according to xBRZ scaling factor
|
||||
// so unscaled mode will use 10px, xbrz2 will use 16px, and xbrz3 will use 22
|
||||
// "style" - italic and\or bold, indicates font style
|
||||
// "blend" - if set to true, font will be antialiased
|
||||
"trueType":
|
||||
|
@ -271,6 +271,19 @@
|
||||
"portraitYoungYog" : 162
|
||||
}
|
||||
},
|
||||
"chronicles" : {
|
||||
"supported" : true,
|
||||
"iconIndex" : 2,
|
||||
|
||||
"portraits" : {
|
||||
"portraitTarnumBarbarian" : 163,
|
||||
"portraitTarnumKnight" : 164,
|
||||
"portraitTarnumWizard" : 165,
|
||||
"portraitTarnumRanger" : 166,
|
||||
"portraitTarnumOverlord" : 167,
|
||||
"portraitTarnumBeastmaster" : 168
|
||||
}
|
||||
},
|
||||
"jsonVCMI" : {
|
||||
"supported" : true,
|
||||
"iconIndex" : 3
|
||||
|
@ -202,5 +202,179 @@
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
},
|
||||
"portraitTarnumBarbarian" :
|
||||
{
|
||||
"class" : "barbarian",
|
||||
"special" : true,
|
||||
"images": {
|
||||
"large" : "HPL137",
|
||||
"small" : "HPS137",
|
||||
"specialtySmall" : "default",
|
||||
"specialtyLarge" : "default"
|
||||
},
|
||||
"texts" : {
|
||||
"name" : "",
|
||||
"biography" : "",
|
||||
"specialty" : {
|
||||
"description" : "",
|
||||
"tooltip" : "",
|
||||
"name" : ""
|
||||
}
|
||||
},
|
||||
"army" : [
|
||||
{
|
||||
"creature" : "goblin",
|
||||
"min" : 1,
|
||||
"max" : 1
|
||||
}
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
},
|
||||
"portraitTarnumKnight" :
|
||||
{
|
||||
"class" : "knight",
|
||||
"special" : true,
|
||||
"images": {
|
||||
"large" : "HPL138",
|
||||
"small" : "HPS138",
|
||||
"specialtySmall" : "default",
|
||||
"specialtyLarge" : "default"
|
||||
},
|
||||
"texts" : {
|
||||
"name" : "",
|
||||
"biography" : "",
|
||||
"specialty" : {
|
||||
"description" : "",
|
||||
"tooltip" : "",
|
||||
"name" : ""
|
||||
}
|
||||
},
|
||||
"army" : [
|
||||
{
|
||||
"creature" : "pikeman",
|
||||
"min" : 1,
|
||||
"max" : 1
|
||||
}
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
},
|
||||
"portraitTarnumWizard" :
|
||||
{
|
||||
"class" : "wizard",
|
||||
"special" : true,
|
||||
"images": {
|
||||
"large" : "HPL139",
|
||||
"small" : "HPS139",
|
||||
"specialtySmall" : "default",
|
||||
"specialtyLarge" : "default"
|
||||
},
|
||||
"texts" : {
|
||||
"name" : "",
|
||||
"biography" : "",
|
||||
"specialty" : {
|
||||
"description" : "",
|
||||
"tooltip" : "",
|
||||
"name" : ""
|
||||
}
|
||||
},
|
||||
"army" : [
|
||||
{
|
||||
"creature" : "enchanter",
|
||||
"min" : 1,
|
||||
"max" : 1
|
||||
}
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
},
|
||||
"portraitTarnumRanger" :
|
||||
{
|
||||
"class" : "ranger",
|
||||
"special" : true,
|
||||
"images": {
|
||||
"large" : "HPL140",
|
||||
"small" : "HPS140",
|
||||
"specialtySmall" : "default",
|
||||
"specialtyLarge" : "default"
|
||||
},
|
||||
"texts" : {
|
||||
"name" : "",
|
||||
"biography" : "",
|
||||
"specialty" : {
|
||||
"description" : "",
|
||||
"tooltip" : "",
|
||||
"name" : ""
|
||||
}
|
||||
},
|
||||
"army" : [
|
||||
{
|
||||
"creature" : "sharpshooter",
|
||||
"min" : 1,
|
||||
"max" : 1
|
||||
}
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
},
|
||||
"portraitTarnumOverlord" :
|
||||
{
|
||||
"class" : "overlord",
|
||||
"special" : true,
|
||||
"images": {
|
||||
"large" : "HPL141",
|
||||
"small" : "HPS141",
|
||||
"specialtySmall" : "default",
|
||||
"specialtyLarge" : "default"
|
||||
},
|
||||
"texts" : {
|
||||
"name" : "",
|
||||
"biography" : "",
|
||||
"specialty" : {
|
||||
"description" : "",
|
||||
"tooltip" : "",
|
||||
"name" : ""
|
||||
}
|
||||
},
|
||||
"army" : [
|
||||
{
|
||||
"creature" : "troglodyte",
|
||||
"min" : 1,
|
||||
"max" : 1
|
||||
}
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
},
|
||||
"portraitTarnumBeastmaster" :
|
||||
{
|
||||
"class" : "beastmaster",
|
||||
"special" : true,
|
||||
"images": {
|
||||
"large" : "HPL142",
|
||||
"small" : "HPS142",
|
||||
"specialtySmall" : "default",
|
||||
"specialtyLarge" : "default"
|
||||
},
|
||||
"texts" : {
|
||||
"name" : "",
|
||||
"biography" : "",
|
||||
"specialty" : {
|
||||
"description" : "",
|
||||
"tooltip" : "",
|
||||
"name" : ""
|
||||
}
|
||||
},
|
||||
"army" : [
|
||||
{
|
||||
"creature" : "gnoll",
|
||||
"min" : 1,
|
||||
"max" : 1
|
||||
}
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
}
|
||||
}
|
@ -167,6 +167,7 @@
|
||||
"targetfps",
|
||||
"vsync",
|
||||
"upscalingFilter",
|
||||
"fontUpscalingFilter",
|
||||
"downscalingFilter"
|
||||
],
|
||||
"properties" : {
|
||||
@ -231,6 +232,11 @@
|
||||
"type" : "boolean",
|
||||
"default" : true
|
||||
},
|
||||
"fontUpscalingFilter" : {
|
||||
"type" : "string",
|
||||
"enum" : [ "nearest", "bilinear", "xbrz" ],
|
||||
"default" : "nearest"
|
||||
},
|
||||
"upscalingFilter" : {
|
||||
"type" : "string",
|
||||
"enum" : [ "auto", "none", "xbrz2", "xbrz3", "xbrz4" ],
|
||||
|
@ -52,6 +52,9 @@ In header are parameters describing campaign properties
|
||||
- `"campaignVersion"` is creator defined version
|
||||
- `"creationDateTime"` unix time of campaign creation
|
||||
- `"allowDifficultySelection"` is a boolean field (`true`/`false`) which allows or disallows to choose difficulty before scenario start
|
||||
- `"loadingBackground"` is for setting a different loading screen background
|
||||
- `"introVideo"` is for defining an optional intro video
|
||||
- `"introVideoRim"` is for the Rim around the optional video (default is INTRORIM)
|
||||
|
||||
## Scenario description
|
||||
|
||||
|
@ -12,10 +12,12 @@ set(launcher_SRCS
|
||||
modManager/cmodlistview_moc.cpp
|
||||
modManager/cmodmanager.cpp
|
||||
modManager/imageviewer_moc.cpp
|
||||
modManager/chroniclesextractor.cpp
|
||||
settingsView/csettingsview_moc.cpp
|
||||
firstLaunch/firstlaunch_moc.cpp
|
||||
main.cpp
|
||||
helper.cpp
|
||||
innoextract.cpp
|
||||
mainwindow_moc.cpp
|
||||
languages.cpp
|
||||
launcherdirs.cpp
|
||||
@ -42,6 +44,7 @@ set(launcher_HEADERS
|
||||
modManager/cmodlistview_moc.h
|
||||
modManager/cmodmanager.h
|
||||
modManager/imageviewer_moc.h
|
||||
modManager/chroniclesextractor.h
|
||||
settingsView/csettingsview_moc.h
|
||||
firstLaunch/firstlaunch_moc.h
|
||||
mainwindow_moc.h
|
||||
@ -51,6 +54,7 @@ set(launcher_HEADERS
|
||||
updatedialog_moc.h
|
||||
main.h
|
||||
helper.h
|
||||
innoextract.h
|
||||
prepare.h
|
||||
)
|
||||
|
||||
|
@ -21,11 +21,7 @@
|
||||
#include "../../lib/filesystem/Filesystem.h"
|
||||
#include "../helper.h"
|
||||
#include "../languages.h"
|
||||
|
||||
#ifdef ENABLE_INNOEXTRACT
|
||||
#include "cli/extract.hpp"
|
||||
#include "setup/version.hpp"
|
||||
#endif
|
||||
#include "../innoextract.h"
|
||||
|
||||
#ifdef VCMI_IOS
|
||||
#include "ios/selectdirectory.h"
|
||||
@ -386,44 +382,11 @@ void FirstLaunchView::extractGogData()
|
||||
if(isGogGalaxyExe(tmpFileExe))
|
||||
errorText = tr("You've provided GOG Galaxy installer! This file doesn't contain the game. Please download the offline backup game installer!");
|
||||
|
||||
::extract_options o;
|
||||
o.extract = true;
|
||||
|
||||
// standard settings
|
||||
o.gog_galaxy = true;
|
||||
o.codepage = 0U;
|
||||
o.output_dir = tempDir.path().toStdString();
|
||||
o.extract_temp = true;
|
||||
o.extract_unknown = true;
|
||||
o.filenames.set_expand(true);
|
||||
|
||||
o.preserve_file_times = true; // also correctly closes file -> without it: on Windows the files are not written completely
|
||||
|
||||
try
|
||||
{
|
||||
if(errorText.isEmpty())
|
||||
process_file(tmpFileExe.toStdString(), o, [this](float progress) {
|
||||
ui->progressBarGog->setValue(progress * 100);
|
||||
qApp->processEvents();
|
||||
});
|
||||
}
|
||||
catch(const std::ios_base::failure & e)
|
||||
{
|
||||
errorText = tr("Stream error while extracting files!\nerror reason: ");
|
||||
errorText += e.what();
|
||||
}
|
||||
catch(const format_error & e)
|
||||
{
|
||||
errorText = e.what();
|
||||
}
|
||||
catch(const std::runtime_error & e)
|
||||
{
|
||||
errorText = e.what();
|
||||
}
|
||||
catch(const setup::version_error &)
|
||||
{
|
||||
errorText = tr("Not a supported Inno Setup installer!");
|
||||
}
|
||||
if(errorText.isEmpty())
|
||||
errorText = Innoextract::extract(tmpFileExe, tempDir.path(), [this](float progress) {
|
||||
ui->progressBarGog->setValue(progress * 100);
|
||||
qApp->processEvents();
|
||||
});
|
||||
|
||||
ui->progressBarGog->setVisible(false);
|
||||
ui->pushButtonGogInstall->setVisible(true);
|
||||
|
62
launcher/innoextract.cpp
Normal file
62
launcher/innoextract.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* innoextract.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 "innoextract.h"
|
||||
|
||||
#ifdef ENABLE_INNOEXTRACT
|
||||
#include "cli/extract.hpp"
|
||||
#include "setup/version.hpp"
|
||||
#endif
|
||||
|
||||
QString Innoextract::extract(QString installer, QString outDir, std::function<void (float percent)> cb)
|
||||
{
|
||||
QString errorText{};
|
||||
|
||||
#ifdef ENABLE_INNOEXTRACT
|
||||
::extract_options o;
|
||||
o.extract = true;
|
||||
|
||||
// standard settings
|
||||
o.gog_galaxy = true;
|
||||
o.codepage = 0U;
|
||||
o.output_dir = outDir.toStdString();
|
||||
o.extract_temp = true;
|
||||
o.extract_unknown = true;
|
||||
o.filenames.set_expand(true);
|
||||
|
||||
o.preserve_file_times = true; // also correctly closes file -> without it: on Windows the files are not written completely
|
||||
|
||||
try
|
||||
{
|
||||
process_file(installer.toStdString(), o, cb);
|
||||
}
|
||||
catch(const std::ios_base::failure & e)
|
||||
{
|
||||
errorText = tr("Stream error while extracting files!\nerror reason: ");
|
||||
errorText += e.what();
|
||||
}
|
||||
catch(const format_error & e)
|
||||
{
|
||||
errorText = e.what();
|
||||
}
|
||||
catch(const std::runtime_error & e)
|
||||
{
|
||||
errorText = e.what();
|
||||
}
|
||||
catch(const setup::version_error &)
|
||||
{
|
||||
errorText = tr("Not a supported Inno Setup installer!");
|
||||
}
|
||||
#else
|
||||
errorText = tr("VCMI was compiled without innoextract support, which is needed to extract exe files!");
|
||||
#endif
|
||||
|
||||
return errorText;
|
||||
}
|
16
launcher/innoextract.h
Normal file
16
launcher/innoextract.h
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* innoextract.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
|
||||
|
||||
class Innoextract : public QObject
|
||||
{
|
||||
public:
|
||||
static QString extract(QString installer, QString outDir, std::function<void (float percent)> cb = nullptr);
|
||||
};
|
225
launcher/modManager/chroniclesextractor.cpp
Normal file
225
launcher/modManager/chroniclesextractor.cpp
Normal file
@ -0,0 +1,225 @@
|
||||
/*
|
||||
* chroniclesextractor.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 "chroniclesextractor.h"
|
||||
|
||||
#include "../../lib/VCMIDirs.h"
|
||||
#include "../../lib/filesystem/CArchiveLoader.h"
|
||||
|
||||
#include "../innoextract.h"
|
||||
|
||||
ChroniclesExtractor::ChroniclesExtractor(QWidget *p, std::function<void(float percent)> cb) :
|
||||
parent(p), cb(cb)
|
||||
{
|
||||
}
|
||||
|
||||
bool ChroniclesExtractor::createTempDir()
|
||||
{
|
||||
tempDir = QDir(pathToQString(VCMIDirs::get().userDataPath()));
|
||||
if(tempDir.cd("tmp"))
|
||||
{
|
||||
tempDir.removeRecursively(); // remove if already exists (e.g. previous run)
|
||||
tempDir.cdUp();
|
||||
}
|
||||
tempDir.mkdir("tmp");
|
||||
if(!tempDir.cd("tmp"))
|
||||
return false; // should not happen - but avoid deleting wrong folder in any case
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChroniclesExtractor::removeTempDir()
|
||||
{
|
||||
tempDir.removeRecursively();
|
||||
}
|
||||
|
||||
int ChroniclesExtractor::getChronicleNo(QFile & file)
|
||||
{
|
||||
if(!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
QMessageBox::critical(parent, tr("File cannot opened"), file.errorString());
|
||||
return 0;
|
||||
}
|
||||
|
||||
QByteArray magic{"MZ"};
|
||||
QByteArray magicFile = file.read(magic.length());
|
||||
if(!magicFile.startsWith(magic))
|
||||
{
|
||||
QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select an gog installer file!"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
QByteArray dataBegin = file.read(1'000'000);
|
||||
int chronicle = 0;
|
||||
for (const auto& kv : chronicles) {
|
||||
if(dataBegin.contains(kv.second))
|
||||
{
|
||||
chronicle = kv.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!chronicle)
|
||||
{
|
||||
QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select an chronicle installer file!"));
|
||||
return 0;
|
||||
}
|
||||
return chronicle;
|
||||
}
|
||||
|
||||
bool ChroniclesExtractor::extractGogInstaller(QString file)
|
||||
{
|
||||
QString errorText = Innoextract::extract(file, tempDir.path(), [this](float progress) {
|
||||
float overallProgress = ((1.0 / static_cast<float>(fileCount)) * static_cast<float>(extractionFile)) + (progress / static_cast<float>(fileCount));
|
||||
if(cb)
|
||||
cb(overallProgress);
|
||||
});
|
||||
|
||||
if(!errorText.isEmpty())
|
||||
{
|
||||
QMessageBox::critical(parent, tr("Extracting error!"), errorText);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChroniclesExtractor::createBaseMod() const
|
||||
{
|
||||
QDir dir(pathToQString(VCMIDirs::get().userDataPath() / "Mods"));
|
||||
dir.mkdir("chronicles");
|
||||
dir.cd("chronicles");
|
||||
dir.mkdir("Mods");
|
||||
|
||||
QJsonObject mod
|
||||
{
|
||||
{ "modType", "Expansion" },
|
||||
{ "name", tr("Heroes Chronicles") },
|
||||
{ "description", tr("Heroes Chronicles") },
|
||||
{ "author", "3DO" },
|
||||
{ "version", "1.0" },
|
||||
{ "contact", "vcmi.eu" },
|
||||
};
|
||||
|
||||
QFile jsonFile(dir.filePath("mod.json"));
|
||||
jsonFile.open(QFile::WriteOnly);
|
||||
jsonFile.write(QJsonDocument(mod).toJson());
|
||||
}
|
||||
|
||||
void ChroniclesExtractor::createChronicleMod(int no)
|
||||
{
|
||||
QDir dir(pathToQString(VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "Mods" / ("chronicles_" + std::to_string(no))));
|
||||
dir.removeRecursively();
|
||||
dir.mkpath(".");
|
||||
|
||||
QByteArray tmpChronicles = chronicles.at(no);
|
||||
tmpChronicles.replace('\0', "");
|
||||
|
||||
QJsonObject mod
|
||||
{
|
||||
{ "modType", "Expansion" },
|
||||
{ "name", QString::number(no) + " - " + QString(tmpChronicles) },
|
||||
{ "description", tr("Heroes Chronicles") + " - " + QString::number(no) + " - " + QString(tmpChronicles) },
|
||||
{ "author", "3DO" },
|
||||
{ "version", "1.0" },
|
||||
{ "contact", "vcmi.eu" },
|
||||
};
|
||||
|
||||
QFile jsonFile(dir.filePath("mod.json"));
|
||||
jsonFile.open(QFile::WriteOnly);
|
||||
jsonFile.write(QJsonDocument(mod).toJson());
|
||||
|
||||
dir.cd("content");
|
||||
|
||||
extractFiles(no);
|
||||
}
|
||||
|
||||
void ChroniclesExtractor::extractFiles(int no) const
|
||||
{
|
||||
QByteArray tmpChronicles = chronicles.at(no);
|
||||
tmpChronicles.replace('\0', "");
|
||||
|
||||
std::string chroniclesDir = "chronicles_" + std::to_string(no);
|
||||
QDir tmpDir = tempDir.filePath(tempDir.entryList({"app"}, QDir::Filter::Dirs).front());
|
||||
tmpDir.setPath(tmpDir.filePath(tmpDir.entryList({QString(tmpChronicles)}, QDir::Filter::Dirs).front()));
|
||||
tmpDir.setPath(tmpDir.filePath(tmpDir.entryList({"data"}, QDir::Filter::Dirs).front()));
|
||||
auto basePath = VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "Mods" / chroniclesDir / "content";
|
||||
QDir outDirDataPortraits(pathToQString(VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "content" / "Data"));
|
||||
QDir outDirData(pathToQString(basePath / "Data" / chroniclesDir));
|
||||
QDir outDirSprites(pathToQString(basePath / "Sprites" / chroniclesDir));
|
||||
QDir outDirVideo(pathToQString(basePath / "Video" / chroniclesDir));
|
||||
QDir outDirSounds(pathToQString(basePath / "Sounds" / chroniclesDir));
|
||||
QDir outDirMaps(pathToQString(basePath / "Maps"));
|
||||
|
||||
auto extract = [](QDir scrDir, QDir dest, QString file, std::vector<std::string> files = {}){
|
||||
CArchiveLoader archive("", scrDir.filePath(scrDir.entryList({file}).front()).toStdString(), false);
|
||||
for(auto & entry : archive.getEntries())
|
||||
if(files.empty())
|
||||
archive.extractToFolder(dest.absolutePath().toStdString(), "", entry.second, true);
|
||||
else
|
||||
{
|
||||
for(const auto & item : files)
|
||||
if(boost::algorithm::to_lower_copy(entry.second.name).find(boost::algorithm::to_lower_copy(item)) != std::string::npos)
|
||||
archive.extractToFolder(dest.absolutePath().toStdString(), "", entry.second, true);
|
||||
}
|
||||
};
|
||||
|
||||
extract(tmpDir, outDirData, "xBitmap.lod");
|
||||
extract(tmpDir, outDirData, "xlBitmap.lod");
|
||||
extract(tmpDir, outDirSprites, "xSprite.lod");
|
||||
extract(tmpDir, outDirSprites, "xlSprite.lod");
|
||||
extract(tmpDir, outDirVideo, "xVideo.vid");
|
||||
extract(tmpDir, outDirSounds, "xSound.snd");
|
||||
|
||||
tmpDir.cdUp();
|
||||
if(tmpDir.entryList({"maps"}, QDir::Filter::Dirs).size()) // special case for "The World Tree": the map is in the "Maps" folder instead of inside the lod
|
||||
{
|
||||
QDir tmpDirMaps = tmpDir.filePath(tmpDir.entryList({"maps"}, QDir::Filter::Dirs).front());
|
||||
for(const auto & entry : tmpDirMaps.entryList())
|
||||
QFile(tmpDirMaps.filePath(entry)).copy(outDirData.filePath(entry));
|
||||
}
|
||||
|
||||
tmpDir.cdUp();
|
||||
QDir tmpDirData = tmpDir.filePath(tmpDir.entryList({"data"}, QDir::Filter::Dirs).front());
|
||||
auto tarnumPortraits = std::vector<std::string>{"HPS137", "HPS138", "HPS139", "HPS140", "HPS141", "HPS142", "HPL137", "HPL138", "HPL139", "HPL140", "HPL141", "HPL142"};
|
||||
extract(tmpDirData, outDirDataPortraits, "bitmap.lod", tarnumPortraits);
|
||||
extract(tmpDirData, outDirData, "lbitmap.lod", std::vector<std::string>{"INTRORIM"});
|
||||
|
||||
if(!outDirMaps.exists())
|
||||
outDirMaps.mkpath(".");
|
||||
QString campaignFileName = "Hc" + QString::number(no) + "_Main.h3c";
|
||||
QFile(outDirData.filePath(outDirData.entryList({"Main.h3c"}).front())).copy(outDirMaps.filePath(campaignFileName));
|
||||
}
|
||||
|
||||
void ChroniclesExtractor::installChronicles(QStringList exe)
|
||||
{
|
||||
extractionFile = -1;
|
||||
fileCount = exe.size();
|
||||
for(QString f : exe)
|
||||
{
|
||||
extractionFile++;
|
||||
QFile file(f);
|
||||
|
||||
int chronicleNo = getChronicleNo(file);
|
||||
if(!chronicleNo)
|
||||
continue;
|
||||
|
||||
if(!createTempDir())
|
||||
continue;
|
||||
|
||||
if(!extractGogInstaller(f))
|
||||
continue;
|
||||
|
||||
createBaseMod();
|
||||
createChronicleMod(chronicleNo);
|
||||
|
||||
removeTempDir();
|
||||
}
|
||||
}
|
47
launcher/modManager/chroniclesextractor.h
Normal file
47
launcher/modManager/chroniclesextractor.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* chroniclesextractor.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 "../StdInc.h"
|
||||
|
||||
class ChroniclesExtractor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QWidget *parent;
|
||||
std::function<void(float percent)> cb;
|
||||
|
||||
QDir tempDir;
|
||||
int extractionFile;
|
||||
int fileCount;
|
||||
|
||||
bool createTempDir();
|
||||
void removeTempDir();
|
||||
int getChronicleNo(QFile & file);
|
||||
bool extractGogInstaller(QString filePath);
|
||||
void createBaseMod() const;
|
||||
void createChronicleMod(int no);
|
||||
void extractFiles(int no) const;
|
||||
|
||||
const std::map<int, QByteArray> chronicles = {
|
||||
{1, QByteArray{reinterpret_cast<const char*>(u"Warlords of the Wasteland"), 50}},
|
||||
{2, QByteArray{reinterpret_cast<const char*>(u"Conquest of the Underworld"), 52}},
|
||||
{3, QByteArray{reinterpret_cast<const char*>(u"Masters of the Elements"), 46}},
|
||||
{4, QByteArray{reinterpret_cast<const char*>(u"Clash of the Dragons"), 40}},
|
||||
{5, QByteArray{reinterpret_cast<const char*>(u"The World Tree"), 28}},
|
||||
{6, QByteArray{reinterpret_cast<const char*>(u"The Fiery Moon"), 28}},
|
||||
{7, QByteArray{reinterpret_cast<const char*>(u"Revolt of the Beastmasters"), 52}},
|
||||
{8, QByteArray{reinterpret_cast<const char*>(u"The Sword of Frost"), 36}}
|
||||
};
|
||||
public:
|
||||
void installChronicles(QStringList exe);
|
||||
|
||||
ChroniclesExtractor(QWidget *p, std::function<void(float percent)> cb = nullptr);
|
||||
};
|
@ -20,6 +20,7 @@
|
||||
#include "cmodlistmodel_moc.h"
|
||||
#include "cmodmanager.h"
|
||||
#include "cdownloadmanager_moc.h"
|
||||
#include "chroniclesextractor.h"
|
||||
#include "../settingsView/csettingsview_moc.h"
|
||||
#include "../launcherdirs.h"
|
||||
#include "../jsonutils.h"
|
||||
@ -29,6 +30,9 @@
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/texts/Languages.h"
|
||||
#include "../../lib/modding/CModVersion.h"
|
||||
#include "../../lib/filesystem/Filesystem.h"
|
||||
|
||||
#include <future>
|
||||
|
||||
static double mbToBytes(double mb)
|
||||
{
|
||||
@ -55,7 +59,7 @@ void CModListView::dragEnterEvent(QDragEnterEvent* event)
|
||||
{
|
||||
if(event->mimeData()->hasUrls())
|
||||
for(const auto & url : event->mimeData()->urls())
|
||||
for(const auto & ending : QStringList({".zip", ".h3m", ".h3c", ".vmap", ".vcmp", ".json"}))
|
||||
for(const auto & ending : QStringList({".zip", ".h3m", ".h3c", ".vmap", ".vcmp", ".json", ".exe"}))
|
||||
if(url.fileName().endsWith(ending, Qt::CaseInsensitive))
|
||||
{
|
||||
event->acceptProposedAction();
|
||||
@ -636,8 +640,16 @@ void CModListView::on_installFromFileButton_clicked()
|
||||
// https://bugreports.qt.io/browse/QTBUG-98651
|
||||
QTimer::singleShot(0, this, [this]
|
||||
{
|
||||
QString filter = tr("All supported files") + " (*.h3m *.vmap *.h3c *.vcmp *.zip *.json);;" + tr("Maps") + " (*.h3m *.vmap);;" + tr("Campaigns") + " (*.h3c *.vcmp);;" + tr("Configs") + " (*.json);;" + tr("Mods") + " (*.zip)";
|
||||
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files (configs, mods, maps, campaigns) to install..."), QDir::homePath(), filter);
|
||||
QString filter = tr("All supported files") + " (*.h3m *.vmap *.h3c *.vcmp *.zip *.json *.exe);;" +
|
||||
tr("Maps") + " (*.h3m *.vmap);;" +
|
||||
tr("Campaigns") + " (*.h3c *.vcmp);;" +
|
||||
tr("Configs") + " (*.json);;" +
|
||||
tr("Mods") + " (*.zip);;" +
|
||||
tr("Gog files") + " (*.exe)";
|
||||
#if defined(VCMI_MOBILE)
|
||||
filter = tr("All files (*.*)"); //Workaround for sometimes incorrect mime for some extensions (e.g. for exe)
|
||||
#endif
|
||||
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files (configs, mods, maps, campaigns, gog files) to install..."), QDir::homePath(), filter);
|
||||
|
||||
for(const auto & file : files)
|
||||
{
|
||||
@ -786,6 +798,7 @@ void CModListView::installFiles(QStringList files)
|
||||
QStringList mods;
|
||||
QStringList maps;
|
||||
QStringList images;
|
||||
QStringList exe;
|
||||
QVector<QVariantMap> repositories;
|
||||
|
||||
// TODO: some better way to separate zip's with mods and downloaded repository files
|
||||
@ -795,6 +808,8 @@ void CModListView::installFiles(QStringList files)
|
||||
mods.push_back(filename);
|
||||
else if(filename.endsWith(".h3m", Qt::CaseInsensitive) || filename.endsWith(".h3c", Qt::CaseInsensitive) || filename.endsWith(".vmap", Qt::CaseInsensitive) || filename.endsWith(".vcmp", Qt::CaseInsensitive))
|
||||
maps.push_back(filename);
|
||||
if(filename.endsWith(".exe", Qt::CaseInsensitive))
|
||||
exe.push_back(filename);
|
||||
else if(filename.endsWith(".json", Qt::CaseInsensitive))
|
||||
{
|
||||
//download and merge additional files
|
||||
@ -832,6 +847,35 @@ void CModListView::installFiles(QStringList files)
|
||||
if(!maps.empty())
|
||||
installMaps(maps);
|
||||
|
||||
if(!exe.empty())
|
||||
{
|
||||
ui->progressBar->setFormat(tr("Installing chronicles"));
|
||||
|
||||
float prog = 0.0;
|
||||
|
||||
auto futureExtract = std::async(std::launch::async, [this, exe, &prog]()
|
||||
{
|
||||
ChroniclesExtractor ce(this, [&prog](float progress) { prog = progress; });
|
||||
ce.installChronicles(exe);
|
||||
return true;
|
||||
});
|
||||
|
||||
while(futureExtract.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready)
|
||||
{
|
||||
emit extractionProgress(static_cast<int>(prog * 1000.f), 1000);
|
||||
qApp->processEvents();
|
||||
}
|
||||
|
||||
if(futureExtract.get())
|
||||
{
|
||||
//update
|
||||
CResourceHandler::get("initial")->updateFilteredFiles([](const std::string &){ return true; });
|
||||
manager->loadMods();
|
||||
modModel->reloadRepositories();
|
||||
emit modsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
if(!images.empty())
|
||||
loadScreenshots();
|
||||
}
|
||||
|
@ -79,6 +79,7 @@ void GameSettings::load(const JsonNode & input)
|
||||
{EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" },
|
||||
{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" },
|
||||
{EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" },
|
||||
{EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" },
|
||||
{EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" },
|
||||
{EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" },
|
||||
{EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" },
|
||||
|
@ -58,6 +58,7 @@ enum class EGameSettings
|
||||
MAP_FORMAT_RESTORATION_OF_ERATHIA,
|
||||
MAP_FORMAT_ARMAGEDDONS_BLADE,
|
||||
MAP_FORMAT_SHADOW_OF_DEATH,
|
||||
MAP_FORMAT_CHRONICLES,
|
||||
MAP_FORMAT_HORN_OF_THE_ABYSS,
|
||||
MAP_FORMAT_JSON_VCMI,
|
||||
MAP_FORMAT_IN_THE_WAKE_OF_GODS,
|
||||
|
@ -104,8 +104,8 @@ int BonusList::totalValue() const
|
||||
};
|
||||
|
||||
BonusCollection accumulated;
|
||||
bool hasIndepMax = false;
|
||||
bool hasIndepMin = false;
|
||||
int indexMaxCount = 0;
|
||||
int indexMinCount = 0;
|
||||
|
||||
std::array<int, vstd::to_underlying(BonusSource::NUM_BONUS_SOURCE)> percentToSource = {};
|
||||
|
||||
@ -141,12 +141,12 @@ int BonusList::totalValue() const
|
||||
case BonusValueType::ADDITIVE_VALUE:
|
||||
accumulated.additive += valModified;
|
||||
break;
|
||||
case BonusValueType::INDEPENDENT_MAX:
|
||||
hasIndepMax = true;
|
||||
case BonusValueType::INDEPENDENT_MAX: // actual meaning: at least this value
|
||||
indexMaxCount++;
|
||||
vstd::amax(accumulated.indepMax, valModified);
|
||||
break;
|
||||
case BonusValueType::INDEPENDENT_MIN:
|
||||
hasIndepMin = true;
|
||||
case BonusValueType::INDEPENDENT_MIN: // actual meaning: at most this value
|
||||
indexMinCount++;
|
||||
vstd::amin(accumulated.indepMin, valModified);
|
||||
break;
|
||||
}
|
||||
@ -156,21 +156,18 @@ int BonusList::totalValue() const
|
||||
accumulated.base += accumulated.additive;
|
||||
auto valFirst = applyPercentage(accumulated.base ,accumulated.percentToAll);
|
||||
|
||||
if(hasIndepMin && hasIndepMax && accumulated.indepMin < accumulated.indepMax)
|
||||
if(indexMinCount && indexMaxCount && accumulated.indepMin < accumulated.indepMax)
|
||||
accumulated.indepMax = accumulated.indepMin;
|
||||
|
||||
const int notIndepBonuses = static_cast<int>(std::count_if(bonuses.cbegin(), bonuses.cend(), [](const std::shared_ptr<Bonus>& b)
|
||||
{
|
||||
return b->valType != BonusValueType::INDEPENDENT_MAX && b->valType != BonusValueType::INDEPENDENT_MIN;
|
||||
}));
|
||||
const int notIndepBonuses = bonuses.size() - indexMaxCount - indexMinCount;
|
||||
|
||||
if(notIndepBonuses)
|
||||
return std::clamp(valFirst, accumulated.indepMax, accumulated.indepMin);
|
||||
|
||||
if (hasIndepMin)
|
||||
if (indexMinCount)
|
||||
return accumulated.indepMin;
|
||||
|
||||
if (hasIndepMax)
|
||||
if (indexMaxCount)
|
||||
return accumulated.indepMax;
|
||||
|
||||
return 0;
|
||||
|
@ -18,7 +18,7 @@ enum class CampaignVersion : uint8_t
|
||||
AB = 5,
|
||||
SoD = 6,
|
||||
WoG = 6,
|
||||
// Chr = 7, // Heroes Chronicles, likely identical to SoD, untested
|
||||
Chr = 7,
|
||||
|
||||
VCMI = 1,
|
||||
VCMI_MIN = 1,
|
||||
|
@ -38,12 +38,14 @@ void CampaignHandler::readCampaign(Campaign * ret, const std::vector<ui8> & inpu
|
||||
CBinaryReader reader(&stream);
|
||||
|
||||
readHeaderFromMemory(*ret, reader, filename, modName, encoding);
|
||||
ret->overrideCampaign();
|
||||
|
||||
for(int g = 0; g < ret->numberOfScenarios; ++g)
|
||||
{
|
||||
auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
|
||||
ret->scenarios[scenarioID] = readScenarioFromMemory(reader, *ret);
|
||||
}
|
||||
ret->overrideCampaignScenarios();
|
||||
}
|
||||
else // text format (json)
|
||||
{
|
||||
@ -166,6 +168,9 @@ void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader
|
||||
ret.filename = filename;
|
||||
ret.modName = modName;
|
||||
ret.encoding = encoding;
|
||||
ret.loadingBackground = ImagePath::fromJson(reader["loadingBackground"]);
|
||||
ret.introVideoRim = ImagePath::fromJson(reader["introVideoRim"]);
|
||||
ret.introVideo = VideoPath::fromJson(reader["introVideo"]);
|
||||
}
|
||||
|
||||
CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
|
||||
@ -392,7 +397,8 @@ void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader
|
||||
{
|
||||
ret.version = static_cast<CampaignVersion>(reader.readUInt32());
|
||||
ui8 campId = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19]
|
||||
ret.loadLegacyData(campId);
|
||||
if(ret.version != CampaignVersion::Chr) // For chronicles: Will be overridden later; Chronicles uses own logic (reusing OH3 ID's)
|
||||
ret.loadLegacyData(campId);
|
||||
ret.name.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "name"));
|
||||
ret.description.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "description"));
|
||||
ret.author.appendRawString("");
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "../mapObjects/CGHeroInstance.h"
|
||||
#include "../serializer/JsonDeserializer.h"
|
||||
#include "../serializer/JsonSerializer.h"
|
||||
#include "../json/JsonUtils.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -138,6 +139,12 @@ void CampaignHeader::loadLegacyData(ui8 campId)
|
||||
numberOfScenarios = VLC->generaltexth->getCampaignLength(campId);
|
||||
}
|
||||
|
||||
void CampaignHeader::loadLegacyData(CampaignRegions regions, int numOfScenario)
|
||||
{
|
||||
campaignRegions = regions;
|
||||
numberOfScenarios = numOfScenario;
|
||||
}
|
||||
|
||||
bool CampaignHeader::playerSelectedDifficulty() const
|
||||
{
|
||||
return difficultyChosenByPlayer;
|
||||
@ -198,6 +205,21 @@ AudioPath CampaignHeader::getMusic() const
|
||||
return music;
|
||||
}
|
||||
|
||||
ImagePath CampaignHeader::getLoadingBackground() const
|
||||
{
|
||||
return loadingBackground;
|
||||
}
|
||||
|
||||
ImagePath CampaignHeader::getIntroVideoRim() const
|
||||
{
|
||||
return introVideoRim;
|
||||
}
|
||||
|
||||
VideoPath CampaignHeader::getIntroVideo() const
|
||||
{
|
||||
return introVideo;
|
||||
}
|
||||
|
||||
const CampaignRegions & CampaignHeader::getRegions() const
|
||||
{
|
||||
return campaignRegions;
|
||||
@ -455,6 +477,45 @@ std::set<CampaignScenarioID> Campaign::allScenarios() const
|
||||
return result;
|
||||
}
|
||||
|
||||
void Campaign::overrideCampaign()
|
||||
{
|
||||
const JsonNode node = JsonUtils::assembleFromFiles("config/campaignOverrides.json");
|
||||
for (auto & entry : node.Struct())
|
||||
if(filename == entry.first)
|
||||
{
|
||||
if(!entry.second["regions"].isNull() && !entry.second["scenarioCount"].isNull())
|
||||
loadLegacyData(CampaignRegions::fromJson(entry.second["regions"]), entry.second["scenarioCount"].Integer());
|
||||
if(!entry.second["loadingBackground"].isNull())
|
||||
loadingBackground = ImagePath::builtin(entry.second["loadingBackground"].String());
|
||||
if(!entry.second["introVideoRim"].isNull())
|
||||
introVideoRim = ImagePath::builtin(entry.second["introVideoRim"].String());
|
||||
if(!entry.second["introVideo"].isNull())
|
||||
introVideo = VideoPath::builtin(entry.second["introVideo"].String());
|
||||
}
|
||||
}
|
||||
|
||||
void Campaign::overrideCampaignScenarios()
|
||||
{
|
||||
const JsonNode node = JsonUtils::assembleFromFiles("config/campaignOverrides.json");
|
||||
for (auto & entry : node.Struct())
|
||||
if(filename == entry.first)
|
||||
{
|
||||
if(!entry.second["scenarios"].isNull())
|
||||
{
|
||||
auto sc = entry.second["scenarios"].Vector();
|
||||
for(int i = 0; i < sc.size(); i++)
|
||||
{
|
||||
auto it = scenarios.begin();
|
||||
std::advance(it, i);
|
||||
if(!sc.at(i)["voiceProlog"].isNull())
|
||||
it->second.prolog.prologVoice = AudioPath::builtin(sc.at(i)["voiceProlog"].String());
|
||||
if(!sc.at(i)["voiceEpilog"].isNull())
|
||||
it->second.epilog.prologVoice = AudioPath::builtin(sc.at(i)["voiceEpilog"].String());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Campaign::scenariosCount() const
|
||||
{
|
||||
return allScenarios().size();
|
||||
|
@ -83,6 +83,7 @@ public:
|
||||
class DLL_LINKAGE CampaignHeader : public boost::noncopyable
|
||||
{
|
||||
friend class CampaignHandler;
|
||||
friend class Campaign;
|
||||
|
||||
CampaignVersion version = CampaignVersion::NONE;
|
||||
CampaignRegions campaignRegions;
|
||||
@ -96,11 +97,15 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable
|
||||
std::string filename;
|
||||
std::string modName;
|
||||
std::string encoding;
|
||||
ImagePath loadingBackground;
|
||||
ImagePath introVideoRim;
|
||||
VideoPath introVideo;
|
||||
|
||||
int numberOfScenarios = 0;
|
||||
bool difficultyChosenByPlayer = false;
|
||||
|
||||
void loadLegacyData(ui8 campId);
|
||||
void loadLegacyData(CampaignRegions regions, int numOfScenario);
|
||||
|
||||
TextContainerRegistrable textContainer;
|
||||
|
||||
@ -118,6 +123,9 @@ public:
|
||||
std::string getModName() const;
|
||||
std::string getEncoding() const;
|
||||
AudioPath getMusic() const;
|
||||
ImagePath getLoadingBackground() const;
|
||||
ImagePath getIntroVideoRim() const;
|
||||
VideoPath getIntroVideo() const;
|
||||
|
||||
const CampaignRegions & getRegions() const;
|
||||
TextContainerRegistrable & getTexts();
|
||||
@ -142,6 +150,12 @@ public:
|
||||
h & music;
|
||||
h & encoding;
|
||||
h & textContainer;
|
||||
if (h.version >= Handler::Version::CHRONICLES_SUPPORT)
|
||||
{
|
||||
h & loadingBackground;
|
||||
h & introVideoRim;
|
||||
h & introVideo;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -247,6 +261,9 @@ public:
|
||||
std::set<CampaignScenarioID> allScenarios() const;
|
||||
int scenariosCount() const;
|
||||
|
||||
void overrideCampaign();
|
||||
void overrideCampaignScenarios();
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & static_cast<CampaignHeader&>(*this);
|
||||
|
@ -197,6 +197,11 @@ std::string CArchiveLoader::getMountPoint() const
|
||||
return mountPoint;
|
||||
}
|
||||
|
||||
const std::unordered_map<ResourcePath, ArchiveEntry> & CArchiveLoader::getEntries() const
|
||||
{
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::unordered_set<ResourcePath> CArchiveLoader::getFilteredFiles(std::function<bool(const ResourcePath &)> filter) const
|
||||
{
|
||||
std::unordered_set<ResourcePath> foundID;
|
||||
@ -209,7 +214,7 @@ std::unordered_set<ResourcePath> CArchiveLoader::getFilteredFiles(std::function<
|
||||
return foundID;
|
||||
}
|
||||
|
||||
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, const ArchiveEntry & entry) const
|
||||
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, const ArchiveEntry & entry, bool absolute) const
|
||||
{
|
||||
si64 currentPosition = fileStream.tell(); // save filestream position
|
||||
|
||||
@ -217,7 +222,7 @@ void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, CInput
|
||||
fileStream.seek(entry.offset);
|
||||
fileStream.read(data.data(), entry.fullSize);
|
||||
|
||||
boost::filesystem::path extractedFilePath = createExtractedFilePath(outputSubFolder, entry.name);
|
||||
boost::filesystem::path extractedFilePath = createExtractedFilePath(outputSubFolder, entry.name, absolute);
|
||||
|
||||
// writeToOutputFile
|
||||
std::ofstream out(extractedFilePath.string(), std::ofstream::binary);
|
||||
@ -227,17 +232,17 @@ void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, CInput
|
||||
fileStream.seek(currentPosition); // restore filestream position
|
||||
}
|
||||
|
||||
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry) const
|
||||
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry, bool absolute) const
|
||||
{
|
||||
std::unique_ptr<CInputStream> inputStream = load(ResourcePath(mountPoint + entry.name));
|
||||
|
||||
entry.offset = 0;
|
||||
extractToFolder(outputSubFolder, *inputStream, entry);
|
||||
extractToFolder(outputSubFolder, *inputStream, entry, absolute);
|
||||
}
|
||||
|
||||
boost::filesystem::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName)
|
||||
boost::filesystem::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName, bool absolute)
|
||||
{
|
||||
boost::filesystem::path extractionFolderPath = VCMIDirs::get().userExtractedPath() / outputSubFolder;
|
||||
boost::filesystem::path extractionFolderPath = absolute ? outputSubFolder : VCMIDirs::get().userExtractedPath() / outputSubFolder;
|
||||
boost::filesystem::path extractedFilePath = extractionFolderPath / entryName;
|
||||
|
||||
boost::filesystem::create_directories(extractionFolderPath);
|
||||
|
@ -63,12 +63,13 @@ public:
|
||||
std::unique_ptr<CInputStream> load(const ResourcePath & resourceName) const override;
|
||||
bool existsResource(const ResourcePath & resourceName) const override;
|
||||
std::string getMountPoint() const override;
|
||||
const std::unordered_map<ResourcePath, ArchiveEntry> & getEntries() const;
|
||||
void updateFilteredFiles(std::function<bool(const std::string &)> filter) const override {}
|
||||
std::unordered_set<ResourcePath> getFilteredFiles(std::function<bool(const ResourcePath &)> filter) const override;
|
||||
/** Extracts one archive entry to the specified subfolder. Used for Video and Sound */
|
||||
void extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, const ArchiveEntry & entry) const;
|
||||
void extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, const ArchiveEntry & entry, bool absolute = false) const;
|
||||
/** Extracts one archive entry to the specified subfolder. Used for Images, Sprites, etc */
|
||||
void extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry) const;
|
||||
void extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry, bool absolute = false) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
@ -105,6 +106,6 @@ private:
|
||||
};
|
||||
|
||||
/** Constructs the file path for the extracted file. Creates the subfolder hierarchy aswell **/
|
||||
boost::filesystem::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName);
|
||||
boost::filesystem::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName, bool absolute);
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -536,7 +536,14 @@ void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
|
||||
const IOwnableObject * CGDwelling::asOwnable() const
|
||||
{
|
||||
return this;
|
||||
switch (ID.toEnum())
|
||||
{
|
||||
case Obj::WAR_MACHINE_FACTORY:
|
||||
case Obj::REFUGEE_CAMP:
|
||||
return nullptr; // can't be owned
|
||||
default:
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
ResourceSet CGDwelling::dailyIncome() const
|
||||
|
@ -1227,6 +1227,21 @@ BoatId CGShipyard::getBoatType() const
|
||||
return createdBoat;
|
||||
}
|
||||
|
||||
const IOwnableObject * CGShipyard::asOwnable() const
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
ResourceSet CGShipyard::dailyIncome() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<CreatureID> CGShipyard::providedCreatures() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void CGDenOfthieves::onHeroVisit (const CGHeroInstance * h) const
|
||||
{
|
||||
cb->showObjectWindow(this, EOpenWindowMode::THIEVES_GUILD, h, false);
|
||||
|
@ -346,7 +346,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CGShipyard : public CGObjectInstance, public IShipyard
|
||||
class DLL_LINKAGE CGShipyard : public CGObjectInstance, public IShipyard, public IOwnableObject
|
||||
{
|
||||
friend class ShipyardInstanceConstructor;
|
||||
|
||||
@ -358,6 +358,10 @@ protected:
|
||||
const IObjectInterface * getObject() const override;
|
||||
BoatId getBoatType() const override;
|
||||
|
||||
const IOwnableObject * asOwnable() const final;
|
||||
ResourceSet dailyIncome() const override;
|
||||
std::vector<CreatureID> providedCreatures() const override;
|
||||
|
||||
public:
|
||||
using CGObjectInstance::CGObjectInstance;
|
||||
|
||||
|
@ -172,6 +172,8 @@ int CMapInfo::getMapSizeFormatIconId() const
|
||||
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["iconIndex"].Integer();
|
||||
case EMapFormat::SOD:
|
||||
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH)["iconIndex"].Integer();
|
||||
case EMapFormat::CHR:
|
||||
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES)["iconIndex"].Integer();
|
||||
case EMapFormat::WOG:
|
||||
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["iconIndex"].Integer();
|
||||
case EMapFormat::HOTA:
|
||||
|
@ -157,6 +157,7 @@ std::unique_ptr<IMapLoader> CMapService::getMapLoader(std::unique_ptr<CInputStre
|
||||
case static_cast<int>(EMapFormat::AB) :
|
||||
case static_cast<int>(EMapFormat::ROE) :
|
||||
case static_cast<int>(EMapFormat::SOD) :
|
||||
case static_cast<int>(EMapFormat::CHR) :
|
||||
case static_cast<int>(EMapFormat::HOTA) :
|
||||
return std::unique_ptr<IMapLoader>(new CMapLoaderH3M(mapName, modName, encoding, stream.get()));
|
||||
default :
|
||||
|
@ -25,6 +25,8 @@ MapFormatFeaturesH3M MapFormatFeaturesH3M::find(EMapFormat format, uint32_t hota
|
||||
return getFeaturesAB();
|
||||
case EMapFormat::SOD:
|
||||
return getFeaturesSOD();
|
||||
case EMapFormat::CHR:
|
||||
return getFeaturesCHR();
|
||||
case EMapFormat::WOG:
|
||||
return getFeaturesWOG();
|
||||
case EMapFormat::HOTA:
|
||||
@ -107,6 +109,16 @@ MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesSOD()
|
||||
return result;
|
||||
}
|
||||
|
||||
MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesCHR()
|
||||
{
|
||||
MapFormatFeaturesH3M result = getFeaturesSOD();
|
||||
result.levelCHR = true;
|
||||
|
||||
result.heroesPortraitsCount = 169; // +6x tarnum
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesWOG()
|
||||
{
|
||||
MapFormatFeaturesH3M result = getFeaturesSOD();
|
||||
|
@ -21,6 +21,7 @@ public:
|
||||
static MapFormatFeaturesH3M getFeaturesROE();
|
||||
static MapFormatFeaturesH3M getFeaturesAB();
|
||||
static MapFormatFeaturesH3M getFeaturesSOD();
|
||||
static MapFormatFeaturesH3M getFeaturesCHR();
|
||||
static MapFormatFeaturesH3M getFeaturesWOG();
|
||||
static MapFormatFeaturesH3M getFeaturesHOTA(uint32_t hotaVersion);
|
||||
|
||||
@ -64,6 +65,7 @@ public:
|
||||
bool levelROE = false;
|
||||
bool levelAB = false;
|
||||
bool levelSOD = false;
|
||||
bool levelCHR = false;
|
||||
bool levelWOG = false;
|
||||
bool levelHOTA0 = false;
|
||||
bool levelHOTA1 = false;
|
||||
|
@ -19,7 +19,7 @@ enum class EMapFormat : uint8_t
|
||||
ROE = 0x0e, // 14
|
||||
AB = 0x15, // 21
|
||||
SOD = 0x1c, // 28
|
||||
// CHR = 0x1d, // 29 Heroes Chronicles, presumably - identical to SoD, untested
|
||||
CHR = 0x1d, // 29
|
||||
HOTA = 0x20, // 32
|
||||
WOG = 0x33, // 51
|
||||
VCMI = 0x64
|
||||
|
@ -135,6 +135,8 @@ static MapIdentifiersH3M generateMapping(EMapFormat format)
|
||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE));
|
||||
if(features.levelSOD)
|
||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH));
|
||||
if(features.levelCHR)
|
||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES));
|
||||
if(features.levelWOG)
|
||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
|
||||
if(features.levelHOTA0)
|
||||
@ -161,6 +163,7 @@ static std::map<EMapFormat, MapIdentifiersH3M> generateMappings()
|
||||
addMapping(EMapFormat::ROE);
|
||||
addMapping(EMapFormat::AB);
|
||||
addMapping(EMapFormat::SOD);
|
||||
addMapping(EMapFormat::CHR);
|
||||
addMapping(EMapFormat::HOTA);
|
||||
addMapping(EMapFormat::WOG);
|
||||
|
||||
|
@ -30,9 +30,6 @@ CMapGenOptions::CMapGenOptions()
|
||||
customizedPlayers(false)
|
||||
{
|
||||
initPlayersMap();
|
||||
setRoadEnabled(RoadId(Road::DIRT_ROAD), true);
|
||||
setRoadEnabled(RoadId(Road::GRAVEL_ROAD), true);
|
||||
setRoadEnabled(RoadId(Road::COBBLESTONE_ROAD), true);
|
||||
}
|
||||
|
||||
si32 CMapGenOptions::getWidth() const
|
||||
|
@ -56,6 +56,7 @@ enum class ESerializationVersion : int32_t
|
||||
NEW_MARKETS, // 857 - reworked market classes
|
||||
PLAYER_STATE_OWNED_OBJECTS, // 858 - player state stores all owned objects in a single list
|
||||
SAVE_COMPATIBILITY_FIXES, // 859 - implementation of previoulsy postponed changes to serialization
|
||||
CHRONICLES_SUPPORT, // 860 - support for heroes chronicles
|
||||
|
||||
CURRENT = SAVE_COMPATIBILITY_FIXES
|
||||
CURRENT = CHRONICLES_SUPPORT
|
||||
};
|
||||
|
@ -326,7 +326,7 @@ SetAvailableCreatures NewTurnProcessor::generateTownGrowth(const CGTownInstance
|
||||
|
||||
if (weekType == EWeekType::PLAGUE)
|
||||
resultingCreatures = creaturesBefore / 2;
|
||||
else if (weekType == EWeekType::DOUBLE_GROWTH)
|
||||
else if (weekType == EWeekType::DOUBLE_GROWTH && vstd::contains(t->creatures.at(k).second, creatureWeek))
|
||||
resultingCreatures = (creaturesBefore + creatureGrowth) * 2;
|
||||
else
|
||||
resultingCreatures = creaturesBefore + creatureGrowth;
|
||||
|
Reference in New Issue
Block a user