Changing Unit's Costs/Ability With Game Progression

Mr. Spiffy

Chieftain
Joined
Mar 5, 2016
Messages
20
Location
Tennessee
My situation is this: I have a custom civilization that throws the player into rebellion if they are not in a golden age. Since staying in a golden age can be difficult or impossible in the early game, I made a custom unit that grants a golden age. While this unit might eat up a lot of production early game, by the end or middle of the game, the cost would be trivial. This would make it simple to stay in a golden age without devoting much to it.

With this problem, the obvious solution is to make the unit's cost go up or golden age turns go down based on the era or turns passed. I can use GetCurrentEra() for the era check, but I'm not sure how to alter the production of a certain unit.

This led me to try changing the golden age modifier based on the number of golden ages a player has had. I ran into a wall, however, when I figured out that GetNumGoldenAges() only increased every time a golden age started and ended. It doesn't count golden ages that are triggered in the middle of a golden age. Could I perhaps use ChangeNumUnitGoldenAges()? I'm sure what that method even does.

I've just started using lua days ago, so I'm sure there's a solution here that I'm not able to see in my inexperience. I would greatly appreciate it if someone were to point me in the right direction with solving this problem. Anything that would make this unit harder to obtain based on the era will work for me.
 
Make many copies of the same unit, with only the cost being different and use PlayerCanTrain to negate/allow correct training based on current era.

That's a good idea. I saw another thread where someone did this, but I wasn't able to do it myself. I would have to add a large number of units, and if I don't replace an existing unit with them, they are available to every civ. But I suppose I could use PlayerCanTrain() to make it only available to my civ. I'll give it a try.
 
I would have to add a large number of units,
1 per era.

and if I don't replace an existing unit with them,
Don't replace them, otherwise your Civ would have lots of the same UU.

they are available to every civ.
That's what PlayerCanTrain is for!

But I suppose I could use PlayerCanTrain() to make it only available to my civ.
That's what PlayerCanTrain is for!
Just remember to place Era checks as well.
 
1 per era.


Don't replace them, otherwise your Civ would have lots of the same UU.


That's what PlayerCanTrain is for!


That's what PlayerCanTrain is for!
Just remember to place Era checks as well.

Whenever I attempt to change the boolean value of CanTrain, it comes up with an error. When I tried putting the true and false inside of the parentheses, the error went away, but nothing happened. I'm certain that I'm doing something wrong, but there's so little documentation that I can't figure it out. The wiki says there are 4 boolean values that are included with CanTrain but doesn't explain a single one. Here is my code:

Code:
function LordranTrait(playerID)

	print("Function called.")
	local player = Players[playerID]

	if player:GetCivilizationType() == GameInfoTypes["CIVILIZATION_LORDRAN"] and player:IsAlive() then

		print("Lordran is the player.")

		if player:GetCurrentEra() == GameInfoTypes["ERA_ANCIENT"] then
			
			player:CanTrain(GameInfoTypes["UNIT_CHOSEN_UNDEAD"]) = true
			print("Lordran can produce the Chosen Undead.")

		end
		
		if player:IsGoldenAge() == false then
			
			player:ChangeUnhappinessFromUnits(200)			
			print("age of darkness")
			
		else

			local UFU = player:GetUnhappinessFromUnits()
			player:ChangeUnhappinessFromUnits(-UFU)
			print("let there be light")		

		end
		
	else
	
		player:CanTrain(GameInfoTypes["UNIT_CHOSEN_UNDEAD"]) = false
		print("Other Civ")

	end

end

GameEvents.PlayerDoTurn.Add(LordranTrait)

The non-golden age unhappiness and era checks work, but my custom unit can still be built by other civs. How can I fix this mess?
 
Code:
#1	local [COLOR="Plum"]eSpiffyUnitBase[/COLOR] = GameInfoTypes["UNIT_SPIFFY_UNIQUE_BASE"]
#2	local [COLOR="DarkOrchid"]eSpiffyUnitII[/COLOR] = GameInfoTypes["UNIT_SPIFFY_UNIQUE_II"]
#3	local [COLOR="Purple"]eSpiffyUnitIII[/COLOR] = GameInfoTypes["UNIT_SPIFFY_UNIQUE_III"]
#4	local eMrSpiffyCiv = GameInfoTypes["CIVILIZATION_MR_SPIFFY"]
#5
#6
#7	function MrSpiffyCanTrain([COLOR="Magenta"]iPlayer[/COLOR], [COLOR="DarkOrange"]iUnit[/COLOR])
#8		local pPlayer = Players[[COLOR="magenta"]iPlayer[/COLOR]]
#9		if [COLOR="DarkOrange"]iUnit[/COLOR] == [COLOR="plum"]eSpiffyUnitBase[/COLOR] or [COLOR="darkorange"]iUnit[/COLOR] == [COLOR="darkorchid"]eSpiffyUnitII[/COLOR] or [COLOR="darkorange"]iUnit[/COLOR] == [COLOR="purple"]eSpiffyUnitIII[/COLOR] then
#10			if pPlayer:GetCivilizationType() == eMrSpiffyCiv then
#11				local [COLOR="Blue"]iEra[/COLOR] = pPlayer:GetCurrentEra()
#12				if [COLOR="DarkOrange"]iUnit[/COLOR] == [COLOR="plum"]eSpiffyUnitBase[/COLOR] then
#13					if [COLOR="blue"]iEra[/COLOR] == 0 then
#14						[COLOR="red"]return true[/COLOR]
#15					end
#16				elseif [COLOR="darkorange"]iUnit[/COLOR] == [COLOR="darkorchid"]eSpiffyUnitII[/COLOR] then
#17					if [COLOR="blue"]iEra[/COLOR] == 1 then
#18						[COLOR="red"]return true[/COLOR]
#19					end
#20				elseif [COLOR="darkorange"]iUnit[/COLOR] == [COLOR="purple"]eSpiffyUnitIII[/COLOR] then
#21					if [COLOR="blue"]iEra[/COLOR] == 2 then
#22						[COLOR="red"]return true[/COLOR]
#23					end
#24				end
#25			end
#26			[COLOR="red"]return false[/COLOR]
#27		end
#28		[COLOR="Red"]return true[/COLOR]
#29	end
#30
#31	GameEvents.[COLOR="mediumturquoise"]PlayerCanTrain[/COLOR].Add(MrSpiffyCanTrain)

