I think I need a BREAK in this code, but where?

Craig_Sutter

Deity
Joined
Aug 13, 2002
Messages
2,773
Location
Calgary, Canada
The following code works and is tied to a PlayerDoTurn event.

There is also a CityCanTrain function that occurs that restricts this function somewhat. It restricts the training of said unit to 1 for every 2 cities. A separate function also deducts the cost from faith and refunds the gold of said unit... in effect, the unit is bought with faith. That function checks that the player's faith is greater than the gold cost.

This results as expected. Units are purchased with gold and are restricted by the CityCanTrain function as expected... and faith is deducted in the right amounts. The problem is that the function below seems to be ignoring the faith adjustments done by the other function, and will purchase as many units as CityCanTrain will allow, regardless of the faith check in the code below and in the CityCanTrain function. It seems as if the deductions from faith occur after the function fires in total, and not incrementally as each city purchases a unit. Players will go into negative faith to purchase all units available in one turn.

I need to adjust the function below somehow. The CityCanTrain function does not allow a human player to buy a unit if the faith requirements are not met, so the problem must be in the set up below... which, when finished, will only fire for AI players (as you can see from the commented out bit...)

If there is no way to easily stop the player going into negative faith, I would prefer they be restricted to purchasing one unit per turn instead... hence, the code needs to be fixed to check faith prior to every unit being purchased or a BREAK needs to be inserted to stop cycling through the cities upon one unit being built.


Code:
-- To spawn a BerserkerWarrior in AI player city and deduct purchase price while adding promotion and experience from city.

function ForceBerserkerWarriorPurchase (iPlayer)
	
	for iPlayer=0, GameDefines.MAX_CIV_PLAYERS - 1 do 

		local pPlayer = Players[iPlayer]
		if pPlayer:IsAlive () and pPlayer:GetNumCities() >= 1 then --and not pPlayer:IsHuman() then
			
			for pCity in pPlayer:Cities() do	

				local pUnit = GameInfoTypes["UNIT_BERSERKER_WARRIOR"]
				local cost = pCity:GetUnitPurchaseCost(pUnit)
				local currentFaith = pPlayer:GetFaith()
				local pPlot = pCity:Plot()
				local iPromotion
				local iActiveUnit
				local ActiveUnit
							
				-- determine if Player can purchase BerserkerWarrior in city and has enough gold then spawn and deduct gold
			
				if (pCity:IsCanPurchase(true, true, pUnit, -1, -1, YieldTypes.YIELD_GOLD)) and currentFaith >= cost then
						
					local SpawnUnit;
					local iSpawnX = pPlot:GetX();
					local iSpawnY = pPlot:GetY();
			
					-- spawn the unit and deduct gold

					SpawnUnit = pPlayer:InitUnit(GameInfoTypes["UNIT_BERSERKER_WARRIOR"], iSpawnX, iSpawnY, UNITAI_ATTACK, DIRECTION_NORTHWEST )
					SpawnUnit:SetExperience(pCity:GetDomainFreeExperience( SpawnUnit:GetDomainType())) 
					SpawnUnit:SetMoves(0)
					pPlayer:ChangeGold( -cost )
					print (pCity:GetName() , "...spawning and paying for BerserkerWarrior...");

					-- loop for promotions from buildings in city	
		
					for promotion in GameInfo.UnitPromotions() do

					iPromotion = promotion.ID	

						-- check for promotions from city

						if ( pCity:GetFreePromotionCount( iPromotion ) > 0 ) then

							-- find units in city

							local ActiveUnit = pPlot:GetUnit(i)
							local iActiveUnit = GameInfo.Units[ActiveUnit:GetUnitType()].ID
			
							-- see if unit created this turn

							if ActiveUnit:GetGameTurnCreated() == Game.GetGameTurn() then

								--check if unit can get promotion and give it

								-- looks for unit combat class and changes it to string

								local sCombatClass = "none"
								for row in GameInfo.Units() do

									if row.ID == iActiveUnit then

									sCombatClass = tostring(row.CombatClass)
									--print("...finding CombatClass of...", ActiveUnit:GetName())

									end
								end

								--looks for promotion combat type (class) and changes it to string

								local sType = "none"
								for row in GameInfo.UnitPromotions() do

									if row.ID == iPromotion then

									sType = tostring(row.Type)
									--		print("...finding Promotion Type...", sType)

									end
								end	

								local sUnitCombatType = "none"
								local sPromotionType = "none"
								for row in GameInfo.UnitPromotions_UnitCombats() do
							
									sPromotionType = tostring(row.PromotionType)
									sUnitCombatType = tostring(row.UnitCombatType)
									--print("...finding... Promotion Combat Type...", tostring(row.UnitCombatType))
									if sType == sPromotionType	then
									--print("Checking if...", sType, "... equals...", sPromotionType)

										if sUnitCombatType == sCombatClass then

										print(pPlayer:GetName(), "...spawned unit......getting Promotion...", ActiveUnit:GetName() )
										ActiveUnit:SetHasPromotion(iPromotion, true);

										end
									end
								end
							end
						end
					end
				end						
			end
		end		
	end	
