1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-14 10:12:59 +02:00

Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Xilmi 2024-09-18 14:52:24 +02:00
commit 27aed74397
26 changed files with 415 additions and 138 deletions

View File

@ -3,9 +3,9 @@
"vcmi.adventureMap.monsterThreat.levels.0" : "Sem Esforço",
"vcmi.adventureMap.monsterThreat.levels.1" : "Muito Fraca",
"vcmi.adventureMap.monsterThreat.levels.2" : "Fraca",
"vcmi.adventureMap.monsterThreat.levels.3" : "Um pouco mais fraca",
"vcmi.adventureMap.monsterThreat.levels.3" : "Um Pouco Mais Fraca",
"vcmi.adventureMap.monsterThreat.levels.4" : "Igual",
"vcmi.adventureMap.monsterThreat.levels.5" : "Um pouco mais forte",
"vcmi.adventureMap.monsterThreat.levels.5" : "Um Pouco Mais Forte",
"vcmi.adventureMap.monsterThreat.levels.6" : "Forte",
"vcmi.adventureMap.monsterThreat.levels.7" : "Muito Forte",
"vcmi.adventureMap.monsterThreat.levels.8" : "Desafiante",
@ -163,8 +163,8 @@
"vcmi.systemOptions.townsGroup" : "Tela da Cidade",
"vcmi.statisticWindow.statistics" : "Estatísticas",
"vcmi.statisticWindow.tsvCopy" : "Copiar dados",
"vcmi.statisticWindow.selectView" : "Selecionar visualização",
"vcmi.statisticWindow.tsvCopy" : "Para a área de transf.",
"vcmi.statisticWindow.selectView" : "Selec. visualização",
"vcmi.statisticWindow.value" : "Valor",
"vcmi.statisticWindow.title.overview" : "Visão geral",
"vcmi.statisticWindow.title.resources" : "Recursos",
@ -178,7 +178,7 @@
"vcmi.statisticWindow.title.experience" : "Experiência",
"vcmi.statisticWindow.title.resourcesSpentArmy" : "Custo do exército",
"vcmi.statisticWindow.title.resourcesSpentBuildings" : "Custo de construção",
"vcmi.statisticWindow.title.mapExplored" : "Exploração do mapa",
"vcmi.statisticWindow.title.mapExplored" : "Mapa explorado",
"vcmi.statisticWindow.param.playerName" : "Nome do jogador",
"vcmi.statisticWindow.param.daysSurvived" : "Dias sobrevividos",
"vcmi.statisticWindow.param.maxHeroLevel" : "Nível máximo do herói",
@ -325,8 +325,8 @@
"vcmi.townHall.missingBase" : "A construção base %s deve ser construída primeiro",
"vcmi.townHall.noCreaturesToRecruit" : "Não há criaturas para recrutar!",
"vcmi.townStructure.bank.borrow" : "Você entra no banco. Um banqueiro o vê e diz: \"Temos uma oferta especial para você. Você pode tomar um empréstimo de 2500 de ouro por 5 dias. Você terá que pagar 500 de ouro todos os dias.\"",
"vcmi.townStructure.bank.payBack" : "Você entra no banco. Um banqueiro o vê e diz: \"Você já pegou um empréstimo. Pague-o antes de tomar um novo.\"",
"vcmi.townStructure.bank.borrow" : "Você entra no banco. Um banqueiro o vê e diz: \"Temos uma oferta especial para você. Você pode pegar um empréstimo de 2500 de ouro por 5 dias. Você terá que pagar 500 de ouro todos os dias.\"",
"vcmi.townStructure.bank.payBack" : "Você entra no banco. Um banqueiro o vê e diz: \"Você já pegou um empréstimo. Pague-o antes de pegar um novo.\"",
"vcmi.logicalExpressions.anyOf" : "Qualquer um dos seguintes:",
"vcmi.logicalExpressions.allOf" : "Todos os seguintes:",
@ -660,5 +660,7 @@
"core.bonus.WATER_IMMUNITY.name" : "Imunidade à Água",
"core.bonus.WATER_IMMUNITY.description" : "Imune a todos os feitiços da escola de magia da Água",
"core.bonus.WIDE_BREATH.name" : "Sopro Amplo",
"core.bonus.WIDE_BREATH.description" : "Ataque de sopro amplo (vários hexágonos)"
"core.bonus.WIDE_BREATH.description" : "Ataque de sopro amplo (vários hexágonos)",
"core.bonus.DISINTEGRATE.name": "Desintegrar",
"core.bonus.DISINTEGRATE.description": "Nenhum corpo permanece após a morte"
}

View File

