Destroy Building on City Capture

Magil

Monarch
Joined
Sep 26, 2010
Messages
1,618
So, I'm looking for a way to, when a city is captured, check to see if a specific building is present, and if it is, remove that building (alternately, look to see if a building has a particular property, in this case MaxPlayerInstances > 0). I imagine this would need to be done in LUA as it doesn't appear the game's XML/SQL supports removing buildings, except perhaps if it was hard-coded to an outer defense value.

I'm pretty clueless when it comes to LUA. Nevertheless, I have made some attempts to figure it out. I first supposed maybe if I could put it into RazeCity.lua somewhere, I could maybe hack that in, but then I looked at this table (which is helpful, thank you Gedemon) and determined that City.RemoveBuilding was a GameplayScript rather than a UI Script, so that probably wouldn't work. So, a Gameplay Script using an event was my next thought, but also checking that table, I couldn't find any events that corresponded to a city capture.

I'm at a bit of a loss, so I thought I'd just ask if anyone knew a better way to go about this, or could explain where my logic failed.
 
@Magil - I'm necroing this thread as I'm currently watching a Let's Play series from @PotatoMcWhiskey where he's using Wondrous Wonders. It looks like it still uses an OuterDefenseHitPoints value to destroy the National Wonders on capture. I'm assuming you still haven't been able to figure out a way via LUA to remove buildings, correct?

@AnyoneElse - It's been awhile since Magil's been on here, so I just want to throw it out there to anyone else (@LeeS , @Gedemon , @FearSunn , etc.) who might have an idea how the OP could be accomplished?
 
Last edited:
There is GameEvents.CityConquered (iVictoriousPlayer, iDefeatedPlayer, iNewCityID, iCityPlotX, iCityPlotY) that was added in a patch and could be used maybe, but I've not tested it.

I already use Events.DistrictRemovedFromMap followed by Events.CityInitialized to determine if a city has been captured (when a city is captured, the core seems to "remove" the city center district and "initialize" a new city, so you just need to track that happening sequentially on a specific plot...)

edit: correct arguments, thx LeeS :o
 
Last edited:
@AnyoneElse - It's been awhile since Magil's been on here, so I just want to throw it out there to anyone else (@LeeS , @Gedemon , @FearSunn , etc.) who might have an idea how the OP could be accomplished?
The basic code to eliminate a building is
Code:
function RemoveBuildingFromCityCenter(pCity, iBuilding)
	if pCity:GetBuildings():HasBuilding(iBuilding) then
		pCity:GetBuildings():RemoveBuilding(iBuilding);
	end
end
You have to know the Building's Index number (by grabbing it via lua methods from the Buildings database table), and you need the city object before you can run the function. Even though the function is named "RemoveBuildingFromCityCenter" it will work for any district since the "RemoveBuilding" method is not looking for a district ID or a plot ID

I've not run any experiments on Buildings with MaxPlayerInstances > 0 to determine if ever having such a building (even if instantly removed from a city on capture) persists in not allowing a player to construct the number they otherwise would have been able to. In Civ5 MaxPlayerInstances for Buildings was based on the current condition rather than the total from start of the game, so I assume it works the same in 6. In 5, if you lost one of your guilds to city capture you could re-construct it, as I recall.
 
Last edited:
erm. pretty sure it's GameEvents.CityConquered(iVictoriousPlayer, iDefeatedPlayer, iNewCityID, iCityPlotX, iCityPlotY) where the XY are for the city center tile.

As in Civ5, the CityID will be changed when a city is conquered.
 
Thanks for the heads-up. I haven't done a lot of scripting in Civ so I can't guarantee when I'll be able to get around to doing something like this, but I do appreciate the aid.
 
Thanks Magil for the great mod. I balanced it a bit for personal use and thought I could also try to write the function that destroys national wonders if they are transfered (peace deal or simple trade).

