1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-26 03:52:01 +02:00

h3c converter implementation

This commit is contained in:
Laserlicht 2024-12-24 22:46:44 +01:00
parent ec25eb557b
commit a345517776
9 changed files with 342 additions and 48 deletions

View File

@ -366,6 +366,15 @@ namespace vstd
return it->second;
}
// given a map from keys to values, creates a new map from values to keys
template<typename K, typename V>
static std::map<V, K> reverseMap(const std::map<K, V>& m) {
std::map<V, K> r;
for (const auto& kv : m)
r[kv.second] = kv.first;
return r;
}
//returns first key that maps to given value if present, returns success via found if provided
template <typename Key, typename T>
Key findKey(const std::map<Key, T> & map, const T & value, bool * found = nullptr)

View File

@ -50,12 +50,14 @@ void CampaignHandler::readCampaign(Campaign * ret, const std::vector<ui8> & inpu
{
JsonNode jsonCampaign(reinterpret_cast<const std::byte*>(input.data()), input.size(), filename);
readHeaderFromJson(*ret, jsonCampaign, filename, modName, encoding);
ret->overrideCampaign();
for(auto & scenario : jsonCampaign["scenarios"].Vector())
{
auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
ret->scenarios[scenarioID] = readScenarioFromJson(scenario);
}
ret->overrideCampaignScenarios();
}
}
@ -171,6 +173,26 @@ void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader
ret.outroVideo = VideoPath::fromJson(reader["outroVideo"]);
}
JsonNode CampaignHandler::writeHeaderToJson(CampaignHeader & header)
{
JsonNode node;
node["version"].Integer() = static_cast<ui64>(CampaignVersion::VCMI);
node["regions"] = CampaignRegions::toJson(header.campaignRegions);
node["name"].String() = header.name.toString();
node["description"].String() = header.description.toString();
node["author"].String() = header.author.toString();
node["authorContact"].String() = header.authorContact.toString();
node["campaignVersion"].String() = header.campaignVersion.toString();
node["creationDateTime"].Integer() = header.creationDateTime;
node["allowDifficultySelection"].Bool() = header.difficultyChosenByPlayer;
node["music"].String() = header.music.getName();
node["loadingBackground"].String() = header.loadingBackground.getName();
node["videoRim"].String() = header.videoRim.getName();
node["introVideo"].String() = header.introVideo.getName();
node["outroVideo"].String() = header.outroVideo.getName();
return node;
}
CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
{
auto prologEpilogReader = [](JsonNode & identifier) -> CampaignScenarioPrologEpilog
@ -203,56 +225,86 @@ CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
return ret;
}
JsonNode CampaignHandler::writeScenarioToJson(const CampaignScenario & scenario)
{
auto prologEpilogWriter = [](const CampaignScenarioPrologEpilog & elem) -> JsonNode
{
JsonNode node;
if(elem.hasPrologEpilog)
{
node["video"].String() = elem.prologVideo.getName();
node["music"].String() = elem.prologMusic.getName();
node["voice"].String() = elem.prologVoice.getName();
node["text"].String() = elem.prologText.toString();
}
return node;
};
JsonNode node;
node["map"].String() = scenario.mapName;
for(auto & g : scenario.preconditionRegions)
node["preconditions"].Vector().push_back(JsonNode(static_cast<ui32>(g)));
node["color"].Integer() = scenario.regionColor;
node["difficulty"].Integer() = scenario.difficulty;
node["regionText"].String() = scenario.regionText.toString();
node["prolog"] = prologEpilogWriter(scenario.prolog);
node["epilog"] = prologEpilogWriter(scenario.epilog);
writeScenarioTravelToJson(node, scenario.travelOptions);
return node;
}
std::map<std::string, CampaignStartOptions> startOptionsMap = {
{"none", CampaignStartOptions::NONE},
{"bonus", CampaignStartOptions::START_BONUS},
{"crossover", CampaignStartOptions::HERO_CROSSOVER},
{"hero", CampaignStartOptions::HERO_OPTIONS}
};
std::map<std::string, CampaignBonusType> bonusTypeMap = {
{"spell", CampaignBonusType::SPELL},
{"creature", CampaignBonusType::MONSTER},
{"building", CampaignBonusType::BUILDING},
{"artifact", CampaignBonusType::ARTIFACT},
{"scroll", CampaignBonusType::SPELL_SCROLL},
{"primarySkill", CampaignBonusType::PRIMARY_SKILL},
{"secondarySkill", CampaignBonusType::SECONDARY_SKILL},
{"resource", CampaignBonusType::RESOURCE},
//{"prevHero", CScenarioTravel::STravelBonus::EBonusType::HEROES_FROM_PREVIOUS_SCENARIO},
//{"hero", CScenarioTravel::STravelBonus::EBonusType::HERO},
};
std::map<std::string, ui32> primarySkillsMap = {
{"attack", 0},
{"defence", 8},
{"spellpower", 16},
{"knowledge", 24},
};
std::map<std::string, ui16> heroSpecialMap = {
{"strongest", 0xFFFD},
{"generated", 0xFFFE},
{"random", 0xFFFF}
};
std::map<std::string, ui8> resourceTypeMap = {
//FD - wood+ore
//FE - mercury+sulfur+crystal+gem
{"wood", 0},
{"mercury", 1},
{"ore", 2},
{"sulfur", 3},
{"crystal", 4},
{"gems", 5},
{"gold", 6},
{"common", 0xFD},
{"rare", 0xFE}
};
CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
{
CampaignTravel ret;
std::map<std::string, CampaignStartOptions> startOptionsMap = {
{"none", CampaignStartOptions::NONE},
{"bonus", CampaignStartOptions::START_BONUS},
{"crossover", CampaignStartOptions::HERO_CROSSOVER},
{"hero", CampaignStartOptions::HERO_OPTIONS}
};
std::map<std::string, CampaignBonusType> bonusTypeMap = {
{"spell", CampaignBonusType::SPELL},
{"creature", CampaignBonusType::MONSTER},
{"building", CampaignBonusType::BUILDING},
{"artifact", CampaignBonusType::ARTIFACT},
{"scroll", CampaignBonusType::SPELL_SCROLL},
{"primarySkill", CampaignBonusType::PRIMARY_SKILL},
{"secondarySkill", CampaignBonusType::SECONDARY_SKILL},
{"resource", CampaignBonusType::RESOURCE},
//{"prevHero", CScenarioTravel::STravelBonus::EBonusType::HEROES_FROM_PREVIOUS_SCENARIO},
//{"hero", CScenarioTravel::STravelBonus::EBonusType::HERO},
};
std::map<std::string, ui32> primarySkillsMap = {
{"attack", 0},
{"defence", 8},
{"spellpower", 16},
{"knowledge", 24},
};
std::map<std::string, ui16> heroSpecialMap = {
{"strongest", 0xFFFD},
{"generated", 0xFFFE},
{"random", 0xFFFF}
};
std::map<std::string, ui8> resourceTypeMap = {
//FD - wood+ore
//FE - mercury+sulfur+crystal+gem
{"wood", 0},
{"mercury", 1},
{"ore", 2},
{"sulfur", 3},
{"crystal", 4},
{"gems", 5},
{"gold", 6},
{"common", 0xFD},
{"rare", 0xFE}
};
for(auto & k : reader["heroKeeps"].Vector())
{
@ -390,6 +442,109 @@ CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
return ret;
}
void CampaignHandler::writeScenarioTravelToJson(JsonNode & node, const CampaignTravel & travel)
{
if(travel.whatHeroKeeps.experience)
node["heroKeeps"].Vector().push_back(JsonNode("experience"));
if(travel.whatHeroKeeps.primarySkills)
node["heroKeeps"].Vector().push_back(JsonNode("primarySkills"));
if(travel.whatHeroKeeps.secondarySkills)
node["heroKeeps"].Vector().push_back(JsonNode("secondarySkills"));
if(travel.whatHeroKeeps.spells)
node["heroKeeps"].Vector().push_back(JsonNode("spells"));
if(travel.whatHeroKeeps.artifacts)
node["heroKeeps"].Vector().push_back(JsonNode("artifacts"));
for(auto & c : travel.monstersKeptByHero)
node["keepCreatures"].Vector().push_back(JsonNode(CreatureID::encode(c)));
for(auto & a : travel.artifactsKeptByHero)
node["keepArtifacts"].Vector().push_back(JsonNode(ArtifactID::encode(a)));
node["startOptions"].String() = vstd::reverseMap(startOptionsMap)[travel.startOptions];
switch(travel.startOptions)
{
case CampaignStartOptions::NONE:
break;
case CampaignStartOptions::START_BONUS:
{
node["playerColor"].String() = PlayerColor::encode(travel.playerColor);
for(auto & bonus : travel.bonusesToChoose)
{
JsonNode bnode;
bnode["what"].String() = vstd::reverseMap(bonusTypeMap)[bonus.type];
switch (bonus.type)
{
case CampaignBonusType::RESOURCE:
bnode["type"].String() = vstd::reverseMap(resourceTypeMap)[bonus.info1];
bnode["amount"].Integer() = bonus.info2;
break;
case CampaignBonusType::BUILDING:
bnode["type"].String() = EBuildingType::names[bonus.info1];
break;
default:
if(vstd::contains(vstd::reverseMap(heroSpecialMap), bonus.info1))
bnode["hero"].String() = vstd::reverseMap(heroSpecialMap)[bonus.info1];
else
bnode["hero"].String() = HeroTypeID::encode(bonus.info1);
bnode["amount"].Integer() = bonus.info3;
switch(bonus.type)
{
case CampaignBonusType::SPELL:
bnode["type"].String() = SpellID::encode(bonus.info2);
break;
case CampaignBonusType::MONSTER:
bnode["type"].String() = CreatureID::encode(bonus.info2);
break;
case CampaignBonusType::SECONDARY_SKILL:
bnode["type"].String() = SecondarySkill::encode(bonus.info2);
break;
case CampaignBonusType::ARTIFACT:
bnode["type"].String() = ArtifactID::encode(bonus.info2);
break;
case CampaignBonusType::SPELL_SCROLL:
bnode["type"].String() = SpellID::encode(bonus.info2);
break;
case CampaignBonusType::PRIMARY_SKILL:
for(auto & ps : primarySkillsMap)
bnode[ps.first].Integer() = (bonus.info2 >> ps.second) & 0xff;
break;
default:
bnode["type"].Integer() = bonus.info2;
}
break;
}
node["bonuses"].Vector().push_back(bnode);
}
break;
}
case CampaignStartOptions::HERO_CROSSOVER:
{
for(auto & bonus : travel.bonusesToChoose)
{
JsonNode bnode;
bnode["playerColor"].Integer() = bonus.info1;
bnode["scenario"].Integer() = bonus.info2;
node["bonuses"].Vector().push_back(bnode);
}
break;
}
case CampaignStartOptions::HERO_OPTIONS:
{
for(auto & bonus : travel.bonusesToChoose)
{
JsonNode bnode;
bnode["playerColor"].Integer() = bonus.info1;
if(vstd::contains(vstd::reverseMap(heroSpecialMap), bonus.info2))
bnode["hero"].String() = vstd::reverseMap(heroSpecialMap)[bonus.info2];
else
bnode["hero"].String() = HeroTypeID::encode(bonus.info2);
node["bonuses"].Vector().push_back(bnode);
}
break;
}
}
}
void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding )
{

View File

@ -26,6 +26,9 @@ class DLL_LINKAGE CampaignHandler
static CampaignScenario readScenarioFromJson(JsonNode & reader);
static CampaignTravel readScenarioTravelFromJson(JsonNode & reader);
//writer for VCMI campaigns (*.vcmp)
static void writeScenarioTravelToJson(JsonNode & node, const CampaignTravel & travel);
//parsers for original H3C campaigns
static void readHeaderFromMemory(CampaignHeader & target, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding);
static CampaignScenario readScenarioFromMemory(CBinaryReader & reader, CampaignHeader & header);
@ -43,6 +46,10 @@ public:
static std::unique_ptr<Campaign> getHeader( const std::string & name); //name - name of appropriate file
static std::shared_ptr<CampaignState> getCampaign(const std::string & name); //name - name of appropriate file
//writer for VCMI campaigns (*.vcmp)
static JsonNode writeHeaderToJson(CampaignHeader & header);
static JsonNode writeScenarioToJson(const CampaignScenario & scenario);
};
VCMI_LIB_NAMESPACE_END

