Changing city to another Civ via LUA and Instance Does Not Exist

Karatekid5

Warlord
Joined
Nov 11, 2012
Messages
198
Location
Pennsylvania
Making another silly addition for my AI games and decided to do something simple. This is intended to be an Classical Era unit that deletes all of a player's cities but takes it a step further and flips their capital to the barbarians. I found the correct routine and am running it within the PlayerKermited function in hopes that variables from it will carry, as the thread I found the city flip code in mentioned. It works for the most part but the code never finishes, giving me an Instance Does Not Exist error on Line 12 in the ChangeCityOwner function. I have iBarbarianCiv as an argument and it should be plugging into newOwner, but I assume that the player ID for the Barbarians isn't being grabbed it seems.
Thanks in advance to anyone who can help!

Code:
include("FLuaVector.lua")

print("Stay woke!")

local iUnitKermit = GameInfoTypes.UNIT_KERMIT
local iPalace = GameInfoTypes.BUILDING_PALACE
local iBarbarianCiv = GameInfoTypes.CIVILIZATION_BARBARIAN

function ChangeCityOwner(oldOwner, cityID, newOwner)
  local pPlayer = Players[newOwner];
  local pCity = cityID
  pPlayer:AcquireCity(pCity, true, false);
end

function PlayerKermited(iPlayer,iUnit,_,iPlotX, iPlotY, bDelay)

          local pPlayer = Players[iPlayer]   
          local pPlot = Map.GetPlot(iPlotX, iPlotY)
          local pUnit = pPlayer:GetUnitByID(iUnit);
          local iOriginalPlotOwner = pPlot:GetOwner()
                
           if (iOriginalPlotOwner ~= -1) and pUnit:GetUnitType()==iUnitKermit then -- (iOriginalPlotOwner ~= iPlayer) then
                   local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
                       if bDelay == true then
                          Events.AudioPlay2DSound("AS2D_KERMIT_MUSIC")
                       end
                   for city in pPlotOwnerPlayer:Cities() do
                      if city:IsCapital() == false and city:IsOriginalMajorCapital() == false and city:IsHolyCityAnyReligion() == false then
                        local plot = city:Plot()
                        local hexpos = ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()));
                        local cityID = city:GetID()
                        city:SetPopulation(0)
                        city:Kill()
                        print("Kermited!")
                       Events.SerialEventCityDestroyed(hexpos, iOriginalPlotOwner, cityID, -1)
                      else
                        city:SetPopulation(1)
                        local cityID = city:GetID()
                        ChangeCityOwner(pPlotOwnerPlayer, cityID, iBarbarianCiv)

                        end
                   end
           end
end

GameEvents.UnitPrekill.Add(PlayerKermited);
 
You cannot change what you are iterating over (the number of cities owned by pPlotOwnerPlayer) within the loop (with city:Kill())

A player has three cities, A, B and C
You set city to A, then kill it, the player now only has 2 cities (but the loop is going to run to three)
The next city you get is C (not B), which you kill
The next city doesn't exist any more, so throws an error when you try to access it

You'll need to find all the city IDs and store them in a temporary array, then iterate over them with your kill code
 
I'm not completely sure if that's the problem, though. This is based off of previous working code and I successfully had the capital city exception reduce population to 1 and remove all buildings despite the iteration change. And this code still sets the population to 1 so variable "city" is still valid. Is variable cityID grabbing the ID for a deleted city instead of the capital?
 
Before giving the array a shot I decided to play with the code a little more. First I tried adding a second city iteration loop after the first one, splitting up the functions. First one had the city deletion routine, while the second one had the capital city routine. The intent was to have the deletion loop run first and delete secondary cities, then the second one would run after it over all of the remaining cities (which would be capitals) so that the iteration isn't changed. This didn't work, likely the two iteration city loops are running at the same time so I should try setting one of the cityID variables so that one is cityID (for deleted cities) and the other is cityID2 (for capitals) so that they won't step on each other's toes. It probably won't work but I want to see if there's a more simple way to do this before I work on the array tonight.

Next I reverted the code back to before the double city loop and made a couple of tweaks, including adding comments to the ChangeCityOwner function (which does thankfully run). I noticed that the city loop iterates over the player's capital first, as when running the code, my capital's population was set to 1 before hitting a runtime error. Though the error was due to the checks I added to the ChangeCityOwner function. The intent was to have them grab the City and Player IDs of the city being flipped and the newOwner and printing them to the log. But I got an error for trying to print a number value to the log despite being able to do it in another script. With this I'm just trying to see exactly when functionality ceases.
 
Code:
local iBarbarianCiv = GameInfoTypes.CIVILIZATION_BARBARIAN