@ -25,6 +25,7 @@ void AssetGenerator::generateAll()
createAdventureOptionsCleanBackground();
for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i)
createPlayerColoredBackground(PlayerColor(i));
createCombatUnitNumberWindow();
}
void AssetGenerator::createAdventureOptionsCleanBackground()

View File

@ -191,14 +191,14 @@ void Canvas::drawText(const Point & position, const EFonts & font, const ColorRG
void Canvas::drawColor(const Rect & target, const ColorRGBA & color)
{
Rect realTarget = (target + renderArea.topLeft()) * getScalingFactor();
Rect realTarget = target * getScalingFactor() + renderArea.topLeft();
CSDL_Ext::fillRect(surface, realTarget, CSDL_Ext::toSDL(color));
}
void Canvas::drawColorBlended(const Rect & target, const ColorRGBA & color)
{
Rect realTarget = (target + renderArea.topLeft()) * getScalingFactor();
Rect realTarget = target * getScalingFactor() + renderArea.topLeft();
CSDL_Ext::fillRectBlended(surface, realTarget, CSDL_Ext::toSDL(color));
}

View File

@ -415,6 +415,10 @@ void SDLImageIndexed::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove,
void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
{
// If shadow is enabled, following colors must be skipped unconditionally
if (shadowEnabled)
colorsToSkipMask |= (1 << 0) + (1 << 1) + (1 << 4);
// Note: here we skip first colors in the palette that are predefined in H3 images
for(int i = 0; i < currentPalette->ncolors; i++)
{

View File

@ -54,6 +54,7 @@
#include "../../lib/entities/building/CBuilding.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapObjects/TownBuildingInstance.h"
static bool useCompactCreatureBox()
@ -845,7 +846,21 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu
void CCastleBuildings::enterRewardable(BuildingID building)
{
LOCPLINT->cb->visitTownBuilding(town, building);
if (town->visitingHero == nullptr)
{
MetaString message;
message.appendTextID("core.genrltxt.273"); // only visiting heroes may visit %s
message.replaceTextID(town->town->buildings.at(building)->getNameTextID());
LOCPLINT->showInfoDialog(message.toString());
}
else
{
if (town->rewardableBuildings.at(building)->wasVisited(town->visitingHero))
enterBuilding(building);
else
LOCPLINT->cb->visitTownBuilding(town, building);
}
}
void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactID)

View File

@ -20,6 +20,7 @@
#include "../windows/CMessage.h"
#include "../renderSDL/SDL_PixelAccess.h"
#include "../render/IImage.h"
#include "../render/IScreenHandler.h"
#include "../render/IRenderHandler.h"
#include "../render/Canvas.h"
@ -115,7 +116,8 @@ void CWindowObject::updateShadow()
void CWindowObject::setShadow(bool on)
{
//size of shadow
static const int size = 8;
int sizeOriginal = 8;
int size = sizeOriginal * GH.screenHandler().getScalingFactor();
if(on == !shadowParts.empty())
return;
@ -180,9 +182,9 @@ void CWindowObject::setShadow(bool on)
//FIXME: do something with this points
Point shadowStart;
if (options & BORDERED)
shadowStart = Point(size - 14, size - 14);
shadowStart = Point(sizeOriginal - 14, sizeOriginal - 14);
else
shadowStart = Point(size, size);
shadowStart = Point(sizeOriginal, sizeOriginal);
Point shadowPos;
if (options & BORDERED)
@ -198,8 +200,8 @@ void CWindowObject::setShadow(bool on)
//create base 8x8 piece of shadow
SDL_Surface * shadowCorner = CSDL_Ext::copySurface(shadowCornerTempl);
SDL_Surface * shadowBottom = CSDL_Ext::scaleSurface(shadowBottomTempl, fullsize.x - size, size);
SDL_Surface * shadowRight = CSDL_Ext::scaleSurface(shadowRightTempl, size, fullsize.y - size);
SDL_Surface * shadowBottom = CSDL_Ext::scaleSurface(shadowBottomTempl, (fullsize.x - sizeOriginal) * GH.screenHandler().getScalingFactor(), size);
SDL_Surface * shadowRight = CSDL_Ext::scaleSurface(shadowRightTempl, size, (fullsize.y - sizeOriginal) * GH.screenHandler().getScalingFactor());
blitAlphaCol(shadowBottom, 0);
blitAlphaRow(shadowRight, 0);

View File

@ -19,7 +19,7 @@ class VCMI(ConanFile):
"sdl_image/[~2.0.5]",
"sdl_mixer/[~2.0.4]",
"sdl_ttf/[~2.0.18]",
"onetbb/[^2021.3]",
"onetbb/[^2021.7 <2021.10]", # 2021.10+ breaks mobile builds due to added hwloc dependency
"xz_utils/[>=5.2.5]", # Required for innoextract
]
@ -39,7 +39,6 @@ class VCMI(ConanFile):
"boost/*:shared": True,
"minizip/*:shared": True,
"onetbb/*:shared": True,
}
def configure(self):

View File

@ -197,6 +197,7 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
"bonuses" : [ BONUS_FORMAT ]
// If set to true, this building will not automatically activate on new day or on entering town and needs to be activated manually on click
// Note that such building can only be activated by visiting hero, and not by garrisoned hero.
"manualHeroVisit" : false,
// Bonuses provided by this special building if this building or any of its upgrades are constructed in town

View File

@ -21,6 +21,80 @@
VCMI_LIB_NAMESPACE_BEGIN
// Algorithm for detection of typos in words
// Determines how 'different' two strings are - how many changes must be done to turn one string into another one
// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows
static int getLevenshteinDistance(const std::string & s, const std::string & t)
{
int n = t.size();
int m = s.size();
// create two work vectors of integer distances
std::vector<int> v0(n+1, 0);
std::vector<int> v1(n+1, 0);
// initialize v0 (the previous row of distances)
// this row is A[0][i]: edit distance from an empty s to t;
// that distance is the number of characters to append to s to make t.
for (int i = 0; i < n; ++i)
v0[i] = i;
for (int i = 0; i < m; ++i)
{
// calculate v1 (current row distances) from the previous row v0
// first element of v1 is A[i + 1][0]
// edit distance is delete (i + 1) chars from s to match empty t
v1[0] = i + 1;
// use formula to fill in the rest of the row
for (int j = 0; j < n; ++j)
{
// calculating costs for A[i + 1][j + 1]
int deletionCost = v0[j + 1] + 1;
int insertionCost = v1[j] + 1;
int substitutionCost;
if (s[i] == t[j])
substitutionCost = v0[j];
else
substitutionCost = v0[j] + 1;
v1[j + 1] = std::min({deletionCost, insertionCost, substitutionCost});
}
// copy v1 (current row) to v0 (previous row) for next iteration
// since data in v1 is always invalidated, a swap without copy could be more efficient
std::swap(v0, v1);
}
// after the last swap, the results of v1 are now in v0
return v0[n];
}
/// Searches for keys similar to 'target' in 'candidates' map
/// Returns closest match or empty string if no suitable candidates are found
static std::string findClosestMatch(JsonMap candidates, std::string target)
{
// Maximum distance at which we can consider strings to be similar
// If strings have more different symbols than this number then it is not a typo, but a completely different word
static constexpr int maxDistance = 5;
int bestDistance = maxDistance;
std::string bestMatch;
for (auto const & candidate : candidates)
{
int newDistance = getLevenshteinDistance(candidate.first, target);
if (newDistance < bestDistance)
{
bestDistance = newDistance;
bestMatch = candidate.first;
}
}
return bestMatch;
}
static std::string emptyCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
// check is not needed - e.g. incorporated into another check
@ -417,7 +491,13 @@ static std::string additionalPropertiesCheck(JsonValidator & validator, const Js
// or, additionalItems field can be bool which indicates if such items are allowed
else if(!schema.isNull() && !schema.Bool()) // present and set to false - error
errors += validator.makeErrorMessage("Unknown entry found: " + entry.first);
{
std::string bestCandidate = findClosestMatch(baseSchema["properties"].Struct(), entry.first);
if (!bestCandidate.empty())
errors += validator.makeErrorMessage("Unknown entry found: '" + entry.first + "'. Perhaps you meant '" + bestCandidate + "'?");
else
errors += validator.makeErrorMessage("Unknown entry found: " + entry.first);
}
}
}
return errors;

