Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/s25main/FOWObjects.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions libs/s25main/GlobalGameSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ void GlobalGameSettings::registerAllAddons()
AddonForesterFarmFieldAvoidance,
AddonForesterReachRadius,
AddonWoodcutterReachRadius,
AddonStonemasonReachRadius
AddonStonemasonReachRadius,
AddonBuildingRadius
>;
// clang-format on
using namespace boost::mp11;
Expand Down Expand Up @@ -155,7 +156,7 @@ const GlobalGameSettings::AddonWithState* GlobalGameSettings::getAddon(AddonId i
bool GlobalGameSettings::isEnabled(AddonId id) const
{
const auto* addon = getAddon(id);
return addon && addon->status != addon->addon->getDefaultStatus();
return addon && addon->status != 0;
}

unsigned GlobalGameSettings::getSelection(AddonId id) const
Expand Down
4 changes: 2 additions & 2 deletions libs/s25main/addons/AddonBool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
#include "Window.h"
#include "controls/ctrlCheck.h"

AddonBool::AddonBool(const AddonId id, AddonGroup groups, const std::string& name, const std::string& description)
: Addon(id, groups, name, description, 0)
AddonBool::AddonBool(const AddonId id, AddonGroup groups, const std::string& name, const std::string& description, unsigned defaultStatus)
: Addon(id, groups, name, description, defaultStatus)
{}

std::unique_ptr<AddonGui> AddonBool::createGui(Window& window, bool readonly) const
Expand Down
2 changes: 1 addition & 1 deletion libs/s25main/addons/AddonBool.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class AddonBool : public Addon
};

public:
AddonBool(AddonId id, AddonGroup groups, const std::string& name, const std::string& description);
AddonBool(AddonId id, AddonGroup groups, const std::string& name, const std::string& description, unsigned defaultStatus = 0);

unsigned getNumOptions() const override;

Expand Down
22 changes: 22 additions & 0 deletions libs/s25main/addons/AddonBuildingRadius.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "AddonBool.h"
#include "mygettext/mygettext.h"

/**
* Show building radius information in tooltips and as map overlay
*/
class AddonBuildingRadius : public AddonBool
{
public:
AddonBuildingRadius()
: AddonBool(AddonId::BUILDING_RADIUS, AddonGroup::GamePlay, _("Show building radius"),
_("Shows the working radius of buildings in the build menu tooltip and as an overlay on the map "
"when hovering over a building icon or selecting a building."),
1) // Enabled by default
{}
};
2 changes: 2 additions & 0 deletions libs/s25main/addons/Addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,5 @@
#include "addons/AddonForesterReachRadius.h"
#include "addons/AddonStonemasonReachRadius.h"
#include "addons/AddonWoodcutterReachRadius.h"

#include "addons/AddonBuildingRadius.h"
5 changes: 4 additions & 1 deletion libs/s25main/addons/const_addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down Expand Up @@ -82,7 +83,9 @@ ENUM_WITH_STRING(AddonId, LIMIT_CATAPULTS = 0x00000000, INEXHAUSTIBLE_MINES = 0x
FORESTER_FARM_FIELD_AVOIDANCE = 0x01100000,

FORESTER_REACH_RADIUS = 0x01200000, WOODCUTTER_REACH_RADIUS = 0x01200001,
STONEMASON_REACH_RADIUS = 0x01200002)
STONEMASON_REACH_RADIUS = 0x01200002,

BUILDING_RADIUS = 0x01300000)
//-V:AddonId:801

enum class AddonGroup : unsigned
Expand Down
2 changes: 1 addition & 1 deletion libs/s25main/figures/nofCatapultMan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
3 changes: 3 additions & 0 deletions libs/s25main/figures/nofCatapultMan.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
6 changes: 2 additions & 4 deletions libs/s25main/figures/nofHunter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhm hunters look for animals in a SQUARE?

Should fix to be circle? Kinda out of scope but can include or do separate PR. Cause right now the range outline doesn't match what game uses.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created PR #1948 to make Hunters use circle instead of square.

const int SQUARE_SIZE = 19;

// Liste mit den gefundenen Tieren
std::vector<noAnimal*> 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);

Expand Down
3 changes: 3 additions & 0 deletions libs/s25main/figures/nofHunter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
51 changes: 51 additions & 0 deletions libs/s25main/gameData/BuildingConsts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later

#include "BuildingConsts.h"
#include "figures/nofCatapultMan.h"
#include "figures/nofFarmhand.h"
#include "figures/nofHunter.h"
#include "mygettext/mygettext.h"
#include "gameTypes/BuildingTypes.h"
#include "gameData/GameConsts.h"
#include "gameData/MilitaryConsts.h"
#include <type_traits>