function ChangeCityOwner(oldOwner, cityID, newOwner)
  local pPlayer = Players[newOwner];
  local pCity = cityID
  pPlayer:AcquireCity(pCity, true, false);
end
You are sending the value for GameInfoTypes.CIVILIZATION_BARBARIAN to ChangeCityOwner as "newOwner". But the value of GameInfoTypes.CIVILIZATION_BARBARIAN is not a Player ID # from the lua "players" table. It is the database ID # for the thing called CIVILIZATION_BARBARIAN.
Code:
ChangeCityOwner(pPlotOwnerPlayer, cityID, iBarbarianCiv)
Also you're sending a player object variable for "oldOwner" instead of a player ID #, but it doesn't actually matter since the defined ChangeCityOwner(oldOwner, cityID, newOwner) is never making use of the first argument passed to it.

The Barb player's Player ID # from the lua "Players" table is # 63.
The "GameInfoTypes" value for CIVILIZATION_BARBARIAN is "19" unless the database is severely alterred by mods.

You're also giving Player:AcquireCity() a city ID # but I'm not sure you don't need a city object there instead.
 
Last edited:
Thank you for the advice! Looks like that's one thing I forgot to do! I'd set it directly to 63 but I want to properly grab a player ID via code so that I can reference it later. I added a couple of lines to the beginning of the code to get the Barbarian civ's table slot and player ID.

I have managed to make some progress. While I still get errors with the line that's supposed to flip the city to the Barbarians (it complains that it can't index it due to being a number value), if I comment that line out the rest of the code works as intended. I used it on a civ that I gave England's capital to along with two other cities. Running the code, both capitals had their population set to 1 while the others were deleted (based on what I saw in the logs there seems to be no need for a temporary array). I checked the LUA logs and it is grabbing the PlayerID and the Capital City IDs as it should. For some reason, though, it is reporting a player ID of 19 and not 63.

Here is the current code:

Code:
include("FLuaVector.lua")

print("Stay woke!")

local iUnitKermit = GameInfoTypes.UNIT_KERMIT
local iPalace = GameInfoTypes.BUILDING_PALACE
local iBarbarianCiv = GameInfoTypes.CIVILIZATION_BARBARIAN
local pBarbarianCiv = Players[iBarbarianCiv]
local pBarbarianID = pBarbarianCiv:GetID()

function ChangeCityOwner(oldOwner, cityID, newOwner)
  print("ChangeCityOwner has fired!")
  local pPlayer = newOwner;
  print(newOwner, "should recieve the Kermited City")
  local pCity = cityID
  print(cityID, "is the City ID")
  newOwner:AcquireCity(pCity, true, false);
end

function PlayerKermited(iPlayer,iUnit,_,iPlotX, iPlotY, bDelay)

          local pPlayer = Players[iPlayer]   
         local pPlot = Map.GetPlot(iPlotX, iPlotY)
         local pUnit = pPlayer:GetUnitByID(iUnit);
         local iOriginalPlotOwner = pPlot:GetOwner()
               
          if (iOriginalPlotOwner ~= -1) and pUnit:GetUnitType()==iUnitKermit then -- (iOriginalPlotOwner ~= iPlayer) then
                  local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
                      if bDelay == true then
                         Events.AudioPlay2DSound("AS2D_KERMIT_MUSIC")
                      end
                   for city in pPlotOwnerPlayer:Cities() do
                      if city:IsCapital() == false and city:IsOriginalMajorCapital() == false and city:IsHolyCityAnyReligion() == false then
                        local plot = city:Plot()
                        local hexpos = ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()));
                        local cityID = city:GetID()
                       city:SetPopulation(0)
                        city:Kill()
                       print("Kermited!")
                       Events.SerialEventCityDestroyed(hexpos, iOriginalPlotOwner, cityID, -1)
                      else
                       local cityID2 = city:GetID()
                       city:SetPopulation(1)
                       ChangeCityOwner(pPlotOwnerPlayer, cityID2, pBarbarianID)
                        end
                  end
          end
end

GameEvents.UnitPrekill.Add(PlayerKermited);
 
GameInfoTypes.CIVILIZATION_BARBARIAN <> 63 !!
GameInfoTypes is pulling ID #s directly from the game's database for the <Type> within a given XML/SQL game-table. The lua "Players" table has nothing at all to do with anything in the game's Database.

For some reason, though, it is reporting a player ID of 19 and not 63.
And also please read my previous post.
 
These two things are equivalent
Code:
GameInfoTypes.CIVILIZATION_BARBARIAN
GameInfo.Civilizations["CIVILIZATION_BARBARIAN"].ID
And neither of them are the lua Player ID number for the Barbarians.