It seems that you already got the basics of Lua (matching variables, closing arguments, etc), so I'll try to keep that in mind while explaining the code. Here it goes:

Lines #1 through #4 are variables that will work throughout your whole code file, regardless of function. These are basically there for organization purposes alone, they make your code cleaner.
So, #7 is the start of our function. This particular function is tethered to the line #31's PlayerCanTrain GameEvent, which is a TestAll event handler, meaning you must have a return value (the difference between TestAll and TestAny is actually important, but I'll ignore because it's outside this post's scope).

Onto the code, then:
This function will run every time the game checks which units a player can run. It'll ask for a returned boolean for each and every unit in the game. This is used as an additional lock, which means a blank code with just a 'return true' will not allow, for example, you to train Unique Units (because there are other locks in place for those).
Knowing that, line #9 states which Units we are checking against, they are:
eSpiffyUnitBase, eSpiffyUnitII or eSpiffyUnitIII.

If the game is checking ANY of these three, the code will go on. Otherwise (aka, the unit is not one of those), it'll jump to right after the end of the argument (line #27) and return the boolean true, in the designated by the next line.
Assuming it is one of those three, it'll then check to see if you are of the correct Civilization (#10), with a check you're familiar with.
If you're NOT of the correct Civ, the argument ends (#25), but this time we will return false, because we don't want other Civs to be able to train any of the three aforementioned units.
Moving on, we now need to check the player's current Era against the Era IDs (you could check against the Types instead), and lock those units if they are not correspondent. That's exactly what is happening in lines #12, #16 and #20. In case of a match, the code returns true and the player is allowed to train said unit.