const helpers::EnumArray<const char*, BuildingType> BUILDING_NAMES = {
Expand Down Expand Up @@ -409,3 +415,48 @@ const helpers::MultiEnumArray<DrawPoint, Nation, BuildingType> 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;
}
// All other building types have no relevant radius overlay
default: return 0;
}
}
3 changes: 3 additions & 0 deletions libs/s25main/gameData/BuildingConsts.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,6 @@ constexpr std::array<DrawPoint, 4> SUPPRESS_UNUSED NUBIAN_MINE_FIRE = {{

/// Hilfetexte für Gebäude
extern const helpers::EnumArray<const char*, BuildingType> BUILDING_HELP_STRINGS;

/// Get the radius in tiles for a building type (worker reach, territory influence, attack range, etc.)
unsigned GetBuildingRadius(BuildingType bld);
34 changes: 34 additions & 0 deletions libs/s25main/ingameWindows/iwAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "nodeObjs/noFlag.h"
#include "gameData/BuildingConsts.h"
#include "gameData/const_gui_ids.h"
#include <boost/format.hpp>
#include <sstream>

// Tab - Flags
Expand Down Expand Up @@ -159,6 +160,7 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP
building_available[BuildingType::LeatherWorks] = false;
}

const bool showBuildingRadius = gwv.GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS);
constexpr helpers::EnumArray<unsigned, BuildTab> NUM_TABS = {1, 2, 3, 1, 3};

for(unsigned char i = 0; i < NUM_TABS[tabs.build_tabs]; ++i)
Expand All @@ -175,6 +177,13 @@ iwAction::iwAction(GameInterface& gi, GameWorldView& gwv, const Tabs& tabs, MapP
std::stringstream tooltip;
tooltip << _(BUILDING_NAMES[bld]);

// Radius anzeigen falls vorhanden
unsigned radius = 0;
if(showBuildingRadius)
radius = GetBuildingRadius(bld);
if(radius > 0)
tooltip << boost::format(_("\nRange: %1% tiles")) % radius;

tooltip << _("\nCosts: ");
if(BUILDING_COSTS[bld].boards > 0)
tooltip << (int)BUILDING_COSTS[bld].boards << _(" boards");
Expand Down Expand Up @@ -407,6 +416,7 @@ void iwAction::Close()
{
if(ShouldBeClosed())
return;
gwv.SetRadiusPreview(std::nullopt);
IngameWindow::Close();
if(mousePosAtOpen_.isValid())
VIDEODRIVER.SetMousePos(mousePosAtOpen_);
Expand Down Expand Up @@ -526,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<ctrlTab>(0);
auto* buildTabCtrl =
(mainTab && mainTab->GetCurrentTab() == TAB_BUILD) ? mainTab->GetGroup(TAB_BUILD)->GetCtrl<ctrlTab>(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<ctrlBuildingIcon>())
{
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<ctrlTab>(0);
if(tab)
{
Expand Down
86 changes: 86 additions & 0 deletions libs/s25main/world/GameWorldView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -36,6 +38,7 @@
#include <glad/glad.h>
#include <boost/format.hpp>
#include <cmath>
#include <optional>

GameWorldView::GameWorldView(const GameWorldViewer& gwv, const Position& pos, const Extent& size)
: selPt(0, 0), show_bq(SETTINGS.ingame.showBQ), show_names(SETTINGS.ingame.showNames),
Expand Down Expand Up @@ -220,6 +223,35 @@ void GameWorldView::Draw(const RoadBuildState& rb, const MapPoint selected, bool
if(show_names || show_productivity)
DrawNameProductivityOverlay(terrainRenderer);

// Draw radius preview outline
if(radiusPreview_)
DrawRadiusOutline(radiusPreview_->first, radiusPreview_->second);

// Auto-detect radius for the building under the cursor
if(!radiusPreview_ && GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS))
{
std::optional<BuildingType> bldType;
const Visibility vis = gwv.GetVisibility(selPt);
if(vis == Visibility::Visible)
{
const auto* bld = GetWorld().GetSpecObj<noBaseBuilding>(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<const fowBuilding&>(*fow).GetBuildingType();
}

if(bldType)
{
const unsigned bldRadius = GetBuildingRadius(*bldType);
if(bldRadius > 0)
DrawRadiusOutline(selPt, bldRadius);
}
}

DrawGUI(rb, terrainRenderer, selected, drawMouse);

// Draw catapult stones
Expand Down Expand Up @@ -713,6 +745,60 @@ 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<int>(std::floor((ref.x - pt.x) / static_cast<double>(mapPxSize.x) + 0.5));
pt.x += kx * mapPxSize.x;
}
if(mapPxSize.y > 0)
{
int ky = static_cast<int>(std::floor((ref.y - pt.y) / static_cast<double>(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();
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);

// 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& [basePt, dist] : pts)
{
if(dist != radius)
continue;

DrawPoint scr = GetNodePos(basePt, world.GetNode(basePt).altitude);
scr = SnapToNearestCopy(scr, ref, mapPxSize) - 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)
Expand Down
Loading