TUTORIAL: Explorer with new actions: Build Watchtower and Convert to Raft

Contribute, display and discuss rulesets and modpacks for use in Freeciv here.
Post Reply
leo.priori
Posts: 22
Joined: Mon Jan 01, 2024 3:22 am

TUTORIAL: Explorer with new actions: Build Watchtower and Convert to Raft

Post by leo.priori »

Freeciv version:
* 3.2 (but the script can be run on older versions)

Level needed -
* Ruleset: Beginner
* Scipt LUA: None

After seeing this new ability in civ7, I thought, what a great idea for our explorer, so why not?
The new actions are:
1) Build Watchtower
2) Convert Unit (River) (the game's default "Convert Unit" action)
3) Convert Unit (Land or Sea) ("User Action 1" action)

Unlike the game's default action, the "User Action 1" restores all of the unit's hit points!
I believe that some units, like the Explorer, don't unbalance the game if they keep jumping around in the sea recovering every time they get hurt!
But of course, that's up to you!
At the end of this post I provide 3 rulesets with the new explorer configured for you to test.

So let's begin!

1) Actions.ruleset file
Copy and paste the following code into the actions.ruleset file, in the [actions] sector.

Code: Select all

ui_name_convert_unit = _("%sConvert Unit (River)%s")

ui_name_user_action_1                = _("%sConvert Unit (Land or Sea)%s")
user_action_1_target_kind            = "Tile"
user_action_1_min_range              = 0
user_action_1_max_range              = 1
user_action_1_actor_consuming_always = FALSE

ui_name_user_action_2                = _("Build Watch%stower (1 turn without movement)%s")
user_action_2_target_kind            = "Tile"
user_action_2_min_range              = 0
user_action_2_max_range              = 0
user_action_2_actor_consuming_always = FALSE

Now paste the following code at the end of the same file (actions.ruleset).

Code: Select all

; ----------------------------------------------------------------------------------------------------
; CONVERT UNIT
; ----------------------------------------------------------------------------------------------------
[actionenabler_convert_unit_anywhere]
action        = "Convert Unit"
actor_reqs    =
    { "type",         "name",                  "range", "present"
      ; "Tech", "Seafaring",                     "Player", TRUE
      "MinMoveFrags", "1",                     "Local", TRUE
      "UnitState",    "Transported",           "Local", FALSE
      "Extra",        "Watchtower_READY",      "Tile",  FALSE
    }

; ----------------------------------------------------------------------------------------------------
; USER ACTION 1 - Convert Unit
; ----------------------------------------------------------------------------------------------------
; Explorer
[actionenabler_user_action_convert_explorer]
action        = "User Action 1"
actor_reqs    =
    { "type",         "name",                 "range", "present"
      "UnitType",     "Explorer",             "Local", TRUE
      "MinMoveFrags", "1",                    "Local", TRUE
      "Extra",        "Watchtower_READY",      "Tile",  FALSE
    }
target_reqs   =
    { "type",             "name",    "range", "present"
      "TerrainClass",     "Oceanic", "Tile",  TRUE
      "Extra",            "River",   "Tile",  FALSE
    }
; Raft
[actionenabler_user_action_convert_raft]
action        = "User Action 1"
actor_reqs    =
    { "type",         "name",    "range", "present"
      "UnitType",     "Raft",    "Local", TRUE
      "MinMoveFrags", "1",       "Local", TRUE
    }
target_reqs   =
    { "type",             "name",    "range", "present"
      "TerrainClass",     "Land",    "Tile",  TRUE
      "Extra",            "Hut",     "Tile",  FALSE
      "Extra",            "River",   "Tile",  FALSE
    }

; ----------------------------------------------------------------------------------------------------
; USER ACTION 2 - Watchtower
; ----------------------------------------------------------------------------------------------------
[actionenabler_user_action_2_explorer]
action        = "User Action 2"
actor_reqs    =
    { "type",         "name",      "range", "present"
      "UnitType",     "Explorer",  "Local", TRUE
      "MinMoveFrags", "1",         "Local", TRUE
    }
