1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-15 20:03:15 +02:00

Merge remote-tracking branch 'upstream/develop' into editor-improvements-1.4

# Conflicts:
#	mapeditor/inspector/inspector.cpp
This commit is contained in:
nordsoft
2023-09-08 17:58:14 +02:00
56 changed files with 365 additions and 145 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 934 B

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 914 B

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -31,7 +31,7 @@
"vcmi.capitalColors.7" : "Pink",
"vcmi.radialWheel.mergeSameUnit" : "Merge same creatures",
"vcmi.radialWheel.showUnitInformation" : "Show creature information",
"vcmi.radialWheel.fillSingleUnit" : "Fill with single creatures",
"vcmi.radialWheel.splitSingleUnit" : "Split off single creature",
"vcmi.radialWheel.splitUnitEqually" : "Split creatures equally",
"vcmi.radialWheel.moveUnit" : "Move creatures to another army",

View File

@@ -31,7 +31,6 @@
"vcmi.capitalColors.7" : "Rosa",
"vcmi.radialWheel.mergeSameUnit" : "Gleiche Kreaturen zusammenführen",
"vcmi.radialWheel.showUnitInformation" : "Informationen zur Kreatur anzeigen",
"vcmi.radialWheel.splitSingleUnit" : "Wegtrennen einzelner Kreaturen",
"vcmi.radialWheel.splitUnitEqually" : "Gleichmäßiges trennen der Kreaturen",
"vcmi.radialWheel.moveUnit" : "Verschieben der Kreatur in andere Armee",

View File

@@ -31,7 +31,6 @@
"vcmi.capitalColors.7" : "Różowy",
"vcmi.radialWheel.mergeSameUnit" : "Złącz takie same stworzenia",
"vcmi.radialWheel.showUnitInformation" : "Pokaż informacje o stworzeniu",
"vcmi.radialWheel.splitSingleUnit" : "Wydziel pojedyncze stworzenie",
"vcmi.radialWheel.splitUnitEqually" : "Podziel stworzenia równo",
"vcmi.radialWheel.moveUnit" : "Przenieś stworzenia do innej armii",

View File

@@ -31,7 +31,7 @@
"vcmi.capitalColors.7" : "Рожевий",
"vcmi.radialWheel.mergeSameUnit" : "Об'єднати однакових істот",
"vcmi.radialWheel.showUnitInformation" : "Показати відомості про істоту",
"vcmi.radialWheel.fillSingleUnit" : "Заповнити одиничними істотами",
"vcmi.radialWheel.splitSingleUnit" : "Відділити одну істоту",
"vcmi.radialWheel.splitUnitEqually" : "Розділити істот порівну",
"vcmi.radialWheel.moveUnit" : "Перемістити істоту до іншої армії",

View File

@@ -24,6 +24,7 @@
#include "../gui/CIntObject.h"
#include "../gui/WindowHandler.h"
#include "../windows/CCreatureWindow.h"
#include "../windows/InfoWindows.h"
#include "../../CCallback.h"
#include "../../lib/CConfigHandler.h"
@@ -607,10 +608,10 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
return false;
case PossiblePlayerBattleAction::ANY_LOCATION:
return isCastingPossibleHere(action.spell().toSpell(), targetStack, targetHex);
return isCastingPossibleHere(action.spell().toSpell(), nullptr, targetHex);
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
return !selectedStack && targetStack && isCastingPossibleHere(action.spell().toSpell(), targetStack, targetHex);
return !selectedStack && targetStack && isCastingPossibleHere(action.spell().toSpell(), nullptr, targetHex);
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
if(targetStack && targetStackOwned && targetStack != owner.stacksController->getActiveStack() && targetStack->alive()) //only positive spells for other allied creatures
@@ -628,7 +629,7 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
case PossiblePlayerBattleAction::OBSTACLE:
case PossiblePlayerBattleAction::FREE_LOCATION:
return isCastingPossibleHere(action.spell().toSpell(), targetStack, targetHex);
return isCastingPossibleHere(action.spell().toSpell(), nullptr, targetHex);
case PossiblePlayerBattleAction::CATAPULT:
return owner.siegeController && owner.siegeController->isAttackableByCatapult(targetHex);
@@ -1003,6 +1004,7 @@ void BattleActionsController::onHexRightClicked(BattleHex clickedHex)
if (spellcastingModeActive() || isCurrentStackInSpellcastMode)
{
endCastingSpell();
CRClickPopup::createAndPush(CGI->generaltexth->translate("core.genrltxt.731")); // spell cancelled
return;
}

View File

@@ -129,6 +129,9 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
attackCursors = GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT"));
attackCursors->preload();
spellCursors = GH.renderHandler().loadAnimation(AnimationPath::builtin("CRSPELL"));
spellCursors->preload();
initializeHexEdgeMaskToFrameIndex();
rangedFullDamageLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsGreen.json"));
@@ -889,10 +892,22 @@ void BattleFieldController::show(Canvas & to)
if (isActive() && isGesturing() && getHoveredHex() != BattleHex::INVALID)
{
auto cursorIndex = CCS->curh->get<Cursor::Combat>();
auto imageIndex = static_cast<size_t>(cursorIndex);
auto combatCursorIndex = CCS->curh->get<Cursor::Combat>();
if (combatCursorIndex)
{
auto combatImageIndex = static_cast<size_t>(*combatCursorIndex);
to.draw(attackCursors->getImage(combatImageIndex), hexPositionAbsolute(getHoveredHex()).center() - CCS->curh->getPivotOffsetCombat(combatImageIndex));
return;
}
auto spellCursorIndex = CCS->curh->get<Cursor::Spellcast>();
if (spellCursorIndex)
{
auto spellImageIndex = static_cast<size_t>(*spellCursorIndex);
to.draw(spellCursors->getImage(spellImageIndex), hexPositionAbsolute(getHoveredHex()).center() - CCS->curh->getPivotOffsetSpellcast());
return;
}
to.draw(attackCursors->getImage(imageIndex), hexPositionAbsolute(getHoveredHex()).center() - CCS->curh->getPivotOffsetCombat(imageIndex));
}
}

View File

@@ -38,6 +38,7 @@ class BattleFieldController : public CIntObject
std::shared_ptr<CAnimation> shootingRangeLimitImages;
std::shared_ptr<CAnimation> attackCursors;
std::shared_ptr<CAnimation> spellCursors;
/// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack
std::unique_ptr<Canvas> backgroundWithHexes;

View File

