Detecting City with PlotAreaSweepIterator

Danmacsch

Geheimekabinetsminister
Joined
Jan 14, 2014
Messages
1,316
Location
Copenhagen, Denmark
Hi,

I'm trying to detect if there is a city within a specific radius when a unit is killed. But for some reason, the code only executes to a certain point and I fail to see why that is.
Here's my code:
Code:
function DMS_UnitKilledNearBorg(iOwner, iUnit, iUnitType, iX, iY, bDelay, iKiller)
	local bEnemyKilledNearDanishBorg = false
	if bDelay then
	print("DMS_UnitKilledNearBorg: a unit is killed somewhere..")
		local pPlayer = Players[iKiller]
		local iPlayer = Players[iOwner]
		if pPlayer and pPlayer:GetCivilizationType() == civilisationDenmarkValdemarIIID then
			if pPlayer ~= iPlayer then
			print("DMS_UnitKilledNearBorg: the unit was killed by the Kingdom of Denmark and was not Danish itself..")
				local pUnit = iPlayer:GetUnitByID(iUnit)
				local pPlot = Map.GetPlot(iX, iY)
				for iPlot in PlotAreaSweepIterator(pPlot, 2, SECTOR_NORTH, DIRECTION_CLOCKWISE, DIRECTION_OUTWARDS, CENTRE_EXCLUDE) do
					if iPlot:IsCity() then
					print("DMS_UnitKilledNearBorg: the unit was killed within 2 tiles of a City..")
						local pCity = iPlot:GetPlotCity()
						if pCity:GetOwner() == pPlayer then
						print("DMS_UnitKilledNearBorg: the city (" .. pCity:GetName() .. ") is owned by the Kingdom of Denmark..")
							if pCity:IsHasBuilding(buildingBorgID) then
							print("DMS_UnitKilledNearBorg: the city (" .. pCity:GetName() .. ") has a Borg..")
								bEnemyKilledNearDanishBorg = true
						
								break
								
							end
						end
					end	
					
				return pCity, bEnemyKilledNearDanishCity
				
				end

				if (bEnemyKilledNearDanishBorg == true) then
				print("DMS_UnitKilledNearBorg: calling function DMS_BorgGrantsFaithPerKill..")
					DMS_BorgGrantsFaithPerKill(pCity)
				end
			end
		end
	end					
end

if isDenmarkValdemarIICivActive then
	GameEvents.UnitPrekill.Add(DMS_UnitKilledNearBorg)
end
But upon testing (killing a unit next to a player-owned city with the required building) I only get these print statements:
Code:
DMS_DenmarkValdemarII_functions: DMS_UnitKilledNearBorg: a unit is killed somewhere..
DMS_DenmarkValdemarII_functions: DMS_UnitKilledNearBorg: the unit was killed by the Kingdom of Denmark and was not Danish itself..
DMS_DenmarkValdemarII_functions: PlotAreaSweepIterator((12, 42), r=2, s=1, d=fwd, w=out, c=no)
So I imagine that the fault lies in the "if iPlot:IsCity() then" check.

Can somebody point out my error(s)? Thanks in advance..
 
Is it near the edge of the map?

You do not have any "if iPlot then" check before you check for the City, so if the plot doesn't exist, that check fails. (Not to mention the awkward use of iPlot here, since the returned value isn't an integer.) Otherwise, I'm not too sure what that PlotAreaSweepIterator print statement means. I never used that particular iterator.

However, if you simply want to figure out if a given unit is within range of any of a player's Cities, you can probably make it simpler for yourself; instead of initiating a sweep of all plots around the unit searching for Cities, you can make use of Map.PlotDistance() and loop through the player's Cities instead. With UnitPreKill, you should have the coordinates of the unit in question, so it's just a matter of going through pPlayer:Cities(), grabbing the coordinates of each City, plugging all that in and seeing if the distance returned is less than or equal to your desired radius.

This has the bonus of not requiring quite as many plot scans.
 
Is it near the edge of the map?

You do not have any "if iPlot then" check before you check for the City, so if the plot doesn't exist, that check fails. (Not to mention the awkward use of iPlot here, since the returned value isn't an integer.) Otherwise, I'm not too sure what that PlotAreaSweepIterator print statement means. I never used that particular iterator.

