1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-09-16 09:26:28 +02:00

Support for defining new campaign regions in mods, for hota h3c

This commit is contained in:
Ivan Savenko
2025-05-30 18:27:54 +03:00
parent 1ea2ce7959
commit a842dfb3c4
27 changed files with 677 additions and 477 deletions

238
config/campaignRegions.json Normal file
View File

@@ -0,0 +1,238 @@
{
// RoE
"good1" : {
"prefix": "G1",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 57, "y": 314 },
{ "infix": "B", "x": 137, "y": 309 },
{ "infix": "C", "x": 44, "y": 163 }
]
},
"good2" : {
"prefix": "G2",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 56, "y": 90 },
{ "infix": "B", "x": 316, "y": 49 },
{ "infix": "C", "x": 54, "y": 378 },
{ "infix": "D", "x": 151, "y": 126 }
]
},
"good3" : {
"prefix": "G3",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 289, "y": 376 },
{ "infix": "B", "x": 60, "y": 147 },
{ "infix": "C", "x": 131, "y": 202 }
]
},
"evil1" : {
"prefix": "E1",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 270, "y": 332 },
{ "infix": "B", "x": 138, "y": 113 },
{ "infix": "C", "x": 26, "y": 70 },
{ "infix": "P1", "x": 256, "y": 127 },
{ "infix": "P2", "x": 57, "y": 314 },
{ "infix": "P3", "x": 137, "y": 310 },
{ "infix": "P4", "x": 44, "y": 163 }
]
},
"evil2" : {
"prefix": "E2",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 131, "y": 202 },
{ "infix": "B", "x": 60, "y": 145 },
{ "infix": "C", "x": 92, "y": 261 },
{ "infix": "D", "x": 218, "y": 307 }
]
},
"neutral1" : {
"prefix": "N1",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 42, "y": 94 },
{ "infix": "B", "x": 309, "y": 290 },
{ "infix": "CD", "x": 188, "y": 202 }
]
},
"secret1" : {
"prefix": "S1",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 263, "y": 199 },
{ "infix": "B", "x": 182, "y": 210 },
{ "infix": "C", "x": 82, "y": 152 }
]
},
// AB
"dragonSlayer" : {
"prefix": "BR",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 18, "y": 233 },
{ "infix": "B", "x": 125, "y": 381 },
{ "infix": "C", "x": 224, "y": 357 },
{ "infix": "D", "x": 192, "y": 320 }
]
},
"foolhardyWaywardness" : {
"prefix": "IS",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 294, "y": 399 },
{ "infix": "B", "x": 183, "y": 293 },
{ "infix": "C", "x": 40, "y": 92 },
{ "infix": "D", "x": 294, "y": 398 }
]
},
"festivalOfLife" : {
"prefix": "KR",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 148, "y": 323 },
{ "infix": "B", "x": 192, "y": 235 },
{ "infix": "C", "x": 136, "y": 158 },
{ "infix": "D", "x": 87, "y": 107 }
]
},
"dragonsBlood" : {
"prefix": "NI",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 118, "y": 111 },
{ "infix": "B", "x": 223, "y": 145 },
{ "infix": "C", "x": 320, "y": 213 },
{ "infix": "D", "x": 233, "y": 250 }
]
},
"playingWithFire" : {
"prefix": "TA",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 228, "y": 233 },
{ "infix": "B", "x": 147, "y": 194 },
{ "infix": "C", "x": 112, "y": 97 }
]
},
"armageddonsBlade" : {
"prefix": "AR",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 135, "y": 238 },
{ "infix": "B", "x": 135, "y": 121 },
{ "infix": "C", "x": 206, "y": 155 },
{ "infix": "D", "x": 105, "y": 397 },
{ "infix": "E", "x": 109, "y": 275 },
{ "infix": "F", "x": 158, "y": 188 },
{ "infix": "G", "x": 200, "y": 261 },
{ "infix": "H", "x": 232, "y": 197 }
]
},
// SoD
"hackAndSlash" : {
"prefix": "HS",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 141, "y": 326 },
{ "infix": "B", "x": 238, "y": 275 },
{ "infix": "C", "x": 22, "y": 161 },
{ "infix": "D", "x": 5, "y": 9 }
]
},
"birthOfBarbarian" : {
"prefix": "BB",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 167, "y": 342 },
{ "infix": "B", "x": 217, "y": 263 },
{ "infix": "C", "x": 0, "y": 71 },
{ "infix": "D", "x": 291, "y": 79 },
{ "infix": "E", "x": 316, "y": 199 }
]
},
"newBeginning" : {
"prefix": "NB",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 6, "y": 292 },
{ "infix": "B", "x": 161, "y": 334 },
{ "infix": "C", "x": 63, "y": 195 },
{ "infix": "D", "x": 56, "y": 46 }
]
},
"elixirOfLife" : {
"prefix": "EL",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 11, "y": 73 },
{ "infix": "B", "x": 0, "y": 241 },
{ "infix": "C", "x": 254, "y": 34 },
{ "infix": "D", "x": 91, "y": 144 }
]
},
"riseOfTheNecromancer" : {
"prefix": "RN",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 84, "y": 319 },
{ "infix": "B", "x": 194, "y": 275 },
{ "infix": "C", "x": 67, "y": 185 },
{ "infix": "D", "x": 77, "y": 30 }
]
},
"unholyAlliance" : {
"prefix": "UA",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 157, "y": 409 },
{ "infix": "B", "x": 62, "y": 346 },
{ "infix": "C", "x": 8, "y": 8 },
{ "infix": "D", "x": 206, "y": 1 },
{ "infix": "E", "x": 132, "y": 357 },
{ "infix": "F", "x": 184, "y": 83 },
{ "infix": "G", "x": 159, "y": 263 },
{ "infix": "H", "x": 108, "y": 173 },
{ "infix": "I", "x": 55, "y": 127 },
{ "infix": "J", "x": 9, "y": 252 },
{ "infix": "K", "x": 210, "y": 176 },
{ "infix": "L", "x": 260, "y": 210 }
]
},
"spectreOfPower" : {
"prefix": "SP",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 7, "y": 295 },
{ "infix": "B", "x": 44, "y": 141 },
{ "infix": "C", "x": 141, "y": 21 },
{ "infix": "D", "x": 243, "y": 156 }
]
}
}