target_reqs   =
    { "type",          "name",                 "range", "present"
      "TerrainClass",  "Land",                 "Tile",  TRUE
      "Extra",         "Watchtower_BEGIN",     "Tile",  FALSE
      "Extra",         "Watchtower_READY",     "Tile",  FALSE
    }


2) Effects.ruleset file
Copy and paste the following code into the end of the effects.ruleset file.

Code: Select all

; ----------------------------------------------------------------------------------------------------
; USER ACTION EFFECTS - Build Watchtower
; ----------------------------------------------------------------------------------------------------
[effect_action_success_user_action_2]
type    = "Action_Success_Actor_Move_Cost"
value   = 65535
reqs    =
    { "type",   "name",               "range", "quiet"
      "Action", "User Action 2", "Local", TRUE
    }

[effect_vision_watchtowers]
type    = "Unit_Vision_Radius_Sq"
value   = 25
reqs    =
    { "type", "name", "range"
      "Extra", "Watchtower_READY", "Tile"
      "UnitType", "Explorer", "Local"
    }


3) Terrain.ruleset file
Copy and paste the following code into the end of the terrain.ruleset file.

Code: Select all

; ----------------------------------------------------------------------------------------------------
; EXPLORER - WATCHTOWER
; ----------------------------------------------------------------------------------------------------
[extra_watchtower_begin]
name         = _("Watchtower BEGIN")
rule_name    = "Watchtower_BEGIN"
category     = "Bonus"
graphic      = "base.outpost"
graphic_alt  = "extra.ruins"
activity_gfx = "None"
rmact_gfx    = "None"
buildable    = FALSE
helptext     = ""

[extra_watchtower_ready]
name           = _("Watchtower READY")
rule_name    = "Watchtower_READY"
category     = "Bonus"
graphic      = "base.outpost"
graphic_alt  = "extra.ruins"
activity_gfx = "None"
rmact_gfx    = "None"
buildable    = FALSE
helptext     = ""


4) Units.ruleset file
This raft is a copy of the Trireme from the civ2civ3 ruleset with the ability to transport removed.
Adjust it to your ruleset.
Copy and paste the following code into the end of the units.ruleset file.

Code: Select all

; ----------------------------------------------------------------------------------------------------
; RAFT
; ----------------------------------------------------------------------------------------------------
[unit_raft]
name          = _("Explorer Raft")
rule_name     = "Raft"
convert_to    = "Explorer"
class         = "Trireme"
reqs          =
  { "type", "name", "range"
    "Tech", "Seafaring", "Player"
  }
graphic       = "u.feluca"
graphic_alt   = "u.trireme"
graphic_alt2  = "u.trireme"
sound_move    = "m_trireme"
sound_move_alt = "m_generic"
sound_fight   = "f_trireme"
sound_fight_alt = "f_generic"
build_cost    = 30
pop_cost      = 0
attack        = 0
defense       = 1
hitpoints     = 10
firepower     = 1
move_rate     = 6
vision_radius_sq = 10
transport_cap = 0
fuel          = 0
uk_happy      = 0
uk_shield     = 1
uk_food       = 0
uk_gold       = 1
tp_defense    = "Alight"
flags         = "BadCityDefender", "HasNoZOC", "Provoking", "NonMil"
roles         = "Explorer", "ExplorerStartUnit"
helptext      = _("\
The Raft can travel on rivers, but it may not enter deep ocean\
 tiles. It cannot attack other ships, though it\
 may still defend itself when attacked.\
")

Edit your Explorer unit by adding the “convert_to” property

Code: Select all

[unit_explorer]
name          = _("Explorer")
...
convert_to    = "Raft"
...

5) Last but not least, the script.lua file
Copy and paste the following code into the end of the script.lua file.

Code: Select all


-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
function action_finished_unit_callback(action, result, actor, target)
  if actor then
    local nmAction = action:rule_name()
    if nmAction == 'User Action 1' then
      run_convert_unit(actor, target)
    elseif nmAction == 'User Action 2' then
      run_watchtower(actor, target)
    end
  end
  return false
