how do I check for enemy/barbarian units on a nearby plot

LeeS

Imperator
Joined
Jul 23, 2013
Messages
7,241
Location
Illinois, USA
I've been trying to write something to check for whether there are barbarian or enemy units in plots around a city. But every plot:somemethod() that I've tried gives me either "nil" or "table 654907" or "false" even when I know for a certainty there's a barbarian unit standing on one of the plots adjacent to my capital city. I put a barbarian brute next to my capital in a test-game so I could test for detecting his presence.

I'm using this method to look through plots around a city:
Code:
function CheckForBarbariansNearCity(pPlayer, pCity)
	local iNumPlots = pCity:GetNumCityPlots()
	for i = 0, iNumPlots - 1 do
		local pPlot = pCity:GetCityIndexPlot(i)
		local iPlotIndex = pPlot:GetPlotIndex()	--not sure if I actually need this for anything
		local plotvisible = pPlot:IsVisible(pPlayer:GetTeam(), false)

--I extracted out everything else in this function from here because it's confusing and isn't working anyway
is the method using pCity:GetNumCityPlots() and for i = 0, iNumPlots - 1 do, etc., just not going to be of any use?

I tried pPlot:GetUnit() with or without stuff inside the "()" and all I ever get from that is "nil" or a table reference #. The wiki isn't particularly helpful on what any of the Plot:SomeKindOfUnitSomethings() will actually give you in terms or whether it's a unit ID# or a UnitType or what exactly it gives.

This is being run as part of a PlayerDoTurn function.

PS: looking for a notification isn't going to work because AI's don't get notifications. Why would they? The game already let's AI's know everything about where everything and everyone on the map is anyway.
 
pPlot:GetUnit() returns an object, that's why you get a table reference.

I was wrong, you need to tell which unit you want in that plot.
To check if you get a barbarian unit, do this:

Code:
for direction = 0, DirectionTypes.NUM_DIRECTION_TYPES - 1, 1 do
	local pAdjacentPlot = Map.PlotDirection(pPlot:GetX(), pPlot:GetY(), direction)
	if pAdjacentPlot and pAdjacentPlot:IsUnit() then 
		for i = 0, [B]pAdjacentPlot[/B]:GetNumUnits() do
			local pUnit = pAdjacentPlot:GetUnit(i)
			if pUnit and pUnit:IsBarbarian() then
				return true
			end
		end
	end
end
 
attempt to index global 'nextPlot' (a nil value)

I'm too tired to think anymore. Plus I've been banging my head against this for hours. Tomorrow evening I'll try to see if I urped it by the way I added your code. Won't be back at my computer before then, anyway.
 
I was about to respond to that, but figured I'd refresh the page before composing.

bane_, nextPlot is undefined in that particular snippet.

@LeeS
His code also only checks the tiles immediately adjacent to the City, while your loop would go through all 36 tiles around it. Which one do you want? The loop itself doesn't seem to be a problem, it's just that there is an issue with the way you are detecting for units.

I've got some code doing just this in the code I'm writing for the Shana Civ; Her UB is supposed to damage all enemies around the City.

What exactly do you need to do?
 
Up early in the morning b4 long cross-country drive. What I was trying to do was scan through all 36 plots "around a city" looking for barbarians on plots that were owned by and/or visible to said city. Bane_'s method for scanning plots adjacent to the city would have been good enough to at least let me try to code-in the rest of what I want.

You did ask what it is I really want, so here goes:
  1. if the city belongs to the founder of catholicism, and has majority religion catholicism
  2. if the city has no defending units in it or next to it
  3. if a barbarian unit is within the visible "range" of the city
  4. if a unit of a civilization the city is at war with is within the visible range of the city
  5. spawn a special 'holy defender' unit in the city.
This is the main function that runs as a PlayerDoTurn. I highlighted in blue the parts I'm needing help with (#2, 3, and 4 from the list). #1 and #5 from the list I'm already able to do just fine, and the code below has everything needed except for the definitions of a couple variables that are referenced in ValkyrieSpawn.

I was assuming that once I figured out how to do #3, I'd be able to use that to figure out how to do #2 & #4. But maybe not so much.

Spoiler :
Code:
function ValkyrieSpawn(iPlayer)
	local pPlayer = Players[iPlayer]
	local iPlayerReligion = pPlayer:GetReligionCreatedByPlayer()
	local iRequiredReligion = iChristianity
	if (pPlayer:IsEverAlive() and not (pPlayer:IsMinorCiv()) and not (pPlayer:IsBarbarian())) then
		if pPlayer:IsAlive() then
			if (iPlayerReligion < 1) and (gPrintStatements == 1) then print("iPlayerReligion = " .. tostring(iPlayerReligion) .. ". Aborting the ValkyrieSpawn function.") end
			if iPlayerReligion < 1 then return end	--player has not founded a major religion, end the function
			if iPlayerReligion ~= iRequiredReligion then return end	--when the religion founded by the player is not Catholicism, end the function
			for pCity in pPlayer:Cities() do
				local iCityMajority = pCity:GetReligiousMajority()
				if iCityMajority == iRequiredReligion then
					bEnemyEncroatchers = false
					bBarbarianEncroatchers = false
					bHolyDefendersCheck = false
					[COLOR="Blue"]bBarbarianEncroatchers = CheckForBarbariansNearCity(pPlayer, pCity)[/COLOR]
					print("bBarbarianEncroatchers = " .. tostring(bBarbarianEncroatchers))
					bEnemyEncroatchers = bBarbarianEncroatchers
					if not bEnemyEncroatchers then
						[COLOR="blue"]bEnemyEncroatchers = CheckForEnemyUnitsNearCity(pPlayer, pCity)[/COLOR]
					end
					if bEnemyEncroatchers then
						[COLOR="blue"]bHolyDefendersCheck = CheckForHolyDefendersNearOrInCity(pPlayer, pCity)[/COLOR]
						if not bHolyDefendersCheck then
							--spawn Valkyrie unit at or near city plot
							SpawnAtPlot(pPlayer, iValkyrie, pCity:Plot())
						end
					end
				end
			end
		end
	end
end
GameEvents.PlayerDoTurn.Add(ValkyrieSpawn)
 
I've got work myself in a few minutes, but for this case I would recommend usage of whoward's PlotIterators, since it loops through the City's plots in a consistent and predictable manner, and I believe it would also let you do everything in one pass; otherwise you would need to do multiple loops.

At a high level (I will post actual code once I return from work tonight, ~4 hours from now) the logic is roughly:
Code:
for pCity in pPlayer:Cities() do
	local pOriginPlot = pCity:Plot()
	local iPlotCount = 0
	for pAreaPlot in PlotAreaSpiralIterator(pOriginPlot, 3, SECTOR_NORTH, DIRECTION_ANTICLOCKWISE, DIRECTION_OUTWARDS, CENTRE_INCLUDE) do
		iPlotCount = iPlotCount + 1
		if pAreaPlot:IsUnit() then
			if iPlotCount <= 7 then -- this means it's a plot in or adjacent to the city
			else -- these are plots in the "second ring" and beyond
			end
		end
	end
end

It's pseudo code for now, since again I've got work, but if no one's posted a more complete version (or you haven't figured it out yourself yet) by the time I return tonight, I'll post a complete function.

The gist is that with the PlotAreaSpiralIterator, you will know it scans rings around the city, so the first 7 plots scanned will be the CIty's plot, and all immediately adjacent plots, then every plot scanned after that is "away" from the City.

If the IsUnit() check returns true, then you go with the above stuff to further drill down on what types of units are on the plot and whose team it belongs to (Barbarian, or otherwise.) You can also place a counter in the beginning (where I have the plot counter) to act as a counter for "defenders," or friendly units within those first 7 plots, and if there are no defenders, then you can add an if-check at the end where if that counter is still zero, you've got no one nearby.
 
Making progress. I sort-of hybridized Bane_'s method with the original one I was using. This is the check for barbarians near the city, with a few added statements to pull-in more data:
Spoiler :
Code:
function CheckForBarbariansNearCity(iPlayer, pPlayer, pCity)
	result = false
	iNumberBarbsPresent = 0
	local iNumPlots = pCity:GetNumCityPlots()
	for i = 0, iNumPlots - 1 do
		local pPlot = pCity:GetCityIndexPlot(i)
		local iPlotIndex = pPlot:GetPlotIndex()	--not sure if I actually need this for anything
		local plotvisible = pPlot:IsVisible(pPlayer:GetTeam(), false)
		if plotvisible then
			if pPlot:IsUnit() then
				for i = 0, pPlot:GetNumUnits() do
					local pUnit = pPlot:GetUnit(i)
					print("where i has a value of " .. tostring(i) .. ", pUnit = pPlot:GetUnit(i) has given a result of " .. tostring(pUnit))
					if pUnit then
						local pUnitOwner = pUnit:GetOwner()
						print("pUnitOwner = pUnit:GetOwner() has given a result of " .. tostring(pUnitOwner))
						local pUnitCombat = pUnit:IsCombatUnit()
						print("pUnitCombat = pUnit:IsCombatUnit() has given a result of " .. tostring(pUnitCombat))
					else print("cannot access any pUnit:Get--() data because pUnit is a nil value")
					end
					if pUnit and pUnit:IsBarbarian() then
						iNumberBarbsPresent = iNumberBarbsPresent + 1
					end
				end
			end
		end
	end
	if iNumberBarbsPresent > 0 then
		result = true
	end
	return result
