From 56b78e05e422b5cf1949980d0dd2820a33421f4f Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Sun, 14 Jun 2026 18:24:13 +0200 Subject: [PATCH 01/23] Radius overlay & tooltips --- libs/s25main/FOWObjects.h | 1 + libs/s25main/controls/ctrlBuildingIcon.cpp | 11 ++++ libs/s25main/controls/ctrlBuildingIcon.h | 8 +++ libs/s25main/gameData/BuildingConsts.cpp | 55 +++++++++++++++++ libs/s25main/gameData/BuildingConsts.h | 14 +++++ libs/s25main/ingameWindows/iwAction.cpp | 37 +++++++++++- libs/s25main/ingameWindows/iwAction.h | 6 ++ libs/s25main/world/GameWorldView.cpp | 68 ++++++++++++++++++++++ libs/s25main/world/GameWorldView.h | 10 ++++ 9 files changed, 208 insertions(+), 2 deletions(-) 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/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..2c4d07b85d 100644 --- a/libs/s25main/controls/ctrlBuildingIcon.h +++ b/libs/s25main/controls/ctrlBuildingIcon.h @@ -5,6 +5,7 @@ #pragma once #include "ctrlButton.h" +#include class Window; class ctrlBuildingIcon : public ctrlButton @@ -15,11 +16,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/gameData/BuildingConsts.cpp b/libs/s25main/gameData/BuildingConsts.cpp index 105ed23d7c..8571258c31 100644 --- a/libs/s25main/gameData/BuildingConsts.cpp +++ b/libs/s25main/gameData/BuildingConsts.cpp @@ -3,7 +3,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "BuildingConsts.h" +#include "GlobalGameSettings.h" +#include "addons/AddonForesterReachRadius.h" +#include "addons/AddonStonemasonReachRadius.h" +#include "addons/AddonWoodcutterReachRadius.h" +#include "addons/const_addons.h" #include "mygettext/mygettext.h" +#include "gameData/GameConsts.h" +#include "gameData/MilitaryConsts.h" #include const helpers::EnumArray BUILDING_NAMES = { @@ -409,3 +416,51 @@ const helpers::MultiEnumArray BUILDING_ARMOR_SI babylonians[BuildingType::Fortress] = DrawPoint(20, -34); return result; }(); + +unsigned GetBuildingRadius(BuildingType bld, const GlobalGameSettings& ggs) +{ + switch(bld) + { + // Military buildings (territory influence radius) + 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; + // Production buildings with (adjustable) reach radius + case BuildingType::Woodcutter: + { + const unsigned sel = ggs.getSelection(AddonId::WOODCUTTER_REACH_RADIUS); + return woodcutterRadiusValues[sel]; + } + case BuildingType::Forester: + { + const unsigned sel = ggs.getSelection(AddonId::FORESTER_REACH_RADIUS); + return foresterRadiusValues[sel]; + } + case BuildingType::Quarry: + { + const unsigned sel = ggs.getSelection(AddonId::STONEMASON_REACH_RADIUS); + return stonemasonRadiusValues[sel]; + } + case BuildingType::Fishery: return FISHERY_RADIUS; + case BuildingType::Catapult: return CATAPULT_RANGE; + case BuildingType::LookoutTower: + return VISUALRANGE_LOOKOUTTOWER; // Scouting range + // Farm, Charburner, and Hunter have fixed working/search radii + case BuildingType::Farm: return FARMER_RADIUS; + case BuildingType::Charburner: return CHARBURNER_RADIUS; + case BuildingType::Hunter: + return HUNTER_SEARCH_RADIUS; // Search radius (square half-side 19) + // Mines extract resources from within a 2-tile radius (miner stays inside building) + case BuildingType::GraniteMine: + case BuildingType::CoalMine: + case BuildingType::IronMine: + case BuildingType::GoldMine: return MINER_RADIUS; + // Buildings that don't have a notable radius + default: return 0; + } +} diff --git a/libs/s25main/gameData/BuildingConsts.h b/libs/s25main/gameData/BuildingConsts.h index 1b7f3f1ffb..8e156b6eb1 100644 --- a/libs/s25main/gameData/BuildingConsts.h +++ b/libs/s25main/gameData/BuildingConsts.h @@ -119,3 +119,17 @@ constexpr std::array NUBIAN_MINE_FIRE = {{ /// Hilfetexte für Gebäude extern const helpers::EnumArray BUILDING_HELP_STRINGS; + +/// Reach radius for various production buildings (in tiles) +constexpr unsigned FISHERY_RADIUS = 7; ///< Fisher searches for fish within this radius +constexpr unsigned HUNTER_SEARCH_RADIUS = 19; ///< Hunter searches for animals in a square of this half-side length +constexpr unsigned FARMER_RADIUS = 2; ///< Farmer plants and harvests crops within this radius +constexpr unsigned CHARBURNER_RADIUS = 3; ///< Charburner places and harvests charcoal piles within this radius +constexpr unsigned CATAPULT_RANGE = 12; ///< Catapult attack range + +class GlobalGameSettings; + +/// Get the radius in tiles for a building type (influence radius for military, working radius for workers, attack range +/// for catapult) Uses the actual game settings to get the current addon selection for adjustable ranges (woodcutter, +/// forester, stonemason). +unsigned GetBuildingRadius(BuildingType bld, const GlobalGameSettings& ggs); diff --git a/libs/s25main/ingameWindows/iwAction.cpp b/libs/s25main/ingameWindows/iwAction.cpp index ec810eac1f..64a8110a93 100644 --- a/libs/s25main/ingameWindows/iwAction.cpp +++ b/libs/s25main/ingameWindows/iwAction.cpp @@ -175,6 +175,11 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP std::stringstream tooltip; tooltip << _(BUILDING_NAMES[bld]); + // Radius anzeigen falls vorhanden + const unsigned radius = GetBuildingRadius(bld, gwv.GetWorld().GetGGS()); + if(radius > 0) + tooltip << _("\nRange: ") << radius << _(" tiles"); + tooltip << _("\nCosts: "); if(BUILDING_COSTS[bld].boards > 0) tooltip << (int)BUILDING_COSTS[bld].boards << _(" boards"); @@ -186,8 +191,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()); + + // Set hover callback to show radius preview on the game world + const unsigned bldRadius = GetBuildingRadius(bld, gwv.GetWorld().GetGGS()); + if(bldRadius > 0) + { + icon->SetOnHoverChanged([this, icon, bldRadius](bool hovered) noexcept { + if(hovered) + { + hoveredBldIcon_ = icon; + this->gwv.SetRadiusPreview(std::make_pair(this->selectedPt, bldRadius)); + } else if(hoveredBldIcon_ == icon) + { + // Only clear if no other icon took over hover + hoveredBldIcon_ = nullptr; + this->gwv.SetRadiusPreview(boost::none); + } + }); + } ++k; } @@ -407,6 +430,8 @@ void iwAction::Close() { if(ShouldBeClosed()) return; + // Clear radius preview on the game world + gwv.SetRadiusPreview(boost::none); IngameWindow::Close(); if(mousePosAtOpen_.isValid()) VIDEODRIVER.SetMousePos(mousePosAtOpen_); @@ -469,6 +494,10 @@ void iwAction::Msg_Group_ButtonClick(const unsigned /*group_id*/, const unsigned void iwAction::Msg_TabChange(const unsigned ctrl_id, const unsigned short tab_id) { + // Clear radius preview when switching tabs — old icons won't trigger mouse-leave + hoveredBldIcon_ = nullptr; + gwv.SetRadiusPreview(boost::none); + switch(ctrl_id) { case 0: // Haupttabs @@ -513,6 +542,10 @@ void iwAction::Msg_TabChange(const unsigned ctrl_id, const unsigned short tab_id void iwAction::Msg_Group_TabChange(const unsigned /*group_id*/, const unsigned ctrl_id, const unsigned short tab_id) { + // Clear radius preview when switching build subtabs, same reason as above + hoveredBldIcon_ = nullptr; + gwv.SetRadiusPreview(boost::none); + switch(ctrl_id) { case 1: // Gebäudetabs diff --git a/libs/s25main/ingameWindows/iwAction.h b/libs/s25main/ingameWindows/iwAction.h index 3007f4df09..dd74998691 100644 --- a/libs/s25main/ingameWindows/iwAction.h +++ b/libs/s25main/ingameWindows/iwAction.h @@ -7,6 +7,9 @@ #include "IngameWindow.h" #include "variant.h" #include "gameTypes/MapCoordinates.h" +#include + +class ctrlBuildingIcon; #include class GameInterface; @@ -69,6 +72,9 @@ class iwAction : public IngameWindow /// Die einzelnen Höhen für die einzelnen Tabs im Bautab std::array building_tab_heights; + /// The building icon currently hovered in the build tabs (used to show radius preview) + const ctrlBuildingIcon* hoveredBldIcon_ = nullptr; + 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 4b087267db..2acc680380 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -11,7 +11,9 @@ #include "GlobalGameSettings.h" #include "Loader.h" #include "MapGeometry.h" +#include "ReturnMapPointWithRadius.h" #include "Settings.h" +#include "Window.h" #include "addons/AddonMaxWaterwayLength.h" #include "buildings/noBuildingSite.h" #include "buildings/nobMilitary.h" @@ -34,6 +36,7 @@ #include "s25util/error.h" #include #include +#include #include GameWorldView::GameWorldView(const GameWorldViewer& gwv, const Position& pos, const Extent& size) @@ -219,6 +222,36 @@ void GameWorldView::Draw(const RoadBuildState& rb, const MapPoint selected, bool if(show_names || show_productivity) DrawNameProductivityOverlay(terrainRenderer); + // Draw radius preview outline (if set via action window hover) + if(radiusPreview_) + DrawRadiusOutline(radiusPreview_->first, radiusPreview_->second); + + // Draw radius outline for the building under the mouse cursor + if(!radiusPreview_ && mousePos.x >= 0 && mousePos.x < static_cast(size_.x) && mousePos.y >= 0 + && mousePos.y < static_cast(size_.y)) + { + boost::optional bldType; + const Visibility vis = gwv.GetVisibility(selPt); + if(vis == Visibility::Visible) + { + const auto* bld = GetWorld().GetSpecObj(selPt); + if(bld) + bldType = bld->GetBuildingType(); + } else if(vis == Visibility::FogOfWar) + { + const FOWObject* fow = gwv.GetYoungestFOWObject(selPt); + if(fow && fow->GetType() == FoW_Type::Building) + bldType = static_cast(*fow).GetBuildingType(); + } + + if(bldType) + { + const unsigned bldRadius = GetBuildingRadius(*bldType, GetWorld().GetGGS()); + if(bldRadius > 0) + DrawRadiusOutline(selPt, bldRadius); + } + } + DrawGUI(rb, terrainRenderer, selected, drawMouse); // Draw catapult stones @@ -712,6 +745,41 @@ void GameWorldView::RemoveDrawNodeCallback(IDrawNodeCallback* callbackToRemove) drawNodeCallbacks.erase(itPos); } +void GameWorldView::DrawRadiusOutline(const MapPoint& center, unsigned radius) +{ + const auto& world = GetWorld(); + // Get all border points at the exact radius + auto pts = world.GetPointsInRadius(center, radius, ReturnMapPointWithRadius{}); + + const MapExtent mapSize = world.GetSize(); + constexpr unsigned BORDER_COLOR = 0xFFFF0000; // Red with full alpha + + const int w = mapSize.x; + const int h = mapSize.y; + + for(const auto& ptWithRadius : pts) + { + if(ptWithRadius.second != radius) + continue; + + const Position pt(ptWithRadius.first); + + // Draw at all 9 toroidal copies (canonical ± 1 map dimension). + // Using all copies guarantees the ring is continuous across the seam + // regardless of viewport position — the renderer clips off-screen pixels. + for(int dw : {-w, 0, w}) + { + for(int dh : {-h, 0, h}) + { + const Position copyPos = pt + Position(dw, dh); + const auto alt = world.GetNode(MakeMapPoint(copyPos, mapSize)).altitude; + const DrawPoint scr = Position(GetNodePos(copyPos) - Position(0, HEIGHT_FACTOR * alt)) - offset; + Window::DrawRectangle(Rect(scr - DrawPoint(2, 2), Extent(5, 5)), BORDER_COLOR); + } + } + } +} + 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..762196f278 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -8,6 +8,7 @@ #include "DrawPoint.h" #include "gameTypes/MapCoordinates.h" #include "gameTypes/MapTypes.h" +#include #include #include @@ -46,6 +47,9 @@ class GameWorldView /// Show productivities bool show_productivity; + /// Optional radius preview (center position, radius) drawn as outline on the map + boost::optional> radiusPreview_; + /// Offset from world origin in screen units (not map units): "scroll position" DrawPoint offset; /// Last scroll position (before jump) @@ -117,6 +121,9 @@ class GameWorldView void AddDrawNodeCallback(IDrawNodeCallback* newCallback); void RemoveDrawNodeCallback(IDrawNodeCallback* callbackToRemove); + /// Set/show a radius preview outline on the map (or clear with boost::none) + void SetRadiusPreview(const boost::optional>& preview) { radiusPreview_ = preview; } + /// Gibt selektierten Punkt zurück MapPoint GetSelectedPt() const { return selPt; } @@ -146,6 +153,9 @@ 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); + void SaveIngameSettingsValues() const; void updateEffectiveZoomFactor(); }; From 7bc74e590fadb8dfccdee03e2daf607e34e4cd3b Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Mon, 15 Jun 2026 10:50:41 +0200 Subject: [PATCH 02/23] Tidy --- libs/s25main/figures/nofCatapultMan.cpp | 2 +- libs/s25main/figures/nofCatapultMan.h | 3 ++ libs/s25main/figures/nofHunter.cpp | 6 +-- libs/s25main/figures/nofHunter.h | 3 ++ libs/s25main/gameData/BuildingConsts.cpp | 58 ++++++++++-------------- libs/s25main/gameData/BuildingConsts.h | 15 +----- libs/s25main/ingameWindows/iwAction.cpp | 21 +++++---- libs/s25main/ingameWindows/iwAction.h | 2 - libs/s25main/world/GameWorldView.cpp | 14 +++--- libs/s25main/world/GameWorldView.h | 8 ++-- 10 files changed, 56 insertions(+), 76 deletions(-) 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 c22745b8fb..6a660611e2 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 8571258c31..6fd9ea9474 100644 --- a/libs/s25main/gameData/BuildingConsts.cpp +++ b/libs/s25main/gameData/BuildingConsts.cpp @@ -3,12 +3,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "BuildingConsts.h" -#include "GlobalGameSettings.h" -#include "addons/AddonForesterReachRadius.h" -#include "addons/AddonStonemasonReachRadius.h" -#include "addons/AddonWoodcutterReachRadius.h" -#include "addons/const_addons.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 @@ -417,11 +416,11 @@ const helpers::MultiEnumArray BUILDING_ARMOR_SI return result; }(); -unsigned GetBuildingRadius(BuildingType bld, const GlobalGameSettings& ggs) +unsigned GetBuildingRadius(BuildingType bld) { switch(bld) { - // Military buildings (territory influence radius) + // 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]; @@ -430,37 +429,26 @@ unsigned GetBuildingRadius(BuildingType bld, const GlobalGameSettings& ggs) case BuildingType::Headquarters: return HQ_RADIUS; // Harbor building case BuildingType::HarborBuilding: return HARBOR_RADIUS; - // Production buildings with (adjustable) reach radius - case BuildingType::Woodcutter: - { - const unsigned sel = ggs.getSelection(AddonId::WOODCUTTER_REACH_RADIUS); - return woodcutterRadiusValues[sel]; - } - case BuildingType::Forester: - { - const unsigned sel = ggs.getSelection(AddonId::FORESTER_REACH_RADIUS); - return foresterRadiusValues[sel]; - } - case BuildingType::Quarry: - { - const unsigned sel = ggs.getSelection(AddonId::STONEMASON_REACH_RADIUS); - return stonemasonRadiusValues[sel]; - } - case BuildingType::Fishery: return FISHERY_RADIUS; - case BuildingType::Catapult: return CATAPULT_RANGE; - case BuildingType::LookoutTower: - return VISUALRANGE_LOOKOUTTOWER; // Scouting range - // Farm, Charburner, and Hunter have fixed working/search radii - case BuildingType::Farm: return FARMER_RADIUS; - case BuildingType::Charburner: return CHARBURNER_RADIUS; - case BuildingType::Hunter: - return HUNTER_SEARCH_RADIUS; // Search radius (square half-side 19) - // Mines extract resources from within a 2-tile radius (miner stays inside building) + // 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; - // Buildings that don't have a notable radius - default: return 0; + // For farmhand-based buildings, delegate to nofFarmhand::GetWorkRadius via the BLD_WORK_DESC + // job mapping. This is the single source of truth for worker reach radii, including + // addon-adjustable ranges (woodcutter, forester, stonemason). + default: + { + const auto job = BLD_WORK_DESC[bld].job; + if(job) + return nofFarmhand::GetWorkRadius(*job); + return 0; + } } } diff --git a/libs/s25main/gameData/BuildingConsts.h b/libs/s25main/gameData/BuildingConsts.h index 8e156b6eb1..166c753895 100644 --- a/libs/s25main/gameData/BuildingConsts.h +++ b/libs/s25main/gameData/BuildingConsts.h @@ -120,16 +120,5 @@ constexpr std::array NUBIAN_MINE_FIRE = {{ /// Hilfetexte für Gebäude extern const helpers::EnumArray BUILDING_HELP_STRINGS; -/// Reach radius for various production buildings (in tiles) -constexpr unsigned FISHERY_RADIUS = 7; ///< Fisher searches for fish within this radius -constexpr unsigned HUNTER_SEARCH_RADIUS = 19; ///< Hunter searches for animals in a square of this half-side length -constexpr unsigned FARMER_RADIUS = 2; ///< Farmer plants and harvests crops within this radius -constexpr unsigned CHARBURNER_RADIUS = 3; ///< Charburner places and harvests charcoal piles within this radius -constexpr unsigned CATAPULT_RANGE = 12; ///< Catapult attack range - -class GlobalGameSettings; - -/// Get the radius in tiles for a building type (influence radius for military, working radius for workers, attack range -/// for catapult) Uses the actual game settings to get the current addon selection for adjustable ranges (woodcutter, -/// forester, stonemason). -unsigned GetBuildingRadius(BuildingType bld, const GlobalGameSettings& ggs); +/// 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 64a8110a93..89f3609058 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 @@ -176,9 +177,9 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP tooltip << _(BUILDING_NAMES[bld]); // Radius anzeigen falls vorhanden - const unsigned radius = GetBuildingRadius(bld, gwv.GetWorld().GetGGS()); + const unsigned radius = GetBuildingRadius(bld); if(radius > 0) - tooltip << _("\nRange: ") << radius << _(" tiles"); + tooltip << boost::format(_("\nRange: %1% tiles")) % radius; tooltip << _("\nCosts: "); if(BUILDING_COSTS[bld].boards > 0) @@ -195,19 +196,18 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP ->AddBuildingIcon(k, iconPos, bld, player.nation, 36, tooltip.str()); // Set hover callback to show radius preview on the game world - const unsigned bldRadius = GetBuildingRadius(bld, gwv.GetWorld().GetGGS()); - if(bldRadius > 0) + if(radius > 0) { - icon->SetOnHoverChanged([this, icon, bldRadius](bool hovered) noexcept { + icon->SetOnHoverChanged([this, icon, radius](bool hovered) noexcept { if(hovered) { hoveredBldIcon_ = icon; - this->gwv.SetRadiusPreview(std::make_pair(this->selectedPt, bldRadius)); + this->gwv.SetRadiusPreview(std::make_pair(this->selectedPt, radius)); } else if(hoveredBldIcon_ == icon) { // Only clear if no other icon took over hover hoveredBldIcon_ = nullptr; - this->gwv.SetRadiusPreview(boost::none); + this->gwv.SetRadiusPreview(std::nullopt); } }); } @@ -431,7 +431,8 @@ void iwAction::Close() if(ShouldBeClosed()) return; // Clear radius preview on the game world - gwv.SetRadiusPreview(boost::none); + hoveredBldIcon_ = nullptr; + gwv.SetRadiusPreview(std::nullopt); IngameWindow::Close(); if(mousePosAtOpen_.isValid()) VIDEODRIVER.SetMousePos(mousePosAtOpen_); @@ -496,7 +497,7 @@ void iwAction::Msg_TabChange(const unsigned ctrl_id, const unsigned short tab_id { // Clear radius preview when switching tabs — old icons won't trigger mouse-leave hoveredBldIcon_ = nullptr; - gwv.SetRadiusPreview(boost::none); + gwv.SetRadiusPreview(std::nullopt); switch(ctrl_id) { @@ -544,7 +545,7 @@ void iwAction::Msg_Group_TabChange(const unsigned /*group_id*/, const unsigned c { // Clear radius preview when switching build subtabs, same reason as above hoveredBldIcon_ = nullptr; - gwv.SetRadiusPreview(boost::none); + gwv.SetRadiusPreview(std::nullopt); switch(ctrl_id) { diff --git a/libs/s25main/ingameWindows/iwAction.h b/libs/s25main/ingameWindows/iwAction.h index dd74998691..d41b9a3eb6 100644 --- a/libs/s25main/ingameWindows/iwAction.h +++ b/libs/s25main/ingameWindows/iwAction.h @@ -7,8 +7,6 @@ #include "IngameWindow.h" #include "variant.h" #include "gameTypes/MapCoordinates.h" -#include - class ctrlBuildingIcon; #include diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index 2acc680380..26fd39b2a7 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -36,8 +36,8 @@ #include "s25util/error.h" #include #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), @@ -230,7 +230,7 @@ void GameWorldView::Draw(const RoadBuildState& rb, const MapPoint selected, bool if(!radiusPreview_ && mousePos.x >= 0 && mousePos.x < static_cast(size_.x) && mousePos.y >= 0 && mousePos.y < static_cast(size_.y)) { - boost::optional bldType; + std::optional bldType; const Visibility vis = gwv.GetVisibility(selPt); if(vis == Visibility::Visible) { @@ -246,7 +246,7 @@ void GameWorldView::Draw(const RoadBuildState& rb, const MapPoint selected, bool if(bldType) { - const unsigned bldRadius = GetBuildingRadius(*bldType, GetWorld().GetGGS()); + const unsigned bldRadius = GetBuildingRadius(*bldType); if(bldRadius > 0) DrawRadiusOutline(selPt, bldRadius); } @@ -762,7 +762,7 @@ void GameWorldView::DrawRadiusOutline(const MapPoint& center, unsigned radius) if(ptWithRadius.second != radius) continue; - const Position pt(ptWithRadius.first); + const MapPoint& basePt = ptWithRadius.first; // Draw at all 9 toroidal copies (canonical ± 1 map dimension). // Using all copies guarantees the ring is continuous across the seam @@ -771,9 +771,9 @@ void GameWorldView::DrawRadiusOutline(const MapPoint& center, unsigned radius) { for(int dh : {-h, 0, h}) { - const Position copyPos = pt + Position(dw, dh); - const auto alt = world.GetNode(MakeMapPoint(copyPos, mapSize)).altitude; - const DrawPoint scr = Position(GetNodePos(copyPos) - Position(0, HEIGHT_FACTOR * alt)) - offset; + const MapPoint copyPt = MakeMapPoint(Position(basePt.x + dw, basePt.y + dh), mapSize); + const auto alt = world.GetNode(copyPt).altitude; + const DrawPoint scr = GetNodePos(copyPt) - DrawPoint(0, HEIGHT_FACTOR * alt) - offset; Window::DrawRectangle(Rect(scr - DrawPoint(2, 2), Extent(5, 5)), BORDER_COLOR); } } diff --git a/libs/s25main/world/GameWorldView.h b/libs/s25main/world/GameWorldView.h index 762196f278..d2f8cde22f 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -8,8 +8,8 @@ #include "DrawPoint.h" #include "gameTypes/MapCoordinates.h" #include "gameTypes/MapTypes.h" -#include #include +#include #include class GameWorldBase; @@ -48,7 +48,7 @@ class GameWorldView bool show_productivity; /// Optional radius preview (center position, radius) drawn as outline on the map - boost::optional> radiusPreview_; + std::optional> radiusPreview_; /// Offset from world origin in screen units (not map units): "scroll position" DrawPoint offset; @@ -121,8 +121,8 @@ class GameWorldView void AddDrawNodeCallback(IDrawNodeCallback* newCallback); void RemoveDrawNodeCallback(IDrawNodeCallback* callbackToRemove); - /// Set/show a radius preview outline on the map (or clear with boost::none) - void SetRadiusPreview(const boost::optional>& preview) { radiusPreview_ = preview; } + /// Set/show a radius preview outline on the map (or clear with std::nullopt) + void SetRadiusPreview(const std::optional>& preview) { radiusPreview_ = preview; } /// Gibt selektierten Punkt zurück MapPoint GetSelectedPt() const { return selPt; } From 9a02c3a8487b66f15014ad6c08e1a8ddae671193 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Mon, 15 Jun 2026 17:01:35 +0200 Subject: [PATCH 03/23] Simpify UI/mouse changes --- libs/s25main/ingameWindows/iwAction.cpp | 20 ++------------------ libs/s25main/ingameWindows/iwAction.h | 4 ---- libs/s25main/world/GameWorldView.cpp | 7 ++----- 3 files changed, 4 insertions(+), 27 deletions(-) diff --git a/libs/s25main/ingameWindows/iwAction.cpp b/libs/s25main/ingameWindows/iwAction.cpp index 89f3609058..4ce793085f 100644 --- a/libs/s25main/ingameWindows/iwAction.cpp +++ b/libs/s25main/ingameWindows/iwAction.cpp @@ -198,17 +198,11 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP // Set hover callback to show radius preview on the game world if(radius > 0) { - icon->SetOnHoverChanged([this, icon, radius](bool hovered) noexcept { + icon->SetOnHoverChanged([this, radius](bool hovered) noexcept { if(hovered) - { - hoveredBldIcon_ = icon; this->gwv.SetRadiusPreview(std::make_pair(this->selectedPt, radius)); - } else if(hoveredBldIcon_ == icon) - { - // Only clear if no other icon took over hover - hoveredBldIcon_ = nullptr; + else this->gwv.SetRadiusPreview(std::nullopt); - } }); } @@ -430,8 +424,6 @@ void iwAction::Close() { if(ShouldBeClosed()) return; - // Clear radius preview on the game world - hoveredBldIcon_ = nullptr; gwv.SetRadiusPreview(std::nullopt); IngameWindow::Close(); if(mousePosAtOpen_.isValid()) @@ -495,10 +487,6 @@ void iwAction::Msg_Group_ButtonClick(const unsigned /*group_id*/, const unsigned void iwAction::Msg_TabChange(const unsigned ctrl_id, const unsigned short tab_id) { - // Clear radius preview when switching tabs — old icons won't trigger mouse-leave - hoveredBldIcon_ = nullptr; - gwv.SetRadiusPreview(std::nullopt); - switch(ctrl_id) { case 0: // Haupttabs @@ -543,10 +531,6 @@ void iwAction::Msg_TabChange(const unsigned ctrl_id, const unsigned short tab_id void iwAction::Msg_Group_TabChange(const unsigned /*group_id*/, const unsigned ctrl_id, const unsigned short tab_id) { - // Clear radius preview when switching build subtabs, same reason as above - hoveredBldIcon_ = nullptr; - gwv.SetRadiusPreview(std::nullopt); - switch(ctrl_id) { case 1: // Gebäudetabs diff --git a/libs/s25main/ingameWindows/iwAction.h b/libs/s25main/ingameWindows/iwAction.h index d41b9a3eb6..3007f4df09 100644 --- a/libs/s25main/ingameWindows/iwAction.h +++ b/libs/s25main/ingameWindows/iwAction.h @@ -7,7 +7,6 @@ #include "IngameWindow.h" #include "variant.h" #include "gameTypes/MapCoordinates.h" -class ctrlBuildingIcon; #include class GameInterface; @@ -70,9 +69,6 @@ class iwAction : public IngameWindow /// Die einzelnen Höhen für die einzelnen Tabs im Bautab std::array building_tab_heights; - /// The building icon currently hovered in the build tabs (used to show radius preview) - const ctrlBuildingIcon* hoveredBldIcon_ = nullptr; - 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 26fd39b2a7..361afd9a92 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -222,13 +222,10 @@ void GameWorldView::Draw(const RoadBuildState& rb, const MapPoint selected, bool if(show_names || show_productivity) DrawNameProductivityOverlay(terrainRenderer); - // Draw radius preview outline (if set via action window hover) + // Draw radius preview outline — either from build-menu hover or from the building under the cursor if(radiusPreview_) DrawRadiusOutline(radiusPreview_->first, radiusPreview_->second); - - // Draw radius outline for the building under the mouse cursor - if(!radiusPreview_ && mousePos.x >= 0 && mousePos.x < static_cast(size_.x) && mousePos.y >= 0 - && mousePos.y < static_cast(size_.y)) + else { std::optional bldType; const Visibility vis = gwv.GetVisibility(selPt); From 0d9781cf04769a238c51cb9f75bd0332052638a5 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Mon, 15 Jun 2026 17:09:07 +0200 Subject: [PATCH 04/23] Fix map wrap clipping again --- libs/s25main/world/GameWorldView.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index 361afd9a92..cdbdafa557 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -760,17 +760,17 @@ void GameWorldView::DrawRadiusOutline(const MapPoint& center, unsigned radius) continue; const MapPoint& basePt = ptWithRadius.first; + const auto alt = world.GetNode(basePt).altitude; - // Draw at all 9 toroidal copies (canonical ± 1 map dimension). - // Using all copies guarantees the ring is continuous across the seam - // regardless of viewport position — the renderer clips off-screen pixels. + // Draw at the canonical position and its 8 toroidal copies (shifted by ± map dimensions). + // We use Position arithmetic (no modulo) so each shifted copy produces a different screen + // position. Copies outside the viewport are clipped by the renderer. for(int dw : {-w, 0, w}) { for(int dh : {-h, 0, h}) { - const MapPoint copyPt = MakeMapPoint(Position(basePt.x + dw, basePt.y + dh), mapSize); - const auto alt = world.GetNode(copyPt).altitude; - const DrawPoint scr = GetNodePos(copyPt) - DrawPoint(0, HEIGHT_FACTOR * alt) - offset; + const DrawPoint scr = + GetNodePos(Position(basePt.x + dw, basePt.y + dh)) - DrawPoint(0, HEIGHT_FACTOR * alt) - offset; Window::DrawRectangle(Rect(scr - DrawPoint(2, 2), Extent(5, 5)), BORDER_COLOR); } } From 0ed50126fe374aff4bc8427c367315945a190c9c Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Tue, 16 Jun 2026 11:35:28 +0200 Subject: [PATCH 05/23] Fix "Invalid job id" from GetWorkRadius() --- libs/s25main/gameData/BuildingConsts.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/libs/s25main/gameData/BuildingConsts.cpp b/libs/s25main/gameData/BuildingConsts.cpp index 6fd9ea9474..a4b7ac3dca 100644 --- a/libs/s25main/gameData/BuildingConsts.cpp +++ b/libs/s25main/gameData/BuildingConsts.cpp @@ -440,15 +440,23 @@ unsigned GetBuildingRadius(BuildingType bld) case BuildingType::CoalMine: case BuildingType::IronMine: case BuildingType::GoldMine: return MINER_RADIUS; - // For farmhand-based buildings, delegate to nofFarmhand::GetWorkRadius via the BLD_WORK_DESC - // job mapping. This is the single source of truth for worker reach radii, including - // addon-adjustable ranges (woodcutter, forester, stonemason). - default: + // 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; } + // All other building types have no relevant radius overlay + default: return 0; } } From 46e8a4c8f278f99dba5f8b1df42923d66788462c Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Tue, 16 Jun 2026 12:30:04 +0200 Subject: [PATCH 06/23] Toggle via addon --- libs/s25main/GlobalGameSettings.cpp | 3 +- libs/s25main/addons/AddonBuildingRadius.h | 21 +++++++++++ libs/s25main/addons/Addons.h | 2 + libs/s25main/addons/const_addons.h | 5 ++- libs/s25main/controls/ctrlBuildingIcon.cpp | 11 ------ libs/s25main/controls/ctrlBuildingIcon.h | 8 ---- libs/s25main/ingameWindows/iwAction.cpp | 44 +++++++++++++++------- libs/s25main/world/GameWorldView.cpp | 6 ++- 8 files changed, 63 insertions(+), 37 deletions(-) create mode 100644 libs/s25main/addons/AddonBuildingRadius.h diff --git a/libs/s25main/GlobalGameSettings.cpp b/libs/s25main/GlobalGameSettings.cpp index d0fa0d3e0c..b60cb4e173 100644 --- a/libs/s25main/GlobalGameSettings.cpp +++ b/libs/s25main/GlobalGameSettings.cpp @@ -120,7 +120,8 @@ void GlobalGameSettings::registerAllAddons() AddonForesterFarmFieldAvoidance, AddonForesterReachRadius, AddonWoodcutterReachRadius, - AddonStonemasonReachRadius + AddonStonemasonReachRadius, + AddonBuildingRadius >; // clang-format on using namespace boost::mp11; diff --git a/libs/s25main/addons/AddonBuildingRadius.h b/libs/s25main/addons/AddonBuildingRadius.h new file mode 100644 index 0000000000..de9d493872 --- /dev/null +++ b/libs/s25main/addons/AddonBuildingRadius.h @@ -0,0 +1,21 @@ +// 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.")) + {} +}; diff --git a/libs/s25main/addons/Addons.h b/libs/s25main/addons/Addons.h index b5f912e1de..20d8493407 100644 --- a/libs/s25main/addons/Addons.h +++ b/libs/s25main/addons/Addons.h @@ -72,3 +72,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 31165b45e5..d27add09e3 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! @@ -88,7 +89,9 @@ ENUM_WITH_STRING(AddonId, LIMIT_CATAPULTS = 0x00000000, INEXHAUSTIBLE_MINES = 0x STRANDED_SOLDIER_RETURN_SEARCH = 0x01100001, 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 51ba42099c..e43493f752 100644 --- a/libs/s25main/controls/ctrlBuildingIcon.cpp +++ b/libs/s25main/controls/ctrlBuildingIcon.cpp @@ -4,7 +4,6 @@ #include "ctrlBuildingIcon.h" #include "Loader.h" -#include "driver/MouseCoords.h" #include "files.h" #include "ogl/glArchivItem_Bitmap.h" #include "gameTypes/BuildingType.h" @@ -29,13 +28,3 @@ 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 2c4d07b85d..393d597c18 100644 --- a/libs/s25main/controls/ctrlBuildingIcon.h +++ b/libs/s25main/controls/ctrlBuildingIcon.h @@ -5,7 +5,6 @@ #pragma once #include "ctrlButton.h" -#include class Window; class ctrlBuildingIcon : public ctrlButton @@ -16,18 +15,11 @@ 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/ingameWindows/iwAction.cpp b/libs/s25main/ingameWindows/iwAction.cpp index 4ce793085f..b1a2070d7d 100644 --- a/libs/s25main/ingameWindows/iwAction.cpp +++ b/libs/s25main/ingameWindows/iwAction.cpp @@ -160,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) @@ -177,7 +178,9 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP tooltip << _(BUILDING_NAMES[bld]); // Radius anzeigen falls vorhanden - const unsigned radius = GetBuildingRadius(bld); + unsigned radius = 0; + if(showBuildingRadius) + radius = GetBuildingRadius(bld); if(radius > 0) tooltip << boost::format(_("\nRange: %1% tiles")) % radius; @@ -192,19 +195,8 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP } DrawPoint iconPos((k % 5) * 36, (k / 5) * 36 + 45); - ctrlBuildingIcon* icon = build_tab->GetGroup(static_cast(bt)) - ->AddBuildingIcon(k, iconPos, bld, player.nation, 36, tooltip.str()); - - // Set hover callback to show radius preview on the game world - if(radius > 0) - { - icon->SetOnHoverChanged([this, radius](bool hovered) noexcept { - if(hovered) - this->gwv.SetRadiusPreview(std::make_pair(this->selectedPt, radius)); - else - this->gwv.SetRadiusPreview(std::nullopt); - }); - } + build_tab->GetGroup(static_cast(bt)) + ->AddBuildingIcon(k, iconPos, bld, player.nation, 36, tooltip.str()); ++k; } @@ -544,6 +536,30 @@ void iwAction::Msg_Group_TabChange(const unsigned /*group_id*/, const unsigned c void iwAction::Msg_PaintAfter() { IngameWindow::Msg_PaintAfter(); + + // Resolve building icon hover preview after all mouse events are processed + auto* mainTab = GetCtrl(0); + auto* buildTabCtrl = + (mainTab && mainTab->GetCurrentTab() == TAB_BUILD) ? mainTab->GetGroup(TAB_BUILD)->GetCtrl(1) : nullptr; + auto* bldGroup = buildTabCtrl ? buildTabCtrl->GetGroup(buildTabCtrl->GetCurrentTab()) : nullptr; + + bool hasHoveredIcon = false; + if(bldGroup && gwv.GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS)) + { + for(auto* icon : bldGroup->GetCtrls()) + { + const unsigned radius = GetBuildingRadius(icon->GetType()); + if(icon->IsMouseOver() && radius > 0) + { + gwv.SetRadiusPreview(std::make_pair(selectedPt, radius)); + hasHoveredIcon = true; + break; + } + } + } + if(!hasHoveredIcon) + gwv.SetRadiusPreview(std::nullopt); + auto* tab = GetCtrl(0); if(tab) { diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index cdbdafa557..cd617e7fee 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -222,10 +222,12 @@ void GameWorldView::Draw(const RoadBuildState& rb, const MapPoint selected, bool if(show_names || show_productivity) DrawNameProductivityOverlay(terrainRenderer); - // Draw radius preview outline — either from build-menu hover or from the building under the cursor + // Draw radius preview outline if(radiusPreview_) DrawRadiusOutline(radiusPreview_->first, radiusPreview_->second); - else + + // Auto-detect radius for the building under the cursor + if(!radiusPreview_ && GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS)) { std::optional bldType; const Visibility vis = gwv.GetVisibility(selPt); From 9ab4c7bc8ffc9d94fdaedc6d29c0f4be11c77ec8 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Thu, 18 Jun 2026 11:11:53 +0200 Subject: [PATCH 07/23] Better radius ouline drawing --- libs/s25main/world/GameWorldView.cpp | 59 ++++++++++++++++++---------- libs/s25main/world/GameWorldView.h | 3 ++ 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index cd617e7fee..bd61c40f23 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -744,38 +744,57 @@ void GameWorldView::RemoveDrawNodeCallback(IDrawNodeCallback* callbackToRemove) drawNodeCallbacks.erase(itPos); } +// ----------------------------------------------------------------------------- +// Snap a point to the nearest toroidal copy relative to a reference position. +// Same formula as s25edit's correctMouseBlit(): +// k = round((reference - vertex) / mapSize) +// vertex += k * mapSize +// +// Reference: s25edit/external/s25edit/CMap.cpp :: correctMouseBlit() (line 1088) +// ----------------------------------------------------------------------------- +DrawPoint GameWorldView::SnapToNearestCopy(DrawPoint pt, const DrawPoint& ref, const DrawPoint& mapPxSize) +{ + if(mapPxSize.x > 0) + { + int kx = static_cast(std::floor((ref.x - pt.x) / static_cast(mapPxSize.x) + 0.5)); + pt.x += kx * mapPxSize.x; + } + if(mapPxSize.y > 0) + { + int ky = static_cast(std::floor((ref.y - pt.y) / static_cast(mapPxSize.y) + 0.5)); + pt.y += ky * mapPxSize.y; + } + return pt; +} + +// ----------------------------------------------------------------------------- +// Draw radius overlay using CorrectMouseBlit for toroidal wrapping. +// Reference: s25edit/external/s25edit/CMap.cpp :: render() (lines 1159, 1230) +// - Computes brush blit positions via correctMouseBlit() +// - Draws overlay sprites at those positions +// ----------------------------------------------------------------------------- void GameWorldView::DrawRadiusOutline(const MapPoint& center, unsigned radius) { const auto& world = GetWorld(); - // Get all border points at the exact radius auto pts = world.GetPointsInRadius(center, radius, ReturnMapPointWithRadius{}); const MapExtent mapSize = world.GetSize(); - constexpr unsigned BORDER_COLOR = 0xFFFF0000; // Red with full alpha + constexpr unsigned BORDER_COLOR = 0xFFFF0000; + const DrawPoint mapPxSize(mapSize.x * TR_W, mapSize.y * TR_H); - const int w = mapSize.x; - const int h = mapSize.y; + // Reference: screen position of the center vertex (with seam offset). + const auto centerAlt = world.GetNode(center).altitude; + const DrawPoint ref = GetNodePos(center) - DrawPoint(0, HEIGHT_FACTOR * centerAlt) + selPtOffset; - for(const auto& ptWithRadius : pts) + for(const auto& [basePt, dist] : pts) { - if(ptWithRadius.second != radius) + if(dist != radius) continue; - const MapPoint& basePt = ptWithRadius.first; - const auto alt = world.GetNode(basePt).altitude; + DrawPoint scr = GetNodePos(basePt, world.GetNode(basePt).altitude); + scr = SnapToNearestCopy(scr, ref, mapPxSize) - offset; - // Draw at the canonical position and its 8 toroidal copies (shifted by ± map dimensions). - // We use Position arithmetic (no modulo) so each shifted copy produces a different screen - // position. Copies outside the viewport are clipped by the renderer. - for(int dw : {-w, 0, w}) - { - for(int dh : {-h, 0, h}) - { - const DrawPoint scr = - GetNodePos(Position(basePt.x + dw, basePt.y + dh)) - DrawPoint(0, HEIGHT_FACTOR * alt) - offset; - Window::DrawRectangle(Rect(scr - DrawPoint(2, 2), Extent(5, 5)), BORDER_COLOR); - } - } + Window::DrawRectangle(Rect(scr - DrawPoint(2, 2), Extent(5, 5)), BORDER_COLOR); } } diff --git a/libs/s25main/world/GameWorldView.h b/libs/s25main/world/GameWorldView.h index d2f8cde22f..63a28b862d 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -156,6 +156,9 @@ class GameWorldView /// 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, const DrawPoint& ref, const DrawPoint& mapPxSize); + void SaveIngameSettingsValues() const; void updateEffectiveZoomFactor(); }; From 66bff68264f1be8a8d7878dd57b0de820cb547ed Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Sat, 20 Jun 2026 15:41:48 +0200 Subject: [PATCH 08/23] Ignore game buildings when mouse over window --- libs/s25main/world/GameWorldView.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index bd61c40f23..655258bd72 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -14,6 +14,7 @@ #include "ReturnMapPointWithRadius.h" #include "Settings.h" #include "Window.h" +#include "WindowManager.h" #include "addons/AddonMaxWaterwayLength.h" #include "buildings/noBuildingSite.h" #include "buildings/nobMilitary.h" @@ -226,8 +227,10 @@ void GameWorldView::Draw(const RoadBuildState& rb, const MapPoint selected, bool if(radiusPreview_) DrawRadiusOutline(radiusPreview_->first, radiusPreview_->second); - // Auto-detect radius for the building under the cursor - if(!radiusPreview_ && GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS)) + // Auto-detect radius for the building under the cursor. + // Do not trigger hover when the mouse is over an ingame window. + if(!radiusPreview_ && GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS) + && !WINDOWMANAGER.FindWindowAtPos(VIDEODRIVER.GetMousePos())) { std::optional bldType; const Visibility vis = gwv.GetVisibility(selPt); From 6cfbdd2147bcc002ba4ce4d9d519eff977210b93 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Tue, 30 Jun 2026 16:10:23 +0200 Subject: [PATCH 09/23] Move OnHover() callsite to Msg_MouseMove --- libs/s25main/controls/ctrlBuildingIcon.cpp | 11 +++++ libs/s25main/controls/ctrlBuildingIcon.h | 10 +++++ libs/s25main/ingameWindows/iwAction.cpp | 48 ++++++++++------------ libs/s25main/ingameWindows/iwAction.h | 4 ++ 4 files changed, 47 insertions(+), 26 deletions(-) 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/ingameWindows/iwAction.cpp b/libs/s25main/ingameWindows/iwAction.cpp index b1a2070d7d..13426cb2c5 100644 --- a/libs/s25main/ingameWindows/iwAction.cpp +++ b/libs/s25main/ingameWindows/iwAction.cpp @@ -47,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 @@ -195,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; } @@ -416,6 +434,7 @@ void iwAction::Close() { if(ShouldBeClosed()) return; + activeHoveredIcon_ = nullptr; gwv.SetRadiusPreview(std::nullopt); IngameWindow::Close(); if(mousePosAtOpen_.isValid()) @@ -537,29 +556,6 @@ void iwAction::Msg_PaintAfter() { IngameWindow::Msg_PaintAfter(); - // Resolve building icon hover preview after all mouse events are processed - auto* mainTab = GetCtrl(0); - auto* buildTabCtrl = - (mainTab && mainTab->GetCurrentTab() == TAB_BUILD) ? mainTab->GetGroup(TAB_BUILD)->GetCtrl(1) : nullptr; - auto* bldGroup = buildTabCtrl ? buildTabCtrl->GetGroup(buildTabCtrl->GetCurrentTab()) : nullptr; - - bool hasHoveredIcon = false; - if(bldGroup && gwv.GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS)) - { - for(auto* icon : bldGroup->GetCtrls()) - { - const unsigned radius = GetBuildingRadius(icon->GetType()); - if(icon->IsMouseOver() && radius > 0) - { - gwv.SetRadiusPreview(std::make_pair(selectedPt, radius)); - hasHoveredIcon = true; - break; - } - } - } - if(!hasHoveredIcon) - gwv.SetRadiusPreview(std::nullopt); - 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); From 6fda124be7bfbeb0b17a86afbd8d74412f176a5e Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Tue, 30 Jun 2026 16:46:59 +0200 Subject: [PATCH 10/23] UpdateRadiusPreviewForMousePos --- libs/s25main/desktops/dskGameInterface.cpp | 3 + libs/s25main/world/GameWorldView.cpp | 68 +++++++++++++--------- libs/s25main/world/GameWorldView.h | 6 ++ 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/libs/s25main/desktops/dskGameInterface.cpp b/libs/s25main/desktops/dskGameInterface.cpp index 49d19bcdad..b6c3cf1724 100644 --- a/libs/s25main/desktops/dskGameInterface.cpp +++ b/libs/s25main/desktops/dskGameInterface.cpp @@ -751,6 +751,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/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index 655258bd72..0d9c4fbaa1 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -43,7 +43,8 @@ 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}); @@ -223,37 +224,10 @@ void GameWorldView::Draw(const RoadBuildState& rb, const MapPoint selected, bool if(show_names || show_productivity) DrawNameProductivityOverlay(terrainRenderer); - // Draw radius preview outline + // Draw radius preview outline (set by icon-hover or map-hover) if(radiusPreview_) DrawRadiusOutline(radiusPreview_->first, radiusPreview_->second); - // Auto-detect radius for the building under the cursor. - // Do not trigger hover when the mouse is over an ingame window. - if(!radiusPreview_ && GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS) - && !WINDOWMANAGER.FindWindowAtPos(VIDEODRIVER.GetMousePos())) - { - std::optional bldType; - const Visibility vis = gwv.GetVisibility(selPt); - if(vis == Visibility::Visible) - { - const auto* bld = GetWorld().GetSpecObj(selPt); - if(bld) - bldType = bld->GetBuildingType(); - } else if(vis == Visibility::FogOfWar) - { - const FOWObject* fow = gwv.GetYoungestFOWObject(selPt); - if(fow && fow->GetType() == FoW_Type::Building) - bldType = static_cast(*fow).GetBuildingType(); - } - - if(bldType) - { - const unsigned bldRadius = GetBuildingRadius(*bldType); - if(bldRadius > 0) - DrawRadiusOutline(selPt, bldRadius); - } - } - DrawGUI(rb, terrainRenderer, selected, drawMouse); // Draw catapult stones @@ -801,6 +775,42 @@ void GameWorldView::DrawRadiusOutline(const MapPoint& center, unsigned radius) } } +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 63a28b862d..5a789559c2 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -49,6 +49,8 @@ class GameWorldView /// 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; @@ -124,6 +126,10 @@ class GameWorldView /// 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; } From 90a2f6501cbbe308d2492bfa49f50e3b184303c2 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Tue, 30 Jun 2026 17:03:30 +0200 Subject: [PATCH 11/23] SnapToNearestCopy --- libs/s25main/world/GameWorldView.cpp | 33 ++++++++-------------------- libs/s25main/world/GameWorldView.h | 2 +- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index 0d9c4fbaa1..035007d9b3 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -721,34 +721,19 @@ void GameWorldView::RemoveDrawNodeCallback(IDrawNodeCallback* callbackToRemove) drawNodeCallbacks.erase(itPos); } -// ----------------------------------------------------------------------------- -// Snap a point to the nearest toroidal copy relative to a reference position. -// Same formula as s25edit's correctMouseBlit(): -// k = round((reference - vertex) / mapSize) -// vertex += k * mapSize -// -// Reference: s25edit/external/s25edit/CMap.cpp :: correctMouseBlit() (line 1088) -// ----------------------------------------------------------------------------- -DrawPoint GameWorldView::SnapToNearestCopy(DrawPoint pt, const DrawPoint& ref, const DrawPoint& mapPxSize) -{ - if(mapPxSize.x > 0) - { - int kx = static_cast(std::floor((ref.x - pt.x) / static_cast(mapPxSize.x) + 0.5)); - pt.x += kx * mapPxSize.x; - } - if(mapPxSize.y > 0) - { - int ky = static_cast(std::floor((ref.y - pt.y) / static_cast(mapPxSize.y) + 0.5)); - pt.y += ky * mapPxSize.y; - } +// 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 using CorrectMouseBlit for toroidal wrapping. -// Reference: s25edit/external/s25edit/CMap.cpp :: render() (lines 1159, 1230) -// - Computes brush blit positions via correctMouseBlit() -// - Draws overlay sprites at those positions +// Draw radius overlay for a building's working range, handling map wrapping. // ----------------------------------------------------------------------------- void GameWorldView::DrawRadiusOutline(const MapPoint& center, unsigned radius) { diff --git a/libs/s25main/world/GameWorldView.h b/libs/s25main/world/GameWorldView.h index 5a789559c2..4238f3809b 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -163,7 +163,7 @@ class GameWorldView 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, const DrawPoint& ref, const DrawPoint& mapPxSize); + static DrawPoint SnapToNearestCopy(DrawPoint pt, DrawPoint ref, DrawPoint mapPxSize); void SaveIngameSettingsValues() const; void updateEffectiveZoomFactor(); From 0b7d105942f9ccea8c9bdc3ef34ec3dccf4b4cf7 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Tue, 30 Jun 2026 17:21:49 +0200 Subject: [PATCH 12/23] DrawRadiusOutline --- libs/s25main/world/GameWorldView.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index 035007d9b3..09a3e689eb 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -744,19 +744,19 @@ void GameWorldView::DrawRadiusOutline(const MapPoint& center, unsigned radius) constexpr unsigned BORDER_COLOR = 0xFFFF0000; const DrawPoint mapPxSize(mapSize.x * TR_W, mapSize.y * TR_H); - // Reference: screen position of the center vertex (with seam offset). - const auto centerAlt = world.GetNode(center).altitude; - const DrawPoint ref = GetNodePos(center) - DrawPoint(0, HEIGHT_FACTOR * centerAlt) + selPtOffset; + // 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& [basePt, dist] : pts) + for(const auto& [mapPt, dist] : pts) { if(dist != radius) continue; - DrawPoint scr = GetNodePos(basePt, world.GetNode(basePt).altitude); - scr = SnapToNearestCopy(scr, ref, mapPxSize) - offset; + DrawPoint screenPt = GetNodePos(mapPt, world.GetNode(mapPt).altitude); + screenPt = SnapToNearestCopy(screenPt, centerScr, mapPxSize) - offset; - Window::DrawRectangle(Rect(scr - DrawPoint(2, 2), Extent(5, 5)), BORDER_COLOR); + Window::DrawRectangle(Rect(screenPt - DrawPoint(2, 2), Extent(5, 5)), BORDER_COLOR); } } From 50d2045f39cd6ce9c769a518869a1f033086e900 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Tue, 30 Jun 2026 17:24:38 +0200 Subject: [PATCH 13/23] GetBuildingRadius comment wording --- libs/s25main/gameData/BuildingConsts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/s25main/gameData/BuildingConsts.cpp b/libs/s25main/gameData/BuildingConsts.cpp index a4b7ac3dca..6a6708823d 100644 --- a/libs/s25main/gameData/BuildingConsts.cpp +++ b/libs/s25main/gameData/BuildingConsts.cpp @@ -456,7 +456,7 @@ unsigned GetBuildingRadius(BuildingType bld) return nofFarmhand::GetWorkRadius(*job); return 0; } - // All other building types have no relevant radius overlay + // Remaining building types have no relevant radius default: return 0; } } From 31d0ab81ccf367105439c8204c94e68941f849b6 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sun, 4 Oct 2026 13:43:32 +0200 Subject: [PATCH 14/23] Fixup comments and const-variable Co-authored-by: Alexander Grund --- libs/s25main/desktops/dskGameInterface.cpp | 2 +- libs/s25main/ingameWindows/iwAction.cpp | 8 ++------ libs/s25main/world/GameWorldView.cpp | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/libs/s25main/desktops/dskGameInterface.cpp b/libs/s25main/desktops/dskGameInterface.cpp index b6c3cf1724..3075b3e78b 100644 --- a/libs/s25main/desktops/dskGameInterface.cpp +++ b/libs/s25main/desktops/dskGameInterface.cpp @@ -751,7 +751,7 @@ 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) + // Update radius preview for buildings under the cursor gwv.UpdateRadiusPreviewForMousePos(mc.pos); if(!isScrolling) diff --git a/libs/s25main/ingameWindows/iwAction.cpp b/libs/s25main/ingameWindows/iwAction.cpp index 13426cb2c5..86bdf59e7a 100644 --- a/libs/s25main/ingameWindows/iwAction.cpp +++ b/libs/s25main/ingameWindows/iwAction.cpp @@ -177,10 +177,8 @@ 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); + // Show radius if any + const unsigned radius = showBuildingRadius ? GetBuildingRadius(bld) : 0; if(radius > 0) tooltip << boost::format(_("\nRange: %1% tiles")) % radius; @@ -198,8 +196,6 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP 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 { diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index 09a3e689eb..c8bae103f3 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -224,7 +224,7 @@ 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) + // Draw radius preview outline if enabled if(radiusPreview_) DrawRadiusOutline(radiusPreview_->first, radiusPreview_->second); From 74b329fd55ad4a0b10df9ce921fe228d10ec5b79 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sun, 4 Oct 2026 14:46:35 +0200 Subject: [PATCH 15/23] Fix init order and unhandled enum case --- libs/s25main/world/GameWorldView.cpp | 9 +++++---- libs/s25main/world/GameWorldView.h | 14 +++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index c8bae103f3..c23b094b2c 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -41,10 +41,10 @@ #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), - isBuildingRadiusEnabled_(GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS)) + : gwv(gwv), origin_(pos), size_(size), selPt(0, 0), show_bq(SETTINGS.ingame.showBQ), + show_names(SETTINGS.ingame.showNames), show_productivity(SETTINGS.ingame.showProductivity), + isBuildingRadiusEnabled_(GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS)), offset(0, 0), lastOffset(0, 0), + zoomFactor_(1.f), targetZoomFactor_(1.f), zoomSpeed_(0.f) { updateEffectiveZoomFactor(); MoveBy({0, 0}); @@ -783,6 +783,7 @@ void GameWorldView::UpdateRadiusPreviewForMousePos(const Position& mousePos) bldType = static_cast(*fow).GetBuildingType(); } break; + case Visibility::Invisible: break; } if(bldType) diff --git a/libs/s25main/world/GameWorldView.h b/libs/s25main/world/GameWorldView.h index 4238f3809b..29748e5d2a 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -32,6 +32,13 @@ struct ObjectBetweenLines; class GameWorldView { + const GameWorldViewer& gwv; + + /// Top-Left position of the view (window) + Position origin_; + /// Size of the view + Extent size_; + /// Currently selected point (where the mouse points to) MapPoint selPt; /// Offset to selected point @@ -61,13 +68,6 @@ class GameWorldView /// Last drawn map point DrawPoint lastPt; - const GameWorldViewer& gwv; - - /// Top-Left position of the view (window) - Position origin_; - /// Size of the view - Extent size_; - /// How much the view is scaled (1=normal, >1=bigger, >0 && <1=smaller) float zoomFactor_; float effectiveZoomFactor_; ///< DPI scale corrected zoom factor From 093d2c8bfc0b0b732f0b668f135baac10977ce56 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sun, 4 Oct 2026 16:19:14 +0200 Subject: [PATCH 16/23] Make GetWorkRadius non-static It uses `world` which only exists during a game. Make that explicit by taking the settings as a parameter --- libs/s25main/figures/nofFarmhand.cpp | 13 +++++++++---- libs/s25main/figures/nofFarmhand.h | 6 ++++-- libs/s25main/figures/nofForester.cpp | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/libs/s25main/figures/nofFarmhand.cpp b/libs/s25main/figures/nofFarmhand.cpp index 39cad7c87c..8755fa4e25 100644 --- a/libs/s25main/figures/nofFarmhand.cpp +++ b/libs/s25main/figures/nofFarmhand.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -33,6 +33,11 @@ nofFarmhand::nofFarmhand(SerializedGameData& sgd, const unsigned obj_id) {} unsigned nofFarmhand::GetWorkRadius(const Job job) +{ + return GetWorkRadius(world->GetGGS(), job); +} + +unsigned nofFarmhand::GetWorkRadius(const GlobalGameSettings& ggs, const Job job) { switch(job) { @@ -43,18 +48,18 @@ unsigned nofFarmhand::GetWorkRadius(const Job job) case Job::CharBurner: return 3; case Job::Woodcutter: { - const unsigned sel = world->GetGGS().getSelection(AddonId::WOODCUTTER_REACH_RADIUS); + const unsigned sel = ggs.getSelection(AddonId::WOODCUTTER_REACH_RADIUS); return woodcutterRadiusValues[sel]; } case Job::Forester: { - const unsigned sel = world->GetGGS().getSelection(AddonId::FORESTER_REACH_RADIUS); + const unsigned sel = ggs.getSelection(AddonId::FORESTER_REACH_RADIUS); return foresterRadiusValues[sel]; } case Job::Fisher: return 7; case Job::Stonemason: { - const unsigned sel = world->GetGGS().getSelection(AddonId::STONEMASON_REACH_RADIUS); + const unsigned sel = ggs.getSelection(AddonId::STONEMASON_REACH_RADIUS); return stonemasonRadiusValues[sel]; } default: throw std::logic_error("Invalid job"); diff --git a/libs/s25main/figures/nofFarmhand.h b/libs/s25main/figures/nofFarmhand.h index 910fffe8fb..a9e35590f0 100644 --- a/libs/s25main/figures/nofFarmhand.h +++ b/libs/s25main/figures/nofFarmhand.h @@ -1,10 +1,11 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "nofBuildingWorker.h" +class GlobalGameSettings; class SerializedGameData; class nobUsual; @@ -54,7 +55,8 @@ class nofFarmhand : public nofBuildingWorker nofFarmhand(Job job, MapPoint pos, unsigned char player, nobUsual* workplace); nofFarmhand(SerializedGameData& sgd, unsigned obj_id); - static unsigned GetWorkRadius(Job job); + static unsigned GetWorkRadius(const GlobalGameSettings& ggs, Job job); + unsigned GetWorkRadius(Job job); void Serialize(SerializedGameData& sgd) const override; diff --git a/libs/s25main/figures/nofForester.cpp b/libs/s25main/figures/nofForester.cpp index 91b617e21e..62ea5d4897 100644 --- a/libs/s25main/figures/nofForester.cpp +++ b/libs/s25main/figures/nofForester.cpp @@ -26,7 +26,7 @@ bool IsPotentialNewFieldForOwnFarm(GameWorld& world, const MapPoint pt, const un return false; return world.CheckPointsInRadius( - pt, nofFarmhand::GetWorkRadius(Job::Farmer), + pt, nofFarmhand::GetWorkRadius(world.GetGGS(), Job::Farmer), [&world, player](const MapPoint farmPt, unsigned) { if(world.GetNO(farmPt)->GetType() != NodalObjectType::Building) return false; From a6c7acdacac949ff32a0f59479f63e1d90f60029 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sun, 4 Oct 2026 16:21:56 +0200 Subject: [PATCH 17/23] Move some job-related constants from `GameConsts.h` to `JobConsts.h` --- libs/s25main/Pathfinding.cpp | 2 +- libs/s25main/TradePathCache.cpp | 2 +- libs/s25main/Ware.cpp | 1 - libs/s25main/ai/aijh/AIPlayerJH.cpp | 2 +- libs/s25main/buildings/nobBaseMilitary.cpp | 1 - libs/s25main/buildings/nobHarborBuilding.cpp | 3 ++- libs/s25main/buildings/nobMilitary.cpp | 1 - libs/s25main/buildings/nobUsual.cpp | 2 +- libs/s25main/figures/noFigure.cpp | 1 - libs/s25main/figures/nofCatapultMan.h | 5 +---- libs/s25main/figures/nofGeologist.cpp | 1 - libs/s25main/figures/nofHunter.cpp | 1 - libs/s25main/figures/nofHunter.h | 5 +---- libs/s25main/figures/nofMiner.cpp | 2 +- libs/s25main/figures/nofScout_Free.cpp | 1 - libs/s25main/figures/nofShipWright.cpp | 1 - libs/s25main/figures/nofTradeDonkey.cpp | 1 - libs/s25main/figures/nofTradeLeader.cpp | 1 - libs/s25main/figures/nofWorkman.cpp | 1 - libs/s25main/gameData/BuildingConsts.cpp | 1 - libs/s25main/gameData/GameConsts.h | 3 --- libs/s25main/gameData/JobConsts.h | 11 +++++++++-- libs/s25main/gameTypes/MineResourceBehavior.cpp | 2 +- libs/s25main/nodeObjs/noAnimal.cpp | 1 - libs/s25main/pathfinding/RoadPathFinder.cpp | 3 ++- libs/s25main/world/GameWorldBase.cpp | 2 +- libs/s25main/world/TradeRoute.cpp | 2 +- tests/s25Main/integration/testGamePlayer.cpp | 4 ++-- tests/s25Main/integration/testPathfinding.cpp | 1 - 29 files changed, 25 insertions(+), 39 deletions(-) diff --git a/libs/s25main/Pathfinding.cpp b/libs/s25main/Pathfinding.cpp index 76cc75327c..21677e7db3 100644 --- a/libs/s25main/Pathfinding.cpp +++ b/libs/s25main/Pathfinding.cpp @@ -14,7 +14,7 @@ #include "pathfinding/ShipPathData.h" #include "world/GameWorld.h" #include "gameTypes/ShipDirection.h" -#include "gameData/GameConsts.h" +#include /// Findet einen Weg für Figuren helpers::OptionalEnum GameWorldBase::FindHumanPath(const MapPoint start, const MapPoint dest, diff --git a/libs/s25main/TradePathCache.cpp b/libs/s25main/TradePathCache.cpp index d8cb75f509..9f72142f31 100644 --- a/libs/s25main/TradePathCache.cpp +++ b/libs/s25main/TradePathCache.cpp @@ -6,7 +6,7 @@ #include "EventManager.h" #include "GamePlayer.h" #include "world/GameWorld.h" -#include "gameData/GameConsts.h" +#include bool TradePathCache::pathExists(const MapPoint start, const MapPoint goal, const PlayerIdx player) { diff --git a/libs/s25main/Ware.cpp b/libs/s25main/Ware.cpp index 99886d1063..78bcbf7366 100644 --- a/libs/s25main/Ware.cpp +++ b/libs/s25main/Ware.cpp @@ -15,7 +15,6 @@ #include "nodeObjs/noFlag.h" #include "nodeObjs/noRoadNode.h" #include "gameData/BuildingProperties.h" -#include "gameData/GameConsts.h" #include "gameData/GoodConsts.h" #include "gameData/ShieldConsts.h" #include "s25util/Log.h" diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index 6a665bf2a2..b9acdfbc9d 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -17,6 +17,7 @@ #include "buildings/nobHarborBuilding.h" #include "buildings/nobMilitary.h" #include "buildings/nobUsual.h" +#include "helpers/EnumArray.h" #include "helpers/IdRange.h" #include "helpers/MaxEnumValue.h" #include "helpers/containerUtils.h" @@ -35,7 +36,6 @@ #include "gameTypes/MineResourceBehavior.h" #include "gameData/BuildingConsts.h" #include "gameData/BuildingProperties.h" -#include "gameData/GameConsts.h" #include "gameData/JobConsts.h" #include "gameData/TerrainDesc.h" #include "gameData/ToolConsts.h" diff --git a/libs/s25main/buildings/nobBaseMilitary.cpp b/libs/s25main/buildings/nobBaseMilitary.cpp index 815dc4a3e5..85ddec8343 100644 --- a/libs/s25main/buildings/nobBaseMilitary.cpp +++ b/libs/s25main/buildings/nobBaseMilitary.cpp @@ -17,7 +17,6 @@ #include "random/Random.h" #include "world/GameWorld.h" #include "gameData/BuildingProperties.h" -#include "gameData/GameConsts.h" #include #include diff --git a/libs/s25main/buildings/nobHarborBuilding.cpp b/libs/s25main/buildings/nobHarborBuilding.cpp index 1a8d528773..a100631ac4 100644 --- a/libs/s25main/buildings/nobHarborBuilding.cpp +++ b/libs/s25main/buildings/nobHarborBuilding.cpp @@ -12,6 +12,7 @@ #include "figures/noFigure.h" #include "figures/nofAttacker.h" #include "figures/nofDefender.h" +#include "helpers/EnumArray.h" #include "helpers/containerUtils.h" #include "helpers/pointerContainerUtils.h" #include "network/GameClient.h" @@ -25,9 +26,9 @@ #include "world/GameWorld.h" #include "nodeObjs/noShip.h" #include "gameData/BuildingConsts.h" -#include "gameData/GameConsts.h" #include "gameData/MilitaryConsts.h" #include "gameData/ShieldConsts.h" +#include nobHarborBuilding::ExpeditionInfo::ExpeditionInfo(SerializedGameData& sgd) : boards(sgd.PopUnsignedInt()), stones(sgd.PopUnsignedInt()), active(sgd.PopBool()), builder(sgd.PopBool()) diff --git a/libs/s25main/buildings/nobMilitary.cpp b/libs/s25main/buildings/nobMilitary.cpp index 47c8a7b05e..e1f48441a3 100644 --- a/libs/s25main/buildings/nobMilitary.cpp +++ b/libs/s25main/buildings/nobMilitary.cpp @@ -32,7 +32,6 @@ #include "nodeObjs/noFlag.h" #include "gameData/BuildingConsts.h" #include "gameData/BuildingProperties.h" -#include "gameData/GameConsts.h" #include "gameData/MilitaryConsts.h" #include "gameData/SettingTypeConv.h" #include "s25util/Log.h" diff --git a/libs/s25main/buildings/nobUsual.cpp b/libs/s25main/buildings/nobUsual.cpp index 32db3a2883..f1f9140662 100644 --- a/libs/s25main/buildings/nobUsual.cpp +++ b/libs/s25main/buildings/nobUsual.cpp @@ -12,6 +12,7 @@ #include "addons/const_addons.h" #include "figures/nofBuildingWorker.h" #include "figures/nofPigbreeder.h" +#include "helpers/EnumArray.h" #include "helpers/containerUtils.h" #include "network/GameClient.h" #include "notifications/BuildingNote.h" @@ -23,7 +24,6 @@ #include "gameTypes/Resource.h" #include "gameData/BuildingConsts.h" #include "gameData/BuildingProperties.h" -#include "gameData/GameConsts.h" #include /// Number of GFs after which the productivity is recalculated, i.e. productivity is averaged over intervals of this diff --git a/libs/s25main/figures/noFigure.cpp b/libs/s25main/figures/noFigure.cpp index e39a3e16df..d0b000fee0 100644 --- a/libs/s25main/figures/noFigure.cpp +++ b/libs/s25main/figures/noFigure.cpp @@ -29,7 +29,6 @@ #include "nodeObjs/noRoadNode.h" #include "nodeObjs/noSkeleton.h" #include "gameTypes/DirectionToImgDir.h" -#include "gameData/GameConsts.h" #include "gameData/JobConsts.h" #include "s25util/Log.h" #include "s25util/colors.h" diff --git a/libs/s25main/figures/nofCatapultMan.h b/libs/s25main/figures/nofCatapultMan.h index 4514f5e03b..40a8776f4d 100644 --- a/libs/s25main/figures/nofCatapultMan.h +++ b/libs/s25main/figures/nofCatapultMan.h @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,9 +8,6 @@ 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/nofGeologist.cpp b/libs/s25main/figures/nofGeologist.cpp index e84a204d6b..9ee97eddd9 100644 --- a/libs/s25main/figures/nofGeologist.cpp +++ b/libs/s25main/figures/nofGeologist.cpp @@ -22,7 +22,6 @@ #include "world/GameWorld.h" #include "nodeObjs/noFlag.h" #include "nodeObjs/noSign.h" -#include "gameData/GameConsts.h" nofGeologist::nofGeologist(const MapPoint pos, const unsigned char player, noRoadNode* goal) : nofFlagWorker(Job::Geologist, pos, player, goal), signs(0), node_goal(0, 0) diff --git a/libs/s25main/figures/nofHunter.cpp b/libs/s25main/figures/nofHunter.cpp index 6a660611e2..bdc2fcf4d0 100644 --- a/libs/s25main/figures/nofHunter.cpp +++ b/libs/s25main/figures/nofHunter.cpp @@ -19,7 +19,6 @@ #include "random/Random.h" #include "world/GameWorld.h" #include "nodeObjs/noAnimal.h" -#include "gameData/GameConsts.h" #include "gameData/JobConsts.h" #include diff --git a/libs/s25main/figures/nofHunter.h b/libs/s25main/figures/nofHunter.h index 6e137eda6b..5f5321a344 100644 --- a/libs/s25main/figures/nofHunter.h +++ b/libs/s25main/figures/nofHunter.h @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -7,9 +7,6 @@ #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/figures/nofMiner.cpp b/libs/s25main/figures/nofMiner.cpp index 5f5da8e384..2429750f48 100644 --- a/libs/s25main/figures/nofMiner.cpp +++ b/libs/s25main/figures/nofMiner.cpp @@ -15,7 +15,7 @@ #include "gameTypes/MineNoOutputFallback.h" #include "gameTypes/MineResourceBehavior.h" #include "gameTypes/Resource.h" -#include "gameData/GameConsts.h" +#include "gameData/JobConsts.h" #include #include diff --git a/libs/s25main/figures/nofScout_Free.cpp b/libs/s25main/figures/nofScout_Free.cpp index fcbbd41428..a65fec802b 100644 --- a/libs/s25main/figures/nofScout_Free.cpp +++ b/libs/s25main/figures/nofScout_Free.cpp @@ -9,7 +9,6 @@ #include "random/Random.h" #include "world/GameWorld.h" #include "nodeObjs/noFlag.h" -#include "gameData/GameConsts.h" #include "gameData/MilitaryConsts.h" #include class noRoadNode; diff --git a/libs/s25main/figures/nofShipWright.cpp b/libs/s25main/figures/nofShipWright.cpp index 17fca8fe2a..abe4dd55b7 100644 --- a/libs/s25main/figures/nofShipWright.cpp +++ b/libs/s25main/figures/nofShipWright.cpp @@ -15,7 +15,6 @@ #include "world/GameWorld.h" #include "nodeObjs/noShipBuildingSite.h" #include "gameTypes/Direction.h" -#include "gameData/GameConsts.h" #include "gameData/JobConsts.h" #include "s25util/colors.h" diff --git a/libs/s25main/figures/nofTradeDonkey.cpp b/libs/s25main/figures/nofTradeDonkey.cpp index d9332530ab..7dc95910c4 100644 --- a/libs/s25main/figures/nofTradeDonkey.cpp +++ b/libs/s25main/figures/nofTradeDonkey.cpp @@ -11,7 +11,6 @@ #include "ogl/glArchivItem_Bitmap.h" #include "world/GameWorld.h" #include "gameData/BuildingProperties.h" -#include "gameData/GameConsts.h" #include "gameData/JobConsts.h" nofTradeDonkey::nofTradeDonkey(const MapPoint pos, const unsigned char player, diff --git a/libs/s25main/figures/nofTradeLeader.cpp b/libs/s25main/figures/nofTradeLeader.cpp index 737bd03887..18bd6c96c9 100644 --- a/libs/s25main/figures/nofTradeLeader.cpp +++ b/libs/s25main/figures/nofTradeLeader.cpp @@ -14,7 +14,6 @@ #include "postSystem/PostMsgWithBuilding.h" #include "world/GameWorld.h" #include "gameData/BuildingProperties.h" -#include "gameData/GameConsts.h" #include "gameData/GoodConsts.h" #include "gameData/JobConsts.h" #include "gameData/NationConsts.h" diff --git a/libs/s25main/figures/nofWorkman.cpp b/libs/s25main/figures/nofWorkman.cpp index a0a0e30d9e..1def798a94 100644 --- a/libs/s25main/figures/nofWorkman.cpp +++ b/libs/s25main/figures/nofWorkman.cpp @@ -7,7 +7,6 @@ #include "SoundManager.h" #include "buildings/nobUsual.h" #include "world/GameWorld.h" -#include "gameData/GameConsts.h" #include "gameData/JobConsts.h" nofWorkman::nofWorkman(const Job job, const MapPoint pos, const unsigned char player, nobUsual* workplace) diff --git a/libs/s25main/gameData/BuildingConsts.cpp b/libs/s25main/gameData/BuildingConsts.cpp index 6a6708823d..ef364240b5 100644 --- a/libs/s25main/gameData/BuildingConsts.cpp +++ b/libs/s25main/gameData/BuildingConsts.cpp @@ -8,7 +8,6 @@ #include "figures/nofHunter.h" #include "mygettext/mygettext.h" #include "gameTypes/BuildingTypes.h" -#include "gameData/GameConsts.h" #include "gameData/MilitaryConsts.h" #include diff --git a/libs/s25main/gameData/GameConsts.h b/libs/s25main/gameData/GameConsts.h index cbc24b7920..cc45ae7a4f 100644 --- a/libs/s25main/gameData/GameConsts.h +++ b/libs/s25main/gameData/GameConsts.h @@ -40,9 +40,6 @@ constexpr auto gfs_to_duration(const unsigned gfs) return gfs * REFERENCE_SPEED; } -/// Reichweite der Bergarbeiter -constexpr unsigned MINER_RADIUS = 2; - /// Konstante für die Pfadrichtung bei einer Schiffsverbindung constexpr unsigned char SHIP_DIR = 100; constexpr unsigned char INVALID_DIR = 0xFF; diff --git a/libs/s25main/gameData/JobConsts.h b/libs/s25main/gameData/JobConsts.h index 61761d2eaf..4e7a438a53 100644 --- a/libs/s25main/gameData/JobConsts.h +++ b/libs/s25main/gameData/JobConsts.h @@ -45,8 +45,15 @@ struct JobSpriteData extern const helpers::EnumArray JOB_CONSTS; extern const helpers::EnumArray JOB_SPRITE_CONSTS; -/// Katapultmann-Wartezeit -const unsigned CATAPULT_WAIT1_LENGTH = 1300; // eigenlich 310 - aber hochgestellt wegen zu schneller Warenverteilung +/// Distance in which miners use resources, also other "digging" jobs like wells +constexpr unsigned MINER_RADIUS = 2; +/// Maximum distance to a target the catapult can attack +constexpr unsigned CATAPULT_MAX_TARGET_RANGE = 13; +/// Half-side length of the square the hunter scans for animals (centered on the building) +constexpr int HUNTER_SEARCH_HALFSIDE = 19; + +/// Time in GFs the catapult needs to wait before shooting (after aiming) +const unsigned CATAPULT_WAIT1_LENGTH = 1300; // actually 310 - but increased due to too fast goods distribution /// Position of the ware on a donkey per direction and animation step const helpers::EnumArray, Direction> WARE_POS_DONKEY = {{ diff --git a/libs/s25main/gameTypes/MineResourceBehavior.cpp b/libs/s25main/gameTypes/MineResourceBehavior.cpp index 91ad66f940..f2ea65b182 100644 --- a/libs/s25main/gameTypes/MineResourceBehavior.cpp +++ b/libs/s25main/gameTypes/MineResourceBehavior.cpp @@ -10,7 +10,7 @@ #include "gameTypes/GoodTypes.h" #include "gameTypes/MapCoordinates.h" #include "gameTypes/Resource.h" -#include "gameData/GameConsts.h" +#include "gameData/JobConsts.h" #include namespace { diff --git a/libs/s25main/nodeObjs/noAnimal.cpp b/libs/s25main/nodeObjs/noAnimal.cpp index 43139a83e2..f54c6b6a2b 100644 --- a/libs/s25main/nodeObjs/noAnimal.cpp +++ b/libs/s25main/nodeObjs/noAnimal.cpp @@ -17,7 +17,6 @@ #include "ogl/glSmartBitmap.h" #include "random/Random.h" #include "world/GameWorld.h" -#include "gameData/GameConsts.h" #include "gameData/TerrainDesc.h" #include "s25util/colors.h" diff --git a/libs/s25main/pathfinding/RoadPathFinder.cpp b/libs/s25main/pathfinding/RoadPathFinder.cpp index 681b0c44d3..7e628cd1f6 100644 --- a/libs/s25main/pathfinding/RoadPathFinder.cpp +++ b/libs/s25main/pathfinding/RoadPathFinder.cpp @@ -6,12 +6,13 @@ #include "EventManager.h" #include "RttrForeachPt.h" #include "buildings/nobHarborBuilding.h" +#include "helpers/EnumArray.h" #include "pathfinding/OpenListPrioQueue.h" #include "pathfinding/OpenListVector.h" #include "world/GameWorldBase.h" #include "nodeObjs/noRoadNode.h" -#include "gameData/GameConsts.h" #include "s25util/Log.h" +#include /// Comparison operator for road nodes that returns true if lhs > rhs (descending order) struct RoadNodeComperatorGreater diff --git a/libs/s25main/world/GameWorldBase.cpp b/libs/s25main/world/GameWorldBase.cpp index b0578519c4..72d0202882 100644 --- a/libs/s25main/world/GameWorldBase.cpp +++ b/libs/s25main/world/GameWorldBase.cpp @@ -25,8 +25,8 @@ #include "pathfinding/ShipPathData.h" #include "nodeObjs/noFlag.h" #include "gameData/BuildingProperties.h" -#include "gameData/GameConsts.h" #include "gameData/TerrainDesc.h" +#include #include GameWorldBase::GameWorldBase(std::vector players, const GlobalGameSettings& gameSettings, EventManager& em) diff --git a/libs/s25main/world/TradeRoute.cpp b/libs/s25main/world/TradeRoute.cpp index 66331ea7cd..97d9e27f56 100644 --- a/libs/s25main/world/TradeRoute.cpp +++ b/libs/s25main/world/TradeRoute.cpp @@ -5,7 +5,7 @@ #include "world/TradeRoute.h" #include "SerializedGameData.h" #include "world/GameWorld.h" -#include "gameData/GameConsts.h" +#include TradeRoute::TradeRoute(const GameWorld& world, unsigned char player, const MapPoint& start, const MapPoint& goal) : world(world), player(player) diff --git a/tests/s25Main/integration/testGamePlayer.cpp b/tests/s25Main/integration/testGamePlayer.cpp index 9aa8a8b561..cc3ed10a01 100644 --- a/tests/s25Main/integration/testGamePlayer.cpp +++ b/tests/s25Main/integration/testGamePlayer.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,7 +15,7 @@ #include "gameTypes/MineResourceBehavior.h" #include "gameTypes/Resource.h" #include "gameData/BuildingProperties.h" -#include "gameData/GameConsts.h" +#include "gameData/JobConsts.h" #include "rttr/test/random.hpp" #include "s25util/warningSuppression.h" #include diff --git a/tests/s25Main/integration/testPathfinding.cpp b/tests/s25Main/integration/testPathfinding.cpp index e2e6b5bf5e..c94b393ec2 100644 --- a/tests/s25Main/integration/testPathfinding.cpp +++ b/tests/s25Main/integration/testPathfinding.cpp @@ -10,7 +10,6 @@ #include "worldFixtures/terrainHelpers.h" #include "nodeObjs/noGranite.h" #include "gameTypes/GameTypesOutput.h" -#include "gameData/GameConsts.h" #include #include #include From e29162c95821da49214da7ec7bbf14e6c52b9018 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sun, 4 Oct 2026 16:22:16 +0200 Subject: [PATCH 18/23] Move `GetBuildingRadius` to `GameWorldView` It is not constant as it may depend on game settings. --- libs/s25main/gameData/BuildingConsts.cpp | 50 +----------------------- libs/s25main/gameData/BuildingConsts.h | 3 -- libs/s25main/ingameWindows/iwAction.cpp | 2 +- libs/s25main/world/GameWorldView.cpp | 44 +++++++++++++++++++++ libs/s25main/world/GameWorldView.h | 6 ++- 5 files changed, 51 insertions(+), 54 deletions(-) diff --git a/libs/s25main/gameData/BuildingConsts.cpp b/libs/s25main/gameData/BuildingConsts.cpp index ef364240b5..395f49eea4 100644 --- a/libs/s25main/gameData/BuildingConsts.cpp +++ b/libs/s25main/gameData/BuildingConsts.cpp @@ -1,11 +1,8 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // 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/MilitaryConsts.h" @@ -414,48 +411,3 @@ 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 166c753895..1b7f3f1ffb 100644 --- a/libs/s25main/gameData/BuildingConsts.h +++ b/libs/s25main/gameData/BuildingConsts.h @@ -119,6 +119,3 @@ constexpr std::array 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 86bdf59e7a..3274e88b7e 100644 --- a/libs/s25main/ingameWindows/iwAction.cpp +++ b/libs/s25main/ingameWindows/iwAction.cpp @@ -178,7 +178,7 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP tooltip << _(BUILDING_NAMES[bld]); // Show radius if any - const unsigned radius = showBuildingRadius ? GetBuildingRadius(bld) : 0; + const unsigned radius = showBuildingRadius ? gwv.GetBuildingRadius(bld) : 0; if(radius > 0) tooltip << boost::format(_("\nRange: %1% tiles")) % radius; diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index c23b094b2c..091ca3221c 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -20,6 +20,7 @@ #include "buildings/nobMilitary.h" #include "buildings/nobUsual.h" #include "drivers/VideoDriverWrapper.h" +#include "figures/nofFarmhand.h" #include "helpers/EnumArray.h" #include "helpers/Range.h" #include "helpers/containerUtils.h" @@ -33,7 +34,9 @@ #include "gameTypes/RoadBuildState.h" #include "gameData/BuildingConsts.h" #include "gameData/GuiConsts.h" +#include "gameData/JobConsts.h" #include "gameData/MapConsts.h" +#include "gameData/MilitaryConsts.h" #include "s25util/error.h" #include #include @@ -708,6 +711,47 @@ void GameWorldView::MoveToLastPosition() lastOffset = newLastOffset; } +unsigned GameWorldView::GetBuildingRadius(BuildingType bld) const +{ + switch(bld) + { + // Military buildings (territory influence radius) + 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]; + case BuildingType::Headquarters: return HQ_RADIUS; + case BuildingType::HarborBuilding: return HARBOR_RADIUS; + + // 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. + case BuildingType::Woodcutter: + case BuildingType::Forester: + case BuildingType::Fishery: + case BuildingType::Quarry: + case BuildingType::Farm: + case BuildingType::Vineyard: + case BuildingType::Charburner: + { + if(const auto job = BLD_WORK_DESC[bld].job) + return nofFarmhand::GetWorkRadius(GetWorld().GetGGS(), *job); + return 0; + } + // Remaining building types have no relevant radius + default: return 0; + } +} + void GameWorldView::AddDrawNodeCallback(IDrawNodeCallback* newCallback) { RTTR_Assert(newCallback); diff --git a/libs/s25main/world/GameWorldView.h b/libs/s25main/world/GameWorldView.h index 29748e5d2a..c95ae20b6d 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -6,6 +6,7 @@ #include "Cheats.h" #include "DrawPoint.h" +#include "gameTypes/BuildingType.h" #include "gameTypes/MapCoordinates.h" #include "gameTypes/MapTypes.h" #include @@ -119,6 +120,9 @@ class GameWorldView DrawPoint GetOffset() const { return offset; } + /// Get the radius in tiles for a building type (worker reach, territory influence, attack range, etc.) + unsigned GetBuildingRadius(BuildingType bld) const; + /// Add a debug node printer void AddDrawNodeCallback(IDrawNodeCallback* newCallback); void RemoveDrawNodeCallback(IDrawNodeCallback* callbackToRemove); From 296ac47aedec094c080084fb70332436f6a4c31c Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sun, 4 Oct 2026 16:32:36 +0200 Subject: [PATCH 19/23] Use 2 methods to show/hide the radius preview --- libs/s25main/ingameWindows/iwAction.cpp | 6 +++--- libs/s25main/world/GameWorldView.cpp | 11 ++++++++--- libs/s25main/world/GameWorldView.h | 5 +++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/libs/s25main/ingameWindows/iwAction.cpp b/libs/s25main/ingameWindows/iwAction.cpp index 3274e88b7e..5e18949a72 100644 --- a/libs/s25main/ingameWindows/iwAction.cpp +++ b/libs/s25main/ingameWindows/iwAction.cpp @@ -202,11 +202,11 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP if(hovered) { this->activeHoveredIcon_ = icon; - this->gwv.SetRadiusPreview(std::make_pair(this->selectedPt, radius)); + this->gwv.ShowRadiusPreview(this->selectedPt, radius); } else if(this->activeHoveredIcon_ == icon) { this->activeHoveredIcon_ = nullptr; - this->gwv.SetRadiusPreview(std::nullopt); + this->gwv.HideRadiusPreview(); } // else: stale leave from a previously-hovered icon, ignore }); @@ -431,7 +431,7 @@ void iwAction::Close() if(ShouldBeClosed()) return; activeHoveredIcon_ = nullptr; - gwv.SetRadiusPreview(std::nullopt); + gwv.HideRadiusPreview(); IngameWindow::Close(); if(mousePosAtOpen_.isValid()) VIDEODRIVER.SetMousePos(mousePosAtOpen_); diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index 091ca3221c..9313d3e94d 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -765,6 +765,11 @@ void GameWorldView::RemoveDrawNodeCallback(IDrawNodeCallback* callbackToRemove) drawNodeCallbacks.erase(itPos); } +void GameWorldView::ShowRadiusPreview(MapPoint center, unsigned radius) +{ + radiusPreview_ = std::make_pair(center, radius); +} + // 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) @@ -834,11 +839,11 @@ void GameWorldView::UpdateRadiusPreviewForMousePos(const Position& mousePos) { const unsigned bldRadius = GetBuildingRadius(*bldType); if(bldRadius > 0) - radiusPreview_ = std::make_pair(selPt, bldRadius); + ShowRadiusPreview(selPt, bldRadius); else - radiusPreview_ = std::nullopt; + HideRadiusPreview(); } else - radiusPreview_ = std::nullopt; + HideRadiusPreview(); } void GameWorldView::CalcFxLx() diff --git a/libs/s25main/world/GameWorldView.h b/libs/s25main/world/GameWorldView.h index c95ae20b6d..a4178330b4 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -127,8 +127,9 @@ 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; } + /// Show a radius preview outline on the map + void ShowRadiusPreview(MapPoint center, unsigned radius); + void HideRadiusPreview() { radiusPreview_ = std::nullopt; } /// Update radius preview based on which map node is under the mouse cursor /// Called from mouse-move handler, not from draw loop From 7eda3bfce952a52c11212b59d6cb6004fe81bc4e Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sun, 4 Oct 2026 16:50:07 +0200 Subject: [PATCH 20/23] Explain calculation in `SnapToNearestCopy` --- libs/s25main/world/GameWorldView.cpp | 16 +++++++--------- libs/s25main/world/GameWorldView.h | 4 ++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index 9313d3e94d..0fec371102 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -770,15 +770,13 @@ void GameWorldView::ShowRadiusPreview(MapPoint center, unsigned radius) radiusPreview_ = std::make_pair(center, radius); } -// 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; +DrawPoint GameWorldView::SnapToNearestCopy(const DrawPoint pt, const DrawPoint ref, const Extent mapSize) +{ + // Find how many map sizes separate `pt` from `ref`, round to the nearest integer number of wraps. + const auto k = PointF(ref - pt) / mapSize; + const auto kRounded = PointF(std::round(k.x), std::round(k.y)); + // Shift `pt` by that many map sizes to get the nearest toroidal copy. + return pt + DrawPoint(kRounded) * mapSize; } // ----------------------------------------------------------------------------- diff --git a/libs/s25main/world/GameWorldView.h b/libs/s25main/world/GameWorldView.h index a4178330b4..8a605c70ac 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -167,8 +167,8 @@ class GameWorldView /// 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); + /// Snap a point to the nearest toroidal copy relative to a reference position, given the map size in pixels + static DrawPoint SnapToNearestCopy(DrawPoint pt, DrawPoint ref, Extent mapSize); void SaveIngameSettingsValues() const; void updateEffectiveZoomFactor(); From 2f8205fb80dbb7a0e23cad492e06979821f3c7d9 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sun, 4 Oct 2026 17:09:33 +0200 Subject: [PATCH 21/23] Cache radius outline Calculating the points in a large radius in every draw call is costly. Do the constant part only once when the radius gets enabled. --- libs/s25main/world/GameWorldView.cpp | 37 ++++++++++++++-------------- libs/s25main/world/GameWorldView.h | 13 +++++++--- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index 0fec371102..201d7dbf20 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -227,9 +227,7 @@ void GameWorldView::Draw(const RoadBuildState& rb, const MapPoint selected, bool if(show_names || show_productivity) DrawNameProductivityOverlay(terrainRenderer); - // Draw radius preview outline if enabled - if(radiusPreview_) - DrawRadiusOutline(radiusPreview_->first, radiusPreview_->second); + DrawRadiusOutline(); DrawGUI(rb, terrainRenderer, selected, drawMouse); @@ -767,7 +765,15 @@ void GameWorldView::RemoveDrawNodeCallback(IDrawNodeCallback* callbackToRemove) void GameWorldView::ShowRadiusPreview(MapPoint center, unsigned radius) { - radiusPreview_ = std::make_pair(center, radius); + if(radiusPreview_ && radiusPreview_->center == center && radiusPreview_->radius == radius) + return; + std::vector pts; + for(const auto& [mapPt, dist] : GetWorld().GetPointsInRadius(center, radius, ReturnMapPointWithRadius{})) + { + if(dist == radius) + pts.push_back(GetWorld().GetNodePos(mapPt)); + } + radiusPreview_ = RadiusPreview{center, radius, std::move(pts)}; } DrawPoint GameWorldView::SnapToNearestCopy(const DrawPoint pt, const DrawPoint ref, const Extent mapSize) @@ -779,31 +785,26 @@ DrawPoint GameWorldView::SnapToNearestCopy(const DrawPoint pt, const DrawPoint r return pt + DrawPoint(kRounded) * mapSize; } -// ----------------------------------------------------------------------------- -// Draw radius overlay for a building's working range, handling map wrapping. -// ----------------------------------------------------------------------------- -void GameWorldView::DrawRadiusOutline(const MapPoint& center, unsigned radius) +void GameWorldView::DrawRadiusOutline() { + if(!radiusPreview_) + return; + 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); + constexpr auto BORDER_SIZE = Extent::all(5); + const Extent mapPxSize = mapSize * Extent(TR_W, 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; + const DrawPoint centerScr = world.GetNodePos(radiusPreview_->center) + selPtOffset; - for(const auto& [mapPt, dist] : pts) + for(auto screenPt : radiusPreview_->outline) { - 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); + Window::DrawRectangle(Rect(screenPt - BORDER_SIZE / 2, BORDER_SIZE), BORDER_COLOR); } } diff --git a/libs/s25main/world/GameWorldView.h b/libs/s25main/world/GameWorldView.h index 8a605c70ac..8c6d28b984 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -33,6 +33,13 @@ struct ObjectBetweenLines; class GameWorldView { + struct RadiusPreview + { + MapPoint center; + unsigned radius; + std::vector outline; + }; + const GameWorldViewer& gwv; /// Top-Left position of the view (window) @@ -56,7 +63,7 @@ class GameWorldView bool show_productivity; /// Optional radius preview (center position, radius) drawn as outline on the map - std::optional> radiusPreview_; + std::optional radiusPreview_; /// Cached addon status: whether building radius overlay is enabled bool isBuildingRadiusEnabled_; @@ -164,8 +171,8 @@ 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); + /// Draw a radius outline ring + void DrawRadiusOutline(); /// Snap a point to the nearest toroidal copy relative to a reference position, given the map size in pixels static DrawPoint SnapToNearestCopy(DrawPoint pt, DrawPoint ref, Extent mapSize); From 1143329812d277523e0ca0fc53edcfee6e24d643 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sun, 4 Oct 2026 17:55:22 +0200 Subject: [PATCH 22/23] Show radius of well --- libs/s25main/world/GameWorldView.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index 201d7dbf20..427dd55e25 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -731,7 +731,9 @@ unsigned GameWorldView::GetBuildingRadius(BuildingType bld) const case BuildingType::GraniteMine: case BuildingType::CoalMine: case BuildingType::IronMine: - case BuildingType::GoldMine: return MINER_RADIUS; + case BuildingType::GoldMine: + case BuildingType::Well: // Well uses the same radius + return MINER_RADIUS; // Farmhand-based buildings — worker goes out to gather resources from the map. case BuildingType::Woodcutter: case BuildingType::Forester: From 2c4b1c16ff2db0382129678aaccdd76029463f83 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sun, 4 Oct 2026 21:17:52 +0200 Subject: [PATCH 23/23] Fix stale building radius preview When the desktop does not has the focus it won't update the radius preview. It then shows the last highlighted buildings radius when the selected point is close enough. Move the selected-building check back to GameWorldView and use the `showMouse` parameter as the sign whether to handle the mouse. --- libs/s25main/desktops/dskGameInterface.cpp | 3 --- libs/s25main/world/GameWorldView.cpp | 12 +++++++----- libs/s25main/world/GameWorldView.h | 5 ++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/libs/s25main/desktops/dskGameInterface.cpp b/libs/s25main/desktops/dskGameInterface.cpp index 3075b3e78b..49d19bcdad 100644 --- a/libs/s25main/desktops/dskGameInterface.cpp +++ b/libs/s25main/desktops/dskGameInterface.cpp @@ -751,9 +751,6 @@ bool dskGameInterface::Msg_LeftUp(const MouseCoords& mc) bool dskGameInterface::Msg_MouseMove(const MouseCoords& mc) { - // Update radius preview for buildings under the cursor - gwv.UpdateRadiusPreviewForMousePos(mc.pos); - if(!isScrolling) { if(mc.num_tfingers == 1) diff --git a/libs/s25main/world/GameWorldView.cpp b/libs/s25main/world/GameWorldView.cpp index 427dd55e25..685bccf8d5 100644 --- a/libs/s25main/world/GameWorldView.cpp +++ b/libs/s25main/world/GameWorldView.cpp @@ -227,7 +227,12 @@ void GameWorldView::Draw(const RoadBuildState& rb, const MapPoint selected, bool if(show_names || show_productivity) DrawNameProductivityOverlay(terrainRenderer); - DrawRadiusOutline(); + if(isBuildingRadiusEnabled_) + { + if(drawMouse) + UpdateRadiusPreviewForMousePos(); + DrawRadiusOutline(); + } DrawGUI(rb, terrainRenderer, selected, drawMouse); @@ -810,11 +815,8 @@ void GameWorldView::DrawRadiusOutline() } } -void GameWorldView::UpdateRadiusPreviewForMousePos(const Position& mousePos) +void GameWorldView::UpdateRadiusPreviewForMousePos() { - if(!isBuildingRadiusEnabled_ || WINDOWMANAGER.FindWindowAtPos(mousePos)) - return; - const auto& world = GetWorld(); std::optional bldType; switch(gwv.GetVisibility(selPt)) diff --git a/libs/s25main/world/GameWorldView.h b/libs/s25main/world/GameWorldView.h index 8c6d28b984..9b8b5bd039 100644 --- a/libs/s25main/world/GameWorldView.h +++ b/libs/s25main/world/GameWorldView.h @@ -138,9 +138,8 @@ class GameWorldView void ShowRadiusPreview(MapPoint center, unsigned radius); void HideRadiusPreview() { radiusPreview_ = std::nullopt; } - /// 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); + /// Update radius preview based on which map node is selected + void UpdateRadiusPreviewForMousePos(); /// Gibt selektierten Punkt zurück MapPoint GetSelectedPt() const { return selPt; }