Trait to Increase Tile Yields Near Mountains?

OnlyHeStandsHere

Chieftain
Joined
May 4, 2018
Messages
46
Heya! I'm new to modding Civ5 and am in the process of making my first custom civ. Hopefully this is the right place to post this (seems like there are other help topics here). I'm trying to make a leader trait which increases the food yield near mountains.

Is there a way to increase the yields of all tiles near mountains? There's the "Improvement_AdjacentMountainYieldChanges" from the Inca Terrace Farm, but that seems to require being on a specific improvement. I'm not sure what I'd have to do to apply a yield change to all worked tiles (including tiles without improvements) next to a mountain. Any help or pointers would be appreciated.
 
You'd have to do it via lua programming and to be honest it would be rather complicated.

Using XML or SQL, even if you tie the effect to improvements you would have to create a duplicate improvement for all the standard improvements, duplicate all their standard yield changes and the like, and then for the duplicate versions of the improvements you would have to add in the <Improvement_AdjacentMountainYieldChanges> specifications as well as making all the duplicates "uniques" to your civilization. And then your code would not necessarily catch and account for anything from another mod that adds new improvements to the game. And unimproved plots would not be affected, either.

Come to think of it either method seems like it is equally complicated.
 
I was afraid it might come to something like that. The second method sounds like something I could manage, although it would certainly be tedious. And it would end up with an inelegant solution which doesn't exactly do what I'm looking for. I'll shelve the idea for now. Thanks for the reply!

I've actually been looking at your Building Guide and using it as reference, so thanks for that as well! My back-up idea is to use a hidden-building for a thematically similar effect: increasing yields when the city is built next to a mountain.
 
So, while I've moved on to other things, I've somehow looped myself back to adjacent mountain things. I figured I'll post it here so as to not bloat the forum with new topics (though it's a question about Lua, so I don't know if that means the topic should be moved or not). So, yeah, I've done some coding before but know very little about Lua specifically. Trying to learn by doing right now, but I'm not too familiar with Civ's vocabulary.

What I'm experimenting with now is making a unique fortress that gives a better defensive bonus if next to a mountain. Though maybe it can work for tile yields as well. I intend to check if a plot has the unique improvement's ID, set the base defense modifier for the improvement, then check for adjacent mountains and update the number if true. I've scrounged around google and these forums and put together (i.e. blatantly copied) this function for checking if there's an adjacent mountain:

Code:
  function CheckForMountain(NextToMountain)
        local NextToMountain = false

        for direction = 0, DirectionTypes.NUM_DIRECTION_TYPES - 1, 1 do
            local pAdjacentPlot = Map.PlotDirection(pPlot:GetX(), pPlot:GetY(), direction)
                if pAdjacentPlot:IsMountain then 
                    NextToMountain = true
                end
        end
    end

My first question is simply: does this look correct? And then, is this redundant to have it return the NextToMountain boolean?

Lastly and probably most-importantly: I have no idea which hook to use, nor what indicates the plot where the code starts checking from. I know you can use a hook to check at the start of each turn, and then you could check every tile in the map based on its grid size... but that seems inefficient. Or maybe that is the way, and I'm grossly overestimating the processing cost of checking the entire map each turn.
 
My first question is simply: does this look correct?
No. You have syntactical errors as well as conceptual errors.
  • This required in order to check whether a plot is a mountain:
    Code:
    pAdjacentPlot:IsMountain()
    You are missing the needed "()" in the method.
  • You are sending an argument value NextToMountain to the function called CheckForMountain, and then within the function you are over-riding that passed argument value with this line
    Code:
    local NextToMountain = false
  • Variable pPlot is undefined and therefore "nil", which will cause the code to fail
And then, is this redundant to have it return the NextToMountain boolean?
  • You are never returning any value from the function so the question is moot. You would have to add
    Code:
    return NextToMountain
    Just before the final "end" in the function
  • Argument values are passed to a function on the line that defines the function-name, but these argument-values are not returned from the function unless a "return VariableNameValue" line is included within the function.
This is properly-created function to check whether a given plot is adjacent to a mountain tile:
Code:
function PlotIsAdjecntToMountain(pPlot)
	local iPlotX, iPlotY = pPlot:GetX(), pPlot:GetY()
	for direction = 0, DirectionTypes.NUM_DIRECTION_TYPES - 1, 1 do
		local pAdjacentPlot = Map.PlotDirection(iPlotX, iPlotY, direction)
		if pAdjacentPlot and pAdjacentPlot:IsMountain() then 
			return true
		end
	end
	return false
end
See the differences?

But you cannot directly subscribe this function to an lua hook-event, because you need other code to get the plot object that will be passed into the function in the place of variable pPlot.

If we wanted to determine whether a newly founded city is adjacent to a mountain tile (for example) we can do as this:
Code:
function PlotIsAdjecntToMountain(pPlot)
	local iPlotX, iPlotY = pPlot:GetX(), pPlot:GetY()
	for direction = 0, DirectionTypes.NUM_DIRECTION_TYPES - 1, 1 do
		local pAdjacentPlot = Map.PlotDirection(iPlotX, iPlotY, direction)
		if pAdjacentPlot and pAdjacentPlot:IsMountain() then 
			return true
		end
	end
	return false