View File

@ -45,6 +45,22 @@ CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(
return rd;
}
JsonNode CampaignRegions::RegionDescription::toJson(CampaignRegions::RegionDescription & rd)
{
JsonNode node;
node["infix"].String() = rd.infix;
node["x"].Float() = rd.pos.x;
node["y"].Float() = rd.pos.y;
if(rd.labelPos != std::nullopt)
{
node["labelPos"]["x"].Float() = (*rd.labelPos).x;
node["labelPos"]["y"].Float() = (*rd.labelPos).y;
}
else
node["labelPos"].clear();
return node;
}
CampaignRegions CampaignRegions::fromJson(const JsonNode & node)
{
CampaignRegions cr;
@ -59,6 +75,25 @@ CampaignRegions CampaignRegions::fromJson(const JsonNode & node)
return cr;
}
JsonNode CampaignRegions::toJson(CampaignRegions cr)
{
JsonNode node;
node["prefix"].String() = cr.campPrefix;
node["colorSuffixLength"].Float() = cr.colorSuffixLength;
if(!cr.campSuffix.size())
node["suffix"].clear();
else
node["suffix"].Vector() = JsonVector{ JsonNode(cr.campSuffix[0]), JsonNode(cr.campSuffix[1]), JsonNode(cr.campSuffix[2]) };
if(cr.campBackground.empty())
node["background"].clear();
else
node["background"].String() = cr.campBackground;
node["desc"].Vector() = JsonVector();
for(auto & region : cr.regions)
node["desc"].Vector().push_back(CampaignRegions::RegionDescription::toJson(region));
return node;
}
CampaignRegions CampaignRegions::getLegacy(int campId)
{
static std::vector<CampaignRegions> campDescriptions;

View File

@ -59,6 +59,7 @@ class DLL_LINKAGE CampaignRegions
}
static CampaignRegions::RegionDescription fromJson(const JsonNode & node);
static JsonNode toJson(CampaignRegions::RegionDescription & rd);
};
std::vector<RegionDescription> regions;
@ -86,6 +87,7 @@ public:
}
static CampaignRegions fromJson(const JsonNode & node);
static JsonNode toJson(CampaignRegions cr);
static CampaignRegions getLegacy(int campId);
};

