mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-24 08:32:34 +02:00
commit
6af0edcf90
28
ChangeLog.md
28
ChangeLog.md
@ -1,3 +1,31 @@
|
||||
# 1.5.5 -> 1.5.6
|
||||
|
||||
# Stability
|
||||
* Fixed possible crash on transferring hero to next campaign scenario if hero has combined artifact some components of which can be transferred
|
||||
* Fixed possible crash on transferring hero to next campaign scenario that has creature with faction limiter in his army
|
||||
* Fixed possible crash on application shutdown due to incorrect destruction order of UI entities
|
||||
|
||||
# Multiplayer
|
||||
* Mod compatibility issues when joining a lobby room now use color coding to make them less easy to miss.
|
||||
* Incompatible mods are now placed before compatible mods when joining lobby room.
|
||||
* Fixed text overflow in online lobby interface
|
||||
* Fixed jittering simultaneous turns slider after moving it twice over short period
|
||||
|
||||
# Interface
|
||||
* Fixed some shortcuts that were not active during the enemy's turn, such as Thieves' Guild.
|
||||
* Game now correctly uses melee damage calculation when forcing a melee attack with a shooter.
|
||||
* Game will now close all open dialogs on start of our turn, to avoid bugs like locked right-click popups
|
||||
|
||||
# Map Objects
|
||||
* Spells the hero can't learn are no longer hidden when received from a rewardable object, such as the Pandora Box
|
||||
* Spells that cannot be learned are now displayed with gray text in the name of the spell.
|
||||
* Configurable objects with scouted state such as Witch Hut in HotA now correctly show their reward on right click after vising them but refusing to accept reward
|
||||
* Right-click tooltip on map dwelling now always shows produced creatures. Player that owns the dwelling can also see number of creatures available for recruit
|
||||
|
||||
# Modding
|
||||
* Fixed possible crash on invalid SPELL_LIKE_ATTACK bonus
|
||||
* Added compatibility check when loading maps with old names for boats
|
||||
|
||||
# 1.5.4 -> 1.5.5
|
||||
|
||||
* Fixed crash when advancing to the next scenario in campaigns when the hero not transferring has a combination artefact that can be transferred to the next scenario.
|
||||
|
@ -26,8 +26,8 @@ android {
|
||||
minSdk = qtMinSdkVersion as Integer
|
||||
targetSdk = qtTargetSdkVersion as Integer // ANDROID_TARGET_SDK_VERSION in the CMake project
|
||||
|
||||
versionCode 1550
|
||||
versionName "1.5.5"
|
||||
versionCode 1560
|
||||
versionName "1.5.6"
|
||||
|
||||
setProperty("archivesBaseName", "vcmi")
|
||||
}
|
||||
|
@ -169,40 +169,44 @@ void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::
|
||||
adventureInt.reset(new AdventureMapInterface());
|
||||
}
|
||||
|
||||
void CPlayerInterface::closeAllDialogs()
|
||||
{
|
||||
// remove all active dialogs that do not expect query answer
|
||||
for (;;)
|
||||
{
|
||||
auto adventureWindow = GH.windows().topWindow<AdventureMapInterface>();
|
||||
auto infoWindow = GH.windows().topWindow<CInfoWindow>();
|
||||
|
||||
if(adventureWindow != nullptr)
|
||||
break;
|
||||
|
||||
if(infoWindow && infoWindow->ID != QueryID::NONE)
|
||||
break;
|
||||
|
||||
if (infoWindow)
|
||||
infoWindow->close();
|
||||
else
|
||||
GH.windows().popWindows(1);
|
||||
}
|
||||
|
||||
if(castleInt)
|
||||
castleInt->close();
|
||||
|
||||
castleInt = nullptr;
|
||||
|
||||
// remove all pending dialogs that do not expect query answer
|
||||
vstd::erase_if(dialogs, [](const std::shared_ptr<CInfoWindow> & window){
|
||||
return window->ID == QueryID::NONE;
|
||||
});
|
||||
}
|
||||
|
||||
void CPlayerInterface::playerEndsTurn(PlayerColor player)
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
if (player == playerID)
|
||||
{
|
||||
makingTurn = false;
|
||||
|
||||
// remove all active dialogs that do not expect query answer
|
||||
for (;;)
|
||||
{
|
||||
auto adventureWindow = GH.windows().topWindow<AdventureMapInterface>();
|
||||
auto infoWindow = GH.windows().topWindow<CInfoWindow>();
|
||||
|
||||
if(adventureWindow != nullptr)
|
||||
break;
|
||||
|
||||
if(infoWindow && infoWindow->ID != QueryID::NONE)
|
||||
break;
|
||||
|
||||
if (infoWindow)
|
||||
infoWindow->close();
|
||||
else
|
||||
GH.windows().popWindows(1);
|
||||
}
|
||||
|
||||
if(castleInt)
|
||||
castleInt->close();
|
||||
|
||||
castleInt = nullptr;
|
||||
|
||||
// remove all pending dialogs that do not expect query answer
|
||||
vstd::erase_if(dialogs, [](const std::shared_ptr<CInfoWindow> & window){
|
||||
return window->ID == QueryID::NONE;
|
||||
});
|
||||
closeAllDialogs();
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,6 +288,7 @@ void CPlayerInterface::gamePause(bool pause)
|
||||
|
||||
void CPlayerInterface::yourTurn(QueryID queryID)
|
||||
{
|
||||
closeAllDialogs();
|
||||
CTutorialWindow::openWindowFirstTime(TutorialMode::TOUCH_ADVENTUREMAP);
|
||||
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
|
@ -203,6 +203,7 @@ public: // public interface for use by client via LOCPLINT access
|
||||
void performAutosave();
|
||||
void gamePause(bool pause);
|
||||
void endNetwork();
|
||||
void closeAllDialogs();
|
||||
|
||||
///returns true if all events are processed internally
|
||||
bool capturedAllEvents();
|
||||
|
@ -553,12 +553,12 @@ bool AdventureMapShortcuts::optionSpellcasting()
|
||||
|
||||
bool AdventureMapShortcuts::optionInMapView()
|
||||
{
|
||||
return state == EAdventureState::MAKING_TURN;
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN;
|
||||
}
|
||||
|
||||
bool AdventureMapShortcuts::optionInWorldView()
|
||||
{
|
||||
return state == EAdventureState::WORLD_VIEW;
|
||||
return state == EAdventureState::WORLD_VIEW || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN;
|
||||
}
|
||||
|
||||
bool AdventureMapShortcuts::optionSidePanelActive()
|
||||
|
@ -499,9 +499,12 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
||||
{
|
||||
const auto * attacker = owner.stacksController->getActiveStack();
|
||||
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
|
||||
int distance = attacker->position.isValid() ? owner.getBattle()->battleGetDistances(attacker, attacker->getPosition())[attackFromHex] : 0;
|
||||
DamageEstimation retaliation;
|
||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex, &retaliation);
|
||||
BattleAttackInfo attackInfo(attacker, targetStack, distance, false );
|
||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation);
|
||||
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
|
||||
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
|
||||
bool enemyMayBeKilled = estimation.kills.max == targetStack->getCount();
|
||||
@ -514,7 +517,8 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
||||
const auto * shooter = owner.stacksController->getActiveStack();
|
||||
|
||||
DamageEstimation retaliation;
|
||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(shooter, targetStack, shooter->getPosition(), &retaliation);
|
||||
BattleAttackInfo attackInfo(shooter, targetStack, 0, true );
|
||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation);
|
||||
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
|
||||
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
|
||||
return formatRangedAttack(estimation, targetStack->getName(), shooter->shots.available());
|
||||
|
@ -43,7 +43,7 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
|
||||
filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
|
||||
labelTitle = std::make_shared<CLabel>( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.login.title"));
|
||||
labelUsernameTitle = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.lobby.login.username"));
|
||||
labelUsername = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, loginAs.toString());
|
||||
labelUsername = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, loginAs.toString(), 265);
|
||||
backgroundUsername = std::make_shared<TransparentFilledRectangle>(Rect(10, 90, 264, 20), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64));
|
||||
inputUsername = std::make_shared<CTextInput>(Rect(15, 93, 260, 16), FONT_SMALL, ETextAlignment::CENTERLEFT, true);
|
||||
buttonLogin = std::make_shared<CButton>(Point(10, 180), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onLogin(); }, EShortcut::GLOBAL_ACCEPT);
|
||||
|
@ -37,7 +37,7 @@ GlobalLobbyRoomAccountCard::GlobalLobbyRoomAccountCard(const GlobalLobbyAccount
|
||||
pos.w = 130;
|
||||
pos.h = 40;
|
||||
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName);
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName, 120);
|
||||
labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, accountDescription.status);
|
||||
}
|
||||
|
||||
@ -56,9 +56,14 @@ GlobalLobbyRoomModCard::GlobalLobbyRoomModCard(const GlobalLobbyRoomModInfo & mo
|
||||
pos.h = 40;
|
||||
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
|
||||
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, modInfo.modName);
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, modInfo.modName, 190);
|
||||
labelVersion = std::make_shared<CLabel>(195, 30, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, modInfo.version);
|
||||
labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.mod.state." + statusToString.at(modInfo.status)));
|
||||
auto statusColor = Colors::RED;
|
||||
if(modInfo.status == ModVerificationStatus::FULL_MATCH)
|
||||
statusColor = ColorRGBA(128, 128, 128);
|
||||
else if(modInfo.status == ModVerificationStatus::VERSION_MISMATCH)
|
||||
statusColor = Colors::YELLOW;
|
||||
labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, statusColor, CGI->generaltexth->translate("vcmi.lobby.mod.state." + statusToString.at(modInfo.status)));
|
||||
}
|
||||
|
||||
static const std::string getJoinRoomErrorMessage(const GlobalLobbyRoom & roomDescription, const std::vector<GlobalLobbyRoomModInfo> & modVerificationList)
|
||||
@ -134,6 +139,13 @@ GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const s
|
||||
|
||||
modVerificationList.push_back(modInfo);
|
||||
}
|
||||
std::sort(modVerificationList.begin(), modVerificationList.end(), [](const GlobalLobbyRoomModInfo &a, const GlobalLobbyRoomModInfo &b)
|
||||
{
|
||||
if(a.status == b.status)
|
||||
return a.modName < b.modName;
|
||||
|
||||
return a.status < b.status;
|
||||
});
|
||||
|
||||
MetaString subtitleText;
|
||||
subtitleText.appendTextID("vcmi.lobby.preview.subtitle");
|
||||
@ -142,7 +154,7 @@ GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const s
|
||||
|
||||
filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
|
||||
labelTitle = std::make_shared<CLabel>( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.preview.title").toString());
|
||||
labelSubtitle = std::make_shared<CLabel>( pos.w / 2, 40, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, subtitleText.toString());
|
||||
labelSubtitle = std::make_shared<CLabel>( pos.w / 2, 40, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, subtitleText.toString(), 400);
|
||||
|
||||
labelVersionTitle = std::make_shared<CLabel>( 10, 60, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.preview.version").toString());
|
||||
labelVersionValue = std::make_shared<CLabel>( 10, 80, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::WHITE, roomDescription.gameVersion);
|
||||
|
@ -207,7 +207,7 @@ GlobalLobbyAccountCard::GlobalLobbyAccountCard(GlobalLobbyWindow * window, const
|
||||
: GlobalLobbyChannelCardBase(window, Point(130, 40), "player", accountDescription.accountID, accountDescription.displayName)
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName);
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName, 120);
|
||||
labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, accountDescription.status);
|
||||
}
|
||||
|
||||
@ -238,8 +238,8 @@ GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const Globa
|
||||
else
|
||||
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
|
||||
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName);
|
||||
labelDescription = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, roomDescription.description);
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName, 180);
|
||||
labelDescription = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, roomDescription.description, 160);
|
||||
labelRoomSize = std::make_shared<CLabel>(212, 10, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, roomSizeText.toString());
|
||||
labelRoomStatus = std::make_shared<CLabel>(225, 30, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, roomStatusText.toString());
|
||||
iconRoomSize = std::make_shared<CPicture>(ImagePath::builtin("lobby/iconPlayer"), Point(214, 5));
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "../eventsSDL/InputHandler.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../adventureMap/AdventureMapInterface.h"
|
||||
#include "../render/Colors.h"
|
||||
#include "../render/Graphics.h"
|
||||
#include "../render/IFont.h"
|
||||
@ -145,7 +146,13 @@ CGuiHandler::CGuiHandler()
|
||||
{
|
||||
}
|
||||
|
||||
CGuiHandler::~CGuiHandler() = default;
|
||||
CGuiHandler::~CGuiHandler()
|
||||
{
|
||||
// enforce deletion order on shutdown
|
||||
// all UI elements including adventure map must be destroyed before Gui Handler
|
||||
// proper solution would be removal of adventureInt global
|
||||
adventureInt.reset();
|
||||
}
|
||||
|
||||
ShortcutHandler & CGuiHandler::shortcuts()
|
||||
{
|
||||
|
@ -345,7 +345,8 @@ std::shared_ptr<CLabel> InterfaceObjectConfigurable::buildLabel(const JsonNode &
|
||||
auto color = readColor(config["color"]);
|
||||
auto text = readText(config["text"]);
|
||||
auto position = readPosition(config["position"]);
|
||||
return std::make_shared<CLabel>(position.x, position.y, font, alignment, color, text);
|
||||
auto maxWidth = config["maxWidth"].Integer();
|
||||
return std::make_shared<CLabel>(position.x, position.y, font, alignment, color, text, maxWidth);
|
||||
}
|
||||
|
||||
std::shared_ptr<CMultiLineLabel> InterfaceObjectConfigurable::buildMultiLineLabel(const JsonNode & config) const
|
||||
|
@ -340,10 +340,10 @@ void OptionsTabBase::recreate(bool campaign)
|
||||
|
||||
//Simultaneous turns
|
||||
if(auto turnSlider = widget<CSlider>("simturnsDurationMin"))
|
||||
turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.requiredTurns);
|
||||
turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.requiredTurns, false);
|
||||
|
||||
if(auto turnSlider = widget<CSlider>("simturnsDurationMax"))
|
||||
turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.optionalTurns);
|
||||
turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.optionalTurns, false);
|
||||
|
||||
if(auto w = widget<CLabel>("labelSimturnsDurationValueMin"))
|
||||
w->setText(generateSimturnsDurationText(SEL->getStartInfo()->simturnsInfo.requiredTurns));
|
||||
@ -388,7 +388,7 @@ void OptionsTabBase::recreate(bool campaign)
|
||||
auto & tpreset = variables["timerPresets"].Vector()[idx];
|
||||
if(tpreset.Vector().at(1).Integer() == turnTimerRemote.turnTimer / 1000)
|
||||
{
|
||||
turnSlider->scrollTo(idx);
|
||||
turnSlider->scrollTo(idx, false);
|
||||
if(auto w = widget<CLabel>("labelTurnDurationValue"))
|
||||
w->setText(CGI->generaltexth->turnDurations[idx]);
|
||||
}
|
||||
|
@ -290,7 +290,10 @@ std::string CComponent::getSubtitle() const
|
||||
return CGI->artifacts()->getById(data.subType.as<ArtifactID>())->getNameTranslated();
|
||||
case ComponentType::SPELL_SCROLL:
|
||||
case ComponentType::SPELL:
|
||||
return CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated();
|
||||
if (data.value < 0)
|
||||
return "{#A9A9A9|" + CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated() + "}";
|
||||
else
|
||||
return CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated();
|
||||
case ComponentType::NONE:
|
||||
case ComponentType::MORALE:
|
||||
case ComponentType::LUCK:
|
||||
|
@ -70,7 +70,7 @@ int CSlider::getValue() const
|
||||
return value;
|
||||
}
|
||||
|
||||
void CSlider::setValue(int to)
|
||||
void CSlider::setValue(int to, bool callCallbacks)
|
||||
{
|
||||
scrollTo(value);
|
||||
}
|
||||
@ -113,7 +113,7 @@ void CSlider::updateSliderPos()
|
||||
}
|
||||
}
|
||||
|
||||
void CSlider::scrollTo(int to)
|
||||
void CSlider::scrollTo(int to, bool callCallbacks)
|
||||
{
|
||||
vstd::amax(to, 0);
|
||||
vstd::amin(to, positions);
|
||||
@ -125,7 +125,8 @@ void CSlider::scrollTo(int to)
|
||||
|
||||
updateSliderPos();
|
||||
|
||||
moved(getValue());
|
||||
if (callCallbacks)
|
||||
moved(getValue());
|
||||
}
|
||||
|
||||
void CSlider::clickPressed(const Point & cursorPosition)
|
||||
@ -321,7 +322,7 @@ int SliderNonlinear::getValue() const
|
||||
return scaledValues.at(CSlider::getValue());
|
||||
}
|
||||
|
||||
void SliderNonlinear::setValue(int to)
|
||||
void SliderNonlinear::setValue(int to, bool callCallbacks)
|
||||
{
|
||||
size_t nearest = 0;
|
||||
|
||||
@ -334,5 +335,5 @@ void SliderNonlinear::setValue(int to)
|
||||
nearest = i;
|
||||
}
|
||||
|
||||
scrollTo(nearest);
|
||||
scrollTo(nearest, callCallbacks);
|
||||
}
|
||||
|
@ -52,14 +52,14 @@ public:
|
||||
void clearScrollBounds();
|
||||
|
||||
/// Value modifiers
|
||||
void scrollTo(int value);
|
||||
void scrollTo(int value, bool callCallbacks = true);
|
||||
void scrollBy(int amount) override;
|
||||
void scrollToMin();
|
||||
void scrollToMax();
|
||||
|
||||
/// Amount modifier
|
||||
void setAmount(int to);
|
||||
virtual void setValue(int to);
|
||||
virtual void setValue(int to, bool callCallbacks = true);
|
||||
|
||||
/// Accessors
|
||||
int getAmount() const;
|
||||
@ -95,7 +95,7 @@ class SliderNonlinear : public CSlider
|
||||
|
||||
using CSlider::setAmount; // make private
|
||||
public:
|
||||
void setValue(int to) override;
|
||||
void setValue(int to, bool callCallbacks) override;
|
||||
int getValue() const override;
|
||||
|
||||
SliderNonlinear(Point position, int length, const std::function<void(int)> & Moved, const std::vector<int> & values, int Value, Orientation orientation, EStyle style);
|
||||
|
@ -1,6 +1,6 @@
|
||||
set(VCMI_VERSION_MAJOR 1)
|
||||
set(VCMI_VERSION_MINOR 5)
|
||||
set(VCMI_VERSION_PATCH 5)
|
||||
set(VCMI_VERSION_PATCH 6)
|
||||
add_definitions(
|
||||
-DVCMI_VERSION_MAJOR=${VCMI_VERSION_MAJOR}
|
||||
-DVCMI_VERSION_MINOR=${VCMI_VERSION_MINOR}
|
||||
|
@ -155,18 +155,21 @@
|
||||
"types" : {
|
||||
"boatNecropolis" : {
|
||||
"index" : 0,
|
||||
"compatibilityIdentifiers" : [ "evil" ],
|
||||
"actualAnimation" : "AB01_.def",
|
||||
"overlayAnimation" : "ABM01_.def",
|
||||
"flagAnimations" : ["ABF01L", "ABF01G", "ABF01R", "ABF01D", "ABF01B", "ABF01P", "ABF01W", "ABF01K"]
|
||||
},
|
||||
"boatCastle" : {
|
||||
"index" : 1,
|
||||
"compatibilityIdentifiers" : [ "good" ],
|
||||
"actualAnimation" : "AB02_.def",
|
||||
"overlayAnimation" : "ABM02_.def",
|
||||
"flagAnimations" : ["ABF02L", "ABF02G", "ABF02R", "ABF02D", "ABF02B", "ABF02P", "ABF02W", "ABF02K"]
|
||||
},
|
||||
"boatFortress" : {
|
||||
"index" : 2,
|
||||
"compatibilityIdentifiers" : [ "neutral" ],
|
||||
"actualAnimation" : "AB03_.def",
|
||||
"overlayAnimation" : "ABM03_.def",
|
||||
"flagAnimations" : ["ABF03L", "ABF03G", "ABF03R", "ABF03D", "ABF03B", "ABF03P", "ABF03W", "ABF03K"]
|
||||
|
@ -43,7 +43,8 @@
|
||||
{
|
||||
"name" : "accountNameLabel",
|
||||
"type": "labelTitleMain",
|
||||
"position": {"x": 15, "y": 10}
|
||||
"position": {"x": 15, "y": 10},
|
||||
"maxWidth": 230
|
||||
},
|
||||
|
||||
{
|
||||
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -1,3 +1,9 @@
|
||||
vcmi (1.5.6) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Ivan Savenko <saven.ivan@gmail.com> Sun, 4 Aug 2024 12:00:00 +0200
|
||||
|
||||
vcmi (1.5.5) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
@ -461,7 +461,9 @@ Configurable object has following structure:
|
||||
|
||||
`"text"`: [text](#text),
|
||||
|
||||
`"position"`: [position](#position)
|
||||
`"position"`: [position](#position),
|
||||
|
||||
`"maxWidth"`: int` optional, trim longer text
|
||||
|
||||
### [VCMI-1.4] Multi-line label
|
||||
|
||||
|
@ -90,6 +90,7 @@
|
||||
</screenshots>
|
||||
<launchable type="desktop-id">vcmilauncher.desktop</launchable>
|
||||
<releases>
|
||||
<release version="1.5.6" date="2024-08-04" type="stable"/>
|
||||
<release version="1.5.5" date="2024-07-17" type="stable"/>
|
||||
<release version="1.5.4" date="2024-07-12" type="stable"/>
|
||||
<release version="1.5.3" date="2024-06-21" type="stable"/>
|
||||
|
@ -742,15 +742,15 @@ DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit *
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE({});
|
||||
auto reachability = battleGetDistances(attacker, attacker->getPosition());
|
||||
int getMovementRange = attackerPosition.isValid() ? reachability[attackerPosition] : 0;
|
||||
return battleEstimateDamage(attacker, defender, getMovementRange, retaliationDmg);
|
||||
int movementRange = attackerPosition.isValid() ? reachability[attackerPosition] : 0;
|
||||
return battleEstimateDamage(attacker, defender, movementRange, retaliationDmg);
|
||||
}
|
||||
|
||||
DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int getMovementRange, DamageEstimation * retaliationDmg) const
|
||||
DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementRange, DamageEstimation * retaliationDmg) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE({});
|
||||
const bool shooting = battleCanShoot(attacker, defender->getPosition());
|
||||
const BattleAttackInfo bai(attacker, defender, getMovementRange, shooting);
|
||||
const BattleAttackInfo bai(attacker, defender, movementRange, shooting);
|
||||
return battleEstimateDamage(bai, retaliationDmg);
|
||||
}
|
||||
|
||||
|
@ -32,18 +32,14 @@ public:
|
||||
int32_t getNum() const
|
||||
{
|
||||
int32_t result;
|
||||
|
||||
std::visit([&result] (const auto& v) { result = v.getNum(); }, value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string toString() const
|
||||
{
|
||||
std::string result;
|
||||
|
||||
std::visit([&result] (const auto& v) { result = v.encode(v.getNum()); }, value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -58,6 +54,13 @@ public:
|
||||
return IdentifierType();
|
||||
}
|
||||
|
||||
bool hasValue() const
|
||||
{
|
||||
bool result = false;
|
||||
std::visit([&result] (const auto& v) { result = v.hasValue(); }, value);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & value;
|
||||
|
@ -130,13 +130,15 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr
|
||||
if(!art)
|
||||
return false;
|
||||
|
||||
bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId());
|
||||
ArtifactLocation al(hero.hero->id, artifactPosition);
|
||||
|
||||
if (takeable)
|
||||
bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId());
|
||||
bool locked = hero.hero->getSlot(al.slot)->locked;
|
||||
|
||||
if (!locked && takeable)
|
||||
hero.transferrableArtifacts.push_back(artifactPosition);
|
||||
|
||||
ArtifactLocation al(hero.hero->id, artifactPosition);
|
||||
if(!takeable && !hero.hero->getSlot(al.slot)->locked) //don't try removing locked artifacts -> it crashes #1719
|
||||
if (!locked && !takeable)
|
||||
{
|
||||
hero.hero->getArt(al.slot)->removeFrom(*hero.hero, al.slot);
|
||||
return true;
|
||||
|
@ -346,15 +346,19 @@ void CGDwelling::newTurn(CRandomGenerator & rand) const
|
||||
|
||||
std::vector<Component> CGDwelling::getPopupComponents(PlayerColor player) const
|
||||
{
|
||||
if (getOwner() != player)
|
||||
return {};
|
||||
bool visitedByOwner = getOwner() == player;
|
||||
|
||||
std::vector<Component> result;
|
||||
|
||||
if (ID == Obj::CREATURE_GENERATOR1 && !creatures.empty())
|
||||
{
|
||||
for (auto const & creature : creatures.front().second)
|
||||
result.emplace_back(ComponentType::CREATURE, creature, creatures.front().first);
|
||||
{
|
||||
if (visitedByOwner)
|
||||
result.emplace_back(ComponentType::CREATURE, creature, creatures.front().first);
|
||||
else
|
||||
result.emplace_back(ComponentType::CREATURE, creature);
|
||||
}
|
||||
}
|
||||
|
||||
if (ID == Obj::CREATURE_GENERATOR4)
|
||||
@ -362,7 +366,12 @@ std::vector<Component> CGDwelling::getPopupComponents(PlayerColor player) const
|
||||
for (auto const & creatureLevel : creatures)
|
||||
{
|
||||
if (!creatureLevel.second.empty())
|
||||
result.emplace_back(ComponentType::CREATURE, creatureLevel.second.back(), creatureLevel.first);
|
||||
{
|
||||
if (visitedByOwner)
|
||||
result.emplace_back(ComponentType::CREATURE, creatureLevel.second.back(), creatureLevel.first);
|
||||
else
|
||||
result.emplace_back(ComponentType::CREATURE, creatureLevel.second.back());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -424,7 +433,7 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const
|
||||
if(count) //there are available creatures
|
||||
{
|
||||
|
||||
if (VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED))
|
||||
if (VLC->settings()->getBoolean(EGameSettings::DWELLINGS_MERGE_ON_RECRUIT))
|
||||
{
|
||||
SlotID testSlot = h->getSlotFor(crid);
|
||||
if(!testSlot.validSlot()) //no available slot - try merging army of visiting hero
|
||||
|
@ -1709,6 +1709,16 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
setHeroTypeName(typeName);
|
||||
}
|
||||
|
||||
if(!handler.saving)
|
||||
{
|
||||
if(!appearance)
|
||||
{
|
||||
// crossoverDeserialize
|
||||
type = getHeroType().toHeroType();
|
||||
appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
|
||||
}
|
||||
}
|
||||
|
||||
CArmedInstance::serializeJsonOptions(handler);
|
||||
|
||||
{
|
||||
@ -1724,13 +1734,6 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
|
||||
if(!handler.saving)
|
||||
{
|
||||
if(!appearance)
|
||||
{
|
||||
// crossoverDeserialize
|
||||
type = getHeroType().toHeroType();
|
||||
appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
|
||||
}
|
||||
|
||||
patrol.patrolling = (rawPatrolRadius > NO_PATROLING);
|
||||
patrol.initialPos = visitablePos();
|
||||
patrol.patrolRadius = (rawPatrolRadius > NO_PATROLING) ? rawPatrolRadius : 0;
|
||||
|
@ -181,7 +181,26 @@ void CRewardableObject::heroLevelUpDone(const CGHeroInstance *hero) const
|
||||
void CRewardableObject::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
|
||||
{
|
||||
if(answer == 0)
|
||||
{
|
||||
switch (configuration.visitMode)
|
||||
{
|
||||
case Rewardable::VISIT_UNLIMITED:
|
||||
case Rewardable::VISIT_BONUS:
|
||||
case Rewardable::VISIT_HERO:
|
||||
case Rewardable::VISIT_LIMITER:
|
||||
{
|
||||
// workaround for object with refusable reward not getting marked as visited
|
||||
// TODO: better solution that would also work for player-visitable objects
|
||||
if (!wasScouted(hero->getOwner()))
|
||||
{
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_TEAM, id, hero->id);
|
||||
cb->sendAndApply(&cov);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return; // player refused
|
||||
}
|
||||
|
||||
if(answer > 0 && answer-1 < configuration.info.size())
|
||||
{
|
||||
|
@ -115,8 +115,10 @@ void Rewardable::Reward::loadComponents(std::vector<Component> & comps, const CG
|
||||
comps.emplace_back(ComponentType::ARTIFACT, entry);
|
||||
|
||||
for(const auto & entry : spells)
|
||||
if (!h || h->canLearnSpell(entry.toEntity(VLC), true))
|
||||
comps.emplace_back(ComponentType::SPELL, entry);
|
||||
{
|
||||
bool learnable = !h || h->canLearnSpell(entry.toEntity(VLC), true);
|
||||
comps.emplace_back(ComponentType::SPELL, entry, learnable ? 0 : -1);
|
||||
}
|
||||
|
||||
for(const auto & entry : creatures)
|
||||
comps.emplace_back(ComponentType::CREATURE, entry.type->getId(), entry.count);
|
||||
|
@ -682,6 +682,15 @@ void CGameHandler::onNewTurn()
|
||||
}
|
||||
}
|
||||
|
||||
for (auto & player : gs->players)
|
||||
{
|
||||
if (player.second.status != EPlayerStatus::INGAME)
|
||||
continue;
|
||||
|
||||
if (player.second.heroes.empty() && player.second.towns.empty())
|
||||
throw std::runtime_error("Invalid player in player state! Player " + std::to_string(player.first.getNum()) + ", map name: " + gs->map->name.toString() + ", map description: " + gs->map->description.toString());
|
||||
}
|
||||
|
||||
if (newWeek && !firstTurn)
|
||||
{
|
||||
n.specialWeek = NewTurn::NORMAL;
|
||||
|
@ -494,7 +494,7 @@ bool BattleActionProcessor::doHealAction(const CBattleInfoCallback & battle, con
|
||||
else
|
||||
destStack = battle.battleGetUnitByPos(target.at(0).hexValue);
|
||||
|
||||
if(stack == nullptr || destStack == nullptr || !healerAbility || healerAbility->subtype == BonusSubtypeID())
|
||||
if(stack == nullptr || destStack == nullptr || !healerAbility || !healerAbility->subtype.hasValue())
|
||||
{
|
||||
gameHandler->complain("There is either no healer, no destination, or healer cannot heal :P");
|
||||
}
|
||||
@ -971,7 +971,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
|
||||
}
|
||||
|
||||
std::shared_ptr<const Bonus> bonus = attacker->getFirstBonus(Selector::type()(BonusType::SPELL_LIKE_ATTACK));
|
||||
if(bonus && ranged) //TODO: make it work in melee?
|
||||
if(bonus && ranged && bonus->subtype.hasValue()) //TODO: make it work in melee?
|
||||
{
|
||||
//this is need for displaying hit animation
|
||||
bat.flags |= BattleAttack::SPELL_LIKE;
|
||||
|
Loading…
Reference in New Issue
Block a user