end


--to set city operations
local g_lastTurn = -1

local function OnPlayerDoTurn(iPlayer)
	--print("OnPlayerDoTurn ", iPlayer)
	local gameTurn = Game.GetGameTurn()

	if g_lastTurn < gameTurn then
		g_lastTurn = gameTurn

		--per game turn function here

		ForceBerserkerWarriorPurchase (iPlayer)		

	end
end
GameEvents.PlayerDoTurn.Add(OnPlayerDoTurn)

I include the CityCanTrain and Religious Cost functions for completeness... although they are working as expected for the human player. And they include code governing a lot of other units...

Spoiler :

Code:
--allows building of Berserker units equal to the number of unique buildings

local iBer_Warrior = GameInfoTypes.UNIT_BERSERKER_WARRIOR
local iBerserker = GameInfoTypes.UNIT_BERSERKER
local iBerserkerHall = GameInfoTypes.BUILDING_PAGODA

function BerserkerUnit(iPlayer, iCity, iUnit)
	--differentiate Berserker units
	if iUnit == iBer_Warrior or iUnit == iBerserker then
		
		-- check for Berserker building
		local player = Players[iPlayer]		
		local city = player:GetCityByID(iCity)			
		if city:IsHasBuilding(iBerserkerHall) then
				
			-- check number of Berserker units
			local numUnits = 0	
			for unit in player:Units() do
				if unit:GetUnitType() == iBer_Warrior or unit:GetUnitType() == iBerserker then				
					numUnits = numUnits + 1
				end
			end
			--print("numUnits was calculated as... ", numUnits)

			-- find number of buildings of applicable type
			BuildingCount = player:CountNumBuildings(iBerserkerHall)

			--print("BuildingCount was calculated as... ", BuildingCount)

			--check if the player has enough faith
			
			cost = city:GetUnitPurchaseCost( iUnit )
			currentFaith = player:GetFaith()
			gold =  player:GetGold()

			return math.ceil(BuildingCount/2) > numUnits and currentFaith >= cost and gold>=cost--compare fraction of building count to number of units and check if enough faith ... return TRUE if BuildingCount/X > numUnits
		else		
			--print("City was seen as NOT having the required number and type of buildings for the unit, so FALSE is returned")
			return false	--needed when the city does not have a building that allows the unit		
		end
		
	else	
		return true	--default needed for units NOT in Berserker Units
	end
end

GameEvents.CityCanTrain.Add(BerserkerUnit)



Spoiler :

Code:
--converts gold cost of religious units to faith amount

local iBer_Warrior = GameInfoTypes.UNIT_BERSERKER_WARRIOR
local iBerserker = GameInfoTypes.UNIT_BERSERKER
local iStellinga = GameInfoTypes.UNIT_SAXON_STELLINGA
local iScariti = GameInfoTypes.UNIT_SCARITI_CHRISTI
local iMiles = GameInfoTypes.UNIT_MILES_CHRISTI
local iFootKnight = GameInfoTypes.UNIT_FOOT_KNIGHT
local iCrusader = GameInfoTypes.UNIT_CRUSADER

function faithpurchase( iPlayer, iUnit )
	local unit = Players[iPlayer]:GetUnitByID( iUnit )
	local iUnitType = unit:GetUnitType()
	
	if iUnitType == iBer_Warrior or iUnitType == iBerserker or iUnitType == iStellinga or iUnitType == iScariti or iUnitType == iMiles or iUnitType == iFootKnight or iUnitType == iCrusader then

		local player = Players[iPlayer]	
		local plot = unit:GetPlot()

		if plot:IsCity() then

			local city = plot:GetPlotCity()
			local cost = city:GetUnitPurchaseCost(iUnitType)
	
			player:ChangeGold( cost )
			player:ChangeFaith(-cost)
			print (player:GetName() ,"...is trained...", unit:GetName(), "...religious unit in...", city:GetName());
		-- else (you could decide what if anything to do here if the plot is not a city plot)
		end
	end
end

LuaEvents.SerialEventUnitCreatedGood.Add( faithpurchase )



Thank-you for your help.
 
It might work better if you removed the "for iPlayer=0, GameDefines.MAX_CIV_PLAYERS - 1 do" loop instruction from the ForceBerserkerWarriorPurchase function supposedly called for each iPlayer, and simply "GameEvents.PlayerDoTurn.Add(ForceBerserkerWarriorPurchase)" at the end of the file to call the function for each player during their turn.
Also, why not simply purchase the unit with the function Game.CityPurchaseUnit( pCity, GameInfoTypes.UNIT_BERSERKER_WARRIOR, YieldTypes.YIELD_GOLD ) ? It will handle all the details for you...
So the code could look something like this:
Code:
-- To spawn a BerserkerWarrior in all player's cities which do not already have a unit there so long as they have the tech and cash to do so, and a minimum faith equal to the unit's gold price:

function ForceBerserkerWarriorPurchase( playerID )

	local player = Players[playerID]
	if player and player:IsAlive() then --and not player:IsHuman() then
		local berserkerID = GameInfoTypes.UNIT_BERSERKER_WARRIOR
		for city in player:Cities() do
			-- determine if Player can purchase BerserkerWarrior in city and has enough gold then spawn and deduct gold
			-- why check the faith ? seems rather pointless
			if city:IsCanPurchase(true, true, berserkerID, -1, -1, YieldTypes.YIELD_GOLD) and player:GetFaith() >= city:GetUnitPurchaseCost(berserkerID) then
				Game.CityPurchaseUnit( city, berserkerID, YieldTypes.YIELD_GOLD )
			end
		end
	end
end

GameEvents.PlayerDoTurn.Add( ForceBerserkerWarriorPurchase )
 
Is that a new API function? Way back when, when I first started working on my mod, no such thing existed... or so I believe, since I recall having help on the forced purchase code (for another purpose), long ago.

I will certainly use it if it works similarly (ie promotions and exp from cities added).

The faith check is a bit of overkill given the citycantrain function does the same...I added it in hopes it would stop the problem I was trying to address.

Finally, as you can see, I have on player do turn event at the end of the code... enclosing the purchasebersrkwarrior function... are you saying that will, in and of itself, loop through the players? If so, I likely have a lot of recoding to do because in many instances, I have functions firing for every player and do a loop through within the function to do it... but similarly to the abovr, run it within a onplayerturn function. (The reason I put a lot of functions within that onplayerdo game turn function is that they play nicely with autoplay when testing).
 
since you are doing this anyway (albeit in a different function):
Code:
player:ChangeGold( cost )
player:ChangeFaith(-cost)
you could streamline to bc1's suggested method and just place these two commands in there with the forced city purchasing:
Code:
function ForceBerserkerWarriorPurchase( playerID )

	local player = Players[playerID]
	if player and player:IsAlive() then --and not player:IsHuman() then
		local berserkerID = GameInfoTypes.UNIT_BERSERKER_WARRIOR
		for city in player:Cities() do
			-- determine if Player can purchase BerserkerWarrior in city and has enough gold and faith then spawn and deduct faith
			-- why check the faith ? because he is trying to get around the irsksome limitations to faith-purchasing of units
			local iUnitCost = city:GetUnitPurchaseCost(berserkerID)
			if city:IsCanPurchase(true, true, berserkerID, -1, -1, YieldTypes.YIELD_GOLD) and player:GetFaith() >= iUnitCost then
				player:ChangeFaith(-iUnitCost)
				Game.CityPurchaseUnit( city, berserkerID, YieldTypes.YIELD_GOLD )
				player:ChangeGold(iUnitCost)
			end
		end
	end
end

GameEvents.PlayerDoTurn.Add( ForceBerserkerWarriorPurchase )
Then you shouldn't need the other function that is doing the alchemy of gold-to-faith and vice versa.

--------------------------------------------------------------------------
  • I would also consider at some point looking at the player's GPT 'score' (Player:CalculateGoldRate()) and available unit supply to put some brakes on the system.
  • I've done this to calculate available unit supply in a couple of things I've experimented with. So far I haven't found a direct 'SupplyRemaining' method.
    Code:
    local iSupplyRemaining = pPlayer:GetNumUnitsSupplied() - pPlayer:GetNumUnits()
 
Only problem would be I need forced purchasing only for AI players... human still needs to be restricted by CityCanTrain... thus the separate function... human must pay as well with faith and refunded cash.

The CityCanTrain has a one unit per 2 cities having a relevant religious building restraint... I will adjust it with play testing. The religious units are themselves maintenance free and not requiring support/supply (I think as I've set it in the xml).

Thank-you... I think I have to rejig some code in any case...

PS. This is for a scenario, so I have a lot of preknowledge to determine how big the religious themed units forces will be... much play testing will ensue :).
 
That's got to be a new API... I remember thr grief I went through and the help I had from excellent modders to create the forced purchase code so long ago. I am certain one of them would have suggested this easy alternative had it existed at the time.

It's one of the reasons I use Machiavelli's serial event unit created function...

Don't suppose there's a replacement for that now too? :) (I say half jokingly...).
 
I believe in this hook: GameEvents.CityTrained(ownerId, cityId, unitId, bGold, bFaithOrCulture); that was added approx a year ago with patch ~276, and which is back-compatible with all three expansion levels of the game, bGold will be "true" when a player has purchased the unit.

So it should be possible to use that hook for all cases (AI and human) to accomplish the gold-to-faith alchemies. Or you could just 'fix' the human player issue more directly from an event that only fires when a city "builds" a unit as opposed to Machi's system (the only downfall of which is that it fires for all units added to the game regardless of how they were created into the game).
 
Back
Top Bottom