However, if you simply want to figure out if a given unit is within range of any of a player's Cities, you can probably make it simpler for yourself; instead of initiating a sweep of all plots around the unit searching for Cities, you can make use of Map.PlotDistance() and loop through the player's Cities instead. With UnitPreKill, you should have the coordinates of the unit in question, so it's just a matter of going through pPlayer:Cities(), grabbing the coordinates of each City, plugging all that in and seeing if the distance returned is less than or equal to your desired radius.

This has the bonus of not requiring quite as many plot scans.
Thanks for your answer.

The plot wasn't at the edge of the map though, out even near it. I tried adding the "is iPlot" check, but unfortunately that didn't change anything.
I will try changing the code as you suggest and see if that'll fix it.
 
This will never be "true"
Code:
if pCity:GetOwner() == pPlayer then
because pCity:GetOwner() returns a player ID# from the lua Players table and you have previously defined
Code:
local pPlayer = Players[iKiller]
So you are trying to compare an integer to a player object. You would need
Code:
if pCity:GetOwner() == iKiller then

-----------------------------------------------------------------------------------

You can re-write the line DarkScythe was referring to as:
Code:
if iPlot and iPlot:IsCity() then
 
Ahh, so that's why. Of course. Just when I thought I finally had learned when player IDs was returned and when civilisation types was. [emoji14]

Thanks LeeS, I'll adjust the code accordingly.

Another question though. I read that lua can handle returns with multiple results hence the "return pCity, bEnemyKilledNearDanishCity" line. Because of the error just pointed out, I haven't had the opportunity to test it yet, but as I've never in civ5 modding seen it used before, I wonder if it actually works?
 
Yes, you can return multiple values with a function. You just need to make sure that you tell Lua to insert the values into multiple variables at once, otherwise Lua returns the first one and drops the rest. Feel free to check this post I made that talks about this while trying to explain something else to AW a while back.

As far as your issue, this is why it's extremely important to understand what values are returned from what functions. When in doubt, put in a print() statement to spit out the values of your variables and see which ones come out with unexpected values.
 
Ahh, so that's why. Of course. Just when I thought I finally had learned when player IDs was returned and when civilisation types was. [emoji14]

Thanks LeeS, I'll adjust the code accordingly.

Another question though. I read that lua can handle returns with multiple results hence the "return pCity, bEnemyKilledNearDanishCity" line. Because of the error just pointed out, I haven't had the opportunity to test it yet, but as I've never in civ5 modding seen it used before, I wonder if it actually works?
Yup. Works just fine. I have several utility functions I am using in different mods that return multiple values. You do need to "catch" the values (in my olden days on outdated machine programming systems we referred to this as "trap" the values) that are returned, as in:
Code:
local bItIsACity, bItHasTheBuilding = CheckPlotForCityWithBuilding(pPlot, iBuildingID)
As long as the function called CheckPlotForCityWithBuilding had a line something like
Code:
return bPlotIsCity, bBuildingIsPresent
And the function was otherwise written correctly, of course, you could use it to check for and return both whether there was a city at a particular plot and whether or not the city had the building for the ID# you specified in iBuildingID.

Just remember that the order you state your "catch" variables:
Code:
local [COLOR="SeaGreen"]bItIsACity[/COLOR], [COLOR="Purple"]bItHasTheBuilding[/COLOR]
have to match with the order you state your "return" variables:
Code:
return [COLOR="seagreen"]bPlotIsCity[/COLOR], [COLOR="purple"]bBuildingIsPresent[/COLOR]
And that if you have more than one return variable but in a particular instance only need to know the second, you still need to provide a place for the 1st return variable to send its value (bPlotIsCity) otherwise you will have bizarro-code.

As if I did this:
Code:
local [COLOR="SeaGreen"]bItHasTheBuilding[/COLOR] = CheckPlotForCityWithBuilding(pPlot, iBuildingID)
because now bItHasTheBuilding would match with the return line's variable bPlotIsCity, and not with its variable bBuildingIsPresent:
Code:
return [COLOR="seagreen"]bPlotIsCity[/COLOR], [COLOR="purple"]bBuildingIsPresent[/COLOR]
 
