Owned Tiles and Bordering Plots Manager

Ja Mes

Chieftain
Joined
Dec 5, 2020
Messages
12
Introduction
An oft-thought about problem in Civilization 5 modding is determining which owned tiles border another Civilization's tiles. To my knowledge, until now, the only way to determine this is with a ludicrous amount of Plot Iteration. While the plot iterator is an incredible tool, when all you have is a hammer, everything looks like a nail.

According to my sources, Plot Iteration is not the friendliest to computer resources, especially if you have a large amount of it going on in your downloaded mods.

Thus, over the past week I developed, with the help of many other passionate modders, a system that will keep track of owned tiles and what tiles they border.

How It Works
In totality, this utility is nowhere near complicated. It's less than 70 lines of code, and is only 3 functions. It's composed of one central part: A table that keeps track of every plot in the game (through their Plot Indices), which Player owns it, and which Players own the 6 surrounding tiles. To keep track of this, the table is created upon loading the game (either a New Game or Loading a Game). To keep resource usage low, whenever a tile changes ownership, it and its 6 bordering tiles are put into an intermediary table. Every turn, this intermediary table is processed into the central table. And that's it! If only it took as long to describe it as it took to write....

The Code & Usage
Code:
print("Border Counter has loaded.") -- Perhaps I will comment on this if there's any requests
include("FLuaVector.lua")

tPlotBorders = {}

local tIndices = {}
local NumOfIndices = 0
local iNumDirections = DirectionTypes.NUM_DIRECTION_TYPES - 1

function PlotsDoBeLikeThat()
  for i = 0, Map.GetNumPlots() - 1, 1 do
    local pPlot = Map.GetPlotByIndex(i)
    tPlotBorders[i] = {}
    tPlotBorders[i][-1] = pPlot:GetOwner()
    for iDir = 0, iNumDirections, 1 do
      local pAdjPlot = Map.PlotDirection(pPlot:GetX(), pPlot:GetY(), iDir)
      if pAdjPlot then
        tPlotBorders[i][iDir] = pAdjPlot:GetOwner()
      else
        tPlotBorders[i][iDir] = -1
      end
    end
  end
end
Events.SequenceGameInitComplete.Add(PlotsDoBeLikeThat)

function OnTileNotification(hexX, hexY, playerID, isUnknown)
local gridPosX, gridPosY = ToGridFromHex( hexX, hexY );
local pPlot3 = Map.GetPlot( gridPosX, gridPosY );
local index = pPlot3:GetPlotIndex()

NumOfIndices = NumOfIndices + 1

tIndices[NumOfIndices] = index

for iDir = 0, iNumDirections, 1 do
    local pAdjPlot3 = Map.PlotDirection( gridPosX, gridPosY, iDir)
        if pAdjPlot3 then
            NumOfIndices = NumOfIndices + 1
            tIndices[NumOfIndices] = pAdjPlot3:GetPlotIndex()
        end
end
end
Events.SerialEventHexCultureChanged.Add(OnTileNotification)

function ProcessIndices(iPlayer)
if iPlayer == 0 then
    for k, v in pairs(tIndices) do
        if Map.GetPlotByIndex(v):GetOwner() then
            tPlotBorders[v] = {}
            tPlotBorders[v][-1] = Map.GetPlotByIndex(v):GetOwner()
            for iDir = 0, iNumDirections, 1 do
                local pAdjPlot2 = Map.PlotDirection(Map.GetPlotByIndex(v):GetX(), Map.GetPlotByIndex(v):GetY(), iDir)
                if pAdjPlot2 then
                    tPlotBorders[v][iDir] = pAdjPlot2:GetOwner()
                 else
                    tPlotBorders[v][iDir] = -1
                 end
            end
        end
    end
NumOfIndices = 0
tIndices = {}
end
end
GameEvents.PlayerDoTurn.Add(ProcessIndices)

