1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

Detecting City with PlotAreaSweepIterator

Discussion in 'Civ5 - SDK / LUA' started by Danmacsch, Aug 19, 2015.

  1. Danmacsch

    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..
     
  2. DarkScythe

    DarkScythe Hunkalicious Holo

    Joined:
    May 6, 2014
    Messages:
    804
    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.
     
  3. Danmacsch

    Danmacsch Geheimekabinetsminister

    Joined:
    Jan 14, 2014
    Messages:
    1,316
    Location:
    Copenhagen, Denmark
    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.
     
  4. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    7,140
    Location:
    Illinois, USA
    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
     
  5. Danmacsch

    Danmacsch Geheimekabinetsminister

    Joined:
    Jan 14, 2014
    Messages:
    1,316
    Location:
    Copenhagen, Denmark
    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?
     
  6. DarkScythe

    DarkScythe Hunkalicious Holo

    Joined:
    May 6, 2014
    Messages:
    804
    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.
     
  7. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    7,140
    Location:
    Illinois, USA
    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] 
     
  8. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    7,140
    Location:
    Illinois, USA
    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.
     
  9. Danmacsch

    Danmacsch Geheimekabinetsminister

    Joined:
    Jan 14, 2014
    Messages:
    1,316
    Location:
    Copenhagen, Denmark
    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
     
  10. whoward69

    whoward69 DLL Minion

    Joined:
    May 30, 2011
    Messages:
    8,529
    Location:
    Near Portsmouth, UK
    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
     

Share This Page