Browse Source

allow defining second video in campaign

Laserlicht 4 days ago
parent
commit
e27a2ff5bd

+ 3 - 11
client/mainmenu/CPrologEpilogVideo.cpp

@@ -30,18 +30,10 @@ CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::f
 
 	backgroundAroundMenu = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(-pos.x, -pos.y, ENGINE->screenDimensions().x, ENGINE->screenDimensions().y));
 
-	//TODO: remove hardcoded paths. Some of campaigns video actually consist from 2 parts
-	// however, currently our campaigns format expects only	a single video file
-	static const std::map<VideoPath, VideoPath> pairedVideoFiles = {
-		{ VideoPath::builtin("EVIL2AP1"),  VideoPath::builtin("EVIL2AP2") },
-		{ VideoPath::builtin("H3ABdb4"),   VideoPath::builtin("H3ABdb4b") },
-		{ VideoPath::builtin("H3x2_RNe1"), VideoPath::builtin("H3x2_RNe2") },
-	};
-
-	if (pairedVideoFiles.count(spe.prologVideo))
-		videoPlayer = std::make_shared<VideoWidget>(Point(0, 0), spe.prologVideo, pairedVideoFiles.at(spe.prologVideo), true);
+	if (!spe.prologVideo.second.empty())
+		videoPlayer = std::make_shared<VideoWidget>(Point(0, 0), spe.prologVideo.first, spe.prologVideo.second, true);
 	else
-		videoPlayer = std::make_shared<VideoWidget>(Point(0, 0), spe.prologVideo, true);
+		videoPlayer = std::make_shared<VideoWidget>(Point(0, 0), spe.prologVideo.first, true);
 
 	//some videos are 800x600 in size while some are 800x400
 	if (videoPlayer->pos.h == 400)

+ 3 - 0
config/gameConfig.json

@@ -325,6 +325,7 @@
 					"GOOD2D.SMK" : 12,
 					"EVIL2A.SMK" : 13,
 					"EVIL2AP1.SMK" : 14,
+					"EVIL2AP2.SMK" : 14, // looped
 					"EVIL2B.SMK" : 15,
 					"EVIL2C.SMK" : 16,
 					"EVIL2D.SMK" : 17,
@@ -383,6 +384,7 @@
 					"H3ABdb2.smk" : 34,
 					"H3ABdb3.smk" : 35,
 					"H3ABdb4.smk" : 36,
+					"H3ABdb4b.smk" : 36, // looped
 					"H3ABdb5.smk" : 37,
 					"H3ABds1.smk" : 38,
 					"H3ABds2.smk" : 39,
@@ -456,6 +458,7 @@
 					"H3x2_RNc.smk" : 80,
 					"H3x2_RNd.smk" : 81,
 					"H3x2_RNe1.smk": 82,
+					"H3x2_RNe2.smk": 82, // looped
 					"H3x2_SPa.smk" : 83,
 					"H3x2_SPb.smk" : 84,
 					"H3x2_SPc.smk" : 85,

+ 1 - 1
docs/modders/Campaign_Format.md

