New features via Lua script

Can you help improve your favourite game? Hardcore C mages, talented artists, and players with any level of experience are welcome!
Molo_Parko
Hardened
Posts: 158
Joined: Fri Jul 02, 2021 4:00 pm

New features via Lua script

Post by Molo_Parko »

I had an idea but Freeciv 2.6.4 doesn't support changing a tile's terrain. If anybody using Freeciv 3 would like a new feature via Lua script, add the code below to any scenario or saved game file (.sav).

Note: These are all designed with/for classic (original) map topology (not iso, hex, etc) and classic (original) square tiles. (And default Freeciv ruleset too, v2.6.4.)

AddMoat: This adds a moat (lake tiles) around each city when the city is built -- as though moats are included in the city specifications. Compatible with Freeciv 3+ only. There are a few options mentioned within the script itself (text after "--" are comments, not code.) This will radically change the gameplay experience of any scenario/map as far as early attacks on cities are concerned. Some moat designs will prevent city capture by any land unit at least until Marines or Paratroopers. A land-bridge tile is optional with any moat (a single land tile connecting city to land across the moat.) Without a land bridge, Map Making technology becomes a necessity for land units to cross to or from the city (or Radio for Airports later in the game.)

Code: Select all

--##############################################################################
function Lua_AddMoat(city)

	if ( AddMoat == nil ) then AddMoat=true end

	-- If this routine is disabled, just return without doing anything else
	if not ( AddMoat ) then return end
	if not ( ( string.sub(fc_version(),17,19) ) >= "2.6" ) then return end -- the commands used require at least Fc 2.6

	if ( debugLuaScripts ) then log.error("%s","AddMoat BEGIN") end

	-- Add moat to all cities
	-- Consider increasing foodbox cost to slow city growth given that towns are less vulnerable to attack

	-- Declare local variables
	local owner, moatTerrain, moatStyle, i, x, y, landBridge, topology, count, field, topoFields, h, v

	-- If needed global vars aren't set, set them
	if ( topology == nil ) or ( _G.wrapX == nil ) or ( _G.wrapY == nil )
	or ( _G.xSize == nil ) or ( _G.ySize == nil ) or ( _G.topo == nil )
	or ( _G.citymindist == nil ) then 

		topology=(server.setting.get("topology"))
		_G.xSize=tonumber(server.setting.get("xsize"))
		_G.ySize=tonumber(server.setting.get("ysize"))
		_G.dispersion=(server.setting.get("dispersion"))
		_G.citymindist=(server.setting.get("citymindist"))
		_G.topo="classic"
		_G.wrapX=false
		_G.wrapY=false
		topoFields={}

		-- Split topology field (from scenario settings) to a table
		count=1
		for field in string.gmatch(topology, "[^|]+") do
			topoFields[count]=field
			count=(count+1)
		end

		-- Evaluate topology settings
		if ( #topoFields > 0 ) then
			for i=1,#topoFields,1 do
				if ( topoFields[i] == "WRAPX" ) then _G.wrapX=true 
				elseif ( topoFields[i] == "WRAPY" ) then _G.wrapY=true 
				elseif ( topoFields[i] == "ISO" ) then _G.topo="ISO" 
				elseif ( topoFields[i] == "HEX" ) then _G.topo="HEX" end
			end
		end
	end
	if not ( _G.topo == "classic" ) then return end -- AddMoat currently only supports classic topology

	-- Declare global function
	function _G.is_XY_map_location_valid(x,y)
		if ( _G.wrapX and _G.wrapY ) 
		or ( _G.wrapX and ( y >= 0 ) and ( y < _G.ySize ) ) 
		or ( _G.wrapY and ( x >= 0 ) and ( x < _G.xSize ) ) 
		or ( y >= 0 and y < _G.ySize and x >= 0  and x < _G.xSize ) then
			return true
		else
			return false
		end
	end

	x=((city).tile).x y=((city).tile).y
	owner=tostring((city).owner)
	moatTerrain=find.terrain("Lake")

	-- Moat Styles:
	-- 1 is next to city, 2 at city radius, 3 is outside city radius, 
	-- 4 is 5x5 with corners outside city radius
	-- 5 is 5x3 horizontal
	-- 6 is 3x5 vertical
	moatStyle=(random(1,6)) -- or specify # such as 1 for consistent moat style on every city

	if moatStyle == 1 then
		-- ======= (C is city, # is tile changed to moat, = is unchanged tile)
		-- ==###==
		-- ==#C#==
		-- ==###==
		-- =======
		-- Next to city, 8 tiles, full moat (no land connection - boat, aircraft, or airport required to/from city)
		-- landBridge specifies which tile to leave for land access, or none if greater than # in tile list
		-- Note that on moatStyle=1, a diagonal tile for landBridge leaves open city blindspot attack by fast attack units
		h={-1,-1,-1, 0, 0, 1, 1, 1}
		v={-1, 0, 1,-1, 1,-1, 0, 1}
		landBridge=(random(1,9)) -- for 8 possible positions and 9=none
	elseif moatStyle == 2 then
		-- ==###==
		-- =##=##=
		-- =#=C=#=
		-- =##=##=
		-- ==###==
		-- inside edge of city radius, 16 tiles
		-- ToDo: Don't plot moat across neightboring moat (only applies if citymindist is too low)
		h={-1,-0, 1,-2,-1, 1, 2,-2, 2,-2,-1, 1, 2,-1,-0, 1}
		v={-2,-2,-2,-1,-1,-1,-1, 0, 0, 1, 1, 1, 1, 2, 2, 2}
		landBridge=(random(1,17))
	elseif moatStyle == 3 then
		-- =#####=
		-- ##===##
		-- #=====#
		-- #==C==#
		-- #=====#
		-- ##===##
		-- =#####=
		-- Outside city radius, 24 tiles
		-- ToDo: Don't plot moat across neightboring moat (only applies if citymindist is too low)
		h={-2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-3,-2, 2, 3,-3,-3,-3, 3, 3, 3,-3,-2, 2, 3}
		v={-3,-3,-3,-3,-3, 3, 3, 3, 3, 3,-2,-2,-2,-2,-1, 0, 1,-1, 0, 1, 2, 2, 2, 2}
		landBridge=(random(1,25))
	elseif moatStyle == 4 then
		-- =#####=
		-- =#===#=
		-- =#=C=#=
		-- =#===#=
		-- =#####=
		-- Within city radius + 4 corner tiles, 16 tiles
		h={-2,-2,-2,-2,-2, 2, 2, 2, 2, 2,-1, 0, 1,-1, 0, 1}
		v={-2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-2,-2,-2, 2, 2, 2}
		landBridge=(random(1,17))
	elseif moatStyle == 5 then 
		-- =======
		-- =#####=
		-- =#=C=#=
		-- =#####=
		-- =======
		-- Within city radius, 12 tiles
		h={-2,-1, 0, 1, 2,-2, 2,-2,-1, 0, 1, 2}
		v={-1,-1,-1,-1,-1, 0, 0, 1, 1, 1, 1, 1}
		landBridge=(random(1,13))
	elseif moatStyle == 6 then
		-- ==###==
		-- ==#=#==
		-- ==#C#==
		-- ==#=#==
		-- ==###==
		-- Within city radius, 12 tiles
		h={-1,-1,-1,-1,-1, 1, 1, 1, 1, 1, 0, 0}
		v={-2,-1, 0, 1, 2,-2,-1, 0, 1, 2,-2, 2}
		landBridge=(random(1,13))
	end

-- ToDo: Check if parts of moat area are already water tiles, especially if landbridge tile is water, then pick another?

	for i=1,#h,1 do
		if not (i == landBridge) then

			if _G.is_XY_map_location_valid( (h[i]+x),(v[i]+y) ) then

			moatTile=find.tile((h[i]+x),(v[i]+y))
			if not  ( moatTile.terrain.id == find.terrain("Lake").id )
			and not ( moatTile.terrain.id == find.terrain("Ocean").id )
			and not ( moatTile.terrain.id == find.terrain("Deep Ocean").id )
			and not ( moatTile.terrain.id == find.terrain("Inaccessible").id ) then

					if ( ( string.sub(fc_version(),17,19) ) >= "2.6" )
					and ( ( string.sub(fc_version(),17,19) ) < "3.0" ) then
						edit.create_extra(moatTile,"River") -- for Freeciv v2 testing purpose only
					elseif ( ( string.sub(fc_version(),17,19) ) >= "3.0" ) then
						edit.change_terrain(moatTile, moatTerrain)
					end
				end
			end
		end
	end
	if ( debugLuaScripts ) then log.error("%s","AddMoat END") end
end
signal.connect("city_built", "Lua_AddMoat")

Image


Attached file "RandoMap Lua AddMoat only" is a scenario file for Freeciv 2.6 and later with only the AddMoat Lua script included. If testing with Freeciv 2.6, river will be added to tiles where moat would be in Freeciv 3. The scenario file FairTest has all the Lua scripts included, and is attached to my latest post in this topic.

To add the script to a scenario or to an uncompressed saved game text file ( .sav ), copy and paste the script into the scenario file between the $$ (dollar signs) in the "[script] code=$$" section of the file.
[script]
code=$

-- paste script here

$
vars=$$
Attachments
RandoMap Lua AddMoat only 48x48=2k, 80% land, Classic, Flat, v1.sav.zip
(7.85 KiB) Downloaded 200 times
Last edited by Molo_Parko on Fri Nov 24, 2023 8:05 pm, edited 32 times in total.
Molo_Parko
Hardened
Posts: 158
Joined: Fri Jul 02, 2021 4:00 pm

Re: New features via Lua script

Post by Molo_Parko »

AddBuoys: places owned buoys on water tiles around city when city is built. I wanted to test whether the AI tribes would be better prepared for attacks on cities with increased awareness of foreign units near their cities. Works in Freeciv 2.6 (actually 2.2 or later, I think) and is supposed to also work in Freeciv 3 but I haven't tested that. And the result of my tests is "no, not really,"

Code: Select all

--##############################################################################
function Lua_AddBuoys(city)
	if ( AddBuoys == nil ) then AddBuoys=true end

	-- If this routine is disabled, just return without doing anything else
	if not ( AddBuoys ) then return end
	if not ( ( string.sub(fc_version(),17,19) ) >= "2.2" ) then return end
	-- ^ the commands used require at least Fc 2.2

	if ( debugLuaScripts ) then log.error("%s","AddBuoys BEGIN") end

	-- This is a technologically ridiculous experiment to help AI tribes know when to staff a city
	-- due to enemy units approaching

	-- Declare local variables
	local owner, i, x, y, h, v, checkTile, tile

	-- If needed global vars aren't set, set them
	if ( topology == nil ) or ( _G.wrapX == nil ) or ( _G.wrapY == nil )
	or ( _G.xSize == nil ) or ( _G.ySize == nil ) or ( _G.topo == nil )
	or ( _G.citymindist == nil ) then 

		topology=(server.setting.get("topology"))
		_G.xSize=tonumber(server.setting.get("xsize"))
		_G.ySize=tonumber(server.setting.get("ysize"))
		_G.dispersion=(server.setting.get("dispersion"))
		_G.citymindist=(server.setting.get("citymindist"))
		_G.topo="classic"
		_G.wrapX=false
		_G.wrapY=false
		topoFields={}

		-- Split topology field (from scenario settings) to a table
		count=1
		for field in string.gmatch(topology, "[^|]+") do
			topoFields[count]=field
			count=(count+1)
		end

		-- Evaluate topology settings
		if ( #topoFields > 0 ) then
			for i=1,#topoFields,1 do
				if ( topoFields[i] == "WRAPX" ) then _G.wrapX=true 
				elseif ( topoFields[i] == "WRAPY" ) then _G.wrapY=true 
				elseif ( topoFields[i] == "ISO" ) then _G.topo="ISO" 
				elseif ( topoFields[i] == "HEX" ) then _G.topo="HEX" end
			end
		end
	end
	if not ( _G.topo == "classic" ) then return end -- AddBuoys currently only supports classic topology

	-- Declare global function
	function _G.is_XY_map_location_valid(x,y)
		if ( _G.wrapX and _G.wrapY ) 
		or ( _G.wrapX and ( y >= 0 ) and ( y < _G.ySize ) ) 
		or ( _G.wrapY and ( x >= 0 ) and ( x < _G.xSize ) ) 
		or ( y >= 0 and y < _G.ySize and x >= 0  and x < _G.xSize ) then
			return true
		else
			return false
		end
	end

	owner=(city).owner x=((city).tile).x y=((city).tile).y

-- ToDo: Determine where placing a buoy adds to existing vision and if it doesn't then don't place it at all. Buoys on outer ring of tiles should be placed before inner ring.

	-- Four corner tiles outside city radius
	h={-2, 2,-2, 2}
	v={-2, 2, 2,-2}
	for i=1,#h,1 do
		if ( _G.is_XY_map_location_valid( ( h[i] + x ),( v[i] + y ) ) ) then

			tile=find.tile((h[i]+x),(v[i]+y))

			-- only lake tiles get buoys
			if ( (tile).terrain.id == find.terrain("Lake").id ) 
			and not ( (tile).terrain:class_name() == "Land" ) 
			and not ( (tile):city() ) 
			and not ( (tile):has_base("Buoy") ) 
			and not ( (tile):num_units() > 0 ) then 
				if ( ( string.sub(fc_version(),17,19) ) >= "2.2" ) 
				and ( ( string.sub(fc_version(),17,19) ) < "3.0" ) then
					edit.create_base(tile, "Buoy", owner)
				elseif ( ( string.sub(fc_version(),17,19) ) >= "3.0" ) then
					edit.create_owned_extra(tile, "Buoy", owner)
				end
			end
		end
	end

	-- Check 20 tiles in city radius but not the city tile itself (offset 0,0)
	h={-1,-0, 1,-2,-1, 1, 2, 0,-2, 2,-1, 1,-2,-1, 1, 2, 0,-1,-0, 1}
	v={-2,-2,-2,-1,-1,-1,-1,-1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2}
	for i=1,#h,1 do
		tile=find.tile( ( h[i] + x ),( v[i] + y ) )

		-- Prevent attempts to modify a tile off the map when wrapX/Y not enabled
		if ( _G.is_XY_map_location_valid( ( h[i] + x ),( v[i] + y ) ) ) then
			-- only lake tiles get buoys here
			if ( (tile).terrain.id == find.terrain("Lake").id ) 
			and not ( (tile).terrain:class_name() == "Land" ) 
			and not ( (tile):city() ) 
			and not ( (tile):has_base("Buoy") ) 
			and not ( (tile):num_units() > 0 ) then 
				if ( ( string.sub(fc_version(),17,19) ) >= "2.2" )
				and ( ( string.sub(fc_version(),17,19) ) < "3.0" ) then
					edit.create_base(tile, "Buoy", owner)
				elseif ( ( string.sub(fc_version(),17,19) ) >= "3.0" ) then
					edit.create_owned_extra(tile, "Buoy", owner)
				end
			end
		end
	end
	if ( debugLuaScripts ) then log.error("%s","AddBuoys END") end
end
signal.connect("city_built", "Lua_AddBuoys")
Image

Attached scenario file "RandoMap Lua AddBuoys only" for Freeciv 2.2 and later includes the AddBuoys Lua script as the only script. The FairTEST scenario file includes all the Lua scripts together, and can be downloaded from my latest post in this topic.
Attachments
RandoMap Lua AddBuoys only 48x48=2k, 80% land, Classic, Flat, v1.sav.zip
(7.56 KiB) Downloaded 224 times
Last edited by Molo_Parko on Sat Nov 11, 2023 4:40 pm, edited 31 times in total.
Molo_Parko
Hardened
Posts: 158
Joined: Fri Jul 02, 2021 4:00 pm

Re: New features via Lua script

Post by Molo_Parko »

ExtendVision for units moving onto a hill or mountain. Shows base terrain but does not remove fog (at least in v2.6.4 it doesn't.) Uses one method for Freeciv 3 and later, and a different method for Freeciv 2.4 through 2.6.x (because the first method isn't supported prior to version 3.) Extends vision 1 extra ring of tiles on a Hill, 2 extra rings on a Mountain in Freeciv 3, and about 5 extra tile rings on Mountains in Freeciv 2.6.x.

Code: Select all

--##############################################################################
function Lua_ExtendVision(unit, src_tile, dst_tile)
	if ( ExtendVision == nil ) then ExtendVision=true end

	-- If this routine is disabled, just return without doing anything else
	if not ( ExtendVision ) then return end

	-- If needed global vars aren't set, set them
	if ( topology == nil ) or ( _G.wrapX == nil ) or ( _G.wrapY == nil )
	or ( _G.xSize == nil ) or ( _G.ySize == nil ) or ( _G.topo == nil )
	or ( _G.citymindist == nil ) then 

		topology=(server.setting.get("topology"))
		_G.xSize=tonumber(server.setting.get("xsize"))
		_G.ySize=tonumber(server.setting.get("ysize"))
		_G.dispersion=(server.setting.get("dispersion"))
		_G.citymindist=(server.setting.get("citymindist"))
		_G.topo="classic"
		_G.wrapX=false
		_G.wrapY=false
		topoFields={}

		-- Split topology field (from scenario settings) to a table
		count=1
		for field in string.gmatch(topology, "[^|]+") do
			topoFields[count]=field
			count=(count+1)
		end

		-- Evaluate topology settings
		if ( #topoFields > 0 ) then
			for i=1,#topoFields,1 do
				if ( topoFields[i] == "WRAPX" ) then _G.wrapX=true 
				elseif ( topoFields[i] == "WRAPY" ) then _G.wrapY=true 
				elseif ( topoFields[i] == "ISO" ) then _G.topo="ISO" 
				elseif ( topoFields[i] == "HEX" ) then _G.topo="HEX" end
			end
		end
	end

	if not ( _G.topo == "classic" ) then return end -- ExtendVision currently only supports classic topology
	if ( debugLuaScripts ) then log.error("%s","ExtendVision BEGIN") end

	-- check to prevent looping forever
	if ( extendVisionInProgress ) then return end
	extendVisionInProgress=true

	-- Declare local variables
	local size=0, x, y, v, h, owner, tile, xsize, ysize, topology, tempUnit, highestNumber, aUnit, aUnitID

	-- Extended vision is limited to hills and mountains
	if ( (dst_tile).terrain.id == find.terrain("Hills").id ) then 
		size=2
	elseif ( (dst_tile).terrain.id == find.terrain("Mountains").id ) then 
		size=3
	end

	if size > 0 then
		x=(dst_tile).x y=(dst_tile).y
		owner=(unit).owner

		-- Uncomment the line below to receive a message list entry when your units trigger ExtendVision
		--notify.event_msg(owner,tile,0,"\tExtendVision (Lua) for unit owned by " .. tostring(owner) .. ".")

		if ( string.sub(fc_version(),17,19) >= "3.1" ) then
			for v=(0-extendVision),extendVision,1 do 
				for h=(0-size),size,1 do 
					tile=find.tile( (h+x),(v+y) )
					(tile):show(owner)
				end
			end
		elseif ( (string.sub(fc_version(),17,19) ) >= "2.4" ) then
			-- Workaround for Freeciv 2.4 through 2.6.4 (at least)

			if size == 2 then
				tempUnit=find.unit_type("Spy")
			else
				tempUnit=find.unit_type("AWACS")
			end
			edit.create_unit(owner, dst_tile, tempUnit, 0, nil, 0)
			highestNumber=0
			for aUnit in ((dst_tile):units_iterate()) do
				aUnitID=tonumber((aUnit).id)
				if (aUnitID > highestNumber) then highestNumber=aUnitID end
			end
			aUnitID=find.unit(owner,highestNumber)

			-- This applies only to the Freeciv v2.4 to v2.6.4 (at least) method
			-- Note that killing the unit will trigger unit_lost_callback
			-- Add the next line to start of unit_lost_callback (without the two hyphens --)
			-- if ( extendVisionInProgress ) then return end

			edit.unit_kill(aUnitID,"disbanded",owner)

		end
	end
	extendVisionInProgress=false
	if ( debugLuaScripts ) then log.error("%s","ExtendVision END") end
end
signal.connect("unit_moved", "Lua_ExtendVision")
Image
^ Before moving onto hill and/or mountain

Image
^ After moving onto Hill and/or Mountain in Freeciv 2.6.4.

Attached scenario file "RandoMap ExtendVision only" includes the ExtendVision Lua script (with no other Lua scripts) for testing. The "FairTEST" scenario file includes all the Lua scripts and can be downloaded from my latest post in this topic.
Attachments
RandoMap ExtendVision only 48x48=2k, 80% land, Classic, Flat, v1.sav.zip
(7.49 KiB) Downloaded 229 times
Last edited by Molo_Parko on Thu Nov 23, 2023 3:25 pm, edited 35 times in total.
Molo_Parko
Hardened
Posts: 158
Joined: Fri Jul 02, 2021 4:00 pm

Re: New features via Lua script

Post by Molo_Parko »

Military-to-Work Program : Guaranteed jobs for honorably discharged soldiers

Allows disbanding soldiers on a land tile (not a city) to retire from military life and instead take a civilian job as Workers, Explorer, or Diplomat.

Code: Select all

--##############################################################################
function Lua_MilitaryToWork(unit, player, reason)

	if ( MilitaryToWork == nil ) then MilitaryToWork=true end

	-- If this routine is disabled, just return without doing anything else
	if not ( MilitaryToWork ) then return end

	if ( debugLuaScripts ) then log.error("%s","MilitaryToWork BEGIN") end

	-- This prevents processing of disbanded/killed temp unit from ExtendVision
	if ( extendVisionInProgress ) then return end

	-- Military-to-Work Program : Guaranteed jobs for honorably discharged soldiers.
	-- Workers, Explorers, and Diplomat positions available now!

	-- To qualify, military unit must be disbanded on land tile without a city
	if not ( reason == "disbanded" ) then
		if ( debugLuaScripts ) then log.error("%s","MilitaryToWork: not a disbanded unit") end
		return
	end

	-- Declare local variables
	local Pottery, Writing, Seafaring, tile, city, newUnitTypeName, unitType, randomNum

	-- Player must know tech Pottery to be able to get a Workers, Writing to get a Diplomat, Seafaring to get an Explorer
	Pottery=find.tech_type("Pottery")
	Writing=find.tech_type("Writing")
	Seafaring=find.tech_type("Seafaring")

	-- If player does not know any of the technologies
	if not ( (player):knows_tech(Pottery) ) 
	and not ( (player):knows_tech(Writing) ) 
	and not ( (player):knows_tech(Seafaring) ) then return end

	-- Military unit must be a veteran
	-- this feature is not available in Freeciv prior to v3.1
	-- DISABLED for compatibility with Freeciv 2.6.4
	-- if not ((unit).veteran) then return end

	-- Get the unit's tile / location
	tile=(unit).tile

	-- Get the unit's homecity
	city=find.city(player,unit.homecity)

	-- Unit must be located on land tile without a city 
	if ( tile.terrain.id == find.terrain("Lake").id ) 
	or ( tile.terrain.id == find.terrain("Ocean").id ) 
	or ( tile.terrain.id == find.terrain("Deep Ocean").id )  
	or ( tile.terrain.id == find.terrain("Inaccessible").id ) 
	or ( (tile):city() ) then
		return
	end

	-- Only military units which are persons qualify
	if ( unit.utype.id == find.unit_type("Warriors").id ) 
	or ( unit.utype.id == find.unit_type("Phalanx").id ) 
	or ( unit.utype.id == find.unit_type("Horsemen").id ) 
	or ( unit.utype.id == find.unit_type("Archers").id ) 
	or ( unit.utype.id == find.unit_type("Chariot").id ) 
	or ( unit.utype.id == find.unit_type("Legion").id ) 
	or ( unit.utype.id == find.unit_type("Pikemen").id ) 
	or ( unit.utype.id == find.unit_type("Musketeers").id ) 
	or ( unit.utype.id == find.unit_type("Alpine Troops").id ) 
	or ( unit.utype.id == find.unit_type("Knights").id ) 
	or ( unit.utype.id == find.unit_type("Marines").id ) 
	or ( unit.utype.id == find.unit_type("Riflemen").id ) 
	or ( unit.utype.id == find.unit_type("Dragoons").id ) 
	or ( unit.utype.id == find.unit_type("Cavalry").id ) 
	or ( unit.utype.id == find.unit_type("Paratroopers").id ) then 
		-- Partisans don't qualify, neither do catapult, canon, tanks, planes and so on
		randomNum=(random(1,100))
	else
		return
	end

	-- Player knows all 3 techs
	if ( (player):knows_tech(Pottery) ) 
	and ( (player):knows_tech(Seafaring) ) 
	and ( (player):knows_tech(Writing) ) then
		if ( randomNum <= 70 ) then newUnitTypeName="Workers" 
		elseif ( randomNum > 70 ) and ( randomNum <= 90 ) then newUnitTypeName="Explorer" 
		else newUnitTypeName="Diplomat" end
		-- 70% Workers, 20% Explorer, 10% Diplomat

	-- Player knows only 2 of 3 techs part 1 of 3
	elseif ( (player):knows_tech(Pottery) and (player):knows_tech(Seafaring) )
	and not ( (player):knows_tech(Writing) ) then
		if ( randomNum <= 75 ) then newUnitTypeName="Workers" else newUnitTypeName="Explorer" end
		-- 75% Workers, 25% Explorer

	-- Player knows only 2 of 3 techs part 2 of 3
	elseif ( (player):knows_tech(Pottery) and (player):knows_tech(Writing) )
	and not ( (player):knows_tech(Seafaring) ) then
		if ( randomNum <= 75 ) then newUnitTypeName="Workers" else newUnitTypeName="Diplomat" end
		-- 75% Workers, 25% Diplomat

	-- Player knows only 2 of 3 techs part 3 of 3
	elseif ( (player):knows_tech(Seafaring) and (player):knows_tech(Writing) )
	and not ( (player):knows_tech(Pottery) ) then
		if ( randomNum <= 75 ) then newUnitTypeName="Explorer" else newUnitTypeName="Diplomat" end
		-- 75% Explorer, 25% Diplomat

	-- Player knows only 1 of 3 techs
	elseif  ( (player):knows_tech(Pottery) ) 
	and not ( (player):knows_tech(Writing) ) 
	and not ( (player):knows_tech(Seafaring) ) then 
			newUnitTypeName="Workers"
	elseif  ( (player):knows_tech(Seafaring) ) 
	and not ( (player):knows_tech(Pottery) ) 
	and not ( (player):knows_tech(Writing) ) then 
			newUnitTypeName="Explorer"
	elseif  ( (player):knows_tech(Writing) ) 
	and not ( (player):knows_tech(Seafaring) ) 
	and not ( (player):knows_tech(Pottery) ) then 
			newUnitTypeName="Diplomat"
	end

	unitType=find.unit_type(newUnitTypeName)

	-- Set movement points and veteran level of new unit to 0 as trade-off for the change in type
	edit.create_unit(player, tile, unitType, 0, city, 0)

	-- Uncomment the next line (remove the two hyphens) to receive a messgae list entry every time a unit takes a new job
	--notify.event_msg(player,tile,0,"\tMilitary-to-Work Program: A soldier has retired to a new job: " .. newUnitTypeName .. "." )

	if ( debugLuaScripts ) then log.error("%s","MilitaryToWork END") end
end
signal.connect("unit_lost", "Lua_MilitaryToWork")
^ Updated with changes 2023/10/22 -- added Explorers to available jobs

Image
Some military units can retire from the military and take a job as either a Worker, Explorer or Diplomat if the player has researched Pottery, Seafaring, or Writing.

Attached scenario file "RandoMap Lua MilitaryToWork only" includes the MilitaryToWork Lua script for testing. The scenario file FairTEST includes all of the Lua scripts from this topic in one scenario, and can be downloaded from my latest post in this topic.
Attachments
RandoMap Lua MilitaryToWork only 48x48=2k, 80% land, Classic, Flat, v1.sav.zip
(7.63 KiB) Downloaded 200 times
Last edited by Molo_Parko on Sat Nov 11, 2023 5:21 pm, edited 7 times in total.
Molo_Parko
Hardened
Posts: 158
Joined: Fri Jul 02, 2021 4:00 pm

Re: New features via Lua script

Post by Molo_Parko »

EscortWorkers: A Phalanx on the same tile with a worker will move with the worker from tile-to-tile. If the Phalanx is in Sentry mode, it will stay in Sentry mode after each move.

Code: Select all

--##############################################################################
function Lua_EscortWorkers(unit, src_tile, dst_tile)

	if ( EscortWorkers == nil ) then EscortWorkers=true end

	-- If this routine is disabled, just return without doing anything else
	if not ( EscortWorkers ) then return end

	if ( debugLuaScripts ) then log.error("%s","EscortWorkers BEGIN") end

	-- Note: Phalanx in Sentry mode will stay in Sentry mode after move
	-- Freeciv 2.6.4 has no Lua method to detect unit status (Sentry or Fortified)
	--	nor any method to re-Fortify the unit after move
	-- Freeciv 3.1 and later has: edit.perform_action(unit, "Fortify")

	-- AI controlled players manage their own units
	if not (unit.owner:is_human()) then return end

	if ( unit.utype.id == find.unit_type("Workers").id ) and not ( (unit):transporter() ) then
		if ( (src_tile):num_units() > 1 ) then 
-- ToDo: 
-- If there are more than 1 Phalanx on src_tile, pick which to move
-- If only 1 Phalanx and src_tile is city, don't move the unit?
-- If 2 Phalanx and src_tile is city, are either's homecity there?
-- If 2 Phalanx and src_tile not a city, then move 1 and done
		elseif ( (src_tile):num_units() == 1 ) then 
			-- If there is only 1 Phalanx on src_tile, follow the Workers
			for guard in (src_tile:units_iterate()) do
				if ( (guard).utype.id == find.unit_type("Phalanx").id ) 
				and ((unit).owner) == ((guard).owner) then
					if ( ( string.sub(fc_version(),17,19) ) >= "2.4" ) then
						edit.unit_move(guard,dst_tile,0)
						break
					end
				end
			end
		end
	end
	if ( debugLuaScripts ) then log.error("%s","EscortWorkers END") end
end
signal.connect("unit_moved", "Lua_EscortWorkers")
Attached scenario file "RandoMap Lua EscortWorkers only" for testing the routine. The FairTEST scenario file has all the Lua scripts that I've posted in this topic, and that file can be downloaded from my latest post in this topic.
Attachments
RandoMap Lua EscortWorkers only 48x48=2k, 80% land, Classic, Flat, v1.sav.zip
(6.71 KiB) Downloaded 207 times
Last edited by Molo_Parko on Sat Nov 11, 2023 6:28 pm, edited 4 times in total.
cazfi
Elite
Posts: 3111
Joined: Tue Jan 29, 2013 6:54 pm

Re: New features via Lua script

Post by cazfi »

Please advertise connection to these forums over secure https instead of plain http.
Molo_Parko
Hardened
Posts: 158
Joined: Fri Jul 02, 2021 4:00 pm

Re: New features via Lua script

Post by Molo_Parko »

For anyone who would like to try a scenario with these Lua additions, attached to this post is a scenario file, "FairTEST" for Freeciv 2.6 or later (using the map from a previously posted scenario, "Fairspun" but with XY wrap disabled.) All five of the scripts posted in this topic so far are included in the scenario file, and enabled by default.

Any of the scripts can be enabled/disabled permanently by changing the value of it's variable in the vars=$$ section of the file and saving the file with the changes, or they can be enabled/disabled on the fly during play, via the "Chat" window/pane and entering "/lua AddMoat=false" to disable or "/lua AddBuoys=true" to enable.

Image

Image

Updated version 1.0.1 also attached. Checks scenario settings for topology and map size. Routines that require classic topology will not run if ISO or HEX topology is set in the scenario file. Also, routines now check whether wrapping is enabled and if not then whether x,y coords of tile to be altered are within map dimensions, and won't attempt to alter tile outside map dimensions without wrapX and/or wrapY enabled.

EDIT: Removed attachments in favor of newer version posted a couple of posts down...
Last edited by Molo_Parko on Sat Oct 28, 2023 4:36 am, edited 1 time in total.
Molo_Parko
Hardened
Posts: 158
Joined: Fri Jul 02, 2021 4:00 pm

Re: New features via Lua script

Post by Molo_Parko »

LabelUnhomedUnits

Code: Select all

--##############################################################################
function Lua_LabelUnhomedUnits(unit, src_tile, dst_tile)

	if ( LabelUnhomedUnits == nil ) then LabelUnhomedUnits=true end

	-- If this routine is disabled, just return without doing anything else
	if not ( LabelUnhomedUnits ) then return end

	if ( debugLuaScripts ) then log.error("%s","LabelUnhomedUnits BEGIN") end

	-- The commands used require Freeciv 2.5 or later
	if ( ( string.sub(fc_version(),17,19) ) < "2.5" ) then return end

	if ( _G.cursedTile ~= nil ) then
		if ( _G.cursedTile ) then
			if ( _G.cursedTile == src_tile ) then return end
		end
	end

	-- Create the global _G.unhomedUnitsTiles table if needed
	if ( _G.unhomedUnitsTiles == nil ) then _G.unhomedUnitsTiles={} end

	-- Note: this will remove existing scenario [map] section tile labels because
	-- Lua can't get the label content so can't tell if any prior label exists!
	-- A separate routine "PersistentMapLabels" which is not included here, may resolve that issue.

	if ( (unit).homecity == 0 ) then 
		if ( ( string.sub(fc_version(),17,19) ) >= "2.5" ) then
			edit.tile_set_label(dst_tile,">>--U--<<")

			-- Add dst_tile to table
-- ToDo: don't add tile to label list if src_tile is cursedTile? Unit will be moved back, except turn 0, and assuming that cursedTile is on
			_G.unhomedUnitsTiles[#_G.unhomedUnitsTiles+1]=(dst_tile)

			-- if there is still an unhomed unit on src_tile, don't remove the label
			for otherUnit in ((src_tile):units_iterate()) do
				if ( (otherUnit).homecity == 0 ) then return end
			end

			-- There is no longer an unhomed unit on src_tile, so do remove the label
			edit.tile_set_label(src_tile,"")


		end
	end
	if ( debugLuaScripts ) then log.error("%s","LabelUnhomedUnits END") end
end
signal.connect("unit_moved", "Lua_LabelUnhomedUnits")


--##############################################################################
function Lua_LabelsCleanUp(turn, year)
	-- If this routine is disabled, just return without doing anything else
	if not ( LabelUnhomedUnits ) then return end

	if ( debugLuaScripts ) then notify.event_msg(nil,nil,0,"debugLuaScripts: Lua_LabelsCleanUp(turn, year) BEGIN") end

	local i, tile, num_units

	-- If the list of tiles is empty, just return
	if #_G.unhomedUnitsTiles < 1 then return end

	-- Clean nils out of the table list
	_G.unhomedUnitsTiles=(table.pack(table.unpack(_G.unhomedUnitsTiles)))

	for i=1,#_G.unhomedUnitsTiles,1 do

		-- If the value in the table is nil, skip it
		-- this should not occur?
		if _G.unhomedUnitsTiles[i] == nil then goto continue end

		tile=_G.unhomedUnitsTiles[i]

		if ( (tile):num_units() > 0 ) then
			for unit in ( (tile):units_iterate() ) do
				-- If there is still any unhomed unit on the tile, then skip this tile
				if ( (unit).homecity == 0 ) then goto continue end
			end
		end
			-- If we are here, then there are no longer unhomed units on tile
			-- so remove the label
			edit.tile_set_label(tile,"")
			-- and remove the tile from the list
			--_G.unhomedUnitsTiles[i]=nil
			-- ^ commented out because if unit does not move, but does disband, tile would no longer be in list
			-- to remove label at next turn start
		::continue::
	end
	if ( debugLuaScripts ) then notify.event_msg(nil,nil,0,"debugLuaScripts: Lua_LabelsCleanUp(turn, year) END") end
end -- function Lua_LabelsCleanUp(turn, year)
Image
^ The Warrior is homed, the rest of the units are unhomed. Added separate routine to fix the label on the city built by an unhomed Settler.

I got the idea for this from nef's Unhomed Lua script at: viewtopic.php?p=104606

Attachment removed in favor of newer version a few posts down.
Last edited by Molo_Parko on Sat Nov 11, 2023 8:23 pm, edited 9 times in total.
cazfi
Elite
Posts: 3111
Joined: Tue Jan 29, 2013 6:54 pm

Re: New features via Lua script

Post by cazfi »

Molo_Parko wrote: Fri Oct 27, 2023 9:48 pmCall the routine from the unit_moved callback.
What if unit dies without moving? What if there's multiple unhomed units in the same tile and one of them moves away, but another one remains?
Molo_Parko
Hardened
Posts: 158
Joined: Fri Jul 02, 2021 4:00 pm

Re: New features via Lua script

Post by Molo_Parko »

The multiple unhomed units on a tile issue is an easy fix...

Code: Select all

function Lua_LabelUnhomedUnits(unit, src_tile, dst_tile)
	-- If this routine is disabled, just return without doing anything else
	if not ( LabelUnhomedUnits ) then return end

	-- Note: this will remove existing tile labels because Lua can't
	-- get the label content so can't tell if any prior label exists!
	if ( (unit).homecity == 0 ) then 
			edit.tile_set_label(dst_tile,">>--U--<<")

			for otherUnit in ((src_tile):units_iterate()) do
				if ( (otherUnit).homecity == 0 ) then return end
			end
			edit.tile_set_label(src_tile,"")
	end
end -- function Lua_LabelUnhomedUnits
The unit_lost (dying, disbanding, etc) would require keeping track of which tiles have had the U label applied (in a global array), then loop through the tiles each turn and remove the label for any tile which did have, and no longer has an unhomed unit, then also remove that tile from the array. I'll add that.

EDIT: New version attached. The only problem I had with the table/array method is when a unit does NOT move, but does disband or die... so I disabled removing the tile from the list of tiles which had had labels applied. That might cause a problem down the road if the table grows too large, but for the moment it seems to work well. This method will leave the label when a unit is killed (like a memorial), but then clears away the label at the start of the next turn. Could clean up the list after removing labels at the next turn, that would keep the table smaller.

EDIT2: Labels aren't hidden by fog-of-war, so you can see the locations of other players startunits through the fog... :)

Attachment removed in favor of newer version posted further in this topic.
Last edited by Molo_Parko on Mon Oct 30, 2023 5:15 pm, edited 3 times in total.
Post Reply