@@ -50,10 +50,10 @@ void InputSourceMouse::handleEventMouseButtonDown(const SDL_MouseButtonEvent & b
if(button.clicks > 1)
GH.events().dispatchMouseDoubleClick(position);
else
GH.events().dispatchMouseLeftButtonPressed(position);
GH.events().dispatchMouseLeftButtonPressed(position, 0);
break;
case SDL_BUTTON_RIGHT:
GH.events().dispatchShowPopup(position);
GH.events().dispatchShowPopup(position, 0);
break;
case SDL_BUTTON_MIDDLE:
middleClickPosition = position;
@@ -74,7 +74,7 @@ void InputSourceMouse::handleEventMouseButtonUp(const SDL_MouseButtonEvent & but
switch(button.button)
{
case SDL_BUTTON_LEFT:
GH.events().dispatchMouseLeftButtonReleased(position);
GH.events().dispatchMouseLeftButtonReleased(position, 0);
break;
case SDL_BUTTON_RIGHT:
GH.events().dispatchClosePopup(position);

View File

@@ -39,6 +39,7 @@ InputSourceTouch::InputSourceTouch()
params.relativeModeSpeedFactor = settings["general"]["relativePointerSpeedMultiplier"].Float();
params.longTouchTimeMilliseconds = settings["general"]["longTouchTimeMilliseconds"].Float();
params.hapticFeedbackEnabled = settings["general"]["hapticFeedback"].Bool();
params.touchToleranceDistance = settings["input"]["touchToleranceDistance"].Float();
if (params.useRelativeMode)
state = TouchState::RELATIVE_MODE;
@@ -121,9 +122,9 @@ void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinge
if(tfinger.x > 0.5)
{
if (tfinger.y < 0.5)
GH.events().dispatchShowPopup(GH.getCursorPosition());
GH.events().dispatchShowPopup(GH.getCursorPosition(), params.touchToleranceDistance);
else
GH.events().dispatchMouseLeftButtonPressed(GH.getCursorPosition());
GH.events().dispatchMouseLeftButtonPressed(GH.getCursorPosition(), params.touchToleranceDistance);
}
break;
}
@@ -168,7 +169,7 @@ void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger)
if (tfinger.y < 0.5)
GH.events().dispatchClosePopup(GH.getCursorPosition());
else
GH.events().dispatchMouseLeftButtonReleased(GH.getCursorPosition());
GH.events().dispatchMouseLeftButtonReleased(GH.getCursorPosition(), params.touchToleranceDistance);
}
break;
}
@@ -180,8 +181,8 @@ void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger)
case TouchState::TAP_DOWN_SHORT:
{
GH.input().setCursorPosition(convertTouchToMouse(tfinger));
GH.events().dispatchMouseLeftButtonPressed(convertTouchToMouse(tfinger));
GH.events().dispatchMouseLeftButtonReleased(convertTouchToMouse(tfinger));
GH.events().dispatchMouseLeftButtonPressed(convertTouchToMouse(tfinger), params.touchToleranceDistance);
GH.events().dispatchMouseLeftButtonReleased(convertTouchToMouse(tfinger), params.touchToleranceDistance);
state = TouchState::IDLE;
break;
}
@@ -230,7 +231,7 @@ void InputSourceTouch::handleUpdate()
uint32_t currentTime = SDL_GetTicks();
if (currentTime > lastTapTimeTicks + params.longTouchTimeMilliseconds)
{
GH.events().dispatchShowPopup(GH.getCursorPosition());
GH.events().dispatchShowPopup(GH.getCursorPosition(), params.touchToleranceDistance);
if (GH.windows().isTopWindowPopup())
{

View File

@@ -78,6 +78,9 @@ struct TouchInputParameters
/// gesture will be qualified as pinch if distance between fingers is at least specified here
uint32_t pinchSensitivityThreshold = 10;
/// touch event will trigger clicking of elements up to X pixels away from actual touch position
uint32_t touchToleranceDistance = 20;
bool useRelativeMode = false;
bool hapticFeedbackEnabled = false;

View File

@@ -263,6 +263,11 @@ bool CIntObject::receiveEvent(const Point & position, int eventType) const
return pos.isInside(position);
}
const Rect & CIntObject::getPosition() const
{
return pos;
}
void CIntObject::onScreenResize()
{
center(pos, true);

View File

@@ -111,6 +111,8 @@ public:
/// by default, usedEvents inside UI elements are always handled
bool receiveEvent(const Point & position, int eventType) const override;
const Rect & getPosition() const override;
const Rect & center(const Rect &r, bool propagate = true); //sets pos so that r will be in the center of screen, assigns sizes of r to pos, returns new position
const Rect & center(const Point &p, bool propagate = true); //moves object so that point p will be in its center
const Rect & center(bool propagate = true); //centers when pos.w and pos.h are set, returns new position

View File

@@ -126,7 +126,6 @@ class CursorHandler final
void changeGraphic(Cursor::Type type, size_t index);
Point getPivotOffsetSpellcast();
Point getPivotOffset();
void updateSpellcastCursor();
@@ -154,7 +153,7 @@ public:
/// Returns current index of cursor
template<typename Index>
Index get()
std::optional<Index> get()
{
bool typeValid = true;
@@ -165,9 +164,10 @@ public:
if (typeValid)
return static_cast<Index>(frame);
return Index::POINTER;
return std::nullopt;
}
Point getPivotOffsetSpellcast();
Point getPivotOffsetDefault(size_t index);
Point getPivotOffsetMap(size_t index);
Point getPivotOffsetCombat(size_t index);

View File

@@ -16,7 +16,7 @@
#include "MouseButton.h"
#include "WindowHandler.h"
#include "../../lib/Point.h"
#include "../../lib/Rect.h"
template<typename Functor>
void EventDispatcher::processLists(ui16 activityFlag, const Functor & cb)
@@ -134,28 +134,64 @@ void EventDispatcher::dispatchMouseDoubleClick(const Point & position)
}
if(!doubleClicked)
handleLeftButtonClick(position, true);
handleLeftButtonClick(position, 0, true);
}
void EventDispatcher::dispatchMouseLeftButtonPressed(const Point & position)
void EventDispatcher::dispatchMouseLeftButtonPressed(const Point & position, int tolerance)
{
handleLeftButtonClick(position, true);
handleLeftButtonClick(position, tolerance, true);
}
void EventDispatcher::dispatchMouseLeftButtonReleased(const Point & position)
void EventDispatcher::dispatchMouseLeftButtonReleased(const Point & position, int tolerance)
{
handleLeftButtonClick(position, false);
handleLeftButtonClick(position, tolerance, false);
}
void EventDispatcher::dispatchShowPopup(const Point & position)
AEventsReceiver * EventDispatcher::findElementInToleranceRange(const EventReceiversList & list, const Point & position, int eventToTest, int tolerance)
{
AEventsReceiver * bestElement = nullptr;
int bestDistance = std::numeric_limits<int>::max();
for(auto & i : list)
{
// if there is element that can actually receive event then tolerance clicking is disabled
if( i->receiveEvent(position, eventToTest))
return nullptr;
if (i->getPosition().distanceTo(position) > bestDistance)
continue;
Point center = i->getPosition().center();
Point distance = center - position;
if (distance.lengthSquared() == 0)
continue;
Point moveDelta = distance * tolerance / distance.length();
Point testPosition = position + moveDelta;
if( !i->receiveEvent(testPosition, eventToTest))
continue;
bestElement = i;
bestDistance = i->getPosition().distanceTo(position);
}
return bestElement;
}
void EventDispatcher::dispatchShowPopup(const Point & position, int tolerance)
{
AEventsReceiver * nearestElement = findElementInToleranceRange(rclickable, position, AEventsReceiver::LCLICK, tolerance);
auto hlp = rclickable;
for(auto & i : hlp)
{
if(!vstd::contains(rclickable, i))
continue;
if( !i->receiveEvent(position, AEventsReceiver::LCLICK))
if( !i->receiveEvent(position, AEventsReceiver::SHOW_POPUP) && i != nearestElement)
continue;
i->showPopupWindow(position);
@@ -170,7 +206,7 @@ void EventDispatcher::dispatchClosePopup(const Point & position)
assert(!GH.windows().isTopWindowPopup());
}
void EventDispatcher::handleLeftButtonClick(const Point & position, bool isPressed)
void EventDispatcher::handleLeftButtonClick(const Point & position, int tolerance, bool isPressed)
{
// WARNING: this approach is NOT SAFE
// 1) We allow (un)registering elements when list itself is being processed/iterated
@@ -181,13 +217,15 @@ void EventDispatcher::handleLeftButtonClick(const Point & position, bool isPress
// 3) new element is created *with exactly same address(!)
// 4) new element is registered and code will incorrectly assume that this element is still registered
// POSSIBLE SOLUTION: make EventReceivers inherit from create_shared_from this and store weak_ptr's in lists
AEventsReceiver * nearestElement = findElementInToleranceRange(lclickable, position, AEventsReceiver::LCLICK, tolerance);
auto hlp = lclickable;
for(auto & i : hlp)
{
if(!vstd::contains(lclickable, i))
continue;
if( i->receiveEvent(position, AEventsReceiver::LCLICK))
if( i->receiveEvent(position, AEventsReceiver::LCLICK) || i == nearestElement)
{
if(isPressed)
i->clickPressed(position);

View File

@@ -35,8 +35,8 @@ class EventDispatcher
EventReceiversList textInterested;
EventReceiversList panningInterested;
void handleLeftButtonClick(const Point & position, bool isPressed);
void handleLeftButtonClick(const Point & position, int tolerance, bool isPressed);
AEventsReceiver * findElementInToleranceRange(const EventReceiversList & list, const Point & position, int eventToTest, int tolerance);
template<typename Functor>
void processLists(ui16 activityFlag, const Functor & cb);
@@ -56,15 +56,15 @@ public:
void dispatchShortcutReleased(const std::vector<EShortcut> & shortcuts);
/// Mouse events
void dispatchMouseLeftButtonPressed(const Point & position);
void dispatchMouseLeftButtonReleased(const Point & position);
void dispatchMouseLeftButtonPressed(const Point & position, int tolerance);
void dispatchMouseLeftButtonReleased(const Point & position, int tolerance);
void dispatchMouseScrolled(const Point & distance, const Point & position);
void dispatchMouseDoubleClick(const Point & position);
void dispatchMouseMoved(const Point & distance, const Point & position);
void dispatchMouseDragged(const Point & currentPosition, const Point & lastUpdateDistance);
void dispatchShowPopup(const Point & position);
void dispatchShowPopup(const Point & position, int tolerance);
void dispatchClosePopup(const Point & position);
void dispatchGesturePanningStarted(const Point & initialPosition);

View File

@@ -11,6 +11,7 @@
VCMI_LIB_NAMESPACE_BEGIN
class Point;
class Rect;
VCMI_LIB_NAMESPACE_END
class EventDispatcher;
@@ -39,6 +40,8 @@ protected:
/// If true, event of selected type in selected position will be processed by this element
virtual bool receiveEvent(const Point & position, int eventType) const= 0;
virtual const Rect & getPosition() const= 0;
public:
virtual void clickPressed(const Point & cursorPosition) {}
virtual void clickReleased(const Point & cursorPosition) {}

View File

@@ -9,4 +9,4 @@
*/
#pragma once
enum class ETextAlignment {TOPLEFT, CENTER, BOTTOMRIGHT};
enum class ETextAlignment {TOPLEFT, TOPCENTER, CENTER, BOTTOMRIGHT};

View File

@@ -151,6 +151,7 @@ void Canvas::drawText(const Point & position, const EFonts & font, const ColorRG
switch (alignment)
{
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLeft (surface, text, colorDest, renderArea.topLeft() + position);
case ETextAlignment::TOPCENTER: return graphics->fonts[font]->renderTextCenter(surface, text, colorDest, renderArea.topLeft() + position);
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextCenter(surface, text, colorDest, renderArea.topLeft() + position);
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextRight (surface, text, colorDest, renderArea.topLeft() + position);
}
@@ -161,6 +162,7 @@ void Canvas::drawText(const Point & position, const EFonts & font, const ColorRG
switch (alignment)
{
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLinesLeft (surface, text, colorDest, renderArea.topLeft() + position);
case ETextAlignment::TOPCENTER: return graphics->fonts[font]->renderTextLinesCenter(surface, text, colorDest, renderArea.topLeft() + position);
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextLinesCenter(surface, text, colorDest, renderArea.topLeft() + position);
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextLinesRight (surface, text, colorDest, renderArea.topLeft() + position);
}

View File

@@ -117,6 +117,13 @@ void CButton::setState(ButtonState newState)
{
if (state == newState)
return;
if (newState == BLOCKED)
removeUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD);
else
addUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD);
state = newState;
update();
}

View File

@@ -11,12 +11,9 @@
#include "CArtifactsOfHeroBackpack.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "Buttons.h"
#include "Images.h"
#include "GameSettings.h"
#include "IHandlerBase.h"
#include "ObjectLists.h"
#include "../CPlayerInterface.h"
@@ -37,21 +34,12 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(const Point & position)
backpack.resize(visibleCapacityMax);
size_t artPlaceIdx = 0;
const int slotSizeWithMargin = 46;
for(int i = 0; i < visibleCapacityMax; i++)
{
auto artifactSlotBackground = std::make_shared<CPicture>( ImagePath::builtin("heroWindow/artifactSlotEmpty"),
Point(slotSizeWithMargin * (i % HERO_BACKPACK_WINDOW_SLOT_COLUMNS), slotSizeWithMargin * (i / HERO_BACKPACK_WINDOW_SLOT_COLUMNS)));
backpackSlotsBackgrounds.emplace_back(artifactSlotBackground);
}
for(auto & artPlace : backpack)
{
artPlace = std::make_shared<CHeroArtPlace>(
Point(slotSizeWithMargin * (artPlaceIdx % HERO_BACKPACK_WINDOW_SLOT_COLUMNS), slotSizeWithMargin * (artPlaceIdx / HERO_BACKPACK_WINDOW_SLOT_COLUMNS)));
const auto pos = Point(slotSizeWithMargin * (artPlaceIdx % HERO_BACKPACK_WINDOW_SLOT_COLUMNS),
slotSizeWithMargin * (artPlaceIdx / HERO_BACKPACK_WINDOW_SLOT_COLUMNS));
backpackSlotsBackgrounds.emplace_back(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/artifactSlotEmpty"), pos));
artPlace = std::make_shared<CHeroArtPlace>(pos);
artPlace->setArtifact(nullptr);
artPlace->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
artPlace->rightClickCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
@@ -70,8 +58,18 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(const Point & position)
};
backpackListBox = std::make_shared<CListBoxWithCallback>(
posMoved, onCreate, Point(0, 0), Point(0, 0), HERO_BACKPACK_WINDOW_SLOT_ROWS, 0, 0, 1,
Rect(HERO_BACKPACK_WINDOW_SLOT_COLUMNS * slotSizeWithMargin + 10, 0, HERO_BACKPACK_WINDOW_SLOT_ROWS * slotSizeWithMargin - 5, 0));
Rect(HERO_BACKPACK_WINDOW_SLOT_COLUMNS * slotSizeWithMargin + sliderPosOffsetX, 0, HERO_BACKPACK_WINDOW_SLOT_ROWS * slotSizeWithMargin - 2, 0));
}
pos.w = visibleCapacityMax > HERO_BACKPACK_WINDOW_SLOT_COLUMNS ? HERO_BACKPACK_WINDOW_SLOT_COLUMNS : visibleCapacityMax;
pos.w *= slotSizeWithMargin;
if(backpackListBox)
pos.w += sliderPosOffsetX + 16; // 16 is slider width. TODO: get it from CListBox directly;
pos.h = (visibleCapacityMax / HERO_BACKPACK_WINDOW_SLOT_COLUMNS);
if(visibleCapacityMax % HERO_BACKPACK_WINDOW_SLOT_COLUMNS != 0)
pos.h += 1;
pos.h *= slotSizeWithMargin;
}
void CArtifactsOfHeroBackpack::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc)
@@ -88,7 +86,7 @@ void CArtifactsOfHeroBackpack::pickUpArtifact(CHeroArtPlace & artPlace)
void CArtifactsOfHeroBackpack::scrollBackpack(int offset)
{
if(backpackListBox)
backpackListBox->resize(getActiveSlotLinesNum());
backpackListBox->resize(getActiveSlotRowsNum());
backpackPos += offset;
auto slot = ArtifactPosition::BACKPACK_START + backpackPos;
for(auto artPlace : backpack)
@@ -102,11 +100,11 @@ void CArtifactsOfHeroBackpack::scrollBackpack(int offset)
void CArtifactsOfHeroBackpack::updateBackpackSlots()
{
if(backpackListBox)
backpackListBox->resize(getActiveSlotLinesNum());
backpackListBox->resize(getActiveSlotRowsNum());
CArtifactsOfHeroBase::updateBackpackSlots();
}
size_t CArtifactsOfHeroBackpack::getActiveSlotLinesNum()
size_t CArtifactsOfHeroBackpack::getActiveSlotRowsNum()
{
return (curHero->artifactsInBackpack.size() + HERO_BACKPACK_WINDOW_SLOT_COLUMNS - 1) / HERO_BACKPACK_WINDOW_SLOT_COLUMNS;
}

View File

@@ -27,11 +27,13 @@ public:
void pickUpArtifact(CHeroArtPlace & artPlace);
void scrollBackpack(int offset) override;
void updateBackpackSlots() override;
size_t getActiveSlotLinesNum();
size_t getActiveSlotRowsNum();
private:
std::shared_ptr<CListBoxWithCallback> backpackListBox;
std::vector<std::shared_ptr<CPicture>> backpackSlotsBackgrounds;
const size_t HERO_BACKPACK_WINDOW_SLOT_COLUMNS = 8;
const size_t HERO_BACKPACK_WINDOW_SLOT_ROWS = 8;
const int slotSizeWithMargin = 46;
const int sliderPosOffsetX = 10;
};

View File

@@ -369,14 +369,15 @@ void CGarrisonSlot::gesture(bool on, const Point & initialPosition, const Point
std::vector<RadialMenuConfig> menuElements = {
{ RadialMenuConfig::ITEM_NW, hasSameUnit, "stackMerge", "vcmi.radialWheel.mergeSameUnit", [this](){owner->bulkMergeStacks(this);} },
{ RadialMenuConfig::ITEM_NE, stackExists, "stackInfo", "vcmi.radialWheel.showUnitInformation", [this](){viewInfo();} },
{ RadialMenuConfig::ITEM_NE, hasOwnEmptySlots, "stackFillOne", "vcmi.radialWheel.fillSingleUnit", [this](){owner->bulkSplitStack(this);} },
{ RadialMenuConfig::ITEM_WW, hasOwnEmptySlots, "stackSplitOne", "vcmi.radialWheel.splitSingleUnit", [this](){splitIntoParts(this->getGarrison(), 1); } },
{ RadialMenuConfig::ITEM_EE, hasOwnEmptySlots, "stackSplitEqual", "vcmi.radialWheel.splitUnitEqually", [this](){owner->bulkSmartSplitStack(this);} },
{ RadialMenuConfig::ITEM_SW, hasOtherEmptySlots, "heroMove", "vcmi.radialWheel.moveUnit", [this](){owner->moveStackToAnotherArmy(this);} },
{ RadialMenuConfig::ITEM_SE, hasAnyEmptySlots, "heroSwap", "vcmi.radialWheel.splitUnit", [this](){ owner->selectSlot(this); owner->splitClick();} },
};
GH.windows().createAndPushWindow<RadialMenu>(pos.center(), menuElements);
if (hasAnyEmptySlots || hasSameUnit)
GH.windows().createAndPushWindow<RadialMenu>(pos.center(), menuElements);
}
void CGarrisonSlot::update()

View File

@@ -17,11 +17,13 @@
#include "../CPlayerInterface.h"
#include "../CGameInfo.h"
#include "../PlayerLocalState.h"
#include "../widgets/TextControls.h"
#include "../widgets/CGarrisonInt.h"
#include "../windows/CCastleInterface.h"
#include "../windows/InfoWindows.h"
#include "../render/Canvas.h"
#include "../render/Graphics.h"
#include "../../CCallback.h"
@@ -30,6 +32,7 @@
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/GameSettings.h"
#include "../../lib/TextOperations.h"
#include "../../lib/mapObjects/CGCreature.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/CGTownInstance.h"
@@ -447,6 +450,27 @@ void CInteractableTownTooltip::init(const InfoAboutTown & town)
}
}
CreatureTooltip::CreatureTooltip(Point pos, const CGCreature * creature)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
auto creatureData = (*CGI->creh)[creature->stacks.begin()->second->getCreatureID()].get();
creatureImage = std::make_shared<CAnimImage>(graphics->getAnimation(AnimationPath::builtin("TWCRPORT")), creatureData->getIconIndex());
creatureImage->center(Point(parent->pos.x + parent->pos.w / 2, parent->pos.y + creatureImage->pos.h / 2 + 11));
bool isHeroSelected = LOCPLINT->localState->getCurrentHero() != nullptr;
std::string textContent = isHeroSelected
? creature->getHoverText(LOCPLINT->localState->getCurrentHero())
: creature->getHoverText(LOCPLINT->playerID);
//TODO: window is bigger than OH3
//TODO: vertical alignment does not match H3. Commented below example that matches H3 for creatures count but supports only 1 line:
/*std::shared_ptr<CLabel> = std::make_shared<CLabel>(parent->pos.w / 2, 103,
FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, creature->getHoverText(LOCPLINT->playerID));*/
tooltipTextbox = std::make_shared<CTextBox>(textContent, Rect(15, 95, 230, 150), 0, FONT_SMALL, ETextAlignment::TOPCENTER, Colors::WHITE);
}
void MoraleLuckBox::set(const AFactionMember * node)
{
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);