View File

@@ -1,234 +0,0 @@
{
"campaign_regions": [
{
"prefix": "G1",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 57, "y": 314 },
{ "infix": "B", "x": 137, "y": 309 },
{ "infix": "C", "x": 44, "y": 163 }
]
},
{
"prefix": "G2",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 56, "y": 90 },
{ "infix": "B", "x": 316, "y": 49 },
{ "infix": "C", "x": 54, "y": 378 },
{ "infix": "D", "x": 151, "y": 126 }
]
},
{
"prefix": "G3",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 289, "y": 376 },
{ "infix": "B", "x": 60, "y": 147 },
{ "infix": "C", "x": 131, "y": 202 }
]
},
{
"prefix": "E1",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 270, "y": 332 },
{ "infix": "B", "x": 138, "y": 113 },
{ "infix": "C", "x": 26, "y": 70 },
{ "infix": "P1", "x": 256, "y": 127 },
{ "infix": "P2", "x": 57, "y": 314 },
{ "infix": "P3", "x": 137, "y": 310 },
{ "infix": "P4", "x": 44, "y": 163 }
]
},
{
"prefix": "E2",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 131, "y": 202 },
{ "infix": "B", "x": 60, "y": 145 },
{ "infix": "C", "x": 92, "y": 261 },
{ "infix": "D", "x": 218, "y": 307 }
]
},
{
"prefix": "N1",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 42, "y": 94 },
{ "infix": "B", "x": 309, "y": 290 },
{ "infix": "CD", "x": 188, "y": 202 }
]
},
{
"prefix": "S1",
"colorSuffixLength": 1,
"desc": [
{ "infix": "A", "x": 263, "y": 199 },
{ "infix": "B", "x": 182, "y": 210 },
{ "infix": "C", "x": 82, "y": 152 }
]
},
{
"prefix": "BR",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 18, "y": 233 },
{ "infix": "B", "x": 125, "y": 381 },
{ "infix": "C", "x": 224, "y": 357 },
{ "infix": "D", "x": 192, "y": 320 }
]
},
{
"prefix": "IS",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 294, "y": 399 },
{ "infix": "B", "x": 183, "y": 293 },
{ "infix": "C", "x": 40, "y": 92 },
{ "infix": "D", "x": 294, "y": 398 }
]
},
{
"prefix": "KR",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 148, "y": 323 },
{ "infix": "B", "x": 192, "y": 235 },
{ "infix": "C", "x": 136, "y": 158 },
{ "infix": "D", "x": 87, "y": 107 }
]
},
{
"prefix": "NI",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 118, "y": 111 },
{ "infix": "B", "x": 223, "y": 145 },
{ "infix": "C", "x": 320, "y": 213 },
{ "infix": "D", "x": 233, "y": 250 }
]
},
{
"prefix": "TA",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 228, "y": 233 },
{ "infix": "B", "x": 147, "y": 194 },
{ "infix": "C", "x": 112, "y": 97 }
]
},
{
"prefix": "AR",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 135, "y": 238 },
{ "infix": "B", "x": 135, "y": 121 },
{ "infix": "C", "x": 206, "y": 155 },
{ "infix": "D", "x": 105, "y": 397 },
{ "infix": "E", "x": 109, "y": 275 },
{ "infix": "F", "x": 158, "y": 188 },
{ "infix": "G", "x": 200, "y": 261 },
{ "infix": "H", "x": 232, "y": 197 }
]
},
{
"prefix": "HS",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 141, "y": 326 },
{ "infix": "B", "x": 238, "y": 275 },
{ "infix": "C", "x": 22, "y": 161 },
{ "infix": "D", "x": 5, "y": 9 }
]
},
{
"prefix": "BB",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 167, "y": 342 },
{ "infix": "B", "x": 217, "y": 263 },
{ "infix": "C", "x": 0, "y": 71 },
{ "infix": "D", "x": 291, "y": 79 },
{ "infix": "E", "x": 316, "y": 199 }
]
},
{
"prefix": "NB",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 6, "y": 292 },
{ "infix": "B", "x": 161, "y": 334 },
{ "infix": "C", "x": 63, "y": 195 },
{ "infix": "D", "x": 56, "y": 46 }
]
},
{
"prefix": "EL",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 11, "y": 73 },
{ "infix": "B", "x": 0, "y": 241 },
{ "infix": "C", "x": 254, "y": 34 },
{ "infix": "D", "x": 91, "y": 144 }
]
},
{
"prefix": "RN",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 84, "y": 319 },
{ "infix": "B", "x": 194, "y": 275 },
{ "infix": "C", "x": 67, "y": 185 },
{ "infix": "D", "x": 77, "y": 30 }
]
},
{
"prefix": "UA",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 157, "y": 409 },
{ "infix": "B", "x": 62, "y": 346 },
{ "infix": "C", "x": 8, "y": 8 },
{ "infix": "D", "x": 206, "y": 1 },
{ "infix": "E", "x": 132, "y": 357 },
{ "infix": "F", "x": 184, "y": 83 },
{ "infix": "G", "x": 159, "y": 263 },
{ "infix": "H", "x": 108, "y": 173 },
{ "infix": "I", "x": 55, "y": 127 },
{ "infix": "J", "x": 9, "y": 252 },
{ "infix": "K", "x": 210, "y": 176 },
{ "infix": "L", "x": 260, "y": 210 }
]
},
{
"prefix": "SP",
"colorSuffixLength": 2,
"desc": [
{ "infix": "A", "x": 7, "y": 295 },
{ "infix": "B", "x": 44, "y": 141 },
{ "infix": "C", "x": 141, "y": 21 },
{ "infix": "D", "x": 243, "y": 156 }
]
}
]
}