I forgot to mention in my last:

It was my understanding that you could only get GameEvents to return one variable, plus there is the issue of where that information is going to "go".

The way you have your GameEvents.UnitPrekill.Add(DMS_UnitKilledNearBorg) set up, I don't see where this line
Code:
return pCity, bEnemyKilledNearDanishCity
would be sending that information. "return" instantly ends execution of the function, so you'd never get to these lines:
Code:
if (bEnemyKilledNearDanishBorg == true) then
	print("DMS_UnitKilledNearBorg: calling function DMS_BorgGrantsFaithPerKill..")
		DMS_BorgGrantsFaithPerKill(pCity)
	end
Also I believe your "break" command is going to end the "for" loop on this line
Code:
for iPlot in PlotAreaSweepIterator(pPlot, 2, SECTOR_NORTH, DIRECTION_CLOCKWISE, DIRECTION_OUTWARDS, CENTRE_EXCLUDE) do
And so your line
Code:
return pCity, bEnemyKilledNearDanishCity
would never be executed if the "break" command is executed.

Also as soon as the "for" loop is terminated by whatever means, local pCity = iPlot:GetPlotCity() will no longer exist since it is contained within the context of the "for" loop.
 
It was my understanding that you could only get GameEvents to return one variable
I'm not quite sure what you mean by this?
---------------------
Thanks for you answers. Even if it's slow progress :)blush:) I really do feel I learn a lot every time I run into problems and get help here on the forum.

Regarding my issues, I think I've chosen a solution that's just much more simple (haven't tested it yet, though). I figured that I in the function didn't really needed to have any info returned, so I rewrote the code like this (@DarkScythe; and stopped using iPlot for a non-integer variable ;)):
Code:
function DMS_UnitKilledNearBorg(iOwner, iUnit, iUnitType, iX, iY, bDelay, iKiller)
	if bDelay then
		print("DMS_UnitKilledNearBorg: a unit is killed somewhere..")
		local pPlayer = Players[iKiller]
		local iPlayer = Players[iOwner]
		if pPlayer and pPlayer:GetCivilizationType() == civilisationDenmarkValdemarIIID then
			if pPlayer ~= iPlayer then
				print("DMS_UnitKilledNearBorg: the unit was killed by the Kingdom of Denmark and was not Danish itself..")
				local unitPlot = Map.GetPlot(iX, iY)
				for pPlot in PlotAreaSweepIterator(unitPlot, 2, SECTOR_NORTH, DIRECTION_CLOCKWISE, DIRECTION_OUTWARDS, CENTRE_EXCLUDE) do
					if pPlot and pPlot:IsCity() then
						print("DMS_UnitKilledNearBorg: the unit was killed within 2 tiles of a City..")
						local pCity = pPlot:GetPlotCity()
						if pCity:GetOwner() == iKiller then -- GetOwner() returns player ID (int), so has to be checked against iKiller (int) rather than pPlayer (player object)
							print("DMS_UnitKilledNearBorg: the city (" .. pCity:GetName() .. ") is owned by the Kingdom of Denmark..")
							if pCity:IsHasBuilding(buildingBorgID) then
								DMS_BorgGrantsFaithPerKill(pCity)
							else
								print("DMS_UnitKilledNearBorg: the city (" .. pCity:GetName() .. ") doesn't have a Borg..")
							end
						else
							print("DMS_UnitKilledNearBorg: the city (" .. pCity:GetName() .. ") is not owned by Denmark..")
						end
					end
				end
			end
		end
	end
end
 
It was my understanding that you could only get GameEvents to return one variable, plus there is the issue of where that information is going to "go".

The return value(s) from GameEvent handlers (ie the function you pass into the Add method) go into the C++ code of the DLL. At most the C++ code only uses the first return value, so while you can return as many values as you want, values beyond the first are ignored.

For the "hook" type events, all return values are ignored - these are notifications that something has happened.
For the "testAll" and "testAny" type events, the return value is assumed to be boolean and used to affect subsequent processing - typically these are "can I do something" events
For the "accumulator" type events, the return value is assumed to be a floating point number and what is done with it depends on the circumstances of the event

W
 
Top Bottom