View File

@ -246,6 +246,9 @@ void CRewardableObject::blockingDialogAnswered(const CGHeroInstance * hero, int3
}
else
{
if (answer == 0)
return; //Player refused
if(answer > 0 && answer - 1 < configuration.info.size())
{
auto list = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT);

View File

@ -165,6 +165,11 @@ void TownRewardableBuildingInstance::grantReward(ui32 rewardID, const CGHeroInst
}
}
bool TownRewardableBuildingInstance::wasVisited(const CGHeroInstance * contextHero) const
{
return wasVisitedBefore(contextHero);
}
bool TownRewardableBuildingInstance::wasVisitedBefore(const CGHeroInstance * contextHero) const
{
switch (configuration.visitMode)

View File

@ -70,6 +70,7 @@ class DLL_LINKAGE TownRewardableBuildingInstance : public TownBuildingInstance,
public:
void setProperty(ObjProperty what, ObjPropertyID identifier) override;
void onHeroVisit(const CGHeroInstance * h) const override;
bool wasVisited(const CGHeroInstance * contextHero) const override;
void newTurn(vstd::RNG & rand) const override;

View File

@ -196,6 +196,7 @@ endif()
target_sources(vcmieditor PRIVATE
${editor_SRCS}
${editor_HEADERS}
${editor_FORMS}
${editor_RESOURCES}
)

View File

@ -13,6 +13,7 @@
#include "towneventdialog.h"
#include "ui_towneventdialog.h"
#include "mapeditorroles.h"
#include "../mapsettings/eventsettings.h"
#include "../../lib/entities/building/CBuilding.h"
#include "../../lib/entities/faction/CTownHandler.h"
#include "../../lib/constants/NumericConstants.h"
@ -63,9 +64,10 @@ TownEventDialog::~TownEventDialog()
void TownEventDialog::initPlayers()
{
auto playerList = params.value("players").toList();
for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i)
{
bool isAffected = (1 << i) & params.value("players").toInt();
bool isAffected = playerList.contains(toQString(PlayerColor(i)));
auto * item = new QListWidgetItem(QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[i]));
item->setData(MapEditorRoles::PlayerIDRole, QVariant::fromValue(i));
item->setCheckState(isAffected ? Qt::Checked : Qt::Unchecked);
@ -155,7 +157,12 @@ void TownEventDialog::initCreatures()
{
auto creatures = params.value("creatures").toList();
auto * ctown = town.town;
for (int i = 0; i < GameConstants::CREATURES_PER_TOWN; ++i)
if (!ctown)
ui->creaturesTable->setRowCount(GameConstants::CREATURES_PER_TOWN);
else
ui->creaturesTable->setRowCount(ctown->creatures.size());
for (int i = 0; i < ui->creaturesTable->rowCount(); ++i)
{
QString creatureNames;
if (!ctown)
@ -208,12 +215,12 @@ void TownEventDialog::on_TownEventDialog_finished(int result)
QVariant TownEventDialog::playersToVariant()
{
int players = 0;
QVariantList players;
for (int i = 0; i < ui->playersAffected->count(); ++i)
{
auto * item = ui->playersAffected->item(i);
if (item->checkState() == Qt::Checked)
players |= 1 << i;
players.push_back(toQString(PlayerColor(i)));
}
return QVariant::fromValue(players);
}
@ -239,7 +246,7 @@ QVariantList TownEventDialog::buildingsToVariant()
QVariantList TownEventDialog::creaturesToVariant()
{
QVariantList creaturesList;
for (int i = 0; i < GameConstants::CREATURES_PER_TOWN; ++i)
for (int i = 0; i < ui->creaturesTable->rowCount(); ++i)
{
auto * item = static_cast<QSpinBox *>(ui->creaturesTable->cellWidget(i, 1));
creaturesList.push_back(item->value());

View File

@ -17,7 +17,7 @@ static QVariantMap JsonToMap(const JsonMap & json)
QVariantMap map;
for(auto & entry : json)
{
map.insert(QString::fromUtf8(entry.first.c_str()), JsonUtils::toVariant(entry.second));
map.insert(QString::fromStdString(entry.first), JsonUtils::toVariant(entry.second));
}
return map;
}
@ -61,23 +61,18 @@ QVariant toVariant(const JsonNode & node)
{
switch(node.getType())
{
break;
case JsonNode::JsonType::DATA_NULL:
return QVariant();
break;
case JsonNode::JsonType::DATA_BOOL:
return QVariant(node.Bool());
break;
case JsonNode::JsonType::DATA_FLOAT:
case JsonNode::JsonType::DATA_INTEGER:
return QVariant(node.Float());
break;
case JsonNode::JsonType::DATA_INTEGER:
return QVariant{static_cast<qlonglong>(node.Integer())};
case JsonNode::JsonType::DATA_STRING:
return QVariant(QString::fromUtf8(node.String().c_str()));
break;
return QVariant(QString::fromStdString(node.String()));
case JsonNode::JsonType::DATA_VECTOR:
return JsonToList(node.Vector());
break;
case JsonNode::JsonType::DATA_STRUCT:
return JsonToMap(node.Struct());
}
@ -87,33 +82,31 @@ QVariant toVariant(const JsonNode & node)
QVariant JsonFromFile(QString filename)
{
QFile file(filename);
file.open(QFile::ReadOnly);
auto data = file.readAll();
if(!file.open(QFile::ReadOnly))
{
logGlobal->error("Failed to open file %s. Reason: %s", qUtf8Printable(filename), qUtf8Printable(file.errorString()));
return {};
}
if(data.size() == 0)
{
logGlobal->error("Failed to open file %s", filename.toUtf8().data());
return QVariant();
}
else
{
JsonNode node(reinterpret_cast<const std::byte*>(data.data()), data.size(), filename.toStdString());
return toVariant(node);
}
const auto data = file.readAll();
JsonNode node(reinterpret_cast<const std::byte*>(data.data()), data.size(), filename.toStdString());
return toVariant(node);
}
JsonNode toJson(QVariant object)
{
JsonNode ret;
if(object.canConvert<QVariantMap>())
ret.Struct() = VariantToMap(object.toMap());
else if(object.canConvert<QVariantList>())
ret.Vector() = VariantToList(object.toList());
else if(object.userType() == QMetaType::QString)
if(object.userType() == QMetaType::QString)
ret.String() = object.toString().toUtf8().data();
else if(object.userType() == QMetaType::Bool)
ret.Bool() = object.toBool();
else if(object.canConvert<QVariantMap>())
ret.Struct() = VariantToMap(object.toMap());
else if(object.canConvert<QVariantList>())
ret.Vector() = VariantToList(object.toList());
else if(object.canConvert<int>())
ret.Integer() = object.toInt();
else if(object.canConvert<double>())
ret.Float() = object.toFloat();

View File

@ -16,11 +16,16 @@
#include "../../lib/constants/NumericConstants.h"
#include "../../lib/constants/StringConstants.h"
QString toQString(const PlayerColor & player)
{
return QString::fromStdString(player.toString());
}
QVariant toVariant(const std::set<PlayerColor> & players)
{
QVariantList result;
for(auto const id : players)
result.push_back(QString::fromStdString(id.toString()));
result.push_back(toQString(id));
return result;
}

View File

@ -15,6 +15,7 @@ namespace Ui {
class EventSettings;
}
QString toQString(const PlayerColor & player);
QVariant toVariant(const TResources & resources);
QVariant toVariant(const std::set<PlayerColor> & players);

View File

@ -10,6 +10,7 @@
#include "StdInc.h"
#include "timedevent.h"
#include "ui_timedevent.h"
#include "eventsettings.h"
#include "../../lib/constants/EntityIdentifiers.h"
#include "../../lib/constants/StringConstants.h"
@ -30,9 +31,10 @@ TimedEvent::TimedEvent(QListWidgetItem * t, QWidget *parent) :
ui->eventFirstOccurrence->setValue(params.value("firstOccurrence").toInt());
ui->eventRepeatAfter->setValue(params.value("nextOccurrence").toInt());
auto playerList = params.value("players").toList();
for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i)
{
bool isAffected = (1 << i) & params.value("players").toInt();
bool isAffected = playerList.contains(toQString(PlayerColor(i)));
auto * item = new QListWidgetItem(QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[i]));
item->setData(Qt::UserRole, QVariant::fromValue(i));
item->setCheckState(isAffected ? Qt::Checked : Qt::Unchecked);
@ -69,12 +71,12 @@ void TimedEvent::on_TimedEvent_finished(int result)
descriptor["firstOccurrence"] = QVariant::fromValue(ui->eventFirstOccurrence->value());
descriptor["nextOccurrence"] = QVariant::fromValue(ui->eventRepeatAfter->value());
int players = 0;
QVariantList players;
for(int i = 0; i < ui->playersAffected->count(); ++i)
{
auto * item = ui->playersAffected->item(i);
if(item->checkState() == Qt::Checked)
players |= 1 << i;
players.push_back(toQString(PlayerColor(i)));
}
descriptor["players"] = QVariant::fromValue(players);

View File

@ -67,22 +67,22 @@
<message>
<location filename="../mapsettings/generalsettings.ui" line="52"/>
<source>Author</source>
<translation type="unfinished"></translation>
<translation>Autor</translation>
</message>
<message>
<location filename="../mapsettings/generalsettings.ui" line="62"/>
<source>Author contact (e.g. email)</source>
<translation type="unfinished"></translation>
<translation>Contato do autor (ex.: e-mail)</translation>
</message>
<message>
<location filename="../mapsettings/generalsettings.ui" line="72"/>
<source>Map Creation Time</source>
<translation type="unfinished"></translation>
<translation>Data de Criação do Mapa</translation>
</message>
<message>
<location filename="../mapsettings/generalsettings.ui" line="86"/>
<source>Map Version</source>
<translation type="unfinished"></translation>
<translation>Versão do Mapa</translation>
</message>
<message>
<location filename="../mapsettings/generalsettings.ui" line="120"/>
@ -947,17 +947,17 @@
<message>
<location filename="../mapcontroller.cpp" line="405"/>
<source>Can&apos;t place object</source>
<translation type="unfinished">Não é possível colocar objeto</translation>
<translation>Não é possível colocar objeto</translation>
</message>
<message>
<location filename="../mapcontroller.cpp" line="577"/>
<source>There can only be one grail object on the map.</source>
<translation type="unfinished"></translation>
<translation>Pode haver apenas um objeto graal no mapa.</translation>
</message>
<message>
<location filename="../mapcontroller.cpp" line="583"/>
<source>Hero %1 cannot be created as NEUTRAL.</source>
<translation type="unfinished"></translation>
<translation>O herói %1 não pode ser criado como NEUTRO.</translation>
</message>
</context>
<context>
@ -1762,17 +1762,17 @@
<message>
<location filename="../validator.cpp" line="148"/>
<source>Spell scroll %1 doesn&apos;t have instance assigned and must be removed</source>
<translation type="unfinished"></translation>
<translation>O pergaminho de feitiço %1 não tem uma instância atribuída e deve ser removido</translation>
</message>
<message>
<location filename="../validator.cpp" line="154"/>
<source>Artifact %1 is prohibited by map settings</source>
<translation type="unfinished"></translation>
<translation>O artefato %1 é proibido pelas configurações do mapa</translation>
</message>
<message>
<location filename="../validator.cpp" line="168"/>
<source>Player %1 has no towns and heroes assigned</source>
<translation type="unfinished"></translation>
<translation>O jogador %1 não tem cidades e heróis atribuídos</translation>
</message>
<message>
<location filename="../validator.cpp" line="116"/>

View File

@ -150,6 +150,10 @@ bool WindowNewMap::loadUserSettings()
ui->monsterOpt4->setChecked(true); break;
}
ui->roadDirt->setChecked(mapGenOptions.isRoadEnabled(Road::DIRT_ROAD));
ui->roadGravel->setChecked(mapGenOptions.isRoadEnabled(Road::GRAVEL_ROAD));
ui->roadCobblestone->setChecked(mapGenOptions.isRoadEnabled(Road::COBBLESTONE_ROAD));
ret = true;
}
@ -236,6 +240,10 @@ void WindowNewMap::on_okButton_clicked()
mapGenOptions.setWaterContent(water);
mapGenOptions.setMonsterStrength(monster);
mapGenOptions.setRoadEnabled(Road::DIRT_ROAD, ui->roadDirt->isChecked());
mapGenOptions.setRoadEnabled(Road::GRAVEL_ROAD, ui->roadGravel->isChecked());
mapGenOptions.setRoadEnabled(Road::COBBLESTONE_ROAD, ui->roadCobblestone->isChecked());
saveUserSettings();

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>444</width>
<height>445</height>
<height>506</height>
</rect>
</property>
<property name="sizePolicy">
@ -52,7 +52,7 @@
<x>0</x>
<y>20</y>
<width>281</width>
<height>68</height>
<height>73</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="3,0,1">
@ -72,7 +72,7 @@
</size>
</property>
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
<set>Qt::InputMethodHint::ImhDigitsOnly</set>
</property>
<property name="text">
<string notr="true">36</string>
@ -98,7 +98,7 @@
</size>
</property>
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
<set>Qt::InputMethodHint::ImhDigitsOnly</set>
</property>
<property name="text">
<string notr="true">36</string>
@ -132,10 +132,10 @@
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -207,7 +207,7 @@
<x>10</x>
<y>140</y>
<width>431</width>
<height>301</height>
<height>361</height>
</rect>
</property>
<property name="sizePolicy">
@ -237,7 +237,7 @@
<x>10</x>
<y>20</y>
<width>391</width>
<height>68</height>
<height>72</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0,0,0">
@ -546,7 +546,7 @@
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -675,7 +675,104 @@
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox_6">
<property name="geometry">
<rect>
<x>10</x>
<y>230</y>
<width>411</width>
<height>51</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>480</width>
<height>96</height>
</size>
</property>
<property name="title">
<string>Roads</string>
</property>
<widget class="QWidget" name="layoutWidget4_2">
<property name="geometry">
<rect>
<x>0</x>
<y>20</y>
<width>411</width>
<height>26</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="0,0,0,0,0,1">
<item>
<widget class="QCheckBox" name="roadDirt">
<property name="text">
<string>Dirt</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="roadGravel">
<property name="text">
<string>Gravel</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="roadCobblestone">
<property name="text">
<string>Cobblestone</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -692,9 +789,9 @@
<property name="geometry">
<rect>
<x>10</x>
<y>230</y>
<y>280</y>
<width>411</width>
<height>32</height>
<height>34</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
@ -732,37 +829,37 @@
</item>
</layout>
</widget>
<widget class="QLineEdit" name="lineSeed">
<property name="enabled">
<bool>false</bool>
</property>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>280</x>
<y>270</y>
<width>131</width>
<height>21</height>
<x>80</x>
<y>320</y>
<width>283</width>
<height>33</height>
</rect>
</property>
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
<property name="text">
<string>0</string>
</property>
</widget>
<widget class="QCheckBox" name="checkSeed">
<property name="geometry">
<rect>
<x>110</x>
<y>270</y>
<width>161</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>Custom seed</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QCheckBox" name="checkSeed">
<property name="text">
<string>Custom seed</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineSeed">
<property name="enabled">
<bool>false</bool>
</property>
<property name="inputMethodHints">
<set>Qt::InputMethodHint::ImhDigitsOnly</set>
</property>
<property name="text">
<string>0</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QCheckBox" name="randomMapCheck">

View File

@ -1171,7 +1171,6 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta
sendAndApply(&vc);
}
visitCastleObjects(obj, hero);
giveSpells (obj, hero);
if (obj->visitingHero && obj->garrisonHero)
useScholarSkill(obj->visitingHero->id, obj->garrisonHero->id);
@ -1180,10 +1179,27 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta
void CGameHandler::visitCastleObjects(const CGTownInstance * t, const CGHeroInstance * h)
{
std::vector<const CGHeroInstance * > visitors;
visitors.push_back(h);
visitCastleObjects(t, visitors);
}
void CGameHandler::visitCastleObjects(const CGTownInstance * t, std::vector<const CGHeroInstance * > visitors)
{
std::vector<BuildingID> buildingsToVisit;
for (auto const & hero : visitors)
giveSpells (t, hero);
for (auto & building : t->rewardableBuildings)
{
if (!t->town->buildings.at(building.first)->manualHeroVisit)
building.second->onHeroVisit(h);
buildingsToVisit.push_back(building.first);
}
if (!buildingsToVisit.empty())
{
auto visitQuery = std::make_shared<TownBuildingVisitQuery>(this, t, visitors, buildingsToVisit);
queries->addQuery(visitQuery);
}
}
@ -2144,10 +2160,15 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
if (!force)
{
if(t->garrisonHero) //garrison hero first - consistent with original H3 Mana Vortex and Battle Scholar Academy levelup windows order
objectVisited(t, t->garrisonHero);
if(t->visitingHero)
objectVisited(t, t->visitingHero);
//garrison hero first - consistent with original H3 Mana Vortex and Battle Scholar Academy levelup windows order
std::vector<const CGHeroInstance *> visitors;
if (t->garrisonHero)
visitors.push_back(t->garrisonHero);
if (t->visitingHero)
visitors.push_back(t->visitingHero);
if (!visitors.empty())
visitCastleObjects(t, visitors);
}
checkVictoryLossConditionsForPlayer(t->tempOwner);
@ -2173,19 +2194,15 @@ bool CGameHandler::visitTownBuilding(ObjectInstanceID tid, BuildingID bid)
return true;
}
if (t->rewardableBuildings.count(bid))
if (t->rewardableBuildings.count(bid) && t->visitingHero && t->town->buildings.at(bid)->manualHeroVisit)
{
auto & hero = t->garrisonHero ? t->garrisonHero : t->visitingHero;
auto * building = t->rewardableBuildings.at(bid);
if (hero && t->town->buildings.at(bid)->manualHeroVisit)
{
auto visitQuery = std::make_shared<TownBuildingVisitQuery>(this, t, hero, bid);
queries->addQuery(visitQuery);
building->onHeroVisit(hero);
queries->popIfTop(visitQuery);
return true;
}
std::vector<BuildingID> buildingsToVisit;
std::vector<const CGHeroInstance*> visitors;
buildingsToVisit.push_back(bid);
visitors.push_back(t->visitingHero);
auto visitQuery = std::make_shared<TownBuildingVisitQuery>(this, t, visitors, buildingsToVisit);
queries->addQuery(visitQuery);
return true;
}
return true;

View File

@ -182,6 +182,7 @@ public:
void visitObjectOnTile(const TerrainTile &t, const CGHeroInstance * h);
bool teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui8 source, PlayerColor asker = PlayerColor::NEUTRAL);
void visitCastleObjects(const CGTownInstance * obj, const CGHeroInstance * hero) override;
void visitCastleObjects(const CGTownInstance * obj, std::vector<const CGHeroInstance * > visitors);
void levelUpHero(const CGHeroInstance * hero, SecondarySkill skill);//handle client respond and send one more request if needed
void levelUpHero(const CGHeroInstance * hero);//initial call - check if hero have remaining levelups & handle them
void levelUpCommander (const CCommanderInstance * c, int skill); //secondary skill 1 to 6, special skill : skill - 100