View File

@@ -127,6 +127,11 @@
[
"config/obstacles.json"
],
"campaignRegions" :
[
"config/campaignRegions.json"
],
"settings":
{
@@ -244,6 +249,16 @@
"portraits" : {
"catherine" : 128, // In "RoE" Catherine only has portrait
"portraitGeneralKendal" : 129
},
"campaignRegions" : {
"good1" : 1, // Long Live the Queen
"good2" : 2, // Liberation
"good3" : 3, // Song for the Father
"evil1" : 4, // Dungeons and devils
"evil2" : 5, // Long Live the King
"neutral1" : 6, // Spoils of War
"secret1" : 7 // Seeds Of Discontent
}
},
"armageddonsBlade" : {
@@ -262,6 +277,14 @@
"portraitGeneralKendal" : 156,
"portraitYoungCristian" : 157,
"portraitOrdwald" : 158
},
"campaignRegions" : {
"dragonSlayer" : 8,
"foolhardyWaywardness" : 9,
"festivalOfLife" : 10,
"dragonsBlood" : 11,
"playingWithFire" : 12,
"armageddonsBlade" : 13
}
},
"shadowOfDeath" : {
@@ -276,6 +299,16 @@
"portraitYoungGem" : 160,
"portraitYoungSandro" : 161,
"portraitYoungYog" : 162
},
"campaignRegions" : {
"hackAndSlash" : 14,
"birthOfBarbarian" : 15,
"newBeginning" : 16,
"elixirOfLife" : 17,
"riseOfTheNecromancer" : 18,
"unholyAlliance" : 19,
"spectreOfPower" : 20
}
},
"chronicles" : {

View File

@@ -0,0 +1,40 @@
{
"type" : "object",
"$schema" : "http://json-schema.org/draft-04/schema",
"title" : "VCMI campaign region format",
"description" : "Format used to define campaign regions for h3c maps in VCMI",
"required" : [ "prefix", "colorSuffixLength", "desc" ],
"additionalProperties" : false,
"properties" : {
"prefix" :
{
"type" : "string",
"description" : "Prefix for all images from this region"
},
"colorSuffixLength" :
{
"type" : "number",
"description" : "Number of symbols used to encode color, 1 or 2"
},
"desc" :
{
"type" : "array",
"description" : "List of regions in this campaign",
"items" : {
"type" : "object",
"additionalProperties" : false,
"properties" : {
"infix" : {
"type" : "string"
},
"x" : {
"type" : "number"
},
"y" : {
"type" : "number"
}
}
}
}
}
}

View File

@@ -89,6 +89,8 @@ set(lib_MAIN_SRCS
campaign/CampaignBonus.cpp
campaign/CampaignHandler.cpp
campaign/CampaignState.cpp
campaign/CampaignRegions.cpp
campaign/CampaignRegionsHandler.cpp
constants/EntityIdentifiers.cpp
@@ -485,6 +487,8 @@ set(lib_MAIN_HEADERS
campaign/CampaignBonus.h
campaign/CampaignConstants.h
campaign/CampaignHandler.h
campaign/CampaignRegions.h
campaign/CampaignRegionsHandler.h
campaign/CampaignScenarioPrologEpilog.h
campaign/CampaignState.h

View File

@@ -26,6 +26,7 @@
#include "entities/hero/CHeroClassHandler.h"
#include "entities/hero/CHeroHandler.h"
#include "texts/CGeneralTextHandler.h"
#include "campaign/CampaignRegionsHandler.h"
#include "mapping/MapFormatSettings.h"
#include "modding/CModHandler.h"
#include "modding/IdentifierStorage.h"
@@ -184,6 +185,7 @@ void GameLibrary::initializeLibrary()
createHandler(spellh);
createHandler(skillh);
createHandler(terviewh);
createHandler(campaignRegions);
createHandler(tplh); //templates need already resolved identifiers (refactor?)
#if SCRIPTING_ENABLED
createHandler(scriptHandler);

View File

@@ -42,6 +42,7 @@ class GameSettings;
class CIdentifierStorage;
class SpellSchoolHandler;
class MapFormatSettings;
class CampaignRegionsHandler;
#if SCRIPTING_ENABLED
namespace scripting
@@ -99,6 +100,7 @@ public:
std::unique_ptr<GameSettings> settingsHandler;
std::unique_ptr<ObstacleSetHandler> biomeHandler;
std::unique_ptr<MapFormatSettings> mapFormat;
std::unique_ptr<CampaignRegionsHandler> campaignRegions;
#if SCRIPTING_ENABLED
std::unique_ptr<scripting::ScriptHandler> scriptHandler;

View File

@@ -15,7 +15,7 @@ class JsonNode;
class Entity;
/// base class for all handlers that can be accessed from mod system
class DLL_LINKAGE IHandlerBase
class DLL_LINKAGE IHandlerBase : boost::noncopyable
{
protected:
static std::string getScopeBuiltin();
@@ -44,7 +44,8 @@ public:
virtual ~IHandlerBase() = default;
};
template <class _ObjectID, class _ObjectBase, class _Object, class _ServiceBase> class CHandlerBase : public _ServiceBase, public IHandlerBase
template <class _ObjectID, class _ObjectBase, class _Object, class _ServiceBase>
class CHandlerBase : public _ServiceBase, public IHandlerBase
{
const _Object * getObjectImpl(const int32_t index) const
{

View File

@@ -11,6 +11,7 @@
#include "CampaignHandler.h"
#include "CampaignState.h"
#include "CampaignRegionsHandler.h"
#include "../filesystem/Filesystem.h"
#include "../filesystem/CCompressedStream.h"
@@ -152,7 +153,7 @@ void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader
}
ret.version = CampaignVersion::VCMI;
ret.campaignRegions = CampaignRegions::fromJson(reader["regions"]);
ret.campaignRegions = CampaignRegions(reader["regions"]);
ret.numberOfScenarios = reader["scenarios"].Vector().size();
ret.name.appendTextID(readLocalizedString(ret, reader["name"].String(), filename, modName, "name"));
ret.description.appendTextID(readLocalizedString(ret, reader["description"].String(), filename, modName, "description"));
@@ -350,14 +351,14 @@ void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader
assert(unknownB == 1);
assert(unknownC == 0);
assert(ret.numberOfScenarios <= 8);
// TODO. Or they are hardcoded in this hota version?
// ret.campaignRegions = ???;
}
ui8 campId = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19]
if(ret.version < CampaignVersion::Chr) // For chronicles: Will be overridden later; Chronicles uses own logic (reusing OH3 ID's)
ret.loadLegacyData(campId);
const auto & mapping = LIBRARY->mapFormat->getMapping(ret.version);
CampaignRegionID campaignMapId(reader.readUInt8());
ret.campaignRegions = *LIBRARY->campaignRegions->getByIndex(mapping.remap(campaignMapId));
if(ret.version != CampaignVersion::HotA)
ret.numberOfScenarios = ret.campaignRegions.regionsCount();
ret.name.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "name"));
ret.description.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "description"));
ret.author.appendRawString("");