end
-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
function run_watchtower(actor, target)
  local has_begin = actor.tile:has_extra('Watchtower_BEGIN')
  local has_ready = actor.tile:has_extra('Watchtower_READY')
  if not has_begin and not has_ready then
    actor.tile:create_extra('Watchtower_BEGIN')
    notify_watchtower_begin(actor)
  end
end
-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
function has_unit_type_name(unit, utype_name)
  return (unit.utype.id == find.unit_type(utype_name).id)
end
-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
function run_convert_unit(actor, target)
  local unit_new = nil
  local moves_left = 0
  if has_unit_type_name(actor, 'Explorer') then
    unit_new = find.unit_type('Raft')
  elseif has_unit_type_name(actor, 'Raft') then
    unit_new = find.unit_type('Explorer')
  end
  if unit_new and unit_new:can_exist_at_tile(target) then
    actor.owner:create_unit(target, unit_new, 0, actor:get_homecity(), moves_left)
    actor:kill('disbanded', actor.owner)
  end
end
-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
function player_phase_begin_callback(player)
  services_watchtowers(player)
end
-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
function services_watchtowers(player)
  for unit in player:units_iterate() do
    local unit_type = unit.utype:rule_name()
    if unit_type == 'Explorer' then
      if unit.tile:has_extra('Watchtower_BEGIN') then
        unit.tile:remove_extra('Watchtower_BEGIN')
        unit.tile:create_extra('Watchtower_READY')
        unit:movement_disallow()
        notify_watchtower_ready(unit)
      elseif unit.tile:has_extra('Watchtower_READY') then
        unit.tile:remove_extra('Watchtower_READY')
        unit:movement_allow()
        notify_watchtower_done(unit)
      end
    end
  end
end
-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
function unit_loss_watchtowers(unit, player, reason)
  local unit_type = unit.utype:rule_name()
  if unit_type == 'Explorer' then
    unit.tile:remove_extra('Watchtower_BEGIN')
    unit.tile:remove_extra('Watchtower_READY')  
  end
end
-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
function notify_watchtower_begin(actor)
  notify.event(actor.owner, actor.tile, E.UNIT_BECAME_VET, 
    string.format('%s will build a watchtower at coordinates %s.',
      actor.utype:name_translation(), 
      actor.tile:link_text()))
end
-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
function notify_watchtower_ready(actor)
  notify.event(actor.owner, actor.tile, E.UNIT_BECAME_VET, 
    string.format('%s is observing the %s area. 1 turn without movement.',
      actor.utype:name_translation(), 
      actor.tile:link_text()))
end
-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
function notify_watchtower_done(actor)
  notify.event(actor.owner, actor.tile, E.UNIT_BECAME_VET, 
    string.format('%s has finished mapping the %s area. Movement is allowed.',
      actor.utype:name_translation(), 
      actor.tile:link_text()))
end
-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
function notify_conversion(actor, unit_new, target)
  notify.event(actor.owner, target.tile, E.UNIT_BUILT, 
    string.format('Unit %s was converted to %s in %s.',
      actor.utype:name_translation(), 
      unit_new:name_translation(), 
      target:link_text()) )
end
-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
signal.connect('unit_lost', 'unit_loss_watchtowers')
signal.connect('player_phase_begin', 'player_phase_begin_callback')
signal.connect('action_finished_unit_extras', 'action_finished_unit_callback')
signal.connect('action_finished_unit_self',   'action_finished_unit_callback')
signal.connect('action_finished_unit_tile',   'action_finished_unit_callback')
signal.connect('action_finished_unit_unit',   'action_finished_unit_callback')
signal.connect('action_finished_unit_units',  'action_finished_unit_callback')
signal.connect('action_finished_unit_city',   'action_finished_unit_callback')
-- ~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=

Attachments
Civ2Civ3_Ruleset_with_New_Explorer.zip
(105.86 KiB) Downloaded 11 times
Civ2_Ruleset_with_New_Explorer.zip
(89.34 KiB) Downloaded 7 times
Classic_Ruleset_with_New_Explorer.zip
(78.07 KiB) Downloaded 7 times
Post Reply