View File

@ -110,7 +110,7 @@ void NewTurnProcessor::handleTownEvents(const CGTownInstance * town)
for (si32 i=0;i<event.creatures.size();i++) //creature growths
{
if (!town->creatures.at(i).second.empty() && event.creatures.at(i) > 0)//there is dwelling
if (i < town->creatures.size() && !town->creatures.at(i).second.empty() && event.creatures.at(i) > 0)//there is dwelling
{
sac.creatures[i].first += event.creatures.at(i);
iw.components.emplace_back(ComponentType::CREATURE, town->creatures.at(i).second.back(), event.creatures.at(i));

View File

@ -11,6 +11,8 @@
#include "VisitQueries.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapObjects/TownBuildingInstance.h"
#include "../CGameHandler.h"
#include "QueriesProcessor.h"
@ -29,7 +31,7 @@ bool VisitQuery::blocksPack(const CPack * pack) const
return true;
}
void VisitQuery::onExposure(QueryPtr topQuery)
void MapObjectVisitQuery::onExposure(QueryPtr topQuery)
{
//Object may have been removed and deleted.
if(gh->isValidObject(visitedObject))
@ -54,13 +56,31 @@ void MapObjectVisitQuery::onRemoval(PlayerColor color)
gh->removeObject(visitedObject, color);
}
TownBuildingVisitQuery::TownBuildingVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero, BuildingID buildingToVisit)
: VisitQuery(owner, Obj, Hero)
, visitedBuilding(buildingToVisit)
TownBuildingVisitQuery::TownBuildingVisitQuery(CGameHandler * owner, const CGTownInstance * Obj, std::vector<const CGHeroInstance *> heroes, std::vector<BuildingID> buildingToVisit)
: VisitQuery(owner, Obj, heroes.front())
, visitedTown(Obj)
{
// generate in reverse order - first building-hero pair to handle must be in the end of vector
for (auto const * hero : boost::adaptors::reverse(heroes))
for (auto const & building : boost::adaptors::reverse(buildingToVisit))
visitedBuilding.push_back({ hero, building});
}
void TownBuildingVisitQuery::onRemoval(PlayerColor color)
void TownBuildingVisitQuery::onExposure(QueryPtr topQuery)
{
onAdded(players.front());
}
void TownBuildingVisitQuery::onAdded(PlayerColor color)
{
while (!visitedBuilding.empty() && owner->topQuery(color).get() == this)
{
visitingHero = visitedBuilding.back().hero;
auto * building = visitedTown->rewardableBuildings.at(visitedBuilding.back().building);
building->onHeroVisit(visitingHero);
visitedBuilding.pop_back();
}
if (visitedBuilding.empty() && owner->topQuery(color).get() == this)
owner->popIfTop(*this);
}