View File

@@ -0,0 +1,148 @@
/*
* CampaignRegions.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 "CampaignRegions.h"
#include "../json/JsonNode.h"
CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(const JsonNode & node)
{
CampaignRegions::RegionDescription rd;
rd.infix = node["infix"].String();
rd.pos = Point(static_cast<int>(node["x"].Float()), static_cast<int>(node["y"].Float()));
if(!node["labelPos"].isNull())
rd.labelPos = Point(static_cast<int>(node["labelPos"]["x"].Float()), static_cast<int>(node["labelPos"]["y"].Float()));
else
rd.labelPos = std::nullopt;
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(const JsonNode & node)
{
campPrefix = node["prefix"].String();
colorSuffixLength = static_cast<int>(node["colorSuffixLength"].Float());
campSuffix = node["suffix"].isNull() ? std::vector<std::string>() : std::vector<std::string>{node["suffix"].Vector()[0].String(), node["suffix"].Vector()[1].String(), node["suffix"].Vector()[2].String()};
campBackground = node["background"].isNull() ? "" : node["background"].String();
for(const JsonNode & desc : node["desc"].Vector())
regions.push_back(CampaignRegions::RegionDescription::fromJson(desc));
}
JsonNode CampaignRegions::toJson(CampaignRegions cr)
{
JsonNode node;
node["prefix"].String() = cr.campPrefix;
node["colorSuffixLength"].Float() = cr.colorSuffixLength;
if(cr.campSuffix.empty())
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;
if(campDescriptions.empty()) //read once
{
const JsonNode config(JsonPath::builtin("config/campaign_regions.json"));
for(const JsonNode & campaign : config["campaign_regions"].Vector())
campDescriptions.push_back(CampaignRegions(campaign));
}
return campDescriptions.at(campId);
}
ImagePath CampaignRegions::getBackgroundName() const
{
if(campBackground.empty())
return ImagePath::builtin(campPrefix + "_BG.BMP");
else
return ImagePath::builtin(campBackground);
}
Point CampaignRegions::getPosition(CampaignScenarioID which) const
{
const auto & region = regions[which.getNum()];
return region.pos;
}
std::optional<Point> CampaignRegions::getLabelPosition(CampaignScenarioID which) const
{
const auto & region = regions[which.getNum()];
return region.labelPos;
}
ImagePath CampaignRegions::getNameFor(CampaignScenarioID which, int colorIndex, const std::string & type) const
{
const auto & region = regions[which.getNum()];
static const std::array<std::array<std::string, 8>, 3> colors = {{
{ "", "", "", "", "", "", "", "" },
{ "R", "B", "N", "G", "O", "V", "T", "P" },
{ "Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi" }
}};
std::string color = colors[colorSuffixLength][colorIndex];
return ImagePath::builtin(campPrefix + region.infix + "_" + type + color + ".BMP");
}
ImagePath CampaignRegions::getAvailableName(CampaignScenarioID which, int color) const
{
if(campSuffix.empty())
return getNameFor(which, color, "En");
else
return getNameFor(which, color, campSuffix[0]);
}
ImagePath CampaignRegions::getSelectedName(CampaignScenarioID which, int color) const
{
if(campSuffix.empty())
return getNameFor(which, color, "Se");
else
return getNameFor(which, color, campSuffix[1]);
}
ImagePath CampaignRegions::getConqueredName(CampaignScenarioID which, int color) const
{
if(campSuffix.empty())
return getNameFor(which, color, "Co");
else
return getNameFor(which, color, campSuffix[2]);
}
int CampaignRegions::regionsCount() const
{
return regions.size();
}

View File

@@ -0,0 +1,77 @@
/*
* CampaignRegions.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 "../Point.h"
#include "../filesystem/ResourcePath.h"
#include "../constants/EntityIdentifiers.h"
VCMI_LIB_NAMESPACE_BEGIN
class DLL_LINKAGE CampaignRegions
{
// Campaign editor
friend class CampaignEditor;
friend class CampaignProperties;
friend class ScenarioProperties;
std::string campPrefix;
std::vector<std::string> campSuffix;
std::string campBackground;
int colorSuffixLength = 0;
struct DLL_LINKAGE RegionDescription
{
std::string infix;
Point pos;
std::optional<Point> labelPos;
template <typename Handler> void serialize(Handler &h)
{
h & infix;
h & pos;
h & labelPos;
}
static CampaignRegions::RegionDescription fromJson(const JsonNode & node);
static JsonNode toJson(CampaignRegions::RegionDescription & rd);
};
std::vector<RegionDescription> regions;
ImagePath getNameFor(CampaignScenarioID which, int color, const std::string & type) const;
public:
CampaignRegions() = default;
explicit CampaignRegions(const JsonNode & node);
ImagePath getBackgroundName() const;
Point getPosition(CampaignScenarioID which) const;
std::optional<Point> getLabelPosition(CampaignScenarioID which) const;
ImagePath getAvailableName(CampaignScenarioID which, int color) const;
ImagePath getSelectedName(CampaignScenarioID which, int color) const;
ImagePath getConqueredName(CampaignScenarioID which, int color) const;
int regionsCount() const;
template <typename Handler> void serialize(Handler &h)
{
h & campPrefix;
h & colorSuffixLength;
h & regions;
h & campSuffix;
h & campBackground;
}
static JsonNode toJson(CampaignRegions cr);
static CampaignRegions getLegacy(int campId);
};
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,30 @@
/*
* CampaignRegionsHandler.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 "CampaignRegionsHandler.h"
#include "../json/JsonNode.h"
std::vector<JsonNode> CampaignRegionsHandler::loadLegacyData()
{
return {};
}
void CampaignRegionsHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
{
auto object = std::make_shared<CampaignRegions>(data);
registerObject(scope, "campaignRegion", name, objects.size());
objects.push_back(object);
}
void CampaignRegionsHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
{
throw std::runtime_error("CampaignRegionsHandler::loadObject - load by index is not supported!");
}

View File

@@ -0,0 +1,38 @@
/*
* CampaignRegionsHandler.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 "CampaignRegions.h"
#include "../IHandlerBase.h"
VCMI_LIB_NAMESPACE_BEGIN
/// Managed campaign region sets - "map" of campaign locations, with selectable scenarios
/// Used only for .h3c campaigns. .vmap's embed campaign regions layout in its format
class DLL_LINKAGE CampaignRegionsHandler : public IHandlerBase
{
public:
std::vector<JsonNode> loadLegacyData() override;
/// loads single object into game. Scope is namespace of this object, same as name of source mod
void loadObject(std::string scope, std::string name, const JsonNode & data) override;
void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
const CampaignRegions * getByIndex(int index) const
{
return objects.at(index).get();
}
private:
std::vector<std::shared_ptr<CampaignRegions>> objects;
};
VCMI_LIB_NAMESPACE_END

View File

@@ -33,151 +33,6 @@ void CampaignScenario::loadPreconditionRegions(ui32 regions)
}
}
CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(const JsonNode & node)
{
CampaignRegions::RegionDescription rd;
rd.infix = node["infix"].String();
rd.pos = Point(static_cast<int>(node["x"].Float()), static_cast<int>(node["y"].Float()));
if(!node["labelPos"].isNull())
rd.labelPos = Point(static_cast<int>(node["labelPos"]["x"].Float()), static_cast<int>(node["labelPos"]["y"].Float()));
else
rd.labelPos = std::nullopt;
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;
cr.campPrefix = node["prefix"].String();
cr.colorSuffixLength = static_cast<int>(node["colorSuffixLength"].Float());
cr.campSuffix = node["suffix"].isNull() ? std::vector<std::string>() : std::vector<std::string>{node["suffix"].Vector()[0].String(), node["suffix"].Vector()[1].String(), node["suffix"].Vector()[2].String()};
cr.campBackground = node["background"].isNull() ? "" : node["background"].String();
for(const JsonNode & desc : node["desc"].Vector())
cr.regions.push_back(CampaignRegions::RegionDescription::fromJson(desc));
return cr;
}
JsonNode CampaignRegions::toJson(CampaignRegions cr)
{
JsonNode node;
node["prefix"].String() = cr.campPrefix;
node["colorSuffixLength"].Float() = cr.colorSuffixLength;
if(cr.campSuffix.empty())
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;
if(campDescriptions.empty()) //read once
{
const JsonNode config(JsonPath::builtin("config/campaign_regions.json"));
for(const JsonNode & campaign : config["campaign_regions"].Vector())
campDescriptions.push_back(CampaignRegions::fromJson(campaign));
}
return campDescriptions.at(campId);
}
ImagePath CampaignRegions::getBackgroundName() const
{
if(campBackground.empty())
return ImagePath::builtin(campPrefix + "_BG.BMP");
else
return ImagePath::builtin(campBackground);
}
Point CampaignRegions::getPosition(CampaignScenarioID which) const
{
auto const & region = regions[which.getNum()];
return region.pos;
}
std::optional<Point> CampaignRegions::getLabelPosition(CampaignScenarioID which) const
{
auto const & region = regions[which.getNum()];
return region.labelPos;
}
ImagePath CampaignRegions::getNameFor(CampaignScenarioID which, int colorIndex, const std::string & type) const
{
auto const & region = regions[which.getNum()];
static const std::array<std::array<std::string, 8>, 3> colors = {{
{ "", "", "", "", "", "", "", "" },
{ "R", "B", "N", "G", "O", "V", "T", "P" },
{ "Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi" }
}};
std::string color = colors[colorSuffixLength][colorIndex];
return ImagePath::builtin(campPrefix + region.infix + "_" + type + color + ".BMP");
}
ImagePath CampaignRegions::getAvailableName(CampaignScenarioID which, int color) const
{
if(campSuffix.empty())
return getNameFor(which, color, "En");
else
return getNameFor(which, color, campSuffix[0]);
}
ImagePath CampaignRegions::getSelectedName(CampaignScenarioID which, int color) const
{
if(campSuffix.empty())
return getNameFor(which, color, "Se");
else
return getNameFor(which, color, campSuffix[1]);
}
ImagePath CampaignRegions::getConqueredName(CampaignScenarioID which, int color) const
{
if(campSuffix.empty())
return getNameFor(which, color, "Co");
else
return getNameFor(which, color, campSuffix[2]);
}
void CampaignHeader::loadLegacyData(ui8 campId)
{
campaignRegions = CampaignRegions::getLegacy(campId);
numberOfScenarios = LIBRARY->generaltexth->getCampaignLength(campId);
}
void CampaignHeader::loadLegacyData(const CampaignRegions & regions, int numOfScenario)
{
campaignRegions = regions;
numberOfScenarios = numOfScenario;
}
bool CampaignHeader::playerSelectedDifficulty() const
{
return difficultyChosenByPlayer;
@@ -526,8 +381,10 @@ void Campaign::overrideCampaign()
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["regions"].isNull())
campaignRegions = CampaignRegions(entry.second["regions"]);
if (!entry.second["scenarioCount"].isNull())
numberOfScenarios = entry.second["scenarioCount"].Integer();
if(!entry.second["loadingBackground"].isNull())
loadingBackground = ImagePath::builtin(entry.second["loadingBackground"].String());
if(!entry.second["videoRim"].isNull())

View File

@@ -9,13 +9,14 @@
*/
#pragma once
#include "CampaignBonus.h"
#include "CampaignRegions.h"
#include "CampaignScenarioPrologEpilog.h"
#include "../filesystem/ResourcePath.h"
#include "../gameState/HighScore.h"
#include "../serializer/Serializeable.h"
#include "../texts/TextLocalizationContainer.h"
#include "CampaignBonus.h"
#include "CampaignScenarioPrologEpilog.h"
#include "../gameState/HighScore.h"
#include "../Point.h"
VCMI_LIB_NAMESPACE_BEGIN
@@ -29,61 +30,6 @@ class CMapInfo;
class JsonNode;
class IGameInfoCallback;
class DLL_LINKAGE CampaignRegions
{
// Campaign editor
friend class CampaignEditor;
friend class CampaignProperties;
friend class ScenarioProperties;
std::string campPrefix;
std::vector<std::string> campSuffix;
std::string campBackground;
int colorSuffixLength;
struct DLL_LINKAGE RegionDescription
{
std::string infix;
Point pos;
std::optional<Point> labelPos;
template <typename Handler> void serialize(Handler &h)
{
h & infix;
h & pos;
h & labelPos;
}
static CampaignRegions::RegionDescription fromJson(const JsonNode & node);
static JsonNode toJson(CampaignRegions::RegionDescription & rd);
};
std::vector<RegionDescription> regions;
ImagePath getNameFor(CampaignScenarioID which, int color, const std::string & type) const;
public:
ImagePath getBackgroundName() const;
Point getPosition(CampaignScenarioID which) const;
std::optional<Point> getLabelPosition(CampaignScenarioID which) const;
ImagePath getAvailableName(CampaignScenarioID which, int color) const;
ImagePath getSelectedName(CampaignScenarioID which, int color) const;
ImagePath getConqueredName(CampaignScenarioID which, int color) const;
template <typename Handler> void serialize(Handler &h)
{
h & campPrefix;
h & colorSuffixLength;
h & regions;
h & campSuffix;
h & campBackground;
}
static CampaignRegions fromJson(const JsonNode & node);
static JsonNode toJson(CampaignRegions cr);
static CampaignRegions getLegacy(int campId);
};
class DLL_LINKAGE CampaignHeader : public boost::noncopyable
{
friend class CampaignHandler;
@@ -114,9 +60,6 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable
int numberOfScenarios = 0;
bool difficultyChosenByPlayer = false;
void loadLegacyData(ui8 campId);
void loadLegacyData(const CampaignRegions & regions, int numOfScenario);
TextContainerRegistrable textContainer;
public:

View File

@@ -1122,6 +1122,13 @@ public:
static const CampaignScenarioID NONE;
};
class DLL_LINKAGE CampaignRegionID : public StaticIdentifier<CampaignRegionID>
{
public:
using StaticIdentifier<CampaignRegionID>::StaticIdentifier;
};
// Deprecated
// TODO: remove
using Obj = MapObjectID;

