Skip to content

Allow ending a road on an existing road by placing a flag - #175

Open
iGEL wants to merge 1 commit into
Pyrdacor:masterfrom
iGEL:road-end-on-existing-road
Open

Allow ending a road on an existing road by placing a flag#175
iGEL wants to merge 1 commit into
Pyrdacor:masterfrom
iGEL:road-end-on-existing-road

Conversation

@iGEL

@iGEL iGEL commented Aug 23, 2026

Copy link
Copy Markdown

What this does

In the original game a road under construction could be connected to an existing road by placing a flag on it, which splits that road. For positions that allowed a flag (not too close to an existing one, no tree) the game showed the height indicator, and a special click (left + right mouse button) placed the flag and connected the road.

freeserf.net marked every position of an existing road as unavailable (the striped marker), so this was not possible.

How it works

Most of it was already there — only the road cursor rejected those positions:

  • Game.BuildFlag already splits an existing road via BuildFlagSplitPath
  • Viewport.HandleDoubleClick already places a flag and connects the road under construction to it
  • Interface.BuildRoadSegment already had a branch for a destination that has paths but no flag (it was unreachable)
  • Pathfinder.FindShortestPath already routes to such a position (its A* runs backwards from the destination, so the destination is never validated as a step target)

So the change is in DetermineMapCursorTypeRoad, which now shows the height indicator for positions of an existing road where a flag can be built.

The direction is intentionally not marked as valid. A single click must not connect the road there, because that would leave the road on top of the existing one without a flag. Only a double click — or the left + right special click, as in the original — places the flag and connects the road. This also keeps it to a single code path: the immediate Click event fires as the first half of every double click, so making the direction clickable would mean two ways to complete the same road.

A road may still only end on an existing road. Passing through one stays rejected, since only the destination of a road may have a flag (Game.CanBuildRoad).

Refactoring

The flag rule moved from Game.CanBuildFlag to Map.CanBuildFlag(position, ownerIndex) so the new Map.CanRoadEndOnExistingRoad can share it instead of duplicating it. Game.CanBuildFlag now delegates to it. This also makes the rule reachable without a Player, which is what allows it to be tested.

Tests

11 tests in Freeserf.Test/Freeserf.Core/RoadBuildingTests.cs, covering the new rule plus the rejections that must stay in place — pass-through, adjacent flag, occupied position, foreign and unowned land — and the two cases that belong to IsRoadSegmentValid instead.

They run on a hand-built map (new Map(new MapGeometry(3), null) with a small flat-grass MapGenerator), so no Game, renderer or game data is needed. dotnet test FreeserfNet.sln -c LinuxRelease passes: 20 tests, including the 9 existing ones.

Not covered

  • Placing a flag on the road you are currently building (looping it back onto itself) is not supported. That worked in the original, but it is a different mechanism: the road under construction has no paths on the map yet, so it would have to be built first, then split. I preferred the simpler behaviour, but happy to be told otherwise.
  • Manually verified in game on Linux. The interaction itself (cursor sprite, double click, resulting split road) is not unit tested, as that needs the Gui and render stack. Multiplayer was not tested — the double click path already sends CreatePlaceFlagUserAction and the place-road action, and that code is unchanged.

Disclosure

I am not a .NET developer. All changes in this PR were made with Claude Code, reviewed by me and tested in the running game. Please review accordingly — I am happy to adjust anything, and if the approach is not what you want for this codebase, feel free to close it.

🤖 Generated with Claude Code

In the original game a road under construction could be connected to an
existing road by placing a flag on it, which splits that road. freeserf.net
marked every position of an existing road as unavailable (striped marker)
so this was not possible.

The game logic for it was already there: Game.BuildFlag splits an existing
road via BuildFlagSplitPath and Viewport.HandleDoubleClick already places a
flag and connects the road under construction to it. Only the road cursor
rejected those positions.

DetermineMapCursorTypeRoad now shows the height indicator for positions of
an existing road where a flag can be built. The direction is intentionally
not marked as valid: a single click must not connect the road there as that
would leave the road on top of the existing one without a flag. Only a double
click (or the left+right special click, like in the original game) places the
flag and connects the road.

The flag rule itself moved from Game.CanBuildFlag to Map.CanBuildFlag so it
can be shared with the new Map.CanRoadEndOnExistingRoad without duplicating
it. Game.CanBuildFlag now delegates to it.

Note that a road may still only *end* on an existing road. Passing through
one stays rejected as only the destination of a road may have a flag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant