Radius overlay#1946
Conversation
| 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 |
There was a problem hiding this comment.
I see a if(distance < 14) check there. So why 12 here?
There was a problem hiding this comment.
Also catapult was not available as constant, I extracted it to constant.
There was a problem hiding this comment.
Extracting constants is always good. You just have to make sure you actually use them after defining it.
@Spikeone I guess we should move to actually using radius for all jobs, not rectangles.
There was a problem hiding this comment.
Probably yes - can check in the original but pretty sure thats it's circular
There was a problem hiding this comment.
I see a
if(distance < 14)check there. So why 12 here?
Isn't it the same reason our building range is +1? So the target node is included or something like that?
There was a problem hiding this comment.
Which building range? if(distance < 14) means the building on the 13th node away can still be hit, so there are 12 nodes in-between. So maybe that is what is meant by google. And maybe the other part you are referring to has a corner case which required that. But would need to check
| // 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 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
That is indeed hard. You can use the GetNodePos overload taking the height as the base point. Then use const Position mapDrawSize = world->GetSize() * Position(TR_W, TR_H); as the offsets
I don't think it gets better than that: You have to draw possibly all copies when the zoom factor is large which it seems is also not yet taken into account. Possibly even that is not enough.
Maybe rather use a (flat_)set to store the radius points and move drawing to DrawGUI where you can check with contains whether to draw the mark
There was a problem hiding this comment.
I found that s25edit draws a brush under the cursor which is resizeable by numpad +/- and based the radius outline drawing on that!
|
Given #129 was a request for an addon, can you add one and guard the code changing the tooltip and setting the draw-circle by the addon setting? We usually keep the default behavior to be the same as in S2 BTW: Unless you just applied a suggested code snippet directly please don't resolve conversations as then we miss your reply |
Yes, I think so |
|
I think it should be player toggleable during game, and per player in multiplayer. But most ppl said it should be addon so maybe best to proceed with that. But should tooltip also be guarded by addon?
Ok I will leave resolving to original commenter from now on. I just did it to track what I resolved. |
|
nice idea, but please make it an optional addon. also, the red isn't very much in the style of S2, |
it is now an addon
not sure what graphics to use, please provide/suggest. |
|
Ok all issues solved. And made the addon enabled by default - mainly so I could use my existing save for testing. |
|
Found an edge case: it hovers game world buildings thru windows and will draw outline |
|
I have played many games with this enabled and it works perfectly. Hard to play without it. Only remaining issue is to use custom graphics instead of red rectangles. |
| return nofFarmhand::GetWorkRadius(*job); | ||
| return 0; | ||
| } | ||
| // All other building types have no relevant radius overlay |
There was a problem hiding this comment.
| // All other building types have no relevant radius overlay | |
| // All other building types have no relevant work radius (e.g. only work inside the building) |
| "when hovering over a building icon or selecting a building."), | ||
| 1) // Enabled by default |
There was a problem hiding this comment.
| "when hovering over a building icon or selecting a building."), | |
| 1) // Enabled by default | |
| "when hovering over a building icon or selecting a building."), 0) |
The convention is that the default is S2-like behavior, so you can "disable" all addons and get vanilla S2
There was a problem hiding this comment.
Yeah can revert this. Making it default enabled allowed me to continue using my save game for testing tho so it's been nice. Also playing campaign with it enabled is lovely.
I'm addicted I can't play without it.
Let's revert to default disable last thing before merge ok?
| auto* bldGroup = buildTabCtrl ? buildTabCtrl->GetGroup(buildTabCtrl->GetCurrentTab()) : nullptr; | ||
|
|
||
| bool hasHoveredIcon = false; | ||
| if(bldGroup && gwv.GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS)) |
There was a problem hiding this comment.
This looks inefficient doing on every single draw call. What was wrong with the MouseMove-hovering?
There was a problem hiding this comment.
There's an issue with icon->SetOnHoverChanged()
When hovering icons in forward direction, the leave event fires after enter event.
Maybe there should be 2 passes, first send leave event, then send enter.
| for(auto* icon : bldGroup->GetCtrls<ctrlBuildingIcon>()) | ||
| { | ||
| const unsigned radius = GetBuildingRadius(icon->GetType()); | ||
| if(icon->IsMouseOver() && radius > 0) |
There was a problem hiding this comment.
Shouldn't you break once you found ANY mouseover?
|
|
||
| // ----------------------------------------------------------------------------- | ||
| // Snap a point to the nearest toroidal copy relative to a reference position. | ||
| // Same formula as s25edit's correctMouseBlit(): |
There was a problem hiding this comment.
Please don't include such references. The client should be self-contained and the code correct on its own. So rather explain why this specific code is correct as-is.
Also the arguments are not clear to me. Why pass 2 by const-ref? Should all be values.
Can mapPxSize ever be zero? When and why?
Without that it gets much easier to read as you can use e.g. pt -ref etc.
| 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). |
There was a problem hiding this comment.
Why "reference"? Isn't this simply a position in screen coordinates? I.e. a DrawPoint from a MapPoint?
| const auto centerAlt = world.GetNode(center).altitude; | ||
| const DrawPoint ref = GetNodePos(center) - DrawPoint(0, HEIGHT_FACTOR * centerAlt) + selPtOffset; |
There was a problem hiding this comment.
Use the overload:
| const auto centerAlt = world.GetNode(center).altitude; | |
| const DrawPoint ref = GetNodePos(center) - DrawPoint(0, HEIGHT_FACTOR * centerAlt) + selPtOffset; | |
| const DrawPoint ref = GetNodePos(center, world.GetNode(center).altitude) + selPtOffset; |
Also where does selPtOffset come from? Would need a comment
| const auto centerAlt = world.GetNode(center).altitude; | ||
| const DrawPoint ref = GetNodePos(center) - DrawPoint(0, HEIGHT_FACTOR * centerAlt) + selPtOffset; | ||
|
|
||
| for(const auto& [basePt, dist] : pts) |
There was a problem hiding this comment.
| for(const auto& [basePt, dist] : pts) | |
| for(const auto& [pt, dist] : pts) |
Or mapPt or similar, I suspected a function parameter or similar especially with scr not containing "Point" which I guess it should. Maybe mapPt and screenPt?
| bool GlobalGameSettings::isEnabled(AddonId id) const | ||
| { | ||
| const auto* addon = getAddon(id); | ||
| return addon && addon->status != addon->addon->getDefaultStatus(); |
There was a problem hiding this comment.
This doesn't seem correct. Please revert
There was a problem hiding this comment.
This is to allow default enabled addons.
Ok I find it very useful to have it default enabled for testing. Can we revert this last thing before merge.
|
|
||
| // 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) |
There was a problem hiding this comment.
Shouldn't that be rather on the outside, e.g. in dskGameInterface where it could be more efficiently in a MouseMove handler. This way it is opt-in and e.g. doesn't enable for observation windows (suspicion only, haven't checked)
And whereever this is done I'd say it makes sense to cache GetWorld().GetGGS().isEnabled(AddonId::BUILDING_RADIUS) (effectively const) instead of getting this on every draw call.
The original logic checks whether status differs from the addon's default (status != getDefaultStatus()). This is the convention used throughout the codebase: an addon is considered 'enabled' when its status has been changed from the default (S2-like) behavior. The previous change to 'status != 0' would break addons with default != 0.
This reverts commit 0ed0c34.
|
Addressed all feedback. |
|
|
||
| bool dskGameInterface::Msg_MouseMove(const MouseCoords& mc) | ||
| { | ||
| // Update radius preview for buildings under the cursor (event-driven, not in draw loop) |
There was a problem hiding this comment.
Make sure to clean such "intermediate" comments from AI
| // Update radius preview for buildings under the cursor (event-driven, not in draw loop) | |
| // Update radius preview for buildings under the cursor |
| std::stringstream tooltip; | ||
| tooltip << _(BUILDING_NAMES[bld]); | ||
|
|
||
| // Radius anzeigen falls vorhanden |
There was a problem hiding this comment.
English comments only (at least for new/changed ones)
| unsigned radius = 0; | ||
| if(showBuildingRadius) | ||
| radius = GetBuildingRadius(bld); |
There was a problem hiding this comment.
Prefer const variables:
| unsigned radius = 0; | |
| if(showBuildingRadius) | |
| radius = GetBuildingRadius(bld); | |
| const unsigned radius = showBuildingRadius ? GetBuildingRadius(bld) : 0; |
| // Store hover callback; activeHoveredIcon_ guards against stale leaves from | ||
| // reversed child iteration order in Msg_MouseMove dispatch |
There was a problem hiding this comment.
Similar AI comment unless I'm missing something this is completely redundant
| // Store hover callback; activeHoveredIcon_ guards against stale leaves from | |
| // reversed child iteration order in Msg_MouseMove dispatch |
| if(show_names || show_productivity) | ||
| DrawNameProductivityOverlay(terrainRenderer); | ||
|
|
||
| // Draw radius preview outline (set by icon-hover or map-hover) |
There was a problem hiding this comment.
this comment is about the caller and hence can easily be outdated. Avoid please
| // Draw radius preview outline (set by icon-hover or map-hover) | |
| // Draw radius preview outline if enabled |
|
|
||
| void GameWorldView::UpdateRadiusPreviewForMousePos(const Position& mousePos) | ||
| { | ||
| if(!isBuildingRadiusEnabled_ || WINDOWMANAGER.FindWindowAtPos(mousePos)) |
There was a problem hiding this comment.
Hm, this looks like this check should rather be handled by the caller: You update by selPt not mousePos and that is only set/updated in the Draw call so likely to be stale by 1 frame anyway.
Passing a value into this class just to check it using a different layer (world vs windows) feels wrong
Maybe we can turn it around: Add a method that gets the building type at the selection which we can reuse in dskGameInterface::ContextClick too, see existing code.
Do we really want to handle non-owned buildings? Feels like a cheat... If we do the code will be different so then I'd say a method getSelectedBuildingType here and handling the mouse pos/window-manager and buildings-enabled in the caller is cleaner.
| } | ||
|
|
||
| // Snap a point to the nearest toroidal copy (map wrapping) relative to a reference. | ||
| // k = round((reference - pt) / mapSize), pt += k * mapSize. |
There was a problem hiding this comment.
You can (pretty much) directly express this below like k = PointF(ref - pt) / mapPxSize (maybe a better name than k) then pt.x += DrawPoint(round(k.x), round(k.y)) * mapPxSize
And a short comment why this formula is correct/used would be good. I.e. what is this k and what does it mean. This way would keep us wondering later.


Add tooltip and overlay for building ranges