LUA Help - Plot:SetOwner() Function Not Working as Expected

Gleb Bazov

Warlord
Joined
Feb 13, 2017
Messages
176
CULTURE BOMB IMPLEMENTATION in Lua - City unable to work the new tiles.

I have exhausted all avenues of trying to figure out how this function works in LUA. The code I am writing implements a Culture Bomb functionality in LUA and works without problem, apart from this: When the ownership of plot tiles is changed, the borders expand as expected, but the tiles remain unworkable by the city and no buildings or districts can be placed on them.

The only solution is to build a new city next to the culture-bombed area, and then assume ownership of tiles (swap tile ownership) by that city, using the city manager. However, if the culture bomb is detonated near two cities that already exist, neither of the cities can use the tiles. I presume that a third city is then needed to claim ownership of the tiles, and so forth.

This wouldn't be a huge problem for district culture bombs, because I can always terminate the process in XML, and create a dummy building (however, that would lose the flexibility of setting the culture bomb radius at anything I want), but, for improvement culture bombs, this is a real problem.

I've tried a variety of variations to SetOwner(), using SetOwner( pPlayer, pCity) or SetOwner( pPlayer, pCity, true ) and variations of these, but nothing works. SetOwner() appears to work both for a City and a Player, and does required one of them, as giving it a random integer to work with crashes the game.

I've borrowed liberally from LeeS' mods for the purpose, and everything works just fine, except actually allowing the city to work the tiles. Here is the small snippet of the code that zeroes in on SetOwner();

Code:
function DetonateCultureBomb( PlotX, PlotY, iPlotRadius, pPlayer )
    if (PlotX ~= nil) and (PlotY ~= nil) and (PlotX > 0) and (PlotY > 0) then
        for k,pPickPlot in pairs(GetAllPlotsInRadiusR(Map.GetPlot(PlotX, PlotY), iPlotRadius, "ExcludeCenterPlot")) do
            local bAddPlot = (Map.GetPlotDistance(PlotX, PlotY, pPickPlot:GetX(), pPickPlot:GetY()) <= iPlotRadius)
            if bAddPlot then
                pPickPlot:SetOwner(pPlayer)
            end
        end
    end
end

As far as I can tell, the problem is solely with Plot:SetOwner(). I just don't know what arguments go there, although I suspect I got them right the first time (playerID). The problem may also be with needing to refresh the current game state, but I don't know how to do that. Besides, the borders change, so that couldn't be the sole reason for the problem. Maybe this has to do with UI Lua files, and I'll keep digging, but I would really appreciate it if someone could look at this as well. Thank you!

Here is the full code. I am not including the separate chunks of the code that are needed for the improvement culture bomb, but the idea is the same, though the conditions are somewhat different (improvement, resource, resource visibility (check tech, etc.)).

Code:
--==============================================================================
--    Culture Bomb Implementation in LUA
--===============================================================================

--==============================================================================
--    Get All Plots within range 'X' of a given plot
--==============================================================================

local AdjacentPlotDirectionCalcs = { NW = (function(X, Y) if (Y % 2 ~= 0) then return (X + 1), (Y + 1) else return X, (Y + 1) end end),
    W = (function(X, Y) return (X + 1), Y end),
    SW = (function(X, Y) if (Y % 2 ~= 0) then return (X + 1), (Y - 1) else return X, (Y - 1) end end),
    SE = (function(X, Y) if (Y % 2 == 0) then return (X - 1), (Y - 1) else return X, (Y - 1) end end),
    E = (function(X, Y) return (X - 1), Y end),
    NE = (function(X, Y) if (Y % 2 == 0) then return (X - 1), (Y + 1) else return X, (Y + 1) end end) }

function GetPlotCoordsInCardinalDirectionAtRadiusR(X, Y, R, sDirection)
    if (R < 1) or (sDirection == nil) or (AdjacentPlotDirectionCalcs[sDirection] == nil) then
        return X, Y
    end
    local iX, iY = AdjacentPlotDirectionCalcs[sDirection](X, Y)
    if R > 1 then
        local iStartRadius = 1
        while iStartRadius < R do
            iX, iY = AdjacentPlotDirectionCalcs[sDirection](iX, iY)
            iStartRadius = iStartRadius + 1
        end
    end
    return iX, iY
end

function GetAllPlotsInRadiusR(pPlot, iRadius, sExcludeCenterPlot)
    local tTemporaryTable = {}
    if (pPlot ==  nil) or (iRadius == nil) or (iRadius < 1) then
        return tTemporaryTable
    end
    local bExcludeCenter = ((sExcludeCenterPlot ~= nil) and (sExcludeCenterPlot == "ExcludeCenterPlot"))
    local iCenterX, iCenterY = pPlot:GetX(), pPlot:GetY()
    local iStartX, iStartY = GetPlotCoordsInCardinalDirectionAtRadiusR(iCenterX, iCenterY, iRadius, "NE")
    local iEndX, iEndY = GetPlotCoordsInCardinalDirectionAtRadiusR(iCenterX, iCenterY, iRadius, "SW")
    local iRowEndX, iRowEndY = GetPlotCoordsInCardinalDirectionAtRadiusR(iCenterX, iCenterY, iRadius, "NW")
    local iRowStartX, iRowStartY = iStartX, iStartY

    for iPlotY = iStartY, iEndY, -1 do
        local pLoopPlot = nil
        for iPlotX = iRowStartX, iRowEndX do
            local pLoopPlot = Map.GetPlot(iPlotX, iPlotY)
            if (pLoopPlot ~= nil) then
                if (pLoopPlot ~= pPlot) then
                    table.insert(tTemporaryTable,pLoopPlot)
                else
                    if not bExcludeCenter then
                        table.insert(tTemporaryTable,pLoopPlot)
                    end
                end
            end
        end
        if ((iPlotY - 1) >= iCenterY) then
            iRowStartX = AdjacentPlotDirectionCalcs.SE(iRowStartX, iPlotY)
            iRowEndX = AdjacentPlotDirectionCalcs.SW(iRowEndX, iPlotY)
        else
            iRowStartX = AdjacentPlotDirectionCalcs.SW(iRowStartX, iPlotY)
            iRowEndX = AdjacentPlotDirectionCalcs.SE(iRowEndX, iPlotY)
        end
        PrintToLog("GetAllPlotsInRadiusR: After calc for a new Y-row, iRowStartX is now " .. tostring(iRowStartX) .. " and iRowEndX is now " .. tostring(iRowEndX))
    end
    return tTemporaryTable
end

--==============================================================================
--    Check District Adjacency Function
--==============================================================================

function CheckDistrictAdjacency( PlotX, PlotY, zPlotRadius, xRequiredFeature )
    local pPlot = Map.GetPlot(PlotX, PlotY)
    local iPlotIndex = pPlot:GetIndex()
    local iCity = Cities.GetPlotPurchaseCity(iPlotIndex)
    if (iCity ~= nil) then
        print ("Starting the process of checking adjacencies.")
        for k,pPickPlot in pairs(GetAllPlotsInRadiusR(Map.GetPlot(PlotX, PlotY), zPlotRadius, "ExcludeCenterPlot")) do
            local bAddPlot = (Map.GetPlotDistance(PlotX, PlotY, pPickPlot:GetX(), pPickPlot:GetY()) <= zPlotRadius)
            if bAddPlot then
                local pPickPlotFeature = pPickPlot:GetFeatureType()
                local xFeatureMatchPlot = (pPickPlotFeature == GameInfo.Features[xRequiredFeature].Index)
                print ("The feature # of the checked plot is -- " .. pPickPlotFeature .. " -- is this a match? Let's see.")
                if xFeatureMatchPlot then
                    print ("Success! Adjacency confirmed on this plot. No need to check further. Returning true.")
                    return true
                elseif k == zPlotRadius*6 and not xFeatureMatchPlot then
                    break
                else
                    print ("This feature type does not qualify for adjacency requirements. Checking the next plot.")
                end
            end
        end
    end
    print ("Failure! We've run out of adjacent plots and none of them contain the required feature.")
    return false
end

--==============================================================================
--    Culture Bomb Function
--==============================================================================

function DetonateCultureBomb( PlotX, PlotY, iPlotRadius, pPlayer )
    if (PlotX ~= nil) and (PlotY ~= nil) and (PlotX > 0) and (PlotY > 0) then
        print ("Activating the Culture Bomb at the Given Location.")
        for k,pPickPlot in pairs(GetAllPlotsInRadiusR(Map.GetPlot(PlotX, PlotY), iPlotRadius, "ExcludeCenterPlot")) do
            local bAddPlot = (Map.GetPlotDistance(PlotX, PlotY, pPickPlot:GetX(), pPickPlot:GetY()) <= iPlotRadius)
            if bAddPlot then
                pPickPlot:SetOwner(pPlayer)
            end
        end
    end
end

--==============================================================================
--    Activate Culture Bomb When Requirements are Satisfied
--==============================================================================

local sRequiredCiv = "CIVILIZATION_EGYPT"
local iRequiredDistrict = "DISTRICT_ENCAMPMENT"