Code:
-- delete national wonders on city transfers, from Serp
print("DeleteNational: WondrousWonders lua loaded")
local nationaltypes = {"BUILDING_MZ_NATIONAL_LIBRARY","BUILDING_MZ_NATIONAL_COLLEGE","BUILDING_MZ_NATIONAL_SHRINE","BUILDING_MZ_NATIONAL_TEMPLE","BUILDING_MZ_HEROIC_EPIC","BUILDING_MZ_NATIONAL_ACADEMY","BUILDING_MZ_NATIONAL_EPIC","BUILDING_MZ_NATIONAL_ARMORY","BUILDING_MZ_APOSTOLIC_PALACE","BUILDING_MZ_NATIONAL_MARKET","BUILDING_MZ_NATIONAL_BANK","BUILDING_MZ_WRITERS_GUILD","BUILDING_MZ_ARTISTS_GUILD","BUILDING_MZ_MUSICIANS_GUILD","BUILDING_MZ_NATIONAL_MUSEUM","BUILDING_MZ_NATIONAL_LAB","BUILDING_MZ_NATIONAL_AIRPORT","BUILDING_MZ_NATIONAL_ARENA","BUILDING_MZ_NATIONAL_ZOO","BUILDING_MZ_NATIONAL_WORKSHOP","BUILDING_MZ_NATIONAL_FACTORY","BUILDING_MZ_NATIONAL_AQUEDUCT"}
local function OnCityTransfered(playerID,cityID) -- playerID is the new owner
    local pPlayer = Players[playerID];
    if pPlayer~=nil then
        local playercities = pPlayer:GetCities();
        local pCity = playercities:FindID( cityID );
        if pCity~=nil then
            local citybuildings = pCity:GetBuildings();
            for _,nationaltype in ipairs(nationaltypes) do
                local natioanlID = nil
                if GameInfo.Buildings[nationaltype]~=nil then
                    natioanlID = GameInfo.Buildings[nationaltype].Index
                end
                if natioanlID~=nil and citybuildings:HasBuilding(natioanlID) then
                 
                    -------- always remove, because we want to decide ourself where to build nationals
                    citybuildings:RemoveBuilding(natioanlID);
                    print("DeleteNational: City transfered, delete found national wonder")
                 
                    -------- only remove if we already have it somewhere else
                    -- for _,playercity in playercities:Members() do
                        -- if playercity~=nil and playercity:GetBuildings():HasBuilding(natioanlID) then
                            -- citybuildings:RemoveBuilding(natioanlID);
                            -- print("DeleteNational: City transfered, national already in another town, delete new one.")
                            -- break
                        -- end
                    -- end
                 
                end
            end
        end
    end
end


Events.CityTransfered.Add(OnCityTransfered);

edit:
tested and fixed both versions :) Works fine for conquering or transfering/gifting cities :)
 
Last edited:
Interesting development Serp! So you just added this as a Lua GameplayScript under InGameActions? This was the only thing preventing me from using Magil's National Wonders, so well done!
Yes, took a while until I was familiar with this new modinfo style "Components", since other mods add stuff without this components keyword :D
So I just put into the components tags:
Code:
        <AddGameplayScripts id="WW_Lua">
            <Properties>
                <Name>WW_deletelua</Name>
               <LoadOrder>127</LoadOrder>            
            </Properties>
            <Items>
               <File>DeleteNational.lua</File>
            </Items>
        </AddGameplayScripts>
and of course also added the lua to the Files list.
 
<Components> == <InGameActions>
<Settings> == <FrontEndActions>

<Components> and <Settings> were used before the SDK was released at about the third patch to Vanilla. Either method will still work when making a modinfo file manually. Though you should not really mix-n-match the two. Modbuddy, using the newer system, will generate the following for an In-Game GameplayScript:
Code:
  <InGameActions>
    <AddGameplayScripts id="KeepAIFromBuildingDummies">
      <File>LUA/GameplayScripts/KeepAIFromBuildingDummies.lua</File>
    </AddGameplayScripts>
  </InGameActions>
  <Files>
    <File>LUA/GameplayScripts/KeepAIFromBuildingDummies.lua</File>
  </Files>
Assuming no Action 'Properties' are set in Modbuddy.
 
@Serp Everything looks great from my testing so far. Many thanks for putting this together. :thumbsup:

Since there aren't any 3d assets to visually identify which cities have these National Wonders without hovering over those cities, I added a little float text to identify when they get destroyed.
Code:
if pPlayer:IsHuman() then
    local sText ="National Wonder [COLOR_Civ6Red]destroyed![ENDCOLOR]"
    Game.AddWorldViewText(playerID, sText, pCity:GetX(), pCity:GetY(), 0)
end
I would have liked to have added which National Wonder specifically was being destroyed (e.g. National Epic) in the text, but I couldn't figure out how to do that.
heh. I'd forgotten there even was a Events.CityTransfered !
No worries! :)
 
I would have liked to have added which National Wonder specifically was being destroyed (e.g. National Epic) in the text, but I couldn't figure out how to do that.
Code:
if pPlayer:IsHuman() then
    local sText = Locale.Lookup(GameInfo.Buildings[nationaltype].Name) .." [COLOR_Civ6Red]destroyed![ENDCOLOR]"
    Game.AddWorldViewText(EventSubTypes.DAMAGE, sText, pCity:GetX(), pCity:GetY(), 0)
end

you may also want to check if the city plot is revealed:

Code:
    local pLocalPlayerVis = PlayersVisibility[Game.GetLocalPlayer()]
    if (pLocalPlayerVis ~= nil) then
        if (pLocalPlayerVis:IsRevealed(pCity:GetX(), pCity:GetY())) then
            local sText = Locale.Lookup(GameInfo.Buildings[nationaltype].Name) .." [COLOR_Civ6Red]destroyed![ENDCOLOR]"
            Game.AddWorldViewText(EventSubTypes.DAMAGE, sText, pCity:GetX(), pCity:GetY(), 0)
        end
    end
 