View File

@@ -14,6 +14,7 @@
VCMI_LIB_NAMESPACE_BEGIN
class CGGarrison;
class CGCreature;
struct InfoAboutArmy;
struct InfoAboutHero;
struct InfoAboutTown;
@@ -25,6 +26,7 @@ class AFactionMember;
VCMI_LIB_NAMESPACE_END
class CLabel;
class CTextBox;
class CGarrisonInt;
class CCreatureAnim;
class CComponent;
@@ -151,6 +153,15 @@ public:
void setAmount(int newAmount);
};
class CreatureTooltip : public CIntObject
{
std::shared_ptr<CAnimImage> creatureImage;
std::shared_ptr<CTextBox> tooltipTextbox;
public:
CreatureTooltip(Point pos, const CGCreature * creature);
};
/// Resource bar like that at the bottom of the adventure map screen
class CMinorResDataBar : public CIntObject
{

View File

@@ -161,6 +161,12 @@ void CTextContainer::blitLine(Canvas & to, Rect destRect, std::string what)
where.y += getBorderSize().y;
}
if(alignment == ETextAlignment::TOPCENTER)
{
where.x += (int(destRect.w) - int(f->getStringWidth(what) - delimitersCount)) / 2;
where.y += getBorderSize().y;
}
if(alignment == ETextAlignment::CENTER)
{
where.x += (int(destRect.w) - int(f->getStringWidth(what) - delimitersCount)) / 2;
@@ -271,6 +277,7 @@ Rect CMultiLineLabel::getTextLocation()
switch(alignment)
{
case ETextAlignment::TOPLEFT: return Rect(pos.topLeft(), textSize);
case ETextAlignment::TOPCENTER: return Rect(pos.topLeft(), textSize);
case ETextAlignment::CENTER: return Rect(pos.topLeft() + textOffset / 2, textSize);
case ETextAlignment::BOTTOMRIGHT: return Rect(pos.topLeft() + textOffset, textSize);
}
@@ -480,6 +487,7 @@ Point CGStatusBar::getBorderSize()
switch(alignment)
{
case ETextAlignment::TOPLEFT: return Point(borderSize.x, borderSize.y);
case ETextAlignment::TOPCENTER: return Point(pos.w / 2, borderSize.y);
case ETextAlignment::CENTER: return Point(pos.w / 2, pos.h / 2);
case ETextAlignment::BOTTOMRIGHT: return Point(pos.w - borderSize.x, pos.h - borderSize.y);
}

View File

@@ -15,6 +15,7 @@
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../render/Canvas.h"
#include "../widgets/Buttons.h"
#include "../widgets/CArtifactHolder.h"
#include "../widgets/CComponent.h"
@@ -83,7 +84,6 @@ public:
{
}
std::string getName() const
{
if(commander)
@@ -95,11 +95,14 @@ private:
};
CCommanderSkillIcon::CCommanderSkillIcon(std::shared_ptr<CIntObject> object_, std::function<void()> callback)
CCommanderSkillIcon::CCommanderSkillIcon(std::shared_ptr<CIntObject> object_, bool isGrandmasterAbility_, std::function<void()> callback)
: object(),
isGrandmasterAbility(isGrandmasterAbility_),
isSelected(false),
callback(callback)
{
pos = object_->pos;
this->isGrandmasterAbility = isGrandmasterAbility_;
setObject(object_);
}
@@ -116,6 +119,25 @@ void CCommanderSkillIcon::setObject(std::shared_ptr<CIntObject> newObject)
void CCommanderSkillIcon::clickPressed(const Point & cursorPosition)
{
callback();
isSelected = true;
}
void CCommanderSkillIcon::deselect()
{
isSelected = false;
}
bool CCommanderSkillIcon::getIsGrandmasterAbility()
{
return isGrandmasterAbility;
}
void CCommanderSkillIcon::show(Canvas &to)
{
CIntObject::show(to);
if(isGrandmasterAbility && isSelected)
to.drawBorder(pos, Colors::YELLOW, 2);
}
static ImagePath skillToFile(int skill, int level, bool selected)
@@ -375,7 +397,7 @@ CStackWindow::CommanderMainSection::CommanderMainSection(CStackWindow * owner, i
{
Point skillPos = getSkillPos(index);
auto icon = std::make_shared<CCommanderSkillIcon>(std::make_shared<CPicture>(getSkillImage(index), skillPos.x, skillPos.y), [=]()
auto icon = std::make_shared<CCommanderSkillIcon>(std::make_shared<CPicture>(getSkillImage(index), skillPos.x, skillPos.y), false, [=]()
{
LOCPLINT->showInfoDialog(getSkillDescription(index));
});
@@ -429,7 +451,7 @@ CStackWindow::CommanderMainSection::CommanderMainSection(CStackWindow * owner, i
{
const auto bonus = CGI->creh->skillRequirements[skillID-100].first;
const CStackInstance * stack = parent->info->commander;
auto icon = std::make_shared<CCommanderSkillIcon>(std::make_shared<CPicture>(stack->bonusToGraphics(bonus)), [](){});
auto icon = std::make_shared<CCommanderSkillIcon>(std::make_shared<CPicture>(stack->bonusToGraphics(bonus)), true, [](){});
icon->callback = [=]()
{
parent->setSelection(skillID, icon);
@@ -446,6 +468,7 @@ CStackWindow::CommanderMainSection::CommanderMainSection(CStackWindow * owner, i
};
abilities = std::make_shared<CListBox>(onCreate, Point(38, 3+pos.h), Point(63, 0), 6, abilitiesCount);
abilities->setRedrawParent(true);
leftBtn = std::make_shared<CButton>(Point(10, pos.h + 6), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(), [=](){ abilities->moveToPrev(); }, EShortcut::MOVE_LEFT);
rightBtn = std::make_shared<CButton>(Point(411, pos.h + 6), AnimationPath::builtin("hsbtns5.def"), CButton::tooltip(), [=](){ abilities->moveToNext(); }, EShortcut::MOVE_RIGHT);
@@ -896,13 +919,22 @@ void CStackWindow::setSelection(si32 newSkill, std::shared_ptr<CCommanderSkillIc
selectedIcon->setObject(std::make_shared<CPicture>(getSkillImage(oldSelection)));
if(selectedIcon)
selectedIcon->text = getSkillDescription(oldSelection, false); //update previously selected icon's message to existing skill level
{
if(!selectedIcon->getIsGrandmasterAbility()) //unlike WoG, in VCMI grandmaster skill descriptions are taken from bonus descriptions
{
selectedIcon->text = getSkillDescription(oldSelection, false); //update previously selected icon's message to existing skill level
}
selectedIcon->deselect();
}
selectedIcon = newIcon; // update new selection
if(newSkill < 100)
{
newIcon->setObject(std::make_shared<CPicture>(getSkillImage(newSkill)));
newIcon->text = getSkillDescription(newSkill, true); //update currently selected icon's message to show upgrade description
if(!newIcon->getIsGrandmasterAbility())
{
newIcon->text = getSkillDescription(newSkill, true); //update currently selected icon's message to show upgrade description
}
}
}

View File

@@ -33,14 +33,20 @@ class CCommanderArtPlace;
class CCommanderSkillIcon : public LRClickableAreaWText //TODO: maybe bring commander skill button initialization logic inside?
{
std::shared_ptr<CIntObject> object; // passive object that will be used to determine clickable area
bool isGrandmasterAbility; // refers to WoG abilities obtainable via combining grandmaster skills (for example attack + speed unlocks shoot)
bool isSelected; // used only for programatically created border around selected "grandmaster abilities"
public:
CCommanderSkillIcon(std::shared_ptr<CIntObject> object_, std::function<void()> callback);
CCommanderSkillIcon(std::shared_ptr<CIntObject> object_, bool isGrandmasterAbility, std::function<void()> callback);
std::function<void()> callback;
void clickPressed(const Point & cursorPosition) override;
void setObject(std::shared_ptr<CIntObject> object);
void deselect(); //TODO: consider using observer pattern instead?
bool getIsGrandmasterAbility();
void show(Canvas &to) override;
};
class CStackWindow : public CWindowObject

View File

@@ -25,21 +25,25 @@ CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero)
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
stretchedBackground = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, 410, 425));
pos.w = stretchedBackground->pos.w;
pos.h = stretchedBackground->pos.h;
center();
arts = std::make_shared<CArtifactsOfHeroBackpack>(/*Point(-100, -170)*/Point(10, 10));
arts = std::make_shared<CArtifactsOfHeroBackpack>(Point(windowMargin, windowMargin));
arts->setHero(hero);
addSet(arts);
addCloseCallback(std::bind(&CHeroBackpackWindow::close, this));
quitButton = std::make_shared<CButton>(Point(173, 385), AnimationPath::builtin("IOKAY32.def"), CButton::tooltip(""), [this]() { close(); }, EShortcut::GLOBAL_RETURN);
quitButton = std::make_shared<CButton>(Point(), AnimationPath::builtin("IOKAY32.def"), CButton::tooltip(""), [this]() { close(); }, EShortcut::GLOBAL_RETURN);
stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin;
stretchedBackground->pos.h = arts->pos.h + quitButton->pos.h + 3 * windowMargin;
pos.w = stretchedBackground->pos.w;
pos.h = stretchedBackground->pos.h;
center();
quitButton->moveBy(Point(GH.screenDimensions().x / 2 - quitButton->pos.w / 2 - quitButton->pos.x, arts->pos.h + 2 * windowMargin));
}
void CHeroBackpackWindow::showAll(Canvas &to)
void CHeroBackpackWindow::showAll(Canvas & to)
{
CIntObject::showAll(to);
CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15);

View File

@@ -23,6 +23,7 @@ private:
std::shared_ptr<CArtifactsOfHeroBackpack> arts;
std::shared_ptr<CButton> quitButton;
std::shared_ptr<CFilledTexture> stretchedBackground;
const int windowMargin = 10;
void showAll(Canvas &to) override;
void showAll(Canvas & to) override;
};

View File

@@ -89,6 +89,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
{
questlogButton = std::make_shared<CButton>(Point(314, 429), AnimationPath::builtin("hsbtns4.def"), CButton::tooltip(heroscrn[0]), [=](){ LOCPLINT->showQuestLog(); }, EShortcut::ADVENTURE_QUEST_LOG);
backpackButton = std::make_shared<CButton>(Point(424, 429), AnimationPath::builtin("buttons/backpack"), CButton::tooltipLocalized("vcmi.heroWindow.Backpack"), [=](){ createBackpackWindow(); }, EShortcut::HERO_BACKPACK);
backpackButton->addOverlay(std::make_shared<CPicture>(ImagePath::builtin("buttons/backpackButtonIcon")));
dismissButton = std::make_shared<CButton>(Point(534, 429), AnimationPath::builtin("hsbtns2.def"), CButton::tooltip(heroscrn[28]), [=](){ dismissCurrent(); }, EShortcut::HERO_DISMISS);
}
else

View File

@@ -35,6 +35,7 @@
#include "../../lib/CConfigHandler.h"
#include "../../lib/CondSh.h"
#include "../../lib/CGeneralTextHandler.h" //for Unicode related stuff
#include "../../lib/mapObjects/CGCreature.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapObjects/MiscObjects.h"
@@ -243,6 +244,9 @@ CInfoPopup::CInfoPopup(SDL_Surface * Bitmap, const Point &p, ETextAlignment alig
case ETextAlignment::TOPLEFT:
init(p.x, p.y);
break;
case ETextAlignment::TOPCENTER:
init(p.x - Bitmap->w/2, p.y);
break;
default:
assert(0); //not implemented
}
@@ -333,7 +337,7 @@ void CRClickPopup::createAndPush(const std::string & txt, std::shared_ptr<CCompo
void CRClickPopup::createAndPush(const CGObjectInstance * obj, const Point & p, ETextAlignment alignment)
{
auto iWin = createInfoWin(p, obj); //try get custom infowindow for this obj
auto iWin = createCustomInfoWindow(p, obj); //try get custom infowindow for this obj
if(iWin)
{
GH.windows().pushWindow(iWin);
@@ -401,14 +405,21 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr)
tooltip = std::make_shared<CArmyTooltip>(Point(9, 10), iah);
}
std::shared_ptr<WindowBase> CRClickPopup::createInfoWin(Point position, const CGObjectInstance * specific) //specific=0 => draws info about selected town/hero
CInfoBoxPopup::CInfoBoxPopup(Point position, const CGCreature * creature)
: CWindowObject(RCLICK_POPUP | BORDERED, ImagePath::builtin("DIBOXBCK"), toScreen(position))
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
tooltip = std::make_shared<CreatureTooltip>(Point(9, 10), creature);
}
std::shared_ptr<WindowBase> CRClickPopup::createCustomInfoWindow(Point position, const CGObjectInstance * specific) //specific=0 => draws info about selected town/hero
{
if(nullptr == specific)
specific = LOCPLINT->localState->getCurrentArmy();
if(nullptr == specific)
{
logGlobal->error("createInfoWin: no object to describe");
logGlobal->error("createCustomInfoWindow: no object to describe");
return nullptr;
}
@@ -418,6 +429,8 @@ std::shared_ptr<WindowBase> CRClickPopup::createInfoWin(Point position, const CG
return std::make_shared<CInfoBoxPopup>(position, dynamic_cast<const CGHeroInstance *>(specific));
case Obj::TOWN:
return std::make_shared<CInfoBoxPopup>(position, dynamic_cast<const CGTownInstance *>(specific));
case Obj::MONSTER:
return std::make_shared<CInfoBoxPopup>(position, dynamic_cast<const CGCreature *>(specific));
case Obj::GARRISON:
case Obj::GARRISON2:
return std::make_shared<CInfoBoxPopup>(position, dynamic_cast<const CGGarrison *>(specific));

View File

@@ -19,6 +19,7 @@ class CGObjectInstance;
class CGTownInstance;
class CGHeroInstance;
class CGGarrison;
class CGCreature;
class Rect;
VCMI_LIB_NAMESPACE_END
@@ -81,7 +82,7 @@ public:
virtual void close();
bool isPopupWindow() const override;
static std::shared_ptr<WindowBase> createInfoWin(Point position, const CGObjectInstance * specific);
static std::shared_ptr<WindowBase> createCustomInfoWindow(Point position, const CGObjectInstance * specific);
static void createAndPush(const std::string & txt, const CInfoWindow::TCompsInfo &comps = CInfoWindow::TCompsInfo());
static void createAndPush(const std::string & txt, std::shared_ptr<CComponent> component);
static void createAndPush(const CGObjectInstance * obj, const Point & p, ETextAlignment alignment = ETextAlignment::BOTTOMRIGHT);
@@ -111,15 +112,16 @@ public:
~CInfoPopup();
};
/// popup on adventure map for town\hero objects
/// popup on adventure map for town\hero and other objects with customized popup content
class CInfoBoxPopup : public CWindowObject
{
std::shared_ptr<CArmyTooltip> tooltip;
std::shared_ptr<CIntObject> tooltip;
Point toScreen(Point pos);
public:
CInfoBoxPopup(Point position, const CGTownInstance * town);
CInfoBoxPopup(Point position, const CGHeroInstance * hero);
CInfoBoxPopup(Point position, const CGGarrison * garr);
CInfoBoxPopup(Point position, const CGCreature * creature);
};
/// component selection window

View File

@@ -22,6 +22,10 @@
"type" : "string",
"description" : "Author of the mod. Can be nickname, real name or name of team"
},
"downloadSize": {
"type" : "number",
"description" : "Approximate size of mod, compressed by zip algorithm, in Mb"
},
"changelog" : {
"type" : "object",
"description" : "List of changes/new features in each version",

View File

@@ -214,11 +214,15 @@
"type" : "object",
"additionalProperties" : false,
"default" : {},
"required" : [ "radialWheelGarrisonSwipe" ],
"required" : [ "radialWheelGarrisonSwipe", "touchToleranceDistance" ],
"properties" : {
"radialWheelGarrisonSwipe" : {
"type" : "boolean",
"default" : true
},
"touchToleranceDistance" : {
"type" : "number",
"default" : 20
}
}
},

View File

@@ -18,13 +18,13 @@ CDownloadManager::CDownloadManager()
SLOT(downloadFinished(QNetworkReply *)));
}
void CDownloadManager::downloadFile(const QUrl & url, const QString & file)
void CDownloadManager::downloadFile(const QUrl & url, const QString & file, qint64 bytesTotal)
{
QNetworkRequest request(url);
FileEntry entry;
entry.file.reset(new QFile(CLauncherDirs::get().downloadsPath() + '/' + file));
entry.bytesReceived = 0;
entry.totalSize = 0;
entry.totalSize = bytesTotal;
entry.filename = file;
if(entry.file->open(QIODevice::WriteOnly | QIODevice::Truncate))
@@ -79,7 +79,7 @@ void CDownloadManager::downloadFinished(QNetworkReply * reply)
break;
}
}
downloadFile(qurl, filename);
downloadFile(qurl, filename, file.totalSize);
return;
}
@@ -131,7 +131,8 @@ void CDownloadManager::downloadProgressChanged(qint64 bytesReceived, qint64 byte
entry.file->write(entry.reply->readAll());
entry.bytesReceived = bytesReceived;
entry.totalSize = bytesTotal;
if(bytesTotal)
entry.totalSize = bytesTotal;
quint64 total = 0;
for(auto & entry : currentDownloads)
@@ -140,11 +141,14 @@ void CDownloadManager::downloadProgressChanged(qint64 bytesReceived, qint64 byte
quint64 received = 0;
for(auto & entry : currentDownloads)
received += entry.bytesReceived > 0 ? entry.bytesReceived : 0;
if(received > total)
total = received;
emit downloadProgress(received, total);
}
bool CDownloadManager::downloadInProgress(const QUrl & url)
bool CDownloadManager::downloadInProgress(const QUrl & url) const
{
for(auto & entry : currentDownloads)
{

View File

@@ -48,10 +48,10 @@ public:
// returns true if download with such URL is in progress/queued
// FIXME: not sure what's right place for "mod download in progress" check
bool downloadInProgress(const QUrl & url);
bool downloadInProgress(const QUrl & url) const;
// returns network reply so caller can connect to required signals
void downloadFile(const QUrl & url, const QString & file);
void downloadFile(const QUrl & url, const QString & file, qint64 bytesTotal = 0);
public slots:
void downloadFinished(QNetworkReply * reply);

View File

@@ -18,12 +18,9 @@
QString CModEntry::sizeToString(double size)
{
static const QString sizes[] =
{
/*"%1 B", */ "%1 KiB", "%1 MiB", "%1 GiB", "%1 TiB"
};
static const std::array<QString, 5> sizes { "%1 B", "%1 KiB", "%1 MiB", "%1 GiB", "%1 TiB" };
size_t index = 0;
while(size > 1024 && index < 4)
while(size > 1024 && index < sizes.size())
{
size /= 1024;
index++;
@@ -285,9 +282,10 @@ CModEntry CModList::getMod(QString modname) const
|| CModVersion::fromString(repo["version"].toString().toStdString())
< CModVersion::fromString(repoValMap["version"].toString().toStdString()))
{
//take valid download link and screenshots before assignment
//take valid download link, screenshots and mod size before assignment
auto download = repo.value("download");
auto screenshots = repo.value("screenshots");
auto size = repo.value("downloadSize");
repo = repoValMap;
if(repo.value("download").isNull())
{
@@ -295,6 +293,8 @@ CModEntry CModList::getMod(QString modname) const
if(repo.value("screenshots").isNull()) //taking screenshot from the downloadable version
repo["screenshots"] = screenshots;
}
if(repo.value("downloadSize").isNull())
repo["downloadSize"] = size;
}
}
}

View File

@@ -192,13 +192,6 @@ void CModListModel::resetRepositories()
endResetModel();
}
void CModListModel::addRepository(QVariantMap data)
{
beginResetModel();
CModList::addRepository(data);
endResetModel();
}
void CModListModel::modChanged(QString modID)
{
int index = modNameToID.indexOf(modID);

View File

@@ -61,7 +61,6 @@ public:
/// CModListContainer overrides
void resetRepositories() override;
void reloadRepositories() override;
void addRepository(QVariantMap data) override;
void modChanged(QString modID) override;
QVariant data(const QModelIndex & index, int role) const override;

View File

@@ -27,6 +27,11 @@
#include "../../lib/Languages.h"
#include "../../lib/modding/CModVersion.h"
static double mbToBytes(double mb)
{
return mb * 1024 * 1024;
}
void CModListView::setupModModel()
{
modModel = new CModListModel(this);
@@ -244,8 +249,11 @@ QString CModListView::genModInfoText(CModEntry & mod)
result += replaceIfNotEmpty(mod.getValue("installedVersion"), lineTemplate.arg(tr("Installed version")));
result += replaceIfNotEmpty(mod.getValue("latestVersion"), lineTemplate.arg(tr("Latest version")));
if(mod.getValue("size").isValid())
result += replaceIfNotEmpty(CModEntry::sizeToString(mod.getValue("size").toDouble()), lineTemplate.arg(tr("Download size")));
if(mod.getValue("localSizeBytes").isValid())
result += replaceIfNotEmpty(CModEntry::sizeToString(mod.getValue("localSizeBytes").toDouble()), lineTemplate.arg(tr("downloadSize")));
if((mod.isAvailable() || mod.isUpdateable()) && mod.getValue("downloadSize").isValid())
result += replaceIfNotEmpty(CModEntry::sizeToString(mbToBytes(mod.getValue("downloadSize").toDouble())), lineTemplate.arg(tr("Download size")));
result += replaceIfNotEmpty(mod.getValue("author"), lineTemplate.arg(tr("Authors")));
if(mod.getValue("licenseURL").isValid())
@@ -536,7 +544,7 @@ void CModListView::on_updateButton_clicked()
auto mod = modModel->getMod(name);
// update required mod, install missing (can be new dependency)
if(mod.isUpdateable() || !mod.isInstalled())
downloadFile(name + ".zip", mod.getValue("download").toString(), "mods");
downloadFile(name + ".zip", mod.getValue("download").toString(), "mods", mbToBytes(mod.getValue("downloadSize").toDouble()));
}
}
@@ -566,11 +574,11 @@ void CModListView::on_installButton_clicked()
{
auto mod = modModel->getMod(name);
if(!mod.isInstalled())
downloadFile(name + ".zip", mod.getValue("download").toString(), "mods");
downloadFile(name + ".zip", mod.getValue("download").toString(), "mods", mbToBytes(mod.getValue("downloadSize").toDouble()));
}
}
void CModListView::downloadFile(QString file, QString url, QString description)
void CModListView::downloadFile(QString file, QString url, QString description, qint64 size)
{
if(!dlManager)
{
@@ -585,28 +593,28 @@ void CModListView::downloadFile(QString file, QString url, QString description)
connect(modModel, &CModListModel::dataChanged, filterModel, &QAbstractItemModel::dataChanged);
QString progressBarFormat = "Downloading %s%. %p% (%v KB out of %m KB) finished";
QString progressBarFormat = tr("Downloading %s%. %p% (%v MB out of %m MB) finished");
progressBarFormat.replace("%s%", description);
ui->progressBar->setFormat(progressBarFormat);
}
dlManager->downloadFile(QUrl(url), file);
dlManager->downloadFile(QUrl(url), file, size);
}
void CModListView::downloadProgress(qint64 current, qint64 max)
{
// display progress, in kilobytes
ui->progressBar->setMaximum(max / 1024);
ui->progressBar->setValue(current / 1024);
// display progress, in megabytes
ui->progressBar->setMaximum(max / (1024 * 1024));
ui->progressBar->setValue(current / (1024 * 1024));
}
void CModListView::downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors)
{
QString title = "Download failed";
QString firstLine = "Unable to download all files.\n\nEncountered errors:\n\n";
QString lastLine = "\n\nInstall successfully downloaded?";
QString title = tr("Download failed");
QString firstLine = tr("Unable to download all files.\n\nEncountered errors:\n\n");
QString lastLine = tr("\n\nInstall successfully downloaded?");
bool doInstallFiles = false;
// if all files were d/loaded there should be no errors. And on failure there must be an error
@@ -658,6 +666,7 @@ void CModListView::installFiles(QStringList files)
{
QStringList mods;
QStringList images;
QVector<QVariantMap> repositories;
// TODO: some better way to separate zip's with mods and downloaded repository files
for(QString filename : files)
@@ -667,12 +676,12 @@ void CModListView::installFiles(QStringList files)
if(filename.endsWith(".json"))
{
//download and merge additional files
auto repodata = JsonUtils::JsonFromFile(filename).toMap();
if(repodata.value("name").isNull())
auto repoData = JsonUtils::JsonFromFile(filename).toMap();
if(repoData.value("name").isNull())
{
for(const auto & key : repodata.keys())
for(const auto & key : repoData.keys())
{
auto modjson = repodata[key].toMap().value("mod");
auto modjson = repoData[key].toMap().value("mod");
if(!modjson.isNull())
{
downloadFile(key + ".json", modjson.toString(), "repository index");
@@ -683,14 +692,17 @@ void CModListView::installFiles(QStringList files)
{
auto modn = QFileInfo(filename).baseName();
QVariantMap temp;
temp[modn] = repodata;
repodata = temp;
temp[modn] = repoData;
repoData = temp;
}
manager->loadRepository(repodata);
repositories.push_back(repoData);
}
if(filename.endsWith(".png"))
images.push_back(filename);
}
manager->loadRepositories(repositories);
if(!mods.empty())
installMods(mods);
@@ -789,8 +801,8 @@ void CModListView::checkManagerErrors()
QString errors = manager->getErrors().join('\n');
if(errors.size() != 0)
{
QString title = "Operation failed";
QString description = "Encountered errors:\n" + errors;
QString title = tr("Operation failed");
QString description = tr("Encountered errors:\n") + errors;
QMessageBox::warning(this, title, description, QMessageBox::Ok, QMessageBox::Ok);
}
}
@@ -856,7 +868,7 @@ void CModListView::doInstallMod(const QString & modName)
{
auto mod = modModel->getMod(name);
if(!mod.isInstalled())
downloadFile(name + ".zip", mod.getValue("download").toString(), "mods");
downloadFile(name + ".zip", mod.getValue("download").toString(), "mods", mbToBytes(mod.getValue("downloadSize").toDouble()));
}
}

View File

@@ -51,7 +51,7 @@ class CModListView : public QWidget
// find mods that depend on this one
QStringList findDependentMods(QString mod, bool excludeDisabled);
void downloadFile(QString file, QString url, QString description);
void downloadFile(QString file, QString url, QString description, qint64 size = 0);
void installMods(QStringList archives);
void installFiles(QStringList mods);

View File

@@ -70,9 +70,11 @@ void CModManager::resetRepositories()
modList->resetRepositories();
}
void CModManager::loadRepository(QVariantMap repomap)
void CModManager::loadRepositories(QVector<QVariantMap> repomap)
{
modList->addRepository(repomap);
for (auto const & entry : repomap)
modList->addRepository(entry);
modList->reloadRepositories();
}
void CModManager::loadMods()
@@ -87,15 +89,23 @@ void CModManager::loadMods()
auto resID = CModInfo::getModFile(modname);
if(CResourceHandler::get()->existsResource(resID))
{
boost::filesystem::path name = *CResourceHandler::get()->getResourceName(resID);
auto mod = JsonUtils::JsonFromFile(pathToQString(name));
if(!name.is_absolute())
//calculate mod size
qint64 total = 0;
ResourcePath resDir(CModInfo::getModDir(modname), EResType::DIRECTORY);
if(CResourceHandler::get()->existsResource(resDir))
{
auto json = JsonUtils::toJson(mod);
json["storedLocaly"].Bool() = true;
mod = JsonUtils::toVariant(json);
for(QDirIterator iter(QString::fromStdString(CResourceHandler::get()->getResourceName(resDir)->string()), QDirIterator::Subdirectories); iter.hasNext(); iter.next())
total += iter.fileInfo().size();
}
boost::filesystem::path name = *CResourceHandler::get()->getResourceName(resID);
auto mod = JsonUtils::JsonFromFile(pathToQString(name));
auto json = JsonUtils::toJson(mod);
json["localSizeBytes"].Float() = total;
if(!name.is_absolute())
json["storedLocaly"].Bool() = true;
mod = JsonUtils::toVariant(json);
localMods.insert(QString::fromUtf8(modname.c_str()).toLower(), mod);
}
}

View File

@@ -35,7 +35,7 @@ public:
CModManager(CModList * modList);
void resetRepositories();
void loadRepository(QVariantMap repomap);
void loadRepositories(QVector<QVariantMap> repomap);
void loadModSettings();
void loadMods();

View File

@@ -136,4 +136,12 @@ Rect Rect::intersect(const Rect & other) const
}
}
int Rect::distanceTo(const Point & target) const
{
int distanceX = std::max({left() - target.x, 0, target.x - right()});
int distanceY = std::max({top() - target.y, 0, target.y - bottom()});
return Point(distanceX, distanceY).length();
}
VCMI_LIB_NAMESPACE_END

View File

@@ -142,6 +142,9 @@ public:
return x == other.x && y == other.y && w == other.w && h == other.h;
}
/// returns distance from this rect to point, or 0 if inside
DLL_LINKAGE int distanceTo(const Point & target) const;
/// returns true if this rect intersects with another rect
DLL_LINKAGE bool intersectionTest(const Rect & other) const;

View File

@@ -53,7 +53,7 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
ms.appendRawString(" ");
ms.appendLocalString(EMetaText::CRE_PL_NAMES,subID);
ms.appendRawString("\n");
ms.appendRawString("\n\n");
int decision = takenAction(hero, true);

View File

