From fe2a981ddfd48187491b0782333811d9ce0ba03c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 20 Jul 2025 16:25:50 +0300 Subject: [PATCH 1/3] Show skill that can be learned in university on right click As in title. Now if player has already visited university before, game will show list of skills that can be learned in this university on right click --- client/widgets/CComponent.cpp | 2 +- client/windows/InfoWindows.cpp | 2 +- lib/mapObjects/CGMarket.cpp | 25 +++++++++++++++++++++++++ lib/mapObjects/CGMarket.h | 2 ++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index 5b9d84f63..ccd19ce91 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -119,7 +119,7 @@ std::vector CComponent::getFileName() const static const std::array primSkillsArr = {"PSKIL32", "PSKIL32", "PSKIL42", "PSKILL"}; static const std::array secSkillsArr = {"SECSK32", "SECSK32", "SECSKILL", "SECSK82"}; static const std::array resourceArr = {"SMALRES", "RESOURCE", "RESOURCE", "RESOUR82"}; - static const std::array creatureArr = {"CPRSMALL", "CPRSMALL", "CPRSMALL", "TWCRPORT"}; + static const std::array creatureArr = {"CPRSMALL", "CPRSMALL", "TWCRPORT", "TWCRPORT"}; static const std::array artifactArr = {"Artifact", "Artifact", "Artifact", "Artifact"}; static const std::array spellsArr = {"SpellInt", "SpellInt", "SpellInt", "SPELLSCR"}; static const std::array moraleArr = {"IMRL22", "IMRL30", "IMRL42", "imrl82"}; diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index 07b88cb47..98997550e 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -233,7 +233,7 @@ void CRClickPopup::createAndPush(const CGObjectInstance * obj, const Point & p, std::vector> guiComponents; for(auto & component : components) - guiComponents.push_back(std::make_shared(component)); + guiComponents.push_back(std::make_shared(component, CComponent::medium)); if(GAME->interface()->localState->getCurrentHero()) CRClickPopup::createAndPush(obj->getPopupText(GAME->interface()->localState->getCurrentHero()), guiComponents); diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index c66d9271f..8fadfdc2f 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -23,6 +23,7 @@ #include "../mapObjectConstructors/CObjectClassesHandler.h" #include "../mapObjectConstructors/CommonConstructors.h" #include "../networkPacks/PacksForClient.h" +#include "CPlayerState.h" VCMI_LIB_NAMESPACE_BEGIN @@ -135,7 +136,31 @@ std::string CGUniversity::getSpeechTranslated() const void CGUniversity::onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const { + ChangeObjectVisitors cow; + cow.object = id; + cow.mode = ChangeObjectVisitors::VISITOR_ADD_PLAYER; + cow.hero = h->id; + gameEvents.sendAndApply(cow); + gameEvents.showObjectWindow(this, EOpenWindowMode::UNIVERSITY_WINDOW, h, true); } +bool CGUniversity::wasVisited (PlayerColor player) const +{ + return cb->getPlayerState(player)->visitedObjects.count(id) != 0; +} + +std::vector CGUniversity::getPopupComponents(PlayerColor player) const +{ + std::vector result; + + if (!wasVisited(player)) + return result; + + for (auto const & skill : skills) + result.emplace_back(ComponentType::SEC_SKILL, skill.as()); + + return result; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h index f52cecb08..2ddd551f4 100644 --- a/lib/mapObjects/CGMarket.h +++ b/lib/mapObjects/CGMarket.h @@ -65,6 +65,8 @@ public: std::vector availableItemsIds(EMarketMode mode) const override; void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override; //open window + std::vector getPopupComponents(PlayerColor player) const override; + bool wasVisited (PlayerColor player) const override; template void serialize(Handler &h) { From b3167a44eef5164e64162347664bc25088b77229 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 21 Jul 2025 19:38:24 +0300 Subject: [PATCH 2/3] Move MarketInstanceConstructor class to a separate file --- client/windows/GUIClasses.cpp | 1 + lib/CMakeLists.txt | 2 + .../CObjectClassesHandler.cpp | 1 + .../CommonConstructors.cpp | 92 +-------------- .../CommonConstructors.h | 23 ---- .../MarketInstanceConstructor.cpp | 105 ++++++++++++++++++ .../MarketInstanceConstructor.h | 39 +++++++ lib/mapObjects/CGMarket.cpp | 18 +-- 8 files changed, 159 insertions(+), 122 deletions(-) create mode 100644 lib/mapObjectConstructors/MarketInstanceConstructor.cpp create mode 100644 lib/mapObjectConstructors/MarketInstanceConstructor.h diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 362ee283d..8ee1d28e5 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -51,6 +51,7 @@ #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/CGMarket.h" #include "../lib/mapObjects/CGTownInstance.h" +#include "../lib/mapObjects/ObjectTemplate.h" #include "../lib/gameState/CGameState.h" #include "../lib/gameState/SThievesGuildInfo.h" #include "../lib/gameState/TavernHeroesPool.h" diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index bdeee78f6..8f074adce 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -140,6 +140,7 @@ set(lib_MAIN_SRCS mapObjectConstructors/DwellingInstanceConstructor.cpp mapObjectConstructors/FlaggableInstanceConstructor.cpp mapObjectConstructors/HillFortInstanceConstructor.cpp + mapObjectConstructors/MarketInstanceConstructor.cpp mapObjectConstructors/ShipyardInstanceConstructor.cpp mapObjects/CGCreature.cpp @@ -565,6 +566,7 @@ set(lib_MAIN_HEADERS mapObjectConstructors/HillFortInstanceConstructor.h mapObjectConstructors/FlaggableInstanceConstructor.h mapObjectConstructors/IObjectInfo.h + mapObjectConstructors/MarketInstanceConstructor.h mapObjectConstructors/RandomMapInfo.h mapObjectConstructors/ShipyardInstanceConstructor.h mapObjectConstructors/SObjectSounds.h diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index bd199a1d0..dc5dc6407 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -25,6 +25,7 @@ #include "../mapObjectConstructors/DwellingInstanceConstructor.h" #include "../mapObjectConstructors/FlaggableInstanceConstructor.h" #include "../mapObjectConstructors/HillFortInstanceConstructor.h" +#include "../mapObjectConstructors/MarketInstanceConstructor.h" #include "../mapObjectConstructors/ShipyardInstanceConstructor.h" #include "../mapObjects/CGCreature.h" diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index f055f654a..3019e0e28 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -10,24 +10,20 @@ #include "StdInc.h" #include "CommonConstructors.h" -#include "../texts/CGeneralTextHandler.h" #include "../json/JsonRandom.h" #include "../constants/StringConstants.h" -#include "../TerrainHandler.h" #include "../GameLibrary.h" -#include "../CConfigHandler.h" #include "../callback/IGameInfoCallback.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" #include "../mapObjects/MiscObjects.h" #include "../mapObjects/ObjectTemplate.h" #include "../mapping/TerrainTile.h" #include "../modding/IdentifierStorage.h" +#include "../texts/TextIdentifier.h" VCMI_LIB_NAMESPACE_BEGIN @@ -277,90 +273,4 @@ AnimationPath BoatInstanceConstructor::getBoatAnimationName() const return actualAnimation; } -void MarketInstanceConstructor::initTypeData(const JsonNode & input) -{ - if (settings["mods"]["validation"].String() != "off") - JsonUtils::validate(input, "vcmi:market", getJsonKey()); - - if (!input["description"].isNull()) - { - std::string description = input["description"].String(); - descriptionTextID = TextIdentifier(getBaseTextID(), "description").get(); - LIBRARY->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(); - LIBRARY->generaltexth->registerString( input.getModScope(), speechTextID, input["speech"]); - } - } - - for(auto & element : input["modes"].Vector()) - { - if(MappedKeys::MARKET_NAMES_TO_TYPES.count(element.String())) - marketModes.insert(MappedKeys::MARKET_NAMES_TO_TYPES.at(element.String())); - } - - marketEfficiency = input["efficiency"].isNull() ? 5 : input["efficiency"].Integer(); - predefinedOffer = input["offer"]; -} - -bool MarketInstanceConstructor::hasDescription() const -{ - return !descriptionTextID.empty(); -} - -std::shared_ptr MarketInstanceConstructor::createObject(IGameInfoCallback * cb) const -{ - if(marketModes.size() == 1) - { - switch(*marketModes.begin()) - { - case EMarketMode::ARTIFACT_RESOURCE: - case EMarketMode::RESOURCE_ARTIFACT: - return std::make_shared(cb); - - case EMarketMode::RESOURCE_SKILL: - return std::make_shared(cb); - } - } - return std::make_shared(cb); -} - -const std::set & MarketInstanceConstructor::availableModes() const -{ - return marketModes; -} - -void MarketInstanceConstructor::randomizeObject(CGMarket * object, IGameRandomizer & gameRandomizer) const -{ - JsonRandom randomizer(object->cb, gameRandomizer); - JsonRandom::Variables emptyVariables; - - if(auto * university = dynamic_cast(object)) - { - for(auto skill : randomizer.loadSecondaries(predefinedOffer, emptyVariables)) - university->skills.push_back(skill.first); - } -} - -std::string MarketInstanceConstructor::getSpeechTranslated() const -{ - assert(marketModes.count(EMarketMode::RESOURCE_SKILL)); - return LIBRARY->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 a941d3143..4b75c1bc8 100644 --- a/lib/mapObjectConstructors/CommonConstructors.h +++ b/lib/mapObjectConstructors/CommonConstructors.h @@ -15,10 +15,8 @@ #include "../mapObjects/MiscObjects.h" #include "../mapObjects/CGCreature.h" #include "../mapObjects/CGHeroInstance.h" -#include "../mapObjects/CGMarket.h" #include "../mapObjects/CGResource.h" #include "../mapObjects/CGTownInstance.h" -#include "../mapObjects/ObstacleSetHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -126,25 +124,4 @@ public: AnimationPath getBoatAnimationName() const; }; -class MarketInstanceConstructor : public CDefaultObjectTypeHandler -{ - std::string descriptionTextID; - std::string speechTextID; - - std::set marketModes; - JsonNode predefinedOffer; - int marketEfficiency; - - void initTypeData(const JsonNode & config) override; -public: - std::shared_ptr createObject(IGameInfoCallback * cb) const override; - void randomizeObject(CGMarket * object, IGameRandomizer & gameRandomizer) 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/mapObjectConstructors/MarketInstanceConstructor.cpp b/lib/mapObjectConstructors/MarketInstanceConstructor.cpp new file mode 100644 index 000000000..d8e076c2d --- /dev/null +++ b/lib/mapObjectConstructors/MarketInstanceConstructor.cpp @@ -0,0 +1,105 @@ +/* +* MarketInstanceConstructor.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 "MarketInstanceConstructor.h" + +#include "../CConfigHandler.h" +#include "../GameLibrary.h" +#include "../constants/StringConstants.h" +#include "../json/JsonRandom.h" +#include "../json/JsonUtils.h" +#include "../texts/CGeneralTextHandler.h" +#include "../texts/TextIdentifier.h" + +void MarketInstanceConstructor::initTypeData(const JsonNode & input) +{ + if (settings["mods"]["validation"].String() != "off") + JsonUtils::validate(input, "vcmi:market", getJsonKey()); + + if (!input["description"].isNull()) + { + std::string description = input["description"].String(); + descriptionTextID = TextIdentifier(getBaseTextID(), "description").get(); + LIBRARY->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(); + LIBRARY->generaltexth->registerString( input.getModScope(), speechTextID, input["speech"]); + } + } + + for(auto & element : input["modes"].Vector()) + { + if(MappedKeys::MARKET_NAMES_TO_TYPES.count(element.String())) + marketModes.insert(MappedKeys::MARKET_NAMES_TO_TYPES.at(element.String())); + } + + marketEfficiency = input["efficiency"].isNull() ? 5 : input["efficiency"].Integer(); + predefinedOffer = input["offer"]; +} + +bool MarketInstanceConstructor::hasDescription() const +{ + return !descriptionTextID.empty(); +} + +std::shared_ptr MarketInstanceConstructor::createObject(IGameInfoCallback * cb) const +{ + if(marketModes.size() == 1) + { + switch(*marketModes.begin()) + { + case EMarketMode::ARTIFACT_RESOURCE: + case EMarketMode::RESOURCE_ARTIFACT: + return std::make_shared(cb); + + case EMarketMode::RESOURCE_SKILL: + return std::make_shared(cb); + } + } + return std::make_shared(cb); +} + +const std::set & MarketInstanceConstructor::availableModes() const +{ + return marketModes; +} + +void MarketInstanceConstructor::randomizeObject(CGMarket * object, IGameRandomizer & gameRandomizer) const +{ + JsonRandom randomizer(object->cb, gameRandomizer); + JsonRandom::Variables emptyVariables; + + if(auto * university = dynamic_cast(object)) + { + for(auto skill : randomizer.loadSecondaries(predefinedOffer, emptyVariables)) + university->skills.push_back(skill.first); + } +} + +std::string MarketInstanceConstructor::getSpeechTranslated() const +{ + assert(marketModes.count(EMarketMode::RESOURCE_SKILL)); + return LIBRARY->generaltexth->translate(speechTextID); +} + +int MarketInstanceConstructor::getMarketEfficiency() const +{ + return marketEfficiency; +} diff --git a/lib/mapObjectConstructors/MarketInstanceConstructor.h b/lib/mapObjectConstructors/MarketInstanceConstructor.h new file mode 100644 index 000000000..2858b3a86 --- /dev/null +++ b/lib/mapObjectConstructors/MarketInstanceConstructor.h @@ -0,0 +1,39 @@ +/* +* MarketInstanceConstructor.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 "CDefaultObjectTypeHandler.h" +#include "../mapObjects/CGMarket.h" +#include "../json/JsonNode.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class MarketInstanceConstructor : public CDefaultObjectTypeHandler +{ + std::string descriptionTextID; + std::string speechTextID; + + std::set marketModes; + JsonNode predefinedOffer; + int marketEfficiency; + + void initTypeData(const JsonNode & config) override; +public: + std::shared_ptr createObject(IGameInfoCallback * cb) const override; + void randomizeObject(CGMarket * object, IGameRandomizer & gameRandomizer) 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 8fadfdc2f..e4cc0641f 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -11,19 +11,21 @@ #include "StdInc.h" #include "CGMarket.h" -#include "../callback/IGameInfoCallback.h" -#include "../callback/IGameEventCallback.h" -#include "../callback/IGameRandomizer.h" -#include "../texts/CGeneralTextHandler.h" -#include "../CCreatureHandler.h" +#include "CGHeroInstance.h" #include "CGTownInstance.h" + +//#include "../CCreatureHandler.h" +#include "../CPlayerState.h" +//#include "../CSkillHandler.h" #include "../IGameSettings.h" -#include "../CSkillHandler.h" +#include "../callback/IGameEventCallback.h" +#include "../callback/IGameInfoCallback.h" +#include "../callback/IGameRandomizer.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" -#include "../mapObjectConstructors/CommonConstructors.h" +#include "../mapObjectConstructors/MarketInstanceConstructor.h" #include "../networkPacks/PacksForClient.h" -#include "CPlayerState.h" +#include "../texts/TextIdentifier.h" VCMI_LIB_NAMESPACE_BEGIN From 283230eb4ef52030da7ec0f2090773f6dbd66ca6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 21 Jul 2025 19:52:05 +0300 Subject: [PATCH 3/3] Add description for University using H3 string --- config/objects/markets.json | 3 ++- .../MarketInstanceConstructor.cpp | 20 +++++++++++++++++-- .../MarketInstanceConstructor.h | 1 + lib/mapObjects/CGMarket.cpp | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/config/objects/markets.json b/config/objects/markets.json index ea5c517b9..9a1357277 100644 --- a/config/objects/markets.json +++ b/config/objects/markets.json @@ -124,6 +124,7 @@ "rarity" : 20 }, "modes" : ["resource-skill"], + "description" : "@core.xtrainfo.24", "speech" : "@core.genrltxt.603", "offer": [ @@ -135,4 +136,4 @@ } } } -} \ No newline at end of file +} diff --git a/lib/mapObjectConstructors/MarketInstanceConstructor.cpp b/lib/mapObjectConstructors/MarketInstanceConstructor.cpp index d8e076c2d..a008c0108 100644 --- a/lib/mapObjectConstructors/MarketInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/MarketInstanceConstructor.cpp @@ -18,6 +18,8 @@ #include "../texts/CGeneralTextHandler.h" #include "../texts/TextIdentifier.h" +VCMI_LIB_NAMESPACE_BEGIN + void MarketInstanceConstructor::initTypeData(const JsonNode & input) { if (settings["mods"]["validation"].String() != "off") @@ -26,8 +28,15 @@ void MarketInstanceConstructor::initTypeData(const JsonNode & input) if (!input["description"].isNull()) { std::string description = input["description"].String(); - descriptionTextID = TextIdentifier(getBaseTextID(), "description").get(); - LIBRARY->generaltexth->registerString( input.getModScope(), descriptionTextID, input["description"]); + if (!description.empty() && description.at(0) == '@') + { + descriptionTextID = description.substr(1); + } + else + { + descriptionTextID = TextIdentifier(getBaseTextID(), "description").get(); + LIBRARY->generaltexth->registerString( input.getModScope(), descriptionTextID, input["description"]); + } } if (!input["speech"].isNull()) @@ -59,6 +68,11 @@ bool MarketInstanceConstructor::hasDescription() const return !descriptionTextID.empty(); } +std::string MarketInstanceConstructor::getDescriptionTextID() const +{ + return descriptionTextID; +} + std::shared_ptr MarketInstanceConstructor::createObject(IGameInfoCallback * cb) const { if(marketModes.size() == 1) @@ -103,3 +117,5 @@ int MarketInstanceConstructor::getMarketEfficiency() const { return marketEfficiency; } + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjectConstructors/MarketInstanceConstructor.h b/lib/mapObjectConstructors/MarketInstanceConstructor.h index 2858b3a86..5d6741791 100644 --- a/lib/mapObjectConstructors/MarketInstanceConstructor.h +++ b/lib/mapObjectConstructors/MarketInstanceConstructor.h @@ -31,6 +31,7 @@ public: const std::set & availableModes() const; bool hasDescription() const; + std::string getDescriptionTextID() const; std::string getSpeechTranslated() const; int getMarketEfficiency() const; diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index e4cc0641f..6bcb47c1f 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -51,7 +51,7 @@ std::string CGMarket::getPopupText(PlayerColor player) const MetaString message = MetaString::createFromRawString("{%s}\r\n\r\n%s"); message.replaceName(ID, subID); - message.replaceTextID(TextIdentifier(getObjectHandler()->getBaseTextID(), "description").get()); + message.replaceTextID(getMarketHandler()->getDescriptionTextID()); return message.toString(); }