end
function OnPlayerCityFounded(iPlayer, iCityX, iCityY)
	local pPlotObject = Map.GetPlot(iCityX, iCityY)
	local pCityObject = pPlotObject:GetPlotCity()
	local sCityName = pCityObject:GetName()
	if PlotIsAdjecntToMountain(pPlotObject) then
		print("The newly founded city of " .. sCityName .. " is adjecent to a mountain tile")
	else
		print("The newly founded city of " .. sCityName .. " is not adjecent to a mountain tile")
	end
end
GameEvents.PlayerCityFounded.Add(OnPlayerCityFounded)
  • The GameEvents.PlayerCityFounded hook event passes three arguments to any function subscribed to it as a listener, and we can then use the data passes from the gamecore to take the action we want.
  • In the case of this particular hook event, the argument data are for the Player's ID# from the lua "Players" table, and the XY coordinates of the newly-founded city.
  • In the example I named the three variables I am using to "recieve" the data passed from the gamecore as iPlayer, iCityX, iCityY but I can call them anything I want so long as I remember which variable-name I am creating is recieving which type of data. So I could make the PlayerCityFounded function look like this:
    Code:
    function OnPlayerCityFounded(Cheeseburgers, Lettuce, Fries)
    	local pPlotObject = Map.GetPlot(Lettuce, Fries)
    	local pCityObject = pPlotObject:GetPlotCity()
    	local sCityName = pCityObject:GetName()
    	if PlotIsAdjecntToMountain(pPlotObject) then
    		print("The newly founded city of " .. sCityName .. " is adjecent to a mountain tile")
    	else
    		print("The newly founded city of " .. sCityName .. " is not adjecent to a mountain tile")
    	end
    end
    But doing so would not be a very good idea since it would be hard to remember to what these variable-names are referring in terms of the type of data they recieve from the gamecore.
    Lastly and probably most-importantly: I have no idea which hook to use, nor what indicates the plot where the code starts checking from. I know you can use a hook to check at the start of each turn, and then you could check every tile in the map based on its grid size... but that seems inefficient. Or maybe that is the way, and I'm grossly overestimating the processing cost of checking the entire map each turn.
    It depends entirely on what you want to do with checking whether a plot is adjacent to a mountain, but it's generally not a good idea to scan through all a game's map-plots every turn because it can get pretty processing-heavy.

    Unfortunately for what you are wanting to do with Improvements (ie, a unique fortress) I cannot think of a valid lua method that allows you to alter the base-strength of what an improvement does combat-wise from lua. All fortresses do is give units standing on them increased combat-power modifiers as defined within the XML/SQL database. You would have to have two versions of the improvement, and hook to the BNW GameEvents.BuildFinished to detect when the "standard" version of the unique improvement is completed, and replace it with the "upgraded" version of the unique improvement if the plot is adjacent to a mountain. Just remember that BuildFinished only works with BNW.
 
This is very helpful. Thank you for taking the time to respond with all that. Some of the mistakes I feel just silly about. But, I completely misunderstood how the code would read or return values, and now a lot of examples I've seen make much more sense to me. (I'm also trying to wrack my brain as for WHY I thought it worked the way it did.) Seeing the two functions together in their context is immensely enlightening to me.

But you cannot directly subscribe this function to an lua hook-event, because you need other code to get the plot object that will be passed into the function in the place of variable pPlot.

That was something I was realizing, but I hadn't a clue as to what code I'd need to define the plot. Again, thanks for the very in-depth answer.

Unfortunately for what you are wanting to do with Improvements (ie, a unique fortress) I cannot think of a valid lua method that allows you to alter the base-strength of what an improvement does combat-wise from lua. All fortresses do is give units standing on them increased combat-power modifiers as defined within the XML/SQL database.

When I was looking through the Lua references online, I came across the 'DefenseModifier' value under the plot section. From its name, I assumed it means the cumulative defensive bonuses on a plot. Does that value do something different, and/or cannot be updated directly?
 
Code:
Plot:DefenseModifier(args)
is a "get" method, rather than a "set" method, so far as I recall.

It tells you what the modifier value is: it does not allow you to set or adjust the modifier value.
 
Hm... Could one do a work-around where the game adds a defensive bonus directly to the unit? Some sort of thing that checks a unit's plot to see if it is both next to a mountain and on top of the special improvement?

I don't know if something like SetBaseCombatStrength, UnitCombatModifier, or UnitClassDefenseModifier would allow something like that. I'm having a tough time distinguishing getters and setters from the Lua reference, atleast the ones that don't have "get" or "set" in the names. I also assume I'd have to be careful about permanently changing a unit's combat strength for the rest of the game, or having it increase each turn accidentally. I'm not sure how or when Civ recalculates or updates a unit's stats/modifiers.
 
