[HELP] Exploiting mountains (LUA/XML)

Nyjene

Chieftain
Joined
Feb 13, 2018
Messages
9
Greetings !

I'm struggling ,since three days to create a UA for a Civilization.

The effect is to give local happiness to a city which have a mountain within its border (adjacent, nearby or at range, I don't know yet).

LUA
I guess it's very feasible with LUA but while I'm doing ok to understand and modify existing LUA, I just can't manage to make my own from scratch. The problem is many mods use mountains to give a yield effect (food, faith, culture etc.) and for each mountain.

XML
Here it starts to be complicated. The easy way would be to simply put <NearbyMountainRequired> on a dummy building (given by <FreeBuilding>). The problem is that the effect only occurs when the city is founded or conquered. So if you do not directly put your city adjacent to a mountain (and so directly in your borders), the dummy building will never appear. It is a big problem, mostly for the AI, and it's too much restrictive and not properly balanced.
I then thought of another UA in the same style. Giving a small happiness bonus for a city founded on hills. And a better bonus (still happiness) if the city is adjacent to a mountain. This way I eliminate the problem of borders while still prevent the UA to be too much restrictive (but for the gameplay and roleplay it has to be a UA that encourages to build a compact civilization around mountains). However... <Traits> do not allow for twice <FreeBuilding>, and knowing how XML works I guess <FreeBuilding> can't be used twice in a dummy Palace. So I simply can't apply it.

Consequently, anyone has an idea on how to make it works ? If by LUA, I will need help. If through XML (or SQL?), do I miss a workaround ?
In the end, I might keep the bonus for hills, but in this case I can set it independently with XML. But I absolutely want it to works for mountains and here I struggle.
 
Two ways to do this is Lua, both need a dummy building to add local/global happiness

1) At the start of the players turn, check to see if they have the required trait (whatever), and if so loop all their cities, remove the dummy buildings, loop all the tiles the city controls, and add back in the required number of dummy buildings.

