Civ VI - LUA Mods - Culture Bombs for Districts & Improvements

(1) if not (pOtherOwner ~= pPlayer and pOtherDistrict ~= -1) then -- this requires that (a) there be another owner than pPlayer AND that there be a district type on the tile. If both conditions are satisfied, the tile ownership remains unchanged.
I've updated this line of the code now to read as follows:

if not (pOtherOwner ~= pPlayer and (pOtherDistrict ~= -1 or pOtherWonder ~= -1)) then

this should check for the presence of Districts OR Wonders owned by another civ.

(2) First, why would you want to make sure that the plot is within 3 tiles of a city? That's not a requirement of the Culture Bomb in the original base game mechanics. In fact, the original Culture Bomb mechanics does not care EVEN if the center of the culture bomb is within 3 plots of the city. As long as the acquired plot is within this radius: Global Parameters (PLOT_INFLUENCE_MAX_ACQUIRE_DISTANCE = 8) (I have it set to eight), the culture bomb will trigger for improvements (e.g. for forts). If the plot is beyond that radius, the bomb will not work.

Second, for testing purposes, try separating the two limiters.

local bAddPlot = ((Map.GetPlotDistance(PlotX, PlotY, pPickPlot:GetX(), pPickPlot:GetY()) <= iPlotRadius)

local bMaxPlotDist = (Map.GetPlotDistance(pCity:GetX(), pCity:GetY(), pPickPlot:GetX(), pPickPlot:GetY()) <= 3))

if bAddPlot and bMaxPlotDist then

See if that works. If not, something is wrong with getting Getx() and GetY() from the raw cityID. Maybe you need to get another value for that city. I'll investigate.

Cheers.
 
As you got through your code, put some print statements to see what values you are getting, as follows:

local pCityXCoor = pCity:GetX()
local pCityYCoor = pCity:GetY()
print (pCityXCoor)
print (pCityYCoor)

This is helpful and let's you know what you progress is like. Also check the Lua log for how the Lua file is progressing. I've put some print statement throughout, so it should show you approximately where the breakdown is. If you want, send me the chunk of the code that you change, and I'll plug it into mine and test.
 
1) Oh, I suppose if there is no district then it returning NULL won't be a problem. Whoops.

2) But it is part of the base game mechanics. The Culture Bomb does not grab tiles the city is not in range to work. That is distance 3 by default. The max distance it can *acquire* tiles is huge, yes, but the Culture Bomb does not grab tiles that far away in the base game.

I will try separating the logic into 2 variables. And thanks, I didn't know which log to check for the print statements.
 
This is what I get in Firetuner for the pCity:GetX() / Get(Y)

Runtime Error: D:/OneDrive/Documents/My Games/Sid Meier's Civilization VI/Mods/District Culture Bomb/LUA/DistrictAdjacencyCultureBomb.lua:144: attempt to index a number value
stack traceback:
D:/OneDrive/Documents/My Games/Sid Meier's Civilization VI/Mods/District Culture Bomb/LUA/DistrictAdjacencyCultureBomb.lua:144: in function 'DetonateCultureBomb'
D:/OneDrive/Documents/My Games/Sid Meier's Civilization VI/Mods/District Culture Bomb/LUA/DistrictAdjacencyCultureBomb.lua:181: in function 'OnEncampmentCompleted'
[C]: in function 'func'
[C]: in function '(anonymous)'

So, you need to get a further id reference for pCity (can't you the cityID output of the event). Let me figure this out. Hold on.
 
If you place a fort with a Culture bomb 5 TILES away from a city, it will STILL work as a Culture Bomb. The base game mechanic does not prevent this. The only limiting part is this value in Global Parameters:

PLOT_INFLUENCE_MAX_ACQUIRE_DISTANCE = 5

If you place a fort with a Culture bomb 6 TILES away from a city (even if it's still within the culture borders), the Culture Bomb will not activate. That's the limit, not 3 tiles.
 
Try an Encampment with Poland, or a Pasture with Australia.

And if it does work the way you're saying, then Firaxis lied =P
 
Here is your SOLUTION, within the context of the function I coded:

local pPlayerA = Players[ pPlayer ]
local pCityCoorID = pPlayerA:GetCities():FindID(pCity)
local pCityCoorX = pCityCoorID:GetX()
local pCityCoorY = pCityCoorID:GetY()

Now you can use pCityCoorX and pCityCoorY in this: local bMaxPlotDist = (Map.GetPlotDistance(pCity:GetX(), pCity:GetY(), pPickPlot:GetX(), pPickPlot:GetY()) <= 3)), as follows:

local bMaxPlotDist = (Map.GetPlotDistance(pCityCoorX, pCityCoorY, pPickPlot:GetX(), pPickPlot:GetY()) <= 3))

The way you originally had it set up, you were trying to index a number instead of a table, so there was nothing to index :)
 
Atlas, that's how it works for me for a fort with Poland. I am speaking from experience. Perhaps the higher PLOT_INFLUENCE_MAX_ACQUIRE_DISTANCE I use has something to do with it, but not by much.