View File

@@ -239,6 +239,8 @@ static void loadBonusAddInfo(CAddInfo & var, BonusType type, const JsonNode & va
case BonusType::SPELL_BEFORE_ATTACK:
case BonusType::SPELL_AFTER_ATTACK:
// 3 numbers
if (!value.isVector())
break;
var.resize(3);
var[0] = value[0].Integer();
var[1] = value[1].Integer();

View File

@@ -45,7 +45,7 @@ public:
};
/// Main class responsible for creation of all adventure map objects
class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase, boost::noncopyable
class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
{
/// list of object handlers, each of them handles only one type
std::vector< std::unique_ptr<ObjectClass> > mapObjectTypes;
@@ -106,4 +106,4 @@ public:
std::string getJsonKey(MapObjectID type) const;
};
VCMI_LIB_NAMESPACE_END
VCMI_LIB_NAMESPACE_END

View File

@@ -750,16 +750,14 @@ uint64_t CGHeroInstance::getValueForDiplomacy() const
uint32_t CGHeroInstance::getValueForCampaign() const
{
/// Determined by testing H3: hero is preferred for transfer in campaigns if total sum of his primary skills and his secondary skill levels is greatest
uint32_t score = 0;
// Determined by testing H3: hero is preferred for transfer in campaigns if total sum of his primary skills and his secondary skill levels is greatest
// Additional info from wiki: https://heroes.thelazy.net/index.php/Power_rating
uint32_t score = level;
score += getPrimSkillLevel(PrimarySkill::ATTACK);
score += getPrimSkillLevel(PrimarySkill::DEFENSE);
score += getPrimSkillLevel(PrimarySkill::SPELL_POWER);
score += getPrimSkillLevel(PrimarySkill::DEFENSE);
for (const auto& secondary : secSkills)
score += secondary.second;
return score;
}