To use, simply create a new Lua File and name it BorderCounter.lua. (This name should be standardized to allow for compatibility. If it changes, I'll make note of it here). Paste the above code into (or download the file I have hopefully included, convert it from .txt to .Lua), do not InGameUIAddin, but rather set the file's VFS to true. For any other Lua files you want to use it with, be sure to put
Code:
 include ("BorderCounter.lua")
at the top.

In terms of actual usage, there's going to be one main way you get the information you need.

Code:
for k, v in pairs(tPlotBorders) do
-- Your code here
end

This will go through every plot in the map. k is going to the value of a plot index - convert it to a useful Plot with "Map.GetPlotByIndex". v is going to be 7 values: v[-1] is the playerID of the owner of the plot. Values v[0] through v[5] represent the owners of the adjacent plots (v[0] is the Northeast border, and then clockwise as per Civ5's Direction Types). It is very important to remember the difference between these values: k represents a plot while v represents a Player ID.

To easily get through all the v values, just use a loop. This code, for example, will print the owner of the plot and the owners of the 6 adjacent plots.

Code:
for i = -1, 5, 1 do - For the numbers -1 through 5, increasing by 1 do
if v[-1] ~= -1 then -- If the plot is owned
                    print(v[i]) -- Print the i value of v.
                    end
end
end

If you must get the Plot ID of the bordering tiles for whatever reason, you can use this loop technique as well. Include the following code in the above loop

Code:
Map.PlotDirection(Map.GetPlotByIndex(k):GetX(), Map.GetPlotByIndex(k):GetY(), i)

This gets the coordinates of your plot's index, and then uses Civ5's coordinate system to get the bordering plots. 0 is Northeast and 5 is Northwest, like earlier.

Finally, if you need to get the city, I would recommend ":GetWorkingCity()" but this, of course, will be negative if the plot doesn't exist or it is not part of a city. Thus, you can always use this function to find the nearest city, but of course it might not belong to the same person as the owner of the plot!

Demonstration / Example

The inspiration for this whole thing was Sas's wonderful Cascadia mod, which I recommend you play. For the sake of demonstration, I believe I have recreated one function that was done with plot iteration with the Border Counter functionality.

Original Code

Code:
function ER_SasCasc_UAGainTiles(playerID)
    local iBase = (1-0.01) --iBase is the 1 minus the chance of gaining tiles with 0 Tourism (per tile)
    local iScale = 0.005 --Increase by iScale for every Tourism (per tile)
    local pPlayer = Players[playerID]
    if pPlayer:IsAlive() and pPlayer:GetCivilizationType() == iCiv then
        local iTourismScale = iScale * pPlayer:GetTourism()
        --Getting Border Plots
        for pCity in pPlayer:Cities() do
            for pAreaPlot in PlotAreaSpiralIterator(pCity:Plot(), 4, SECTOR_NORTH, DIRECTION_CLOCKWISE, DIRECTION_OUTWARDS, CENTRE_EXCLUDE) do
              if pAreaPlot:GetOwner() ~= -1 and (pAreaPlot:GetOwner() ~= playerID) and (not pAreaPlot:IsCity()) then
                    if math.random() + iTourismScale > iBase then
                        --check if a border plot
                        local bBorder = false
                        for pEdgePlot in PlotRingIterator(pAreaPlot, 1) do
                            if not bBorder and pEdgePlot:GetOwner() == playerID then
                                bBorder = true
                            end
                        end
                        if bBorder then
                            pAreaPlot:SetOwner(playerID, pCity:GetID())
                        end
                    end
                end
            end
        end
    end
end

GameEvents.PlayerDoTurn.Add(ER_SasCasc_UAGainTiles)

Revised w/ Border Counter

Code:
function ER_SasCasc_UAGainTiles_Redux(playerID)
local iBase = (1-0.01) --iBase is the 1 minus the chance of gaining tiles with 0 Tourism (per tile)
local iScale = 0.005 --Increase by iScale for every Tourism (per tile)

for k, v in pairs(tPlotBorders) do
    if v[-1] ~= -1 then
        if Players[v[-1]]:GetCivilizationType() == GameInfoTypes["CIVILIZATION_CASCADIA"] then
        local iTourismScale = iScale * Players[v[-1]]:GetTourism()
            for i = -1, 5, 1 do
                if v[i] ~= -1 and v[i] ~= v[-1] and (not Map.PlotDirection(Map.GetPlotByIndex(k):GetX(), Map.GetPlotByIndex(k):GetY(), i):IsCity()) then
                    if math.random() + iTourismScale > iBase then
                        if Map.PlotDirection(Map.GetPlotByIndex(k):GetX(), Map.GetPlotByIndex(k):GetY(), i):GetWorkingCity() then
                            print ("4")
                            Map.PlotDirection(Map.GetPlotByIndex(k):GetX(), Map.GetPlotByIndex(k):GetY(), i):SetOwner(v[-1], Map.GetPlotByIndex(k):GetWorkingCity())
                        end
                    end
                end
            end
        end
    end
end
end
GameEvents.PlayerDoTurn.Add(ER_SasCasc_UAGainTiles_Redux)

Of course, the two pieces of code do not behave literally the same way. In my example, Cascadia can (and has, from my testing) take over tiles an infinite range away from its cities. I believe this could be solved simply by checking the distance between the :GetWorkingCity and the coordinates of the Indexed Plot, however.

Conclusion, Thanks

This has been nothing but a long, long labor of love. I could not have done it alone: Special thanks to TopHatPaladin, for his support in helping me understand Lua tables and somewhat creating half of this. Thanks to LeeS and whoward69 who helped me when I faced fatal bugs and helping me understand Civ5's two grid systems. Finally, an additional thanks to JFD for room and board.

I hope this tool finds good use with mods, and please do not be afraid to reach out with questions or critiques (because I have no idea if this works 100%....... ;) but I'm pretty sure it does).
 

Attachments

  • BorderCounter.txt
    2 KB · Views: 78
Last edited:
Top Bottom