mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-10 22:31:40 +02:00
Merge branch 'develop' into adv_search
This commit is contained in:
@@ -394,7 +394,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
{
|
||||
std::set<BattleHex> obstacleHexes;
|
||||
|
||||
auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> obstacleHexes) {
|
||||
auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> & obstacleHexes) {
|
||||
auto affectedHexes = spellObst.getAffectedTiles();
|
||||
obstacleHexes.insert(affectedHexes.cbegin(), affectedHexes.cend());
|
||||
};
|
||||
|
@@ -486,11 +486,7 @@ if(NOT FORCE_BUNDLED_MINIZIP)
|
||||
endif()
|
||||
|
||||
if (ENABLE_CLIENT)
|
||||
set(FFMPEG_COMPONENTS avutil swscale avformat avcodec)
|
||||
if(APPLE_IOS AND NOT USING_CONAN)
|
||||
list(APPEND FFMPEG_COMPONENTS swresample)
|
||||
endif()
|
||||
find_package(ffmpeg COMPONENTS ${FFMPEG_COMPONENTS})
|
||||
find_package(ffmpeg COMPONENTS avutil swscale avformat avcodec swresample)
|
||||
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(SDL2_image REQUIRED)
|
||||
|
@@ -12,7 +12,9 @@
|
||||
"vcmi.adventureMap.monsterThreat.levels.9" : "压倒性的",
|
||||
"vcmi.adventureMap.monsterThreat.levels.10" : "致命的",
|
||||
"vcmi.adventureMap.monsterThreat.levels.11" : "无法取胜",
|
||||
"vcmi.adventureMap.monsterLevel" : "\n\n%TOWN%LEVEL级生物",
|
||||
"vcmi.adventureMap.monsterLevel" : "\n\n%TOWN%LEVEL级%ATTACK_TYPE生物",
|
||||
"vcmi.adventureMap.monsterMeleeType" : "近战",
|
||||
"vcmi.adventureMap.monsterRangedType" : "远程",
|
||||
|
||||
"vcmi.adventureMap.confirmRestartGame" : "你想要重新开始游戏吗?",
|
||||
"vcmi.adventureMap.noTownWithMarket" : "没有足够的市场。",
|
||||
|
@@ -12,7 +12,9 @@
|
||||
"vcmi.adventureMap.monsterThreat.levels.9" : "Overpowering",
|
||||
"vcmi.adventureMap.monsterThreat.levels.10" : "Deadly",
|
||||
"vcmi.adventureMap.monsterThreat.levels.11" : "Impossible",
|
||||
"vcmi.adventureMap.monsterLevel" : "\n\nLevel %LEVEL %TOWN unit",
|
||||
"vcmi.adventureMap.monsterLevel" : "\n\nLevel %LEVEL %TOWN %ATTACK_TYPE unit",
|
||||
"vcmi.adventureMap.monsterMeleeType" : "melee",
|
||||
"vcmi.adventureMap.monsterRangedType" : "ranged",
|
||||
"vcmi.adventureMap.search.hover" : "Search map object",
|
||||
"vcmi.adventureMap.search.help" : "Select object to search on map.",
|
||||
|
||||
@@ -145,6 +147,7 @@
|
||||
"vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s",
|
||||
"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
|
||||
"vcmi.server.errors.disconnected" : "{Network Error}\n\nConnection to game server has been lost!",
|
||||
"vcmi.server.errors.playerLeft" : "{Player Left}\n\n%s player have disconnected from the game!", //%s -> player color
|
||||
"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
|
||||
"vcmi.server.errors.modsToEnable" : "{Following mods are required}",
|
||||
"vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}",
|
||||
|
@@ -12,7 +12,9 @@
|
||||
"vcmi.adventureMap.monsterThreat.levels.9" : "Überwältigend",
|
||||
"vcmi.adventureMap.monsterThreat.levels.10" : "Tödlich",
|
||||
"vcmi.adventureMap.monsterThreat.levels.11" : "Unmöglich",
|
||||
"vcmi.adventureMap.monsterLevel" : "\n\nStufe %LEVEL %TOWN-Einheit",
|
||||
"vcmi.adventureMap.monsterLevel" : "\n\nStufe %LEVEL %TOWN-Einheit (%ATTACK_TYPE)",
|
||||
"vcmi.adventureMap.monsterMeleeType" : "Nahkampf",
|
||||
"vcmi.adventureMap.monsterRangedType" : "Fernkampf",
|
||||
"vcmi.adventureMap.search.hover" : "Suche Kartenobjekt",
|
||||
"vcmi.adventureMap.search.help" : "Wähle Objekt das gesucht werden soll.",
|
||||
|
||||
|
@@ -33,7 +33,9 @@ extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
// Define a set of functions to read data
|
||||
@@ -501,32 +503,71 @@ std::pair<std::unique_ptr<ui8 []>, si64> CAudioInstance::extractAudio(const Vide
|
||||
int numChannels = codecpar->ch_layout.nb_channels;
|
||||
#endif
|
||||
|
||||
samples.reserve(44100 * 5); // arbitrary 5-second buffer
|
||||
samples.reserve(44100 * 5); // arbitrary 5-second buffer to reduce reallocations
|
||||
|
||||
for (;;)
|
||||
if (formatProperties.isPlanar && numChannels > 1)
|
||||
{
|
||||
decodeNextFrame();
|
||||
const AVFrame * frame = getCurrentFrame();
|
||||
// Format is 'planar', which is not supported by wav / SDL
|
||||
// Use swresample part of ffmpeg to deplanarize audio into format supported by wav / SDL
|
||||
|
||||
if (!frame)
|
||||
break;
|
||||
auto sourceFormat = static_cast<AVSampleFormat>(codecpar->format);
|
||||
auto targetFormat = av_get_alt_sample_fmt(sourceFormat, false);
|
||||
|
||||
int samplesToRead = frame->nb_samples * numChannels;
|
||||
int bytesToRead = samplesToRead * formatProperties.sampleSizeBytes;
|
||||
SwrContext * swr_ctx = swr_alloc();
|
||||
|
||||
if (formatProperties.isPlanar && numChannels > 1)
|
||||
#if (LIBAVUTIL_VERSION_MAJOR < 58)
|
||||
av_opt_set_channel_layout(swr_ctx, "in_chlayout", codecpar->channel_layout, 0);
|
||||
av_opt_set_channel_layout(swr_ctx, "out_chlayout", codecpar->channel_layout, 0);
|
||||
#else
|
||||
av_opt_set_chlayout(swr_ctx, "in_chlayout", &codecpar->ch_layout, 0);
|
||||
av_opt_set_chlayout(swr_ctx, "out_chlayout", &codecpar->ch_layout, 0);
|
||||
#endif
|
||||
av_opt_set_int(swr_ctx, "in_sample_rate", codecpar->sample_rate, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", sourceFormat, 0);
|
||||
av_opt_set_int(swr_ctx, "out_sample_rate", codecpar->sample_rate, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", targetFormat, 0);
|
||||
|
||||
int initResult = swr_init(swr_ctx);
|
||||
if (initResult < 0)
|
||||
throwFFmpegError(initResult);
|
||||
|
||||
std::vector<uint8_t> frameSamplesBuffer;
|
||||
for (;;)
|
||||
{
|
||||
// Workaround for lack of resampler
|
||||
// Currently, ffmpeg on conan systems is built without sws resampler
|
||||
// Because of that, and because wav format does not supports 'planar' formats from ffmpeg
|
||||
// we need to de-planarize it and convert to "normal" (non-planar / interleaved) stream
|
||||
samples.reserve(samples.size() + bytesToRead);
|
||||
for (int sm = 0; sm < frame->nb_samples; ++sm)
|
||||
for (int ch = 0; ch < numChannels; ++ch)
|
||||
samples.insert(samples.end(), frame->data[ch] + sm * formatProperties.sampleSizeBytes, frame->data[ch] + (sm+1) * formatProperties.sampleSizeBytes );
|
||||
decodeNextFrame();
|
||||
const AVFrame * frame = getCurrentFrame();
|
||||
|
||||
if (!frame)
|
||||
break;
|
||||
|
||||
size_t samplesToRead = frame->nb_samples * numChannels;
|
||||
size_t bytesToRead = samplesToRead * formatProperties.sampleSizeBytes;
|
||||
frameSamplesBuffer.resize(std::max(frameSamplesBuffer.size(), bytesToRead));
|
||||
uint8_t * frameSamplesPtr = frameSamplesBuffer.data();
|
||||
|
||||
int result = swr_convert(swr_ctx, &frameSamplesPtr, frame->nb_samples, (const uint8_t **)frame->data, frame->nb_samples);
|
||||
|
||||
if (result < 0)
|
||||
throwFFmpegError(result);
|
||||
|
||||
size_t samplesToCopy = result * numChannels;
|
||||
size_t bytesToCopy = samplesToCopy * formatProperties.sampleSizeBytes;
|
||||
samples.insert(samples.end(), frameSamplesBuffer.begin(), frameSamplesBuffer.begin() + bytesToCopy);
|
||||
}
|
||||
else
|
||||
swr_free(&swr_ctx);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
decodeNextFrame();
|
||||
const AVFrame * frame = getCurrentFrame();
|
||||
|
||||
if (!frame)
|
||||
break;
|
||||
|
||||
size_t samplesToRead = frame->nb_samples * numChannels;
|
||||
size_t bytesToRead = samplesToRead * formatProperties.sampleSizeBytes;
|
||||
samples.insert(samples.end(), frame->data[0], frame->data[0] + bytesToRead);
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,10 @@
|
||||
#include "../render/IRenderHandler.h"
|
||||
|
||||
#include "../lib/filesystem/Filesystem.h"
|
||||
#include "../lib/GameSettings.h"
|
||||
#include "../lib/IGameSettings.h"
|
||||
#include "../lib/json/JsonNode.h"
|
||||
#include "../lib/VCMI_Lib.h"
|
||||
|
||||
void AssetGenerator::generateAll()
|
||||
{
|
||||
@@ -138,16 +142,17 @@ void AssetGenerator::createPlayerColoredBackground(const PlayerColor & player)
|
||||
|
||||
std::shared_ptr<IImage> texture = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE);
|
||||
|
||||
// Color transform to make color of brown DIBOX.PCX texture match color of specified player
|
||||
// transform to make color of brown DIBOX.PCX texture match color of specified player
|
||||
auto filterSettings = VLC->settingsHandler->getFullConfig()["interface"]["playerColoredBackground"];
|
||||
static const std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = {
|
||||
ColorFilter::genRangeShifter( 0.25, 0, 0, 1.25, 0.00, 0.00 ), // red
|
||||
ColorFilter::genRangeShifter( 0, 0, 0, 0.45, 1.20, 4.50 ), // blue
|
||||
ColorFilter::genRangeShifter( 0.40, 0.27, 0.23, 1.10, 1.20, 1.15 ), // tan
|
||||
ColorFilter::genRangeShifter( -0.27, 0.10, -0.27, 0.70, 1.70, 0.70 ), // green
|
||||
ColorFilter::genRangeShifter( 0.47, 0.17, -0.27, 1.60, 1.20, 0.70 ), // orange
|
||||
ColorFilter::genRangeShifter( 0.12, -0.1, 0.25, 1.15, 1.20, 2.20 ), // purple
|
||||
ColorFilter::genRangeShifter( -0.13, 0.23, 0.23, 0.90, 1.20, 2.20 ), // teal
|
||||
ColorFilter::genRangeShifter( 0.44, 0.15, 0.25, 1.00, 1.00, 1.75 ) // pink
|
||||
ColorFilter::genRangeShifter( filterSettings["red" ].convertTo<std::vector<float>>() ),
|
||||
ColorFilter::genRangeShifter( filterSettings["blue" ].convertTo<std::vector<float>>() ),
|
||||
ColorFilter::genRangeShifter( filterSettings["tan" ].convertTo<std::vector<float>>() ),
|
||||
ColorFilter::genRangeShifter( filterSettings["green" ].convertTo<std::vector<float>>() ),
|
||||
ColorFilter::genRangeShifter( filterSettings["orange"].convertTo<std::vector<float>>() ),
|
||||
ColorFilter::genRangeShifter( filterSettings["purple"].convertTo<std::vector<float>>() ),
|
||||
ColorFilter::genRangeShifter( filterSettings["teal" ].convertTo<std::vector<float>>() ),
|
||||
ColorFilter::genRangeShifter( filterSettings["pink" ].convertTo<std::vector<float>>() )
|
||||
};
|
||||
|
||||
assert(player.isValidPlayer());
|
||||
|
@@ -70,6 +70,13 @@ ColorFilter ColorFilter::genRangeShifter( float minR, float minG, float minB, fl
|
||||
1.f);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genRangeShifter( std::vector<float> parameters )
|
||||
{
|
||||
assert(std::size(parameters) == 6);
|
||||
|
||||
return genRangeShifter(parameters[0], parameters[1], parameters[2], parameters[3], parameters[4], parameters[5]);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a )
|
||||
{
|
||||
return ColorFilter(r, g, b, a);
|
||||
|
@@ -44,6 +44,7 @@ public:
|
||||
|
||||
/// Generates object that transforms each channel independently
|
||||
static ColorFilter genRangeShifter( float minR, float minG, float minB, float maxR, float maxG, float maxB );
|
||||
static ColorFilter genRangeShifter( std::vector<float> parameters );
|
||||
|
||||
/// Generates object that performs arbitrary mixing between any channels
|
||||
static ColorFilter genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a );
|
||||
|
@@ -548,11 +548,12 @@ void CTavernWindow::addInvite()
|
||||
|
||||
if(!inviteableHeroes.empty())
|
||||
{
|
||||
int imageIndex = heroToInvite ? (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex : 156; // 156 => special id for random
|
||||
if(!heroToInvite)
|
||||
heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second;
|
||||
|
||||
inviteHero = std::make_shared<CLabel>(170, 444, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.tavernWindow.inviteHero"));
|
||||
inviteHeroImage = std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex, 0, 245, 428);
|
||||
inviteHeroImage = std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), imageIndex, 0, 245, 428);
|
||||
inviteHeroImageArea = std::make_shared<LRClickableArea>(Rect(245, 428, 48, 32), [this](){ GH.windows().createAndPushWindow<HeroSelector>(inviteableHeroes, [this](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [this](){ GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(heroToInvite)); });
|
||||
}
|
||||
}
|
||||
|
@@ -555,6 +555,22 @@
|
||||
"valueType" : "BASE_NUMBER"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"interface" :
|
||||
{
|
||||
// Color transform to make color of brown DIBOX.PCX texture match color of specified player
|
||||
"playerColoredBackground" :
|
||||
{
|
||||
"red" : [ 0.25, 0, 0, 1.25, 0.00, 0.00 ],
|
||||
"blue" : [ 0, 0, 0, 0.45, 1.20, 4.50 ],
|
||||
"tan" : [ 0.40, 0.27, 0.23, 1.10, 1.20, 1.15 ],
|
||||
"green" : [ -0.27, 0.10, -0.27, 0.70, 1.70, 0.70 ],
|
||||
"orange" : [ 0.47, 0.17, -0.27, 1.60, 1.20, 0.70 ],
|
||||
"purple" : [ 0.12, -0.1, 0.25, 1.15, 1.20, 2.20 ],
|
||||
"teal" : [ -0.13, 0.23, 0.23, 0.90, 1.20, 2.20 ],
|
||||
"pink" : [ 0.44, 0.15, 0.25, 1.00, 1.00, 1.75 ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -148,5 +148,12 @@
|
||||
"perHero" : { "type" : "object" }
|
||||
}
|
||||
},
|
||||
"interface": {
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"properties" : {
|
||||
"playerColoredBackground" : { "type" : "object" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,17 @@
|
||||
"description" : "Format used to define main mod file (mod.json) in VCMI",
|
||||
"required" : [ "name", "description", "modType", "version", "author", "contact" ],
|
||||
"definitions" : {
|
||||
"fileListOrObject" : {
|
||||
"oneOf" : [
|
||||
{
|
||||
"type" : "array",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
},
|
||||
{
|
||||
"type" : "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"localizable" : {
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
@@ -35,9 +46,8 @@
|
||||
"description" : "If set to true, vcmi will skip validation of current translation json files"
|
||||
},
|
||||
"translations" : {
|
||||
"type" : "array",
|
||||
"description" : "List of files with translations for this language",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,9 +132,17 @@
|
||||
"description" : "If set to true, mod will not be enabled automatically on install"
|
||||
},
|
||||
"settings" : {
|
||||
"type" : "object",
|
||||
"description" : "List of changed game settings by mod",
|
||||
"$ref" : "gameSettings.json"
|
||||
"oneOf" : [
|
||||
{
|
||||
"type" : "object",
|
||||
"$ref" : "gameSettings.json"
|
||||
},
|
||||
{
|
||||
"type" : "array",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
},
|
||||
]
|
||||
},
|
||||
"filesystem" : {
|
||||
"type" : "object",
|
||||
@@ -206,94 +224,76 @@
|
||||
"$ref" : "#/definitions/localizable"
|
||||
},
|
||||
"translations" : {
|
||||
"type" : "array",
|
||||
"description" : "List of files with translations for this language",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"factions" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for towns/factions",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"heroClasses" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for hero classes",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"heroes" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for heroes",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"skills" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for skills",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"creatures" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for creatures",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"artifacts" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for artifacts",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"spells" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for spells",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"objects" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for objects",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"biomes" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for biomes",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"bonuses" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for bonuses",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"terrains" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for terrains",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"roads" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for roads",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"rivers" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for rivers",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"battlefields" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for battlefields",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"obstacles" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for obstacles",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"templates" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for RMG templates",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
},
|
||||
"scripts" : {
|
||||
"type" : "array",
|
||||
"description" : "List of configuration files for scripts",
|
||||
"items" : { "type" : "string", "format" : "textFile" }
|
||||
"$ref" : "#/definitions/fileListOrObject"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -90,8 +90,9 @@ These are fields that are present only in local mod.json file
|
||||
{
|
||||
// Following section describes configuration files with content added by mod
|
||||
// It can be split into several files in any way you want but recommended organization is
|
||||
// to keep one file per object (creature/hero/etc) and, if applicable, add separate file
|
||||
// with translatable strings for each type of content
|
||||
// to keep one file per object (creature/hero/etc)
|
||||
// Alternatively, for small changes you can embed changes to content directly in here, e.g.
|
||||
// "creatures" : { "core:imp" : { "health" : 5 }}
|
||||
|
||||
// list of factions/towns configuration files
|
||||
"factions" :
|
||||
|
@@ -431,9 +431,9 @@ std::shared_ptr<CArtifact> CArtHandler::loadFromJson(const std::string & scope,
|
||||
|
||||
const JsonNode & text = node["text"];
|
||||
|
||||
VLC->generaltexth->registerString(scope, art->getNameTextID(), text["name"].String());
|
||||
VLC->generaltexth->registerString(scope, art->getDescriptionTextID(), text["description"].String());
|
||||
VLC->generaltexth->registerString(scope, art->getEventTextID(), text["event"].String());
|
||||
VLC->generaltexth->registerString(scope, art->getNameTextID(), text["name"]);
|
||||
VLC->generaltexth->registerString(scope, art->getDescriptionTextID(), text["description"]);
|
||||
VLC->generaltexth->registerString(scope, art->getEventTextID(), text["event"]);
|
||||
|
||||
const JsonNode & graphics = node["graphics"];
|
||||
art->image = graphics["image"].String();
|
||||
|
@@ -200,8 +200,9 @@ ImagePath CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bonu
|
||||
|
||||
void CBonusTypeHandler::load()
|
||||
{
|
||||
const JsonNode gameConf(JsonPath::builtin("config/gameConfig.json"));
|
||||
const JsonNode config(JsonUtils::assembleFromFiles(gameConf["bonuses"].convertTo<std::vector<std::string>>()));
|
||||
JsonNode gameConf(JsonPath::builtin("config/gameConfig.json"));
|
||||
JsonNode config(JsonUtils::assembleFromFiles(gameConf["bonuses"].convertTo<std::vector<std::string>>()));
|
||||
config.setModScope("vcmi");
|
||||
load(config);
|
||||
}
|
||||
|
||||
@@ -240,8 +241,8 @@ void CBonusTypeHandler::loadItem(const JsonNode & source, CBonusType & dest, con
|
||||
|
||||
if (!dest.hidden)
|
||||
{
|
||||
VLC->generaltexth->registerString( "vcmi", dest.getNameTextID(), source["name"].String());
|
||||
VLC->generaltexth->registerString( "vcmi", dest.getDescriptionTextID(), source["description"].String());
|
||||
VLC->generaltexth->registerString( "vcmi", dest.getNameTextID(), source["name"]);
|
||||
VLC->generaltexth->registerString( "vcmi", dest.getDescriptionTextID(), source["description"]);
|
||||
}
|
||||
|
||||
const JsonNode & graphics = source["graphics"];
|
||||
|
@@ -617,9 +617,9 @@ std::shared_ptr<CCreature> CCreatureHandler::loadFromJson(const std::string & sc
|
||||
|
||||
cre->cost = ResourceSet(node["cost"]);
|
||||
|
||||
VLC->generaltexth->registerString(scope, cre->getNameSingularTextID(), node["name"]["singular"].String());
|
||||
VLC->generaltexth->registerString(scope, cre->getNamePluralTextID(), node["name"]["plural"].String());
|
||||
VLC->generaltexth->registerString(scope, cre->getDescriptionTextID(), node["description"].String());
|
||||
VLC->generaltexth->registerString(scope, cre->getNameSingularTextID(), node["name"]["singular"]);
|
||||
VLC->generaltexth->registerString(scope, cre->getNamePluralTextID(), node["name"]["plural"]);
|
||||
VLC->generaltexth->registerString(scope, cre->getDescriptionTextID(), node["description"]);
|
||||
|
||||
cre->addBonus(node["hitPoints"].Integer(), BonusType::STACK_HEALTH);
|
||||
cre->addBonus(node["speed"].Integer(), BonusType::STACKS_SPEED);
|
||||
|
@@ -855,7 +855,7 @@ std::string CStackInstance::getName() const
|
||||
ui64 CStackInstance::getPower() const
|
||||
{
|
||||
assert(type);
|
||||
return type->getAIValue() * count;
|
||||
return static_cast<ui64>(type->getAIValue()) * count;
|
||||
}
|
||||
|
||||
ArtBearer::ArtBearer CStackInstance::bearerType() const
|
||||
|
@@ -964,7 +964,7 @@ std::vector<ObjectInstanceID> CGameInfoCallback::getVisibleTeleportObjects(std::
|
||||
vstd::erase_if(ids, [&](const ObjectInstanceID & id) -> bool
|
||||
{
|
||||
const auto * obj = getObj(id, false);
|
||||
return player != PlayerColor::UNFLAGGABLE && (!obj || !isVisible(obj->pos, player));
|
||||
return player != PlayerColor::UNFLAGGABLE && (!obj || !isVisible(obj->visitablePos(), player));
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
@@ -459,11 +459,11 @@ std::shared_ptr<CHero> CHeroHandler::loadFromJson(const std::string & scope, con
|
||||
hero->onlyOnWaterMap = node["onlyOnWaterMap"].Bool();
|
||||
hero->onlyOnMapWithoutWater = node["onlyOnMapWithoutWater"].Bool();
|
||||
|
||||
VLC->generaltexth->registerString(scope, hero->getNameTextID(), node["texts"]["name"].String());
|
||||
VLC->generaltexth->registerString(scope, hero->getBiographyTextID(), node["texts"]["biography"].String());
|
||||
VLC->generaltexth->registerString(scope, hero->getSpecialtyNameTextID(), node["texts"]["specialty"]["name"].String());
|
||||
VLC->generaltexth->registerString(scope, hero->getSpecialtyTooltipTextID(), node["texts"]["specialty"]["tooltip"].String());
|
||||
VLC->generaltexth->registerString(scope, hero->getSpecialtyDescriptionTextID(), node["texts"]["specialty"]["description"].String());
|
||||
VLC->generaltexth->registerString(scope, hero->getNameTextID(), node["texts"]["name"]);
|
||||
VLC->generaltexth->registerString(scope, hero->getBiographyTextID(), node["texts"]["biography"]);
|
||||
VLC->generaltexth->registerString(scope, hero->getSpecialtyNameTextID(), node["texts"]["specialty"]["name"]);
|
||||
VLC->generaltexth->registerString(scope, hero->getSpecialtyTooltipTextID(), node["texts"]["specialty"]["tooltip"]);
|
||||
VLC->generaltexth->registerString(scope, hero->getSpecialtyDescriptionTextID(), node["texts"]["specialty"]["description"]);
|
||||
|
||||
hero->iconSpecSmall = node["images"]["specialtySmall"].String();
|
||||
hero->iconSpecLarge = node["images"]["specialtyLarge"].String();
|
||||
|
@@ -212,7 +212,7 @@ std::shared_ptr<CSkill> CSkillHandler::loadFromJson(const std::string & scope, c
|
||||
|
||||
skill->onlyOnWaterMap = json["onlyOnWaterMap"].Bool();
|
||||
|
||||
VLC->generaltexth->registerString(scope, skill->getNameTextID(), json["name"].String());
|
||||
VLC->generaltexth->registerString(scope, skill->getNameTextID(), json["name"]);
|
||||
switch(json["gainChance"].getType())
|
||||
{
|
||||
case JsonNode::JsonType::DATA_INTEGER:
|
||||
@@ -237,7 +237,7 @@ std::shared_ptr<CSkill> CSkillHandler::loadFromJson(const std::string & scope, c
|
||||
skill->addNewBonus(bonus, level);
|
||||
}
|
||||
CSkill::LevelInfo & skillAtLevel = skill->at(level);
|
||||
VLC->generaltexth->registerString(scope, skill->getDescriptionTextID(level), levelNode["description"].String());
|
||||
VLC->generaltexth->registerString(scope, skill->getDescriptionTextID(level), levelNode["description"]);
|
||||
skillAtLevel.iconSmall = levelNode["images"]["small"].String();
|
||||
skillAtLevel.iconMedium = levelNode["images"]["medium"].String();
|
||||
skillAtLevel.iconLarge = levelNode["images"]["large"].String();
|
||||
|
@@ -101,6 +101,7 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
|
||||
{EGameSettings::TEXTS_TERRAIN, "textData", "terrain" },
|
||||
{EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" },
|
||||
{EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" },
|
||||
{EGameSettings::INTERFACE_PLAYER_COLORED_BACKGROUND, "interface", "playerColoredBackground" },
|
||||
};
|
||||
|
||||
void GameSettings::loadBase(const JsonNode & input)
|
||||
|
@@ -79,6 +79,7 @@ enum class EGameSettings
|
||||
TEXTS_TERRAIN,
|
||||
TOWNS_BUILDINGS_PER_TURN_CAP,
|
||||
TOWNS_STARTING_DWELLING_CHANCES,
|
||||
INTERFACE_PLAYER_COLORED_BACKGROUND,
|
||||
|
||||
OPTIONS_COUNT,
|
||||
OPTIONS_BEGIN = BONUSES_GLOBAL
|
||||
|
@@ -50,7 +50,7 @@ std::shared_ptr<RiverType> RiverTypeHandler::loadFromJson(
|
||||
info->paletteAnimation.push_back(element);
|
||||
}
|
||||
|
||||
VLC->generaltexth->registerString(scope, info->getNameTextID(), json["text"].String());
|
||||
VLC->generaltexth->registerString(scope, info->getNameTextID(), json["text"]);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
@@ -41,7 +41,7 @@ std::shared_ptr<RoadType> RoadTypeHandler::loadFromJson(
|
||||
info->shortIdentifier = json["shortIdentifier"].String();
|
||||
info->movementCost = json["moveCost"].Integer();
|
||||
|
||||
VLC->generaltexth->registerString(scope,info->getNameTextID(), json["text"].String());
|
||||
VLC->generaltexth->registerString(scope,info->getNameTextID(), json["text"]);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ std::shared_ptr<TerrainType> TerrainTypeHandler::loadFromJson( const std::string
|
||||
info->transitionRequired = json["transitionRequired"].Bool();
|
||||
info->terrainViewPatterns = json["terrainViewPatterns"].String();
|
||||
|
||||
VLC->generaltexth->registerString(scope, info->getNameTextID(), json["text"].String());
|
||||
VLC->generaltexth->registerString(scope, info->getNameTextID(), json["text"]);
|
||||
|
||||
const JsonVector & unblockedVec = json["minimapUnblocked"].Vector();
|
||||
info->minimapUnblocked =
|
||||
|
@@ -292,8 +292,8 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
|
||||
ret->modScope = source.getModScope();
|
||||
ret->town = town;
|
||||
|
||||
VLC->generaltexth->registerString(source.getModScope(), ret->getNameTextID(), source["name"].String());
|
||||
VLC->generaltexth->registerString(source.getModScope(), ret->getDescriptionTextID(), source["description"].String());
|
||||
VLC->generaltexth->registerString(source.getModScope(), ret->getNameTextID(), source["name"]);
|
||||
VLC->generaltexth->registerString(source.getModScope(), ret->getDescriptionTextID(), source["description"]);
|
||||
|
||||
ret->subId = vstd::find_or(MappedKeys::SPECIAL_BUILDINGS, source["type"].String(), BuildingSubID::NONE);
|
||||
ret->resources = TResources(source["cost"]);
|
||||
@@ -603,7 +603,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
|
||||
town->namesCount = 0;
|
||||
for(const auto & name : source["names"].Vector())
|
||||
{
|
||||
VLC->generaltexth->registerString(town->faction->modScope, town->getRandomNameTextID(town->namesCount), name.String());
|
||||
VLC->generaltexth->registerString(town->faction->modScope, town->getRandomNameTextID(town->namesCount), name);
|
||||
town->namesCount += 1;
|
||||
}
|
||||
|
||||
@@ -718,8 +718,8 @@ std::shared_ptr<CFaction> CTownHandler::loadFromJson(const std::string & scope,
|
||||
faction->modScope = scope;
|
||||
faction->identifier = identifier;
|
||||
|
||||
VLC->generaltexth->registerString(scope, faction->getNameTextID(), source["name"].String());
|
||||
VLC->generaltexth->registerString(scope, faction->getDescriptionTextID(), source["description"].String());
|
||||
VLC->generaltexth->registerString(scope, faction->getNameTextID(), source["name"]);
|
||||
VLC->generaltexth->registerString(scope, faction->getDescriptionTextID(), source["description"]);
|
||||
|
||||
faction->creatureBg120 = ImagePath::fromJson(source["creatureBackground"]["120px"]);
|
||||
faction->creatureBg130 = ImagePath::fromJson(source["creatureBackground"]["130px"]);
|
||||
|
@@ -230,6 +230,27 @@ void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base)
|
||||
std::swap(descendant, inheritedNode);
|
||||
}
|
||||
|
||||
JsonNode JsonUtils::assembleFromFiles(const JsonNode & files, bool & isValid)
|
||||
{
|
||||
if (files.isVector())
|
||||
{
|
||||
auto configList = files.convertTo<std::vector<std::string> >();
|
||||
JsonNode result = JsonUtils::assembleFromFiles(configList, isValid);
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
JsonNode JsonUtils::assembleFromFiles(const JsonNode & files)
|
||||
{
|
||||
bool isValid = false;
|
||||
return assembleFromFiles(files, isValid);
|
||||
}
|
||||
|
||||
JsonNode JsonUtils::assembleFromFiles(const std::vector<std::string> & files)
|
||||
{
|
||||
bool isValid = false;
|
||||
|
@@ -44,6 +44,8 @@ namespace JsonUtils
|
||||
* @brief generate one Json structure from multiple files
|
||||
* @param files - list of filenames with parts of json structure
|
||||
*/
|
||||
DLL_LINKAGE JsonNode assembleFromFiles(const JsonNode & files);
|
||||
DLL_LINKAGE JsonNode assembleFromFiles(const JsonNode & files, bool & isValid);
|
||||
DLL_LINKAGE JsonNode assembleFromFiles(const std::vector<std::string> & files);
|
||||
DLL_LINKAGE JsonNode assembleFromFiles(const std::vector<std::string> & files, bool & isValid);
|
||||
|
||||
|
@@ -28,7 +28,7 @@ void CBankInstanceConstructor::initTypeData(const JsonNode & input)
|
||||
if (input.Struct().count("name") == 0)
|
||||
logMod->warn("Bank %s missing name!", getJsonKey());
|
||||
|
||||
VLC->generaltexth->registerString(input.getModScope(), getNameTextID(), input["name"].String());
|
||||
VLC->generaltexth->registerString(input.getModScope(), getNameTextID(), input["name"]);
|
||||
|
||||
levels = input["levels"].Vector();
|
||||
bankResetDuration = static_cast<si32>(input["resetDuration"].Float());
|
||||
|
@@ -278,7 +278,7 @@ std::unique_ptr<ObjectClass> CObjectClassesHandler::loadFromJson(const std::stri
|
||||
newObject->base = json["base"];
|
||||
newObject->id = index;
|
||||
|
||||
VLC->generaltexth->registerString(scope, newObject->getNameTextID(), json["name"].String());
|
||||
VLC->generaltexth->registerString(scope, newObject->getNameTextID(), json["name"]);
|
||||
|
||||
newObject->objectTypeHandlers.resize(json["lastReservedIndex"].Float() + 1);
|
||||
|
||||
|
@@ -23,7 +23,7 @@ void CRewardableConstructor::initTypeData(const JsonNode & config)
|
||||
blockVisit = config["blockedVisitable"].Bool();
|
||||
|
||||
if (!config["name"].isNull())
|
||||
VLC->generaltexth->registerString( config.getModScope(), getNameTextID(), config["name"].String());
|
||||
VLC->generaltexth->registerString( config.getModScope(), getNameTextID(), config["name"]);
|
||||
|
||||
JsonUtils::validate(config, "vcmi:rewardable", getJsonKey());
|
||||
|
||||
@@ -43,9 +43,10 @@ CGObjectInstance * CRewardableConstructor::create(IGameCallback * cb, std::share
|
||||
return ret;
|
||||
}
|
||||
|
||||
Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID) const
|
||||
Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID, const std::map<std::string, JsonNode> & presetVariables) const
|
||||
{
|
||||
Rewardable::Configuration result;
|
||||
result.variables.preset = presetVariables;
|
||||
objectInfo.configureObject(result, rand, cb);
|
||||
|
||||
for(auto & rewardInfo : result.info)
|
||||
@@ -67,7 +68,7 @@ void CRewardableConstructor::configureObject(CGObjectInstance * object, vstd::RN
|
||||
if (!rewardableObject)
|
||||
throw std::runtime_error("Object " + std::to_string(object->getObjGroupIndex()) + ", " + std::to_string(object->getObjTypeIndex()) + " is not a rewardable object!" );
|
||||
|
||||
rewardableObject->configuration = generateConfiguration(object->cb, rng, object->ID);
|
||||
rewardableObject->configuration = generateConfiguration(object->cb, rng, object->ID, rewardableObject->configuration.variables.preset);
|
||||
rewardableObject->initializeGuards();
|
||||
|
||||
if (rewardableObject->configuration.info.empty())
|
||||
|
@@ -31,7 +31,7 @@ public:
|
||||
|
||||
std::unique_ptr<IObjectInfo> getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const override;
|
||||
|
||||
Rewardable::Configuration generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID) const;
|
||||
Rewardable::Configuration generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID, const std::map<std::string, JsonNode> & presetVariables) const;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@@ -29,7 +29,7 @@ void DwellingInstanceConstructor::initTypeData(const JsonNode & input)
|
||||
if (input.Struct().count("name") == 0)
|
||||
logMod->warn("Dwelling %s missing name!", getJsonKey());
|
||||
|
||||
VLC->generaltexth->registerString( input.getModScope(), getNameTextID(), input["name"].String());
|
||||
VLC->generaltexth->registerString( input.getModScope(), getNameTextID(), input["name"]);
|
||||
|
||||
const JsonVector & levels = input["creatures"].Vector();
|
||||
const auto totalLevels = levels.size();
|
||||
|
@@ -66,6 +66,18 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
|
||||
}
|
||||
}
|
||||
|
||||
std::string CGCreature::getMonsterLevelText() const
|
||||
{
|
||||
std::string monsterLevel = VLC->generaltexth->translate("vcmi.adventureMap.monsterLevel");
|
||||
bool isRanged = VLC->creatures()->getById(getCreature())->getBonusBearer()->hasBonusOfType(BonusType::SHOOTER);
|
||||
std::string attackTypeKey = isRanged ? "vcmi.adventureMap.monsterRangedType" : "vcmi.adventureMap.monsterMeleeType";
|
||||
std::string attackType = VLC->generaltexth->translate(attackTypeKey);
|
||||
boost::replace_first(monsterLevel, "%TOWN", (*VLC->townh)[VLC->creatures()->getById(getCreature())->getFaction()]->getNameTranslated());
|
||||
boost::replace_first(monsterLevel, "%LEVEL", std::to_string(VLC->creatures()->getById(getCreature())->getLevel()));
|
||||
boost::replace_first(monsterLevel, "%ATTACK_TYPE", attackType);
|
||||
return monsterLevel;
|
||||
}
|
||||
|
||||
std::string CGCreature::getPopupText(const CGHeroInstance * hero) const
|
||||
{
|
||||
std::string hoverName;
|
||||
@@ -102,15 +114,13 @@ std::string CGCreature::getPopupText(const CGHeroInstance * hero) const
|
||||
|
||||
if (settings["general"]["enableUiEnhancements"].Bool())
|
||||
{
|
||||
std::string monsterLevel = VLC->generaltexth->translate("vcmi.adventureMap.monsterLevel");
|
||||
boost::replace_first(monsterLevel, "%TOWN", (*VLC->townh)[VLC->creatures()->getById(getCreature())->getFaction()]->getNameTranslated());
|
||||
boost::replace_first(monsterLevel, "%LEVEL", std::to_string(VLC->creatures()->getById(getCreature())->getLevel()));
|
||||
hoverName += monsterLevel;
|
||||
|
||||
hoverName += getMonsterLevelText();
|
||||
hoverName += VLC->generaltexth->translate("vcmi.adventureMap.monsterThreat.title");
|
||||
|
||||
int choice;
|
||||
double ratio = (static_cast<double>(getArmyStrength()) / hero->getTotalStrength());
|
||||
uint64_t armyStrength = getArmyStrength();
|
||||
uint64_t heroStrength = hero->getTotalStrength();
|
||||
double ratio = static_cast<double>(armyStrength) / heroStrength;
|
||||
if (ratio < 0.1) choice = 0;
|
||||
else if (ratio < 0.25) choice = 1;
|
||||
else if (ratio < 0.6) choice = 2;
|
||||
@@ -131,7 +141,10 @@ std::string CGCreature::getPopupText(const CGHeroInstance * hero) const
|
||||
|
||||
std::string CGCreature::getPopupText(PlayerColor player) const
|
||||
{
|
||||
return getHoverText(player);
|
||||
std::string hoverName = getHoverText(player);
|
||||
if (settings["general"]["enableUiEnhancements"].Bool())
|
||||
hoverName += getMonsterLevelText();
|
||||
return hoverName;
|
||||
}
|
||||
|
||||
std::vector<Component> CGCreature::getPopupComponents(PlayerColor player) const
|
||||
|
@@ -81,7 +81,7 @@ private:
|
||||
|
||||
int takenAction(const CGHeroInstance *h, bool allowJoin=true) const; //action on confrontation: -2 - fight, -1 - flee, >=0 - will join for given value of gold (may be 0)
|
||||
void giveReward(const CGHeroInstance * h) const;
|
||||
|
||||
std::string getMonsterLevelText() const;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@@ -329,7 +329,7 @@ void CRewardableObject::newTurn(vstd::RNG & rand) const
|
||||
if (configuration.resetParameters.rewards)
|
||||
{
|
||||
auto handler = std::dynamic_pointer_cast<const CRewardableConstructor>(getObjectHandler());
|
||||
auto newConfiguration = handler->generateConfiguration(cb, rand, ID);
|
||||
auto newConfiguration = handler->generateConfiguration(cb, rand, ID, configuration.variables.preset);
|
||||
cb->setRewardableObjectConfiguration(id, newConfiguration);
|
||||
}
|
||||
if (configuration.resetParameters.visitors)
|
||||
|
@@ -189,7 +189,7 @@ void CMapHeader::registerMapStrings()
|
||||
JsonUtils::mergeCopy(data, translations[language]);
|
||||
|
||||
for(auto & s : data.Struct())
|
||||
texts.registerString("map", TextIdentifier(s.first), s.second.String(), language);
|
||||
texts.registerString("map", TextIdentifier(s.first), s.second.String());
|
||||
}
|
||||
|
||||
std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized)
|
||||
@@ -199,7 +199,7 @@ std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeade
|
||||
|
||||
std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized, const std::string & language)
|
||||
{
|
||||
mapHeader.texts.registerString(modContext, UID, localized, language);
|
||||
mapHeader.texts.registerString(modContext, UID, localized);
|
||||
mapHeader.translations.Struct()[language].Struct()[UID.get()].String() = localized;
|
||||
return UID.get();
|
||||
}
|
||||
|
@@ -384,7 +384,7 @@ std::set<TModID> CModHandler::getModDependencies(const TModID & modId, bool & is
|
||||
|
||||
void CModHandler::initializeConfig()
|
||||
{
|
||||
VLC->settingsHandler->loadBase(coreMod->config["settings"]);
|
||||
VLC->settingsHandler->loadBase(JsonUtils::assembleFromFiles(coreMod->config["settings"]));
|
||||
|
||||
for(const TModID & modName : activeMods)
|
||||
{
|
||||
@@ -401,33 +401,6 @@ CModVersion CModHandler::getModVersion(TModID modName) const
|
||||
return {};
|
||||
}
|
||||
|
||||
bool CModHandler::validateTranslations(TModID modName) const
|
||||
{
|
||||
bool result = true;
|
||||
const auto & mod = allMods.at(modName);
|
||||
|
||||
{
|
||||
auto fileList = mod.config["translations"].convertTo<std::vector<std::string> >();
|
||||
JsonNode json = JsonUtils::assembleFromFiles(fileList);
|
||||
result |= VLC->generaltexth->validateTranslation(mod.baseLanguage, modName, json);
|
||||
}
|
||||
|
||||
for(const auto & language : Languages::getLanguageList())
|
||||
{
|
||||
if (mod.config[language.identifier].isNull())
|
||||
continue;
|
||||
|
||||
if (mod.config[language.identifier]["skipValidation"].Bool())
|
||||
continue;
|
||||
|
||||
auto fileList = mod.config[language.identifier]["translations"].convertTo<std::vector<std::string> >();
|
||||
JsonNode json = JsonUtils::assembleFromFiles(fileList);
|
||||
result |= VLC->generaltexth->validateTranslation(language.identifier, modName, json);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void CModHandler::loadTranslation(const TModID & modName)
|
||||
{
|
||||
const auto & mod = allMods[modName];
|
||||
@@ -435,14 +408,11 @@ void CModHandler::loadTranslation(const TModID & modName)
|
||||
std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage();
|
||||
std::string modBaseLanguage = allMods[modName].baseLanguage;
|
||||
|
||||
auto baseTranslationList = mod.config["translations"].convertTo<std::vector<std::string> >();
|
||||
auto extraTranslationList = mod.config[preferredLanguage]["translations"].convertTo<std::vector<std::string> >();
|
||||
JsonNode baseTranslation = JsonUtils::assembleFromFiles(mod.config["translations"]);
|
||||
JsonNode extraTranslation = JsonUtils::assembleFromFiles(mod.config[preferredLanguage]["translations"]);
|
||||
|
||||
JsonNode baseTranslation = JsonUtils::assembleFromFiles(baseTranslationList);
|
||||
JsonNode extraTranslation = JsonUtils::assembleFromFiles(extraTranslationList);
|
||||
|
||||
VLC->generaltexth->loadTranslationOverrides(modBaseLanguage, modName, baseTranslation);
|
||||
VLC->generaltexth->loadTranslationOverrides(preferredLanguage, modName, extraTranslation);
|
||||
VLC->generaltexth->loadTranslationOverrides(modName, baseTranslation);
|
||||
VLC->generaltexth->loadTranslationOverrides(modName, extraTranslation);
|
||||
}
|
||||
|
||||
void CModHandler::load()
|
||||
@@ -480,12 +450,6 @@ void CModHandler::load()
|
||||
for(const TModID & modName : activeMods)
|
||||
loadTranslation(modName);
|
||||
|
||||
#if 0
|
||||
for(const TModID & modName : activeMods)
|
||||
if (!validateTranslations(modName))
|
||||
allMods[modName].validation = CModInfo::FAILED;
|
||||
#endif
|
||||
|
||||
logMod->info("\tLoading mod data: %d ms", timer.getDiff());
|
||||
VLC->creh->loadCrExpMod();
|
||||
VLC->identifiersHandler->finalize();
|
||||
|
@@ -49,8 +49,6 @@ class DLL_LINKAGE CModHandler final : boost::noncopyable
|
||||
void loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, bool enableMods);
|
||||
void loadTranslation(const TModID & modName);
|
||||
|
||||
bool validateTranslations(TModID modName) const;
|
||||
|
||||
CModVersion getModVersion(TModID modName) const;
|
||||
|
||||
public:
|
||||
|
@@ -50,7 +50,7 @@ ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string
|
||||
}
|
||||
}
|
||||
|
||||
bool ContentTypeHandler::preloadModData(const std::string & modName, const std::vector<std::string> & fileList, bool validate)
|
||||
bool ContentTypeHandler::preloadModData(const std::string & modName, const JsonNode & fileList, bool validate)
|
||||
{
|
||||
bool result = false;
|
||||
JsonNode data = JsonUtils::assembleFromFiles(fileList, result);
|
||||
@@ -216,7 +216,7 @@ bool CContentHandler::preloadModData(const std::string & modName, JsonNode modCo
|
||||
bool result = true;
|
||||
for(auto & handler : handlers)
|
||||
{
|
||||
result &= handler.second.preloadModData(modName, modConfig[handler.first].convertTo<std::vector<std::string> >(), validate);
|
||||
result &= handler.second.preloadModData(modName, modConfig[handler.first], validate);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ public:
|
||||
|
||||
/// local version of methods in ContentHandler
|
||||
/// returns true if loading was successful
|
||||
bool preloadModData(const std::string & modName, const std::vector<std::string> & fileList, bool validate);
|
||||
bool preloadModData(const std::string & modName, const JsonNode & fileList, bool validate);
|
||||
bool loadMod(const std::string & modName, bool validate);
|
||||
void loadCustom();
|
||||
void afterLoadFinalization();
|
||||
|
@@ -1715,7 +1715,7 @@ void BulkEraseArtifacts::applyGs(CGameState *gs)
|
||||
for(auto & slotInfoWorn : artSet->artifactsWorn)
|
||||
{
|
||||
auto art = slotInfoWorn.second.artifact;
|
||||
if(art->isCombined() && art->isPart(slotInfo->getArt()))
|
||||
if(art->isCombined() && art->isPart(slotInfo->artifact))
|
||||
{
|
||||
dis.al.slot = artSet->getArtPos(art);
|
||||
break;
|
||||
|
@@ -76,7 +76,7 @@ void Rewardable::Info::init(const JsonNode & objectConfig, const std::string & o
|
||||
|
||||
auto loadString = [&](const JsonNode & entry, const TextIdentifier & textID){
|
||||
if (entry.isString() && !entry.String().empty() && entry.String()[0] != '@')
|
||||
VLC->generaltexth->registerString(entry.getModScope(), textID, entry.String());
|
||||
VLC->generaltexth->registerString(entry.getModScope(), textID, entry);
|
||||
};
|
||||
|
||||
parameters = objectConfig;
|
||||
|
@@ -783,7 +783,7 @@ std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, c
|
||||
spell->combat = type == "combat";
|
||||
}
|
||||
|
||||
VLC->generaltexth->registerString(scope, spell->getNameTextID(), json["name"].String());
|
||||
VLC->generaltexth->registerString(scope, spell->getNameTextID(), json["name"]);
|
||||
|
||||
logMod->trace("%s: loading spell %s", __FUNCTION__, spell->getNameTranslated());
|
||||
|
||||
@@ -1005,7 +1005,7 @@ std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, c
|
||||
const si32 levelPower = levelObject.power = static_cast<si32>(levelNode["power"].Integer());
|
||||
|
||||
if (!spell->isCreatureAbility())
|
||||
VLC->generaltexth->registerString(scope, spell->getDescriptionTextID(levelIndex), levelNode["description"].String());
|
||||
VLC->generaltexth->registerString(scope, spell->getDescriptionTextID(levelIndex), levelNode["description"]);
|
||||
|
||||
levelObject.cost = static_cast<si32>(levelNode["cost"].Integer());
|
||||
levelObject.AIValue = static_cast<si32>(levelNode["aiValue"].Integer());
|
||||
|
@@ -120,21 +120,16 @@ void CGeneralTextHandler::readToVector(const std::string & sourceID, const std::
|
||||
}
|
||||
|
||||
CGeneralTextHandler::CGeneralTextHandler():
|
||||
victoryConditions(*this, "core.vcdesc" ),
|
||||
lossConditions (*this, "core.lcdesc" ),
|
||||
colors (*this, "core.plcolors" ),
|
||||
tcommands (*this, "core.tcommand" ),
|
||||
hcommands (*this, "core.hallinfo" ),
|
||||
fcommands (*this, "core.castinfo" ),
|
||||
advobtxt (*this, "core.advevent" ),
|
||||
restypes (*this, "core.restypes" ),
|
||||
randsign (*this, "core.randsign" ),
|
||||
overview (*this, "core.overview" ),
|
||||
arraytxt (*this, "core.arraytxt" ),
|
||||
primarySkillNames(*this, "core.priskill" ),
|
||||
jktexts (*this, "core.jktext" ),
|
||||
tavernInfo (*this, "core.tvrninfo" ),
|
||||
tavernRumors (*this, "core.randtvrn" ),
|
||||
turnDurations (*this, "core.turndur" ),
|
||||
heroscrn (*this, "core.heroscrn" ),
|
||||
tentColors (*this, "core.tentcolr" ),
|
||||
|
@@ -53,7 +53,6 @@ public:
|
||||
LegacyTextContainer jktexts;
|
||||
LegacyTextContainer heroscrn;
|
||||
LegacyTextContainer overview;//text for Kingdom Overview window
|
||||
LegacyTextContainer colors; //names of player colors ("red",...)
|
||||
LegacyTextContainer capColors; //names of player colors with first letter capitalized ("Red",...)
|
||||
LegacyTextContainer turnDurations; //turn durations for pregame (1 Minute ... Unlimited)
|
||||
|
||||
@@ -62,18 +61,14 @@ public:
|
||||
LegacyTextContainer hcommands; // town hall screen
|
||||
LegacyTextContainer fcommands; // fort screen
|
||||
LegacyTextContainer tavernInfo;
|
||||
LegacyTextContainer tavernRumors;
|
||||
|
||||
LegacyTextContainer qeModCommands;
|
||||
|
||||
LegacyHelpContainer zelp;
|
||||
LegacyTextContainer lossConditions;
|
||||
LegacyTextContainer victoryConditions;
|
||||
|
||||
//objects
|
||||
LegacyTextContainer advobtxt;
|
||||
LegacyTextContainer restypes; //names of resources
|
||||
LegacyTextContainer randsign;
|
||||
LegacyTextContainer seerEmpty;
|
||||
LegacyTextContainer seerNames;
|
||||
LegacyTextContainer tentColors;
|
||||
|
@@ -22,20 +22,31 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
std::recursive_mutex TextLocalizationContainer::globalTextMutex;
|
||||
|
||||
void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized)
|
||||
void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized)
|
||||
{
|
||||
std::lock_guard globalLock(globalTextMutex);
|
||||
|
||||
assert(!modContext.empty());
|
||||
assert(!language.empty());
|
||||
|
||||
// NOTE: implicitly creates entry, intended - strings added by maps, campaigns, vcmi and potentially - UI mods are not registered anywhere at the moment
|
||||
auto & entry = stringsLocalizations[UID.get()];
|
||||
|
||||
entry.overrideLanguage = language;
|
||||
entry.overrideValue = localized;
|
||||
if (entry.modContext.empty())
|
||||
entry.modContext = modContext;
|
||||
// load string override only in following cases:
|
||||
// a) string was not modified in another mod (e.g. rebalance mod gave skill new description)
|
||||
// b) this string override is defined in the same mod as one that provided modified version of this string
|
||||
if (entry.identifierModContext == entry.baseStringModContext || modContext == entry.baseStringModContext)
|
||||
{
|
||||
entry.translatedText = localized;
|
||||
if (entry.identifierModContext.empty())
|
||||
{
|
||||
entry.identifierModContext = modContext;
|
||||
entry.baseStringModContext = modContext;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->debug("Skipping translation override for string %s: changed in a different mod", UID.get());
|
||||
}
|
||||
}
|
||||
|
||||
void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container)
|
||||
@@ -55,7 +66,7 @@ void TextLocalizationContainer::removeSubContainer(const TextLocalizationContain
|
||||
subContainers.erase(std::remove(subContainers.begin(), subContainers.end(), &container), subContainers.end());
|
||||
}
|
||||
|
||||
const std::string & TextLocalizationContainer::deserialize(const TextIdentifier & identifier) const
|
||||
const std::string & TextLocalizationContainer::translateString(const TextIdentifier & identifier) const
|
||||
{
|
||||
std::lock_guard globalLock(globalTextMutex);
|
||||
|
||||
@@ -63,108 +74,63 @@ const std::string & TextLocalizationContainer::deserialize(const TextIdentifier
|
||||
{
|
||||
for(auto containerIter = subContainers.rbegin(); containerIter != subContainers.rend(); ++containerIter)
|
||||
if((*containerIter)->identifierExists(identifier))
|
||||
return (*containerIter)->deserialize(identifier);
|
||||
return (*containerIter)->translateString(identifier);
|
||||
|
||||
logGlobal->error("Unable to find localization for string '%s'", identifier.get());
|
||||
return identifier.get();
|
||||
}
|
||||
|
||||
const auto & entry = stringsLocalizations.at(identifier.get());
|
||||
|
||||
if (!entry.overrideValue.empty())
|
||||
return entry.overrideValue;
|
||||
return entry.baseValue;
|
||||
return entry.translatedText;
|
||||
}
|
||||
|
||||
void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language)
|
||||
void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & inputUID, const JsonNode & localized)
|
||||
{
|
||||
assert(localized.isNull() || !localized.getModScope().empty());
|
||||
assert(localized.isNull() || !getModLanguage(localized.getModScope()).empty());
|
||||
|
||||
if (localized.isNull())
|
||||
registerString(modContext, modContext, inputUID, localized.String());
|
||||
else
|
||||
registerString(modContext, localized.getModScope(), inputUID, localized.String());
|
||||
}
|
||||
|
||||
void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized)
|
||||
{
|
||||
registerString(modContext, modContext, UID, localized);
|
||||
}
|
||||
|
||||
void TextLocalizationContainer::registerString(const std::string & identifierModContext, const std::string & localizedStringModContext, const TextIdentifier & UID, const std::string & localized)
|
||||
{
|
||||
std::lock_guard globalLock(globalTextMutex);
|
||||
|
||||
assert(!modContext.empty());
|
||||
assert(!Languages::getLanguageOptions(language).identifier.empty());
|
||||
assert(!identifierModContext.empty());
|
||||
assert(!localizedStringModContext.empty());
|
||||
assert(UID.get().find("..") == std::string::npos); // invalid identifier - there is section that was evaluated to empty string
|
||||
//assert(stringsLocalizations.count(UID.get()) == 0); // registering already registered string?
|
||||
assert(stringsLocalizations.count(UID.get()) == 0 || identifierModContext == "map"); // registering already registered string?
|
||||
|
||||
if(stringsLocalizations.count(UID.get()) > 0)
|
||||
{
|
||||
auto & value = stringsLocalizations[UID.get()];
|
||||
value.baseLanguage = language;
|
||||
value.baseValue = localized;
|
||||
value.translatedText = localized;
|
||||
value.identifierModContext = identifierModContext;
|
||||
value.baseStringModContext = localizedStringModContext;
|
||||
}
|
||||
else
|
||||
{
|
||||
StringState value;
|
||||
value.baseLanguage = language;
|
||||
value.baseValue = localized;
|
||||
value.modContext = modContext;
|
||||
value.translatedText = localized;
|
||||
value.identifierModContext = identifierModContext;
|
||||
value.baseStringModContext = localizedStringModContext;
|
||||
|
||||
stringsLocalizations[UID.get()] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized)
|
||||
{
|
||||
assert(!getModLanguage(modContext).empty());
|
||||
registerString(modContext, UID, localized, getModLanguage(modContext));
|
||||
}
|
||||
|
||||
bool TextLocalizationContainer::validateTranslation(const std::string & language, const std::string & modContext, const JsonNode & config) const
|
||||
{
|
||||
std::lock_guard globalLock(globalTextMutex);
|
||||
|
||||
bool allPresent = true;
|
||||
|
||||
for(const auto & string : stringsLocalizations)
|
||||
{
|
||||
if (string.second.modContext != modContext)
|
||||
continue; // Not our mod
|
||||
|
||||
if (string.second.overrideLanguage == language)
|
||||
continue; // Already translated
|
||||
|
||||
if (string.second.baseLanguage == language && !string.second.baseValue.empty())
|
||||
continue; // Base string already uses our language
|
||||
|
||||
if (string.second.baseLanguage.empty())
|
||||
continue; // String added in localization, not present in base language (e.g. maps/campaigns)
|
||||
|
||||
if (config.Struct().count(string.first) > 0)
|
||||
continue;
|
||||
|
||||
if (allPresent)
|
||||
logMod->warn("Translation into language '%s' in mod '%s' is incomplete! Missing lines:", language, modContext);
|
||||
|
||||
std::string currentText;
|
||||
if (string.second.overrideValue.empty())
|
||||
currentText = string.second.baseValue;
|
||||
else
|
||||
currentText = string.second.overrideValue;
|
||||
|
||||
logMod->warn(R"( "%s" : "%s",)", string.first, TextOperations::escapeString(currentText));
|
||||
allPresent = false;
|
||||
}
|
||||
|
||||
bool allFound = true;
|
||||
|
||||
// for(const auto & string : config.Struct())
|
||||
// {
|
||||
// if (stringsLocalizations.count(string.first) > 0)
|
||||
// continue;
|
||||
//
|
||||
// if (allFound)
|
||||
// logMod->warn("Translation into language '%s' in mod '%s' has unused lines:", language, modContext);
|
||||
//
|
||||
// logMod->warn(R"( "%s" : "%s",)", string.first, TextOperations::escapeString(string.second.String()));
|
||||
// allFound = false;
|
||||
// }
|
||||
|
||||
return allPresent && allFound;
|
||||
}
|
||||
|
||||
void TextLocalizationContainer::loadTranslationOverrides(const std::string & language, const std::string & modContext, const JsonNode & config)
|
||||
void TextLocalizationContainer::loadTranslationOverrides(const std::string & modContext, const JsonNode & config)
|
||||
{
|
||||
for(const auto & node : config.Struct())
|
||||
registerStringOverride(modContext, language, node.first, node.second.String());
|
||||
registerStringOverride(modContext, node.first, node.second.String());
|
||||
}
|
||||
|
||||
bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) const
|
||||
@@ -184,17 +150,19 @@ void TextLocalizationContainer::exportAllTexts(std::map<std::string, std::map<st
|
||||
for (auto const & entry : stringsLocalizations)
|
||||
{
|
||||
std::string textToWrite;
|
||||
std::string modName = entry.second.modContext;
|
||||
std::string modName = entry.second.baseStringModContext;
|
||||
|
||||
if (modName.find('.') != std::string::npos)
|
||||
modName = modName.substr(0, modName.find('.'));
|
||||
if (entry.second.baseStringModContext == entry.second.identifierModContext)
|
||||
{
|
||||
if (modName.find('.') != std::string::npos)
|
||||
modName = modName.substr(0, modName.find('.'));
|
||||
}
|
||||
boost::range::replace(modName, '.', '_');
|
||||
|
||||
if (!entry.second.overrideValue.empty())
|
||||
textToWrite = entry.second.overrideValue;
|
||||
else
|
||||
textToWrite = entry.second.baseValue;
|
||||
textToWrite = entry.second.translatedText;
|
||||
|
||||
storage[modName][entry.first] = textToWrite;
|
||||
if (!textToWrite.empty())
|
||||
storage[modName][entry.first] = textToWrite;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,11 +178,7 @@ void TextLocalizationContainer::jsonSerialize(JsonNode & dest) const
|
||||
std::lock_guard globalLock(globalTextMutex);
|
||||
|
||||
for(auto & s : stringsLocalizations)
|
||||
{
|
||||
dest.Struct()[s.first].String() = s.second.baseValue;
|
||||
if(!s.second.overrideValue.empty())
|
||||
dest.Struct()[s.first].String() = s.second.overrideValue;
|
||||
}
|
||||
dest.Struct()[s.first].String() = s.second.translatedText;
|
||||
}
|
||||
|
||||
TextContainerRegistrable::TextContainerRegistrable()
|
||||
|
@@ -23,26 +23,21 @@ protected:
|
||||
struct StringState
|
||||
{
|
||||
/// Human-readable string that was added on registration
|
||||
std::string baseValue;
|
||||
|
||||
/// Language of base string
|
||||
std::string baseLanguage;
|
||||
|
||||
/// Translated human-readable string
|
||||
std::string overrideValue;
|
||||
|
||||
/// Language of the override string
|
||||
std::string overrideLanguage;
|
||||
std::string translatedText;
|
||||
|
||||
/// ID of mod that created this string
|
||||
std::string modContext;
|
||||
std::string identifierModContext;
|
||||
|
||||
/// ID of mod that provides original, untranslated version of this string
|
||||
/// Different from identifierModContext if mod has modified object from another mod (e.g. rebalance mods)
|
||||
std::string baseStringModContext;
|
||||
|
||||
template <typename Handler>
|
||||
void serialize(Handler & h)
|
||||
{
|
||||
h & baseValue;
|
||||
h & baseLanguage;
|
||||
h & modContext;
|
||||
h & translatedText;
|
||||
h & identifierModContext;
|
||||
h & baseStringModContext;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -52,7 +47,7 @@ protected:
|
||||
std::vector<const TextLocalizationContainer *> subContainers;
|
||||
|
||||
/// add selected string to internal storage as high-priority strings
|
||||
void registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized);
|
||||
void registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized);
|
||||
|
||||
std::string getModLanguage(const std::string & modContext);
|
||||
|
||||
@@ -60,29 +55,25 @@ protected:
|
||||
bool identifierExists(const TextIdentifier & UID) const;
|
||||
|
||||
public:
|
||||
/// validates translation of specified language for specified mod
|
||||
/// returns true if localization is valid and complete
|
||||
/// any error messages will be written to log file
|
||||
bool validateTranslation(const std::string & language, const std::string & modContext, JsonNode const & file) const;
|
||||
|
||||
/// Loads translation from provided json
|
||||
/// Any entries loaded by this will have priority over texts registered normally
|
||||
void loadTranslationOverrides(const std::string & language, const std::string & modContext, JsonNode const & file);
|
||||
void loadTranslationOverrides(const std::string & modContext, JsonNode const & file);
|
||||
|
||||
/// add selected string to internal storage
|
||||
void registerString(const std::string & modContext, const TextIdentifier & UID, const JsonNode & localized);
|
||||
void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized);
|
||||
void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language);
|
||||
void registerString(const std::string & identifierModContext, const std::string & localizedStringModContext, const TextIdentifier & UID, const std::string & localized);
|
||||
|
||||
/// returns translated version of a string that can be displayed to user
|
||||
template<typename ... Args>
|
||||
std::string translate(std::string arg1, Args ... args) const
|
||||
{
|
||||
TextIdentifier id(arg1, args ...);
|
||||
return deserialize(id);
|
||||
return translateString(id);
|
||||
}
|
||||
|
||||
/// converts identifier into user-readable string
|
||||
const std::string & deserialize(const TextIdentifier & identifier) const;
|
||||
const std::string & translateString(const TextIdentifier & identifier) const;
|
||||
|
||||
/// Debug method, returns all currently stored texts
|
||||
/// Format: [mod ID][string ID] -> human-readable text
|
||||
|
@@ -431,10 +431,28 @@ void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
|
||||
continue;
|
||||
|
||||
auto playerConnection = vstd::find(playerConnections.second, c);
|
||||
if(playerConnection != playerConnections.second.end())
|
||||
if(playerConnection == playerConnections.second.end())
|
||||
continue;
|
||||
|
||||
logGlobal->trace("Player %s disconnected. Notifying remaining players", playerId.toString());
|
||||
|
||||
// this player have left the game - broadcast infowindow to all in-game players
|
||||
for (auto i = gs->players.cbegin(); i!=gs->players.cend(); i++)
|
||||
{
|
||||
std::string messageText = boost::str(boost::format("%s (cid %d) was disconnected") % playerSettings->name % c->connectionID);
|
||||
playerMessages->broadcastMessage(playerId, messageText);
|
||||
if (i->first == playerId)
|
||||
continue;
|
||||
|
||||
if (getPlayerState(i->first)->status != EPlayerStatus::INGAME)
|
||||
continue;
|
||||
|
||||
logGlobal->trace("Notifying player %s", i->first);
|
||||
|
||||
InfoWindow out;
|
||||
out.player = i->first;
|
||||
out.text.appendTextID("vcmi.server.errors.playerLeft");
|
||||
out.text.replaceName(playerId);
|
||||
out.components.emplace_back(ComponentType::FLAG, playerId);
|
||||
sendAndApply(&out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -297,25 +297,19 @@ void CVCMIServer::onDisconnected(const std::shared_ptr<INetworkConnection> & con
|
||||
logNetwork->error("Network error receiving a pack. Connection has been closed");
|
||||
|
||||
std::shared_ptr<CConnection> c = findConnection(connection);
|
||||
if (!c)
|
||||
return; // player have already disconnected via clientDisconnected call
|
||||
|
||||
vstd::erase(activeConnections, c);
|
||||
|
||||
if(activeConnections.empty() || hostClientId == c->connectionID)
|
||||
// player may have already disconnected via clientDisconnected call
|
||||
if (c)
|
||||
{
|
||||
setState(EServerState::SHUTDOWN);
|
||||
return;
|
||||
}
|
||||
//clientDisconnected(c);
|
||||
|
||||
if(gh && getState() == EServerState::GAMEPLAY)
|
||||
{
|
||||
gh->handleClientDisconnection(c);
|
||||
|
||||
auto lcd = std::make_unique<LobbyClientDisconnected>();
|
||||
lcd->c = c;
|
||||
lcd->clientId = c->connectionID;
|
||||
handleReceivedPack(std::move(lcd));
|
||||
if(gh && getState() == EServerState::GAMEPLAY)
|
||||
{
|
||||
auto lcd = std::make_unique<LobbyClientDisconnected>();
|
||||
lcd->c = c;
|
||||
lcd->clientId = c->connectionID;
|
||||
handleReceivedPack(std::move(lcd));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,9 +428,21 @@ void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<st
|
||||
|
||||
void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> connection)
|
||||
{
|
||||
connection->getConnection()->close();
|
||||
assert(vstd::contains(activeConnections, connection));
|
||||
logGlobal->trace("Received disconnection request");
|
||||
vstd::erase(activeConnections, connection);
|
||||
|
||||
if(activeConnections.empty() || hostClientId == connection->connectionID)
|
||||
{
|
||||
setState(EServerState::SHUTDOWN);
|
||||
return;
|
||||
}
|
||||
|
||||
if(gh && getState() == EServerState::GAMEPLAY)
|
||||
{
|
||||
gh->handleClientDisconnection(connection);
|
||||
}
|
||||
|
||||
// PlayerReinitInterface startAiPack;
|
||||
// startAiPack.playerConnectionId = PlayerSettings::PLAYER_AI;
|
||||
//
|
||||
|
@@ -108,6 +108,7 @@ void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientDisconnected(LobbyC
|
||||
|
||||
void ApplyOnServerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)
|
||||
{
|
||||
pack.c->getConnection()->close();
|
||||
srv.clientDisconnected(pack.c);
|
||||
result = true;
|
||||
}
|
||||
|
@@ -571,7 +571,8 @@ void BattleFlowProcessor::onActionMade(const CBattleInfoCallback & battle, const
|
||||
assert(activeStack != nullptr);
|
||||
assert(actedStack != nullptr);
|
||||
|
||||
if(actedStack->castSpellThisTurn && SpellID(ba.spell).toSpell()->canCastWithoutSkip())
|
||||
// NOTE: in case of random spellcaster, (e.g. Master Genie) spell has been selected by server and was not present in action received from player
|
||||
if(actedStack->castSpellThisTurn && ba.spell.hasValue() && ba.spell.toSpell()->canCastWithoutSkip())
|
||||
{
|
||||
setActiveStack(battle, actedStack);
|
||||
return;
|
||||
|
Reference in New Issue
Block a user