The ONLY reason it has to be within 3 plots of a city for an Encampment for Poland is because a District cannot be placed more than 3 plot tiles away from a city. But if the culture bomb area affects plots beyond 3 plots away from a city, then the Encampment culture bomb will grab it, no problem at all.

I will go try this with Australia now and report :)
 
Hmm, interesting. How does the game know which city to assign the tiles to if you use a Fort out of workable range of the city? Does it use proximity? Just pick a city?

This might be the problem with the function you tried to use before, without the world builder. It gave the tile to the civ but not to a city. Did you try going into the city and telling it to work those tiles, like how you have to reassign them between cities?
 
Hey, so I tested this with Australia. As expected ;), the following happened:

(1) Pasture placed 3 tiles away from a city grabbed land 4 tiles away from a city.
(2) Pasture placed 5 tiles away from a city grabbed land 6 tiles away from a city.

Because I have PLOT_INFLUENCE_MAX_ACQUIRE_DISTANCE set to 8, this behaviour will continue to that limit. So:

(1) Pasture placed 7 tiles away will grab land 8 tiles away; but:
(2) Pasture place 8 tiles away WILL NOT grab any further tiles. The limit is 8.
One-PastureGrabsTilesMoreThan3Away.PNG
Two-PasturePlaced5TilesAwayStillGrabsTiles.PNG
 
The way the game recognizes which CITY to give ownership of culture bombed tiles (through the XML mechanic) is to determine which city OWNS the tile that is the source of the Culture Bomb. So, whatever city owns the tile on which the Pasture sits, will get the extra tiles. This becomes particularly problematic once the tile is beyond the city's workable area. So, if you cannot shift which city owns the tile, you are stuck with whichever city got to that tile first. This become particularly problematic when trying to plan for National Parks. It's a crapshoot. Whichever city got there first, will claim it.

As for the original function Plot:SetOwner() I tried, the function itself is bugged. Gedemon says he has not been able to make it work as intended in his tests. I haven't either. I tried all sorts of variations:

Plot:SetOwner( pPlayer )

Plot:SetOwner( pPlayer, pCity )

Plot:SetOwner( pPlayer, pCity, true)

even

Plot:SetOwner( pCity )

The result was always the same. It didn't matter if the city was mentioned or the player. It always assigned the ownership right. And the plot tooltips identified THE RIGHT CITY ownership, but the tiles were never workable. I did try going into the city and assigning the tiles, but they were disabled. Once I put a second city, I was able to reassign those tiles. However, once I triggered the function near 2 existing cities, a 3 city was necessary to work the tiles. And so forth. Bugged.
 
Last edited:
Anyways, for your function, I would do this:

Code:
function DetonateCultureBomb( PlotX, PlotY, iPlotRadius, pPlayer, pCity )
    if (PlotX ~= nil) and (PlotY ~= nil) and (PlotX > 0) and (PlotY > 0) then
        for k,pPickPlot in pairs(GetAllPlotsInRadiusR(Map.GetPlot(PlotX, PlotY), iPlotRadius, "ExcludeCenterPlot")) do
            local bAddPlot = (Map.GetPlotDistance(PlotX, PlotY, pPickPlot:GetX(), pPickPlot:GetY()) <= iPlotRadius)
            if bAddPlot then
                local pPlayerA = Players[ pPlayer ]
                local pCityCoorID = pPlayerA:GetCities():FindID(pCity)
                local pCityCoorX = pCityCoorID:GetX()
                local pCityCoorY = pCityCoorID:GetY()
                local bMaxPlotDist = (Map.GetPlotDistance(pCityCoorX, pCityCoorY, pPickPlot:GetX(), pPickPlot:GetY()) <= 3))
                if bMaxPlotDist then
                    WorldBuilder.CityManager():SetPlotOwner( pPickPlot:GetX(), pPickPlot:GetY(), pPlayer, pCity )
                end
            end
        end
    end
end
It should work fine. Except that I would change 3 to 5.

Cheers and good luck! :)
 
Watch out.. you have an extra bracket in
local bMaxPlotDist = (Map.GetPlotDistance(pCityCoorX, pCityCoorY, pPickPlot:GetX(), pPickPlot:GetY()) <= 3))

should be
local bMaxPlotDist = (Map.GetPlotDistance(pCityCoorX, pCityCoorY, pPickPlot:GetX(), pPickPlot:GetY()) <= 3)

I almost broke my head against the wall debugging this :)))
 
Ok, Atlas, I've now updated all three mods to have the value equivalent to PLOT_INFLUENCE_MAX_ACQUIRE_DISTANCE in Global Parameters, which is set to 5 in the base game. So, the culture bomb will not have effect for any tiles beyond the 5 tile distance from the owner city of the district or improvement.
 
Ok, I think I have finessed all the changes and corrected all obvious errors in the code. Please let me know if something else does not work. For downloads, see OP.

Also, please let me know if there is any demand for having the options (i.e. District Type, Civ Type, Feature Type, Radius, etc.) be available as a table in SQL instead of customizing in Lua directly. Seems to me there is not much difference doing it either way.
 