If I play the game as Rome I am lua Player # 0, but Rome's
Code:
GameInfoTypes.CIVILIZATION_ROME
is never '0'.

If I play as Brazil my lua Player # is still # 0, but again
Code:
GameInfoTypes.CIVILIZATION_BRAZIL
is never '0'.
 
I've been doing my research all night and trying all sorts of different code loops in attempts to get the Barbarians LUA Player ID# of 63 instead of their XML/SQL player ID# of 19. The only success was running a Player.DoTurn loop that checked every player while they were taking their turn, but that was inside its' own function and not a global variable, and thus won't carry over and results in newOwner still coming up as nil. Plugging 63 in directly makes an error that newOwner can't be indexed due t being a number value, and # 63 results in an error about a number value of undetermined length. Every other loop I've tried results in the database Player ID#, and no other code I could find on other threads loops through and checks player LUA ID numbers. I even tried a "for playerID = 0, GameDefines.MAX_MAJOR_CIVS - 1 do" loop in several ways but could never get the LUA ID. How would I properly make this loop? Or if impossible then what is the proper syntax to make the ChangeCityOwner function recognize it as an ID and not just a number?
I'm sorry for being such a nuisance, I really feel awful about it. I'm just beyond frustrated that I'm struggling to do something simple and end up having to bother others all the time.

Here is the current code. The loop at the top returns either a table value or a database player ID but not the LUA player ID.

Code:
include("FLuaVector.lua")

print("Stay woke!")

local iUnitKermit = GameInfoTypes.UNIT_KERMIT
local iPalace = GameInfoTypes.BUILDING_PALACE
local iBarbarianCiv = GameInfoTypes.CIVILIZATION_BARBARIAN

for iPlayerInLoop = 0,64 do
   if (iPlayerInLoop == iBarbarianCiv) then
       local pBarbarianID = Players[iPlayerInLoop]
                print(iPlayerInLoop, "is the Barbarian ID")
   end
end

function ChangeCityOwner(oldOwner, cityID, newOwner)
  print("ChangeCityOwner has fired!")
  local pPlayer = newOwner;
  print(newOwner, "should recieve the Kermited City")
  local pCity = cityID
  print(cityID, "is the City ID")
  newOwner:AcquireCity(pCity, true, false);
end

function PlayerKermited(iPlayer,iUnit,_,iPlotX, iPlotY, bDelay)

          local pPlayer = Players[iPlayer] 
         local pPlot = Map.GetPlot(iPlotX, iPlotY)
         local pUnit = pPlayer:GetUnitByID(iUnit);
         local iOriginalPlotOwner = pPlot:GetOwner()

             
          if (iOriginalPlotOwner ~= -1) and pUnit:GetUnitType()==iUnitKermit then -- (iOriginalPlotOwner ~= iPlayer) then
                  local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
                      if bDelay == true then
                         Events.AudioPlay2DSound("AS2D_KERMIT_MUSIC")
                      end
                   for city in pPlotOwnerPlayer:Cities() do
                      if city:IsCapital() == false and city:IsOriginalMajorCapital() == false and city:IsHolyCityAnyReligion() == false then
                        local plot = city:Plot()
                        local hexpos = ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()));
                        local cityID = city:GetID()
                       city:SetPopulation(0)
                        city:Kill()
                       print("Kermited!")
                       Events.SerialEventCityDestroyed(hexpos, iOriginalPlotOwner, cityID, -1)
                      else
                       local cityID2 = city:GetID()
                       city:SetPopulation(1)
                       ChangeCityOwner(pPlotOwnerPlayer, cityID2, pBarbarianID)
                        end
                  end
          end
end

GameEvents.UnitPrekill.Add(PlayerKermited);
 
pPlayer:AcquireCity() takes as its first parameter a city object NOT a city ID

Assuming "oldOwner" is a player ID, then pCity should be set to Players[oldOwner]:GetCityByID(cityID)

In your call to ChangeCityOwner() pBarbarianID is nil, as you local'ed it in the top loop.

You don't need to discover the ID of the Barbie player, it is ALWAYS 63
 
Now I see, that was the key difference here! I grabbed ID data in some places where I should've grabbed object data and visa versa. Though, for future reference if I wanna grab a player LUA ID that isn't consistent, how would I do that?
I couldn't find some of these functions documented on the wiki so I wasn't sure whether they were looking for an Object or ID. Aside from the changes you recommended, I noticed that I needed to plug in iOriginalPlotOwner as the oldOwner argument instead of pPlotOwnerPlayer, and to get the player object for AcquireCity I needed to add "local pPlayer = Players[newOwner]". The code now works completely as intended! It's been a roller coaster but I learn something new every time I do this. I can't thank you guys enough for always helping out!
 
Back
Top Bottom