@@ -98,7 +98,7 @@ Prolog and epilog properties are optional
 
 ```json
 {
-    "video": "NEUTRALA.smk", //video to show
+    "video": ["NEUTRALA.smk"], //video to show (if second video in array: this video will looped after playing the first)
     "music": "musicFile.ogg", //music to play, should be located in music directory
     "voice": "musicFile.wav", //voice to play, should be located in sounds directory
     "text": "some long text" //text to be shown

+ 9 - 3
lib/campaign/CampaignHandler.cpp

@@ -187,7 +187,13 @@ CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
 		ret.hasPrologEpilog = !identifier.isNull();
 		if(ret.hasPrologEpilog)
 		{
-			ret.prologVideo = VideoPath::fromJson(identifier["video"]);
+			auto loadStringOrVector = [](auto & target, auto & node){
+				if(node.isVector())
+					target = {VideoPath::fromJson(node.Vector().at(0)), node.Vector().size() > 1 ? VideoPath::fromJson(node.Vector().at(1)) : VideoPath::builtin("")};
+				else
+					target = {VideoPath::fromJson(node), VideoPath::builtin("")};
+			};
+			loadStringOrVector(ret.prologVideo, identifier["video"]);
 			ret.prologMusic = AudioPath::fromJson(identifier["music"]);
 			ret.prologVoice = AudioPath::fromJson(identifier["voice"]);
 			ret.prologText.jsonDeserialize(identifier["text"]);
@@ -218,7 +224,7 @@ JsonNode CampaignHandler::writeScenarioToJson(const CampaignScenario & scenario)
 		JsonNode node;
 		if(elem.hasPrologEpilog)
 		{
-			node["video"].String() = elem.prologVideo.getName();
+			node["video"].Vector() = JsonVector{ JsonNode(elem.prologVideo.first.getName()), JsonNode(elem.prologVideo.second.getName()) };
 			node["music"].String() = elem.prologMusic.getName();
 			node["voice"].String() = elem.prologVoice.getName();
 			node["text"].String() = elem.prologText.toString();
@@ -390,7 +396,7 @@ CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader
 		{
 			ret.prologVideo = mapping.remapCampaignVideo(reader.readUInt8());
 			ret.prologMusic = mapping.remapCampaignMusic(reader.readUInt8());
-			logGlobal->trace("Campaign %s, scenario %s: music theme: %s, video: %s", header.filename, identifier, ret.prologMusic.getOriginalName(), ret.prologVideo.getOriginalName());
+			logGlobal->trace("Campaign %s, scenario %s: music theme: %s, video: %s", header.filename, identifier, ret.prologMusic.getOriginalName(), ret.prologVideo.first.getOriginalName());
 			ret.prologText.appendTextID(readLocalizedString(header, reader, header.filename, header.modName, header.encoding, identifier));
 		}
 		return ret;

+ 5 - 2
lib/campaign/CampaignScenarioPrologEpilog.h

@@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 struct DLL_LINKAGE CampaignScenarioPrologEpilog
 {
 	bool hasPrologEpilog = false;
-	VideoPath prologVideo;
+	std::pair<VideoPath, VideoPath> prologVideo;
 	AudioPath prologMusic;
 	AudioPath prologVoice;
 	MetaString prologText;
@@ -25,7 +25,10 @@ struct DLL_LINKAGE CampaignScenarioPrologEpilog
 	template <typename Handler> void serialize(Handler &h)
 	{
 		h & hasPrologEpilog;
-		h & prologVideo;
+		if(h.version >= Handler::Version::CAMPAIGN_VIDEO)
+			h & prologVideo;
+		else
+			h & prologVideo.first;
 		h & prologMusic;
 		h & prologVoice;
 		h & prologText;

+ 7 - 2
lib/mapping/MapIdentifiersH3M.cpp

@@ -95,7 +95,12 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping)
 	}
 
 	for (auto entry : mapping["campaignVideo"].Struct())
-		mappingCampaignVideo[entry.second.Integer()] = VideoPath::builtinTODO(entry.first);
+	{
+		if(mappingCampaignVideo[entry.second.Integer()].first.empty())
+			mappingCampaignVideo[entry.second.Integer()].first = VideoPath::builtinTODO(entry.first);
+		else
+			mappingCampaignVideo[entry.second.Integer()].second = VideoPath::builtinTODO(entry.first);
+	}
 
 	for (auto entry : mapping["campaignMusic"].Struct())
 		mappingCampaignMusic[entry.second.Integer()] = AudioPath::builtinTODO(entry.first);
@@ -233,7 +238,7 @@ CampaignRegionID MapIdentifiersH3M::remap(CampaignRegionID input) const
 	return mappingCampaignRegions.at(input);
 }
 
-VideoPath MapIdentifiersH3M::remapCampaignVideo(int input) const
+std::pair<VideoPath, VideoPath> MapIdentifiersH3M::remapCampaignVideo(int input) const
 {
 	if (!mappingCampaignVideo.count(input))
 		throw std::out_of_range("Campaign video with ID " + std::to_string(input) + " is not defined");

+ 2 - 2
lib/mapping/MapIdentifiersH3M.h

@@ -44,7 +44,7 @@ class DLL_LINKAGE MapIdentifiersH3M
 	std::map<ArtifactID, ArtifactID> mappingArtifact;
 	std::map<SecondarySkill, SecondarySkill> mappingSecondarySkill;
 	std::map<CampaignRegionID, CampaignRegionID> mappingCampaignRegions;
-	std::map<int, VideoPath> mappingCampaignVideo;
+	std::map<int, std::pair<VideoPath, VideoPath>> mappingCampaignVideo;
 	std::map<int, AudioPath> mappingCampaignMusic;
 
 	std::map<AnimationPath, AnimationPath> mappingObjectTemplate;
@@ -60,7 +60,7 @@ public:
 	void remapTemplate(ObjectTemplate & objectTemplate);
 
 	AudioPath remapCampaignMusic(int index) const;
-	VideoPath remapCampaignVideo(int index) const;
+	std::pair<VideoPath, VideoPath> remapCampaignVideo(int index) const;
 	BuildingID remapBuilding(std::optional<FactionID> owner, BuildingID input) const;
 	HeroTypeID remapPortrait(HeroTypeID input) const;
 	FactionID remap(FactionID input) const;

+ 2 - 1
lib/serializer/ESerializationVersion.h

@@ -52,8 +52,9 @@ enum class ESerializationVersion : int32_t
 	CONFIGURABLE_RESOURCES, // configurable resources
 	CUSTOM_NAMES, // custom names
 	BATTLE_ONLY, // battle only mode
+	CAMPAIGN_VIDEO, // second video for prolog/epilog in campaigns
 
-	CURRENT = BATTLE_ONLY,
+	CURRENT = CAMPAIGN_VIDEO,
 };
 
 static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!");

+ 8 - 4
mapeditor/campaigneditor/scenarioproperties.cpp

@@ -65,12 +65,14 @@ ScenarioProperties::ScenarioProperties(std::shared_ptr<CampaignState> campaignSt
 	}
 
 	ui->checkBoxPrologueEnabled->setChecked(campaignState->scenarios.at(scenario).prolog.hasPrologEpilog);
-	ui->lineEditPrologueVideo->setText(QString::fromStdString(campaignState->scenarios.at(scenario).prolog.prologVideo.getName()));
+	ui->lineEditPrologueVideo->setText(QString::fromStdString(campaignState->scenarios.at(scenario).prolog.prologVideo.first.getName()));
+	ui->lineEditPrologueVideoLoop->setText(QString::fromStdString(campaignState->scenarios.at(scenario).prolog.prologVideo.second.getName()));
 	ui->lineEditPrologueMusic->setText(QString::fromStdString(campaignState->scenarios.at(scenario).prolog.prologMusic.getName()));
 	ui->lineEditPrologueVoice->setText(QString::fromStdString(campaignState->scenarios.at(scenario).prolog.prologVoice.getName()));
 	ui->plainTextEditPrologueText->setPlainText(QString::fromStdString(campaignState->scenarios.at(scenario).prolog.prologText.toString()));
 	ui->checkBoxEpilogueEnabled->setChecked(campaignState->scenarios.at(scenario).epilog.hasPrologEpilog);
-	ui->lineEditEpilogueVideo->setText(QString::fromStdString(campaignState->scenarios.at(scenario).epilog.prologVideo.getName()));
+	ui->lineEditEpilogueVideo->setText(QString::fromStdString(campaignState->scenarios.at(scenario).epilog.prologVideo.first.getName()));
+	ui->lineEditEpilogueVideoLoop->setText(QString::fromStdString(campaignState->scenarios.at(scenario).epilog.prologVideo.second.getName()));
 	ui->lineEditEpilogueMusic->setText(QString::fromStdString(campaignState->scenarios.at(scenario).epilog.prologMusic.getName()));
 	ui->lineEditEpilogueVoice->setText(QString::fromStdString(campaignState->scenarios.at(scenario).epilog.prologVoice.getName()));
 	ui->plainTextEditEpilogueText->setPlainText(QString::fromStdString(campaignState->scenarios.at(scenario).epilog.prologText.toString()));
@@ -276,12 +278,14 @@ void ScenarioProperties::on_buttonBox_clicked(QAbstractButton * button)
 		campaignState->scenarios.at(scenario).difficulty = ui->comboBoxDefaultDifficulty->currentData().toInt();
 
 		campaignState->scenarios.at(scenario).prolog.hasPrologEpilog = ui->checkBoxPrologueEnabled->isChecked();
-		campaignState->scenarios.at(scenario).prolog.prologVideo = VideoPath::builtin(ui->lineEditPrologueVideo->text().toStdString());
+		campaignState->scenarios.at(scenario).prolog.prologVideo.first = VideoPath::builtin(ui->lineEditPrologueVideo->text().toStdString());
+		campaignState->scenarios.at(scenario).prolog.prologVideo.second = VideoPath::builtin(ui->lineEditPrologueVideoLoop->text().toStdString());
 		campaignState->scenarios.at(scenario).prolog.prologMusic = AudioPath::builtin(ui->lineEditPrologueMusic->text().toStdString());
 		campaignState->scenarios.at(scenario).prolog.prologVoice = AudioPath::builtin(ui->lineEditPrologueVoice->text().toStdString());
 		campaignState->scenarios.at(scenario).prolog.prologText = MetaString::createFromRawString(ui->plainTextEditPrologueText->toPlainText().toStdString());
 		campaignState->scenarios.at(scenario).epilog.hasPrologEpilog = ui->checkBoxEpilogueEnabled->isChecked();
-		campaignState->scenarios.at(scenario).epilog.prologVideo = VideoPath::builtin(ui->lineEditEpilogueVideo->text().toStdString());
+		campaignState->scenarios.at(scenario).epilog.prologVideo.first = VideoPath::builtin(ui->lineEditEpilogueVideo->text().toStdString());
+		campaignState->scenarios.at(scenario).epilog.prologVideo.second = VideoPath::builtin(ui->lineEditEpilogueVideoLoop->text().toStdString());
 		campaignState->scenarios.at(scenario).epilog.prologMusic = AudioPath::builtin(ui->lineEditEpilogueMusic->text().toStdString());
 		campaignState->scenarios.at(scenario).epilog.prologVoice = AudioPath::builtin(ui->lineEditEpilogueVoice->text().toStdString());
 		campaignState->scenarios.at(scenario).epilog.prologText = MetaString::createFromRawString(ui->plainTextEditEpilogueText->toPlainText().toStdString());

+ 34 - 0
mapeditor/campaigneditor/scenarioproperties.ui

@@ -174,6 +174,23 @@
             </item>
            </layout>
           </item>
+          <item>
+           <layout class="QHBoxLayout" name="horizontalLayout_3_">
+            <property name="topMargin">
+             <number>0</number>
+            </property>
+            <item>
+             <widget class="QLabel" name="labelPrologueVideoLoop">
+              <property name="text">
+               <string>Video 2</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLineEdit" name="lineEditPrologueVideoLoop"/>
+            </item>
+           </layout>
+          </item>
           <item>
            <layout class="QHBoxLayout" name="horizontalLayout_4">
             <property name="topMargin">
@@ -251,6 +268,23 @@
             </item>
            </layout>
           </item>
+          <item>
+           <layout class="QHBoxLayout" name="horizontalLayout_6_">
+            <property name="topMargin">
+             <number>0</number>
+            </property>
+            <item>
+             <widget class="QLabel" name="labelEpilogueVideoLoop">
+              <property name="text">
+               <string>Video 2</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLineEdit" name="lineEditEpilogueVideoLoop"/>
+            </item>
+           </layout>
+          </item>
           <item>
            <layout class="QHBoxLayout" name="horizontalLayout_8">
             <property name="topMargin">