1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

[HELP] Exploiting mountains (LUA/XML)

Discussion in 'Civ5 - Creation & Customization' started by Nyjene, Apr 26, 2021.

  1. Nyjene

    Nyjene Chieftain

    Joined:
    Feb 13, 2018
    Messages:
    9
    Gender:
    Male
    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.
     
  2. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,537
    Location:
    Near Portsmouth, UK
    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
     
  3. Nyjene

    Nyjene Chieftain

    Joined:
    Feb 13, 2018
    Messages:
    9
    Gender:
    Male
    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.
     
  4. Jiska

    Jiska Warlord

    Joined:
    Apr 23, 2016
    Messages:
    138
    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>
     
  5. Raitaki

    Raitaki Chieftain

    Joined:
    Oct 19, 2015
    Messages:
    16
    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".
     
  6. Jiska

    Jiska Warlord

    Joined:
    Apr 23, 2016
    Messages:
    138
    Yes, and I forgot, You'll have to check if the tile is owned, something like

    Code:
    if pPlot:GetOwner() == ePlayer
     
  7. Nyjene

    Nyjene Chieftain

    Joined:
    Feb 13, 2018
    Messages:
    9
    Gender:
    Male
    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: Apr 27, 2021
  8. Jiska

    Jiska Warlord

    Joined:
    Apr 23, 2016
    Messages:
    138
    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.
     
  9. Raitaki

    Raitaki Chieftain

    Joined:
    Oct 19, 2015
    Messages:
    16
    "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.
     
  10. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    7,168
    Location:
    Illinois, USA
    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: Apr 27, 2021
  11. Nyjene

    Nyjene Chieftain

    Joined:
    Feb 13, 2018
    Messages:
    9
    Gender:
    Male
    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 ?
     
  12. Raitaki

    Raitaki Chieftain

    Joined:
    Oct 19, 2015
    Messages:
    16
    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.
     

Share This Page