View File

@@ -102,7 +102,7 @@ private:
};
// TODO: Instantiate ObstacleSetHandler
class DLL_LINKAGE ObstacleSetHandler : public IHandlerBase, boost::noncopyable
class DLL_LINKAGE ObstacleSetHandler : public IHandlerBase
{
public:

View File

@@ -15,6 +15,7 @@
#include "../GameLibrary.h"
#include "../IGameSettings.h"
#include "../json/JsonUtils.h"
MapIdentifiersH3M MapFormatSettings::generateMapping(EMapFormat format)
{
@@ -79,6 +80,8 @@ std::map<EMapFormat, MapIdentifiersH3M> MapFormatSettings::generateMappings()
MapFormatSettings::MapFormatSettings()
: mapping(generateMappings())
, campaignToMap(generateCampaignMapping())
, campaignOverridesConfig(JsonUtils::assembleFromFiles("config/campaignOverrides.json"))
, mapOverridesConfig(JsonUtils::assembleFromFiles("config/mapOverrides.json"))
{
}

View File

@@ -13,10 +13,11 @@
#include "MapIdentifiersH3M.h"
#include "MapFormat.h"
#include "../campaign/CampaignConstants.h"
#include "../json/JsonNode.h"
VCMI_LIB_NAMESPACE_BEGIN
class MapFormatSettings
class MapFormatSettings : boost::noncopyable
{
static MapIdentifiersH3M generateMapping(EMapFormat format);
static std::map<EMapFormat, MapIdentifiersH3M> generateMappings();
@@ -24,6 +25,9 @@ class MapFormatSettings
std::map<EMapFormat, MapIdentifiersH3M> mapping;
std::map<CampaignVersion, EMapFormat> campaignToMap;
JsonNode campaignOverridesConfig;
JsonNode mapOverridesConfig;
public:
MapFormatSettings();
@@ -46,6 +50,16 @@ public:
{
return mapping.at(campaignToMap.at(format));
}
const JsonNode & campaignOverrides(std::string & campaignName)
{
return campaignOverridesConfig[campaignName];
}
const JsonNode & mapOverrides(std::string & mapName)
{
return mapOverridesConfig[mapName];
}
};
VCMI_LIB_NAMESPACE_END

View File

@@ -98,6 +98,7 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping)
loadMapping(mappingTerrain, mapping["terrains"], "terrain");
loadMapping(mappingArtifact, mapping["artifacts"], "artifact");
loadMapping(mappingSecondarySkill, mapping["skills"], "skill");
loadMapping(mappingCampaignRegions, mapping["campaignRegions"], "campaignRegion");
}
void MapIdentifiersH3M::remapTemplate(ObjectTemplate & objectTemplate)
@@ -213,4 +214,11 @@ SecondarySkill MapIdentifiersH3M::remap(SecondarySkill input) const
return input;
}
CampaignRegionID MapIdentifiersH3M::remap(CampaignRegionID input) const
{
if (mappingCampaignRegions.count(input))
return mappingCampaignRegions.at(input);
return input;
}
VCMI_LIB_NAMESPACE_END

