mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-23 22:37:55 +02:00
Merge pull request #4543 from Laserlicht/chronicles_installer
Chronicles support
This commit is contained in:
@@ -532,7 +532,10 @@ void CServerHandler::sendGuiAction(ui8 action) const
|
||||
|
||||
void CServerHandler::sendRestartGame() const
|
||||
{
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>();
|
||||
if(si->campState && !si->campState->getLoadingBackground().empty())
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>(si->campState->getLoadingBackground());
|
||||
else
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>();
|
||||
|
||||
LobbyRestartGame endGame;
|
||||
sendLobbyPack(endGame);
|
||||
@@ -576,7 +579,12 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const
|
||||
verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
|
||||
|
||||
if(!settings["session"]["headless"].Bool())
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>();
|
||||
{
|
||||
if(si->campState && !si->campState->getLoadingBackground().empty())
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>(si->campState->getLoadingBackground());
|
||||
else
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>();
|
||||
}
|
||||
|
||||
LobbyPrepareStartGame lpsg;
|
||||
sendLobbyPack(lpsg);
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "../lib/CConfigHandler.h"
|
||||
#include "../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../lib/serializer/Connection.h"
|
||||
#include "../lib/campaign/CampaignState.h"
|
||||
|
||||
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack)
|
||||
{
|
||||
@@ -203,7 +204,10 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState &
|
||||
if(!lobby->bonusSel && handler.si->campState && handler.getState() == EClientState::LOBBY_CAMPAIGN)
|
||||
{
|
||||
lobby->bonusSel = std::make_shared<CBonusSelection>();
|
||||
GH.windows().pushWindow(lobby->bonusSel);
|
||||
if(!handler.si->campState->conqueredScenarios().size() && !handler.si->campState->getIntroVideo().empty())
|
||||
GH.windows().createAndPushWindow<CampaignIntroVideo>(handler.si->campState->getIntroVideo(), handler.si->campState->getIntroVideoRim().empty() ? ImagePath::builtin("INTRORIM") : handler.si->campState->getIntroVideoRim(), lobby->bonusSel);
|
||||
else
|
||||
GH.windows().pushWindow(lobby->bonusSel);
|
||||
}
|
||||
|
||||
if(lobby->bonusSel)
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "../widgets/MiscWidgets.h"
|
||||
#include "../widgets/ObjectLists.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../widgets/VideoWidget.h"
|
||||
#include "../windows/GUIClasses.h"
|
||||
#include "../windows/InfoWindows.h"
|
||||
#include "../render/IImage.h"
|
||||
@@ -58,6 +59,41 @@
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
|
||||
|
||||
CampaignIntroVideo::CampaignIntroVideo(VideoPath video, ImagePath rim, std::shared_ptr<CBonusSelection> bonusSel)
|
||||
: CWindowObject(BORDERED), bonusSel(bonusSel)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
addUsedEvents(LCLICK | KEYBOARD);
|
||||
|
||||
pos = center(Rect(0, 0, 800, 600));
|
||||
|
||||
videoPlayer = std::make_shared<VideoWidgetOnce>(Point(80, 186), video, true, [this](){ exit(); });
|
||||
setBackground(rim);
|
||||
|
||||
CCS->musich->stopMusic();
|
||||
}
|
||||
|
||||
void CampaignIntroVideo::exit()
|
||||
{
|
||||
close();
|
||||
|
||||
if (!CSH->si->campState->getMusic().empty())
|
||||
CCS->musich->playMusic(CSH->si->campState->getMusic(), true, false);
|
||||
|
||||
GH.windows().pushWindow(bonusSel);
|
||||
}
|
||||
|
||||
void CampaignIntroVideo::clickPressed(const Point & cursorPosition)
|
||||
{
|
||||
exit();
|
||||
}
|
||||
|
||||
void CampaignIntroVideo::keyPressed(EShortcut key)
|
||||
{
|
||||
exit();
|
||||
}
|
||||
|
||||
std::shared_ptr<CampaignState> CBonusSelection::getCampaign()
|
||||
{
|
||||
return CSH->si->campState;
|
||||
@@ -93,7 +129,9 @@ CBonusSelection::CBonusSelection()
|
||||
labelCampaignDescription = std::make_shared<CLabel>(481, 63, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
|
||||
campaignDescription = std::make_shared<CTextBox>(getCampaign()->getDescriptionTranslated(), Rect(480, 86, 286, 117), 1);
|
||||
|
||||
mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getNameTranslated(), 285);
|
||||
bool videoButtonActive = CSH->getState() == EClientState::GAMEPLAY;
|
||||
int availableSpace = videoButtonActive ? 225 : 285;
|
||||
mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getNameTranslated(), availableSpace );
|
||||
labelMapDescription = std::make_shared<CLabel>(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
|
||||
mapDescription = std::make_shared<CTextBox>("", Rect(480, 278, 292, 108), 1);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "../windows/CWindowObject.h"
|
||||
|
||||
#include "../lib/campaign/CampaignConstants.h"
|
||||
#include "../lib/filesystem/ResourcePath.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@@ -28,6 +29,22 @@ class CLabel;
|
||||
class CFlagBox;
|
||||
class ISelectionScreenInfo;
|
||||
class ExtraOptionsTab;
|
||||
class VideoWidgetOnce;
|
||||
class CBonusSelection;
|
||||
|
||||
|
||||
class CampaignIntroVideo : public CWindowObject
|
||||
{
|
||||
std::shared_ptr<VideoWidgetOnce> videoPlayer;
|
||||
std::shared_ptr<CBonusSelection> bonusSel;
|
||||
|
||||
void exit();
|
||||
public:
|
||||
CampaignIntroVideo(VideoPath video, ImagePath rim, std::shared_ptr<CBonusSelection> bonusSel);
|
||||
|
||||
void clickPressed(const Point & cursorPosition) override;
|
||||
void keyPressed(EShortcut key) override;
|
||||
};
|
||||
|
||||
/// Campaign screen where you can choose one out of three starting bonuses
|
||||
class CBonusSelection : public CWindowObject
|
||||
|
||||
@@ -787,6 +787,8 @@ bool SelectionTab::isMapSupported(const CMapInfo & info)
|
||||
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["supported"].Bool();
|
||||
case EMapFormat::SOD:
|
||||
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH)["supported"].Bool();
|
||||
case EMapFormat::CHR:
|
||||
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES)["supported"].Bool();
|
||||
case EMapFormat::WOG:
|
||||
return CGI->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["supported"].Bool();
|
||||
case EMapFormat::HOTA:
|
||||
|
||||
@@ -629,7 +629,12 @@ void CSimpleJoinScreen::startConnection(const std::string & addr, ui16 port)
|
||||
}
|
||||
|
||||
CLoadingScreen::CLoadingScreen()
|
||||
: CWindowObject(BORDERED, getBackground())
|
||||
: CLoadingScreen(getBackground())
|
||||
{
|
||||
}
|
||||
|
||||
CLoadingScreen::CLoadingScreen(ImagePath background)
|
||||
: CWindowObject(BORDERED, background)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
|
||||
@@ -192,6 +192,7 @@ class CLoadingScreen : virtual public CWindowObject, virtual public Load::Progre
|
||||
|
||||
public:
|
||||
CLoadingScreen();
|
||||
CLoadingScreen(ImagePath background);
|
||||
~CLoadingScreen();
|
||||
|
||||
void tick(uint32_t msPassed) override;
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -271,6 +271,19 @@
|
||||
"portraitYoungYog" : 162
|
||||
}
|
||||
},
|
||||
"chronicles" : {
|
||||
"supported" : true,
|
||||
"iconIndex" : 2,
|
||||
|
||||
"portraits" : {
|
||||
"portraitTarnumBarbarian" : 163,
|
||||
"portraitTarnumKnight" : 164,
|
||||
"portraitTarnumWizard" : 165,
|
||||
"portraitTarnumRanger" : 166,
|
||||
"portraitTarnumOverlord" : 167,
|
||||
"portraitTarnumBeastmaster" : 168
|
||||
}
|
||||
},
|
||||
"jsonVCMI" : {
|
||||
"supported" : true,
|
||||
"iconIndex" : 3
|
||||
|
||||
@@ -202,5 +202,179 @@
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
},
|
||||
"portraitTarnumBarbarian" :
|
||||
{
|
||||
"class" : "barbarian",
|
||||
"special" : true,
|
||||
"images": {
|
||||
"large" : "HPL137",
|
||||
"small" : "HPS137",
|
||||
"specialtySmall" : "default",
|
||||
"specialtyLarge" : "default"
|
||||
},
|
||||
"texts" : {
|
||||
"name" : "",
|
||||
"biography" : "",
|
||||
"specialty" : {
|
||||
"description" : "",
|
||||
"tooltip" : "",
|
||||
"name" : ""
|
||||
}
|
||||
},
|
||||
"army" : [
|
||||
{
|
||||
"creature" : "goblin",
|
||||
"min" : 1,
|
||||
"max" : 1
|
||||
}
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
},
|
||||
"portraitTarnumKnight" :
|
||||
{
|
||||
"class" : "knight",
|
||||
"special" : true,
|
||||
"images": {
|
||||
"large" : "HPL138",
|
||||
"small" : "HPS138",
|
||||
"specialtySmall" : "default",
|
||||
"specialtyLarge" : "default"
|
||||
},
|
||||
"texts" : {
|
||||
"name" : "",
|
||||
"biography" : "",
|
||||
"specialty" : {
|
||||
"description" : "",
|
||||
"tooltip" : "",
|
||||
"name" : ""
|
||||
}
|
||||
},
|
||||
"army" : [
|
||||
{
|
||||
"creature" : "pikeman",
|
||||
"min" : 1,
|
||||
"max" : 1
|
||||
}
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
},
|
||||
"portraitTarnumWizard" :
|
||||
{
|
||||
"class" : "wizard",
|
||||
"special" : true,
|
||||
"images": {
|
||||
"large" : "HPL139",
|
||||
"small" : "HPS139",
|
||||
"specialtySmall" : "default",
|
||||
"specialtyLarge" : "default"
|
||||
},
|
||||
"texts" : {
|
||||
"name" : "",
|
||||
"biography" : "",
|
||||
"specialty" : {
|
||||
"description" : "",
|
||||
"tooltip" : "",
|
||||
"name" : ""
|
||||
}
|
||||
},
|
||||
"army" : [
|
||||
{
|
||||
"creature" : "enchanter",
|
||||
"min" : 1,
|
||||
"max" : 1
|
||||
}
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
},
|
||||
"portraitTarnumRanger" :
|
||||
{
|
||||
"class" : "ranger",
|
||||
"special" : true,
|
||||
"images": {
|
||||
"large" : "HPL140",
|
||||
"small" : "HPS140",
|
||||
"specialtySmall" : "default",
|
||||
"specialtyLarge" : "default"
|
||||
},
|
||||
"texts" : {
|
||||
"name" : "",
|
||||
"biography" : "",
|
||||
"specialty" : {
|
||||
"description" : "",
|
||||
"tooltip" : "",
|
||||
"name" : ""
|
||||
}
|
||||
},
|
||||
"army" : [
|
||||
{
|
||||
"creature" : "sharpshooter",
|
||||
"min" : 1,
|
||||
"max" : 1
|
||||
}
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
},
|
||||
"portraitTarnumOverlord" :
|
||||
{
|
||||
"class" : "overlord",
|
||||
"special" : true,
|
||||
"images": {
|
||||
"large" : "HPL141",
|
||||
"small" : "HPS141",
|
||||
"specialtySmall" : "default",
|
||||
"specialtyLarge" : "default"
|
||||
},
|
||||
"texts" : {
|
||||
"name" : "",
|
||||
"biography" : "",
|
||||
"specialty" : {
|
||||
"description" : "",
|
||||
"tooltip" : "",
|
||||
"name" : ""
|
||||
}
|
||||
},
|
||||
"army" : [
|
||||
{
|
||||
"creature" : "troglodyte",
|
||||
"min" : 1,
|
||||
"max" : 1
|
||||
}
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
},
|
||||
"portraitTarnumBeastmaster" :
|
||||
{
|
||||
"class" : "beastmaster",
|
||||
"special" : true,
|
||||
"images": {
|
||||
"large" : "HPL142",
|
||||
"small" : "HPS142",
|
||||
"specialtySmall" : "default",
|
||||
"specialtyLarge" : "default"
|
||||
},
|
||||
"texts" : {
|
||||
"name" : "",
|
||||
"biography" : "",
|
||||
"specialty" : {
|
||||
"description" : "",
|
||||
"tooltip" : "",
|
||||
"name" : ""
|
||||
}
|
||||
},
|
||||
"army" : [
|
||||
{
|
||||
"creature" : "gnoll",
|
||||
"min" : 1,
|
||||
"max" : 1
|
||||
}
|
||||
],
|
||||
"skills" : [],
|
||||
"specialty" : {}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,9 @@ In header are parameters describing campaign properties
|
||||
- `"campaignVersion"` is creator defined version
|
||||
- `"creationDateTime"` unix time of campaign creation
|
||||
- `"allowDifficultySelection"` is a boolean field (`true`/`false`) which allows or disallows to choose difficulty before scenario start
|
||||
- `"loadingBackground"` is for setting a different loading screen background
|
||||
- `"introVideo"` is for defining an optional intro video
|
||||
- `"introVideoRim"` is for the Rim around the optional video (default is INTRORIM)
|
||||
|
||||
## Scenario description
|
||||
|
||||
|
||||
@@ -12,10 +12,12 @@ set(launcher_SRCS
|
||||
modManager/cmodlistview_moc.cpp
|
||||
modManager/cmodmanager.cpp
|
||||
modManager/imageviewer_moc.cpp
|
||||
modManager/chroniclesextractor.cpp
|
||||
settingsView/csettingsview_moc.cpp
|
||||
firstLaunch/firstlaunch_moc.cpp
|
||||
main.cpp
|
||||
helper.cpp
|
||||
innoextract.cpp
|
||||
mainwindow_moc.cpp
|
||||
languages.cpp
|
||||
launcherdirs.cpp
|
||||
@@ -42,6 +44,7 @@ set(launcher_HEADERS
|
||||
modManager/cmodlistview_moc.h
|
||||
modManager/cmodmanager.h
|
||||
modManager/imageviewer_moc.h
|
||||
modManager/chroniclesextractor.h
|
||||
settingsView/csettingsview_moc.h
|
||||
firstLaunch/firstlaunch_moc.h
|
||||
mainwindow_moc.h
|
||||
@@ -51,6 +54,7 @@ set(launcher_HEADERS
|
||||
updatedialog_moc.h
|
||||
main.h
|
||||
helper.h
|
||||
innoextract.h
|
||||
prepare.h
|
||||
)
|
||||
|
||||
|
||||
@@ -21,11 +21,7 @@
|
||||
#include "../../lib/filesystem/Filesystem.h"
|
||||
#include "../helper.h"
|
||||
#include "../languages.h"
|
||||
|
||||
#ifdef ENABLE_INNOEXTRACT
|
||||
#include "cli/extract.hpp"
|
||||
#include "setup/version.hpp"
|
||||
#endif
|
||||
#include "../innoextract.h"
|
||||
|
||||
#ifdef VCMI_IOS
|
||||
#include "ios/selectdirectory.h"
|
||||
@@ -386,44 +382,11 @@ void FirstLaunchView::extractGogData()
|
||||
if(isGogGalaxyExe(tmpFileExe))
|
||||
errorText = tr("You've provided GOG Galaxy installer! This file doesn't contain the game. Please download the offline backup game installer!");
|
||||
|
||||
::extract_options o;
|
||||
o.extract = true;
|
||||
|
||||
// standard settings
|
||||
o.gog_galaxy = true;
|
||||
o.codepage = 0U;
|
||||
o.output_dir = tempDir.path().toStdString();
|
||||
o.extract_temp = true;
|
||||
o.extract_unknown = true;
|
||||
o.filenames.set_expand(true);
|
||||
|
||||
o.preserve_file_times = true; // also correctly closes file -> without it: on Windows the files are not written completely
|
||||
|
||||
try
|
||||
{
|
||||
if(errorText.isEmpty())
|
||||
process_file(tmpFileExe.toStdString(), o, [this](float progress) {
|
||||
ui->progressBarGog->setValue(progress * 100);
|
||||
qApp->processEvents();
|
||||
});
|
||||
}
|
||||
catch(const std::ios_base::failure & e)
|
||||
{
|
||||
errorText = tr("Stream error while extracting files!\nerror reason: ");
|
||||
errorText += e.what();
|
||||
}
|
||||
catch(const format_error & e)
|
||||
{
|
||||
errorText = e.what();
|
||||
}
|
||||
catch(const std::runtime_error & e)
|
||||
{
|
||||
errorText = e.what();
|
||||
}
|
||||
catch(const setup::version_error &)
|
||||
{
|
||||
errorText = tr("Not a supported Inno Setup installer!");
|
||||
}
|
||||
if(errorText.isEmpty())
|
||||
errorText = Innoextract::extract(tmpFileExe, tempDir.path(), [this](float progress) {
|
||||
ui->progressBarGog->setValue(progress * 100);
|
||||
qApp->processEvents();
|
||||
});
|
||||
|
||||
ui->progressBarGog->setVisible(false);
|
||||
ui->pushButtonGogInstall->setVisible(true);
|
||||
|
||||
62
launcher/innoextract.cpp
Normal file
62
launcher/innoextract.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* innoextract.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "innoextract.h"
|
||||
|
||||
#ifdef ENABLE_INNOEXTRACT
|
||||
#include "cli/extract.hpp"
|
||||
#include "setup/version.hpp"
|
||||
#endif
|
||||
|
||||
QString Innoextract::extract(QString installer, QString outDir, std::function<void (float percent)> cb)
|
||||
{
|
||||
QString errorText{};
|
||||
|
||||
#ifdef ENABLE_INNOEXTRACT
|
||||
::extract_options o;
|
||||
o.extract = true;
|
||||
|
||||
// standard settings
|
||||
o.gog_galaxy = true;
|
||||
o.codepage = 0U;
|
||||
o.output_dir = outDir.toStdString();
|
||||
o.extract_temp = true;
|
||||
o.extract_unknown = true;
|
||||
o.filenames.set_expand(true);
|
||||
|
||||
o.preserve_file_times = true; // also correctly closes file -> without it: on Windows the files are not written completely
|
||||
|
||||
try
|
||||
{
|
||||
process_file(installer.toStdString(), o, cb);
|
||||
}
|
||||
catch(const std::ios_base::failure & e)
|
||||
{
|
||||
errorText = tr("Stream error while extracting files!\nerror reason: ");
|
||||
errorText += e.what();
|
||||
}
|
||||
catch(const format_error & e)
|
||||
{
|
||||
errorText = e.what();
|
||||
}
|
||||
catch(const std::runtime_error & e)
|
||||
{
|
||||
errorText = e.what();
|
||||
}
|
||||
catch(const setup::version_error &)
|
||||
{
|
||||
errorText = tr("Not a supported Inno Setup installer!");
|
||||
}
|
||||
#else
|
||||
errorText = tr("VCMI was compiled without innoextract support, which is needed to extract exe files!");
|
||||
#endif
|
||||
|
||||
return errorText;
|
||||
}
|
||||
16
launcher/innoextract.h
Normal file
16
launcher/innoextract.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* innoextract.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class Innoextract : public QObject
|
||||
{
|
||||
public:
|
||||
static QString extract(QString installer, QString outDir, std::function<void (float percent)> cb = nullptr);
|
||||
};
|
||||
225
launcher/modManager/chroniclesextractor.cpp
Normal file
225
launcher/modManager/chroniclesextractor.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* chroniclesextractor.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
|
||||
#include "chroniclesextractor.h"
|
||||
|
||||
#include "../../lib/VCMIDirs.h"
|
||||
#include "../../lib/filesystem/CArchiveLoader.h"
|
||||
|
||||
#include "../innoextract.h"
|
||||
|
||||
ChroniclesExtractor::ChroniclesExtractor(QWidget *p, std::function<void(float percent)> cb) :
|
||||
parent(p), cb(cb)
|
||||
{
|
||||
}
|
||||
|
||||
bool ChroniclesExtractor::createTempDir()
|
||||
{
|
||||
tempDir = QDir(pathToQString(VCMIDirs::get().userDataPath()));
|
||||
if(tempDir.cd("tmp"))
|
||||
{
|
||||
tempDir.removeRecursively(); // remove if already exists (e.g. previous run)
|
||||
tempDir.cdUp();
|
||||
}
|
||||
tempDir.mkdir("tmp");
|
||||
if(!tempDir.cd("tmp"))
|
||||
return false; // should not happen - but avoid deleting wrong folder in any case
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChroniclesExtractor::removeTempDir()
|
||||
{
|
||||
tempDir.removeRecursively();
|
||||
}
|
||||
|
||||
int ChroniclesExtractor::getChronicleNo(QFile & file)
|
||||
{
|
||||
if(!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
QMessageBox::critical(parent, tr("File cannot opened"), file.errorString());
|
||||
return 0;
|
||||
}
|
||||
|
||||
QByteArray magic{"MZ"};
|
||||
QByteArray magicFile = file.read(magic.length());
|
||||
if(!magicFile.startsWith(magic))
|
||||
{
|
||||
QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select an gog installer file!"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
QByteArray dataBegin = file.read(1'000'000);
|
||||
int chronicle = 0;
|
||||
for (const auto& kv : chronicles) {
|
||||
if(dataBegin.contains(kv.second))
|
||||
{
|
||||
chronicle = kv.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!chronicle)
|
||||
{
|
||||
QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select an chronicle installer file!"));
|
||||
return 0;
|
||||
}
|
||||
return chronicle;
|
||||
}
|
||||
|
||||
bool ChroniclesExtractor::extractGogInstaller(QString file)
|
||||
{
|
||||
QString errorText = Innoextract::extract(file, tempDir.path(), [this](float progress) {
|
||||
float overallProgress = ((1.0 / static_cast<float>(fileCount)) * static_cast<float>(extractionFile)) + (progress / static_cast<float>(fileCount));
|
||||
if(cb)
|
||||
cb(overallProgress);
|
||||
});
|
||||
|
||||
if(!errorText.isEmpty())
|
||||
{
|
||||
QMessageBox::critical(parent, tr("Extracting error!"), errorText);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChroniclesExtractor::createBaseMod() const
|
||||
{
|
||||
QDir dir(pathToQString(VCMIDirs::get().userDataPath() / "Mods"));
|
||||
dir.mkdir("chronicles");
|
||||
dir.cd("chronicles");
|
||||
dir.mkdir("Mods");
|
||||
|
||||
QJsonObject mod
|
||||
{
|
||||
{ "modType", "Expansion" },
|
||||
{ "name", tr("Heroes Chronicles") },
|
||||
{ "description", tr("Heroes Chronicles") },
|
||||
{ "author", "3DO" },
|
||||
{ "version", "1.0" },
|
||||
{ "contact", "vcmi.eu" },
|
||||
};
|
||||
|
||||
QFile jsonFile(dir.filePath("mod.json"));
|
||||
jsonFile.open(QFile::WriteOnly);
|
||||
jsonFile.write(QJsonDocument(mod).toJson());
|
||||
}
|
||||
|
||||
void ChroniclesExtractor::createChronicleMod(int no)
|
||||
{
|
||||
QDir dir(pathToQString(VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "Mods" / ("chronicles_" + std::to_string(no))));
|
||||
dir.removeRecursively();
|
||||
dir.mkpath(".");
|
||||
|
||||
QByteArray tmpChronicles = chronicles.at(no);
|
||||
tmpChronicles.replace('\0', "");
|
||||
|
||||
QJsonObject mod
|
||||
{
|
||||
{ "modType", "Expansion" },
|
||||
{ "name", QString::number(no) + " - " + QString(tmpChronicles) },
|
||||
{ "description", tr("Heroes Chronicles") + " - " + QString::number(no) + " - " + QString(tmpChronicles) },
|
||||
{ "author", "3DO" },
|
||||
{ "version", "1.0" },
|
||||
{ "contact", "vcmi.eu" },
|
||||
};
|
||||
|
||||
QFile jsonFile(dir.filePath("mod.json"));
|
||||
jsonFile.open(QFile::WriteOnly);
|
||||
jsonFile.write(QJsonDocument(mod).toJson());
|
||||
|
||||
dir.cd("content");
|
||||
|
||||
extractFiles(no);
|
||||
}
|
||||
|
||||
void ChroniclesExtractor::extractFiles(int no) const
|
||||
{
|
||||
QByteArray tmpChronicles = chronicles.at(no);
|
||||
tmpChronicles.replace('\0', "");
|
||||
|
||||
std::string chroniclesDir = "chronicles_" + std::to_string(no);
|
||||
QDir tmpDir = tempDir.filePath(tempDir.entryList({"app"}, QDir::Filter::Dirs).front());
|
||||
tmpDir.setPath(tmpDir.filePath(tmpDir.entryList({QString(tmpChronicles)}, QDir::Filter::Dirs).front()));
|
||||
tmpDir.setPath(tmpDir.filePath(tmpDir.entryList({"data"}, QDir::Filter::Dirs).front()));
|
||||
auto basePath = VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "Mods" / chroniclesDir / "content";
|
||||
QDir outDirDataPortraits(pathToQString(VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "content" / "Data"));
|
||||
QDir outDirData(pathToQString(basePath / "Data" / chroniclesDir));
|
||||
QDir outDirSprites(pathToQString(basePath / "Sprites" / chroniclesDir));
|
||||
QDir outDirVideo(pathToQString(basePath / "Video" / chroniclesDir));
|
||||
QDir outDirSounds(pathToQString(basePath / "Sounds" / chroniclesDir));
|
||||
QDir outDirMaps(pathToQString(basePath / "Maps"));
|
||||
|
||||
auto extract = [](QDir scrDir, QDir dest, QString file, std::vector<std::string> files = {}){
|
||||
CArchiveLoader archive("", scrDir.filePath(scrDir.entryList({file}).front()).toStdString(), false);
|
||||
for(auto & entry : archive.getEntries())
|
||||
if(files.empty())
|
||||
archive.extractToFolder(dest.absolutePath().toStdString(), "", entry.second, true);
|
||||
else
|
||||
{
|
||||
for(const auto & item : files)
|
||||
if(boost::algorithm::to_lower_copy(entry.second.name).find(boost::algorithm::to_lower_copy(item)) != std::string::npos)
|
||||
archive.extractToFolder(dest.absolutePath().toStdString(), "", entry.second, true);
|
||||
}
|
||||
};
|
||||
|
||||
extract(tmpDir, outDirData, "xBitmap.lod");
|
||||
extract(tmpDir, outDirData, "xlBitmap.lod");
|
||||
extract(tmpDir, outDirSprites, "xSprite.lod");
|
||||
extract(tmpDir, outDirSprites, "xlSprite.lod");
|
||||
extract(tmpDir, outDirVideo, "xVideo.vid");
|
||||
extract(tmpDir, outDirSounds, "xSound.snd");
|
||||
|
||||
tmpDir.cdUp();
|
||||
if(tmpDir.entryList({"maps"}, QDir::Filter::Dirs).size()) // special case for "The World Tree": the map is in the "Maps" folder instead of inside the lod
|
||||
{
|
||||
QDir tmpDirMaps = tmpDir.filePath(tmpDir.entryList({"maps"}, QDir::Filter::Dirs).front());
|
||||
for(const auto & entry : tmpDirMaps.entryList())
|
||||
QFile(tmpDirMaps.filePath(entry)).copy(outDirData.filePath(entry));
|
||||
}
|
||||
|
||||
tmpDir.cdUp();
|
||||
QDir tmpDirData = tmpDir.filePath(tmpDir.entryList({"data"}, QDir::Filter::Dirs).front());
|
||||
auto tarnumPortraits = std::vector<std::string>{"HPS137", "HPS138", "HPS139", "HPS140", "HPS141", "HPS142", "HPL137", "HPL138", "HPL139", "HPL140", "HPL141", "HPL142"};
|
||||
extract(tmpDirData, outDirDataPortraits, "bitmap.lod", tarnumPortraits);
|
||||
extract(tmpDirData, outDirData, "lbitmap.lod", std::vector<std::string>{"INTRORIM"});
|
||||
|
||||
if(!outDirMaps.exists())
|
||||
outDirMaps.mkpath(".");
|
||||
QString campaignFileName = "Hc" + QString::number(no) + "_Main.h3c";
|
||||
QFile(outDirData.filePath(outDirData.entryList({"Main.h3c"}).front())).copy(outDirMaps.filePath(campaignFileName));
|
||||
}
|
||||
|
||||
void ChroniclesExtractor::installChronicles(QStringList exe)
|
||||
{
|
||||
extractionFile = -1;
|
||||
fileCount = exe.size();
|
||||
for(QString f : exe)
|
||||
{
|
||||
extractionFile++;
|
||||
QFile file(f);
|
||||
|
||||
int chronicleNo = getChronicleNo(file);
|
||||
if(!chronicleNo)
|
||||
continue;
|
||||
|
||||
if(!createTempDir())
|
||||
continue;
|
||||
|
||||
if(!extractGogInstaller(f))
|
||||
continue;
|
||||
|
||||
createBaseMod();
|
||||
createChronicleMod(chronicleNo);
|
||||
|
||||
removeTempDir();
|
||||
}
|
||||
}
|
||||
47
launcher/modManager/chroniclesextractor.h
Normal file
47
launcher/modManager/chroniclesextractor.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* chroniclesextractor.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../StdInc.h"
|
||||
|
||||
class ChroniclesExtractor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QWidget *parent;
|
||||
std::function<void(float percent)> cb;
|
||||
|
||||
QDir tempDir;
|
||||
int extractionFile;
|
||||
int fileCount;
|
||||
|
||||
bool createTempDir();
|
||||
void removeTempDir();
|
||||
int getChronicleNo(QFile & file);
|
||||
bool extractGogInstaller(QString filePath);
|
||||
void createBaseMod() const;
|
||||
void createChronicleMod(int no);
|
||||
void extractFiles(int no) const;
|
||||
|
||||
const std::map<int, QByteArray> chronicles = {
|
||||
{1, QByteArray{reinterpret_cast<const char*>(u"Warlords of the Wasteland"), 50}},
|
||||
{2, QByteArray{reinterpret_cast<const char*>(u"Conquest of the Underworld"), 52}},
|
||||
{3, QByteArray{reinterpret_cast<const char*>(u"Masters of the Elements"), 46}},
|
||||
{4, QByteArray{reinterpret_cast<const char*>(u"Clash of the Dragons"), 40}},
|
||||
{5, QByteArray{reinterpret_cast<const char*>(u"The World Tree"), 28}},
|
||||
{6, QByteArray{reinterpret_cast<const char*>(u"The Fiery Moon"), 28}},
|
||||
{7, QByteArray{reinterpret_cast<const char*>(u"Revolt of the Beastmasters"), 52}},
|
||||
{8, QByteArray{reinterpret_cast<const char*>(u"The Sword of Frost"), 36}}
|
||||
};
|
||||
public:
|
||||
void installChronicles(QStringList exe);
|
||||
|
||||
ChroniclesExtractor(QWidget *p, std::function<void(float percent)> cb = nullptr);
|
||||
};
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "cmodlistmodel_moc.h"
|
||||
#include "cmodmanager.h"
|
||||
#include "cdownloadmanager_moc.h"
|
||||
#include "chroniclesextractor.h"
|
||||
#include "../settingsView/csettingsview_moc.h"
|
||||
#include "../launcherdirs.h"
|
||||
#include "../jsonutils.h"
|
||||
@@ -29,6 +30,9 @@
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/texts/Languages.h"
|
||||
#include "../../lib/modding/CModVersion.h"
|
||||
#include "../../lib/filesystem/Filesystem.h"
|
||||
|
||||
#include <future>
|
||||
|
||||
static double mbToBytes(double mb)
|
||||
{
|
||||
@@ -55,7 +59,7 @@ void CModListView::dragEnterEvent(QDragEnterEvent* event)
|
||||
{
|
||||
if(event->mimeData()->hasUrls())
|
||||
for(const auto & url : event->mimeData()->urls())
|
||||
for(const auto & ending : QStringList({".zip", ".h3m", ".h3c", ".vmap", ".vcmp", ".json"}))
|
||||
for(const auto & ending : QStringList({".zip", ".h3m", ".h3c", ".vmap", ".vcmp", ".json", ".exe"}))
|
||||
if(url.fileName().endsWith(ending, Qt::CaseInsensitive))
|
||||
{
|
||||
event->acceptProposedAction();
|
||||
@@ -636,8 +640,16 @@ void CModListView::on_installFromFileButton_clicked()
|
||||
// https://bugreports.qt.io/browse/QTBUG-98651
|
||||
QTimer::singleShot(0, this, [this]
|
||||
{
|
||||
QString filter = tr("All supported files") + " (*.h3m *.vmap *.h3c *.vcmp *.zip *.json);;" + tr("Maps") + " (*.h3m *.vmap);;" + tr("Campaigns") + " (*.h3c *.vcmp);;" + tr("Configs") + " (*.json);;" + tr("Mods") + " (*.zip)";
|
||||
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files (configs, mods, maps, campaigns) to install..."), QDir::homePath(), filter);
|
||||
QString filter = tr("All supported files") + " (*.h3m *.vmap *.h3c *.vcmp *.zip *.json *.exe);;" +
|
||||
tr("Maps") + " (*.h3m *.vmap);;" +
|
||||
tr("Campaigns") + " (*.h3c *.vcmp);;" +
|
||||
tr("Configs") + " (*.json);;" +
|
||||
tr("Mods") + " (*.zip);;" +
|
||||
tr("Gog files") + " (*.exe)";
|
||||
#if defined(VCMI_MOBILE)
|
||||
filter = tr("All files (*.*)"); //Workaround for sometimes incorrect mime for some extensions (e.g. for exe)
|
||||
#endif
|
||||
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files (configs, mods, maps, campaigns, gog files) to install..."), QDir::homePath(), filter);
|
||||
|
||||
for(const auto & file : files)
|
||||
{
|
||||
@@ -786,6 +798,7 @@ void CModListView::installFiles(QStringList files)
|
||||
QStringList mods;
|
||||
QStringList maps;
|
||||
QStringList images;
|
||||
QStringList exe;
|
||||
QVector<QVariantMap> repositories;
|
||||
|
||||
// TODO: some better way to separate zip's with mods and downloaded repository files
|
||||
@@ -795,6 +808,8 @@ void CModListView::installFiles(QStringList files)
|
||||
mods.push_back(filename);
|
||||
else if(filename.endsWith(".h3m", Qt::CaseInsensitive) || filename.endsWith(".h3c", Qt::CaseInsensitive) || filename.endsWith(".vmap", Qt::CaseInsensitive) || filename.endsWith(".vcmp", Qt::CaseInsensitive))
|
||||
maps.push_back(filename);
|
||||
if(filename.endsWith(".exe", Qt::CaseInsensitive))
|
||||
exe.push_back(filename);
|
||||
else if(filename.endsWith(".json", Qt::CaseInsensitive))
|
||||
{
|
||||
//download and merge additional files
|
||||
@@ -832,6 +847,35 @@ void CModListView::installFiles(QStringList files)
|
||||
if(!maps.empty())
|
||||
installMaps(maps);
|
||||
|
||||
if(!exe.empty())
|
||||
{
|
||||
ui->progressBar->setFormat(tr("Installing chronicles"));
|
||||
|
||||
float prog = 0.0;
|
||||
|
||||
auto futureExtract = std::async(std::launch::async, [this, exe, &prog]()
|
||||
{
|
||||
ChroniclesExtractor ce(this, [&prog](float progress) { prog = progress; });
|
||||
ce.installChronicles(exe);
|
||||
return true;
|
||||
});
|
||||
|
||||
while(futureExtract.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready)
|
||||
{
|
||||
emit extractionProgress(static_cast<int>(prog * 1000.f), 1000);
|
||||
qApp->processEvents();
|
||||
}
|
||||
|
||||
if(futureExtract.get())
|
||||
{
|
||||
//update
|
||||
CResourceHandler::get("initial")->updateFilteredFiles([](const std::string &){ return true; });
|
||||
manager->loadMods();
|
||||
modModel->reloadRepositories();
|
||||
emit modsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
if(!images.empty())
|
||||
loadScreenshots();
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ void GameSettings::load(const JsonNode & input)
|
||||
{EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" },
|
||||
{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" },
|
||||
{EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" },
|
||||
{EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" },
|
||||
{EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" },
|
||||
{EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" },
|
||||
{EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" },
|
||||
|
||||
@@ -58,6 +58,7 @@ enum class EGameSettings
|
||||
MAP_FORMAT_RESTORATION_OF_ERATHIA,
|
||||
MAP_FORMAT_ARMAGEDDONS_BLADE,
|
||||
MAP_FORMAT_SHADOW_OF_DEATH,
|
||||
MAP_FORMAT_CHRONICLES,
|
||||
MAP_FORMAT_HORN_OF_THE_ABYSS,
|
||||
MAP_FORMAT_JSON_VCMI,
|
||||
MAP_FORMAT_IN_THE_WAKE_OF_GODS,
|
||||
|
||||
@@ -18,7 +18,7 @@ enum class CampaignVersion : uint8_t
|
||||
AB = 5,
|
||||
SoD = 6,
|
||||
WoG = 6,
|
||||
// Chr = 7, // Heroes Chronicles, likely identical to SoD, untested
|
||||
Chr = 7,
|
||||
|
||||
VCMI = 1,
|
||||
VCMI_MIN = 1,
|
||||
|
||||
@@ -38,12 +38,14 @@ void CampaignHandler::readCampaign(Campaign * ret, const std::vector<ui8> & inpu
|
||||
CBinaryReader reader(&stream);
|
||||
|
||||
readHeaderFromMemory(*ret, reader, filename, modName, encoding);
|
||||
ret->overrideCampaign();
|
||||
|
||||
for(int g = 0; g < ret->numberOfScenarios; ++g)
|
||||
{
|
||||
auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
|
||||
ret->scenarios[scenarioID] = readScenarioFromMemory(reader, *ret);
|
||||
}
|
||||
ret->overrideCampaignScenarios();
|
||||
}
|
||||
else // text format (json)
|
||||
{
|
||||
@@ -166,6 +168,9 @@ void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader
|
||||
ret.filename = filename;
|
||||
ret.modName = modName;
|
||||
ret.encoding = encoding;
|
||||
ret.loadingBackground = ImagePath::fromJson(reader["loadingBackground"]);
|
||||
ret.introVideoRim = ImagePath::fromJson(reader["introVideoRim"]);
|
||||
ret.introVideo = VideoPath::fromJson(reader["introVideo"]);
|
||||
}
|
||||
|
||||
CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
|
||||
@@ -392,7 +397,8 @@ void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader
|
||||
{
|
||||
ret.version = static_cast<CampaignVersion>(reader.readUInt32());
|
||||
ui8 campId = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19]
|
||||
ret.loadLegacyData(campId);
|
||||
if(ret.version != CampaignVersion::Chr) // For chronicles: Will be overridden later; Chronicles uses own logic (reusing OH3 ID's)
|
||||
ret.loadLegacyData(campId);
|
||||
ret.name.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "name"));
|
||||
ret.description.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "description"));
|
||||
ret.author.appendRawString("");
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "../mapObjects/CGHeroInstance.h"
|
||||
#include "../serializer/JsonDeserializer.h"
|
||||
#include "../serializer/JsonSerializer.h"
|
||||
#include "../json/JsonUtils.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@@ -138,6 +139,12 @@ void CampaignHeader::loadLegacyData(ui8 campId)
|
||||
numberOfScenarios = VLC->generaltexth->getCampaignLength(campId);
|
||||
}
|
||||
|
||||
void CampaignHeader::loadLegacyData(CampaignRegions regions, int numOfScenario)
|
||||
{
|
||||
campaignRegions = regions;
|
||||
numberOfScenarios = numOfScenario;
|
||||
}
|
||||
|
||||
bool CampaignHeader::playerSelectedDifficulty() const
|
||||
{
|
||||
return difficultyChosenByPlayer;
|
||||
@@ -198,6 +205,21 @@ AudioPath CampaignHeader::getMusic() const
|
||||
return music;
|
||||
}
|
||||
|
||||
ImagePath CampaignHeader::getLoadingBackground() const
|
||||
{
|
||||
return loadingBackground;
|
||||
}
|
||||
|
||||
ImagePath CampaignHeader::getIntroVideoRim() const
|
||||
{
|
||||
return introVideoRim;
|
||||
}
|
||||
|
||||
VideoPath CampaignHeader::getIntroVideo() const
|
||||
{
|
||||
return introVideo;
|
||||
}
|
||||
|
||||
const CampaignRegions & CampaignHeader::getRegions() const
|
||||
{
|
||||
return campaignRegions;
|
||||
@@ -455,6 +477,45 @@ std::set<CampaignScenarioID> Campaign::allScenarios() const
|
||||
return result;
|
||||
}
|
||||
|
||||
void Campaign::overrideCampaign()
|
||||
{
|
||||
const JsonNode node = JsonUtils::assembleFromFiles("config/campaignOverrides.json");
|
||||
for (auto & entry : node.Struct())
|
||||
if(filename == entry.first)
|
||||
{
|
||||
if(!entry.second["regions"].isNull() && !entry.second["scenarioCount"].isNull())
|
||||
loadLegacyData(CampaignRegions::fromJson(entry.second["regions"]), entry.second["scenarioCount"].Integer());
|
||||
if(!entry.second["loadingBackground"].isNull())
|
||||
loadingBackground = ImagePath::builtin(entry.second["loadingBackground"].String());
|
||||
if(!entry.second["introVideoRim"].isNull())
|
||||
introVideoRim = ImagePath::builtin(entry.second["introVideoRim"].String());
|
||||
if(!entry.second["introVideo"].isNull())
|
||||
introVideo = VideoPath::builtin(entry.second["introVideo"].String());
|
||||
}
|
||||
}
|
||||
|
||||
void Campaign::overrideCampaignScenarios()
|
||||
{
|
||||
const JsonNode node = JsonUtils::assembleFromFiles("config/campaignOverrides.json");
|
||||
for (auto & entry : node.Struct())
|
||||
if(filename == entry.first)
|
||||
{
|
||||
if(!entry.second["scenarios"].isNull())
|
||||
{
|
||||
auto sc = entry.second["scenarios"].Vector();
|
||||
for(int i = 0; i < sc.size(); i++)
|
||||
{
|
||||
auto it = scenarios.begin();
|
||||
std::advance(it, i);
|
||||
if(!sc.at(i)["voiceProlog"].isNull())
|
||||
it->second.prolog.prologVoice = AudioPath::builtin(sc.at(i)["voiceProlog"].String());
|
||||
if(!sc.at(i)["voiceEpilog"].isNull())
|
||||
it->second.epilog.prologVoice = AudioPath::builtin(sc.at(i)["voiceEpilog"].String());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Campaign::scenariosCount() const
|
||||
{
|
||||
return allScenarios().size();
|
||||
|
||||
@@ -83,6 +83,7 @@ public:
|
||||
class DLL_LINKAGE CampaignHeader : public boost::noncopyable
|
||||
{
|
||||
friend class CampaignHandler;
|
||||
friend class Campaign;
|
||||
|
||||
CampaignVersion version = CampaignVersion::NONE;
|
||||
CampaignRegions campaignRegions;
|
||||
@@ -96,11 +97,15 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable
|
||||
std::string filename;
|
||||
std::string modName;
|
||||
std::string encoding;
|
||||
ImagePath loadingBackground;
|
||||
ImagePath introVideoRim;
|
||||
VideoPath introVideo;
|
||||
|
||||
int numberOfScenarios = 0;
|
||||
bool difficultyChosenByPlayer = false;
|
||||
|
||||
void loadLegacyData(ui8 campId);
|
||||
void loadLegacyData(CampaignRegions regions, int numOfScenario);
|
||||
|
||||
TextContainerRegistrable textContainer;
|
||||
|
||||
@@ -118,6 +123,9 @@ public:
|
||||
std::string getModName() const;
|
||||
std::string getEncoding() const;
|
||||
AudioPath getMusic() const;
|
||||
ImagePath getLoadingBackground() const;
|
||||
ImagePath getIntroVideoRim() const;
|
||||
VideoPath getIntroVideo() const;
|
||||
|
||||
const CampaignRegions & getRegions() const;
|
||||
TextContainerRegistrable & getTexts();
|
||||
@@ -142,6 +150,12 @@ public:
|
||||
h & music;
|
||||
h & encoding;
|
||||
h & textContainer;
|
||||
if (h.version >= Handler::Version::CHRONICLES_SUPPORT)
|
||||
{
|
||||
h & loadingBackground;
|
||||
h & introVideoRim;
|
||||
h & introVideo;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -247,6 +261,9 @@ public:
|
||||
std::set<CampaignScenarioID> allScenarios() const;
|
||||
int scenariosCount() const;
|
||||
|
||||
void overrideCampaign();
|
||||
void overrideCampaignScenarios();
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & static_cast<CampaignHeader&>(*this);
|
||||
|
||||
@@ -197,6 +197,11 @@ std::string CArchiveLoader::getMountPoint() const
|
||||
return mountPoint;
|
||||
}
|
||||
|
||||
const std::unordered_map<ResourcePath, ArchiveEntry> & CArchiveLoader::getEntries() const
|
||||
{
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::unordered_set<ResourcePath> CArchiveLoader::getFilteredFiles(std::function<bool(const ResourcePath &)> filter) const
|
||||
{
|
||||
std::unordered_set<ResourcePath> foundID;
|
||||
@@ -209,7 +214,7 @@ std::unordered_set<ResourcePath> CArchiveLoader::getFilteredFiles(std::function<
|
||||
return foundID;
|
||||
}
|
||||
|
||||
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, const ArchiveEntry & entry) const
|
||||
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, const ArchiveEntry & entry, bool absolute) const
|
||||
{
|
||||
si64 currentPosition = fileStream.tell(); // save filestream position
|
||||
|
||||
@@ -217,7 +222,7 @@ void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, CInput
|
||||
fileStream.seek(entry.offset);
|
||||
fileStream.read(data.data(), entry.fullSize);
|
||||
|
||||
boost::filesystem::path extractedFilePath = createExtractedFilePath(outputSubFolder, entry.name);
|
||||
boost::filesystem::path extractedFilePath = createExtractedFilePath(outputSubFolder, entry.name, absolute);
|
||||
|
||||
// writeToOutputFile
|
||||
std::ofstream out(extractedFilePath.string(), std::ofstream::binary);
|
||||
@@ -227,17 +232,17 @@ void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, CInput
|
||||
fileStream.seek(currentPosition); // restore filestream position
|
||||
}
|
||||
|
||||
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry) const
|
||||
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry, bool absolute) const
|
||||
{
|
||||
std::unique_ptr<CInputStream> inputStream = load(ResourcePath(mountPoint + entry.name));
|
||||
|
||||
entry.offset = 0;
|
||||
extractToFolder(outputSubFolder, *inputStream, entry);
|
||||
extractToFolder(outputSubFolder, *inputStream, entry, absolute);
|
||||
}
|
||||
|
||||
boost::filesystem::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName)
|
||||
boost::filesystem::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName, bool absolute)
|
||||
{
|
||||
boost::filesystem::path extractionFolderPath = VCMIDirs::get().userExtractedPath() / outputSubFolder;
|
||||
boost::filesystem::path extractionFolderPath = absolute ? outputSubFolder : VCMIDirs::get().userExtractedPath() / outputSubFolder;
|
||||
boost::filesystem::path extractedFilePath = extractionFolderPath / entryName;
|
||||
|
||||
boost::filesystem::create_directories(extractionFolderPath);
|
||||
|
||||
@@ -63,12 +63,13 @@ public:
|
||||
std::unique_ptr<CInputStream> load(const ResourcePath & resourceName) const override;
|
||||
bool existsResource(const ResourcePath & resourceName) const override;
|
||||
std::string getMountPoint() const override;
|
||||
const std::unordered_map<ResourcePath, ArchiveEntry> & getEntries() const;
|
||||
void updateFilteredFiles(std::function<bool(const std::string &)> filter) const override {}
|
||||
std::unordered_set<ResourcePath> getFilteredFiles(std::function<bool(const ResourcePath &)> filter) const override;
|
||||
/** Extracts one archive entry to the specified subfolder. Used for Video and Sound */
|
||||
void extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, const ArchiveEntry & entry) const;
|
||||
void extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, const ArchiveEntry & entry, bool absolute = false) const;
|
||||
/** Extracts one archive entry to the specified subfolder. Used for Images, Sprites, etc */
|
||||
void extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry) const;
|
||||
void extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry, bool absolute = false) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
@@ -105,6 +106,6 @@ private:
|
||||
};
|
||||
|
||||
/** Constructs the file path for the extracted file. Creates the subfolder hierarchy aswell **/
|
||||
boost::filesystem::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName);
|
||||
boost::filesystem::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName, bool absolute);
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
@@ -172,6 +172,8 @@ int CMapInfo::getMapSizeFormatIconId() const
|
||||
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE)["iconIndex"].Integer();
|
||||
case EMapFormat::SOD:
|
||||
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH)["iconIndex"].Integer();
|
||||
case EMapFormat::CHR:
|
||||
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES)["iconIndex"].Integer();
|
||||
case EMapFormat::WOG:
|
||||
return VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS)["iconIndex"].Integer();
|
||||
case EMapFormat::HOTA:
|
||||
|
||||
@@ -157,6 +157,7 @@ std::unique_ptr<IMapLoader> CMapService::getMapLoader(std::unique_ptr<CInputStre
|
||||
case static_cast<int>(EMapFormat::AB) :
|
||||
case static_cast<int>(EMapFormat::ROE) :
|
||||
case static_cast<int>(EMapFormat::SOD) :
|
||||
case static_cast<int>(EMapFormat::CHR) :
|
||||
case static_cast<int>(EMapFormat::HOTA) :
|
||||
return std::unique_ptr<IMapLoader>(new CMapLoaderH3M(mapName, modName, encoding, stream.get()));
|
||||
default :
|
||||
|
||||
@@ -25,6 +25,8 @@ MapFormatFeaturesH3M MapFormatFeaturesH3M::find(EMapFormat format, uint32_t hota
|
||||
return getFeaturesAB();
|
||||
case EMapFormat::SOD:
|
||||
return getFeaturesSOD();
|
||||
case EMapFormat::CHR:
|
||||
return getFeaturesCHR();
|
||||
case EMapFormat::WOG:
|
||||
return getFeaturesWOG();
|
||||
case EMapFormat::HOTA:
|
||||
@@ -107,6 +109,16 @@ MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesSOD()
|
||||
return result;
|
||||
}
|
||||
|
||||
MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesCHR()
|
||||
{
|
||||
MapFormatFeaturesH3M result = getFeaturesSOD();
|
||||
result.levelCHR = true;
|
||||
|
||||
result.heroesPortraitsCount = 169; // +6x tarnum
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesWOG()
|
||||
{
|
||||
MapFormatFeaturesH3M result = getFeaturesSOD();
|
||||
|
||||
@@ -21,6 +21,7 @@ public:
|
||||
static MapFormatFeaturesH3M getFeaturesROE();
|
||||
static MapFormatFeaturesH3M getFeaturesAB();
|
||||
static MapFormatFeaturesH3M getFeaturesSOD();
|
||||
static MapFormatFeaturesH3M getFeaturesCHR();
|
||||
static MapFormatFeaturesH3M getFeaturesWOG();
|
||||
static MapFormatFeaturesH3M getFeaturesHOTA(uint32_t hotaVersion);
|
||||
|
||||
@@ -64,6 +65,7 @@ public:
|
||||
bool levelROE = false;
|
||||
bool levelAB = false;
|
||||
bool levelSOD = false;
|
||||
bool levelCHR = false;
|
||||
bool levelWOG = false;
|
||||
bool levelHOTA0 = false;
|
||||
bool levelHOTA1 = false;
|
||||
|
||||
@@ -19,7 +19,7 @@ enum class EMapFormat : uint8_t
|
||||
ROE = 0x0e, // 14
|
||||
AB = 0x15, // 21
|
||||
SOD = 0x1c, // 28
|
||||
// CHR = 0x1d, // 29 Heroes Chronicles, presumably - identical to SoD, untested
|
||||
CHR = 0x1d, // 29
|
||||
HOTA = 0x20, // 32
|
||||
WOG = 0x33, // 51
|
||||
VCMI = 0x64
|
||||
|
||||
@@ -135,6 +135,8 @@ static MapIdentifiersH3M generateMapping(EMapFormat format)
|
||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE));
|
||||
if(features.levelSOD)
|
||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH));
|
||||
if(features.levelCHR)
|
||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES));
|
||||
if(features.levelWOG)
|
||||
identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
|
||||
if(features.levelHOTA0)
|
||||
@@ -161,6 +163,7 @@ static std::map<EMapFormat, MapIdentifiersH3M> generateMappings()
|
||||
addMapping(EMapFormat::ROE);
|
||||
addMapping(EMapFormat::AB);
|
||||
addMapping(EMapFormat::SOD);
|
||||
addMapping(EMapFormat::CHR);
|
||||
addMapping(EMapFormat::HOTA);
|
||||
addMapping(EMapFormat::WOG);
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ enum class ESerializationVersion : int32_t
|
||||
NEW_MARKETS, // 857 - reworked market classes
|
||||
PLAYER_STATE_OWNED_OBJECTS, // 858 - player state stores all owned objects in a single list
|
||||
SAVE_COMPATIBILITY_FIXES, // 859 - implementation of previoulsy postponed changes to serialization
|
||||
CHRONICLES_SUPPORT, // 860 - support for heroes chronicles
|
||||
|
||||
CURRENT = SAVE_COMPATIBILITY_FIXES
|
||||
CURRENT = CHRONICLES_SUPPORT
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user