* Ruleset: Basics
* Scipt LUA: none, 0, nadica
Hey guys! Firstly, I want to thank all the developers, especially cazfi and Ignatus who helped me create the units I wanted and also make this tutorial possible.
Based on their idea, it is possible to make several units with different actions.
Their idea is simple, use extras (extra terrain) as counters and flags.
This way it is possible to count how many charges each unit spent.
To do this, the unit must be Unique ("Unique" flag).
This is because it is not possible to associate each unit with an extra.
So if the unit has the "Unique" flag, or if it is possible to create a unit per city, it is then possible to store the counter (unit charges) on the land of its hometown.
So, with a little scripting in Lua, i could make the following units:
Actions in cities with varied effects:
Marshall, Clown, TaxMen, Cleaners, General and Constructor.
Actions on units (using "Heal Unit" and "Heal Unit 2")
Healer, Doctor, Carpenter and Mechanic
Actions on tiles
Archaeologist (just like civ6) (my favorite one)
It is possible to control the effects in cities for several turns.
Different effects in each turn or in a range of turns.
Unfortunately I had to stop because I discovered the limit of 45 flags. So sad
So I want to share my script in LUA and how to use with your own units.
How it works:
1) The unit perform the action to a city, unit or a tile
2) If action is successful, the script places an extra in his hometown (pay one charge);
3) If the charges run out, the unit disbands;
4) If the unit is in the city with a specific building for a turn without moving, recharge completely.
Our example (Doctor) will have 5 charges, and can recharge them in a city with Temples.
So, briefly we need:
a) The unit
- Create 1 flag. Example: "Healer";
- Create 1 unit. Example: "The Doctors".
b) The charges
- Create 4 extras to control the charges (5 charges)
c) The effects
- Configure the effects.
d) The game
- Configure unit actions.
e) The script
- Copy, paste and cross your fingers.
To take a ruleset as a example, copy the entire classic folder and the classic.serv file
(usually located in /INSTALLED_FOLDER/share/freeciv/ ) to your own rules folder (usually (In linux) : /home/username/.freeciv/3.1)
I used 3.1 version, and I DONT believe that other previous versions (like 3.0) should work.
This is due to the fact that the "action_finished_unit_unit" function is from version 3.1
Lets begin!
a) The unit
Open the file units.ruleset, put the flag "Healer" in the "flags" property, right below "Bomber".
File: units.ruleset
Code: Select all
flags =
{ "name", "helptxt"
...
...
_("Bomber"), _("Bad at attacking Fighters")
_("Healer")
}
Change the "tech_req" property and the "obsolete_by" property to "None".
Add the "Healer" and "Unique" flags in the end of the "flags" property.
Comment the line that contains the "roles" property (add a semicolon at the beginning of the line).
File: units.ruleset
Code: Select all
; ----------------------------------------
; UNIT The Doctors (copied from Explorer)
; ----------------------------------------
[unit_doctors]
name = _("The Doctors")
tech_req = "None"
obsolete_by = "None"
...
...
flags = "IgTer", "IgZOC", "NonMil", "HasNoZOC", "Healer", "Unique"
; roles = "Explorer", "ExplorerStartUnit"
...
...
; ----------------------------------------
Now let's create the charges for the unit.
In our example, we need 4 new extras to control its charges.
The rule is simple, 5 charges = 4 new extras. If you want 10 charges = 9 new extras
ATTENTION:
The "rule_name" property must start with the same name as the flag.
And end with "_CHARGE_1" or "_CHARGE_2" or "_CHARGE_3", etc...
Open the terrain.ruleset file and place at the end of it:
File: terrain.ruleset
Code: Select all
; ----------------------------------------
; DOCTOR - CHARGES
; (5 charges => "_CHARGE_1" until "_CHARGE_4" )
; ----------------------------------------
[extra_healer_charges_1]
name = _("Doctor used charge 1")
rule_name = "Healer_CHARGE_1"
category = "Bonus"
graphic = "extra.ruins"
activity_gfx = "None"
rmact_gfx = "None"
buildable = FALSE
helptext = ""
; ----------------------------------------
[extra_healer_charges_2]
name = _("Doctor used charge 2")
rule_name = "Healer_CHARGE_2"
category = "Bonus"
graphic = "extra.ruins"
activity_gfx = "None"
rmact_gfx = "None"
buildable = FALSE
helptext = ""
; ----------------------------------------
[extra_healer_charges_3]
name = _("Doctor used charge 3")
rule_name = "Healer_CHARGE_3"
category = "Bonus"
graphic = "extra.ruins"
activity_gfx = "None"
rmact_gfx = "None"
buildable = FALSE
helptext = ""
; ----------------------------------------
[extra_healer_charges_4]
name = _("Doctor used charge 4")
rule_name = "Healer_CHARGE_4"
category = "Bonus"
graphic = "extra.ruins"
activity_gfx = "None"
rmact_gfx = "None"
buildable = FALSE
helptext = ""
; ----------------------------------------
Now open the file effects.ruleset.
Copy and paste all of it at the end of the file.
The first two effects are to control the movement after the action is successful.
Both units lose movement after successful action.
The next effect is to stipulate how much the unit will recover.
In this case 50%
And the last one is "User_Effect_1", which controls whether it can recharge
File: effects.ruleset
Code: Select all
; ----------------------------------------
; EFFECTS HEALER
; ----------------------------------------
[effect_action_success_heal_unit_actor]
type = "Action_Success_Actor_Move_Cost"
value = 65535
reqs = {
"type", "name", "range", "present"
"Action", "Heal Unit", "Local", TRUE
}
; ----------------------------------------
[effect_action_success_heal_unit_target]
type = "Action_Success_Target_Move_Cost"
value = 65535
reqs = {
"type", "name", "range", "present"
"Action", "Heal Unit", "Local", TRUE
}
; ----------------------------------------
[effect_action_heal_unit_recover]
type = "Heal_Unit_Pct"
value = -50
reqs = {
"type", "name", "range", "present"
"Action", "Heal Unit", "Local", TRUE
}
; ----------------------------------------
[effect_action_heal_unit_recharge]
type = "User_Effect_1"
value = 1
reqs = {
"type", "name", "range", "present"
"UnitState", "MovedThisTurn", "Local", FALSE
"MinMoveFrags", "1", "Local", TRUE
"Extra", "Healer_CHARGE_1", "Local", TRUE
"UnitFlag", "Healer", "Local", TRUE
"CityTile", "Center", "Local", TRUE
"Building", "Temple", "City", TRUE
}
; ----------------------------------------
Now, in the game.ruleset file, we cannot place the following codes at the end of the file as we did before.
We need to put in specific places that will be detailed below.
This next script should be placed inside the [actions] property.
Add the following line right below the "Keep Moving" action
ui_name_heal_unit = _("%sHEAL THIS UNIT%s")
File: game.ruleset
Code: Select all
...
...
...
; /* TRANS: Regular _Move (100% chance of success). */
ui_name_unit_move = _("%sKeep moving%s")
ui_name_heal_unit = _("%sHEAL THIS UNIT%s")
...
...
...
File: game.ruleset
Code: Select all
...
...
...
;
; */ <-- avoid gettext warnings
; ----------------------------------------
; ACTION HEAL UNIT
; ----------------------------------------
[actionenabler_heal_unit]
action = "Heal Unit"
actor_reqs =
{"type", "name", "range", "present"
"UnitFlag", "Healer", "Local", TRUE
"MinMoveFrags", 1, "Local", TRUE
"DiplRel", "Armistice", "Local", FALSE
"DiplRel", "War", "Local", FALSE
"DiplRel", "Cease-fire", "Local", FALSE
"DiplRel", "Peace", "Local", FALSE
"DiplRel", "Never met", "Local", FALSE
}
target_reqs =
{"type", "name", "range", "present"
"MaxUnitsOnTile", 1, "Local", TRUE
}
; ----------------------------------------
[actionenabler_sabotage_city]
action = "Sabotage City"
...
...
...
Last but not least, the LUA scripts.
Copy everything and paste it at the end of the script.lua file
File: script.lua
Code: Select all
--------------------------------------------------------------------------------
-- Units Flags and the charging system.
-- Place here the units that will use the charging system and how many charges each one has.
-- Remember! Only units that also have the "Unique" flag.
__flagsWithCharges = {
['Healer'] = 5, -- Unit: The Doctor with 5 charges
-- ['ReduceCrime'] = 3, -- Unit: The Marshall with 3 charges (EXAMPLE)
-- ['Happiness'] = 2, -- Unit: The Clown with 2 charges (ANOTHER EXAMPLE)
}
--------------------------------------------------------------------------------
-- This script is adjusted for actions on units.
-- If the action is in the city or on the tile, the call is similar to the one below
--------------------------------------------------------------------------------
function action_finished_unit_unit_callback(action, result, actor, target)
if action:rule_name() == 'Heal Unit' and result == true then
manage_charges(actor)
end
end
signal.connect('action_finished_unit_unit', 'action_finished_unit_unit_callback')
--------------------------------------------------------------------------------
function unit_built_callback(unit, city)
local nmFlag = getFlagUnitByExtras(unit, __flagsWithCharges)
if nmFlag ~= '' then
notify_charges_unit_built(unit, nmFlag)
end
return false
end
signal.connect('unit_built', 'unit_built_callback')
--------------------------------------------------------------------------------
function player_phase_begin_callback(player, new_phase)
notify_charges_remaining_all_units(player)
end
signal.connect('player_phase_begin', 'player_phase_begin_callback')
--------------------------------------------------------------------------------
function player_alive_phase_end_callback(player)
manage_charges_recharge(player)
end
signal.connect('player_alive_phase_end', 'player_alive_phase_end_callback')
--------------------------------------------------------------------------------
-- CHARGING SYSTEM
--------------------------------------------------------------------------------
function manage_charges(actor)
-- check if the unit use the charging system
local nmFlag = ''
nmFlag = getFlagUnitByExtras(actor, __flagsWithCharges)
-- if does, run the function 'manage_charges_actor'
if nmFlag ~= '' then
local disbanded = manage_charges_actor(actor, nmFlag)
-- if run out charges, disband unit
if disbanded == true then
actor:kill('disbanded', actor.owner)
end
end
end
--------------------------------------------------------------------------------
function manage_charges_actor(actor, nmFlag)
local city = actor:get_homecity()
local disbanded = false
if city then
local nmFlagFinal = ''
local nmFlagFinalC = ''
local nmFlagFinalT = ''
local hasExtraC = false
local hasExtraT = false
local totalCharges = __flagsWithCharges[nmFlag]
nmFlagFinal = nmFlag .. '_CHARGE_'
nmFlagFinalC = nmFlagFinal .. '1'
nmFlagFinalT = nmFlagFinal .. totalCharges - 1
-- Is it the first charge or still rolling?
hasExtraC = city.tile:has_extra(nmFlagFinalC)
-- Is it in the end? (if it is, so it will be used and will run out)
hasExtraT = city.tile:has_extra(nmFlagFinalT)
if hasExtraC then
if hasExtraT then
-- end of charges (remove all extras)
rmvExtrasTile(nmFlagFinal, totalCharges, city.tile)
notify_charges_run_out(actor)
disbanded = true
else
-- pay one more charge (add one more extra)
addExtrasTile(nmFlagFinal, totalCharges, city.tile)
notify_charges_used(actor)
end
else
-- begin the counter (first charge used)
city.tile:create_extra(nmFlagFinalC)
notify_charges_first(actor)
end
if disbanded == false then
-- show how many charges left
notify_charges_show(actor, nmFlag, totalCharges)
end
end
return disbanded
end
--------------------------------------------------------------------------------
function manage_charges_recharge(player)
local txtNacao = player.nation:name_translation()
if txtNacao ~= "Animal Kingdom" and
txtNacao ~= "Barbarian" and
txtNacao ~= "Pirate" then
local unit
for unit in player:units_iterate() do
-- check if the unit use the charging system
local nmFlag = getFlagUnitByExtras(unit, __flagsWithCharges)
if nmFlag ~= '' then
local recharge = 0
-- Check if the effect 'User_Effect_1' returns POSITIVE (1).
-- This means that the unit is in the city with the Temple
-- and has not moved this turn
recharge = effects.unit_bonus(unit, unit.owner, 'User_Effect_1')
if recharge > 0 then
local city = unit:get_homecity()
if city then
-- remove all the extras from the homecity
local totalCharges = __flagsWithCharges[nmFlag]
rmvExtrasTile(nmFlag .. '_CHARGE_', totalCharges, city.tile)
notify_charges_recharge(unit, nmFlag)
end
end
end
end
end
end
--------------------------------------------------------------------------------
function getFlagUnitByExtras(unit, extras)
local nmFlag
local total
local nmFlag_final = ''
for nmFlag, total in pairs(extras) do
if unit.utype:has_flag(nmFlag) then
nmFlag_final = nmFlag
break
end
end
return nmFlag_final
end
--------------------------------------------------------------------------------
function addExtrasTile(nmFlag, total, tile)
local pos
for pos = 1, total do
if not tile:has_extra(nmFlag .. pos) then
tile:create_extra(nmFlag .. pos)
return pos
end
end
end
--------------------------------------------------------------------------------
function rmvExtrasTile(nmFlag, total, tile)
local pos
for pos = 1, total do
if tile:has_extra(nmFlag.. pos) then
tile:remove_extra(nmFlag .. pos)
end
end
end
--------------------------------------------------------------------------------
function getChargesRemaining(actor, nmFlag, totalCharges)
local pos = 0
local total = 0
local pos_final = 0
local city = actor:get_homecity()
if city then
for pos = totalCharges, 1, -1 do
local hasExtra = city.tile:has_extra(nmFlag .. '_CHARGE_' .. pos)
if hasExtra then
pos_final = pos
break
end
end
end
total = totalCharges - pos_final
return total
end
--------------------------------------------------------------------------------
function notify_charges_remaining_all_units(player)
local unit
local city
local totalCharges
if player:is_human() then
for unit in player:units_iterate() do
city = unit:get_homecity()
if city then
for nmFlag, totalCharges in pairs(__flagsWithCharges) do
local useChargeSystem = unit.utype:has_flag(nmFlag)
if useChargeSystem == true then
notify_charges_show(unit, nmFlag, totalCharges)
end
end
end
end
end
end
--------------------------------------------------------------------------------
function notify_charges_run_out(actor)
local msg = 'The unit %s exhausted its charges and was disbanded.'
notify.event(actor.owner, actor.tile, E.UNIT_ACTION_ACTOR_SUCCESS, msg, actor:link_text())
end
--------------------------------------------------------------------------------
function notify_charges_used(actor)
local msg = 'The unit %s used a charge.'
notify.event(actor.owner, actor.tile, E.UNIT_ACTION_ACTOR_SUCCESS, msg, actor:link_text())
end
--------------------------------------------------------------------------------
function notify_charges_first(actor)
local msg = 'The unit %s has just used its first charge.'
notify.event(actor.owner, actor.tile, E.UNIT_ACTION_ACTOR_SUCCESS, msg, actor:link_text())
end
--------------------------------------------------------------------------------
function notify_charges_last(actor)
local msg = 'The unit %s has its LAST charge.'
notify.event(actor.owner, actor.tile, E.UNIT_ACTION_ACTOR_SUCCESS, msg, actor:link_text())
end
--------------------------------------------------------------------------------
function notify_charges_remaining(actor, charges)
local msg = 'The unit %s has %d charge(s) left.'
notify.event(actor.owner, actor.tile, E.UNIT_ACTION_ACTOR_SUCCESS, msg, actor:link_text(), charges)
end
--------------------------------------------------------------------------------
function notify_charges_recharge(actor, nmFlag)
local msg = 'The unit %s has recharged its charges. Now it has %d charge(s) left.'
notify.event(actor.owner, actor.tile, E.UNIT_ACTION_ACTOR_SUCCESS, msg, actor:link_text(),
__flagsWithCharges[nmFlag])
end
--------------------------------------------------------------------------------
function notify_charges_unit_built(actor, nmFlag)
local msg = 'The unit %s has just been built. It has %d charge(s).'
notify.event(actor.owner, actor.tile, E.UNIT_ACTION_ACTOR_SUCCESS, msg, actor:link_text(),
__flagsWithCharges[nmFlag])
end
--------------------------------------------------------------------------------
function notify_charges_show(actor, nmFlag, totalCharges)
local charges = getChargesRemaining(actor, nmFlag, totalCharges)
if charges ~= totalCharges then
if charges == 1 then
notify_charges_last(actor)
end
end
if charges ~= 1 then
notify_charges_remaining(actor, charges)
end
end
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- EDIT: add the functions below to control when the unit or the city is lost
--------------------------------------------------------------------------------
function unit_lost_callback(unit, player, reason)
local txtNacao = player.nation:name_translation()
if txtNacao ~= "Animal Kingdom" and txtNacao ~= "Barbarian" and txtNacao ~= "Pirate" then
manage_charges_unit_lost(unit)
end
end
signal.connect('unit_lost', 'unit_lost_callback')
--------------------------------------------------------------------------------
function city_transferred_callback(city, loser, winner, reason)
manage_charges_city_lost(city)
end
signal.connect('city_transferred', 'city_transferred_callback')
--------------------------------------------------------------------------------
function manage_charges_city_lost(city)
local nmFlag = ''
for nmFlag, total in pairs(__flagsWithCharges) do
if city.tile:has_extra(nmFlag .. '_CHARGE_1') then
rmvExtrasTile(nmFlag .. '_CHARGE_', __flagsWithCharges[nmFlag], city.tile)
end
end
end
--------------------------------------------------------------------------------
function manage_charges_unit_lost(unit)
local nmFlag = ''
nmFlag = getFlagUnitByExtras(unit, __flagsWithCharges)
if nmFlag ~= '' then
local city = unit:get_homecity()
if city then
if city.tile:has_extra(nmFlag .. '_CHARGE_1') then
rmvExtrasTile(nmFlag .. '_CHARGE_', __flagsWithCharges[nmFlag], city.tile)
end
end
end
end
--------------------------------------------------------------------------------
So a made my own tileset(copied from hexemplio of course), create a LOT of buildings, and grab a LOT of images from internet about artifacts, fossil, etc. Soooo cool!
I hope this helps someone
See ya