View File

@@ -43,6 +43,7 @@ class MapIdentifiersH3M
std::map<TerrainId, TerrainId> mappingTerrain;
std::map<ArtifactID, ArtifactID> mappingArtifact;
std::map<SecondarySkill, SecondarySkill> mappingSecondarySkill;
std::map<CampaignRegionID, CampaignRegionID> mappingCampaignRegions;
std::map<AnimationPath, AnimationPath> mappingObjectTemplate;
std::map<ObjectTypeIdentifier, ObjectTypeIdentifier> mappingObjectIndex;
@@ -63,6 +64,7 @@ public:
TerrainId remap(TerrainId input) const;
ArtifactID remap(ArtifactID input) const;
SecondarySkill remap(SecondarySkill input) const;
CampaignRegionID remap(CampaignRegionID input) const;
};

View File

@@ -18,6 +18,7 @@
#include "../BattleFieldHandler.h"
#include "../CCreatureHandler.h"
#include "../CConfigHandler.h"
#include "../campaign/CampaignRegionsHandler.h"
#include "../entities/artifact/CArtHandler.h"
#include "../entities/faction/CTownHandler.h"
#include "../entities/hero/CHeroClassHandler.h"
@@ -245,6 +246,7 @@ void CContentHandler::init()
handlers.insert(std::make_pair("artifacts", ContentTypeHandler(LIBRARY->arth.get(), "artifact")));
handlers.insert(std::make_pair("bonuses", ContentTypeHandler(LIBRARY->bth.get(), "bonus")));
handlers.insert(std::make_pair("creatures", ContentTypeHandler(LIBRARY->creh.get(), "creature")));
handlers.insert(std::make_pair("campaignRegions", ContentTypeHandler(LIBRARY->campaignRegions.get(), "campaignRegion")));
handlers.insert(std::make_pair("factions", ContentTypeHandler(LIBRARY->townh.get(), "faction")));
handlers.insert(std::make_pair("objects", ContentTypeHandler(LIBRARY->objtypeh.get(), "object")));
handlers.insert(std::make_pair("heroes", ContentTypeHandler(LIBRARY->heroh.get(), "hero")));

