[BNW] Triggering a unique ability based on Puppeting a captured city?

A_Wandering_Man

Chieftain
Joined
Oct 31, 2019
Messages
47
Location
Canada
I'm currently theory-crafting a new mod wherein I'd like the UA to trigger off of a city being puppeted after being captured. I think I know how to do the "back-end" of this; implementing the effect through a dummy policy or dummy building, since I've done that a few times before, but I'm not sure how I would go about writing the Lua to check for when this happens. I've been looking through the API and it seems like there's something called
Code:
GameEvents.CityCaptureComplete
...which seems to be close to what I'm looking for.

I'm wondering if anyone knows if:
1) this event triggers before or after the player/AI chooses to annex/raze/puppet a city?
2) this could be combined with "City.IsPuppet" in some way to get the effect I desire?

Alternatively, would it be possible to check when "City.SetPuppet" is used, and trigger the event off that (presumably also using something to check that it's the correct player)?
 
So I've been reading through various threads here and I think I've cobbled together something which is close to what I want to do, would anyone mind taking a look?

Code:
iBuilding = GameInfoTypes.BUILDING_NAC_CHAGATAI_PUPPET_DUMMY

function PuppetingReward(oldOwner, isCapital, X, Y, newOwner)
    local pPlayer = Players[newOwner]
    local iPlayerCiv = pPlayer:GetCivilizationType()
    if iPlayerCiv == GameInfoTypes.CIVILIZATION_NAC_CHAGATAI and (pCity:IsPuppet()) then
        local pCapital = pPlayer:GetCapitalCity();
        if (pCity:IsOriginalCapital()) then
        pCapital:SetNumRealBuilding(iBuilding, 2);
        else
        pCapital:SetNumRealBuilding(iBuilding, 1);
    end
end
GameEvents.CityCaptureComplete.Add(PuppetingReward)

The idea is to add one copy of the dummy building for puppeting a non-capital city, and 2 copies for a Capital city. I'm still new to Lua and coding in general so I have a feeling there's some errors in there somewhere...
 
To be honest I don't remember anymore whether CityCaptureComplete fires before or after the decision to puppet.

Regardless of that there are a couple issues with the code as written:
  1. Code error issue: You have never defined "pCity" so as soon as this line is encountered the remainder of the code will be killed because of a nil value error
    Code:
    if (pCity:IsOriginalCapital()) then
  2. Logic Issue: What happens if on turn 20 the player captures an original capital and puppets it, and then on turn 22 captures a city that is not an original capital and puppets that city also ?
  3. Timing issues wrt to when CityCaptureComplete fires in relation to other stuff happening:
    • If it were me I would just make the code execute from PlayerDoTurn and then run through all the cities a player has. This eliminates the issues of when CityCaptureComplete fires in relation to player decision to raze, puppet, etc.
  4. Using PlayerDoTurn logic:
    • If one of the player's cities is a puppet and an original capital, then set the 2 dummy buildings in the player's capital if there are not already 2 copies of the building there.
    • If one of the player's cities is a puppet and is not an original capital, then set the 1 dummy building in the player's capital only if the player's capital does not already have at least one of the dummy buildings.
    • If none of the player's cities is a puppet, them remove all the buildings from the player's capital city.
    • Remember that player's can puppet a city and then later on decide to make it a regular city. This is a strategy some player's will use to deal with the occupied unhappiness period after conquering a city -- just puppet it until it calms down and can actually be useful to the overall empire.
 
Last edited:
You have never defined "pCity" so as soon as this line is encountered the remainder of the code will be killed because of a nil value error
It was my understanding that the hook passed that along to the function, from having looked at others' code on the forums, but perhaps their code was incomplete or I missed something.

What happens if on turn 20 the player captures an original capital and puppets it, and then on turn 22 captures a city that is not an original capital and puppets that city also ?
Apologies for being unclear -- my intent was to add a dummy building for each puppeted city, and 2 for each original Capital; not just have one copy if the empire had any puppets and two if one of those puppets was a Capital.

Regarding making it a PlayerDoTurn: You're right that that's probably the way to go, I wasn't really thinking about players annexing a previously puppeted city -- despite doing that frequently in my own games!
 
Code:
iBuilding = GameInfoTypes.BUILDING_NAC_CHAGATAI_PUPPET_DUMMY

function PuppetingReward()
    local pPlayer = Players[iPlayer]
    local iPlayerCiv = pPlayer:GetCivilizationType()
    local iNumPuppets = 0
    if iPlayerCiv == GameInfoTypes.CIVILIZATION_NAC_CHAGATAI then
    for pCity in pPlayer:Cities() do
        if (pCity:IsPuppet()) then
            if (pCity:IsOriginalCapital()) then
            iNumPuppets = iNumPuppets + 2
            else
            iNumPuppets = iNumPuppets + 1
            end
        end
    end
    local pCapital = pPlayer:GetCapitalCity();
    pCapital:SetNumRealBuilding(iBuilding, iNumPuppets);
end
GameEvents.PlayerDoTurn.Add(PuppetingReward)

Just wrote this up quickly, am I on the right track here?
 
Code:
pCapital:SetNumRealBuilding(iBuilding, 2)
Ignores the current number of the specified building already in the specified city and makes the number of buildings in the city 2,

Argument data passed by the hook event to functions assigned to it
Code:
(oldOwner, isCapital, X, Y, newOwner)
The data represent
Code:
oldOwner    --    the Player ID # of the city's previous owner
isCapital    --    boolean true or false as to whether the city was a capital city for the previous owner
X    --   the map Grid X position of the city
Y    --   the map Grid Y position of the city
newOwner    --        the Player ID # of the city's new owner
None of these directly give the city ID number or other data directly related to the city itself except the map grid XY location

Code:
local iBuilding = GameInfoTypes.BUILDING_NAC_CHAGATAI_PUPPET_DUMMY

function PuppetingReward(iOldOwner, bIsCapital, iX, iY, iNewOwner, iPop, bConquest)
	local pPlayer = Players[iNewOwner]
	local pCity = Map.GetPlot(iX, iY):GetPlotCity();
	if (pPlayer ~= nil) and (pCity ~= nil) then
		if (pPlayer:GetCivilizationType() == GameInfoTypes.CIVILIZATION_NAC_CHAGATAI) and pCity:IsPuppet() then
			local pCapital = pPlayer:GetCapitalCity();
			local iNumCurrentBuildings = pCity:GetNumRealBuilding(iBuilding)
			if (pCity:IsOriginalCapital()) then
				pCapital:SetNumRealBuilding(iBuilding, (iNumCurrentBuildings + 2))
			else
				pCapital:SetNumRealBuilding(iBuilding, (iNumCurrentBuildings + 1))
			end
		end
	end
end
GameEvents.CityCaptureComplete.Add(PuppetingReward)
Game event CityCaptureComplete actually passes 9 argument datas but most of us only ever need to use the first 5. From William Howard's documentation
Code:
  <event name="CityCaptureComplete" type="Hook">
    <arg pos="1" type="PlayerTypes" name="eOldPlayer"/>
    <arg pos="2" type="bool" name="bCapital"/>
    <arg pos="3" type="int" name="iPlotX"/>
    <arg pos="4" type="int" name="iPlotY"/>
    <arg pos="5" type="int" name="eNewPlayer"/>
    <arg pos="6" type="int" name="iOldPop"/>
    <arg pos="7" type="bool" name="bConquest"/>
    <arg pos="8" type="int" name="iGreatWorkCount">
	  <!-- Appears to be a duplicate of param 9, just obtained a different way -->
	</arg>
    <arg pos="9" type="int" name="iGreatWorkCount">
	  <!-- Appears to be a duplicate of param 8, just obtained a different way -->
	</arg>
  </event>
 
Last edited:
Code:
iBuilding = GameInfoTypes.BUILDING_NAC_CHAGATAI_PUPPET_DUMMY

function PuppetingReward()
    local pPlayer = Players[iPlayer]
    local iPlayerCiv = pPlayer:GetCivilizationType()
    local iNumPuppets = 0
    if iPlayerCiv == GameInfoTypes.CIVILIZATION_NAC_CHAGATAI then
    for pCity in pPlayer:Cities() do
        if (pCity:IsPuppet()) then
            if (pCity:IsOriginalCapital()) then
            iNumPuppets = iNumPuppets + 2
            else
            iNumPuppets = iNumPuppets + 1
            end
        end
    end
    local pCapital = pPlayer:GetCapitalCity();
    pCapital:SetNumRealBuilding(iBuilding, iNumPuppets);
end
GameEvents.PlayerDoTurn.Add(PuppetingReward)

Just wrote this up quickly, am I on the right track here?
Closer but not quite there -- you are missing at least one needed 'end' command in your PlayerDoTurn version. Note the way I indent the code with every 'if' or 'for' keyword in the example reworked CityCaptureComplete code -- I find this makes it much easier to spot missing end commands. I just use Notepad to edit my lua files and use the tab key to make my indents. Lua does not care about indentation nor leading spaces so write lua code in a manner that makes it easier for you to "see" the forest of the code above the trees.

Also as a general logic rule if the dummy you are adding has effects that are realtime and occur when the building is added to a city (like free units) you need to compare the current number of buildings to those you calculate are needed, and only adjust the number of dummy buildings when the two are not equal.
 
Also as a general logic rule if the dummy you are adding has effects that are realtime and occur when the building is added to a city (like free units) you need to compare the current number of buildings to those you calculate are needed, and only adjust the number of dummy buildings when the two are not equal.
My plan was to add UnmoddedHappiness and MilitaryProductionModifier, so that shouldn't be an issue, correct?
 
Finally getting around to finishing this mod, everything is working fine except for the Lua, and having gone over it several times, I'm stumped. Nothing in the xml, Database, or Lua logs, but the code just doesn't seem to do anything, despite me having InGameUIAddIn set and all that... I have attached the mod so others can take a look.

Have I just missed something obvious?
 

Attachments

  • NAC's Chagatai Khanate (v 1).civ5mod
    6.2 MB · Views: 41
You're missing the iPlayer parameter in PuppetingReward(..)
As a result, I would expect Lua.log to show an "attempt to index a nil value" on the line where you obtain the player's civiliationtype (as pPlayer = nil at that point)

Code:
function PuppetingReward(iPlayer) -- The parameter was missing here 
    local pPlayer = Players[iPlayer]
    local iPlayerCiv = pPlayer:GetCivilizationType() -- This line should've shown something in Lua.log
    [...]
end
GameEvents.PlayerDoTurn.Add(PuppetingReward)


To improve performance, you can also define GameInfoTypes.CIVILIZATION_NAC_CHAGATAI outside of your function so that the game only has to check the database for that value once (just like you already did for the building). I.e.:
Code:
[..]
local iChagataiCiv = GameInfoTypes.CIVILIZATION_NAC_CHAGATAI
function PuppetingReward(iPlayer)
    [..]
    if iPlayerCiv == iChagataiCiv then
        [..]
    end
    [..]
end
GameEvents.PlayerDoTurn.Add(PuppetingReward)
 
Figured it might be a Lua-noob thing! Thank you! Going over the Lua log just now, I found the indexing a nil value thing! Don't know if I would have spotted that on my own.

Edit: it occurred to me that this might be something just left in the game (like the barbarian horseman stuff in the xml log), this is the only nil value thing in the Lua log:
Code:
[175934.250] Runtime Error: Assets\DLC\Expansion2\UI\InGame\InGame.lua:1262: attempt to index local 'addinFile' (a nil value)
[175934.250] Runtime Error: Error loading Assets\DLC\Expansion2\UI\InGame\InGame.lua.

It still doesn't seem to be working at all...

This is the Lua as it stands now:
Code:
local iBuilding = GameInfoTypes.BUILDING_NAC_LEGACY_OF_OGEDAI
local iChagataiCiv = GameInfoTypes.CIVILIZATION_NAC_CHAGATAI
function PuppetingReward(iPlayer)
    local pPlayer = Players[iPlayer]
    local iPlayerCiv = pPlayer:GetCivilizationType()
    local iNumPuppets = 0
    if iPlayerCiv == iChagataiCiv then
        --counts puppet cities
        for pCity in pPlayer:Cities() do
            if (pCity:IsPuppet()) then
                if (pCity:IsOriginalCapital()) then
                iNumPuppets = iNumPuppets + 2
                else
                iNumPuppets = iNumPuppets + 1
                end
            end
        end
    end
    --sets dummy building to the correct number
    local pCapital = pPlayer:GetCapitalCity();
    pCapital:SetNumRealBuilding(iBuilding, iNumPuppets);
end
GameEvents.PlayerDoTurn.Add(PuppetingReward)

Second edit: It works! I just had the InGameUIAddIn mucked up!
 
Last edited:
Edit: it occurred to me that this might be something just left in the game (like the barbarian horseman stuff in the xml log), this is the only nil value thing in the Lua log:
Code:
[175934.250] Runtime Error: Assets\DLC\Expansion2\UI\InGame\InGame.lua:1262: attempt to index local 'addinFile' (a nil value)
[175934.250] Runtime Error: Error loading Assets\DLC\Expansion2\UI\InGame\InGame.lua.
As far as I'm aware, there shouldn't be any Firaxis-errors in lua.log. This is also an error that I don't think I've ever seen before, and I'm unsure if it has any effects/breaks anything
Have you modified/replaced your core game files directly? Or tested while other mods were active as well?

(Or was this error related to the botched InGameUIAddin?)
 
(Or was this error related to the botched InGameUIAddin?)

Seems like it, it isn't appearing in the log anymore.

Weirdly, this:
Code:
[178994.125] Runtime Error: C:\Users\Nicholas\Documents\My Games\Sid Meier's Civilization 5\MODS\Mods\NAC's Chagatai Khanate (v 1)\lua/NAC_Chagatai_PuppetReward.lua:21: attempt to index local 'pCapital' (a nil value)
...is showing up in the Lua log, despite the effect working in my testing.
 
Is it because it's running the SetNumRealBuildings for the other civ(s)? Just realized that bit doesn't seem nested in the "if civ = chagatai civ" bit...
 
Weirdly, this:
Code:
[178994.125] Runtime Error: C:\Users\Nicholas\Documents\My Games\Sid Meier's Civilization 5\MODS\Mods\NAC's Chagatai Khanate (v 1)\lua/NAC_Chagatai_PuppetReward.lua:21: attempt to index local 'pCapital' (a nil value)
...is showing up in the Lua log, despite the effect working in my testing.
Is it because it's running the SetNumRealBuildings for the other civ(s)? Just realized that bit doesn't seem nested in the "if civ = chagatai civ" bit...

Exactly! Because the SetNumRealBuilding(..) isn't inside the civ-check, it will always run for every player. This includes Barbarians, which will never have a capital.
As a result, the value of pCapital after running
Code:
local pCapital = pPlayer:GetCapitalCity()
will still be nil.
Similarly, the problem will also occur if any other player doesn't have a capital but is still alive (e.g. if "complete kills" is enabled, or they simply haven't settled yet).

Moving the SetNumRealBuilding(..) inside your civ-check will fix the problem of your code running for other players. To prevent the same error if your civ doesn't have a capital city, you can adapt your code:
Code:
if iPlayerCiv == iChagataiCiv then
       [...]
    --sets dummy building to the correct number
    local pCapital = pPlayer:GetCapitalCity();
    if pCapital then -- This'll skip the statement below if the player doesn't have a capital (i.e., pCapital = nil).
       pCapital:SetNumRealBuilding(iBuilding, iNumPuppets);
    end
 end
 
Code:
if (pPlayer:GetCapitalCity() == nil) then return end
local pCapital = pPlayer:GetCapitalCity();
Because there's nothing to do here so why bother to go any further.
 
Top Bottom