2) (Which is the way I'd do it). As mountain plots are a) rare and b) rarely expanded onto. At the start of the game (or game re-load), loop all the plots on the map and remember which ones are mountains and if so are they owned by a city. At the start of the players turn, check to see if they have the required trait (whatever), and if so loop all the mountain plots and see if their ownership has changed, if so adjust the dummy buildings in the owning (ex-owning) cities
 
Thanks for your quick answer !

So first, I need to learn how to do a loop that checks mountain through LUA. When you talk about "trait", you mean the <Trait> in XML, right ? Tiles controled by the city are those within range of citizens (3 tiles) or simply those in borders ?
Then, well, I need to learn how to exploit the checked plots.

Just in case, do you have any idea of a mod that uses a similar script ? It would help me to better understand and learn LUA syntax.
 
Lua would something likes this:

Code:
GameEvents.PlayerDoTurn.Add(function(ePlayer)
    local pPlayer = Players[ePlayer]
    for pCity in pPlayer:Cities() do
        local bAnyMountains = 0
        for i = 0, pCity:GetNumCityPlots() - 1, 1 do
            local pPlot = pCity:GetCityIndexPlot(i)
            if pPlot:GetFeatureType() == GameInfoTypes["FEATURE_MOUNTAIN"] then
                bAnyMountains = 1
            end
        end
        pCity:SetNumRealBuilding(GameInfoTypes["DUMMY_MOUNTAIN_HAPPINESS"], bAnyMountains)
    end
end)

And the dummy building would be something like

Code:
    <BuildingClasses>
        <Row>
            <Type>DUMMY_MOUNTAIN_HAPPINESS_CLASS</Type>
            <DefaultBuilding>DUMMY_MOUNTAIN_HAPPINESS</DefaultBuilding>
            <Description>TXT_KEY_DUMMY</Description>
        </Row>
    </BuildingClasses>
    <Buildings>
        <Row>
            <Type>DUMMY_MOUNTAIN_HAPPINESS</Type>
            <BuildingClass>DUMMY_MOUNTAIN_HAPPINESS_CLASS</BuildingClass>
            <UnmoddedHappiness>1</UnmoddedHappiness>
            <Cost>-1</Cost>
            <FaithCost>-1</FaithCost>
            <PrereqTech>NULL</PrereqTech>
            <GreatWorkCount>-1</GreatWorkCount>
            <Description>TXT_KEY_DUMMY</Description>
            <NeverCapture>true</NeverCapture>
        </Row>
    </Buildings>
 
By "checking to see if they have the trait", whoward likely meant just a simple check to see which civ is your mod civ. That can be done by adding a line to check for that in the function Jiska posted, like so:

Code:
GameEvents.PlayerDoTurn.Add(function(ePlayer)
   local pPlayer = Players[ePlayer]
   if pPlayer:GetCivilizationType() == GameInfoTypes.CIVILIZATION_YOURCIV then
      ...

Note that since this adds another if-then loop to the function, we'll need another "end" at the end of the function to close it. It's recommended that you indent all the lines after the "if" statement one extra level to remind yourself to add the extra "end".
 
Yes, and I forgot, You'll have to check if the tile is owned, something like

Code:
if pPlot:GetOwner() == ePlayer
 
So my attempt at this is :

Code:
GameEvents.PlayerDoTurn.Add(function(ePlayer)
    local pPlayer = Players[ePlayer]
    if pPlayer:GetCivilizationType() == GameInfoTypes.CIVILIZATION_MYCIV then
        for pCity in pPlayer:Cities() do
            local bAnyMountains = 0
            for i = 0, pCity:GetNumCityPlots() - 1, 1 do
                local pPlot = pCity:GetCityIndexPlot(i)
                if pPlot:GetFeatureType() == GameInfoTypes["FEATURE_MOUNTAIN"] then
                    bAnyMountains = 1
                end
            end
            if pPlot:GetOwner() == ePlayer then
                pCity:SetNumRealBuilding(GameInfoTypes["DUMMY_MOUNTAIN_HAPPINESS"], bAnyMountains)
            end
        end
    end
end)

I'm sure I'm doing something wrong, but that's how I would do it based on what I understand (and of course based on your initial codes).

May I ask information on certain lines ?

for i = 0, pCity:GetNumCityPlots() - 1, 1 do = Why -1 ? It means false because we set the i(integer) at 0 so -1 = false and 1 = true ?
for pCity in pPlayer:Cities() do
local bAnyMountains = 0
= I'm not sure to understand this. Is it to check mountain plots on the map ?

EDIT:

As expected, I failed. I have this error in LUA log :
Spoiler error line :
Runtime Error: C:\Users\Alexandre\Documents\My Games\Sid Meier's Civilization 5\MODS\[...]:17: attempt to index global 'pPlot' (a nil value)
 
Last edited:
Code:
GameEvents.PlayerDoTurn.Add(function(ePlayer)
--Events.PlayerDoTurn fires every turn, and it also fires every function registered to it, such as the unnamed one we'll define below
    local pPlayer = Players[ePlayer]
    --This defines the local variable pPlayer (the little p refers to a pointer type, but this doesn't matter in lua) as the entry in the Players table ePlayer, which is passed by DoTurn, so this defines the player who's turn it is
    if pPlayer:GetCivilizationType() == GameInfoTypes.CIVILIZATION_MYCIV then
        --if the player whose turn it is returns GameInfoTypes.CIVILIZATION_MYCIV on the function call GetCivilizationType() then
        --You'll want to change CIVILIZATION_MYCIV to the civ whose UA you want to mod
        for pCity in pPlayer:Cities() do
        --for each entry in the list of cities created by pPlayer:Cities(), do something, and we'll call that entry pCity each time we do it
            local bAnyMountains = 0
            --Define a local variable, bAnyMountains, which is set to 0
            for i = 0, pCity:GetNumCityPlots() - 1, 1 do
                --for each Plot (tile)) in the City's available plot radius
                local pPlot = pCity:GetCityIndexPlot(i)
                --Define a variable to refer to that Plot
                if pPlot:GetFeatureType() == GameInfoTypes["FEATURE_MOUNTAIN"] and pPlot:GetOwner() == ePlayer then
                    --If that plot is a mountain AND the owner of that Plot is the Player, change bAnyMountains to 1
                    bAnyMountains = 1
                end
            end
            pCity:SetNumRealBuilding(GameInfoTypes["DUMMY_MOUNTAIN_HAPPINESS"], bAnyMountains)
            --Set the number of dummy happiness type buildings in the city equal to bAnyMountains, which will either be 1 or 0
        end
    end
end)

Bon chance.
 
"for i = 0, x - 1, y" is the syntax of a for loop in lua:
- "for i = 0": here you set up a temporary variable that your for loop will use to count the number of times it will run, so to speak. When we're looping through a list (in this case, we're looping through each city plot owned by pCity), this is generally set to 0 because in programming, most lists start at index 0.
- "x - 1": the second field is to define the "stopping point" of the loop, after the value of i reaches this value it will stop. In this example, the stopping point is based on the value returned by GetNumCityPlots; but as explained above, the indices of the city plots (given by pCity:GetCityIndexPlot(i)) start at 0, so the index of the final plot will be actually 1 less than the total number of city plots. Hence the -1.
- "y": the third field defines how much i is increased by whenever the loop finishes once. We're looping through items in the list one at a time, so this is 1.
 
This is not correct
Code:
pPlot:GetFeatureType() == GameInfoTypes["FEATURE_MOUNTAIN"]
No such Feature in table Features as FEATURE_MOUNTAIN

The result of
Code:
GameInfoTypes["FEATURE_MOUNTAIN"]
will be "-1", which will be the same thing as no feature on the plot. So the code will equate a plot with no features (ie, a flat grass plot without forest or marsh for example) as being a plot with FEATURE_MOUNTAIN

Code:
if pPlot:IsMountain() and (pPlot:GetOwner() == ePlayer) then
     bAnyMountains = 1
end

--------------------------------------

Code:
GameEvents.PlayerDoTurn.Add(function(ePlayer)
	local pPlayer = Players[ePlayer]
	if pPlayer:GetCivilizationType() == GameInfoTypes.CIVILIZATION_MYCIV then
		for pCity in pPlayer:Cities() do
			pCity:SetNumRealBuilding(GameInfoTypes.DUMMY_MOUNTAIN_HAPPINESS, 0)
			for i = 0, pCity:GetNumCityPlots() - 1, 1 do
				local pPlot = pCity:GetCityIndexPlot(i)
				if (pPlot ~= nil) and pPlot:IsMountain() and (pPlot:GetOwner() == ePlayer) then
					pCity:SetNumRealBuilding(GameInfoTypes.DUMMY_MOUNTAIN_HAPPINESS, 1)
					break
				end
			end
		end
	end
end)
 
Last edited:
Thanks all !

@Jiska Thanks for this ! I keep it to help me learn better LUA.

@Raitaki Thank you, I do understand better now.

@LeeS It works from a quick test ! I just have a few questions :

Code:
for pCity in pPlayer:Cities() do
            pCity:SetNumRealBuilding(GameInfoTypes.DUMMY_MOUNTAIN_HAPPINESS, 0)
Is it method 1) mentionned by whoward69 ? If I understand properly, it is the line that removes the building(s) if the conditions are not met anymore, right ?

Code:
break
First time I see it, or at least I pay attention to it. What does it mean ?
 
When lua encounters a break statement, it breaks out of the current for/while loop. Here, in case the preceding if statement returns true (a mountain tile is found), then after adding the dummy building the break statement ends the for loop right then and there so it doesn't waste time checking for more mountains in that city since additional mountains don't matter.
 
Top Bottom