diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 209b03ef7..ddc78eace 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -951,8 +951,8 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID bu else if(auto uni = dynamic_cast(_market); uni->appearance) { titlePic = std::make_shared(uni->appearance->animationFile, 0, 0, 0, 0, CShowableAnim::CREATURE_MODE); - titleStr = uni->title; - speechStr = uni->speech; + titleStr = uni->getObjectName(); + speechStr = uni->getSpeechTranslated(); } else { diff --git a/config/gameConfig.json b/config/gameConfig.json index 0f51e2ea7..9692abfaf 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -56,6 +56,7 @@ "config/objects/lighthouse.json", "config/objects/magicSpring.json", "config/objects/magicWell.json", + "config/objects/markets.json", "config/objects/moddables.json", "config/objects/observatory.json", "config/objects/pyramid.json", diff --git a/config/objects/generic.json b/config/objects/generic.json index 238cfd848..3be342df6 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -18,115 +18,6 @@ } }, - "altarOfSacrifice" : { - "index" :2, - "handler" : "market", - "base" : { - "sounds" : { - "visit" : ["MYSTERY"] - } - }, - "types" : { - "object" : { - "index" : 0, - "aiValue" : 100, - "rmg" : { - "zoneLimit" : 1, - "value" : 100, - "rarity" : 20 - }, - "modes" : ["creature-experience", "artifact-experience"] - } - } - }, - "tradingPost" : { - "index" :221, - "handler" : "market", - "base" : { - "sounds" : { - "ambient" : ["LOOPMARK"], - "visit" : ["STORE"] - } - }, - "types" : { - "object" : { - "index" : 0, - "aiValue" : 100, - "rmg" : { - "zoneLimit" : 1, - "value" : 100, - "rarity" : 100 - }, - "modes" : ["resource-resource", "resource-player"], - "efficiency" : 5, - "title" : "core.genrltxt.159" - } - } - }, - "tradingPostDUPLICATE" : { - "index" :99, - "handler" : "market", - "base" : { - "sounds" : { - "ambient" : ["LOOPMARK"], - "visit" : ["STORE"] - } - }, - "types" : { - "object" : { - "index" : 0, - "aiValue" : 100, - "rmg" : { - "zoneLimit" : 1, - "value" : 100, - "rarity" : 100 - }, - "modes" : ["resource-resource", "resource-player"], - "efficiency" : 5, - "title" : "core.genrltxt.159" - } - } - }, - "freelancersGuild" : { - "index" :213, - "handler" : "market", - "types" : { - "object" : { - "index" : 0, - "aiValue" : 100, - "rmg" : { - "zoneLimit" : 1, - "value" : 100, - "rarity" : 100 - }, - "modes" : ["creature-resource"] - } - } - }, - - "blackMarket" : { - "index" :7, - "handler" : "market", - "base" : { - "sounds" : { - "ambient" : ["LOOPMARK"], - "visit" : ["MYSTERY"] - } - }, - "types" : { - "object" : { - "index" : 0, - "aiValue" : 8000, - "rmg" : { - "value" : 8000, - "rarity" : 20 - }, - "modes" : ["resource-artifact"], - "title" : "core.genrltxt.349" - } - } - }, - "pandoraBox" : { "index" :6, "handler" : "pandora", @@ -393,35 +284,6 @@ } } }, - "university" : { - "index" :104, - "handler" : "market", - "base" : { - "sounds" : { - "visit" : ["GAZEBO"] - } - }, - "types" : { - "object" : { - "index" : 0, - "aiValue" : 2500, - "rmg" : { - "value" : 2500, - "rarity" : 20 - }, - "modes" : ["resource-skill"], - "title" : "core.genrltxt.602", - "speech" : "core.genrltxt.603", - "offer": - [ - { "noneOf" : ["necromancy"] }, - { "noneOf" : ["necromancy"] }, - { "noneOf" : ["necromancy"] }, - { "noneOf" : ["necromancy"] } - ] - } - } - }, "questGuard" : { "index" :215, "handler" : "questGuard", diff --git a/config/objects/markets.json b/config/objects/markets.json new file mode 100644 index 000000000..ea5c517b9 --- /dev/null +++ b/config/objects/markets.json @@ -0,0 +1,138 @@ +{ + "altarOfSacrifice" : { + "index" :2, + "handler" : "market", + "base" : { + "sounds" : { + "visit" : ["MYSTERY"] + } + }, + "types" : { + "object" : { + "index" : 0, + "aiValue" : 100, + "rmg" : { + "zoneLimit" : 1, + "value" : 100, + "rarity" : 20 + }, + "modes" : ["creature-experience", "artifact-experience"] + } + } + }, + + "tradingPost" : { + "index" :221, + "handler" : "market", + "base" : { + "sounds" : { + "ambient" : ["LOOPMARK"], + "visit" : ["STORE"] + } + }, + "types" : { + "object" : { + "index" : 0, + "aiValue" : 100, + "rmg" : { + "zoneLimit" : 1, + "value" : 100, + "rarity" : 100 + }, + "modes" : ["resource-resource", "resource-player"], + "efficiency" : 5 + } + } + }, + + "tradingPostDUPLICATE" : { + "index" :99, + "handler" : "market", + "base" : { + "sounds" : { + "ambient" : ["LOOPMARK"], + "visit" : ["STORE"] + } + }, + "types" : { + "object" : { + "index" : 0, + "aiValue" : 100, + "rmg" : { + "zoneLimit" : 1, + "value" : 100, + "rarity" : 100 + }, + "modes" : ["resource-resource", "resource-player"], + "efficiency" : 5 + } + } + }, + + "freelancersGuild" : { + "index" :213, + "handler" : "market", + "types" : { + "object" : { + "index" : 0, + "aiValue" : 100, + "rmg" : { + "zoneLimit" : 1, + "value" : 100, + "rarity" : 100 + }, + "modes" : ["creature-resource"] + } + } + }, + + "blackMarket" : { + "index" :7, + "handler" : "market", + "base" : { + "sounds" : { + "ambient" : ["LOOPMARK"], + "visit" : ["MYSTERY"] + } + }, + "types" : { + "object" : { + "index" : 0, + "aiValue" : 8000, + "rmg" : { + "value" : 8000, + "rarity" : 20 + }, + "modes" : ["resource-artifact"] + } + } + }, + "university" : { + "index" :104, + "handler" : "market", + "base" : { + "sounds" : { + "visit" : ["GAZEBO"] + } + }, + "types" : { + "object" : { + "index" : 0, + "aiValue" : 2500, + "rmg" : { + "value" : 2500, + "rarity" : 20 + }, + "modes" : ["resource-skill"], + "speech" : "@core.genrltxt.603", + "offer": + [ + { "noneOf" : ["necromancy"] }, + { "noneOf" : ["necromancy"] }, + { "noneOf" : ["necromancy"] }, + { "noneOf" : ["necromancy"] } + ] + } + } + } +} \ No newline at end of file diff --git a/config/schemas/market.json b/config/schemas/market.json new file mode 100644 index 000000000..7554b453a --- /dev/null +++ b/config/schemas/market.json @@ -0,0 +1,50 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-04/schema", + "title" : "VCMI map object format", + "description" : "Description of map object class", + "required" : [ "modes" ], + + "additionalProperties" : false, + + "properties" : { + "description" : { + "description" : "Message that will be shown on right-clicking this object", + "type" : "string" + }, + + "speech" : { + "description" : "Message that will be shown to player on visiting this object", + "type" : "string" + }, + + "modes" : { + "type" : "array", + "items" : { + "enum" : [ "resource-resource", "resource-player", "creature-resource", "resource-artifact", "artifact-resource", "artifact-experience", "creature-experience", "creature-undead", "resource-skill" ], + "type" : "string" + } + }, + "efficiency" : { + "type" : "number", + "minimum" : 1, + "maximum" : 9 + }, + "offer" : { + "type" : "array" + }, + + // Properties that might appear since this node is shared with object config + "compatibilityIdentifiers" : { }, + "blockedVisitable" : { }, + "removable" : { }, + "aiValue" : { }, + "index" : { }, + "base" : { }, + "name" : { }, + "rmg" : { }, + "templates" : { }, + "battleground" : { }, + "sounds" : { } + } +} diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index 57b52bd43..a3986304a 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -17,8 +17,10 @@ #include "../TerrainHandler.h" #include "../VCMI_Lib.h" +#include "../CConfigHandler.h" #include "../entities/faction/CTownHandler.h" #include "../entities/hero/CHeroClass.h" +#include "../json/JsonUtils.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGMarket.h" #include "../mapObjects/CGTownInstance.h" @@ -242,10 +244,28 @@ AnimationPath BoatInstanceConstructor::getBoatAnimationName() const void MarketInstanceConstructor::initTypeData(const JsonNode & input) { + if (settings["mods"]["validation"].String() != "off") + JsonUtils::validate(input, "vcmi:market", getJsonKey()); + if (!input["description"].isNull()) { - description = input["description"].String(); - VLC->generaltexth->registerString(input.getModScope(), TextIdentifier(getBaseTextID(), "description"), description); + std::string description = input["description"].String(); + descriptionTextID = TextIdentifier(getBaseTextID(), "description").get(); + VLC->generaltexth->registerString( input.getModScope(), descriptionTextID, input["description"]); + } + + if (!input["speech"].isNull()) + { + std::string speech = input["speech"].String(); + if (!speech.empty() && speech.at(0) == '@') + { + speechTextID = speech.substr(1); + } + else + { + speechTextID = TextIdentifier(getBaseTextID(), "speech").get(); + VLC->generaltexth->registerString( input.getModScope(), speechTextID, input["speech"]); + } } for(auto & element : input["modes"].Vector()) @@ -256,14 +276,11 @@ void MarketInstanceConstructor::initTypeData(const JsonNode & input) marketEfficiency = input["efficiency"].isNull() ? 5 : input["efficiency"].Integer(); predefinedOffer = input["offer"]; - - title = input["title"].String(); - speech = input["speech"].String(); } bool MarketInstanceConstructor::hasDescription() const { - return !description.empty(); + return !descriptionTextID.empty(); } CGMarket * MarketInstanceConstructor::createObject(IGameCallback * cb) const @@ -283,21 +300,6 @@ CGMarket * MarketInstanceConstructor::createObject(IGameCallback * cb) const return new CGMarket(cb); } -void MarketInstanceConstructor::initializeObject(CGMarket * market) const -{ - market->marketEfficiency = marketEfficiency; - - if(auto university = dynamic_cast(market)) - { - university->title = market->getObjectName(); - if(!title.empty()) - university->title = VLC->generaltexth->translate(title); - - if(!speech.empty()) - university->speech = VLC->generaltexth->translate(speech); - } -} - const std::set & MarketInstanceConstructor::availableModes() const { return marketModes; @@ -315,4 +317,15 @@ void MarketInstanceConstructor::randomizeObject(CGMarket * object, vstd::RNG & r } } +std::string MarketInstanceConstructor::getSpeechTranslated() const +{ + assert(marketModes.count(EMarketMode::RESOURCE_SKILL)); + return VLC->generaltexth->translate(speechTextID); +} + +int MarketInstanceConstructor::getMarketEfficiency() const +{ + return marketEfficiency; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjectConstructors/CommonConstructors.h b/lib/mapObjectConstructors/CommonConstructors.h index 8d4abcc67..73ba43874 100644 --- a/lib/mapObjectConstructors/CommonConstructors.h +++ b/lib/mapObjectConstructors/CommonConstructors.h @@ -115,25 +115,23 @@ public: class MarketInstanceConstructor : public CDefaultObjectTypeHandler { -protected: - void initTypeData(const JsonNode & config) override; + std::string descriptionTextID; + std::string speechTextID; std::set marketModes; JsonNode predefinedOffer; int marketEfficiency; - - std::string description; - std::string title; - std::string speech; - + + void initTypeData(const JsonNode & config) override; public: CGMarket * createObject(IGameCallback * cb) const override; - void initializeObject(CGMarket * object) const override; void randomizeObject(CGMarket * object, vstd::RNG & rng) const override; const std::set & availableModes() const; bool hasDescription() const; + std::string getSpeechTranslated() const; + int getMarketEfficiency() const; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index ff2118bf2..a7eef18ad 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -57,7 +57,7 @@ std::string CGMarket::getPopupText(const CGHeroInstance * hero) const int CGMarket::getMarketEfficiency() const { - return marketEfficiency; + return getMarketHandler()->getMarketEfficiency(); } int CGMarket::availableUnits(EMarketMode mode, int marketItemSerial) const @@ -125,6 +125,11 @@ std::vector CGUniversity::availableItemsIds(EMarketMode mode) cons } } +std::string CGUniversity::getSpeechTranslated() const +{ + return getMarketHandler()->getSpeechTranslated(); +} + void CGUniversity::onHeroVisit(const CGHeroInstance * h) const { cb->showObjectWindow(this, EOpenWindowMode::UNIVERSITY_WINDOW, h, true); diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h index b28a386bc..6f4f92a42 100644 --- a/lib/mapObjects/CGMarket.h +++ b/lib/mapObjects/CGMarket.h @@ -19,11 +19,10 @@ class MarketInstanceConstructor; class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket { +protected: std::shared_ptr getMarketHandler() const; public: - int marketEfficiency; - CGMarket(IGameCallback *cb); ///IObjectInterface void onHeroVisit(const CGHeroInstance * h) const override; //open trading window @@ -48,7 +47,12 @@ public: h & marketModes; } - h & marketEfficiency; + if (h.version < Handler::Version::MARKET_TRANSLATION_FIX) + { + int unused = 0; + h & unused; + } + if (h.version < Handler::Version::NEW_MARKETS) { std::string speech; @@ -103,8 +107,8 @@ class DLL_LINKAGE CGUniversity : public CGMarket { public: using CGMarket::CGMarket; - std::string speech; //currently shown only in university - std::string title; + + std::string getSpeechTranslated() const; std::vector skills; //available skills @@ -115,10 +119,11 @@ public: { h & static_cast(*this); h & skills; - if (h.version >= Handler::Version::NEW_MARKETS) + if (h.version >= Handler::Version::NEW_MARKETS && h.version < Handler::Version::MARKET_TRANSLATION_FIX) { - h & speech; - h & title; + std::string temp; + h & temp; + h & temp; } } }; diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 5e038bbf0..fbf723a88 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -68,6 +68,7 @@ enum class ESerializationVersion : int32_t REMOVE_VLC_POINTERS, // 869 removed remaining pointers to VLC entities FOLDER_NAME_REWORK, // 870 - rework foldername REWARDABLE_GUARDS, // 871 - fix missing serialization of guards in rewardable objects + MARKET_TRANSLATION_FIX, // 872 - remove serialization of markets translateable strings - CURRENT = REWARDABLE_GUARDS + CURRENT = MARKET_TRANSLATION_FIX };