View File

@@ -288,8 +288,6 @@ CGeneralTextHandler::CGeneralTextHandler():
}
}
while (parser.endLine() && !text.empty());
scenariosCountPerCampaign.push_back(region);
}
}
}
@@ -306,15 +304,6 @@ int32_t CGeneralTextHandler::pluralText(const int32_t textIndex, const int32_t c
return textIndex + 1;
}
size_t CGeneralTextHandler::getCampaignLength(size_t campaignID) const
{
assert(campaignID < scenariosCountPerCampaign.size());
if(campaignID < scenariosCountPerCampaign.size())
return scenariosCountPerCampaign[campaignID];
return 0;
}
std::string CGeneralTextHandler::getPreferredLanguage()
{
assert(!settings["general"]["language"].String().empty());

View File

@@ -42,9 +42,6 @@ class DLL_LINKAGE CGeneralTextHandler: public TextLocalizationContainer
{
void readToVector(const std::string & sourceID, const std::string & sourceName);
/// number of scenarios in specific campaign. TODO: move to a better location
std::vector<size_t> scenariosCountPerCampaign;
public:
LegacyTextContainer allTexts;
@@ -78,8 +75,6 @@ public:
int32_t pluralText(int32_t textIndex, int32_t count) const;
size_t getCampaignLength(size_t campaignID) const;
CGeneralTextHandler();
CGeneralTextHandler(const CGeneralTextHandler&) = delete;
CGeneralTextHandler operator=(const CGeneralTextHandler&) = delete;