How Would I Code This Lua

Murphy613

Prince
Joined
Nov 18, 2012
Messages
483
How would I make a lua that:
1 - grants all units of a certain civ - say, Egypt - an ability
2 - triggered by a button (like for building a landmark)
3 - that can be used only once per game
4 - which reduces the population of ALL other civilization's cities by 2
5 - and increases the population of all that civ's (Egypt's) cities by two.
This is basically the Calabim world spell in FFH2, River of Blood.
 
Hello!
I'll try to help you on what I can, let's do it then:

1) Use Unit:SetHasPromotion and check initially if the Civilization is the right one.
2) That I don't know! I'm actually trying to find more info on this one for a while.
3) Easy peasy! Make a variable that starts as true/1 and when you press the button it changes the variable to false/0.
4) This would be a bit greater, but I can do that for you if you want. You would have to check all players and their capitals, then set population = to actual population - 2.
5) Same thing!

If you want, I can make the code for 1 through 5 with the exception of 2 (which I believe is VERY important though, haha).
 
Hello!
I'll try to help you on what I can, let's do it then:

1) Use Unit:SetHasPromotion and check initially if the Civilization is the right one.
2) That I don't know! I'm actually trying to find more info on this one for a while.
3) Easy peasy! Make a variable that starts as true/1 and when you press the button it changes the variable to false/0.
4) This would be a bit greater, but I can do that for you if you want. You would have to check all players and their capitals, then set population = to actual population - 2.
5) Same thing!

If you want, I can make the code for 1 through 5 with the exception of 2 (which I believe is VERY important though, haha).

I'd really appreciate if you could do that. Hopefully someone else will know how to do #2, making the code triggerable.:lol: I like that word.
 
Actually, I've currently been working on adding a triggerable action to one of my units for a civ that I'm currently working on. I've derived the code from Vicevirtuoso's Madoka civs, where the Kyuubey units have a 'once per game' ability you can use. The script appears to use a version of Whoward's moddable UI, judging from what the comments in Vicevirtuoso's version says.
 
Wow. I just read the code bouncymisha is talking about and it's beautiful. The way that guy codes is really inspiring.
I don't feel comfortable giving you the code with the mission because I don't know much, even reading the code - I'll test some for myself and if you're still needing it at the time I conclude my tests I'll gladly help you.

Now, I'm making the rest (1 and 3-5) and will post in a few minutes after some quick tests.

EDIT: Just realized the '1' has nothing to do with promotions. The ability is the one with the button and all that, right? The code for 1 will be part of the 2 anyway; 3-5 it is then!
 
I don't know if you actually NEED the 'true' boolean when reducing the population, but I believe it won't hurt.

Also, I've added a check to see the player's capital population, because I have no idea what happens if a Capital get's reduced to 0 Population. It may crash the game, do nothing, reduce to 1 or explode the planet and I don't want to risk that.
The way it's coded, it'll reduce all Capital's population by 2, as long there is 3 or more, reduce by 1 if it's 2 and do nothing if it is 1.

Code:
function MurphysLaw()
	local user = Game.GetActivePlayer()
	local userCiv = Game.GetActiveCivilizationType()
	local Used = false    -- will be removed when the button code is made.
	if userCiv == GameInfoTypes["CIVILIZATION_EGYPT"] and Used == false then
		local Used = true    -- will be removed when the button code is made.
		local userCap = user:GetCapitalCity()
		local userPop = userCap:GetPopulation()
		userCap:SetPopulation((userPop + 2), true)
		for iPlayer=GameDefines.MAX_MAJOR_CIVS, GameDefines.MAX_CIV_PLAYERS-1, 1 do  
			local playerCap = iPlayer:GetCapitalCity()
			local playerPop = playerCap:GetPopulation()
			if playerPop >= 3 then
				playerCap:SetPopulation((playerPop - 2), true)
			elseif playerPop == 2 then
				playerCap:SetPopulation((playerPop - 1), true)
			end
		end
	end
end

The initial local variables and the first condition will probably be set and checked in the button's Lua code, but I just wanted to put it here for completeness sake.
 
EDIT: Just realized the '1' has nothing to do with promotions. The ability is the one with the button and all that, right? The code for 1 will be part of the 2 anyway; 3-5 it is then!