Hm... Could one do a work-around where the game adds a defensive bonus directly to the unit? Some sort of thing that checks a unit's plot to see if it is both next to a mountain and on top of the special improvement?
Certainly. A promotion with <DefenseMod> (like the BNW Iron Plating promotion) would be the easiest way if you're going that route. Hook into BNW GameEvents.BuildFinished (as LeeS suggested earlier) to grant the promotions to units that are standing on the tile when the special improvement is constructed. Also hook into GameEvents.UnitSetXY to grant or remove the promotion based on whether the conditions are still met (I.e. is this unit still adjacent to a mountain on the special improvement?).
While UnitSetXY is quite an expensive function, especially in the late game when there can be hundreds of units, it is certainly not as bad as scanning over every plot on the map every turn or so.

That being said, replacing the special improvement with a special improvement with a defense modifier when the condition is met would also work, as was suggested earlier.

I don't know if something like SetBaseCombatStrength, UnitCombatModifier, or UnitClassDefenseModifier would allow something like that.
[..]snip[..]
I also assume I'd have to be careful about permanently changing a unit's combat strength for the rest of the game, or having it increase each turn accidentally. I'm not sure how or when Civ recalculates or updates a unit's stats/modifiers.
SetBaseCombatStrength sets the combat strength of a unit to a raw value. As far as I know, the game never resets a unit's combat strength to the default value after changing it with SetBaseCombatStrength. Note that there is no equivalent method for setting ranged combat strength (outside of VMC/CP modded DLL). Note that setting a unit's combat strength affects its capabilities to defend as well as to attack!

The other two are getters. pUnit:UnitCombatModifier(UnitCombatTypes.UNITCOMBAT_MOUNTED) returns the combat modifier from pUnit to mounted units. pUnit:UnitClassDefenseModifier(UnitClassTypes.UNITCLASS_ARCHER) returns the defense modifier from pUnit towards units of the Archer class.

I'm having a tough time distinguishing getters and setters from the Lua reference, atleast the ones that don't have "get" or "set" in the names.
Look at their parameters and return values! Usually, setters 'return' void, and getters return something that is not void. Furthermore, setters require the new value to set to as a parameter, whereas getters don't. E.g. UnitClassDefenseModifier requires a unitClass as a parameter, and no integer value for a new defense modifier, hence it is probably a getter.
 
Last edited:
I've been working on the improvement-replacement method (might try the other suggested method afterwards for science) and I'm having issue getting the function to load in. Should the hook be something like this:

Code:
GameEvents.BuildFinished.Add(ReplaceRedoubt)

From what I've gathered from the references, this hook takes in four arguments. So, I have the following when I define the replacement function:

Code:
function ReplaceRedoubt(iPlayer, iImprovX, iImprovY, iImprovement)
local pPlotObject = Map.GetPlot(iImprovX, iImprovY)
    local pImprovObject = iImprovement:GetImprovementType()
    local RedoubtID = 40
    local ImprovedRedoubtID = 41
    print("Doing the Thing")
    if PlotIsAdjacentToMountain(pPlotObject) and pImprovObject == RedoubtID then
        pPlotObject:SetImprovementType(ImprovedRedoubtID)
        print("Replacing the Redoubt")                                           
    end
end

The prints don't show up, which leads me to believe that the hook isn't even firing off. Or that I completely missed some basic part of Lua. I've also set the file's "Import into VFS" to true. I've been trying to figure out what I can on my own rather than coming back for every question, but I'm hitting a bit of a wall here.
 
You need the file set up as an InGameUIAddin. "Import Into VFS" should be "false". whoward69's what ModBuddy setting for what file types tutorial And post #3 from that tutorial: https://forums.civfanatics.com/thre...addin-or-updatedatabase.487846/#post-12212467

You have fatal syntax error. Improvements are never "objects" so are never "indexable" as you are trying here
Code:
iImprovement:GetImprovementType()

What the game passes for that fourth argument you are using as iImprovement is the Improvement's ID # from database table <Improvements>. So you can directly compare RedoubtID against iImprovement, and this should actually be the first thing your function does even before deciding if it needs to do anything else, since this will cause the function to only do anything when the completed improvement is a RedoubtID.

Also, don't hard-code your improvement ID #s. Use
Code:
local RedoubtID = GameInfoTypes["IMPROVEMENT_SOMETHING"]
local ImprovedRedoubtID = GameInfoTypes["IMPROVEMENT_SOMETHING_BETTER"]
And you should really move these two definitions out of your function and state them just above the line
Code:
function ReplaceRedoubt(iPlayer, iImprovX, iImprovY, iImprovement)
Remember that this function is going to fire a lot over the course of a game, so don't keep looking up the same information from the game's database every time the function fires.

When swapping one improvement for another, first eliminate the existing terrain improvement before placeing the new one
Code:
Plot:SetImprovementType(-1)
Plot:SetImprovementType(ImprovedRedoubtID)
 
And here I thought I was getting the hang of this. xD I even had looked over that post before, but I must have confused what I needed to do with one of the other methods.

Thanks so much for the help. I just tested it after all the changes, and it works! I've still got a ways to go, but I'm beginning to better understand things bit by bit.
 
Back
Top Bottom