From 53a501c63c4b4268017867698b69d25b28f38eb4 Mon Sep 17 00:00:00 2001
From: Marko Lindqvist <cazfi74@gmail.com>
Date: Sun, 4 Oct 2026 10:39:42 +0300
Subject: [PATCH 80/80] granularity: Add Biplane unit

- Add tech Radio, requiring Flight
- Change Bomber requirement from Flight to Radio
- Add Biplane unit, requiring Flight
- Make only Bomber, instead of all Air units, able to Bombard
- Make Biplane able to do Recon missions

See RM #2288

Signed-off-by: Marko Lindqvist <cazfi74@gmail.com>
---
 data/granularity/actions.ruleset | 24 +++++++++++++++++---
 data/granularity/effects.ruleset |  8 +++++++
 data/granularity/techs.ruleset   |  9 ++++++++
 data/granularity/units.ruleset   | 38 +++++++++++++++++++++++++++++++-
 4 files changed, 75 insertions(+), 4 deletions(-)

diff --git a/data/granularity/actions.ruleset b/data/granularity/actions.ruleset
index 3e71cc0fc8..bb4ab0d14c 100644
--- a/data/granularity/actions.ruleset
+++ b/data/granularity/actions.ruleset
@@ -163,6 +163,12 @@ nuke_units_consuming_always = TRUE
 ; Will performing this action always consume the actor unit?
 found_city_consuming_always = TRUE
 
+; The maximum distance from the actor unit to the center of the
+; scouted area. The value 1 means that the tile must be a tile
+; adjacent to the actor unit. The special value "unlimited" lifts the
+; maximum distance restriction.
+scout_max_range = 6
+
 ; Blank ruleset defined user actions.
 ; See the section "Ruleset defined actions" in doc/README.actions
 ; Example: set up "User Action 1"
@@ -231,6 +237,10 @@ ui_name = _("%sKeep moving%s")
 action  = "Teleport"
 ui_name = _("%sRelocate%s")
 
+[action_recon]
+action  = "Scout"
+ui_name = _("Re%scon%s")
+
 
 ; /* <-- avoid gettext warnings
 ;
@@ -582,9 +592,17 @@ actor_reqs    =
 [enabler_bombard]
 action        = "Bombard"
 actor_reqs    =
-    { "type",         "name", "range"
-      "UnitClass",    "Air",  "Local"
-      "MinMoveFrags", "5",    "Local"
+    { "type",         "name",   "range"
+      "UnitType",     "Bomber", "Local"
+      "MinMoveFrags", "5",      "Local"
+    }
+
+[enabler_recon]
+action        = "Scout"
+actor_reqs    =
+    { "type",         "name",    "range"
+      "UnitType",     "Biplane", "Local"
+      "MinMoveFrags", "5",       "Local"
     }
 
 [enabler_fortify]
diff --git a/data/granularity/effects.ruleset b/data/granularity/effects.ruleset
index b90ca49302..3e40f499af 100644
--- a/data/granularity/effects.ruleset
+++ b/data/granularity/effects.ruleset
@@ -637,6 +637,14 @@ reqs    =
       "Action", "Surgical Strike Building", "Local", TRUE
     }
 
+[effect_action_success_recon]
+type    = "Action_Success_Actor_Move_Cost"
+value   = 65535
+reqs    =
+    { "type",   "name",  "range", "present"
+      "Action", "Scout", "Local", TRUE
+    }
+
 [effect_no_tools]
 type    = "Output_Bonus"
 value   = -50
diff --git a/data/granularity/techs.ruleset b/data/granularity/techs.ruleset
index cbd1cddd3f..f79e04e023 100644
--- a/data/granularity/techs.ruleset
+++ b/data/granularity/techs.ruleset
@@ -529,6 +529,15 @@ As food can be stored, granary is set to 15% full when city grows\
  or 25% full when city shrinks.\
 ")
 
+[advance_radio]
+name        = _("Radio")
+req1        = "Flight"
+req2        = "None"
+flags       = ""
+graphic     = "a.radio"
+graphic_alt = "-"
+;helptext    =
+
 [advance_road_building]
 name        = _("Road Building")
 req1        = "None"
diff --git a/data/granularity/units.ruleset b/data/granularity/units.ruleset
index c4cd812ab6..2bda7f7dd1 100644
--- a/data/granularity/units.ruleset
+++ b/data/granularity/units.ruleset
@@ -741,12 +741,48 @@ helptext      = _("\
 Bounty Hunters are able to capture all enemy units.\
 ")
 
+[unit_biplane]
+name          = _("Biplane")
+class         = "Air"
+reqs          =
+  { "type", "name", "range"
+    "Tech", "Flight", "Player"
+  }
+obsolete_by   = "None"
+graphic       = "u.biplane"
+graphic_alt   = "u.fighter"
+sound_move    = "m_biplane"
+sound_move_alt = "m_generic"
+sound_fight   = "f_biplane"
+sound_fight_alt = "f_generic"
+build_cost    = 10000
+pop_cost      = 0
+attack        = 50
+defense       = 50
+hitpoints     = 20
+firepower     = 1
+move_rate     = 100
+bombard_rate  = 8
+vision_radius_sq = 2
+transport_cap = 0
+fuel          = 0
+uk_happy      = 0
+uk_shield     = 100
+uk_food       = 0
+uk_gold       = 0
+tp_defense    = "Alight"
+;flags         =
+;roles         =
+helptext      = _("\
+Unit capable of recon missions.\
+")
+
 [unit_bomber]
 name          = _("Bomber")
 class         = "Air"
 reqs          =
   { "type", "name", "range"
-    "Tech", "Flight", "Player"
+    "Tech", "Radio", "Player"
   }
 obsolete_by   = "None"
 graphic       = "u.bomber"
-- 
2.53.0