NOTES
- Change the types (units and Civ) to the correct ones (those in your XML/SQL files) in lines #1 through #4.
- Once you copy this code directly to your Notepad++ (or whatever editor you use), remove the line denotations (#).
- In this code's case, the last check (#20) could've been an 'else' instead of 'elseif', because if the code passed through line #9's check, there are only three options available - one of the three units is being checked by the GameEvent-, but I thought it would be easier to visualize this way.
 
Code:
#1	local [COLOR="Plum"]eSpiffyUnitBase[/COLOR] = GameInfoTypes["UNIT_SPIFFY_UNIQUE_BASE"]
#2	local [COLOR="DarkOrchid"]eSpiffyUnitII[/COLOR] = GameInfoTypes["UNIT_SPIFFY_UNIQUE_II"]
#3	local [COLOR="Purple"]eSpiffyUnitIII[/COLOR] = GameInfoTypes["UNIT_SPIFFY_UNIQUE_III"]
#4	local eMrSpiffyCiv = GameInfoTypes["CIVILIZATION_MR_SPIFFY"]
#5
#6
#7	function MrSpiffyCanTrain([COLOR="Magenta"]iPlayer[/COLOR], [COLOR="DarkOrange"]iUnit[/COLOR])
#8		local pPlayer = Players[[COLOR="magenta"]iPlayer[/COLOR]]
#9		if [COLOR="DarkOrange"]iUnit[/COLOR] == [COLOR="plum"]eSpiffyUnitBase[/COLOR] or [COLOR="darkorange"]iUnit[/COLOR] == [COLOR="darkorchid"]eSpiffyUnitII[/COLOR] or [COLOR="darkorange"]iUnit[/COLOR] == [COLOR="purple"]eSpiffyUnitIII[/COLOR] then
#10			if pPlayer:GetCivilizationType() == eMrSpiffyCiv then
#11				local [COLOR="Blue"]iEra[/COLOR] = pPlayer:GetCurrentEra()
#12				if [COLOR="DarkOrange"]iUnit[/COLOR] == [COLOR="plum"]eSpiffyUnitBase[/COLOR] then
#13					if [COLOR="blue"]iEra[/COLOR] == 0 then
#14						[COLOR="red"]return true[/COLOR]
#15					end
#16				elseif [COLOR="darkorange"]iUnit[/COLOR] == [COLOR="darkorchid"]eSpiffyUnitII[/COLOR] then
#17					if [COLOR="blue"]iEra[/COLOR] == 1 then
#18						[COLOR="red"]return true[/COLOR]
#19					end
#20				elseif [COLOR="darkorange"]iUnit[/COLOR] == [COLOR="purple"]eSpiffyUnitIII[/COLOR] then
#21					if [COLOR="blue"]iEra[/COLOR] == 2 then
#22						[COLOR="red"]return true[/COLOR]
#23					end
#24				end
#25			end
#26			[COLOR="red"]return false[/COLOR]
#27		end
#28		[COLOR="Red"]return true[/COLOR]
#29	end
#30
#31	GameEvents.[COLOR="mediumturquoise"]PlayerCanTrain[/COLOR].Add(MrSpiffyCanTrain)

It seems that you already got the basics of Lua (matching variables, closing arguments, etc), so I'll try to keep that in mind while explaining the code. Here it goes:

Lines #1 through #4 are variables that will work throughout your whole code file, regardless of function. These are basically there for organization purposes alone, they make your code cleaner.
So, #7 is the start of our function. This particular function is tethered to the line #31's PlayerCanTrain GameEvent, which is a TestAll event handler, meaning you must have a return value (the difference between TestAll and TestAny is actually important, but I'll ignore because it's outside this post's scope).

Onto the code, then:
This function will run every time the game checks which units a player can run. It'll ask for a returned boolean for each and every unit in the game. This is used as an additional lock, which means a blank code with just a 'return true' will not allow, for example, you to train Unique Units (because there are other locks in place for those).
Knowing that, line #9 states which Units we are checking against, they are:
eSpiffyUnitBase, eSpiffyUnitII or eSpiffyUnitIII.

If the game is checking ANY of these three, the code will go on. Otherwise (aka, the unit is not one of those), it'll jump to right after the end of the argument (line #27) and return the boolean true, in the designated by the next line.
Assuming it is one of those three, it'll then check to see if you are of the correct Civilization (#10), with a check you're familiar with.
If you're NOT of the correct Civ, the argument ends (#25), but this time we will return false, because we don't want other Civs to be able to train any of the three aforementioned units.
Moving on, we now need to check the player's current Era against the Era IDs (you could check against the Types instead), and lock those units if they are not correspondent. That's exactly what is happening in lines #12, #16 and #20. In case of a match, the code returns true and the player is allowed to train said unit.


NOTES
- Change the types (units and Civ) to the correct ones (those in your XML/SQL files) in lines #1 through #4.
- Once you copy this code directly to your Notepad++ (or whatever editor you use), remove the line denotations (#).
- In this code's case, the last check (#20) could've been an 'else' instead of 'elseif', because if the code passed through line #9's check, there are only three options available - one of the three units is being checked by the GameEvent-, but I thought it would be easier to visualize this way.

You are a saint! Thank you so much for this very detailed and helpful post in such a short time. It works fantastically and I learned a good bit about LUA. Now I just need to balance the costs.

This forum is the best.
 
There is one last problem that I face with my custom civilization is when I give control of it to the AI. They don't know how to use my custom unit to keep their golden ages going. With that being the case, they inevitable fall into revolt, and that would be a pretty lame AI to play against. I tried making the AI for the unit the same as a great artist, but that doesn't solve the problem of the AI just not bothering to build them.

I wouldn't mind making the civilization available to human players only, but I also don't want to give up so easily. Is there a doable way to update the AI for this civilization's unique trait?

I feel bad requesting for help again, but this should be the last time for a while.
 
The game is notorious for not using secondary unit-abilities as an AI. AI Legions never build roads or forts, AI Samuari never build fishing boats, Spanish Conquistadors don't city-found. There's just no way to get the game to use the secondary ability of a unit when the player is an AI, without a certain amount of lua to essentiallly take control of the unit away from the AI. And then there are all kinds of irksome behaviors where the order you give to an AI unit gets cancelled. So far as the AI wanting to build the unit, it won't do so if the unit has no 'flavors', and it still will not do so if those flavor-types don't match with its decision-making for "what do I need to produce in this city?" at the time that the decision-making is conducted.
 
The game is notorious for not using secondary unit-abilities as an AI. AI Legions never build roads or forts, AI Samuari never build fishing boats, Spanish Conquistadors don't city-found. There's just no way to get the game to use the secondary ability of a unit when the player is an AI, without a certain amount of lua to essentiallly take control of the unit away from the AI. And then there are all kinds of irksome behaviors where the order you give to an AI unit gets cancelled. So far as the AI wanting to build the unit, it won't do so if the unit has no 'flavors', and it still will not do so if those flavor-types don't match with its decision-making for "what do I need to produce in this city?" at the time that the decision-making is conducted.

I see then. It'd be folly to try fixing the AI with my knowledge level. I guess that's case closed then. I genuinely appreciate everyone's help here.
 
Back
Top Bottom