@@ -140,13 +140,11 @@ void Rewardable::Interface::grantRewardAfterLevelup(IGameCallback * cb, const Re
caster.setActualCaster(hero);
caster.setSpellSchoolLevel(info.reward.spellCast.second);
cb->castSpell(&caster, info.reward.spellCast.first, int3{-1, -1, -1});
if(info.reward.removeObject)
logMod->warn("Removal of object with spell casts is not supported!");
}
else if(info.reward.removeObject) //FIXME: object can't track spell cancel or finish, so removeObject leads to crash
if(info.reward.removeObject)
if(auto * instance = dynamic_cast<const CGObjectInstance*>(this))
cb->removeObject(instance);
cb->removeAfterVisit(instance);
}
VCMI_LIB_NAMESPACE_END

View File

@@ -49,7 +49,7 @@ void Teleport::adjustTargetTypes(std::vector<TargetType> & types) const
bool Teleport::applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const
{
if(target.size() == 1) //Assume, this is check only for selecting a unit
return UnitEffect::applicable(problem, m);
return UnitEffect::applicable(problem, m, target);
if(target.size() != 2)
return m->adaptProblem(ESpellCastProblem::WRONG_SPELL_TARGET, problem);

View File

@@ -177,6 +177,11 @@ void BattleFlowProcessor::trySummonGuardians(const CBattleInfoCallback & battle,
gameHandler->sendAndApply(&pack);
}
}
// send empty event to client
// temporary(?) workaround to force animations to trigger
StacksInjured fakeEvent;
gameHandler->sendAndApply(&fakeEvent);
}
void BattleFlowProcessor::castOpeningSpells(const CBattleInfoCallback & battle)