2023-05-24 00:14:06 +02:00
|
|
|
/*
|
|
|
|
* CMapHeader.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 "CMapHeader.h"
|
|
|
|
|
|
|
|
#include "MapFormat.h"
|
|
|
|
|
2024-07-21 12:49:40 +02:00
|
|
|
#include "../CHeroHandler.h"
|
2023-05-24 00:14:06 +02:00
|
|
|
#include "../VCMI_Lib.h"
|
2024-07-21 12:49:40 +02:00
|
|
|
#include "../entities/faction/CTownHandler.h"
|
2024-02-11 23:09:01 +02:00
|
|
|
#include "../json/JsonUtils.h"
|
2023-09-29 00:24:45 +02:00
|
|
|
#include "../modding/CModHandler.h"
|
2024-07-21 12:49:40 +02:00
|
|
|
#include "../texts/CGeneralTextHandler.h"
|
2024-07-20 14:55:17 +02:00
|
|
|
#include "../texts/Languages.h"
|
2023-05-24 00:14:06 +02:00
|
|
|
|
2023-05-24 01:12:25 +02:00
|
|
|
VCMI_LIB_NAMESPACE_BEGIN
|
|
|
|
|
2023-05-24 00:14:06 +02:00
|
|
|
SHeroName::SHeroName() : heroId(-1)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PlayerInfo::PlayerInfo(): canHumanPlay(false), canComputerPlay(false),
|
|
|
|
aiTactic(EAiTactic::RANDOM), isFactionRandom(false), hasRandomHero(false), mainCustomHeroPortrait(-1), mainCustomHeroId(-1), hasMainTown(false),
|
|
|
|
generateHeroAtMainTown(false), posOfMainTown(-1), team(TeamID::NO_TEAM)
|
|
|
|
{
|
|
|
|
allowedFactions = VLC->townh->getAllowedFactions();
|
|
|
|
}
|
|
|
|
|
2023-09-28 18:43:04 +02:00
|
|
|
FactionID PlayerInfo::defaultCastle() const
|
2023-05-24 00:14:06 +02:00
|
|
|
{
|
|
|
|
//if random allowed set it as default
|
|
|
|
if(isFactionRandom)
|
2023-09-28 18:43:04 +02:00
|
|
|
return FactionID::RANDOM;
|
2023-05-24 00:14:06 +02:00
|
|
|
|
|
|
|
if(!allowedFactions.empty())
|
|
|
|
return *allowedFactions.begin();
|
|
|
|
|
|
|
|
// fall back to random
|
2023-09-28 18:43:04 +02:00
|
|
|
return FactionID::RANDOM;
|
2023-05-24 00:14:06 +02:00
|
|
|
}
|
|
|
|
|
2023-09-28 18:43:04 +02:00
|
|
|
HeroTypeID PlayerInfo::defaultHero() const
|
2023-05-24 00:14:06 +02:00
|
|
|
{
|
|
|
|
// we will generate hero in front of main town
|
|
|
|
if((generateHeroAtMainTown && hasMainTown) || hasRandomHero)
|
|
|
|
{
|
|
|
|
//random hero
|
2023-09-28 18:43:04 +02:00
|
|
|
return HeroTypeID::RANDOM;
|
2023-05-24 00:14:06 +02:00
|
|
|
}
|
|
|
|
|
2023-09-28 18:43:04 +02:00
|
|
|
return HeroTypeID::NONE;
|
2023-05-24 00:14:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool PlayerInfo::canAnyonePlay() const
|
|
|
|
{
|
|
|
|
return canHumanPlay || canComputerPlay;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PlayerInfo::hasCustomMainHero() const
|
|
|
|
{
|
2023-09-28 18:43:04 +02:00
|
|
|
return mainCustomHeroId.isValid();
|
2023-05-24 00:14:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
EventCondition::EventCondition(EWinLoseType condition):
|
|
|
|
value(-1),
|
|
|
|
position(-1, -1, -1),
|
|
|
|
condition(condition)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-11-08 17:35:17 +02:00
|
|
|
EventCondition::EventCondition(EWinLoseType condition, si32 value, TargetTypeID objectType, const int3 & position):
|
2023-05-24 00:14:06 +02:00
|
|
|
value(value),
|
|
|
|
objectType(objectType),
|
|
|
|
position(position),
|
|
|
|
condition(condition)
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
|
|
void CMapHeader::setupEvents()
|
|
|
|
{
|
|
|
|
EventCondition victoryCondition(EventCondition::STANDARD_WIN);
|
|
|
|
EventCondition defeatCondition(EventCondition::DAYS_WITHOUT_TOWN);
|
|
|
|
defeatCondition.value = 7;
|
|
|
|
|
|
|
|
//Victory condition - defeat all
|
|
|
|
TriggeredEvent standardVictory;
|
|
|
|
standardVictory.effect.type = EventEffect::VICTORY;
|
2023-06-18 12:51:11 +02:00
|
|
|
standardVictory.effect.toOtherMessage.appendTextID("core.genrltxt.5");
|
2023-05-24 00:14:06 +02:00
|
|
|
standardVictory.identifier = "standardVictory";
|
|
|
|
standardVictory.description.clear(); // TODO: display in quest window
|
2023-06-18 12:51:11 +02:00
|
|
|
standardVictory.onFulfill.appendTextID("core.genrltxt.659");
|
2023-05-24 00:14:06 +02:00
|
|
|
standardVictory.trigger = EventExpression(victoryCondition);
|
|
|
|
|
|
|
|
//Loss condition - 7 days without town
|
|
|
|
TriggeredEvent standardDefeat;
|
|
|
|
standardDefeat.effect.type = EventEffect::DEFEAT;
|
2023-06-18 12:51:11 +02:00
|
|
|
standardDefeat.effect.toOtherMessage.appendTextID("core.genrltxt.8");
|
2023-05-24 00:14:06 +02:00
|
|
|
standardDefeat.identifier = "standardDefeat";
|
|
|
|
standardDefeat.description.clear(); // TODO: display in quest window
|
2023-06-18 12:51:11 +02:00
|
|
|
standardDefeat.onFulfill.appendTextID("core.genrltxt.7");
|
2023-05-24 00:14:06 +02:00
|
|
|
standardDefeat.trigger = EventExpression(defeatCondition);
|
|
|
|
|
|
|
|
triggeredEvents.push_back(standardVictory);
|
|
|
|
triggeredEvents.push_back(standardDefeat);
|
|
|
|
|
|
|
|
victoryIconIndex = 11;
|
2023-06-18 12:51:11 +02:00
|
|
|
victoryMessage.appendTextID("core.vcdesc.0");
|
2023-05-24 00:14:06 +02:00
|
|
|
|
|
|
|
defeatIconIndex = 3;
|
2023-06-18 12:51:11 +02:00
|
|
|
defeatMessage.appendTextID("core.lcdesc.0");
|
2023-05-24 00:14:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
CMapHeader::CMapHeader() : version(EMapFormat::VCMI), height(72), width(72),
|
2024-02-05 21:27:55 +02:00
|
|
|
twoLevel(true), difficulty(EMapDifficulty::NORMAL), levelLimit(0), howManyTeams(0), areAnyPlayers(false)
|
2023-05-24 00:14:06 +02:00
|
|
|
{
|
|
|
|
setupEvents();
|
|
|
|
allowedHeroes = VLC->heroh->getDefaultAllowed();
|
|
|
|
players.resize(PlayerColor::PLAYER_LIMIT_I);
|
2023-09-28 02:52:49 +02:00
|
|
|
}
|
|
|
|
|
2024-01-15 13:10:25 +02:00
|
|
|
CMapHeader::~CMapHeader() = default;
|
2023-05-24 00:14:06 +02:00
|
|
|
|
|
|
|
ui8 CMapHeader::levels() const
|
|
|
|
{
|
|
|
|
return (twoLevel ? 2 : 1);
|
|
|
|
}
|
2023-05-24 01:12:25 +02:00
|
|
|
|
2023-09-28 13:09:01 +02:00
|
|
|
void CMapHeader::registerMapStrings()
|
|
|
|
{
|
2023-10-01 18:00:36 +02:00
|
|
|
//get supported languages. Assuming that translation containing most strings is the base language
|
2024-01-10 02:23:24 +02:00
|
|
|
std::set<std::string, std::less<>> mapLanguages;
|
|
|
|
std::set<std::string, std::less<>> mapBaseLanguages;
|
2023-10-01 18:00:36 +02:00
|
|
|
int maxStrings = 0;
|
|
|
|
for(auto & translation : translations.Struct())
|
2023-09-28 13:09:01 +02:00
|
|
|
{
|
2023-10-01 18:00:36 +02:00
|
|
|
if(translation.first.empty() || !translation.second.isStruct() || translation.second.Struct().empty())
|
|
|
|
continue;
|
2023-09-28 13:09:01 +02:00
|
|
|
|
2023-10-01 18:00:36 +02:00
|
|
|
if(translation.second.Struct().size() > maxStrings)
|
|
|
|
maxStrings = translation.second.Struct().size();
|
|
|
|
mapLanguages.insert(translation.first);
|
2023-09-28 13:09:01 +02:00
|
|
|
}
|
2023-10-01 18:00:36 +02:00
|
|
|
|
2023-10-05 23:34:29 +02:00
|
|
|
if(maxStrings == 0 || mapLanguages.empty())
|
2023-10-01 18:00:36 +02:00
|
|
|
{
|
2024-02-05 21:11:00 +02:00
|
|
|
logGlobal->trace("Map %s doesn't have any supported translation", name.toString());
|
2023-10-01 18:00:36 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//identifying base languages
|
|
|
|
for(auto & translation : translations.Struct())
|
|
|
|
{
|
|
|
|
if(translation.second.isStruct() && translation.second.Struct().size() == maxStrings)
|
|
|
|
mapBaseLanguages.insert(translation.first);
|
|
|
|
}
|
|
|
|
|
2024-01-10 00:38:54 +02:00
|
|
|
std::string baseLanguage;
|
|
|
|
std::string language;
|
2024-06-24 03:23:26 +02:00
|
|
|
//english is preferable as base language
|
2023-10-01 18:00:36 +02:00
|
|
|
if(mapBaseLanguages.count(Languages::getLanguageOptions(Languages::ELanguages::ENGLISH).identifier))
|
|
|
|
baseLanguage = Languages::getLanguageOptions(Languages::ELanguages::ENGLISH).identifier;
|
|
|
|
else
|
|
|
|
baseLanguage = *mapBaseLanguages.begin();
|
|
|
|
|
|
|
|
if(mapBaseLanguages.count(CGeneralTextHandler::getPreferredLanguage()))
|
|
|
|
{
|
|
|
|
language = CGeneralTextHandler::getPreferredLanguage(); //preferred language is base language - use it
|
|
|
|
baseLanguage = language;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(mapLanguages.count(CGeneralTextHandler::getPreferredLanguage()))
|
|
|
|
language = CGeneralTextHandler::getPreferredLanguage();
|
|
|
|
else
|
|
|
|
language = baseLanguage; //preferred language is not supported, use base language
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(!language.empty());
|
|
|
|
|
|
|
|
JsonNode data = translations[baseLanguage];
|
|
|
|
if(language != baseLanguage)
|
|
|
|
JsonUtils::mergeCopy(data, translations[language]);
|
|
|
|
|
|
|
|
for(auto & s : data.Struct())
|
2024-01-15 13:10:25 +02:00
|
|
|
texts.registerString("map", TextIdentifier(s.first), s.second.String(), language);
|
2023-09-28 13:09:01 +02:00
|
|
|
}
|
|
|
|
|
2023-09-29 00:24:45 +02:00
|
|
|
std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized)
|
2023-09-28 14:38:31 +02:00
|
|
|
{
|
2023-09-29 00:24:45 +02:00
|
|
|
return mapRegisterLocalizedString(modContext, mapHeader, UID, localized, VLC->modh->getModLanguage(modContext));
|
2023-09-28 14:38:31 +02:00
|
|
|
}
|
|
|
|
|
2023-09-29 00:24:45 +02:00
|
|
|
std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized, const std::string & language)
|
2023-09-28 14:38:31 +02:00
|
|
|
{
|
2024-01-15 13:10:25 +02:00
|
|
|
mapHeader.texts.registerString(modContext, UID, localized, language);
|
2023-09-28 14:38:31 +02:00
|
|
|
mapHeader.translations.Struct()[language].Struct()[UID.get()].String() = localized;
|
|
|
|
return UID.get();
|
|
|
|
}
|
|
|
|
|
2023-05-24 01:12:25 +02:00
|
|
|
VCMI_LIB_NAMESPACE_END
|