Actually, it's kind of the reverse -- the way Vicevirtuoso did it (and the way I did it for my own version), the script checks to see if the unit has a certain promotion, and if it does, activates the button. When the ability is used, it removes the promotion from the unit, at which point the button goes away! It's a very tidy way to make the ability "once per game" since you don't need to do any excess bookkeeping, and you could even make functions like the ability to "reload" the ability by having something else grant the promotion back again...
 
Yeah, I took whoward's obsolete G&K Modular UnitPanel and modified it to make it BNW compatible, as well as to make it dynamically determine if the game is running BNW or G&K for compatibility. (It also has an explicit reference to "GameInfoTypes.UNITCOMBAT_MAGICALGIRL", so you may want to delete that if you want to use it in your own mod :crazyeye:)

Looking at whoward's site, it appears he's removed it entirely. Glad I grabbed it when I could. If can get his permission, I could upload my BNW compatible version, giving him full credit for the original code.

Very Important Note, though: The AI cannot use any missions you add in this way. You'll need to add some Lua AI logic (or goofy hacks if you're me) if you want the AI to utilize your new missions. S3rgeus has a DLL replacement that is much more effective in this regard, but do note that users can only use one DLL mod at a time, so it will block out Civ IV Diplomacy, City-State Diplomacy, DLL Various Mod Components, etc.
 
Also, I've added a check to see the player's capital population, because I have no idea what happens if a Capital get's reduced to 0 Population. It may crash the game, do nothing, reduce to 1 or explode the planet and I don't want to risk that.
The way it's coded, it'll reduce all Capital's population by 2, as long there is 3 or more, reduce by 1 if it's 2 and do nothing if it is 1.

I should've thought of that before. Could you please change it so no cities can go below 1 pop? Also, I know the below code makes a notification show up by the specified player. How I make it show up for all ? Do I just delete the '[Game.GetActivePlayer()]'?
Code:
Players[Game.GetActivePlayer()]:AddNotification(NotificationTypes.NOTIFICATION_XYZ, "A player has cast Burning Blood, increasing the population of their cities and decreasing the population of all others!", "A player has cast Burning Blood!");
Thank you so much for taking the time to help me out.:)
 
Also, I know the below code makes a notification show up by the specified player. How I make it show up for all ? Do I just delete the '[Game.GetActivePlayer()]'?

You would need to loop through all of the players in the game and add the notification to each of them.

Code:
for i = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
   local pPlayer = Players[i]
   if pPlayer:IsEverAlive() then
      pPlayer:AddNotification(NotificationTypes.NOTIFICATION_XYZ, "A player has cast Burning Blood, increasing the population of their cities and decreasing the population of all others!", "A player has cast Burning Blood!")
   end
end
 
You would need to loop through all of the players in the game and add the notification to each of them.

Code:
for i = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
   local pPlayer = Players[i]
   if pPlayer:IsEverAlive() then
      pPlayer:AddNotification(NotificationTypes.NOTIFICATION_XYZ, "A player has cast Burning Blood, increasing the population of their cities and decreasing the population of all others!", "A player has cast Burning Blood!")
   end
end

Thank you very much. So my code now looks like this:
Spoiler :
Code:
function MurphysLaw()
	local user = Game.GetActivePlayer()
	local userCiv = Game.GetActiveCivilizationType()
	local Used = false    -- will be removed when the button code is made.
	if userCiv == GameInfoTypes["CIVILIZATION_EGYPT"] and Used == false then
		local Used = true    -- will be removed when the button code is made.
		local userCap = user:GetCapitalCity()
		local userPop = userCap:GetPopulation()
		userCap:SetPopulation((userPop + 2), true)
		for iPlayer=GameDefines.MAX_MAJOR_CIVS, GameDefines.MAX_CIV_PLAYERS-1, 1 do  
			local playerCap = iPlayer:GetCapitalCity()
			local playerPop = playerCap:GetPopulation()
			if playerPop >= 3 then
				playerCap:SetPopulation((playerPop - 2), true)
			elseif playerPop == 2 then
				playerCap:SetPopulation((playerPop - 1), true)
			end
		end
	end
        for i = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
           local pPlayer = Players[i]
           if pPlayer:IsEverAlive() then
           pPlayer:AddNotification(NotificationTypes.NOTIFICATION_XYZ, "A player has cast Burning Blood, increasing the population of their cities and decreasing the population of all others!", "A player has cast Burning Blood!")
           end
        end

