diff --git a/libs/s25main/FOWObjects.h b/libs/s25main/FOWObjects.h index e50a9b8d8d..a206f94cce 100644 --- a/libs/s25main/FOWObjects.h +++ b/libs/s25main/FOWObjects.h @@ -32,6 +32,7 @@ class fowBuilding : public FOWObject void Serialize(SerializedGameData& sgd) const override; void Draw(DrawPoint drawPt) const override; FoW_Type GetType() const override { return FoW_Type::Building; } + BuildingType GetBuildingType() const { return type; } }; /// Baustelle diff --git a/libs/s25main/GlobalGameSettings.cpp b/libs/s25main/GlobalGameSettings.cpp index f5047f9893..c2d24e3ae7 100644 --- a/libs/s25main/GlobalGameSettings.cpp +++ b/libs/s25main/GlobalGameSettings.cpp @@ -108,7 +108,8 @@ void GlobalGameSettings::registerAllAddons() AddonForesterFarmFieldAvoidance, AddonForesterReachRadius, AddonWoodcutterReachRadius, - AddonStonemasonReachRadius + AddonStonemasonReachRadius, + AddonBuildingRadius >; // clang-format on using namespace boost::mp11; @@ -155,7 +156,7 @@ const GlobalGameSettings::AddonWithState* GlobalGameSettings::getAddon(AddonId i bool GlobalGameSettings::isEnabled(AddonId id) const { const auto* addon = getAddon(id); - return addon && addon->status != addon->addon->getDefaultStatus(); + return addon && addon->status != 0; } unsigned GlobalGameSettings::getSelection(AddonId id) const diff --git a/libs/s25main/addons/AddonBool.cpp b/libs/s25main/addons/AddonBool.cpp index 3286fedf3e..f72f6e2332 100644 --- a/libs/s25main/addons/AddonBool.cpp +++ b/libs/s25main/addons/AddonBool.cpp @@ -7,8 +7,9 @@ #include "Window.h" #include "controls/ctrlCheck.h" -AddonBool::AddonBool(const AddonId id, AddonGroup groups, const std::string& name, const std::string& description) - : Addon(id, groups, name, description, 0) +AddonBool::AddonBool(const AddonId id, AddonGroup groups, const std::string& name, const std::string& description, + unsigned defaultStatus) + : Addon(id, groups, name, description, defaultStatus) {} std::unique_ptr AddonBool::createGui(Window& window, bool readonly) const diff --git a/libs/s25main/addons/AddonBool.h b/libs/s25main/addons/AddonBool.h index a20e928fb8..6895e3f7c8 100644 --- a/libs/s25main/addons/AddonBool.h +++ b/libs/s25main/addons/AddonBool.h @@ -20,7 +20,8 @@ class AddonBool : public Addon }; public: - AddonBool(AddonId id, AddonGroup groups, const std::string& name, const std::string& description); + AddonBool(AddonId id, AddonGroup groups, const std::string& name, const std::string& description, + unsigned defaultStatus = 0); unsigned getNumOptions() const override; diff --git a/libs/s25main/addons/AddonBuildingRadius.h b/libs/s25main/addons/AddonBuildingRadius.h new file mode 100644 index 0000000000..7744ba6b05 --- /dev/null +++ b/libs/s25main/addons/AddonBuildingRadius.h @@ -0,0 +1,22 @@ +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "AddonBool.h" +#include "mygettext/mygettext.h" + +/** + * Show building radius information in tooltips and as map overlay + */ +class AddonBuildingRadius : public AddonBool +{ +public: + AddonBuildingRadius() + : AddonBool(AddonId::BUILDING_RADIUS, AddonGroup::GamePlay, _("Show building radius"), + _("Shows the working radius of buildings in the build menu tooltip and as an overlay on the map " + "when hovering over a building icon or selecting a building."), + 1) // Enabled by default + {} +}; diff --git a/libs/s25main/addons/Addons.h b/libs/s25main/addons/Addons.h index 96b3203ff7..fec8a57ee6 100644 --- a/libs/s25main/addons/Addons.h +++ b/libs/s25main/addons/Addons.h @@ -70,3 +70,5 @@ #include "addons/AddonForesterReachRadius.h" #include "addons/AddonStonemasonReachRadius.h" #include "addons/AddonWoodcutterReachRadius.h" + +#include "addons/AddonBuildingRadius.h" diff --git a/libs/s25main/addons/const_addons.h b/libs/s25main/addons/const_addons.h index f7ddf54f08..414bd96eca 100644 --- a/libs/s25main/addons/const_addons.h +++ b/libs/s25main/addons/const_addons.h @@ -28,6 +28,7 @@ // 010 aztimh // 011 DevOpsOfChaos // 012 MichalLabuda +// 013 Morgan // Do not forget to add your Addon to GlobalGameSettings::registerAllAddons @ GlobalGameSettings.cpp! // Never use a number twice! @@ -82,7 +83,9 @@ ENUM_WITH_STRING(AddonId, LIMIT_CATAPULTS = 0x00000000, INEXHAUSTIBLE_MINES = 0x FORESTER_FARM_FIELD_AVOIDANCE = 0x01100000, FORESTER_REACH_RADIUS = 0x01200000, WOODCUTTER_REACH_RADIUS = 0x01200001, - STONEMASON_REACH_RADIUS = 0x01200002) + STONEMASON_REACH_RADIUS = 0x01200002, + + BUILDING_RADIUS = 0x01300000) //-V:AddonId:801 enum class AddonGroup : unsigned diff --git a/libs/s25main/controls/ctrlBuildingIcon.cpp b/libs/s25main/controls/ctrlBuildingIcon.cpp index e43493f752..51ba42099c 100644 --- a/libs/s25main/controls/ctrlBuildingIcon.cpp +++ b/libs/s25main/controls/ctrlBuildingIcon.cpp @@ -4,6 +4,7 @@ #include "ctrlBuildingIcon.h" #include "Loader.h" +#include "driver/MouseCoords.h" #include "files.h" #include "ogl/glArchivItem_Bitmap.h" #include "gameTypes/BuildingType.h" @@ -28,3 +29,13 @@ void ctrlBuildingIcon::Draw_() } void ctrlBuildingIcon::DrawContent() const {} + +bool ctrlBuildingIcon::Msg_MouseMove(const MouseCoords& mc) +{ + const bool wasHovered = (state == ButtonState::Hover); + const bool result = ctrlButton::Msg_MouseMove(mc); + const bool nowHovered = (state == ButtonState::Hover); + if(wasHovered != nowHovered && onHoverChanged_) + onHoverChanged_(nowHovered); + return result; +} diff --git a/libs/s25main/controls/ctrlBuildingIcon.h b/libs/s25main/controls/ctrlBuildingIcon.h index 393d597c18..060cc70648 100644 --- a/libs/s25main/controls/ctrlBuildingIcon.h +++ b/libs/s25main/controls/ctrlBuildingIcon.h @@ -5,6 +5,9 @@ #pragma once #include "ctrlButton.h" +#include + +struct MouseCoords; class Window; class ctrlBuildingIcon : public ctrlButton @@ -15,11 +18,18 @@ class ctrlBuildingIcon : public ctrlButton /// liefert den GebäudeTyp des Icons. BuildingType GetType() const { return type; } + /// Set callback for hover-state changes (called with true when hovered, false when not) + void SetOnHoverChanged(std::function cb) { onHoverChanged_ = std::move(cb); } + protected: /// zeichnet das Fenster. void Draw_() override; void DrawContent() const override; + bool Msg_MouseMove(const MouseCoords& mc) override; const BuildingType type; /// der GebäudeType des Icons. const Nation nation; /// Volk + +private: + std::function onHoverChanged_; }; diff --git a/libs/s25main/desktops/dskGameInterface.cpp b/libs/s25main/desktops/dskGameInterface.cpp index 14038ecb1e..cba9069548 100644 --- a/libs/s25main/desktops/dskGameInterface.cpp +++ b/libs/s25main/desktops/dskGameInterface.cpp @@ -750,6 +750,9 @@ bool dskGameInterface::Msg_LeftUp(const MouseCoords& mc) bool dskGameInterface::Msg_MouseMove(const MouseCoords& mc) { + // Update radius preview for buildings under the cursor (event-driven, not in draw loop) + gwv.UpdateRadiusPreviewForMousePos(mc.pos); + if(!isScrolling) { if(mc.num_tfingers == 1) diff --git a/libs/s25main/figures/nofCatapultMan.cpp b/libs/s25main/figures/nofCatapultMan.cpp index 80b11c5a5e..065d3fd536 100644 --- a/libs/s25main/figures/nofCatapultMan.cpp +++ b/libs/s25main/figures/nofCatapultMan.cpp @@ -121,7 +121,7 @@ void nofCatapultMan::HandleDerivedEvent(const unsigned /*id*/) unsigned distance = world->CalcDistance(pos, building->GetPos()); // Entfernung nicht zu hoch? - if(distance < 14) + if(distance <= CATAPULT_MAX_TARGET_RANGE) { // Mit in die Liste aufnehmen possibleTargets.push_back(PossibleTarget(building->GetPos(), distance)); diff --git a/libs/s25main/figures/nofCatapultMan.h b/libs/s25main/figures/nofCatapultMan.h index 737cf833d4..4514f5e03b 100644 --- a/libs/s25main/figures/nofCatapultMan.h +++ b/libs/s25main/figures/nofCatapultMan.h @@ -8,6 +8,9 @@ class SerializedGameData; class nobUsual; +/// Maximum distance to a target the catapult can attack (distance < 14 -> max 13) +constexpr unsigned CATAPULT_MAX_TARGET_RANGE = 13; + /// Arbeiter im Katapult class nofCatapultMan : public nofBuildingWorker { diff --git a/libs/s25main/figures/nofHunter.cpp b/libs/s25main/figures/nofHunter.cpp index 80a57c8a79..3ef9038afe 100644 --- a/libs/s25main/figures/nofHunter.cpp +++ b/libs/s25main/figures/nofHunter.cpp @@ -132,16 +132,14 @@ void nofHunter::HandleDerivedEvent(unsigned /*id*/) void nofHunter::TryStartHunting() { // Find animals in a square around building (actually should be circle, but animals are moving anyway) - const int SQUARE_SIZE = 19; - // Liste mit den gefundenen Tieren std::vector available_animals; // Durchgehen und nach Tieren suchen Position curPos; - for(curPos.y = pos.y - SQUARE_SIZE; curPos.y <= pos.y + SQUARE_SIZE; ++curPos.y) + for(curPos.y = pos.y - HUNTER_SEARCH_HALFSIDE; curPos.y <= pos.y + HUNTER_SEARCH_HALFSIDE; ++curPos.y) { - for(curPos.x = pos.x - SQUARE_SIZE; curPos.x <= pos.x + SQUARE_SIZE; ++curPos.x) + for(curPos.x = pos.x - HUNTER_SEARCH_HALFSIDE; curPos.x <= pos.x + HUNTER_SEARCH_HALFSIDE; ++curPos.x) { MapPoint curMapPos = world->MakeMapPoint(curPos); diff --git a/libs/s25main/figures/nofHunter.h b/libs/s25main/figures/nofHunter.h index 1f780ebd5d..6e137eda6b 100644 --- a/libs/s25main/figures/nofHunter.h +++ b/libs/s25main/figures/nofHunter.h @@ -7,6 +7,9 @@ #include "nofBuildingWorker.h" #include "gameTypes/Direction.h" +/// Half-side length of the square the hunter scans for animals (centered on the building) +constexpr int HUNTER_SEARCH_HALFSIDE = 19; + class noAnimal; class SerializedGameData; class nobUsual; diff --git a/libs/s25main/gameData/BuildingConsts.cpp b/libs/s25main/gameData/BuildingConsts.cpp index 105ed23d7c..6a6708823d 100644 --- a/libs/s25main/gameData/BuildingConsts.cpp +++ b/libs/s25main/gameData/BuildingConsts.cpp @@ -3,7 +3,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "BuildingConsts.h" +#include "figures/nofCatapultMan.h" +#include "figures/nofFarmhand.h" +#include "figures/nofHunter.h" #include "mygettext/mygettext.h" +#include "gameTypes/BuildingTypes.h" +#include "gameData/GameConsts.h" +#include "gameData/MilitaryConsts.h" #include const helpers::EnumArray BUILDING_NAMES = { @@ -409,3 +415,48 @@ const helpers::MultiEnumArray BUILDING_ARMOR_SI babylonians[BuildingType::Fortress] = DrawPoint(20, -34); return result; }(); + +unsigned GetBuildingRadius(BuildingType bld) +{ + switch(bld) + { + // Military buildings (territory influence radius) — from MilitaryConsts.h + case BuildingType::Barracks: return MILITARY_RADIUS[0]; + case BuildingType::Guardhouse: return MILITARY_RADIUS[1]; + case BuildingType::Watchtower: return MILITARY_RADIUS[2]; + case BuildingType::Fortress: return MILITARY_RADIUS[3]; + // Headquarters + case BuildingType::Headquarters: return HQ_RADIUS; + // Harbor building + case BuildingType::HarborBuilding: return HARBOR_RADIUS; + // Lookout tower — scouting visibility range + case BuildingType::LookoutTower: return VISUALRANGE_LOOKOUTTOWER; + // Catapult attack range + case BuildingType::Catapult: return CATAPULT_MAX_TARGET_RANGE; + // Hunter searches for animals in a square of this half-side length + case BuildingType::Hunter: return HUNTER_SEARCH_HALFSIDE; + // Mines — miner stays inside and extracts from adjacent tiles + case BuildingType::GraniteMine: + case BuildingType::CoalMine: + case BuildingType::IronMine: + case BuildingType::GoldMine: return MINER_RADIUS; + // Farmhand-based buildings — worker goes out to gather resources from the map. + // Map each building type to its job via BLD_WORK_DESC, then query the work + // radius from nofFarmhand::GetWorkRadius. + case BuildingType::Woodcutter: + case BuildingType::Forester: + case BuildingType::Fishery: + case BuildingType::Quarry: + case BuildingType::Farm: + case BuildingType::Vineyard: + case BuildingType::Charburner: + { + const auto job = BLD_WORK_DESC[bld].job; + if(job) + return nofFarmhand::GetWorkRadius(*job); + return 0; + } + // Remaining building types have no relevant radius + default: return 0; + } +} diff --git a/libs/s25main/gameData/BuildingConsts.h b/libs/s25main/gameData/BuildingConsts.h index 8b07e0cdcb..c94a8d7aa6 100644 --- a/libs/s25main/gameData/BuildingConsts.h +++ b/libs/s25main/gameData/BuildingConsts.h @@ -119,3 +119,6 @@ constexpr std::array SUPPRESS_UNUSED NUBIAN_MINE_FIRE = {{ /// Hilfetexte für Gebäude extern const helpers::EnumArray BUILDING_HELP_STRINGS; + +/// Get the radius in tiles for a building type (worker reach, territory influence, attack range, etc.) +unsigned GetBuildingRadius(BuildingType bld); diff --git a/libs/s25main/ingameWindows/iwAction.cpp b/libs/s25main/ingameWindows/iwAction.cpp index ec810eac1f..13426cb2c5 100644 --- a/libs/s25main/ingameWindows/iwAction.cpp +++ b/libs/s25main/ingameWindows/iwAction.cpp @@ -28,6 +28,7 @@ #include "nodeObjs/noFlag.h" #include "gameData/BuildingConsts.h" #include "gameData/const_gui_ids.h" +#include #include // Tab - Flags @@ -46,7 +47,7 @@ enum TabID iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapPoint selectedPt, const DrawPoint& mousePos, Params params, bool military_buildings) : IngameWindow(CGI_ACTION, mousePos, Extent(200, 254), _("Activity window"), LOADER.GetImageN("io", 1)), gi(gi), - gwv(gwv), selectedPt(selectedPt), mousePosAtOpen_(mousePos) + gwv(gwv), selectedPt(selectedPt), mousePosAtOpen_(mousePos), activeHoveredIcon_(nullptr) { /* TAB_FLAG 1 = Land road @@ -159,6 +160,7 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP building_available[BuildingType::LeatherWorks] = false; } + const bool showBuildingRadius = gwv.GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS); constexpr helpers::EnumArray NUM_TABS = {1, 2, 3, 1, 3}; for(unsigned char i = 0; i < NUM_TABS[tabs.build_tabs]; ++i) @@ -175,6 +177,13 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP std::stringstream tooltip; tooltip << _(BUILDING_NAMES[bld]); + // Radius anzeigen falls vorhanden + unsigned radius = 0; + if(showBuildingRadius) + radius = GetBuildingRadius(bld); + if(radius > 0) + tooltip << boost::format(_("\nRange: %1% tiles")) % radius; + tooltip << _("\nCosts: "); if(BUILDING_COSTS[bld].boards > 0) tooltip << (int)BUILDING_COSTS[bld].boards << _(" boards"); @@ -186,8 +195,26 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP } DrawPoint iconPos((k % 5) * 36, (k / 5) * 36 + 45); - build_tab->GetGroup(static_cast(bt)) - ->AddBuildingIcon(k, iconPos, bld, player.nation, 36, tooltip.str()); + ctrlBuildingIcon* icon = build_tab->GetGroup(static_cast(bt)) + ->AddBuildingIcon(k, iconPos, bld, player.nation, 36, tooltip.str()); + + // Store hover callback; activeHoveredIcon_ guards against stale leaves from + // reversed child iteration order in Msg_MouseMove dispatch + if(radius > 0) + { + icon->SetOnHoverChanged([this, icon, radius](bool hovered) noexcept { + if(hovered) + { + this->activeHoveredIcon_ = icon; + this->gwv.SetRadiusPreview(std::make_pair(this->selectedPt, radius)); + } else if(this->activeHoveredIcon_ == icon) + { + this->activeHoveredIcon_ = nullptr; + this->gwv.SetRadiusPreview(std::nullopt); + } + // else: stale leave from a previously-hovered icon, ignore + }); + } ++k; } @@ -407,6 +434,8 @@ void iwAction::Close() { if(ShouldBeClosed()) return; + activeHoveredIcon_ = nullptr; + gwv.SetRadiusPreview(std::nullopt); IngameWindow::Close(); if(mousePosAtOpen_.isValid()) VIDEODRIVER.SetMousePos(mousePosAtOpen_); @@ -526,6 +555,7 @@ void iwAction::Msg_Group_TabChange(const unsigned /*group_id*/, const unsigned c void iwAction::Msg_PaintAfter() { IngameWindow::Msg_PaintAfter(); + auto* tab = GetCtrl(0); if(tab) { diff --git a/libs/s25main/ingameWindows/iwAction.h b/libs/s25main/ingameWindows/iwAction.h index 3007f4df09..2384f64189 100644 --- a/libs/s25main/ingameWindows/iwAction.h +++ b/libs/s25main/ingameWindows/iwAction.h @@ -11,6 +11,7 @@ class GameInterface; class GameWorldView; +class ctrlBuildingIcon; class ctrlGroup; class iwAction : public IngameWindow @@ -69,6 +70,9 @@ class iwAction : public IngameWindow /// Die einzelnen Höhen für die einzelnen Tabs im Bautab std::array building_tab_heights; + /// Track which icon currently owns the radius preview (to ignore stale leaves) + const ctrlBuildingIcon* activeHoveredIcon_; + public: iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapPoint selectedPt, const DrawPoint& mousePos, Params params, bool military_buildings); diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index b2f59093b7..4b4b2a790c 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -11,7 +11,10 @@ #include "GlobalGameSettings.h" #include "Loader.h" #include "MapGeometry.h" +#include "ReturnMapPointWithRadius.h" #include "Settings.h" +#include "Window.h" +#include "WindowManager.h" #include "addons/AddonMaxWaterwayLength.h" #include "buildings/noBuildingSite.h" #include "buildings/nobMilitary.h" @@ -36,11 +39,13 @@ #include #include #include +#include GameWorldView::GameWorldView(const GameWorldViewer& gwv, const Position& pos, const Extent& size) : selPt(0, 0), show_bq(SETTINGS.ingame.showBQ), show_names(SETTINGS.ingame.showNames), show_productivity(SETTINGS.ingame.showProductivity), offset(0, 0), lastOffset(0, 0), gwv(gwv), origin_(pos), - size_(size), zoomFactor_(1.f), targetZoomFactor_(1.f), zoomSpeed_(0.f) + size_(size), zoomFactor_(1.f), targetZoomFactor_(1.f), zoomSpeed_(0.f), + isBuildingRadiusEnabled_(GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS)) { updateEffectiveZoomFactor(); MoveBy({0, 0}); @@ -220,6 +225,10 @@ void GameWorldView::Draw(const RoadBuildState& rb, const MapPoint selected, bool if(show_names || show_productivity) DrawNameProductivityOverlay(terrainRenderer); + // Draw radius preview outline (set by icon-hover or map-hover) + if(radiusPreview_) + DrawRadiusOutline(radiusPreview_->first, radiusPreview_->second); + DrawGUI(rb, terrainRenderer, selected, drawMouse); // Draw catapult stones @@ -713,6 +722,81 @@ void GameWorldView::RemoveDrawNodeCallback(IDrawNodeCallback* callbackToRemove) drawNodeCallbacks.erase(itPos); } +// Snap a point to the nearest toroidal copy (map wrapping) relative to a reference. +// k = round((reference - pt) / mapSize), pt += k * mapSize. +DrawPoint GameWorldView::SnapToNearestCopy(DrawPoint pt, DrawPoint ref, DrawPoint mapPxSize) +{ + const double kx = std::floor(static_cast(ref.x - pt.x) / mapPxSize.x + 0.5); + pt.x += static_cast(kx) * mapPxSize.x; + const double ky = std::floor(static_cast(ref.y - pt.y) / mapPxSize.y + 0.5); + pt.y += static_cast(ky) * mapPxSize.y; + return pt; +} + +// ----------------------------------------------------------------------------- +// Draw radius overlay for a building's working range, handling map wrapping. +// ----------------------------------------------------------------------------- +void GameWorldView::DrawRadiusOutline(const MapPoint& center, unsigned radius) +{ + const auto& world = GetWorld(); + auto pts = world.GetPointsInRadius(center, radius, ReturnMapPointWithRadius{}); + + const MapExtent mapSize = world.GetSize(); + constexpr unsigned BORDER_COLOR = 0xFFFF0000; + const DrawPoint mapPxSize(mapSize.x * TR_W, mapSize.y * TR_H); + + // Screen position of the center vertex. + // selPtOffset accounts for the pixel shift when the map wraps at the seam. + const DrawPoint centerScr = GetNodePos(center, world.GetNode(center).altitude) + selPtOffset; + + for(const auto& [mapPt, dist] : pts) + { + if(dist != radius) + continue; + + DrawPoint screenPt = GetNodePos(mapPt, world.GetNode(mapPt).altitude); + screenPt = SnapToNearestCopy(screenPt, centerScr, mapPxSize) - offset; + + Window::DrawRectangle(Rect(screenPt - DrawPoint(2, 2), Extent(5, 5)), BORDER_COLOR); + } +} + +void GameWorldView::UpdateRadiusPreviewForMousePos(const Position& mousePos) +{ + if(!isBuildingRadiusEnabled_ || WINDOWMANAGER.FindWindowAtPos(mousePos)) + return; + + const auto& world = GetWorld(); + std::optional bldType; + switch(gwv.GetVisibility(selPt)) + { + case Visibility::Visible: + { + const auto* bld = world.GetSpecObj(selPt); + if(bld) + bldType = bld->GetBuildingType(); + } + break; + case Visibility::FogOfWar: + { + const FOWObject* fow = gwv.GetYoungestFOWObject(selPt); + if(fow && fow->GetType() == FoW_Type::Building) + bldType = static_cast(*fow).GetBuildingType(); + } + break; + } + + if(bldType) + { + const unsigned bldRadius = GetBuildingRadius(*bldType); + if(bldRadius > 0) + radiusPreview_ = std::make_pair(selPt, bldRadius); + else + radiusPreview_ = std::nullopt; + } else + radiusPreview_ = std::nullopt; +} + void GameWorldView::CalcFxLx() { // Calc first and last point in map units (with 1 extra for incomplete triangles) diff --git a/libs/s25main/world/GameWorldView.h b/libs/s25main/world/GameWorldView.h index c405ec803e..4238f3809b 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -9,6 +9,7 @@ #include "gameTypes/MapCoordinates.h" #include "gameTypes/MapTypes.h" #include +#include #include class GameWorldBase; @@ -46,6 +47,11 @@ class GameWorldView /// Show productivities bool show_productivity; + /// Optional radius preview (center position, radius) drawn as outline on the map + std::optional> radiusPreview_; + /// Cached addon status: whether building radius overlay is enabled + bool isBuildingRadiusEnabled_; + /// Offset from world origin in screen units (not map units): "scroll position" DrawPoint offset; /// Last scroll position (before jump) @@ -117,6 +123,13 @@ class GameWorldView void AddDrawNodeCallback(IDrawNodeCallback* newCallback); void RemoveDrawNodeCallback(IDrawNodeCallback* callbackToRemove); + /// Set/show a radius preview outline on the map (or clear with std::nullopt) + void SetRadiusPreview(const std::optional>& preview) { radiusPreview_ = preview; } + + /// Update radius preview based on which map node is under the mouse cursor + /// Called from mouse-move handler, not from draw loop + void UpdateRadiusPreviewForMousePos(const Position& mousePos); + /// Gibt selektierten Punkt zurück MapPoint GetSelectedPt() const { return selPt; } @@ -146,6 +159,12 @@ class GameWorldView void DrawGUI(const RoadBuildState& rb, const TerrainRenderer& terrainRenderer, const MapPoint& selectedPt, bool drawMouse); + /// Draw a radius outline ring around a center point with the given radius + void DrawRadiusOutline(const MapPoint& center, unsigned radius); + + /// Snap a point to the nearest toroidal copy relative to a reference position + static DrawPoint SnapToNearestCopy(DrawPoint pt, DrawPoint ref, DrawPoint mapPxSize); + void SaveIngameSettingsValues() const; void updateEffectiveZoomFactor(); };