Merge remote-tracking branch 'vcmi/beta' into develop
BIN
Mods/vcmi/Data/radialMenu/stackFillOne.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 628 B |
Before Width: | Height: | Size: 934 B After Width: | Height: | Size: 942 B |
Before Width: | Height: | Size: 914 B After Width: | Height: | Size: 927 B |
BIN
Mods/vcmi/Sprites/buttons/backpackButtonIcon.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.0 KiB |
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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" : "Перемістити істоту до іншої армії",
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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())
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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) {}
|
||||
|
@ -9,4 +9,4 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
enum class ETextAlignment {TOPLEFT, CENTER, BOTTOMRIGHT};
|
||||
enum class ETextAlignment {TOPLEFT, TOPCENTER, CENTER, BOTTOMRIGHT};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -369,13 +369,14 @@ 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();} },
|
||||
};
|
||||
|
||||
if (hasAnyEmptySlots || hasSameUnit)
|
||||
GH.windows().createAndPushWindow<RadialMenu>(pos.center(), menuElements);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,14 +919,23 @@ void CStackWindow::setSelection(si32 newSkill, std::shared_ptr<CCommanderSkillIc
|
||||
selectedIcon->setObject(std::make_shared<CPicture>(getSkillImage(oldSelection)));
|
||||
|
||||
if(selectedIcon)
|
||||
{
|
||||
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)));
|
||||
if(!newIcon->getIsGrandmasterAbility())
|
||||
{
|
||||
newIcon->text = getSkillDescription(newSkill, true); //update currently selected icon's message to show upgrade description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<CIntObject> CStackWindow::switchTab(size_t index)
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -666,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)
|
||||
@ -675,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");
|
||||
@ -691,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);
|
||||
|
||||
|
@ -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()
|
||||
|
@ -35,7 +35,7 @@ public:
|
||||
CModManager(CModList * modList);
|
||||
|
||||
void resetRepositories();
|
||||
void loadRepository(QVariantMap repomap);
|
||||
void loadRepositories(QVector<QVariantMap> repomap);
|
||||
void loadModSettings();
|
||||
void loadMods();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -197,6 +197,8 @@ public:
|
||||
ui32 producedQuantity;
|
||||
std::set<GameResID> abandonedMineResources;
|
||||
|
||||
bool isAbandoned() const;
|
||||
|
||||
private:
|
||||
void onHeroVisit(const CGHeroInstance * h) const override;
|
||||
void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
|
||||
@ -209,7 +211,6 @@ private:
|
||||
std::string getObjectName() const override;
|
||||
std::string getHoverText(PlayerColor player) const override;
|
||||
|
||||
bool isAbandoned() const;
|
||||
public:
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
|
@ -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(auto * instance = dynamic_cast<const CGObjectInstance*>(this))
|
||||
cb->removeObject(instance);
|
||||
cb->removeAfterVisit(instance);
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -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);
|
||||
|
@ -118,7 +118,10 @@ void Initializer::initialize(CGHeroInstance * o)
|
||||
|
||||
o->tempOwner = defaultPlayer;
|
||||
if(o->ID == Obj::PRISON)
|
||||
{
|
||||
o->subID = 0;
|
||||
o->tempOwner = PlayerColor::NEUTRAL;
|
||||
}
|
||||
|
||||
if(o->ID == Obj::HERO)
|
||||
{
|
||||
@ -184,8 +187,16 @@ void Initializer::initialize(CGMine * o)
|
||||
if(!o) return;
|
||||
|
||||
o->tempOwner = defaultPlayer;
|
||||
if(o->isAbandoned())
|
||||
{
|
||||
for(auto r = 0; r < GameConstants::RESOURCE_QUANTITY - 1; ++r)
|
||||
o->abandonedMineResources.insert(GameResID(r));
|
||||
}
|
||||
else
|
||||
{
|
||||
o->producedResource = GameResID(o->subID);
|
||||
o->producedQuantity = o->defaultResProduction();
|
||||
}
|
||||
}
|
||||
|
||||
void Initializer::initialize(CGResource * o)
|
||||
|
@ -142,6 +142,7 @@ void MapController::repairMap()
|
||||
{
|
||||
nih->typeName = "prison";
|
||||
nih->subTypeName = "prison";
|
||||
nih->subID = 0;
|
||||
}
|
||||
|
||||
nih->type = type;
|
||||
|
@ -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)
|
||||
|