function OnRequiredDistrictCompleted( playerID, cityID, orderType, unitType, canceled, typeModifier )
    if orderType == 2 and GameInfo.Districts[unitType].DistrictType == iRequiredDistrict then
        print (unitType)
        print ("District Conditions Satisfied")
        local pPlayer = Players[ playerID ]
        local uCity = pPlayer:GetCities():FindID(cityID)
        local pPlayerConfig = PlayerConfigurations[ playerID ]
        local pPlayerCivName = pPlayerConfig:GetCivilizationTypeName()
        if pPlayerCivName == sRequiredCiv then
            print ("Civilization Type Conditions Satisfied")
            local zPlotRadius = 1
            local xRequiredFeature = "FEATURE_JUNGLE"
            if CheckDistrictAdjacency( pDistrictX, pDistrictY, zPlotRadius, xRequiredFeature ) then
                local pDistrict = uCity:GetDistricts():GetDistrict(unitType)
                local pDistrictX = pDistrict:GetX()
                local pDistrictY = pDistrict:GetY()
                local tPlotRadius = 1
                DetonateCultureBomb( pDistrictX, pDistrictY, tPlotRadius, pPlayer )
            end
        end
    end
end
Events.CityProductionCompleted.Add( OnRequiredDistrictCompleted );
 
I've not been able to use plot:SetOwner() in my test, so I've switched to:
Code:
WorldBuilder.CityManager():SetPlotOwner( x, y, playerID, cityID )

and to remove ownership:
Code:
WorldBuilder.CityManager():SetPlotOwner( x, y, false )

but IIRC WorldBuilder exist only in script context, not UI
 
This is terrific, Gedemon! Thank you very much! I will give this a go and see if it works :)
 
Gedemon, if I may trouble your for an additional tip. Where are the WorldBuilder functions? Should I just go check in the game's Lua files, or there is a repository online?

Thanks!
 
As far as I can tell, the problem is solely with Plot:SetOwner(). I just don't know what arguments go there, although I suspect I got them right the first time (playerID).
Well, you figured it out correctly, but still you used pPlayer "table" instead of pPlayerID "number" in the call. That's why it didn't work as expected. It's a sneaky error, happens to me very often... Some Lua functions return number some return table, without documentation it's easy to get confused.
 
I tried all of the variations, Infixo, but it had nothing to do with pPlayer or pPlayerID. It worked equally well with pPlayer (as defined in my code) and the playerID raw result from the event. It EVEN worked with cityID (Event output) and pCity (as defined in the code). Every time the outcome was the same - borders increased as required, ownership change was reflected in the tooltip (and every time it was the right city that got ownership), but none of the newly added plots were workable by a city.

So, the code does not appear to have been the problem. The SetOwner() function appears to simply be bugged. The function that Gedemon suggested works flawlessly.
 
I've evolved to using a far simpler method of getting all plots within a given radius of a central plot:
Code:
function GetValidPlotsInRadiusR(pPlot, iRadius)
	local tTempTable = {}
	if pPlot ~= nil then
		local iPlotX, iPlotY = pPlot:GetX(), pPlot:GetY()
		for dx = (iRadius * -1), iRadius do
			for dy = (iRadius * -1), iRadius do
				local pNearPlot = Map.GetPlotXYWithRangeCheck(iPlotX, iPlotY, dx, dy, iRadius);
				if pNearPlot then
					table.insert(tTempTable, pNearPlot)
				end
			end
		end
	end
	return tTempTable
end
Copied essentially from Firaxis' code for placing goodyhuts but modified slightly.

And for getting all plots that belong to a given city
Code:
local iCityRadius = 3
function GetCityPlots(pCity)
	local tTempTable = {}
	if pCity ~= nil then
		local iCityOwner = pCity:GetOwner()
		local iCityX, iCityY = pCity:GetX(), pCity:GetY()
		for dx = (iCityRadius * -1), iCityRadius do
			for dy = (iCityRadius * -1), iCityRadius do
				local pPlotNearCity = Map.GetPlotXYWithRangeCheck(iCityX, iCityY, dx, dy, iCityRadius);
				if pPlotNearCity and (pPlotNearCity:GetOwner() == iCityOwner) and (pCity == Cities.GetPlotPurchaseCity(pPlotNearCity:GetIndex())) then
					table.insert(tTempTable, pPlotNearCity)
				end
			end
		end
	end
	return tTempTable
end
Still far less effiecient than William Howard's co-routines method from civ5's PlotIterators, but since we still cannot "include" files easily (ie, without pretty much copying the entire code into a file directly) a little bit of efficiency is probably not a bad trade for far less code gymastics than the first few methods I wrote.
 
Top Bottom