Scripts from the scenario The Shadow Vales, which is shared at this link:
https://forum.freeciv.org/f/viewtopic.php?p=109990
The one thing I am most happy about having changed in scripting within this scenario is that I stopped using tables and started using "field strings". What a pain in the butt it is to use tables, backup the tables to strings since tables are lost on reloads, then restore from the string back to the tables. Why not just use a string the whole time? So I did, and it works well! Here's what a "field string" looks like and a simple Lua routine to extract a value.
Below is the maxCitiesFields string. Field #'s are from 0 because player 0 has the first field. Player zero has held a maximum of 16 cities at the start of any turn, so the first field is "16". Player's 3 and 4 have each had a maximum of 18 cities. This method is simple to use and since the field is a string rather than a table, no backing-up and restoring the values after reloads is required at all.
maxCitiesFields="16|16|17|18|18|16|16|15|50|5"
Code: Select all
function s_extract_field( combinedFieldsString, fieldNumber )
local count = 0
for field in string.gmatch( combinedFieldsString, "[^|]+" ) do
if count == fieldNumber then return field end
count = count + 1
end
end
My next favorite change is a consistent construct used to identify a player (to the human, in text output.) It includes the player number, nation name, and the ruler name so that those who can't recall which player is which ( namely, ME! ) see the #, tribe, and ruler together which greatly increases the liklihood of recognizing the player/tribe/ruler.
Code: Select all
function s_number_nation_ruler_name( player )
local playerName
if player and player ~= "" then
playerName = string.sub( tostring( player.nation ), 14, -2 )
playerName = playerName:gsub( "^.-%s", "", 1 )
playerName = ( "#" .. math.floor( player.id ) .. " " ..
playerName .. " ( " .. player.name .. " )"
)
return playerName
end
return false
end
^ Ouput is consistently like this "
#1 Mongol (Genghis)" instead of sometimes just a player number, sometimes just a tribe name, or sometimes just a ruler name.
Another one which was particularly helpful is below, it is used for Suburban Sprawl when creating roads outward from cities, and for determining to whom the bounty for a Barbarian Leader will be paid (in the scenario), and for Weatherizer_Winds when units are blown away to a distance corresponding to wind strength. It is also used in a routine to check whether a given tile is next to a specified type of terrain (as in a Mountains tile next to any Grassland tile.)
Code: Select all
function s_list_ring_of_tileIDs_around( centerTile, ringNumber )
local tileIDs = {}
local x = centerTile.x
local y = centerTile.y
for tile in ( centerTile ):square_iterate( ringNumber ) do
if math.abs( tile.x - x ) == ringNumber or math.abs( tile.y - y ) == ringNumber then
tileIDs[ #tileIDs + 1 ] = tile.id
end
end
return tileIDs
end
Next is one to check whether a city exists within citymindist of a given tile. I tried using the Freeciv Lua function "(unit):is_on_possible_city_tile" but it does not appear to take into account cities within citymindist, but rather only whether the tile terrain would accommodate a city. Next was "(tile):city_exists_within_city_radius and (tile):city_exists_within_max_city_map -- neither of which actually indicated whether or not a city was within citymindist of a given tile. I also tried various uses of (city):map_sq_radius, but became annoyed about having to manually screen-out some tiles (corners just outside city radius and the city tile itself) every time.
Code: Select all
function s_is_a_city_within_citymindist( tile )
local citymindist = tonumber( server.setting.get( "citymindist" ) )
for h = ( tile.x - ( citymindist - 1 ) ), ( tile.x + ( citymindist - 1 ) ), 1 do
for v = ( tile.y - ( citymindist - 1 ) ), ( tile.y + ( citymindist - 1 ) ), 1 do
if find.tile( h, v ):city() then return true end
end
end
return false
end
Suburban Sprawl gradually produces roads emanating outward from cities. The computer-controlled tribes won't produce workers for quite some time in The Shadow Vales, so I wrote this routine to create roads to make unit travel faster. This one is quite portable and can use signal.connect with turn_started ( Freeciv 2 ) or turn_begin (Freeciv 3), or just call it from another per turn routine. This does also need the "s_list_ring_of_tileIDs_around" routine which is posted above. Also the settings are flexible as to how often it will create a road segment per city, and also how far out from the city the roads will go (it works out to at least 10 rings which covers the entire map with roads by about turn 100, provided the frequnecy of creating a road segment is 1 per turn.)
Code: Select all
function s_suburban_sprawl( turn, year )
local output="[ b ]Suburban sprawl creates roads emanating from cities[ /b ]"
for player in players_iterate() do
local roads = ""
local playerName = s_number_nation_ruler_name( player )
for city in player:cities_iterate() do
if random( 1, 100 ) > ( city.size + 1 ) * 10 then
goto loop_exit
end
local tile
local tileIDs = {}
for ring = 1, 4, 1 do
tileIDs = s_list_ring_of_tileIDs_around( city.tile, ring )
for i = 1, #tileIDs, 1 do
local t = tileIDs[ i ]
local tile = find.tile( t )
if tile.terrain:class_name() == "Land"
and tile:has_extra( "Road" ) == false
and tile:has_extra( "River" ) == false
and tile.terrain.id ~= find.terrain( "Inaccessible" ).id
then
edit.create_extra( tile, "Road" )
roads = ( roads .. math.floor( tile.x ) .. "," .. math.floor( tile.y ) .. " " )
goto loop_exit
end
::offmap::
end
end
::loop_exit::
end
if roads ~= "" then
if output ~= "" then
chat( "\t" .. output )
output=""
end
chat( "\t\t" .. playerName .. " at " .. roads )
end
end
chat()
end
Roving Rivers Creates random rivers from a fixed start point ( the isolated Lake tile in each vale ) to a fixed shoreline ( the outer edges of the map ). In "The Shadow Vales", the routine is called once every 17 turns. When called it removes old rivers that it had created, and randomly plots new rivers -- while also adding tile irrigation and destroying roads on flat land tiles. This routine is a little less portable, but provided the scenario has fixed start tiles for the source of the river, and fixed shore lines, it wouldn't be too hard to port it into another scenario.
Code: Select all
function s_roving_rivers( turn, year )
local valeBoundsStartX = { 0, 22, 44, 0, 0, 48, 0, 22, 44 }
local valeBoundsEndX = { 16, 38, 60, 12, 0, 60, 16, 38, 60 }
local valeBoundsStartY = { 0, 0, 0, 22, 0, 22, 44, 48, 44 }
local valeBoundsEndY = { 16, 12, 16, 38, 0, 38, 60, 60, 60 }
local valeLakesX = { 12, 30, 49, 12, 0, 48, 12, 30, 50 }
local valeLakesY = { 10, 12, 10, 30, 0, 30, 50, 48, 49 }
if _G.riverPathsFieldString == nil then
_G.riverPathsFieldString = ""
else
for field in string.gmatch( _G.riverPathsFieldString, "[^|]+" ) do
local tile = find.tile( field )
edit.remove_extra( tile, "River" )
end
_G.riverPathsFieldString = ""
end
local vale, coast, atShoreline, x, y, tile
for vale = 1, 9, 1 do
if vale == 1 then
coast = "x-"
if random( 1, 2 ) == 1 then
coast = "y-"
end
elseif vale == 2 then
coast = "y-"
elseif vale == 3 then
coast = "x+"
if random( 1, 2 ) == 1 then
coast = "y-"
end
elseif vale == 4 then
coast = "x-"
elseif vale == 5 then
goto vale_done
elseif vale == 6 then
coast = "x+"
elseif vale == 7 then
coast = "x-"
if random( 1, 2 ) == 1 then
coast = "y+"
end
elseif vale == 8 then
coast = "y+"
else
coast = "x+"
if random( 1, 2 ) == 1 then
coast = "y+"
end
end
x, y = valeLakesX[ vale ], valeLakesY[ vale ]
atShoreline = 0
repeat
local ok = 0
repeat
local h, v = 0, 0
if coast == "x-" then
if random( 1, 2 ) == 1 then
h = h - 1
else
if random( 1, 2 ) == 1 then
v = v + 1
else
v = v - 1
end
end
elseif coast == "x+" then
if random( 1, 2 ) == 1 then
h = h + 1
else
if random( 1, 2 ) == 1 then
v = v + 1
else
v = v - 1
end
end
elseif coast == "y-" then
if random( 1, 2 ) == 1 then
v = v - 1
else
if random( 1, 2 ) == 1 then
h = h + 1
else
h = h - 1
end
end
else
if random( 1, 2 ) == 1 then
v = v + 1
else
if random( 1, 2 ) == 1 then
h = h + 1
else
h = h - 1
end
end
end
tile = find.tile( x + h, y + v )
if tile.terrain:rule_name() ~= "Mountains"
and tile.terrain:rule_name() ~= "Inaccessible" then
x, y = x + h, y + v
ok = 1
end
until ok > 0
if x < 1 or x > 59 or y < 1 or y > 59 then goto vale_done end
if tile.terrain:rule_name() ~= ( "Lake" ) then
edit.create_extra( tile, "River" )
end
if tile.terrain:rule_name() ~= "Lake"
and tile.terrain:rule_name() ~= "Jungle"
and tile.terrain:rule_name() ~= "Swamp"
and tile.terrain:rule_name() ~= "Forest"
and not tile:city() then
edit.create_extra( tile, "Irrigation" )
end
if not tile:city()
and tile.terrain:rule_name() ~= "Forest" then
edit.remove_extra( tile, "Road" )
end
_G.riverPathsFieldString = ( _G.riverPathsFieldString .. math.floor( tile.id ) .. "|" )
if x < 2 or x > 58 or y < 2 or y > 58 then
atShoreline = 1
end
until atShoreline > 0
::vale_done::
end
_G.riverPathsFieldString = string.sub( _G.riverPathsFieldString, 1, -2 )
if game.current_turn() > 1 then
local output = "[ b ]Vale Echo News:[ /b ]\t\tMeltwater and heavy rains caused rivers to overrun their banks, washing-out roads."
if not _G.ReloadDetected then notify.event( nil, nil, E.REPORT, "__" .. output ) end
notify.event( nil, nil, E.CHAT_MSG, "\t" .. output )
end
end