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.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.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.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.modsToEnable" : "{Następujące mody są wymagane do wczytania gry}",
|
||||||
"vcmi.server.errors.modsToDisable" : "{Następujące mody muszą zostać wyłączone}",
|
"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.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.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!",
|
"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.otherGroup" : "Inne ustawienia", // unused right now
|
||||||
"vcmi.systemOptions.townsGroup" : "Ekran miasta",
|
"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.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.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)",
|
"vcmi.systemOptions.fullscreenExclusive.hover" : "Pełny ekran (tradycyjny)",
|
||||||
@ -628,5 +660,7 @@
|
|||||||
"core.bonus.WATER_IMMUNITY.name": "Odporność: Woda",
|
"core.bonus.WATER_IMMUNITY.name": "Odporność: Woda",
|
||||||
"core.bonus.WATER_IMMUNITY.description": "Odporny na wszystkie czary szkoły wody",
|
"core.bonus.WATER_IMMUNITY.description": "Odporny na wszystkie czary szkoły wody",
|
||||||
"core.bonus.WIDE_BREATH.name": "Szerokie zionięcie",
|
"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
|
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;
|
LobbyRestartGame endGame;
|
||||||
sendLobbyPack(endGame);
|
sendLobbyPack(endGame);
|
||||||
@ -576,7 +579,12 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const
|
|||||||
verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
|
verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
|
||||||
|
|
||||||
if(!settings["session"]["headless"].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;
|
LobbyPrepareStartGame lpsg;
|
||||||
sendLobbyPack(lpsg);
|
sendLobbyPack(lpsg);
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
#include "../lib/CConfigHandler.h"
|
#include "../lib/CConfigHandler.h"
|
||||||
#include "../lib/texts/CGeneralTextHandler.h"
|
#include "../lib/texts/CGeneralTextHandler.h"
|
||||||
#include "../lib/serializer/Connection.h"
|
#include "../lib/serializer/Connection.h"
|
||||||
|
#include "../lib/campaign/CampaignState.h"
|
||||||
|
|
||||||
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack)
|
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack)
|
||||||
{
|
{
|
||||||
@ -203,7 +204,10 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState &
|
|||||||
if(!lobby->bonusSel && handler.si->campState && handler.getState() == EClientState::LOBBY_CAMPAIGN)
|
if(!lobby->bonusSel && handler.si->campState && handler.getState() == EClientState::LOBBY_CAMPAIGN)
|
||||||
{
|
{
|
||||||
lobby->bonusSel = std::make_shared<CBonusSelection>();
|
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)
|
if(lobby->bonusSel)
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "../widgets/MiscWidgets.h"
|
#include "../widgets/MiscWidgets.h"
|
||||||
#include "../widgets/ObjectLists.h"
|
#include "../widgets/ObjectLists.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
|
#include "../widgets/VideoWidget.h"
|
||||||
#include "../windows/GUIClasses.h"
|
#include "../windows/GUIClasses.h"
|
||||||
#include "../windows/InfoWindows.h"
|
#include "../windows/InfoWindows.h"
|
||||||
#include "../render/IImage.h"
|
#include "../render/IImage.h"
|
||||||
@ -58,6 +59,41 @@
|
|||||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
#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()
|
std::shared_ptr<CampaignState> CBonusSelection::getCampaign()
|
||||||
{
|
{
|
||||||
return CSH->si->campState;
|
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]);
|
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);
|
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]);
|
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);
|
mapDescription = std::make_shared<CTextBox>("", Rect(480, 278, 292, 108), 1);
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "../windows/CWindowObject.h"
|
#include "../windows/CWindowObject.h"
|
||||||
|
|
||||||
#include "../lib/campaign/CampaignConstants.h"
|
#include "../lib/campaign/CampaignConstants.h"
|
||||||
|
#include "../lib/filesystem/ResourcePath.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
@ -28,6 +29,22 @@ class CLabel;
|
|||||||
class CFlagBox;
|
class CFlagBox;
|
||||||
class ISelectionScreenInfo;
|
class ISelectionScreenInfo;
|
||||||
class ExtraOptionsTab;
|
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
|
/// Campaign screen where you can choose one out of three starting bonuses
|
||||||
class CBonusSelection : public CWindowObject
|
class CBonusSelection : public CWindowObject
|
||||||
|
@ -601,6 +601,12 @@ void RandomMapTab::loadOptions()
|
|||||||
{
|
{
|
||||||
w->setItem(mapGenOptions->getMapTemplate());
|
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();
|
updateMapInfoByHost();
|
||||||
|
|
||||||
|
@ -787,6 +787,8 @@ bool SelectionTab::isMapSupported(const CMapInfo & info)
|
|||||||
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["supported"].Bool();
|
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["supported"].Bool();
|
||||||
case EMapFormat::SOD:
|
case EMapFormat::SOD:
|
||||||
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH)["supported"].Bool();
|
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:
|
case EMapFormat::WOG:
|
||||||
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["supported"].Bool();
|
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["supported"].Bool();
|
||||||
case EMapFormat::HOTA:
|
case EMapFormat::HOTA:
|
||||||
|
@ -629,7 +629,12 @@ void CSimpleJoinScreen::startConnection(const std::string & addr, ui16 port)
|
|||||||
}
|
}
|
||||||
|
|
||||||
CLoadingScreen::CLoadingScreen()
|
CLoadingScreen::CLoadingScreen()
|
||||||
: CWindowObject(BORDERED, getBackground())
|
: CLoadingScreen(getBackground())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CLoadingScreen::CLoadingScreen(ImagePath background)
|
||||||
|
: CWindowObject(BORDERED, background)
|
||||||
{
|
{
|
||||||
OBJECT_CONSTRUCTION;
|
OBJECT_CONSTRUCTION;
|
||||||
|
|
||||||
|
@ -192,6 +192,7 @@ class CLoadingScreen : virtual public CWindowObject, virtual public Load::Progre
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
CLoadingScreen();
|
CLoadingScreen();
|
||||||
|
CLoadingScreen(ImagePath background);
|
||||||
~CLoadingScreen();
|
~CLoadingScreen();
|
||||||
|
|
||||||
void tick(uint32_t msPassed) override;
|
void tick(uint32_t msPassed) override;
|
||||||
|
@ -16,15 +16,17 @@
|
|||||||
#include "../render/Colors.h"
|
#include "../render/Colors.h"
|
||||||
#include "../render/IScreenHandler.h"
|
#include "../render/IScreenHandler.h"
|
||||||
|
|
||||||
|
#include "../../lib/CConfigHandler.h"
|
||||||
#include "../../lib/Rect.h"
|
#include "../../lib/Rect.h"
|
||||||
|
#include "../../lib/VCMI_Lib.h"
|
||||||
#include "../../lib/filesystem/Filesystem.h"
|
#include "../../lib/filesystem/Filesystem.h"
|
||||||
#include "../../lib/modding/CModHandler.h"
|
#include "../../lib/modding/CModHandler.h"
|
||||||
#include "../../lib/texts/Languages.h"
|
#include "../../lib/texts/Languages.h"
|
||||||
#include "../../lib/texts/TextOperations.h"
|
#include "../../lib/texts/TextOperations.h"
|
||||||
#include "../../lib/vcmi_endian.h"
|
#include "../../lib/vcmi_endian.h"
|
||||||
#include "../../lib/VCMI_Lib.h"
|
|
||||||
|
|
||||||
#include <SDL_surface.h>
|
#include <SDL_surface.h>
|
||||||
|
#include <SDL_image.h>
|
||||||
|
|
||||||
struct AtlasLayout
|
struct AtlasLayout
|
||||||
{
|
{
|
||||||
@ -199,10 +201,20 @@ CBitmapFont::CBitmapFont(const std::string & filename):
|
|||||||
|
|
||||||
if (GH.screenHandler().getScalingFactor() != 1)
|
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);
|
SDL_FreeSurface(atlasImage);
|
||||||
atlasImage = scaledSurface;
|
atlasImage = scaledSurface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IMG_SavePNG(atlasImage, ("/home/ivan/font_" + filename).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
CBitmapFont::~CBitmapFont()
|
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();
|
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)
|
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)
|
if(!TTF_WasInit() && TTF_Init()==-1)
|
||||||
throw std::runtime_error(std::string("Failed to initialize true type support: ") + TTF_GetError() + "\n");
|
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();
|
const JsonVector & names = config["style"].Vector();
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
@ -30,7 +30,8 @@ class CTrueTypeFont final : public IFont
|
|||||||
|
|
||||||
std::pair<std::unique_ptr<ui8[]>, ui64> loadData(const JsonNode & config);
|
std::pair<std::unique_ptr<ui8[]>, ui64> loadData(const JsonNode & config);
|
||||||
TTF_Font * loadFont(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;
|
void renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const override;
|
||||||
public:
|
public:
|
||||||
|
@ -270,30 +270,10 @@ std::shared_ptr<ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palet
|
|||||||
if (factor <= 0)
|
if (factor <= 0)
|
||||||
throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor));
|
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);
|
SDL_SetSurfacePalette(surf, palette);
|
||||||
|
|
||||||
/// Convert current surface to ARGB format suitable for xBRZ
|
SDL_Surface * scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::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);
|
|
||||||
|
|
||||||
auto ret = std::make_shared<SDLImageShared>(scaled);
|
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
|
// erase our own reference
|
||||||
SDL_FreeSurface(scaled);
|
SDL_FreeSurface(scaled);
|
||||||
|
|
||||||
if (surf->format->palette)
|
if (surf && surf->format->palette)
|
||||||
SDL_SetSurfacePalette(surf, originalPalette);
|
SDL_SetSurfacePalette(surf, originalPalette);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -638,8 +638,8 @@ SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface * surf, int width, int height)
|
|||||||
if(!surf || !width || !height)
|
if(!surf || !width || !height)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
if (surf->w * 2 == width && surf->h * 2 == height)
|
// TODO: use xBRZ if possible? E.g. when scaling to 150% do 100% -> 200% via xBRZ and then linear downscale 200% -> 150%?
|
||||||
return scaleSurfaceIntegerFactor(surf, 2);
|
// Need to investigate which is optimal for performance and for visuals
|
||||||
|
|
||||||
SDL_Surface * intermediate = SDL_ConvertSurface(surf, screen->format, 0);
|
SDL_Surface * intermediate = SDL_ConvertSurface(surf, screen->format, 0);
|
||||||
SDL_Surface * ret = newSurface(Point(width, height), intermediate);
|
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;
|
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)
|
if(surf == nullptr || factor == 0)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -662,7 +662,7 @@ SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor
|
|||||||
int newWidth = surf->w * factor;
|
int newWidth = surf->w * factor;
|
||||||
int newHight = surf->h * 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);
|
SDL_Surface * ret = newSurface(Point(newWidth, newHight), intermediate);
|
||||||
|
|
||||||
assert(intermediate->pitch == intermediate->w * 4);
|
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
|
// TODO: compare performance and size of images, recheck values for potentially better parameters
|
||||||
const int granulation = std::clamp(surf->h / 64 * 8, 8, 64);
|
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);
|
SDL_FreeSurface(intermediate);
|
||||||
|
|
||||||
|
@ -27,6 +27,13 @@ class Point;
|
|||||||
|
|
||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|
||||||
|
enum class EScalingAlgorithm : int8_t
|
||||||
|
{
|
||||||
|
NEAREST,
|
||||||
|
BILINEAR,
|
||||||
|
XBRZ
|
||||||
|
};
|
||||||
|
|
||||||
namespace CSDL_Ext
|
namespace CSDL_Ext
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -92,7 +99,7 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &,
|
|||||||
|
|
||||||
// bilinear filtering. Always returns rgba surface
|
// bilinear filtering. Always returns rgba surface
|
||||||
SDL_Surface * scaleSurface(SDL_Surface * surf, int width, int height);
|
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>
|
template<int bpp>
|
||||||
void convertToGrayscaleBpp(SDL_Surface * surf, const Rect & rect);
|
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
|
// each iteration generates one output line
|
||||||
while(text.length())
|
while(text.length())
|
||||||
{
|
{
|
||||||
ui32 lineWidth = 0; //in characters or given char metric
|
|
||||||
ui32 wordBreak = -1; //last position for line break (last space character)
|
ui32 wordBreak = -1; //last position for line break (last space character)
|
||||||
ui32 currPos = 0; //current position in text
|
ui32 currPos = 0; //current position in text
|
||||||
bool opened = false; //set to true when opening brace is found
|
bool opened = false; //set to true when opening brace is found
|
||||||
std::string color; //color found
|
std::string color; //color found
|
||||||
|
|
||||||
size_t symbolSize = 0; // width of character, in bytes
|
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
|
// 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]);
|
symbolSize = TextOperations::getUnicodeCharacterSize(text[currPos]);
|
||||||
glyphWidth = graphics->fonts[font]->getGlyphWidth(text.data() + currPos);
|
|
||||||
|
|
||||||
// candidate for line break
|
// candidate for line break
|
||||||
if(ui8(text[currPos]) <= ui8(' '))
|
if(ui8(text[currPos]) <= ui8(' '))
|
||||||
@ -116,7 +115,14 @@ std::vector<std::string> CMessage::breakText(std::string text, size_t maxLineWid
|
|||||||
color = "";
|
color = "";
|
||||||
}
|
}
|
||||||
else
|
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;
|
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:
|
// Should be in format:
|
||||||
// <replaced bitmap font name, case-sensetive> : <true type font description>
|
// <replaced bitmap font name, case-sensetive> : <true type font description>
|
||||||
// "file" - file to load font from, must be in data/ directory
|
// "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
|
// "style" - italic and\or bold, indicates font style
|
||||||
// "blend" - if set to true, font will be antialiased
|
// "blend" - if set to true, font will be antialiased
|
||||||
"trueType":
|
"trueType":
|
||||||
|
@ -271,6 +271,19 @@
|
|||||||
"portraitYoungYog" : 162
|
"portraitYoungYog" : 162
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"chronicles" : {
|
||||||
|
"supported" : true,
|
||||||
|
"iconIndex" : 2,
|
||||||
|
|
||||||
|
"portraits" : {
|
||||||
|
"portraitTarnumBarbarian" : 163,
|
||||||
|
"portraitTarnumKnight" : 164,
|
||||||
|
"portraitTarnumWizard" : 165,
|
||||||
|
"portraitTarnumRanger" : 166,
|
||||||
|
"portraitTarnumOverlord" : 167,
|
||||||
|
"portraitTarnumBeastmaster" : 168
|
||||||
|
}
|
||||||
|
},
|
||||||
"jsonVCMI" : {
|
"jsonVCMI" : {
|
||||||
"supported" : true,
|
"supported" : true,
|
||||||
"iconIndex" : 3
|
"iconIndex" : 3
|
||||||
|
@ -202,5 +202,179 @@
|
|||||||
],
|
],
|
||||||
"skills" : [],
|
"skills" : [],
|
||||||
"specialty" : {}
|
"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",
|
"targetfps",
|
||||||
"vsync",
|
"vsync",
|
||||||
"upscalingFilter",
|
"upscalingFilter",
|
||||||
|
"fontUpscalingFilter",
|
||||||
"downscalingFilter"
|
"downscalingFilter"
|
||||||
],
|
],
|
||||||
"properties" : {
|
"properties" : {
|
||||||
@ -231,6 +232,11 @@
|
|||||||
"type" : "boolean",
|
"type" : "boolean",
|
||||||
"default" : true
|
"default" : true
|
||||||
},
|
},
|
||||||
|
"fontUpscalingFilter" : {
|
||||||
|
"type" : "string",
|
||||||
|
"enum" : [ "nearest", "bilinear", "xbrz" ],
|
||||||
|
"default" : "nearest"
|
||||||
|
},
|
||||||
"upscalingFilter" : {
|
"upscalingFilter" : {
|
||||||
"type" : "string",
|
"type" : "string",
|
||||||
"enum" : [ "auto", "none", "xbrz2", "xbrz3", "xbrz4" ],
|
"enum" : [ "auto", "none", "xbrz2", "xbrz3", "xbrz4" ],
|
||||||
|
@ -52,6 +52,9 @@ In header are parameters describing campaign properties
|
|||||||
- `"campaignVersion"` is creator defined version
|
- `"campaignVersion"` is creator defined version
|
||||||
- `"creationDateTime"` unix time of campaign creation
|
- `"creationDateTime"` unix time of campaign creation
|
||||||
- `"allowDifficultySelection"` is a boolean field (`true`/`false`) which allows or disallows to choose difficulty before scenario start
|
- `"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
|
## Scenario description
|
||||||
|
|
||||||
|
@ -12,10 +12,12 @@ set(launcher_SRCS
|
|||||||
modManager/cmodlistview_moc.cpp
|
modManager/cmodlistview_moc.cpp
|
||||||
modManager/cmodmanager.cpp
|
modManager/cmodmanager.cpp
|
||||||
modManager/imageviewer_moc.cpp
|
modManager/imageviewer_moc.cpp
|
||||||
|
modManager/chroniclesextractor.cpp
|
||||||
settingsView/csettingsview_moc.cpp
|
settingsView/csettingsview_moc.cpp
|
||||||
firstLaunch/firstlaunch_moc.cpp
|
firstLaunch/firstlaunch_moc.cpp
|
||||||
main.cpp
|
main.cpp
|
||||||
helper.cpp
|
helper.cpp
|
||||||
|
innoextract.cpp
|
||||||
mainwindow_moc.cpp
|
mainwindow_moc.cpp
|
||||||
languages.cpp
|
languages.cpp
|
||||||
launcherdirs.cpp
|
launcherdirs.cpp
|
||||||
@ -42,6 +44,7 @@ set(launcher_HEADERS
|
|||||||
modManager/cmodlistview_moc.h
|
modManager/cmodlistview_moc.h
|
||||||
modManager/cmodmanager.h
|
modManager/cmodmanager.h
|
||||||
modManager/imageviewer_moc.h
|
modManager/imageviewer_moc.h
|
||||||
|
modManager/chroniclesextractor.h
|
||||||
settingsView/csettingsview_moc.h
|
settingsView/csettingsview_moc.h
|
||||||
firstLaunch/firstlaunch_moc.h
|
firstLaunch/firstlaunch_moc.h
|
||||||
mainwindow_moc.h
|
mainwindow_moc.h
|
||||||
@ -51,6 +54,7 @@ set(launcher_HEADERS
|
|||||||
updatedialog_moc.h
|
updatedialog_moc.h
|
||||||
main.h
|
main.h
|
||||||
helper.h
|
helper.h
|
||||||
|
innoextract.h
|
||||||
prepare.h
|
prepare.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,11 +21,7 @@
|
|||||||
#include "../../lib/filesystem/Filesystem.h"
|
#include "../../lib/filesystem/Filesystem.h"
|
||||||
#include "../helper.h"
|
#include "../helper.h"
|
||||||
#include "../languages.h"
|
#include "../languages.h"
|
||||||
|
#include "../innoextract.h"
|
||||||
#ifdef ENABLE_INNOEXTRACT
|
|
||||||
#include "cli/extract.hpp"
|
|
||||||
#include "setup/version.hpp"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef VCMI_IOS
|
#ifdef VCMI_IOS
|
||||||
#include "ios/selectdirectory.h"
|
#include "ios/selectdirectory.h"
|
||||||
@ -386,44 +382,11 @@ void FirstLaunchView::extractGogData()
|
|||||||
if(isGogGalaxyExe(tmpFileExe))
|
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!");
|
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;
|
if(errorText.isEmpty())
|
||||||
o.extract = true;
|
errorText = Innoextract::extract(tmpFileExe, tempDir.path(), [this](float progress) {
|
||||||
|
ui->progressBarGog->setValue(progress * 100);
|
||||||
// standard settings
|
qApp->processEvents();
|
||||||
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!");
|
|
||||||
}
|
|
||||||
|
|
||||||
ui->progressBarGog->setVisible(false);
|
ui->progressBarGog->setVisible(false);
|
||||||
ui->pushButtonGogInstall->setVisible(true);
|
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 "cmodlistmodel_moc.h"
|
||||||
#include "cmodmanager.h"
|
#include "cmodmanager.h"
|
||||||
#include "cdownloadmanager_moc.h"
|
#include "cdownloadmanager_moc.h"
|
||||||
|
#include "chroniclesextractor.h"
|
||||||
#include "../settingsView/csettingsview_moc.h"
|
#include "../settingsView/csettingsview_moc.h"
|
||||||
#include "../launcherdirs.h"
|
#include "../launcherdirs.h"
|
||||||
#include "../jsonutils.h"
|
#include "../jsonutils.h"
|
||||||
@ -29,6 +30,9 @@
|
|||||||
#include "../../lib/CConfigHandler.h"
|
#include "../../lib/CConfigHandler.h"
|
||||||
#include "../../lib/texts/Languages.h"
|
#include "../../lib/texts/Languages.h"
|
||||||
#include "../../lib/modding/CModVersion.h"
|
#include "../../lib/modding/CModVersion.h"
|
||||||
|
#include "../../lib/filesystem/Filesystem.h"
|
||||||
|
|
||||||
|
#include <future>
|
||||||
|
|
||||||
static double mbToBytes(double mb)
|
static double mbToBytes(double mb)
|
||||||
{
|
{
|
||||||
@ -55,7 +59,7 @@ void CModListView::dragEnterEvent(QDragEnterEvent* event)
|
|||||||
{
|
{
|
||||||
if(event->mimeData()->hasUrls())
|
if(event->mimeData()->hasUrls())
|
||||||
for(const auto & url : event->mimeData()->urls())
|
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))
|
if(url.fileName().endsWith(ending, Qt::CaseInsensitive))
|
||||||
{
|
{
|
||||||
event->acceptProposedAction();
|
event->acceptProposedAction();
|
||||||
@ -636,8 +640,16 @@ void CModListView::on_installFromFileButton_clicked()
|
|||||||
// https://bugreports.qt.io/browse/QTBUG-98651
|
// https://bugreports.qt.io/browse/QTBUG-98651
|
||||||
QTimer::singleShot(0, this, [this]
|
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)";
|
QString filter = tr("All supported files") + " (*.h3m *.vmap *.h3c *.vcmp *.zip *.json *.exe);;" +
|
||||||
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files (configs, mods, maps, campaigns) to install..."), QDir::homePath(), filter);
|
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)
|
for(const auto & file : files)
|
||||||
{
|
{
|
||||||
@ -786,6 +798,7 @@ void CModListView::installFiles(QStringList files)
|
|||||||
QStringList mods;
|
QStringList mods;
|
||||||
QStringList maps;
|
QStringList maps;
|
||||||
QStringList images;
|
QStringList images;
|
||||||
|
QStringList exe;
|
||||||
QVector<QVariantMap> repositories;
|
QVector<QVariantMap> repositories;
|
||||||
|
|
||||||
// TODO: some better way to separate zip's with mods and downloaded repository files
|
// 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);
|
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))
|
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);
|
maps.push_back(filename);
|
||||||
|
if(filename.endsWith(".exe", Qt::CaseInsensitive))
|
||||||
|
exe.push_back(filename);
|
||||||
else if(filename.endsWith(".json", Qt::CaseInsensitive))
|
else if(filename.endsWith(".json", Qt::CaseInsensitive))
|
||||||
{
|
{
|
||||||
//download and merge additional files
|
//download and merge additional files
|
||||||
@ -832,6 +847,35 @@ void CModListView::installFiles(QStringList files)
|
|||||||
if(!maps.empty())
|
if(!maps.empty())
|
||||||
installMaps(maps);
|
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())
|
if(!images.empty())
|
||||||
loadScreenshots();
|
loadScreenshots();
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ void GameSettings::load(const JsonNode & input)
|
|||||||
{EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" },
|
{EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" },
|
||||||
{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" },
|
{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" },
|
||||||
{EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" },
|
{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_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" },
|
||||||
{EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" },
|
{EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" },
|
||||||
{EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" },
|
{EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" },
|
||||||
|
@ -58,6 +58,7 @@ enum class EGameSettings
|
|||||||
MAP_FORMAT_RESTORATION_OF_ERATHIA,
|
MAP_FORMAT_RESTORATION_OF_ERATHIA,
|
||||||
MAP_FORMAT_ARMAGEDDONS_BLADE,
|
MAP_FORMAT_ARMAGEDDONS_BLADE,
|
||||||
MAP_FORMAT_SHADOW_OF_DEATH,
|
MAP_FORMAT_SHADOW_OF_DEATH,
|
||||||
|
MAP_FORMAT_CHRONICLES,
|
||||||
MAP_FORMAT_HORN_OF_THE_ABYSS,
|
MAP_FORMAT_HORN_OF_THE_ABYSS,
|
||||||
MAP_FORMAT_JSON_VCMI,
|
MAP_FORMAT_JSON_VCMI,
|
||||||
MAP_FORMAT_IN_THE_WAKE_OF_GODS,
|
MAP_FORMAT_IN_THE_WAKE_OF_GODS,
|
||||||
|
@ -104,8 +104,8 @@ int BonusList::totalValue() const
|
|||||||
};
|
};
|
||||||
|
|
||||||
BonusCollection accumulated;
|
BonusCollection accumulated;
|
||||||
bool hasIndepMax = false;
|
int indexMaxCount = 0;
|
||||||
bool hasIndepMin = false;
|
int indexMinCount = 0;
|
||||||
|
|
||||||
std::array<int, vstd::to_underlying(BonusSource::NUM_BONUS_SOURCE)> percentToSource = {};
|
std::array<int, vstd::to_underlying(BonusSource::NUM_BONUS_SOURCE)> percentToSource = {};
|
||||||
|
|
||||||
@ -141,12 +141,12 @@ int BonusList::totalValue() const
|
|||||||
case BonusValueType::ADDITIVE_VALUE:
|
case BonusValueType::ADDITIVE_VALUE:
|
||||||
accumulated.additive += valModified;
|
accumulated.additive += valModified;
|
||||||
break;
|
break;
|
||||||
case BonusValueType::INDEPENDENT_MAX:
|
case BonusValueType::INDEPENDENT_MAX: // actual meaning: at least this value
|
||||||
hasIndepMax = true;
|
indexMaxCount++;
|
||||||
vstd::amax(accumulated.indepMax, valModified);
|
vstd::amax(accumulated.indepMax, valModified);
|
||||||
break;
|
break;
|
||||||
case BonusValueType::INDEPENDENT_MIN:
|
case BonusValueType::INDEPENDENT_MIN: // actual meaning: at most this value
|
||||||
hasIndepMin = true;
|
indexMinCount++;
|
||||||
vstd::amin(accumulated.indepMin, valModified);
|
vstd::amin(accumulated.indepMin, valModified);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -156,21 +156,18 @@ int BonusList::totalValue() const
|
|||||||
accumulated.base += accumulated.additive;
|
accumulated.base += accumulated.additive;
|
||||||
auto valFirst = applyPercentage(accumulated.base ,accumulated.percentToAll);
|
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;
|
accumulated.indepMax = accumulated.indepMin;
|
||||||
|
|
||||||
const int notIndepBonuses = static_cast<int>(std::count_if(bonuses.cbegin(), bonuses.cend(), [](const std::shared_ptr<Bonus>& b)
|
const int notIndepBonuses = bonuses.size() - indexMaxCount - indexMinCount;
|
||||||
{
|
|
||||||
return b->valType != BonusValueType::INDEPENDENT_MAX && b->valType != BonusValueType::INDEPENDENT_MIN;
|
|
||||||
}));
|
|
||||||
|
|
||||||
if(notIndepBonuses)
|
if(notIndepBonuses)
|
||||||
return std::clamp(valFirst, accumulated.indepMax, accumulated.indepMin);
|
return std::clamp(valFirst, accumulated.indepMax, accumulated.indepMin);
|
||||||
|
|
||||||
if (hasIndepMin)
|
if (indexMinCount)
|
||||||
return accumulated.indepMin;
|
return accumulated.indepMin;
|
||||||
|
|
||||||
if (hasIndepMax)
|
if (indexMaxCount)
|
||||||
return accumulated.indepMax;
|
return accumulated.indepMax;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -18,7 +18,7 @@ enum class CampaignVersion : uint8_t
|
|||||||
AB = 5,
|
AB = 5,
|
||||||
SoD = 6,
|
SoD = 6,
|
||||||
WoG = 6,
|
WoG = 6,
|
||||||
// Chr = 7, // Heroes Chronicles, likely identical to SoD, untested
|
Chr = 7,
|
||||||
|
|
||||||
VCMI = 1,
|
VCMI = 1,
|
||||||
VCMI_MIN = 1,
|
VCMI_MIN = 1,
|
||||||
|
@ -38,12 +38,14 @@ void CampaignHandler::readCampaign(Campaign * ret, const std::vector<ui8> & inpu
|
|||||||
CBinaryReader reader(&stream);
|
CBinaryReader reader(&stream);
|
||||||
|
|
||||||
readHeaderFromMemory(*ret, reader, filename, modName, encoding);
|
readHeaderFromMemory(*ret, reader, filename, modName, encoding);
|
||||||
|
ret->overrideCampaign();
|
||||||
|
|
||||||
for(int g = 0; g < ret->numberOfScenarios; ++g)
|
for(int g = 0; g < ret->numberOfScenarios; ++g)
|
||||||
{
|
{
|
||||||
auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
|
auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
|
||||||
ret->scenarios[scenarioID] = readScenarioFromMemory(reader, *ret);
|
ret->scenarios[scenarioID] = readScenarioFromMemory(reader, *ret);
|
||||||
}
|
}
|
||||||
|
ret->overrideCampaignScenarios();
|
||||||
}
|
}
|
||||||
else // text format (json)
|
else // text format (json)
|
||||||
{
|
{
|
||||||
@ -166,6 +168,9 @@ void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader
|
|||||||
ret.filename = filename;
|
ret.filename = filename;
|
||||||
ret.modName = modName;
|
ret.modName = modName;
|
||||||
ret.encoding = encoding;
|
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)
|
CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
|
||||||
@ -392,7 +397,8 @@ void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader
|
|||||||
{
|
{
|
||||||
ret.version = static_cast<CampaignVersion>(reader.readUInt32());
|
ret.version = static_cast<CampaignVersion>(reader.readUInt32());
|
||||||
ui8 campId = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19]
|
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.name.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "name"));
|
||||||
ret.description.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "description"));
|
ret.description.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "description"));
|
||||||
ret.author.appendRawString("");
|
ret.author.appendRawString("");
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "../mapObjects/CGHeroInstance.h"
|
#include "../mapObjects/CGHeroInstance.h"
|
||||||
#include "../serializer/JsonDeserializer.h"
|
#include "../serializer/JsonDeserializer.h"
|
||||||
#include "../serializer/JsonSerializer.h"
|
#include "../serializer/JsonSerializer.h"
|
||||||
|
#include "../json/JsonUtils.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
@ -138,6 +139,12 @@ void CampaignHeader::loadLegacyData(ui8 campId)
|
|||||||
numberOfScenarios = VLC->generaltexth->getCampaignLength(campId);
|
numberOfScenarios = VLC->generaltexth->getCampaignLength(campId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CampaignHeader::loadLegacyData(CampaignRegions regions, int numOfScenario)
|
||||||
|
{
|
||||||
|
campaignRegions = regions;
|
||||||
|
numberOfScenarios = numOfScenario;
|
||||||
|
}
|
||||||
|
|
||||||
bool CampaignHeader::playerSelectedDifficulty() const
|
bool CampaignHeader::playerSelectedDifficulty() const
|
||||||
{
|
{
|
||||||
return difficultyChosenByPlayer;
|
return difficultyChosenByPlayer;
|
||||||
@ -198,6 +205,21 @@ AudioPath CampaignHeader::getMusic() const
|
|||||||
return music;
|
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
|
const CampaignRegions & CampaignHeader::getRegions() const
|
||||||
{
|
{
|
||||||
return campaignRegions;
|
return campaignRegions;
|
||||||
@ -455,6 +477,45 @@ std::set<CampaignScenarioID> Campaign::allScenarios() const
|
|||||||
return result;
|
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
|
int Campaign::scenariosCount() const
|
||||||
{
|
{
|
||||||
return allScenarios().size();
|
return allScenarios().size();
|
||||||
|
@ -83,6 +83,7 @@ public:
|
|||||||
class DLL_LINKAGE CampaignHeader : public boost::noncopyable
|
class DLL_LINKAGE CampaignHeader : public boost::noncopyable
|
||||||
{
|
{
|
||||||
friend class CampaignHandler;
|
friend class CampaignHandler;
|
||||||
|
friend class Campaign;
|
||||||
|
|
||||||
CampaignVersion version = CampaignVersion::NONE;
|
CampaignVersion version = CampaignVersion::NONE;
|
||||||
CampaignRegions campaignRegions;
|
CampaignRegions campaignRegions;
|
||||||
@ -96,11 +97,15 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable
|
|||||||
std::string filename;
|
std::string filename;
|
||||||
std::string modName;
|
std::string modName;
|
||||||
std::string encoding;
|
std::string encoding;
|
||||||
|
ImagePath loadingBackground;
|
||||||
|
ImagePath introVideoRim;
|
||||||
|
VideoPath introVideo;
|
||||||
|
|
||||||
int numberOfScenarios = 0;
|
int numberOfScenarios = 0;
|
||||||
bool difficultyChosenByPlayer = false;
|
bool difficultyChosenByPlayer = false;
|
||||||
|
|
||||||
void loadLegacyData(ui8 campId);
|
void loadLegacyData(ui8 campId);
|
||||||
|
void loadLegacyData(CampaignRegions regions, int numOfScenario);
|
||||||
|
|
||||||
TextContainerRegistrable textContainer;
|
TextContainerRegistrable textContainer;
|
||||||
|
|
||||||
@ -118,6 +123,9 @@ public:
|
|||||||
std::string getModName() const;
|
std::string getModName() const;
|
||||||
std::string getEncoding() const;
|
std::string getEncoding() const;
|
||||||
AudioPath getMusic() const;
|
AudioPath getMusic() const;
|
||||||
|
ImagePath getLoadingBackground() const;
|
||||||
|
ImagePath getIntroVideoRim() const;
|
||||||
|
VideoPath getIntroVideo() const;
|
||||||
|
|
||||||
const CampaignRegions & getRegions() const;
|
const CampaignRegions & getRegions() const;
|
||||||
TextContainerRegistrable & getTexts();
|
TextContainerRegistrable & getTexts();
|
||||||
@ -142,6 +150,12 @@ public:
|
|||||||
h & music;
|
h & music;
|
||||||
h & encoding;
|
h & encoding;
|
||||||
h & textContainer;
|
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;
|
std::set<CampaignScenarioID> allScenarios() const;
|
||||||
int scenariosCount() const;
|
int scenariosCount() const;
|
||||||
|
|
||||||
|
void overrideCampaign();
|
||||||
|
void overrideCampaignScenarios();
|
||||||
|
|
||||||
template <typename Handler> void serialize(Handler &h)
|
template <typename Handler> void serialize(Handler &h)
|
||||||
{
|
{
|
||||||
h & static_cast<CampaignHeader&>(*this);
|
h & static_cast<CampaignHeader&>(*this);
|
||||||
|
@ -197,6 +197,11 @@ std::string CArchiveLoader::getMountPoint() const
|
|||||||
return mountPoint;
|
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> CArchiveLoader::getFilteredFiles(std::function<bool(const ResourcePath &)> filter) const
|
||||||
{
|
{
|
||||||
std::unordered_set<ResourcePath> foundID;
|
std::unordered_set<ResourcePath> foundID;
|
||||||
@ -209,7 +214,7 @@ std::unordered_set<ResourcePath> CArchiveLoader::getFilteredFiles(std::function<
|
|||||||
return foundID;
|
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
|
si64 currentPosition = fileStream.tell(); // save filestream position
|
||||||
|
|
||||||
@ -217,7 +222,7 @@ void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, CInput
|
|||||||
fileStream.seek(entry.offset);
|
fileStream.seek(entry.offset);
|
||||||
fileStream.read(data.data(), entry.fullSize);
|
fileStream.read(data.data(), entry.fullSize);
|
||||||
|
|
||||||
boost::filesystem::path extractedFilePath = createExtractedFilePath(outputSubFolder, entry.name);
|
boost::filesystem::path extractedFilePath = createExtractedFilePath(outputSubFolder, entry.name, absolute);
|
||||||
|
|
||||||
// writeToOutputFile
|
// writeToOutputFile
|
||||||
std::ofstream out(extractedFilePath.string(), std::ofstream::binary);
|
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
|
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));
|
std::unique_ptr<CInputStream> inputStream = load(ResourcePath(mountPoint + entry.name));
|
||||||
|
|
||||||
entry.offset = 0;
|
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::path extractedFilePath = extractionFolderPath / entryName;
|
||||||
|
|
||||||
boost::filesystem::create_directories(extractionFolderPath);
|
boost::filesystem::create_directories(extractionFolderPath);
|
||||||
|
@ -63,12 +63,13 @@ public:
|
|||||||
std::unique_ptr<CInputStream> load(const ResourcePath & resourceName) const override;
|
std::unique_ptr<CInputStream> load(const ResourcePath & resourceName) const override;
|
||||||
bool existsResource(const ResourcePath & resourceName) const override;
|
bool existsResource(const ResourcePath & resourceName) const override;
|
||||||
std::string getMountPoint() 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 {}
|
void updateFilteredFiles(std::function<bool(const std::string &)> filter) const override {}
|
||||||
std::unordered_set<ResourcePath> getFilteredFiles(std::function<bool(const ResourcePath &)> 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 */
|
/** 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 */
|
/** 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:
|
private:
|
||||||
/**
|
/**
|
||||||
@ -105,6 +106,6 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** Constructs the file path for the extracted file. Creates the subfolder hierarchy aswell **/
|
/** 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
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
@ -536,7 +536,14 @@ void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler)
|
|||||||
|
|
||||||
const IOwnableObject * CGDwelling::asOwnable() const
|
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
|
ResourceSet CGDwelling::dailyIncome() const
|
||||||
|
@ -1227,6 +1227,21 @@ BoatId CGShipyard::getBoatType() const
|
|||||||
return createdBoat;
|
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
|
void CGDenOfthieves::onHeroVisit (const CGHeroInstance * h) const
|
||||||
{
|
{
|
||||||
cb->showObjectWindow(this, EOpenWindowMode::THIEVES_GUILD, h, false);
|
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;
|
friend class ShipyardInstanceConstructor;
|
||||||
|
|
||||||
@ -358,6 +358,10 @@ protected:
|
|||||||
const IObjectInterface * getObject() const override;
|
const IObjectInterface * getObject() const override;
|
||||||
BoatId getBoatType() const override;
|
BoatId getBoatType() const override;
|
||||||
|
|
||||||
|
const IOwnableObject * asOwnable() const final;
|
||||||
|
ResourceSet dailyIncome() const override;
|
||||||
|
std::vector<CreatureID> providedCreatures() const override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using CGObjectInstance::CGObjectInstance;
|
using CGObjectInstance::CGObjectInstance;
|
||||||
|
|
||||||
|
@ -172,6 +172,8 @@ int CMapInfo::getMapSizeFormatIconId() const
|
|||||||
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["iconIndex"].Integer();
|
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["iconIndex"].Integer();
|
||||||
case EMapFormat::SOD:
|
case EMapFormat::SOD:
|
||||||
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH)["iconIndex"].Integer();
|
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:
|
case EMapFormat::WOG:
|
||||||
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["iconIndex"].Integer();
|
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["iconIndex"].Integer();
|
||||||
case EMapFormat::HOTA:
|
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::AB) :
|
||||||
case static_cast<int>(EMapFormat::ROE) :
|
case static_cast<int>(EMapFormat::ROE) :
|
||||||
case static_cast<int>(EMapFormat::SOD) :
|
case static_cast<int>(EMapFormat::SOD) :
|
||||||
|
case static_cast<int>(EMapFormat::CHR) :
|
||||||
case static_cast<int>(EMapFormat::HOTA) :
|
case static_cast<int>(EMapFormat::HOTA) :
|
||||||
return std::unique_ptr<IMapLoader>(new CMapLoaderH3M(mapName, modName, encoding, stream.get()));
|
return std::unique_ptr<IMapLoader>(new CMapLoaderH3M(mapName, modName, encoding, stream.get()));
|
||||||
default :
|
default :
|
||||||
|
@ -25,6 +25,8 @@ MapFormatFeaturesH3M MapFormatFeaturesH3M::find(EMapFormat format, uint32_t hota
|
|||||||
return getFeaturesAB();
|
return getFeaturesAB();
|
||||||
case EMapFormat::SOD:
|
case EMapFormat::SOD:
|
||||||
return getFeaturesSOD();
|
return getFeaturesSOD();
|
||||||
|
case EMapFormat::CHR:
|
||||||
|
return getFeaturesCHR();
|
||||||
case EMapFormat::WOG:
|
case EMapFormat::WOG:
|
||||||
return getFeaturesWOG();
|
return getFeaturesWOG();
|
||||||
case EMapFormat::HOTA:
|
case EMapFormat::HOTA:
|
||||||
@ -107,6 +109,16 @@ MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesSOD()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesCHR()
|
||||||
|
{
|
||||||
|
MapFormatFeaturesH3M result = getFeaturesSOD();
|
||||||
|
result.levelCHR = true;
|
||||||
|
|
||||||
|
result.heroesPortraitsCount = 169; // +6x tarnum
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesWOG()
|
MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesWOG()
|
||||||
{
|
{
|
||||||
MapFormatFeaturesH3M result = getFeaturesSOD();
|
MapFormatFeaturesH3M result = getFeaturesSOD();
|
||||||
|
@ -21,6 +21,7 @@ public:
|
|||||||
static MapFormatFeaturesH3M getFeaturesROE();
|
static MapFormatFeaturesH3M getFeaturesROE();
|
||||||
static MapFormatFeaturesH3M getFeaturesAB();
|
static MapFormatFeaturesH3M getFeaturesAB();
|
||||||
static MapFormatFeaturesH3M getFeaturesSOD();
|
static MapFormatFeaturesH3M getFeaturesSOD();
|
||||||
|
static MapFormatFeaturesH3M getFeaturesCHR();
|
||||||
static MapFormatFeaturesH3M getFeaturesWOG();
|
static MapFormatFeaturesH3M getFeaturesWOG();
|
||||||
static MapFormatFeaturesH3M getFeaturesHOTA(uint32_t hotaVersion);
|
static MapFormatFeaturesH3M getFeaturesHOTA(uint32_t hotaVersion);
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ public:
|
|||||||
bool levelROE = false;
|
bool levelROE = false;
|
||||||
bool levelAB = false;
|
bool levelAB = false;
|
||||||
bool levelSOD = false;
|
bool levelSOD = false;
|
||||||
|
bool levelCHR = false;
|
||||||
bool levelWOG = false;
|
bool levelWOG = false;
|
||||||
bool levelHOTA0 = false;
|
bool levelHOTA0 = false;
|
||||||
bool levelHOTA1 = false;
|
bool levelHOTA1 = false;
|
||||||
|
@ -19,7 +19,7 @@ enum class EMapFormat : uint8_t
|
|||||||
ROE = 0x0e, // 14
|
ROE = 0x0e, // 14
|
||||||
AB = 0x15, // 21
|
AB = 0x15, // 21
|
||||||
SOD = 0x1c, // 28
|
SOD = 0x1c, // 28
|
||||||
// CHR = 0x1d, // 29 Heroes Chronicles, presumably - identical to SoD, untested
|
CHR = 0x1d, // 29
|
||||||
HOTA = 0x20, // 32
|
HOTA = 0x20, // 32
|
||||||
WOG = 0x33, // 51
|
WOG = 0x33, // 51
|
||||||
VCMI = 0x64
|
VCMI = 0x64
|
||||||
|
@ -135,6 +135,8 @@ static MapIdentifiersH3M generateMapping(EMapFormat format)
|
|||||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE));
|
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE));
|
||||||
if(features.levelSOD)
|
if(features.levelSOD)
|
||||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH));
|
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)
|
if(features.levelWOG)
|
||||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
|
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
|
||||||
if(features.levelHOTA0)
|
if(features.levelHOTA0)
|
||||||
@ -161,6 +163,7 @@ static std::map<EMapFormat, MapIdentifiersH3M> generateMappings()
|
|||||||
addMapping(EMapFormat::ROE);
|
addMapping(EMapFormat::ROE);
|
||||||
addMapping(EMapFormat::AB);
|
addMapping(EMapFormat::AB);
|
||||||
addMapping(EMapFormat::SOD);
|
addMapping(EMapFormat::SOD);
|
||||||
|
addMapping(EMapFormat::CHR);
|
||||||
addMapping(EMapFormat::HOTA);
|
addMapping(EMapFormat::HOTA);
|
||||||
addMapping(EMapFormat::WOG);
|
addMapping(EMapFormat::WOG);
|
||||||
|
|
||||||
|
@ -30,9 +30,6 @@ CMapGenOptions::CMapGenOptions()
|
|||||||
customizedPlayers(false)
|
customizedPlayers(false)
|
||||||
{
|
{
|
||||||
initPlayersMap();
|
initPlayersMap();
|
||||||
setRoadEnabled(RoadId(Road::DIRT_ROAD), true);
|
|
||||||
setRoadEnabled(RoadId(Road::GRAVEL_ROAD), true);
|
|
||||||
setRoadEnabled(RoadId(Road::COBBLESTONE_ROAD), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
si32 CMapGenOptions::getWidth() const
|
si32 CMapGenOptions::getWidth() const
|
||||||
|
@ -56,6 +56,7 @@ enum class ESerializationVersion : int32_t
|
|||||||
NEW_MARKETS, // 857 - reworked market classes
|
NEW_MARKETS, // 857 - reworked market classes
|
||||||
PLAYER_STATE_OWNED_OBJECTS, // 858 - player state stores all owned objects in a single list
|
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
|
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)
|
if (weekType == EWeekType::PLAGUE)
|
||||||
resultingCreatures = creaturesBefore / 2;
|
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;
|
resultingCreatures = (creaturesBefore + creatureGrowth) * 2;
|
||||||
else
|
else
|
||||||
resultingCreatures = creaturesBefore + creatureGrowth;
|
resultingCreatures = creaturesBefore + creatureGrowth;
|
||||||
|
Reference in New Issue
Block a user