Enhanced canals: chainable canals, deep canals, and canal irrigation - #1219
Open
acato wants to merge 17 commits into
Open
Enhanced canals: chainable canals, deep canals, and canal irrigation#1219acato wants to merge 17 commits into
acato wants to merge 17 commits into
Conversation
Allow canals to be built adjacent to existing canals (not just water), enabling multi-tile canal chains connecting water bodies. Each additional canal in a connected chain doubles both gold cost and build time. Add BFS-based getAdjacentSeaArea() so mid-chain canals can resolve their sea area for ship navigation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pioneers can now build Ice Huts on TERRAIN_ICE_LAKE tiles, providing +2 Food (matching the total yield of Fishing Nets on regular lakes). Build effort matches Fishing Nets (1000 time, 100 cost). DLL changes allow land units to enter ice lake tiles, treating frozen lakes like large rivers for movement purposes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EventTriggeredData::setRandomNumbers() creates a RandomContainer for each event in the trigger, generates a random number via getSorenRandNum, but never actually stores the container in m_RandomNumbers. The push_back call was missing, so m_RandomNumbers remained empty after the function completed. This caused getRandomNumber() and getRandomNumberForIndex() to always return 0 (the fallback default), which had two consequences: 1. Any Python PythonCanDo callback using getRandomNumberForIndex() for probability checks would always see 0. For example, canTriggerVolcanoDormant1() checks "getRandomNumberForIndex(0) < 250" which was always true (0 < 250), making the volcano dormant event fire 100% of the time instead of the intended ~25%. 2. The DLL-side TriggerChance fastpath in CvPlayer::canDoEvent() (line 14762) uses getRandomNumber(eEvent) to check event probability thresholds. With the vector always empty, this also always returned 0, bypassing intended probability gates for events validated through that code path. The fix adds the missing m_RandomNumbers.push_back(container) so that generated random numbers are actually persisted in the vector and available to both Python and C++ callers. Related: We-the-People-civ4col-mod#1205 (CvRandomInterfaceEvent tuple index out of range) The primary cause of We-the-People-civ4col-mod#1205 was an argsList index mismatch fixed in 38b7518, but this bug compounded the issue by making the random number check in canTriggerVolcanoDormant1 a no-op. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In CvPlayer::initTriggeredData(), after a PythonCanDo callback returns successfully, the code reconstructs a Coordinates object from the trigger data (since Python may have modified it). However, line 14416 passed m_iPlotX as both the X and Y arguments: Coordinates coord(pTriggerData->m_iPlotX, pTriggerData->m_iPlotX); This meant pPlot was resolved to the wrong tile whenever the Python callback modified the trigger's plot coordinates and X != Y. The resolved plot would be at (X, X) instead of (X, Y), causing the event to target the wrong map location for any subsequent logic that uses pPlot (text generation, world news, event application). Events where PythonCanDo does not modify coordinates would be unaffected since pPlot was already correctly set before the callback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Deep canals are a new improvement built by upgrading an existing canal. They allow lake-capable ships (sloops, schooners, etc.) to traverse land tiles, enabling Europe-to-lake routing via deep canal chains. - Cap canal chain cost scaling at 4x (was unbounded 2^n) - Add IMPROVEMENT_DEEP_CANAL with bDeepCanal XML tag + schema - Add BUILD_DEEP_CANAL (iTime=2500, iCost=50) with text keys (EN/FR/DE/RU) - Ship filter: deep canals check TERRAIN_LAKE impassable instead of TERRAIN_OCEAN - Water area bridge graph (union-find) tracks lake-ocean connectivity via deep canal chains; rebuilt on placement/destruction and game load - Civilopedia: updated canal text, added deep canal pedia and helper text - Art: reuses canal graphic (ART_DEF_IMPROVEMENT_DEEP_CANAL) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the previous test package. Now includes deep canals, 4x cost cap, and all modified XML/schema files. Assert build for debugging. Apply on top of stock WtP 4.2.1. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…adjacent desert Canals connected to a freshwater source (lake, large river, or river) now irrigate adjacent desert tiles by creating flood plains. Sea-only canals do not irrigate. This makes canal chains from freshwater through desert strategically valuable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Every unit with BUILD_CANAL now also has BUILD_DEEP_CANAL in their Builds list, so the deep canal upgrade option appears in the UI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The canal filter only returned false to block disallowed ships, but allowed ships fell through to the isWater() check which always fails for canal tiles. Now returns true/false explicitly from the canal block. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three fixes for ship movement on canals: - isAmphibPlot: exclude canal tiles from amphibious landing classification (pathfinder was rejecting canals as destinations) - canMoveInto: bypass the sea-unit land gate for canal tiles - isValidDomainForAction: already fixed in prior commit (explicit returns) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Canals connected to freshwater no longer instantly convert adjacent desert to floodplains. Pioneers can still irrigate desert next to canals, but only if the canal chain is connected to a freshwater source. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AI_omniGroup used getPlot() (which dereferences *plot()) on the head unit of every selection group, but off-map units (e.g. ships traveling to Europe) have a NULL plot(). This crashed during any AI turn with units in transit. Changed to use plot() pointer with explicit NULL check, matching the pattern used by the old AI_group function. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
acato
force-pushed
the
feature/enhanced-canals
branch
from
March 29, 2026 21:08
4970ef7 to
bb85ca0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR extends the existing canal system with three major enhancements that make canals a more strategic and versatile infrastructure option:
1. Chainable Canals
Canals are no longer limited to a single tile adjacent to water. Pioneers can now build canal chains across multiple land tiles by placing each new canal adjacent to an existing one. This enables connecting distant water bodies through multi-tile land crossings.
Cost scaling: Each additional tile in the chain increases both build time and gold cost exponentially (2x per connected canal tile), capped at 4x the base cost. This makes long chains expensive but not prohibitively so, encouraging strategic placement.
Implementation:
CvPlot::canHaveImprovement()now checks for adjacency to existing canals (not just water).CvPlot::countAdjacentConnectedCanals()performs a BFS through the connected canal chain to determine the chain length for cost calculations. Cost scaling is applied in bothCvPlot::getBuildTime()andCvPlayer::getBuildCost().2. Deep Canals
A new improvement type (
IMPROVEMENT_DEEP_CANAL) that is built by upgrading an existing regular canal. Deep canals allow larger lake-capable ships (sloops, schooners, smuggling ships, etc.) to pass through, while regular canals remain limited to coastal ships and fishing boats.Ship filtering logic:
TerrainImpassable(TERRAIN_OCEAN)(coastal-only ships) and gather boats with harbour space 1 (fishing boats) may enter!TerrainImpassable(TERRAIN_LAKE)) may enter — this includes sloops, schooners, smuggling ships, and similar mid-tier vessels, but excludes ocean-going warships like galleons and frigatesWater area bridge graph: A new subsystem in
CvMaptracks which water areas (oceans, lakes) are connected via deep canal chains. This uses a union-find data structure with BFS discovery, rebuilt whenever deep canals are constructed or destroyed. The bridge graph enables the game's Europe-to-lake routing so that lake-capable ships spawning from Europe can reach inland lakes through deep canal chains. Cities adjacent to multiple water areas also create bridges.Implementation files:
CvMap.h/cpp:WaterAreaBridgestruct,rebuildWaterAreaBridges(),areWaterAreasConnected(),getBridgeLocation(),findWaterAreaRoot()(union-find)CvInfos.h/cpp: NewbDeepCanalflag onCvImprovementInfo, read from XMLCvPlot.h/cpp:isDeepCanal(), ship filtering inisValidDomainForAction()andcanMoveInto(), canal BFS ingetAdjacentSeaArea()for spawn routingCvGame.cpp: Bridge graph rebuild on map regeneration and game loadCvSelectionGroup.cpp:isAmphibPlot()excludes canal tiles so ships aren't blocked from entering themIMPROVEMENT_DEEP_CANALdefinition,BUILD_DEEP_CANALbuild action,bDeepCanalschema tag, art defines, all pioneer-type units given the build ability3. Canal Irrigation
Canals connected to a freshwater source (lake, large river, or river) via a canal chain enable pioneers to irrigate adjacent desert tiles into flood plains. The freshwater connection is verified by a BFS through the canal chain (
CvPlot::isCanalChainConnectedToFreshwater()).Key design decisions:
isAdjacentToFreshwaterCanal()which verifies freshwater connectivity through the entire canal chaincanHaveFeature()(general feature placement) andcanBuild()(terraforming validation)Also included: Ice Hut improvement
A new
IMPROVEMENT_ICE_HUTfor frozen lake tiles (TERRAIN_ICE_LAKE), providing +2 food. Land units can now enter ice lake tiles (movement validation updated inisValidDomainForAction()andcanMoveInto()).Also included: Ice Hut improvement
A new
IMPROVEMENT_ICE_HUTfor frozen lake tiles (TERRAIN_ICE_LAKE), providing +2 food. Land units can now enter ice lake tiles (movement validation updated inisValidDomainForAction()andcanMoveInto()).Changed files
DLL Sources
CvPlot.h/cppgetAdjacentSeaArea(), cost scalingCvMap.h/cpprebuildWaterAreaBridges()CvInfos.h/cppbDeepCanalflag onCvImprovementInfoCvPlayer.cppCvUnit.cppcanMoveInto(), ice lake land unit accessCvGame.cppCvGameTextMgr.cppCvSelectionGroup.cppisAmphibPlot()EventTrigger.cpppush_backinsetRandomNumbers())XML
CIV4ImprovementInfos.xmlIMPROVEMENT_DEEP_CANAL,IMPROVEMENT_ICE_HUT; canalbNotAllowedNextToSameAsItselfset to 0CIV4TerrainSchema.xmlbDeepCanalelement typeCIV4BuildInfos.xmlBUILD_DEEP_CANAL,BUILD_ICE_HUTCIV4UnitInfos.xmlBUILD_DEEP_CANALadded to all pioneer-type unitsCIV4ArtDefines_Improvement.xmlXML_AUTO_UTF8_BuildInfo.xmlXML_AUTO_UTF8_ImprovementInfo.xmlTest plan
A test package (DLL + XML files) is included in
test-packages/enhanced-canals/for easy drop-in testing.🤖 Generated with Claude Code