Finding the City-State closest to a unit

Vicevirtuoso

The Modetta Man
Joined
May 14, 2013
Messages
775
Location
The Wreckage, Brother
So, I stitched together this code, which is passed a unit pointer and returns the pointer of the City-State which is closest to that unit on the map.

Code:
function GetCityStateNearestToUnit(unit)
	local tCityStatePlots = {};
	local tPlotDistances = {};
	local iKey;
	local iShortestDistance;
	local pNearestCS;
	for i, pCityState in pairs(Players) do
		if pCityState:IsMinorCiv() then
			for pCSCity in pCityState:Cities() do
				tCityStatePlots[i] = pCSCity:Plot();
				print("Added plot of " ..pCityState:GetCivilizationShortDescription().. " to tCityStatePlots")
			end
		end
	end
	for i=0, GameDefines.MAX_CIV_PLAYERS do
		if tCityStatePlots[i] then
			tPlotDistances[i] = Map.PlotDistance(tCityStatePlots[i]:GetX(), tCityStatePlots[i]:GetY(), unit:GetX(), unit:GetY());
			print("Plot distance between player " ..i.. " and Incubator is " ..tPlotDistances[i])
		else
			tPlotDistances[i] = 999999
		end
	end
	iShortestDistance = math.min(unpack(tPlotDistances));
	print("Shortest distance determined to be " ..iShortestDistance)
	iKey = GetKeyForTableValue(tPlotDistances, iShortestDistance);
	print("Key value for table which corresponds with that distance is " ..iKey)
	pNearestCS = Players[tCityStatePlots[iKey]:GetOwner()]
	print("Determined that the closest City-State is " ..pNearestCS:GetCivilizationShortDescription())
	return pNearestCS
end

function GetKeyForTableValue(t, value)
	for k,v in pairs(t) do
		if v==value then
			return k
		end
	end
end

The code works, but it looks hideous and I'm sure there's a much simpler, cleaner, and faster way of doing it. The way I had to make math.min() work makes me cringe, since it doesn't work with sparse arrays.

Any folks who actually know how to write clean code have any tips?
 
what is the result if a city-state, by some happenstance, has a couple of cities?

It would base the distance calculation off of its most recently acquired city. The GetCapitalCity() function unfortunately does not work for City-States, so I'm not exactly sure how to make it work only with the City-State's original city.
 
No need to build tables of plots from cities and then loop those and then convert from those keys back to player objects, just loop minors, then cities they have and remember the shortest distance and the corresponding minor, eg

Code:
function GetCityStateNearestToUnit(unit)
  local iShortestDistance = 99999
  local pNearestCS = nil
	
  local iUnitX = unit:GetX()
  local iUnitY = unit:GetY()
	
  for iCityState=GameDefines.MAX_MAJOR_CIVS, GameDefines.MAX_CIV_PLAYERS-2, 1 do
    local pCityState = Players[iCityState]
    if (pCityState:IsEverAlive() and pCityState:IsAlive()) then
      for pCSCity in pCityState:Cities() do
        local iDist = Map.PlotDistance(pCSCity:GetX(), pCSCity:GetY(), iUnitX, iUnitY)
        if (iDist < iShortestDistance) then
          iShortestDistance = iDist
          pNearestCS = pCityState
        end
      end
    end
  end

  return pNearestCS
end


Edit:

In your code it should be "for i=0, GameDefines.MAX_CIV_PLAYERS-1 do" as Lua loops include both the start and end value, but you can use -2 as the last player is always the Barbarians
 
Much appreciated.

Since I think you know more about this game's code than Firaxis at this point, do you know if there is a way to specify for this to check only for City-State "capitals", in case a CS happens to capture a Civ's city? Pretty rare in the regular game, but it ends up happening a lot if you play with, say, Gedemon's Revolutions mod.
 
The GetCapitalCity() function unfortunately does not work for City-States

Yes it does. (The C++ code doesn't differentiate between humans, majors or minors for that function)

Even if a CS has more than one city and then loses the original capital, it all works as expected

Code:
[ Lua State = WorldView ]
> Players[27]:GetName()
Almaty
> Players[27]:GetCapitalCity():GetName()
Almaty
> for pCity in Players[27]:Cities() do print(pCity:GetName()) end
 WorldView: Almaty
 WorldView: Mopti

Capture Almaty
 
> for pCity in Players[27]:Cities() do print(pCity:GetName()) end
 WorldView: Mopti
> Players[27]:GetCapitalCity():GetName()
Mopti
 
do you know if there is a way to specify for this to check only for City-State "capitals"

replace the city loop

Code:
for pCSCity in pCityState:Cities() do
  ...
end

with

Code:
  local pCSCity = pCityState:GetCapitalCity()
 
Back
Top Bottom