Delete Player's Cities via LUA

Karatekid5

Warlord
Joined
Nov 11, 2012
Messages
197
Location
Pennsylvania
I'm working on a rather crazy event to spice up my AI-only games and came up with something extreme. The idea was to make a unit based on the Great Musician function (one copy would be obtainable from a wonder) and when said musician is used in another civ's territory the civ that it was used on loses all of their cities (they'd be deleted), their capital population is set to 1, and their techs are reset to just agriculture.

I know how to set the population of the capital once the event is done and how to get the owner of the plot the unit was used on, but I am unsure how to check all of the target's cities (would one use Player.GetCityByID?), get all non-capital cities, and then delete them. I don't see a delete function on the Modding Wiki yet In-Game Editor is able to delete cities entirely so this must be possible via LUA.
 
Try adding this to your function:

Code:
for city in player:Cities() do
    if player:GetCapitalCity() ~= city then
        local plot = city:Plot()
        local hexpos = ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()));
        local cityID = city:GetID()
        city:Kill()
        Events.SerialEventCityDestroyed(hexpos, playerID, cityID, -1)
    end
end

player:Cities() is a function that iterates through all the cities of the player object that calls it. city:Kill() is the relevant function that actually destroys the city; the other functions instantiate the necessary variables to fire SerialEventCityDestroyed so the game can react appropriately.

This code also uses vectors to calculate hexpos, so in order for that to function, add this line at the beginning of the file:
Code:
include("FLuaVector.lua")

And, of course, I don't know what variable names you're using for the player object and its ID, so don't forget to correct those if you named them something different.
 
Bear in mind that culture and tech penalties are based on number of cities EVER owned, not just those currently owned. Late game, the AI is going to take an awfully long time to research Mining (or any of the other 1st column techs) due to those penalties.
 
Try adding this to your function:

Code:
for city in player:Cities() do
    if player:GetCapitalCity() ~= city then
        local plot = city:Plot()
        local hexpos = ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()));
        local cityID = city:GetID()
        city:Kill()
        Events.SerialEventCityDestroyed(hexpos, playerID, cityID, -1)
    end
end

player:Cities() is a function that iterates through all the cities of the player object that calls it. city:Kill() is the relevant function that actually destroys the city; the other functions instantiate the necessary variables to fire SerialEventCityDestroyed so the game can react appropriately.

This code also uses vectors to calculate hexpos, so in order for that to function, add this line at the beginning of the file:
Code:
include("FLuaVector.lua")

And, of course, I don't know what variable names you're using for the player object and its ID, so don't forget to correct those if you named them something different.

I'm glad to see that it's so simple, and knowing that there's a function to cycle through all of a player's cities will be helpful for the future! Thank you!


Bear in mind that culture and tech penalties are based on number of cities EVER owned, not just those currently owned. Late game, the AI is going to take an awfully long time to research Mining (or any of the other 1st column techs) due to those penalties.

This is mainly intended to be a wild joke sort of event for AI-only games so I'm not really worried about balance. Though nonetheless thank you for the warning!


In the mean time I've been looking through the Mod Wiki for any LUA routines that would help me accomplish the rest. The big roadblock is finding a proper game event to hook this into. I'd simply base it on some of the code from my Pleco mod but I only want it to affect the player the unit performs a concert tour in (the pleco unit destroys resources based on where it ends its' turn and checks the plot owner) and not any civs that the unit is just passing through. I dug through Brave New World's files to see if I could find the LUA used for the Great Musician's functions and check which game event it uses, but I could find nothing at all related to the unit or concert tours in any of the LUA files. I fired up the game and checked the LUA logs to see if any scripts or game events reported anything to the log when a normal musician was used in order to aid my search, but nothing was printed to the log. Is there anywhere else I can look or do I have to just use one of the default GameEvents?
 
I've used GameEvents.UnitPreKill to track expending great musicians in my achievements-mod (as france, you unlock an achievement when you expend a great musician in the lands of a civ with who you are at war)
Unfortunately there is no way to find out if the player disbanded or performed a concert tour with the musician, but that's not much of an issue (since you won't be disbanding your musician anyways)


NOTE: I have snipped some logic used for my mod (such as ActivePlayer checking and Achievement-related methods)
Spoiler :
Code:
local iGreatMusician = GameInfoTypes.UNIT_MUSICIAN;
local iFrance = GameInfoTypes.CIVILIZATION_FRANCE;

function CheckWarConcertTour(iPlayer,iUnit,_,iX,iY,bDelay,iKiller)

        local pPlayer = Players[iPlayer];
        --playing as france and human
        if iKiller == -1 and pPlayer:GetCivilizationType()==iFrance then
            local pUnit = pPlayer:GetUnitByID(iUnit);
            local iPlotOwner = pUnit:GetPlot():GetOwner();
            --no neutral land, great musician expended, and not owning the plot yourself
            if iPlotOwner>=0 and pUnit:GetUnitType()==iGreatMusician and iPlotOwner~=iPlayer then
                local iPlotTeam = Players[iPlotOwner]:GetTeam();
                --at war with the plot owner + major civ
                if Teams[pPlayer:GetTeam()]:IsAtWar(iPlotTeam) and not Players[iPlotOwner]:IsMinorCiv() then
                    --insert city kill logic here
                end
            end
        end
end

GameEvents.UnitPrekill.Add(CheckWarConcertTour);

As you may notice, many things can be achieved by using the standard GameEvents in interesting ways.
 
Thank you very much for the example code! Now that I have an event to hook into I decided to piece together some code and give it a try in-game. The game is running the code with no errors (except if I uncomment the SetPopulation which it then gives me a null/invalid instance error) and it is running the proper checks, but the code itself isn't doing anything its' supposed to. Did I make any mistakes in the code?

Code:
include("FLuaVector.lua")

print("Big Chungus")

local iUnitBigChungus = GameInfoTypes.UNIT_BIG_CHUNGUS
local iChungusUnitClass = GameInfoTypes.UNITCLASS_BIG_CHUNGUS

function ChungusPrimalReversion(iPlayer)
    local pPlayer = Players[iPlayer]
    if (pPlayer:GetUnitClassCount(iChungusUnitClass) > 0) then
      for pUnit in pPlayer:Units() do
        if (pUnit:GetUnitType() == iUnitBigChungus) then
          local pPlot = pUnit:GetPlot()
          local iOriginalPlotOwner = pPlot:GetOwner()
           if (iOriginalPlotOwner ~= -1) and (iOriginalPlotOwner ~= iPlayer) then
                   local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
                   for city in pPlayer:Cities(pPlotOwnerPlayer) do
                      if pPlayer:GetCapitalCity() ~= city then
                        local plot = city:Plot()
                        local hexpos = ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()));
                        local cityID = city:GetID()
                        city:Kill()
                       Events.SerialEventCityDestroyed(hexpos, playerID, cityID, -1)
                     -- else
                        --City:SetPopulation(1)
                       end
                   end
           end
       end
     end
   end
end
GameEvents.UnitPrekill.Add(ChungusPrimalReversion);
 
To get the most utility out of UnitPrekill, you'll want to use more of its variables*. UnitPrekill gives you seven different variables, of which you'll want four:

- the first variable: the ID of the player whose unit just died
- the second variable: the ID of the unit that just died
- the fourth and fifth variables: the X- and Y-coordinates of the plot where the unit died

Instead of iterating through the player's units, you can enter the unit ID into Player:GetUnitByID() for the appropriate unit object; then, to find the plot, you can enter the X- and Y-coordinates into Map.GetPlot(). After that, the rest of the function looks like it should work as intended. (The reason the current code isn't working, incidentally, is that when UnitPrekill files the unit is already considered dead; this means that the iterator wouldn't find the unit you're looking for, and even if it did, the game wouldn't know the unit's plot.)

*: For more information on GameEvents and what you can do with them, I recommend the spreadsheet in post #6 of the linked thread.
 
I wasn't sure where to plug in the unit cycle (I tried instead plugging the correct unit ID variable directly into the top of the function), and added the rest of the function's variables based on how Troller used them in his own code, along with trimming some extra unit checks. The code is still nonfunctional but I hope I am currently on the right track. The LUA log produces no errors.

Code:
include("FLuaVector.lua")

print("Big Chungus")

local iUnitBigChungus = GameInfoTypes.UNIT_BIG_CHUNGUS
local iChungusUnitClass = GameInfoTypes.UNITCLASS_BIG_CHUNGUS


function ChungusPrimalReversion(iPlayer, iUnitBigChungus,_ iX, iY)
          local pPlayer = Players[iPlayer]        
         local pPlot = Map.GetPlot(iX, iY)
         local iOriginalPlotOwner = pPlot:GetOwner()
          if (iOriginalPlotOwner ~= -1) and (iOriginalPlotOwner ~= iPlayer) then
                  local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
                  for city in pPlayer:Cities(pPlotOwnerPlayer) do
                      if pPlayer:GetCapitalCity() ~= city then
                        local plot = city:Plot()
                        local hexpos = ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()));
                        local cityID = city:GetID()
                        city:Kill()
                       Events.SerialEventCityDestroyed(hexpos, playerID, cityID, -1)
                    -- else
                        --City:SetPopulation(1)
                      end
                  end
           end
  
end

GameEvents.UnitPrekill.Add(ChungusPrimalReversion);
 
Code:
local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
                  for city in pPlayer:Cities(pPlotOwnerPlayer) do
the code is incorrect. If you want to run through the cities of the player that owns the plot rather than the cities of the player who owned the dead unit, you need:
Code:
local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
                  for city in pPlotOwnerPlayer:Cities() do
What you are attempting is to run through the cities of the player who owned the 'killed' unit. The issue also applies to
Code:
pPlayer:GetCapitalCity()
Which I would code as
Code:
if city:IsCapital() == false then
rather than comparing one object variable to another. wherever possible avoid comparisons of object variables to other object variables.

Also, you have undefined variables
Code:
local hexpos = ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()));
"plot" is not how you have previously defined the plot object where the unit was killed. You defined it as "pPlot". So you are getting a nil value or a runtime error reported in the lua log for
Code:
local hexpos = ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()));
nevermind that paragraph. I see where you defined the object. But it is also unecessary. "city:GetX()" and "city:GetY()" would work just as well and would not need you to define a second object-variable for the city plot.

Here you also have an undefined variable
Code:
Events.SerialEventCityDestroyed(hexpos, playerID, cityID, -1)
"playerID" is a nil value because no such variable has been defined.

This line also does not work because you have an undefined variable-name
Code:
City:SetPopulation(1)
"City" ~= "city".
 
Last edited:
Thank you for the feedback! I fixed the code based on the changes you mentioned. I replaced the playerID value with pPlotOwnerPlayer since this would be the proper player ID to use. Unfortunately the code is still having no effect, and the Lua log still reports no errors. The comment I placed in there to see if the check is working isn't popping up in the Lua log so one of the checks from the first if statement onward seems to be failing. Here is the current code:

Code:
include("FLuaVector.lua")

print("Big Chungus")

local iUnitBigChungus = GameInfoTypes.UNIT_BIG_CHUNGUS
local iChungusUnitClass = GameInfoTypes.UNITCLASS_BIG_CHUNGUS

function ChungusPrimalReversion(iPlayer, iUnitBigChungus,_ iX, iY)
          local pPlayer = Players[iPlayer]        
         local pPlot = Map.GetPlot(iX, iY)
         local iOriginalPlotOwner = pPlot:GetOwner()
          if (iOriginalPlotOwner ~= -1) and (iOriginalPlotOwner ~= iPlayer) then
                  local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
                   for city in pPlotOwnerPlayer:Cities() do
                      if city:IsCapital() == false then
                        local plot = city:Plot()
                        local hexpos = ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()));
                        local cityID = city:GetID()
                        city:Kill()
                       print("Primal Reverted!")
                       Events.SerialEventCityDestroyed(hexpos, pPlotOwnerPlayer, cityID, -1)
                     else
                        city:SetPopulation(1)
                      end
                  end
           end
  
end

GameEvents.UnitPrekill.Add(ChungusPrimalReversion);
 
this
Code:
Events.SerialEventCityDestroyed(hexpos, pPlotOwnerPlayer, cityID, -1)
is incorrect. It needs a player ID# for the second argument rather than a player object-variable. In your case the player ID variable would be "iOriginalPlotOwner".

Is "Big Chungus" even being printed into the lua.log file when the game loads? If it is not, then you do not have the lua-file set-up correctly.
 
Code:
<event name="UnitPrekill" type="Hook">
	<arg pos="1" type="PlayerTypes" name="ePlayer"/>	--this is the ID# for the player whose unit was killed
	<arg pos="2" type="int" name="iUnit"/>			--this is the ID# for the individual unit that was killed
	<arg pos="3" type="UnitTypes" name="eUnit"/>		--this is the ID# for the TYPE of unit from XML table "Units" that was killed
	<arg pos="4" type="int" name="iPlotX"/>			--this is the plot X gridpoint where the unit was killed
	<arg pos="5" type="int" name="iPlotY"/>			--this is the plot Y gridpoint where the unit was killed
	<arg pos="6" type="bool" name="bDelay"/>		--this is boolean for whether the "killing" of the unit is delayed when the event fires
								--the event can fire twice for every unit killed. When it fires twice the first firing is
									actually before the unit is killed, and the 'bDelay' argument will be boolean true
								--you cannot accurately check which units are seen as being on the plot at the passed XY
									when bDelay is true
								--when bDealy is false you can check if any victorious-combat units of the "killer" are
									occupying the plot by iterating through all units currently seen as being on the
									plot. The killed-off unit will still show in the list of units seen as being on
									the plot.
	<arg pos="7" type="PlayerTypes" name="eByPlayer"/>	--this is the ID# of the player who killed the unit. If a player kills his own unit, such
									as by creating a city with a settler, this argument will pass the same ID # as that
									which is passed for argument "ePlayer"
								--this argument only passes a valid player ID# when bDelay is true
								--when bDelay is false the data for this argument is usually "-1"
</event>
 
I need to get this keyboard fixed... I know I typed a comma between _ and iPlotX yet there isn't one there. Fixed that and now the code runs flawlessly!
Now to add the tech removal function, I can do that with TeamTechs:SetHasTech() but I'll take care of that myself. Thank you all for everything! :)
 
Seems like I'm not out of the woods yet.... : (
The unit now works 100% as intended, and a copy of the unit spawns with a wonder I added, but.... for some reason this is causing the game to randomly crash on some computers. After the AI builds it the Unit that this code checks against will linger in their civ's territory and after that it will crash on a random turn number on a random player's turn, no matter the map size or amount of players. We were able to pinpoint the issue to this mod, I don't understand what about this is crashing the game. No errors in the logs or anything, just a big boot back to the desktop.

Code:
include("FLuaVector.lua")

print("Big Chungus")

local iUnitBigChungus = GameInfoTypes.UNIT_BIG_CHUNGUS


function ChungusPrimalReversion(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()==iUnitBigChungus then -- (iOriginalPlotOwner ~= iPlayer) then
                  local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
                   for city in pPlotOwnerPlayer:Cities() do
                      if city:IsCapital() == false then
                        local plot = city:Plot()
                        local hexpos = ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()));
                        local cityID = city:GetID()
                        city:Kill()
                       print("Primal Reverted!")
                       Events.SerialEventCityDestroyed(hexpos, iOriginalPlotOwner, cityID, -1)
                     else
                        city:SetPopulation(1)
                      end
                  end

                  local RevertedTeam = Teams[pPlotOwnerPlayer:GetTeam()]
                  RevertedTeam:SetHasTech(1, false)
                  RevertedTeam:SetHasTech(2, false)
                  RevertedTeam:SetHasTech(3, false)
                  RevertedTeam:SetHasTech(4, false)
                  RevertedTeam:SetHasTech(5, false)
                  RevertedTeam:SetHasTech(6, false)
                  RevertedTeam:SetHasTech(7, false)
                  RevertedTeam:SetHasTech(8, false)
                  RevertedTeam:SetHasTech(9, false)
                  RevertedTeam:SetHasTech(10, false)
                  RevertedTeam:SetHasTech(11, false)
                  RevertedTeam:SetHasTech(12, false)
                  RevertedTeam:SetHasTech(13, false)
                  RevertedTeam:SetHasTech(14, false)
                  RevertedTeam:SetHasTech(15, false)
                  RevertedTeam:SetHasTech(16, false)
                  RevertedTeam:SetHasTech(17, false)
                  RevertedTeam:SetHasTech(18, false)
                  RevertedTeam:SetHasTech(19, false)
                  RevertedTeam:SetHasTech(20, false)
                  RevertedTeam:SetHasTech(21, false)
                  RevertedTeam:SetHasTech(22, false)
                  RevertedTeam:SetHasTech(23, false)
                  RevertedTeam:SetHasTech(24, false)
                  RevertedTeam:SetHasTech(25, false)
                  RevertedTeam:SetHasTech(26, false)
                  RevertedTeam:SetHasTech(27, false)
                  RevertedTeam:SetHasTech(28, false)
                  RevertedTeam:SetHasTech(29, false)
                  RevertedTeam:SetHasTech(30, false)
                  RevertedTeam:SetHasTech(31, false)
                  RevertedTeam:SetHasTech(32, false)
                  RevertedTeam:SetHasTech(33, false)
                  RevertedTeam:SetHasTech(34, false)
                  RevertedTeam:SetHasTech(35, false)
                  RevertedTeam:SetHasTech(36, false)
                  RevertedTeam:SetHasTech(37, false)
                  RevertedTeam:SetHasTech(38, false)
                  RevertedTeam:SetHasTech(39, false)
                  RevertedTeam:SetHasTech(40, false)
                  RevertedTeam:SetHasTech(41, false)
                  RevertedTeam:SetHasTech(42, false)
                  RevertedTeam:SetHasTech(43, false)
                  RevertedTeam:SetHasTech(44, false)
                  RevertedTeam:SetHasTech(45, false)
                  RevertedTeam:SetHasTech(46, false)
                  RevertedTeam:SetHasTech(47, false)
                  RevertedTeam:SetHasTech(48, false)
                  RevertedTeam:SetHasTech(49, false)
                  RevertedTeam:SetHasTech(50, false)
                  RevertedTeam:SetHasTech(51, false)
                  RevertedTeam:SetHasTech(52, false)
                  RevertedTeam:SetHasTech(53, false)
                  RevertedTeam:SetHasTech(54, false)
                  RevertedTeam:SetHasTech(55, false)
                  RevertedTeam:SetHasTech(56, false)
                  RevertedTeam:SetHasTech(57, false)
                  RevertedTeam:SetHasTech(58, false)
                  RevertedTeam:SetHasTech(59, false)
                  RevertedTeam:SetHasTech(60, false)
                  RevertedTeam:SetHasTech(61, false)
                  RevertedTeam:SetHasTech(62, false)
                  RevertedTeam:SetHasTech(63, false)
                  RevertedTeam:SetHasTech(64, false)
                   RevertedTeam:SetHasTech(65, false)
                  RevertedTeam:SetHasTech(66, false)
                  RevertedTeam:SetHasTech(67, false)
                  RevertedTeam:SetHasTech(68, false)
                  RevertedTeam:SetHasTech(69, false)
                  RevertedTeam:SetHasTech(70, false)
                  RevertedTeam:SetHasTech(71, false)
                  RevertedTeam:SetHasTech(72, false)
                  RevertedTeam:SetHasTech(73, false)
           end
  