I just noticed the Murphy's Law thing. I like that.;)
I have another request, my no city below 1 pop still standing. This one is not necessary for the code to work, but it would be helpful. Would there be a way to make a table in the Civ tables for Worldspells, like
Code:
<Row>
			<Type>CIVILIZATION_EGYPT</Type>
			<Description>TXT_KEY_CIV_EGYPT_DESC</Description>
			<ShortDescription>TXT_KEY_CIV_EGYPT_SHORT_DESC</ShortDescription>
			<Adjective>TXT_KEY_CIV_EGYPT_ADJECTIVE</Adjective>
			<CivilopediaTag>TXT_KEY_CIV5_EGYPT</CivilopediaTag>
			<DefaultPlayerColor>PLAYERCOLOR_EGYPT</DefaultPlayerColor>
			<ArtDefineTag>ART_DEF_CIVILIZATION_EGYPT</ArtDefineTag>
			<ArtStyleType>ARTSTYLE_MIDDLE_EAST</ArtStyleType>
                       [B] <Worldspell>WORLDSPELL_BURNING_BLOOD</Worldspell>[/B]
			<ArtStyleSuffix>_AFRI</ArtStyleSuffix>
			<ArtStylePrefix>AFRICAN </ArtStylePrefix>
			<PortraitIndex>5</PortraitIndex>
			<IconAtlas>CIV_COLOR_ATLAS</IconAtlas>
			<AlphaIconAtlas>CIV_ALPHA_ATLAS</AlphaIconAtlas>
			<MapImage>MapEgypt512.dds</MapImage>
			<DawnOfManQuote>TXT_KEY_CIV5_DAWN_EGYPT_TEXT</DawnOfManQuote>
			<DawnOfManImage>DOM_Ramesess.dds</DawnOfManImage>
			<DawnOfManAudio>AS2D_DOM_SPEECH_EGYPT</DawnOfManAudio>
		</Row>

Also, how would I get the AI to use this?
 
There's a couple problems here:
Code:
function MurphysLaw()
	[COLOR="Red"]local user = Game.GetActivePlayer()[/COLOR]
[COLOR="RoyalBlue"]--this gives you an integer; it would be best to call it something informative like iActivePlayer.
--To get a player object, you need another line: local activePlayer = Players[iActivePlayer][/COLOR]
	local userCiv = Game.GetActiveCivilizationType()
	[COLOR="red"]local Used = false    -- will be removed when the button code is made.[/COLOR]
[COLOR="RoyalBlue"]--Keep in mind that this variable is only visible in the current scope.
--That's what "local" is for. By scope I mean from here to the 3rd "end" below
--(i.e., the end at the same indent). After that, Lua will assume you don't need
--the value ever again and will "garbage collect" it.[/COLOR]
	if userCiv == GameInfoTypes["CIVILIZATION_EGYPT"] [COLOR="red"]and Used == false[/COLOR] then