Code:
for _,nationaltype in ipairs(nationaltypes) do
	local tNationalRowData = GameInfo.Buildings[nationaltype]
	if tNationalRowData ~= nil then
		local natioanlID = tNationalRowData.Index
		local nationalName = Locale.Lookup(tNationalRowData.Name)
		if citybuildings:HasBuilding(natioanlID) then
 			print("DeleteNational: City transfered, delete found national wonder " .. nationalName)
			citybuildings:RemoveBuilding(natioanlID);
			if pPlayer:IsHuman() then
				local sText = nationalName .. " [COLOR_Civ6Red]destroyed![ENDCOLOR]"
				Game.AddWorldViewText(EventSubTypes.DAMAGE, sText, pCity:GetX(), pCity:GetY(), 0)
			end
		end
	end
end
"tNationalRowData" will be either an lua table with all the data from the row in the Buildings table that matches to "nationaltype", or it will be nil if there is no such matching row.

The code can be simplified by checking whether the row is nil, and only executing anything more for that pair in the "nationaltypes" table when valid data is recieved back from
Code:
local tNationalRowData = GameInfo.Buildings[nationaltype]
The other advantage is that you only make one database call. Though multiple direct calls like "GameInfo.Buildings[nationaltype].Index" probably aren't as processing-heavy as running through all the rows in a game-table looking for matching data by a "for" loop through the entire table.
 
Code:
if pPlayer:IsHuman() then
    local sText = Locale.Lookup(GameInfo.Buildings[nationaltype].Name) .." [COLOR_Civ6Red]destroyed![ENDCOLOR]"
    Game.AddWorldViewText(EventSubTypes.DAMAGE, sText, pCity:GetX(), pCity:GetY(), 0)
end
you may also want to check if the city plot is revealed
Brilliant Gedemon! Thank you. I feel so silly, it seems so obvious now that I look at it. :crazyeye:

Also, good idea about the visibility, I hadn't thought of that, but it makes sense.

Is there a way to get the previous owner of a city? I know there's an object for "GetOriginalOwner", but I'm not sure if that will only return the city founder or the immediately previous owner.
 
Sorry @LeeS I didn't see your comment when I posted earlier. Thank you for the simplified code. I just ran a quick test and it functions great. I didn't notice anything functionally not working in-game, but I did receive the following errors in the Lua.log when I conquered a city:
Code:
Runtime Error: C:\Program Files (x86)\Steam\steamapps\common\Sid Meier's Civilization VI\Base\Assets\UI\ToolTips\PlotToolTip.lua:423: attempt to index a nil value
stack traceback:
    C:\Program Files (x86)\Steam\steamapps\common\Sid Meier's Civilization VI\Base\Assets\UI\ToolTips\PlotToolTip.lua:423: in function 'View'
    C:\Program Files (x86)\Steam\steamapps\common\Sid Meier's Civilization VI\Base\Assets\UI\ToolTips\PlotToolTip.lua:759: in function 'ShowPlotInfo'
    C:\Program Files (x86)\Steam\steamapps\common\Sid Meier's Civilization VI\Base\Assets\UI\ToolTips\PlotToolTip.lua:768: in function 'RealizeNewPlotTooltipMouse'
    C:\Program Files (x86)\Steam\steamapps\common\Sid Meier's Civilization VI\Base\Assets\UI\ToolTips\PlotToolTip.lua:816: in function 'OnInputHandler'
Lua callstack:
ActionPanel: ERROR: ActionPanel received mismatched blocking types.  Event vs engine call:     0    -183444029
National_Wonders_Destroyed: DeleteNational: City transfered, delete found national wonder National Epic
It looks to me like it's just a temporary UI glitch from deleting the National Wonder building. The city tool tip will list the wonder as "pillaged" even after keeping the city, but will update and disappear once you've advanced to the next turn. I assume I can safely ignore these.
 
I believe so for ignoring the UI file error. I think the GetOriginalOwner is only going to tell you the ID of the player that founded the city, but \shrug/ I'm not sure.

CityConquered hook would tell you the previous city-owner as one of its args but not CityTransferred.

line #423 of Vanilla's PlotToolTip.lua is looking at Districts rather than buildings but R&F is probably doing something different at that line-number. The error could be the result of a National Wonder being given a small city-defense value in the Building Definition to try to make the game want to eliminate the National Wonder as a matter of normal behavior on a city-capture rather than city-transfer, and the city-transfer plus building-deletion is causing a synchronization issue between UI and Gameplay states.

I found the base-game UI files are almost always 'lagging' for a bit in terms of properly showing whether a building is still actually part of a city when a building is removed via lua methods. And in some cases the UI never wanted to update to show properly that the building had been removed -- the removed building would still show in the list of city buildings. Which is why with the dummy system I wrote for Vanilla I re-wrote the UI files for the city-view, the Tooltips, the Civilopedia, etc. There's no real effect on actual gameplay as I recall, just a not-always-accurate city-view and tooltips.
 
Last edited:
Thanks for the scripts folks. I've been implementing them in my latest internal test and so far so good, looks like it works as expected, so I'll roll them out in an early Gathering Storm build, with credits of course. I don't think I would've ever figured that out on my own, so thanks again.
 
Top Bottom