end

GameEvents.UnitPrekill.Add(ChungusPrimalReversion);
 
I'm not sure what might be causing the crash, but— on an unrelated note— you can remove the player's techs more easily if you use a for loop:
Code:
for iTech = 1, 73, 1 do
    RevertedTeam:SetHasTech(iTech, false)
end
 
You could try adding some print("text here")-statements throughout the code to see which part may cause the crash.
Also, is your tech tree modified? Perhaps it could be related to you trying to do "RevertedTeam:SetHasTech(ID, false)" for an ID that is not in the technologies table (which may happen when you remove a tech).
 
Code:
for tech in GameInfo.Technologies() do
  if (tech.ID > 0) then
    RevertedTeam:SetHasTech(tech.ID, false)
  end
end
 
Thank you very much for the loop for the tech removal code, I was wondering if there was a better way to do it when I pasted that long list! xD

The problem is that the crash doesn't occur when the unit is used (I've tested that many times with no issues). It'll just happen at the start of a random player's turn on a random turn number. The only thing that's consistent is that it happens some time after someone built the wonder and that the unit is on the field, usually in the builder's territory. Though the player who's turn crashes the game isn't even the builder of the unit. For example, Kamehameha could build the wonder and have the unit, yet it's Huey Long's turn that crashes the game. We could then reload a save, delete Huey using IGE, but then say Washington is one or two players afterward. Then the start of Washington's turn would crash the game. It always happens at the beginning of a turn, so none of the logs will tell me exactly what the AI was trying to do when it happened. Is there any way to check this? I noticed it happens more easily on my friend's more heavily-modded setup but it contains no mechanics mods, just custom civs, and it was perfectly fine before we started using this mod. So far I've encountered no crashes on my own setup, but I want to see if the issue is this mod's code. We were able to have a 43-player match on my friend's setup with no crashes before adding this mod.
 
Except Custom Civs are heavily modded.
 
The first thing I would do is as mentioned earlier. Lace print statements into the code to see if any of them are printed into the lua.log as the last thing before the crash.

As you've currently written your code, however, the owner of the unit can delete their own cities and de-tech themselves if they kill off the unit within their own territory.

One thing I remember causing one of my games a long time ago to become unrecoverable was using IGE to delete an adversary city that had a trade-route starting from that city. It CTD'd when I progressed turns and if I tried to load a save from the turn when I deleted the city.
 
Top Bottom