[COLOR="RoyalBlue"]--Used will [B]always[/B] be false here. You just set it to false on the
--line above. How could it be anything other than false? (It can't)[/COLOR]
		[COLOR="red"]local Used = true[/COLOR]    -- will be removed when the button code is made.
[COLOR="RoyalBlue"]--This is not the same "Used" as the Used above. You have just created a new variable
--with the same name. The local means that it is only visible within this scope. [/COLOR] 
		local userCap = [COLOR="Red"]user[/COLOR]:GetCapitalCity()
[COLOR="RoyalBlue"]--This won't work because user was set above to be an integer; GetCapitalCity is a
--method that works on player objects, it won't work on an integer[/COLOR]
		local userPop = userCap:GetPopulation()
[COLOR="RoyalBlue"]--The above works, but only on the capital city (is that intended?)[/COLOR]
		userCap:SetPopulation((userPop + 2), true)
		for iPlayer=[COLOR="red"]GameDefines.MAX_MAJOR_CIVS, GameDefines.MAX_CIV_PLAYERS-1[/COLOR], 1 do  
[COLOR="RoyalBlue"]--This only loops through city states (the for loop below loops through major players)[/COLOR]
			local playerCap = iPlayer:GetCapitalCity()
			local playerPop = playerCap:GetPopulation()
			if playerPop >= 3 then
				playerCap:SetPopulation((playerPop - 2), true)
			elseif playerPop == 2 then
				playerCap:SetPopulation((playerPop - 1), true)
			end
[COLOR="RoyalBlue"]--The code above only decreases pop for capital cities.[/COLOR] 
		end
	end
        for i = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
           local pPlayer = Players[i]
           if pPlayer:IsEverAlive() then
           pPlayer:AddNotification(NotificationTypes.NOTIFICATION_XYZ, "A player has cast Burning Blood, increasing the population of their cities and decreasing the population of all others!", "A player has cast Burning Blood!")
           end
        end

Some other notes:

The function above is written to only ever work for the "active" player. Active player is the player whose computer the game is running on. In other words, the human in a single player game. So it will never work if an AI civ tries to use it. It would be better to set it up with an argument:
Code:
furnction RiverOfBlood(iPlayer)
	local player = Players[iPlayer]
and then code it to give boost to player and penalty to everyone else.

To get all cities for a player, use:
Code:
for city in player:Cities() do
	--This will loop through all cities owned by player.
	--"city" is a local variable inside this loop 
end

Keep in mind that local variables disappear outside of their scope. They can even become inaccessible within a scope if you reuse the name for another local variable (that's what bane_ does with Used above, though not intentionally).

You could make Used into a global variable (by omitting "local"), which would make it accessible outside the function. But that won't help here because it would still disappear when the player saves/exits the game. So you need to "persist" this information. There are several easy and more complicated solutions to this. The easy one is provided by the SaveGame database. Use it like this:
Code:
local SavedData = Modding.OpenSaveData()
SavedData:SetValue("UsedRiverOfBlood", 1)

You can find out if the spell has been cast with SavedData:GetValue("UsedRiverOfBlood") == 1. Remember to define SavedData as I did above. This will work even if the player exits/reloads.


Sorry for critiquing. I could just write the code. But I'd rather see you (and bane_) learn how to do it yourselves. It's not that hard if you understand variables and functions. A good exercise is to look at some actual functioning code and figure out (for yourself) what each line is actually doing.


Also, how would I get the AI to use this?
That's easy: have the AI call the function RiverOfBlood(iPlayer) with iPlayer being the id of the AI casting the spell. The question for you is: When do you want the AI to use it?

On the UI, whoward69 has some pretty extensive tutorials on this. It's a bit daunting to learn. For me it takes about 15 minutes to add a button somewhere. Question for you is: Where do you want the button? (Note: even though it is hard to learn, Civ5 is easier to do this sort of thing than Civ4.)
 
There's a couple problems here:
Code:
function MurphysLaw()
	[COLOR="Red"]local user = Game.GetActivePlayer()[/COLOR]
[COLOR="RoyalBlue"]--this gives you an integer; it would be best to call it something informative like iActivePlayer.
--To get a player object, you need another line: local activePlayer = Players[iActivePlayer][/COLOR]
	local userCiv = Game.GetActiveCivilizationType()
	[COLOR="red"]local Used = false    -- will be removed when the button code is made.[/COLOR]
[COLOR="RoyalBlue"]--Keep in mind that this variable is only visible in the current scope.
--That's what "local" is for. By scope I mean from here to the 3rd "end" below
--(i.e., the end at the same indent). After that, Lua will assume you don't need
--the value ever again and will "garbage collect" it.[/COLOR]
	if userCiv == GameInfoTypes["CIVILIZATION_EGYPT"] [COLOR="red"]and Used == false[/COLOR] then
[COLOR="RoyalBlue"]--Used will [B]always[/B] be false here. You just set it to false on the
--line above. How could it be anything other than false? (It can't)[/COLOR]
		[COLOR="red"]local Used = true[/COLOR]    -- will be removed when the button code is made.
[COLOR="RoyalBlue"]--This is not the same "Used" as the Used above. You have just created a new variable
--with the same name. The local means that it is only visible within this scope. [/COLOR] 
		local userCap = [COLOR="Red"]user[/COLOR]:GetCapitalCity()
[COLOR="RoyalBlue"]--This won't work because user was set above to be an integer; GetCapitalCity is a
--method that works on player objects, it won't work on an integer[/COLOR]
		local userPop = userCap:GetPopulation()
[COLOR="RoyalBlue"]--The above works, but only on the capital city (is that intended?)[/COLOR]
		userCap:SetPopulation((userPop + 2), true)
		for iPlayer=[COLOR="red"]GameDefines.MAX_MAJOR_CIVS, GameDefines.MAX_CIV_PLAYERS-1[/COLOR], 1 do  
[COLOR="RoyalBlue"]--This only loops through city states (the for loop below loops through major players)[/COLOR]
			local playerCap = iPlayer:GetCapitalCity()
			local playerPop = playerCap:GetPopulation()
			if playerPop >= 3 then
				playerCap:SetPopulation((playerPop - 2), true)
			elseif playerPop == 2 then
				playerCap:SetPopulation((playerPop - 1), true)
			end
[COLOR="RoyalBlue"]--The code above only decreases pop for capital cities.[/COLOR] 
		end
	end
        for i = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
           local pPlayer = Players[i]
           if pPlayer:IsEverAlive() then
           pPlayer:AddNotification(NotificationTypes.NOTIFICATION_XYZ, "A player has cast Burning Blood, increasing the population of their cities and decreasing the population of all others!", "A player has cast Burning Blood!")
           end
        end

Some other notes:

The function above is written to only ever work for the "active" player. Active player is the player whose computer the game is running on. In other words, the human in a single player game. So it will never work if an AI civ tries to use it. It would be better to set it up with an argument:
Code:
furnction RiverOfBlood(iPlayer)
	local player = Players[iPlayer]
and then code it to give boost to player and penalty to everyone else.

To get all cities for a player, use:
Code:
for city in player:Cities() do
	--This will loop through all cities owned by player.
	--"city" is a local variable inside this loop 
end

Keep in mind that local variables disappear outside of their scope. They can even become inaccessible within a scope if you reuse the name for another local variable (that's what bane_ does with Used above, though not intentionally).

You could make Used into a global variable (by omitting "local"), which would make it accessible outside the function. But that won't help here because it would still disappear when the player saves/exits the game. So you need to "persist" this information. There are several easy and more complicated solutions to this. The easy one is provided by the SaveGame database. Use it like this:
Code:
local SavedData = Modding.OpenSaveData()
SavedData:SetValue("UsedRiverOfBlood", 1)

You can find out if the spell has been cast with SavedData:GetValue("UsedRiverOfBlood") == 1. Remember to define SavedData as I did above. This will work even if the player exits/reloads.


Sorry for critiquing. I could just write the code. But I'd rather see you (and bane_) learn how to do it yourselves. It's not that hard if you understand variables and functions. A good exercise is to look at some actual functioning code and figure out (for yourself) what each line is actually doing.



That's easy: have the AI call the function RiverOfBlood(iPlayer) with iPlayer being the id of the AI casting the spell. The question for you is: When do you want the AI to use it?

On the UI, whoward69 has some pretty extensive tutorials on this. It's a bit daunting to learn. For me it takes about 15 minutes to add a button somewhere. Question for you is: Where do you want the button? (Note: even though it is hard to learn, Civ5 is easier to do this sort of thing than Civ4.)

I'm busy right now, so I only skimmed through your post. I'll take a better look at it later when I have more time, and try to fix the other code. Thank you very much for all your advice.
I want the AI to use the spell at different times depending on their strategy. For example, if they're going for an early rush, they should use it early, while if they're going for a late game win they should use it later. They should not use it in the first 15-20 turns or so, bec. most caps aren't pop 3 by then. They should be inclined to use it right after founding a new city. They should be less likely to use it the more popular they are (so they don't hurt allies). That's all I can think of of the top of my head.
As for where the button should be, for now I want to be with all the unit actions, like fortify, alert, and heal.
 
I want the AI to use the spell at different times depending on their strategy. For example, if they're going for an early rush, they should use it early, while if they're going for a late game win they should use it later. They should not use it in the first 15-20 turns or so, bec. most caps aren't pop 3 by then. They should be inclined to use it right after founding a new city. They should be less likely to use it the more popular they are (so they don't hurt allies). That's all I can think of of the top of my head.
The hardest part about AI is that you have to understand the game mechanics and you have to know when you want the AI to do something. The AI has neither understanding nor wants. If you can define these completely and very precisely, then the programming isn't so hard.

As for where the button should be, for now I want to be with all the unit actions, like fortify, alert, and heal.
I suspect you want this because FFH did it that way. Seriously, it's going to be easier to add it not as a unit action. You probably do want to add unit actions eventually, but that adds a whole layer of complexity. S3rgeus did it in the dll. I did it in Lua/SQL (though if I had to do it from scratch, I'd use S3rgeus' dll method). What about having "civ actions" as buttons on the bottom row to the right of the unit panel? That would be about 5% of the effort of learning to add unit actions.
 
The hardest part about AI is that you have to understand the game mechanics and you have to know when you want the AI to do something. The AI has neither understanding nor wants. If you can define these completely and very precisely, then the programming isn't so hard.

So I would have to do something like 'If using early rush strategy, 50% to use Burning Blood on turns 30, 31, 32, 33'?

I suspect you want this because FFH did it that way. Seriously, it's going to be easier to add it not as a unit action. You probably do want to add unit actions eventually, but that adds a whole layer of complexity. S3rgeus did it in the dll. I did it in Lua/SQL (though if I had to do it from scratch, I'd use S3rgeus' dll method). What about having "civ actions" as buttons on the bottom row to the right of the unit panel? That would be about 5% of the effort of learning to add unit actions.

If figured making it a unit action would be the simplest way to do it, but if its not I'll go with the easiest way.

Edit - Before I start figuring out the code (or just trying to) what woud I need to change to make it affect all cities - not just caps.
 
So I would have to do something like 'If using early rush strategy, 50% to use Burning Blood on turns 30, 31, 32, 33'?

Many mods are organized around PlayerDoTurn. I would have a separate function that checks for proper conditions for world spell. Something like:

Code:
local g_lastTurn = -1

function OnPlayerDoTurn(iPlayer)
	local thisTurn = Game.GetGameTurn()
	if g_lastTurn < thisTurn then
		g_lastTurn = thisTurn 
		--Put things you want to happen once per [B]game[/B] turn here

	end
	--Put things you want to happen once per [B]player[/B] turn here
	if iPlayer < GameDefines.MAX_MAJOR_CIVS then
		--Major civs only
		TestWoldSpell(iPlayer)
	end
end
GameEvents.PlayerDoTurn.Add(OnPlayerDoTurn)

TestWoldSpell(iPlayer)
	local player = Players[iPlayer]
	--find appropriate spell for the civilizationType
	--test whether spell is available (not cast and any other conditions)
	if bAvailable then
		if player:IsHuman() then
			--make sure button is present in human UI
		else
			--logic to see if AI should fire it
		end
	end
end

RiverOfBlood is going to be a separate function that actually does the spell. The code above is just to get you started on your mod.

Sorry... don't have time to fill in bottom function.

Don't worry about UI until your function works. You can test your function by calling it from the Fire Tuner directly.

If figured making it a unit action would be the simplest way to do it, but if its not I'll go with the easiest way.

Buttons centered at the bottom are definitely pretty straightforward. You'll need to add a pair of files, one Lua and one XML, with the same name. See if you can add it as a very simple button from whoward69's tutorial. Don't get fancy yet. Just a button.

After that works, then you can learn how to add spells as a table and have the button use an Icon from the table.
 
Code:
for city in player:Cities() do
	--This will loop through all cities owned by player.
	--"city" is a local variable inside this loop 
end

bane_'s code above is correct for Get and SetPopulation. You just have to change the variable names: "playerCap" is now "city" (because the iterator above puts the city object in a variable called "city"). The population variable can be what you want. It's better to choose something informative though, like "cityPop", rather than "playerPop" which implies something very different. (You can call it "MyPoniesAndUnicorns" if you like, but it's easier if you choose variable names that tell you what the variable is.)
 
Top Bottom