View File

@ -144,7 +144,7 @@ TModID CModHandler::findResourceOrigin(const ResourcePath & name) const
return "core";
if(CResourceHandler::get("mapEditor")->existsResource(name))
return "core"; // Workaround for loading maps via map editor
return "mapEditor"; // Workaround for loading maps via map editor
}
catch( const std::out_of_range & e)
{
@ -189,6 +189,8 @@ std::string CModHandler::getModLanguage(const TModID& modId) const
return VLC->generaltexth->getInstalledLanguage();
if(modId == "map")
return VLC->generaltexth->getPreferredLanguage();
if(modId == "mapEditor")
return VLC->generaltexth->getPreferredLanguage();
return getModInfo(modId).getBaseLanguage();
}

View File

@ -24,7 +24,9 @@
#include "../lib/logging/CBasicLogConfigurator.h"
#include "../lib/CConfigHandler.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/CMemoryBuffer.h"
#include "../lib/GameConstants.h"
#include "../lib/campaign/CampaignHandler.h"
#include "../lib/mapObjectConstructors/AObjectTypeHandler.h"
#include "../lib/mapObjectConstructors/CObjectClassesHandler.h"
#include "../lib/mapObjects/ObjectTemplate.h"
@ -32,6 +34,7 @@
#include "../lib/mapping/CMap.h"
#include "../lib/mapping/CMapEditManager.h"
#include "../lib/mapping/MapFormat.h"
#include "../lib/mapping/MapFormatJson.h"
#include "../lib/modding/ModIncompatibility.h"
#include "../lib/RoadHandler.h"
#include "../lib/RiverHandler.h"
@ -398,6 +401,27 @@ std::unique_ptr<CMap> MainWindow::openMapInternal(const QString & filenameSelect
throw std::runtime_error("Corrupted map");
}
std::shared_ptr<CampaignState> MainWindow::openCampaignInternal(const QString & filenameSelect)
{
QFileInfo fi(filenameSelect);
std::string fname = fi.fileName().toStdString();
std::string fdir = fi.dir().path().toStdString();
ResourcePath resId("MAPEDITOR/" + fname, EResType::CAMPAIGN);
//addFilesystem takes care about memory deallocation if case of failure, no memory leak here
auto * mapEditorFilesystem = new CFilesystemLoader("MAPEDITOR/", fdir, 0);
CResourceHandler::removeFilesystem("local", "mapEditor");
CResourceHandler::addFilesystem("local", "mapEditor", mapEditorFilesystem);
if(!CResourceHandler::get("mapEditor")->existsResource(resId))
throw std::runtime_error("Cannot open campaign from this folder");
if(auto campaign = CampaignHandler::getCampaign(resId.getName()))
return campaign;
else
throw std::runtime_error("Corrupted campaign");
}
bool MainWindow::openMap(const QString & filenameSelect)
{
try
@ -1373,6 +1397,53 @@ void MainWindow::on_actionh3m_converter_triggered()
}
}
void MainWindow::on_actionh3c_converter_triggered()
{
auto campaignFile = QFileDialog::getOpenFileName(this, tr("Select campaign to convert"),
QString::fromStdString(VCMIDirs::get().userDataPath().make_preferred().string()),
tr("HoMM3 campaigns (*.h3c)"));
if(campaignFile.isEmpty())
return;
auto campaignFileDest = QFileDialog::getSaveFileName(this, tr("Select destination file"),
QString::fromStdString(VCMIDirs::get().userDataPath().make_preferred().string()),
tr("VCMI campaigns (*.vcmp)"));
if(campaignFileDest.isEmpty())
return;
QFileInfo fileInfo(campaignFileDest);
if(fileInfo.suffix().toLower() != "vcmp")
campaignFileDest += ".vcmp";
auto campaign = openCampaignInternal(campaignFile);
auto jsonCampaign = CampaignHandler::writeHeaderToJson(*campaign);
std::shared_ptr<CIOApi> io(new CDefaultIOApi());
auto saver = std::make_shared<CZipSaver>(io, campaignFileDest.toStdString());
for(auto & scenario : campaign->allScenarios())
{
CMapService mapService;
auto map = campaign->getMap(scenario, nullptr);
controller.repairMap(map.get());
CMemoryBuffer serializeBuffer;
{
CMapSaverJson jsonSaver(&serializeBuffer);
jsonSaver.saveMap(map);
}
auto mapName = boost::algorithm::to_lower_copy(campaign->scenario(scenario).mapName);
mapName = boost::replace_all_copy(mapName, ".h3m", std::string("")) + ".vmap";
auto stream = saver->addFile(mapName);
stream->write(reinterpret_cast<const ui8 *>(serializeBuffer.getBuffer().data()), serializeBuffer.getSize());
jsonCampaign["scenarios"].Vector().push_back(CampaignHandler::writeScenarioToJson(campaign->scenario(scenario)));
jsonCampaign["scenarios"].Vector().back()["map"].String() = mapName;
}
auto jsonCampaignStr = jsonCampaign.toString();
saver->addFile("header.json")->write(reinterpret_cast<const ui8 *>(jsonCampaignStr.data()), jsonCampaignStr.length());
}
void MainWindow::on_actionLock_triggered()
{

View File

@ -11,6 +11,7 @@ class ObjectBrowserProxyModel;
VCMI_LIB_NAMESPACE_BEGIN
class CMap;
class CampaignState;
class CGObjectInstance;
VCMI_LIB_NAMESPACE_END
@ -35,6 +36,7 @@ class MainWindow : public QMainWindow
#endif
std::unique_ptr<CMap> openMapInternal(const QString &);
std::shared_ptr<CampaignState> openCampaignInternal(const QString &);
public:
explicit MainWindow(QWidget *parent = nullptr);
@ -118,6 +120,8 @@ private slots:
void on_actionh3m_converter_triggered();
void on_actionh3c_converter_triggered();
void on_actionLock_triggered();
void on_actionUnlock_triggered();

View File

@ -71,6 +71,7 @@
<addaction name="actionSave_as"/>
<addaction name="actionExport"/>
<addaction name="actionh3m_converter"/>
<addaction name="actionh3c_converter"/>
</widget>
<widget class="QMenu" name="menuMap">
<property name="title">
@ -1352,6 +1353,14 @@
<string>h3m converter</string>
</property>
</action>
<action name="actionh3c_converter">
<property name="text">
<string>h3c converter</string>
</property>
<property name="toolTip">
<string>h3c converter</string>
</property>
</action>
<action name="actionLock">
<property name="text">
<string>Lock</string>