Ja Mes
Chieftain
- Joined
- Dec 5, 2020
- Messages
- 10
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
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
at the top.
In terms of actual usage, there's going to be one main way you get the information you need.
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.
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
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
Revised w/ Border Counter
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).
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")
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%.......

Attachments
Last edited: