1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-04-13 11:40:38 +02:00

Fixes for configurable markets support

- string "speech" can now be translated
- removed "title" string, VCMI will now use object name instead
- moved configuration of all "markets" into a separate json file
- added schema for validation of market objects
- removed serialization of translated strings from University
This commit is contained in:
Ivan Savenko 2024-11-19 14:38:27 +00:00
parent f0a71c9e21
commit f59834afe1
10 changed files with 252 additions and 179 deletions

View File

@ -951,8 +951,8 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID bu
else if(auto uni = dynamic_cast<const CGUniversity *>(_market); uni->appearance)
{
titlePic = std::make_shared<CAnimImage>(uni->appearance->animationFile, 0, 0, 0, 0, CShowableAnim::CREATURE_MODE);
titleStr = uni->title;
speechStr = uni->speech;
titleStr = uni->getObjectName();
speechStr = uni->getSpeechTranslated();
}
else
{

View File

@ -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",

View File

@ -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",

138
config/objects/markets.json Normal file
View File

@ -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"] }
]
}
}
}
}

View File

@ -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" : { }
}
}

View File

@ -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<CGUniversity*>(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<EMarketMode> & 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

View File

@ -115,25 +115,23 @@ public:
class MarketInstanceConstructor : public CDefaultObjectTypeHandler<CGMarket>
{
protected:
void initTypeData(const JsonNode & config) override;
std::string descriptionTextID;
std::string speechTextID;
std::set<EMarketMode> 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<EMarketMode> & availableModes() const;
bool hasDescription() const;
std::string getSpeechTranslated() const;
int getMarketEfficiency() const;
};
VCMI_LIB_NAMESPACE_END

View File

@ -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<TradeItemBuy> 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);

View File

@ -19,11 +19,10 @@ class MarketInstanceConstructor;
class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket
{
protected:
std::shared_ptr<MarketInstanceConstructor> 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<TradeItemBuy> skills; //available skills
@ -115,10 +119,11 @@ public:
{
h & static_cast<CGMarket&>(*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;
}
}
};

View File

@ -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
};