end

Which gave me log prints of this when the barbarian was next to my city:
Spoiler :
Code:
[275879.406] CatholicUnitsFunctions: where i has a value of 0, pUnit = pPlot:GetUnit(i) has given a result of table: 5930BAF8
[275879.406] CatholicUnitsFunctions: pUnitOwner = pUnit:GetOwner() has given a result of 63
[275879.406] CatholicUnitsFunctions: pUnitCombat = pUnit:IsCombatUnit() has given a result of true
[275879.406] CatholicUnitsFunctions: where i has a value of 1, pUnit = pPlot:GetUnit(i) has given a result of nil
[275879.406] CatholicUnitsFunctions: cannot access any pUnit:Get--() data because pUnit is a nil value
[275879.406] CatholicUnitsFunctions: bBarbarianEncroatchers = true
The special defender unit was successfully spawned, and after I used it to kill off the barbarian brute I got these prints in the lua log:
Spoiler :
Code:
[276064.140] CatholicUnitsFunctions: where i has a value of 0, pUnit = pPlot:GetUnit(i) has given a result of table: 593080D8
[276064.140] CatholicUnitsFunctions: pUnitOwner = pUnit:GetOwner() has given a result of 0
[276064.140] CatholicUnitsFunctions: pUnitCombat = pUnit:IsCombatUnit() has given a result of true
[276064.140] CatholicUnitsFunctions: where i has a value of 1, pUnit = pPlot:GetUnit(i) has given a result of nil
[276064.140] CatholicUnitsFunctions: cannot access any pUnit:Get--() data because pUnit is a nil value
[276064.140] CatholicUnitsFunctions: bBarbarianEncroatchers = false
So I have to rethink how and where to add all these lines into my code so that I can make use of the info I am pulling to determine if the detected unit in question belongs to a rival civ or a city-state with which the city is at war, and also to keep track of how many combat units belonging to the city owner are detected.
 
Just returned from work, but it seems like you've made some progress, so I'll likely not have to come up with any code.

For what it's worth, right now, I don't see any place where you'll need the iPlotIndex. I use it because I need a way to uniquely identify and later recall specific plots without looping over them again, but you don't seem to need that functionality for this function.

The only problem I can see is that I don't know exactly how the city's plots are iterated, because you're at the mercy of GetCityIndexPlot(i) to find the next plot. This would make "city-adjacent" detection tricky unless you tack on a second loop to search adjacent plots of the current plot for a plot with a City on it (seems a bit roundabout to me.)
 
The only problem I can see is that I don't know exactly how the city's plots are iterated, because you're at the mercy of GetCityIndexPlot(i) to find the next plot. This would make "city-adjacent" detection tricky unless you tack on a second loop to search adjacent plots of the current plot for a plot with a City on it (seems a bit roundabout to me.)
I'm thinking to re-work things a little with a check for city garrison unit(s), followed by Bane_'s code running based on the city plot as the "search-center", but instead of looking for barbarians, look for units that are combat, DomainLand, and belong to pPlayer. If there are more such units than SomeDesiredAmount* then there's no point in preceeding further for that city regardless of whether there are Barbarian or Enemy-Civ units nearby. Should also help streamline things a bit.


* my thinking has always been that if there are more than zero such "defender" units near to a city, no extra special unit spawning should occur.
 
Yeah, that can work. I'm just not a fan of having so many loops going; I'd rather streamline it so everything gets done with a single loop, but that's just me. I'm guilty of having many loops in Holo as well, but if I were to redo her Lua again I'd likely look for more ways of streamlining and abstracting more of her functions.
 
Well, I'm becoming more and more of a fan of terminate-early-on-condition(s) that make it pointless to proceed farther into the code. Even if that does mean repeat-loop portions.
 
These two are not mutually exclusive; I have many early-termination sections as well in my code, or at least I do try to build them in nowadays.

Either way, this discussion isn't going anywhere at this point, so I will bow out of your thread.
 
Back
Top Bottom