diff --git a/libs/common/include/Point.h b/libs/common/include/Point.h index 36d014044..a9e313788 100644 --- a/libs/common/include/Point.h +++ b/libs/common/include/Point.h @@ -10,6 +10,50 @@ #include #include +namespace detail { + +template +struct type_identity +{ + using type = T; +}; + +/// Convert the type T to a signed type if the condition is true (safe for float types) +template +using make_signed_if_t = + typename std::conditional_t::value, std::make_signed, type_identity>::type; + +// clang-format off + +/// Creates a mixed type out of types T and U which is +/// the larger type of T & U AND signed iff either is signed +/// Will be a floating point type if either T or U is floating point +template +using mixed_type_t = + make_signed_if_t< + std::is_signed::value || std::is_signed::value, + typename std::conditional_t< + std::is_floating_point::value == std::is_floating_point::value, // both are FP or not FP? + std::conditional<(sizeof(T) > sizeof(U)), T, U>, // Take the larger type + std::conditional::value, T, U> // Take the floating point type + >::type + >; + +template +using IsNonLossyOp = std::integral_constant U (except overflow) if: + std::is_floating_point::value || std::is_signed::value || std::is_unsigned::value +>; + +// clang-format on + +template +using require_nonLossyOp = std::enable_if_t::value>; +template +using require_arithmetic = std::enable_if_t::value>; + +} // namespace detail + /// Type for describing a 2D value (position, size, offset...) /// Note: Combining a signed with an unsigned point will result in a signed type! /// Allowed operations: @@ -54,6 +98,28 @@ struct Point //-V690 static constexpr Point all(const T value) noexcept { return Point(value, value); } constexpr bool isValid() const noexcept { return *this != Invalid(); } + /// Calculate the euclidean distance to the origin + constexpr auto distance() const noexcept { return std::hypot(x, y); } + + /// Calculate the euclidean distance to another point + template::value && std::is_unsigned::value), int> = 0> + constexpr auto distance(const Point& pt) const noexcept + { + using Res = ::detail::mixed_type_t; + return std::hypot(static_cast(x) - static_cast(pt.x), static_cast(y) - static_cast(pt.y)); + } + + /// Calculate the euclidean distance to another point + template::value && std::is_unsigned::value, int> = 0> + constexpr auto distance(const Point& pt) const noexcept + { + using Res = ::detail::mixed_type_t; + const auto x1 = static_cast(x), x2 = static_cast(pt.x); + const auto y1 = static_cast(y), y2 = static_cast(pt.y); + // Calculate difference of usigned values without overflow + return std::hypot((x1 > x2 ? x1 - x2 : x2 - x1), (y1 > y2 ? y1 - y2 : y2 - y1)); + } + constexpr bool operator==(const Point& second) const noexcept; constexpr bool operator!=(const Point& second) const noexcept; @@ -88,50 +154,6 @@ constexpr bool Point::operator!=(const Point& second) const noexcept return !(*this == second); } -namespace detail { - -template -struct type_identity -{ - using type = T; -}; - -/// Convert the type T to a signed type if the condition is true (safe for float types) -template -using make_signed_if_t = - typename std::conditional_t::value, std::make_signed, type_identity>::type; - -// clang-format off - -/// Creates a mixed type out of types T and U which is -/// the larger type of T & U AND signed iff either is signed -/// Will be a floating point type if either T or U is floating point -template -using mixed_type_t = - make_signed_if_t< - std::is_signed::value || std::is_signed::value, - typename std::conditional_t< - std::is_floating_point::value == std::is_floating_point::value, // both are FP or not FP? - std::conditional<(sizeof(T) > sizeof(U)), T, U>, // Take the larger type - std::conditional::value, T, U> // Take the floating point type - >::type - >; - -template -using IsNonLossyOp = std::integral_constant U (except overflow) if: - std::is_floating_point::value || std::is_signed::value || std::is_unsigned::value ->; - -// clang-format on - -template -using require_nonLossyOp = std::enable_if_t::value>; -template -using require_arithmetic = std::enable_if_t::value>; - -} // namespace detail - /// Compute the element wise minimum template constexpr Point elMin(const Point& lhs, const Point& rhs) noexcept diff --git a/libs/s25main/PickedMovableObject.cpp b/libs/s25main/PickedMovableObject.cpp new file mode 100644 index 000000000..f28a59c32 --- /dev/null +++ b/libs/s25main/PickedMovableObject.cpp @@ -0,0 +1,154 @@ +// Copyright (C) 2005 - 2023 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "PickedMovableObject.h" +#include "desktops/dskGameInterface.h" +#include "drivers/VideoDriverWrapper.h" +#include "world/GameWorldBase.h" +#include "world/GameWorldView.h" +#include "nodeObjs/noMovable.h" +#include + +using namespace std::chrono_literals; + +namespace { +constexpr unsigned PickRadius = 2; +constexpr unsigned TrackRadius = 5; +constexpr auto PickedObjectExpiration = 5s; + +// Make (ab-)use of CheckPointsInRadius() easier +constexpr bool CheckPointsBreak = true; +constexpr bool CheckPointsContinue = false; +} // namespace + +PickedMovableObject PickedMovableObject::pick(const GameWorldView& gwv, MapPoint mapPt, DrawPoint drawPt, bool expire) +{ + // DEBUG REMOVE BEFORE MERGE + unsigned i = 1; + + const auto offset = gwv.GetOffset(); + const auto center = gwv.GetSize() / 2.f; + const auto zoomFactor = gwv.GetZoomFactor(); + const auto& world = gwv.GetWorld(); + const auto worldSize = world.GetSize() * DrawPoint(TR_W, TR_H); + auto minDistance = std::numeric_limits::max(); + + PickedMovableObject pmo; + + world.CheckPointsInRadius( + mapPt, PickRadius, + [&](MapPoint curPt, unsigned) { + if(gwv.GetViewer().GetVisibility(curPt) != Visibility::Visible) + return CheckPointsContinue; + + DrawPoint curDrawPt = world.GetNodePos(curPt); + if(curDrawPt.x < offset.x) + curDrawPt.x += worldSize.x; + if(curDrawPt.y < offset.y) + curDrawPt.y += worldSize.y; + curDrawPt -= offset; + for(const noBase& obj : world.GetFigures(curPt)) + { + const auto* movable = dynamic_cast(&obj); + if(!movable) + continue; + + DrawPoint objDrawPt = curDrawPt; + if(movable->IsMoving()) + objDrawPt += movable->CalcWalkingRelative(); + objDrawPt = DrawPoint((objDrawPt - center) * zoomFactor + center); + + // DEBUG REMOVE BEFORE MERGE + dskGameInterface::SetDebugPoint(i++, objDrawPt, 8, 4, MakeColor(255, 255, 0, 255)); + + auto diff = Point(objDrawPt - drawPt); + float distance = std::sqrt(diff.x * diff.x + diff.y * diff.y); + if(distance < minDistance) + { + pmo.id_ = movable->GetObjId(); + pmo.mapPt_ = curPt; + pmo.drawPt_ = objDrawPt; // TODO Do we need the unwrapped point here or was this always wrong? + minDistance = distance; + } + } + + return CheckPointsContinue; + }, + true); + + if(pmo.isValid() && expire) + pmo.expiration_.start(); + + return pmo; +} + +PickedMovableObject PickedMovableObject::pickAtCursor(const GameWorldView& gwv, bool expire) +{ + return pick(gwv, gwv.GetSelectedPt(), DrawPoint(VIDEODRIVER.GetMousePos()), expire); +} + +PickedMovableObject PickedMovableObject::pickAtViewCenter(const GameWorldView& gwv, bool expire) +{ + const auto centerMapPt = gwv.GetWorld().MakeMapPoint((gwv.GetFirstPt() + gwv.GetLastPt()) / 2); + const auto centerDrawPt = DrawPoint(gwv.GetSize() / 2u); + return pick(gwv, centerMapPt, centerDrawPt, expire); +} + +bool PickedMovableObject::isValid() const +{ + return id_ != 0 && (!expiration_.isRunning() || expiration_.getElapsed() < PickedObjectExpiration); +} + +void PickedMovableObject::cancelExpiration() +{ + expiration_.stop(); +} + +void PickedMovableObject::invalidate() +{ + id_ = 0; +} + +bool PickedMovableObject::track(const GameWorldView& gwv) +{ + if(!isValid()) + return false; + + const auto& world = gwv.GetWorld(); + const auto success = world.CheckPointsInRadius( + mapPt_, TrackRadius, + [&](MapPoint curPt, unsigned) { + if(gwv.GetViewer().GetVisibility(curPt) != Visibility::Visible) + return CheckPointsContinue; + + for(const noBase& obj : world.GetFigures(curPt)) + { + if(obj.GetObjId() != id_) + continue; + + const auto& movable = dynamic_cast(obj); + mapPt_ = curPt; + drawPt_ = world.GetNodePos(curPt); + if(movable.IsMoving()) + drawPt_ += movable.CalcWalkingRelative(); + + return CheckPointsBreak; + } + return CheckPointsContinue; + }, + true); + + if(!success) + invalidate(); + + return success; +} + +bool PickedMovableObject::track(GameWorldView& gwv, bool moveTo) +{ + const auto success = track(gwv); + if(success && moveTo) + gwv.MoveTo(drawPt_ - gwv.GetSize() / 2u); + return success; +} diff --git a/libs/s25main/PickedMovableObject.h b/libs/s25main/PickedMovableObject.h new file mode 100644 index 000000000..9f3847ffc --- /dev/null +++ b/libs/s25main/PickedMovableObject.h @@ -0,0 +1,36 @@ +// Copyright (C) 2005 - 2023 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "DrawPoint.h" +#include "Timer.h" +#include "gameTypes/MapCoordinates.h" + +class GameWorldView; + +class PickedMovableObject +{ +public: + static PickedMovableObject pick(const GameWorldView& gwv, MapPoint mapPt, DrawPoint drawPt, bool expire); + static PickedMovableObject pickAtCursor(const GameWorldView& gwv, bool expire); + static PickedMovableObject pickAtViewCenter(const GameWorldView& gwv, bool expire); + + PickedMovableObject() = default; + + unsigned id() const { return id_; } + bool isValid() const; + + void cancelExpiration(); + void invalidate(); + + bool track(const GameWorldView& gwv); + bool track(GameWorldView& gwv, bool moveTo); + +private: + unsigned id_ = 0; + MapPoint mapPt_{}; + DrawPoint drawPt_{}; + Timer expiration_{}; +}; diff --git a/libs/s25main/Window.cpp b/libs/s25main/Window.cpp index 1bb2b63c1..27a0da49f 100644 --- a/libs/s25main/Window.cpp +++ b/libs/s25main/Window.cpp @@ -491,6 +491,20 @@ void Window::DrawLine(DrawPoint pt1, DrawPoint pt2, unsigned short width, unsign VIDEODRIVER.GetRenderer()->DrawLine(pt1, pt2, width, color); } +// DEBUG REMOVE BEFORE MERGE +void Window::DrawCross(DrawPoint pt, unsigned short length, unsigned short width, unsigned color) +{ + auto* renderer = VIDEODRIVER.GetRenderer(); + + DrawPoint pt1 = pt - DrawPoint(-length, length); + DrawPoint pt2 = pt - DrawPoint(length, -length); + DrawPoint pt3 = pt - DrawPoint(length, length); + DrawPoint pt4 = pt - DrawPoint(-length, -length); + + renderer->DrawLine(pt1, pt2, width, color); + renderer->DrawLine(pt3, pt4, width, color); +} + void Window::Msg_PaintBefore() { animations_.update(VIDEODRIVER.GetTickCount()); diff --git a/libs/s25main/Window.h b/libs/s25main/Window.h index 090c5647a..dbd740302 100644 --- a/libs/s25main/Window.h +++ b/libs/s25main/Window.h @@ -201,6 +201,8 @@ class Window static void DrawRectangle(const Rect& rect, unsigned color); /// Zeichnet eine Linie static void DrawLine(DrawPoint pt1, DrawPoint pt2, unsigned short width, unsigned color); + // DEBUG REMOVE BEFORE MERGE + static void DrawCross(DrawPoint pt, unsigned short length, unsigned short width, unsigned color); // GUI-Notify-Messages diff --git a/libs/s25main/desktops/dskGameInterface.cpp b/libs/s25main/desktops/dskGameInterface.cpp index 42bb9c12f..ccc454ef3 100644 --- a/libs/s25main/desktops/dskGameInterface.cpp +++ b/libs/s25main/desktops/dskGameInterface.cpp @@ -4,6 +4,7 @@ #include "dskGameInterface.h" #include "CollisionDetection.h" +#include "DrawPoint.h" #include "EventManager.h" #include "Game.h" #include "GamePlayer.h" @@ -82,6 +83,7 @@ #include "gameData/TerrainDesc.h" #include "gameData/const_gui_ids.h" #include "liblobby/LobbyClient.h" +#include "s25util/colors.h" #include #include #include @@ -668,6 +670,10 @@ bool dskGameInterface::Msg_LeftDown(const MouseCoords& mc) actionwindow->Close(); VIDEODRIVER.SetMousePos(mc.GetPos()); + // DEBUG REMOVE BEFORE MERGE + ClearDebugPoints(); + SetDebugPoint(0, mc.GetPos(), 8, 4, MakeColor(255, 0, 255, 255)); + ShowActionWindow(action_tabs, cSel, mc.GetPos(), enable_military_buildings); } @@ -980,6 +986,14 @@ void dskGameInterface::Run() } messenger.Draw(); + + // DEBUG REMOVE BEFORE MERGE + for(const auto& debugPoint : debugPoints) + { + if(!debugPoint.valid) + continue; + DrawCross(debugPoint.pos, debugPoint.length, debugPoint.width, debugPoint.color); + } } void dskGameInterface::GI_StartRoadBuilding(const MapPoint startPt, bool waterRoad) @@ -1391,3 +1405,23 @@ void dskGameInterface::GI_TeamWinner(const unsigned playerMask) messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_ORANGE); WINDOWMANAGER.Show(std::make_unique(winners)); } + +// DEBUG REMOVE BEFORE MERGE +void dskGameInterface::SetDebugPoint(unsigned i, DrawPoint pos, unsigned short length, unsigned short width, + unsigned color) +{ + unsigned minSize = i + 1; + if(debugPoints.size() < minSize) + debugPoints.resize(minSize); + + debugPoints[i] = {true, pos, length, width, color}; +} + +// DEBUG REMOVE BEFORE MERGE +void dskGameInterface::ClearDebugPoints() +{ + debugPoints.clear(); +} + +// DEBUG REMOVE BEFORE MERGE +std::vector dskGameInterface::debugPoints; diff --git a/libs/s25main/desktops/dskGameInterface.h b/libs/s25main/desktops/dskGameInterface.h index b184e6967..68f7725d7 100644 --- a/libs/s25main/desktops/dskGameInterface.h +++ b/libs/s25main/desktops/dskGameInterface.h @@ -5,6 +5,7 @@ #pragma once #include "Desktop.h" +#include "DrawPoint.h" #include "GameInterface.h" #include "IngameMinimap.h" #include "Messenger.h" @@ -30,6 +31,16 @@ struct BuildingNote; struct KeyEvent; class NWFInfo; +// DEBUG REMOVE BEFORE MERGE +struct DebugPoint +{ + bool valid = false; + DrawPoint pos; + unsigned short length; + unsigned short width; + unsigned color; +}; + class dskGameInterface : public Desktop, public ClientInterface, @@ -38,6 +49,10 @@ class dskGameInterface : public IChatCmdListener { public: + // DEBUG REMOVE BEFORE MERGE + static void SetDebugPoint(unsigned i, DrawPoint pos, unsigned short length, unsigned short width, unsigned color); + static void ClearDebugPoints(); + dskGameInterface(std::shared_ptr game, std::shared_ptr nwfInfo, unsigned playerIdx, bool initOGL = true); ~dskGameInterface() override; @@ -164,4 +179,7 @@ class dskGameInterface : bool isCheatModeOn; std::string curCheatTxt; Subscription evBld; + + // DEBUG REMOVE BEFORE MERGE + static std::vector debugPoints; }; diff --git a/libs/s25main/ingameWindows/iwAction.cpp b/libs/s25main/ingameWindows/iwAction.cpp index a7c96ae37..c38e4e19a 100644 --- a/libs/s25main/ingameWindows/iwAction.cpp +++ b/libs/s25main/ingameWindows/iwAction.cpp @@ -297,6 +297,9 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP curPos.x += btSize.x; group->AddImageButton(4, curPos, btSize, TextureColor::Grey, LOADER.GetImageN("io", 107), _("Notify allies of this location")); + + // Try to pick a movable object at the cursor now, in case the user activates the observation window later + pickedObject_ = PickedMovableObject::pickAtCursor(gwv, true); } main_tab->SetSelection(0, true); @@ -706,8 +709,8 @@ void iwAction::Msg_ButtonClick_TabWatch(const unsigned ctrl_id) switch(ctrl_id) { case 1: - // TODO: bestimen, was an der position selected ist - WINDOWMANAGER.Show(std::make_unique(gwv, selectedPt)); + WINDOWMANAGER.Show( + std::make_unique(gwv, selectedPt, std::exchange(pickedObject_, PickedMovableObject{}))); DisableMousePosResetOnClose(); Close(); break; @@ -731,3 +734,11 @@ void iwAction::DisableMousePosResetOnClose() { mousePosAtOpen_ = DrawPoint::Invalid(); } + +void iwAction::Draw_() +{ + IngameWindow::Draw_(); + + // Track picked object (if valid) + pickedObject_.track(gwv); +} diff --git a/libs/s25main/ingameWindows/iwAction.h b/libs/s25main/ingameWindows/iwAction.h index 52bd6a731..a2de2c237 100644 --- a/libs/s25main/ingameWindows/iwAction.h +++ b/libs/s25main/ingameWindows/iwAction.h @@ -5,6 +5,7 @@ #pragma once #include "IngameWindow.h" +#include "PickedMovableObject.h" #include "gameTypes/MapCoordinates.h" #include #include @@ -69,6 +70,8 @@ class iwAction : public IngameWindow /// Die einzelnen Höhen für die einzelnen Tabs im Bautab std::array building_tab_heights; + PickedMovableObject pickedObject_; + public: iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapPoint selectedPt, const DrawPoint& mousePos, Params params, bool military_buildings); @@ -98,4 +101,6 @@ class iwAction : public IngameWindow void AddAttackControls(ctrlGroup* group, unsigned attackers_count); void AddUpgradeRoad(ctrlGroup* group, unsigned& x, unsigned& width); bool DoUpgradeRoad(); + + void Draw_() override; }; diff --git a/libs/s25main/ingameWindows/iwObservate.cpp b/libs/s25main/ingameWindows/iwObservate.cpp index e93f029c3..8bd1c0531 100644 --- a/libs/s25main/ingameWindows/iwObservate.cpp +++ b/libs/s25main/ingameWindows/iwObservate.cpp @@ -5,6 +5,7 @@ #include "iwObservate.h" #include "CollisionDetection.h" #include "Loader.h" +#include "PickedMovableObject.h" #include "Settings.h" #include "WindowManager.h" #include "controls/ctrlImageButton.h" @@ -23,13 +24,13 @@ const Extent SmallWndSize(260, 190); const Extent MediumWndSize(300, 250); const Extent BigWndSize(340, 310); -iwObservate::iwObservate(GameWorldView& gwv, const MapPoint selectedPt) +iwObservate::iwObservate(GameWorldView& gwv, const MapPoint selectedPt, PickedMovableObject&& pmo) : IngameWindow(CGI_OBSERVATION, IngameWindow::posAtMouse, SmallWndSize, _("Observation window"), nullptr, false, CloseBehavior::NoRightClick), parentView(gwv), view(new GameWorldView(gwv.GetViewer(), Position(GetDrawPos() * DrawPoint(10, 15)), GetSize() - Extent::all(20))), selectedPt(selectedPt), lastWindowPos(Point::Invalid()), isScrolling(false), zoomLvl(0), - followMovableId(0) + pickedObject(std::move(pmo)), following(false), lastValid(pickedObject.isValid()) { view->MoveToMapPt(selectedPt); view->SetZoomFactor(1.9f, false); @@ -41,7 +42,7 @@ iwObservate::iwObservate(GameWorldView& gwv, const MapPoint selectedPt) AddImageButton(1, btPos, btSize, TextureColor::Grey, LOADER.GetImageN("io", 36), _("Zoom")); // Kamera (Folgen): 43 btPos.x += btSize.x; - AddImageButton(2, btPos, btSize, TextureColor::Grey, LOADER.GetImageN("io", 43), _("Follow object")); + AddImageButton(2, btPos, btSize, TextureColor::Grey, LOADER.GetImageN("io", 43)); // Zum Ort btPos.x += btSize.x; AddImageButton(3, btPos, btSize, TextureColor::Grey, LOADER.GetImageN("io", 107), _("Go to place")); @@ -53,6 +54,9 @@ iwObservate::iwObservate(GameWorldView& gwv, const MapPoint selectedPt) parentView.CopyHudSettingsTo(*view, false); gwvSettingsConnection = parentView.onHudSettingsChanged.connect([this]() { parentView.CopyHudSettingsTo(*view, false); }); + + // Set follow button tooltip + UpdateFollowButton(); } void iwObservate::Msg_ButtonClick(const unsigned ctrl_id) @@ -74,53 +78,17 @@ void iwObservate::Msg_ButtonClick(const unsigned ctrl_id) view->SetZoomFactor(2.3f); break; case 2: - { - if(followMovableId) - followMovableId = 0; - else - { - const DrawPoint centerDrawPt = DrawPoint(view->GetSize() / 2u); - - double minDistance = std::numeric_limits::max(); - - for(int y = view->GetFirstPt().y; y <= view->GetLastPt().y; ++y) - { - for(int x = view->GetFirstPt().x; x <= view->GetLastPt().x; ++x) - { - Position curOffset; - const MapPoint curPt = - view->GetViewer().GetTerrainRenderer().ConvertCoords(Position(x, y), &curOffset); - DrawPoint curDrawPt = view->GetWorld().GetNodePos(curPt) - view->GetOffset() + curOffset; - - if(view->GetViewer().GetVisibility(curPt) != Visibility::Visible) - continue; - - for(const noBase& obj : view->GetWorld().GetFigures(curPt)) - { - const auto* movable = dynamic_cast(&obj); - if(!movable) - continue; - - DrawPoint objDrawPt = curDrawPt; - - if(movable->IsMoving()) - objDrawPt += movable->CalcWalkingRelative(); - - DrawPoint diffToCenter = objDrawPt - centerDrawPt; - double distance = sqrt(pow(diffToCenter.x, 2) + pow(diffToCenter.y, 2)); - - if(distance < minDistance) - { - followMovableId = movable->GetObjId(); - minDistance = distance; - } - } - } - } - } - + if(following) + pickedObject.invalidate(); // Stop following + else if(!pickedObject.isValid()) // If object is invalid, pick new object at center of view + pickedObject = PickedMovableObject::pickAtViewCenter(*view, false); + + // Follow picked object, if valid + following = lastValid = pickedObject.isValid(); + // Ensure the object doesn't expire if we started following the initially picked object + pickedObject.cancelExpiration(); + UpdateFollowButton(); break; - } case 3: parentView.MoveToMapPt(MapPoint(view->GetLastPt() - (view->GetLastPt() - view->GetFirstPt()) / 2)); break; @@ -159,10 +127,12 @@ void iwObservate::Draw_() lastWindowPos = GetPos(); } - if(followMovableId) + // If the object was valid previously, track it (checks isValid() for us) + // If it returns false, it either expired or we lost it + if(lastValid && !pickedObject.track(*view, following)) { - if(!MoveToFollowedObj()) - followMovableId = 0; + following = lastValid = false; + UpdateFollowButton(); } if(!IsMinimized()) @@ -172,59 +142,13 @@ void iwObservate::Draw_() view->Draw(road, parentView.GetSelectedPt(), false); // Draw indicator for center point - if(!followMovableId) + if(!following && !lastValid) LOADER.GetMapTexture(23)->DrawFull(view->GetPos() + view->GetSize() / 2u); } return IngameWindow::Draw_(); } -bool iwObservate::MoveToFollowedObj() -{ - // First look around the center (figure is normally still there) - const GameWorldBase& world = view->GetWorld(); - const MapPoint centerPt = world.MakeMapPoint((view->GetFirstPt() + view->GetLastPt()) / 2); - const std::vector centerPts = world.GetPointsInRadiusWithCenter(centerPt, 2); - for(const MapPoint& curPt : centerPts) - { - if(MoveToFollowedObj(curPt)) - return true; - } - - // Not at the center (normally due to lags) -> Check full area - for(int y = view->GetFirstPt().y; y <= view->GetLastPt().y; ++y) - { - for(int x = view->GetFirstPt().x; x <= view->GetLastPt().x; ++x) - { - const MapPoint curPt = world.MakeMapPoint(Position(x, y)); - if(MoveToFollowedObj(curPt)) - return true; - } - } - return false; -} - -bool iwObservate::MoveToFollowedObj(const MapPoint ptToCheck) -{ - if(view->GetViewer().GetVisibility(ptToCheck) != Visibility::Visible) - return false; - for(const noBase& obj : view->GetWorld().GetFigures(ptToCheck)) - { - if(obj.GetObjId() == followMovableId) - { - const auto& followMovable = static_cast(obj); - DrawPoint drawPt = view->GetWorld().GetNodePos(ptToCheck); - - if(followMovable.IsMoving()) - drawPt += followMovable.CalcWalkingRelative(); - - view->MoveTo(drawPt - view->GetSize() / 2u); - return true; - } - } - return false; -} - bool iwObservate::Msg_MouseMove(const MouseCoords& mc) { if(isScrolling) @@ -252,7 +176,9 @@ bool iwObservate::Msg_RightDown(const MouseCoords& mc) scrollOrigin = mc.GetPos(); isScrolling = true; - followMovableId = 0; + following = lastValid = false; + pickedObject.invalidate(); + UpdateFollowButton(); WINDOWMANAGER.SetCursor(Cursor::Scroll); } else { @@ -270,3 +196,14 @@ bool iwObservate::Msg_RightUp(const MouseCoords& /*mc*/) return true; } + +void iwObservate::UpdateFollowButton() +{ + auto* button = GetCtrl(2); + if(following) + button->SetTooltip(_("Stop following object")); + else if(pickedObject.isValid()) + button->SetTooltip(_("Follow picked object")); + else + button->SetTooltip(_("Follow object near the center")); +} diff --git a/libs/s25main/ingameWindows/iwObservate.h b/libs/s25main/ingameWindows/iwObservate.h index e3fa52b79..462b8b3cb 100644 --- a/libs/s25main/ingameWindows/iwObservate.h +++ b/libs/s25main/ingameWindows/iwObservate.h @@ -5,6 +5,7 @@ #pragma once #include "IngameWindow.h" +#include "PickedMovableObject.h" #include "gameTypes/MapCoordinates.h" #include @@ -28,13 +29,15 @@ class iwObservate : public IngameWindow unsigned zoomLvl; - /// id of object currently followed or INVALID_ID - unsigned followMovableId; + // Follow object + PickedMovableObject pickedObject; + bool following; + bool lastValid; // keep previous IsValid() result to detect transitions boost::signals2::scoped_connection gwvSettingsConnection; public: - iwObservate(GameWorldView& gwv, MapPoint selectedPt); + iwObservate(GameWorldView& gwv, MapPoint selectedPt, PickedMovableObject&& pmo); private: void Draw_() override; @@ -42,7 +45,5 @@ class iwObservate : public IngameWindow bool Msg_MouseMove(const MouseCoords& mc) override; bool Msg_RightDown(const MouseCoords& mc) override; bool Msg_RightUp(const MouseCoords& mc) override; - /// Move view to the object we currently follow, return true if it can still be found - bool MoveToFollowedObj(); - inline bool MoveToFollowedObj(MapPoint ptToCheck); + void UpdateFollowButton(); }; diff --git a/libs/s25main/world/GameWorldView.h b/libs/s25main/world/GameWorldView.h index 144c40d14..640979954 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -78,6 +78,7 @@ class GameWorldView Extent GetSize() const { return size_; } void SetZoomFactor(float zoomFactor, bool smoothTransition = true); + float GetZoomFactor() const { return zoomFactor_; } float GetCurrentTargetZoomFactor() const; void SetNextZoomFactor();