View File

@ -11,19 +11,22 @@
#include "CQuery.h"
VCMI_LIB_NAMESPACE_BEGIN
class CGTownInstance;
VCMI_LIB_NAMESPACE_END
//Created when hero visits object.
//Removed when query above is resolved (or immediately after visit if no queries were created)
class VisitQuery : public CQuery
{
protected:
VisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero);
VisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero);
public:
const CGObjectInstance *visitedObject;
const CGHeroInstance *visitingHero;
const CGObjectInstance * visitedObject;
const CGHeroInstance * visitingHero;
bool blocksPack(const CPack *pack) const final;
void onExposure(QueryPtr topQuery) final;
bool blocksPack(const CPack * pack) const final;
};
class MapObjectVisitQuery final : public VisitQuery
@ -31,17 +34,26 @@ class MapObjectVisitQuery final : public VisitQuery
public:
bool removeObjectAfterVisit;
MapObjectVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero);
MapObjectVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero);
void onRemoval(PlayerColor color) final;
void onExposure(QueryPtr topQuery) final;
};
class TownBuildingVisitQuery final : public VisitQuery
{
struct BuildingVisit
{
const CGHeroInstance * hero;
BuildingID building;
};
const CGTownInstance * visitedTown;
std::vector<BuildingVisit> visitedBuilding;
public:
BuildingID visitedBuilding;
TownBuildingVisitQuery(CGameHandler * owner, const CGTownInstance * Obj, std::vector<const CGHeroInstance *> heroes, std::vector<BuildingID> buildingToVisit);
TownBuildingVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero, BuildingID buildingToVisit);
void onRemoval(PlayerColor color) final;
void onAdded(PlayerColor color) final;
void onExposure(QueryPtr topQuery) final;
};