Thanks! Maybe we can start making repositories for this kind of thing haha
 
  1. Don't make a Double-Not in Lua.
    • This is a double-not:
      Code:
      if not (pOtherOwner ~= pPlayer and (pOtherDistrict ~= -1 or pOtherWonder ~= -1)) then
    • You are actually asking lua to give you the opposing boolean to the stuff inside the primary (outer) set of "( )"
    • You would not do
      Code:
      if not (false) then
      	print("true")
      end
    • "~=" is the mathematical opposite of "=="
    • If I were structuring a condition where I want to exclude existing district and wonder tiles from an ownership "takeover", I would do as follows (where "iOurPlayersID_Number" was passed by some other function or a game event as the integer ID# of the player we are grabbing tiles for):
      Code:
      local pPlot = SomeMethodThatReturnsThePlotObject
      if pPlot:GetOwner() ~= -1 then
      	print("the plot belongs to a player")
      	if (pPlot:GetOwner() ~= iOurPlayersID_Number) then
      		print("this plot belongs to someone else")
      		--decide whether to do stuff
      		if (pPlot:GetDistrictType() == -1) and (pPlot:GetWonderType() == -1) then
      			print("we can honestly grab this plot because it has no pre-existing wonder or district")
      			--do other stuff
      		end
      	else
      		print("we already own this plot, we don't need to steal it")
      	end
      else
      	print("the plot belongs to no player: we can safely do whatever we want")
      end
      This is less difficult for the less-experienced lua-programmer to understand and verify the logic makes sense. Unitl you get more used to programming in lua and the requirements of civ6, don't make complex one-line conditionals: break them up so you can more-easily understand what it is you are coding. And if you ever have a "not" in front of a conditional set that is a bunch of mathematical "~=" and "==", re-write to eliminate the "not" because the rules of falsity of lua will almost always take a bite out of you otherwise.

      I'm a little confused as to who was doing this (@Gleb Bazov or someone else he was talking to), but I saw the "offending" Double-Not upthread. William Howard used to yell at us all the time in the civ5 forum's for doing that.
  2. To check plots that are adjacent to a given plot, simply use
    Code:
    local iNumMountainFound = 0
    for direction = 0, DirectionTypes.NUM_DIRECTION_TYPES - 1, 1 do
    	local adjacentPlot = Map.GetAdjacentPlot(pPlot:GetX(), pPlot:GetY(), direction);
    	if adjacentPlot and adjacentPlot:IsMountain() then
    		iNumMountainFound = iNumMountainFound + 1
    	end
    end
    In this example I am calculating how many mountains are adjacent to the given tile.
  3. I'm currently using a far more simple and streamlined version of the plot-grabber method for getting all plots that are 2 or more tiles from an original plot
    Code:
    function GetValidPlotsInRadiusR(pPlot, iRadius)
    	local tTempTable = {}
    	if pPlot ~= nil then
    		local iPlotX, iPlotY = pPlot:GetX(), pPlot:GetY()
    		for dx = (iRadius * -1), iRadius do
    			for dy = (iRadius * -1), iRadius do
    				local pNearPlot = Map.GetPlotXYWithRangeCheck(iPlotX, iPlotY, dx, dy, iRadius);
    				if pNearPlot then
    					table.insert(tTempTable, pNearPlot)
    				end
    			end
    		end
    	end
    	return tTempTable
    end
    I say "2 or more tiles" only because if I am only interested in tiles that are adjacent I can run the other code.

    You still get the adjacent tiles and the original tile in this method as part of the table that is returned, but you only would need to run this code if you also need the tiles that are further out than the adjacent ones.
  4. Hungarian Notation System: none of these conventions are necessary to follow, but it does make reading your own code easier to follow when you know that a variable that starts with smallcase "b" ought to be a boolean rather than say an lua-table.
    1. small "p" at the beginning of a variable name is used to denote the variable is a pointer or an object. Like "pCity" or "pUnit" denotes the lua-object for an individual city or unit.
    2. small "i" at the beginning of a variable name is used to denote the variable is an integer value
    3. small "b" at the beginning of a variable name is used to denote the variable is a boolean and ought usually to only ever be boolean true/false
    4. small "t" at the beginning of a variable name is used to denote the variable is an lua-table of the normal sort
      • pointer objects in Civ6 such as "pCity" are also lua tables
    5. small "s" at the beginning of a variable name is used to denote the variable contains a text string such as
      Code:
      "My Dog Ate the Homework, Mrs. Jonsson. Really, he did!"
 
Last edited:
This is great, LeeS. I've actually implemented your simpler method -- GetValidPlotsInRadiusR(pPlot, iRadius) -- for grabbing tiles in all my mods. I will correct my code in accordance with your further suggestions. Thank you very much!

GB
 
Your mod works great and I have been able to modify it to apply to the civ and terrain type I want... but for some reason when I add it to my own mod it doesn't work anymore. I use the same modinfo formatting you did and put the file in the correct filepath, and just nothing.
 
Top Bottom