LUA Trait Help

1) Jon was the (lead ?) developer for the project and seems to have been on a bit of an ego trip when (presumably) he added Culture to the standard yields. Gold, Science, Food and Production are all handled (in general) by one set of code, Culture (aka JONSCulture) has it's own mismash of code. There are even some snide comments and replies in the C++ source code for the DLL referencing JONSCulture.
 
Yeah I remember hearing about that there was some drama.

So CivHawk when using lua you use Player:ChangeJONSCulture(5) to add 5 culture points to the player's current total culture "saved" for selecting policies. Instead of using what would have been a more obvious method-name like Player:ChangeCulture(5)

You are not the 1st ask what is "up" with JONSCultureThis and JONSCultureThat

------------------------------------

Changing a player's science from lua is a bit involved in that you really need to apply it to a tech rather than just a generic score value for "science".

This is an example of snipped-code from one of my mods
Code:
local iCurrentResearchItem = pPlayer:GetCurrentResearch()
if (iCurrentResearchItem ~= -1) and (iCurrentResearchItem ~= nil) then
	local pTeam = Teams[pPlayer:GetTeam()]
	local pTeamTechs = pTeam:GetTeamTechs()
	pTeamTechs:ChangeResearchProgress(iCurrentResearchItem, iYield)
else
	print("You were not researching anything so for simplicity your Barb Animal Kill [ICON_RESEARCH] was changed to [ICON_PRODUCTION]")
	--code snipped to not confuse the issue
end
It's generally easier to use dummy buildings in most cases that implement a science yield-change

------------------------------------------

I'd have to review the other stuff you were asking about at a later time to answer meaningfully.
 
Last edited:
I appreciate you looking into it further. In the meantime, I do have another question.

What is the actual effect of the 'Set' methods, e.g. SetHappiness, SetFaith, etc.? And especially in the context of SetGold vs ChangeGold, as on the face of it, it would seem to output the same thing?

Is it the case that 'SetGold' would permanently set the players gold value to a specific level, so long as the conditions continue to be met?
 
Set methods literally over-write whatever is already there and implement whatever value as the new total. Change methods alter the current value by the specified amount.

If I SetGold(1000) the player's treasury will be 1000 regardless of what it was previously. Turn processing, purchasing, etc., will then do their normal things on this 1000 gold.

If I ChangeGold(1000) I add 1000 gold to whatever was already there. Turn processing, purchasing, etc., will likewise then do their normal things on this new gold value.

-------------------------------------------

As a general rule lua scripts only have an effect on the game at the instant that the code executes. So you cannot "lock" a player's gold at "1000" by doing SetGold(1000).
 
As a general rule lua scripts only have an effect on the game at the instant that the code executes. So you cannot "lock" a player's gold at "1000" by doing SetGold(1000).

Understood. So if the code executes every turn, then every turn it should show, e.g. 1000 gold.

In that case the SetHappiness method seems the best way to go. I would set the Happiness to 0 and thus stop current Golden Age progress. As that is a 'punishment' for having 2000 gold or more in your coffers, then I thought about off-setting it with 'rewards' to culture and science, since my leader is a bit of an austere athiest.

Few more questions too:

1. Is there a way I can completely disable 'We love the kind day' effects too? I've seen 'City:SetWeLoveTheKingDayCounter'

2. For the last bit of my trait, I wanted to make buying buildings and units cheaper, to encourage spending of gold. I can't seem to find an appropriate method. However, I did notice that building/unit costs are related to their 'production cost' in general, e.g. City:ChangeBuildingProduction and City: ChangeUnitProduction. But I figured that I'd have to iterate all of my cities too for that, since it's a City method. I also saw this field in the trait.xml table:

<MaxPlayerBuildingProductionModifier>

If I set that to -10, would that satisfy there being a 10% reduction in the 'cost' of buildings?

3. Also, I wanted to add one of those on-screen messages that you get near the top of the game window where you would usually see the 'So-and-so has discovered the world is round' messages, etc. Like in the image below.

Are you able to show me how to do that because I have no idea what I should search google for.... 'civ v on screen message' doesn't seem to cut it...

Civ-V-Expansion.jpg
 
Rather than trying to jigger around with happiness values and thereby causing your empire cities to not grow, I would use:
Code:
local pPlayer = Players[iPlayer]
if (pPlayer:GetGold() > 1000) then
	local iHappiness = pPlayer:GetExcessHappiness()
	if (pPlayer:GetGoldenAgeProgressMeter() > iHappiness) and (iHappiness > 0) then
		pPlayer:ChangeGoldenAgeProgressMeter(-iHappiness)
	end
end
For WLTKD counters I would do as
Code:
if (pCity:GetWeLoveTheKingDayCounter() > 1) then
	pCity:SetWeLoveTheKingDayCounter(1)
end
As this will avoid any issues related to setting the counter to "0" while a city is still in WLTKD and then the game never taking that city back out of WLTKD mode.

I've never played around with <MaxPlayerBuildingProductionModifier> but I'm sure as I can be it doesn't do what you would want it to.

City:ChangeBuildingProduction() as I recall will change the city's production progress on the specified building, but will not in any way affect the gold purchase cost of the building.

If I were trying to make purchasing units and buildings cheaper to purchase for a player, I would use a dummy policy with a negative % cost modifier for
Code:
	<Policy_HurryModifiers>
		<Row>
			<PolicyType>POLICY_DUMMY</PolicyType>
			<HurryType>HURRY_GOLD</HurryType>
			<HurryCostModifier>-25</HurryCostModifier>
		</Row>
	</Policy_HurryModifiers>
like as is used by the Mercantilism Policy and a positive PlotGoldCostMod in table <Policies> for the same dummy Policy to offset the savings for plot gold buying.

Main screen messages are done via lua
Code:
Events.GameplayAlertMessage("Here is your message text")
But you need to make sure you're only sending that message when your civ is being used by a/the human player because when the message is sent when the civ is being used as an AI, the human in the game still sees the message.
 
The Dummy trick is pretty nifty, I've seen it a few time now. I've never done it before so I just wanna make sure I've got this right. I've created an xml file called Civ5Policies in my mod and added the following code:

Code:
<?xml version="1.0" encoding="utf-8"?>
<!-- Created by ModBuddy on 4/24/2018 10:32:50 PM -->
<GameData>
    <Policies>
        <Row>
            <Type>POLICY_DUMMY</Type>
            <Description>TXT_KEY_POLICY_TRADITION</Description>
            <PortraitIndex>54</PortraitIndex>
            <IconAtlas>POLICY_ATLAS</IconAtlas>
            <IconAtlasAchieved>POLICY_A_ATLAS</IconAtlasAchieved>
        </Row>
    </Policies>
    <Policy_HurryModifiers>
        <Row>
            <PolicyType>POLICY_DUMMY</PolicyType>
            <HurryType>HURRY_GOLD</HurryType>
            <HurryCostModifier>-25</HurryCostModifier>
        </Row>
    </Policy_HurryModifiers>
</GameData>

Will this work? I've also noticed that I may need to set this dummy as a free policy granted from the beginning of the game, is this correct? If so, how could I go about doing that?
 
Like this
Code:
local iAbilityEffectDummy = GameInfoTypes.POLICY_DUMMY
local iRequiredCivilization = GameInfoTypes.CIVILIZATION_SOMETHING
function AddPolicyOnInit()
	for playerID = 0, GameDefines.MAX_MAJOR_CIVS - 1 do
		local pPlayer = Players[playerID]
		if pPlayer:IsEverAlive() and (pPlayer:GetCivilizationType() == iRequiredCivilization) then
			if not pPlayer:HasPolicy(iAbilityEffectDummy) then
				pPlayer:SetNumFreePolicies(1)
				pPlayer:SetNumFreePolicies(0)
				pPlayer:SetHasPolicy(iAbilityEffectDummy, true)
			end
		end
	end
end
Events.SequenceGameInitComplete.Add(AddPolicyOnInit)
Altho you'd want to add a column under <Policies> to counter-act the hurry modifier for the purposes of plot gold buying because the <Policy_HurryModifiers> applies to all gold purchasing as I recall.

And also you'd want to give the policy a more unique name because Murphy's Law states some other fool will also use POLICY_DUMMY as the name of their special policy in one of their mods.
 
Appreciate the help with that. I've revised my trait to a more simple:

Stop generating Golden Age Points when you reach 2000 gold, but all gold purchases are 25% cheaper.

So I've been compiling some of the code together and so far I have:

Code:
local civilizationID = GameInfoTypes["CIVILIZATION_CIVNAME"]
local bCivNameActive = true

-- Halt Golden Age Progress with gold --
function GoldAboveLimit(iPlayer)
    for iPlayer = 0, GameDefines.MAX_MAJOR_CIVS - 1 do
        local pPlayer = Players[iPlayer]
        if (pPlayer:IsAlive() == true) then
            if (pPlayer:GetCivilizationType() == civilizationID) then
                if (pPlayer:GetGold() > 1000) then
                local iHappiness = pPlayer:GetExcessHappiness()
                if (pPlayer:GetGoldenAgeProgressMeter() > iHappiness) and (iHappiness > 0) then
                    pPlayer:ChangeGoldenAgeProgressMeter(-iHappiness) and
                    if pPlayer:IsHuman(true) then
                    Events.GameplayAlertMessage("You have reached 2000 [ICON_GOLD] gold. Unless you spend or gift your gold to worthy causes, you will not generate any [ICON_GOLDEN_AGE] Golden Age Points.")
                end
            end
        end
    end
end

-- -25% gold costs ---
local iAbilityEffectDummy = GameInfoTypes.POLICY_DUMMY
function AddPolicyOnInit()
    for iPlayer = 0, GameDefines.MAX_MAJOR_CIVS - 1 do
        local pPlayer = Players[iPlayer]
        if pPlayer:IsEverAlive() and (pPlayer:GetCivilizationType() == CivilizationID) then
            if not pPlayer:HasPolicy(iAbilityEffectDummy) then
                pPlayer:SetNumFreePolicies(1)
                pPlayer:SetNumFreePolicies(0)
                pPlayer:SetHasPolicy(iAbilityEffectDummy, true)
            end
        end
    end
end

-- Load screen delay --
function OnLoadScreenClose()
    GameEvents.PlayerDoTurn.Add(GoldAboveLimit)
    Events.SequenceGameInitComplete.Add(AddPolicyOnInit)
end
if (bCivNameActive == true) then
    Events.LoadScreenClose.Add(OnLoadScreenClose)
end

Let me know if I've overstepped the mark with the load screen delay stuff, it seemed to be a general utility code to me, but I could be wrong...

I'm also still a bit clueless on the unit code involving the ideology bonuses. I've had a look at some existing mod codes, but none deal with ideologies, and I'm finding it difficult to find an appropriate method.
 
Taking things a bit at a time, you don't want the Events.SequenceGameInitComplete.Add(AddPolicyOnInit) delayed until LoadScreenClose, and you don't really need the GameEvents.PlayerDoTurn.Add(GoldAboveLimit) delayed until Load Screen Close either, since both of these will not cause odd behaviors when the game is reloaded from a save.

Which would result in this code, which still has multiple problems:
Spoiler :
Code:
local civilizationID = GameInfoTypes["CIVILIZATION_CIVNAME"]
local bCivNameActive = true

-- Halt Golden Age Progress with gold --
function GoldAboveLimit(iPlayer)
	--THE LOOP YOU ARE DOING HERE IS SIMPLY NOT REQUIRED
	for iPlayer = 0, GameDefines.MAX_MAJOR_CIVS - 1 do
		local pPlayer = Players[iPlayer]
		if (pPlayer:IsAlive() == true) then
			if (pPlayer:GetCivilizationType() == civilizationID) then
				if (pPlayer:GetGold() > 1000) then
					local iHappiness = pPlayer:GetExcessHappiness()
					if (pPlayer:GetGoldenAgeProgressMeter() > iHappiness) and (iHappiness > 0) then
						pPlayer:ChangeGoldenAgeProgressMeter(-iHappiness) and
							--THE "and" YOU HAVE HERE IS JUST A DANGLING SYNTAX ERROR
						if pPlayer:IsHuman(true) then
							Events.GameplayAlertMessage("You have reached 2000 [ICON_GOLD] gold. Unless you spend or gift your gold to worthy causes, you will not generate any [ICON_GOLDEN_AGE] Golden Age Points.")
						--WHOOPS!! MISSING A REQUIRED END
					--WHOOPS!! MISSING A REQUIRED END
				end
			end
		end
	end
end

-- -25% gold costs ---
local iAbilityEffectDummy = GameInfoTypes.POLICY_DUMMY
function AddPolicyOnInit()
	for iPlayer = 0, GameDefines.MAX_MAJOR_CIVS - 1 do
		local pPlayer = Players[iPlayer]
		if pPlayer:IsEverAlive() and (pPlayer:GetCivilizationType() == CivilizationID) then
			if not pPlayer:HasPolicy(iAbilityEffectDummy) then
				pPlayer:SetNumFreePolicies(1)
				pPlayer:SetNumFreePolicies(0)
				pPlayer:SetHasPolicy(iAbilityEffectDummy, true)
			end
		end
	end
end
Events.SequenceGameInitComplete.Add(AddPolicyOnInit)

if (bCivNameActive == true) then
	-- this will always evaluate to "true" since you've hard-coded the variable to "true"
	GameEvents.PlayerDoTurn.Add(GoldAboveLimit)
end
PlayerDoTurn executes for every player in the game on every turn, so you do not need a loop through all players in a GameEvents.PlayerDoTurn function as a general rule. Plus you have syntax errors which I pointed out.

You can vastly simplify the PlayerDoTurn function by doing as
Code:
-- Halt Golden Age Progress with gold --
function GoldAboveLimit(iPlayer)
	local pPlayer = Players[iPlayer]
	if (pPlayer:GetCivilizationType() == civilizationID) and (pPlayer:GetGold() > 1000) then
		local iHappiness = pPlayer:GetExcessHappiness()
		if (pPlayer:GetGoldenAgeProgressMeter() > iHappiness) and (iHappiness > 0) then
			pPlayer:ChangeGoldenAgeProgressMeter(-iHappiness)
			if pPlayer:IsHuman() then
				Events.GameplayAlertMessage("You have reached 2000 [ICON_GOLD] gold. Unless you spend or gift your gold to worthy causes, you will not generate any [ICON_GOLDEN_AGE] Golden Age Points.")
			end
		end
	end
end
Also, as shown, method pPlayer:IsHuman() does not need an argument. Sticking "true" in there does not accomplish anything.

This line here
Code:
local bCivNameActive = true
Is what I am referring to when I mention that you've "hard-coded" the variable's value. Instead we want this:
Spoiler :
Code:
local civilizationID = GameInfoTypes["CIVILIZATION_CIVNAME"]

------------------------------------------------------------
---- WilliamHoward's IsCivInPlay
------------------------------------------------------------
function IsCivInPlay(iCivType)
  for iSlot = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
    local iSlotStatus = PreGame.GetSlotStatus(iSlot)
    if (iSlotStatus == SlotStatus.SS_TAKEN or iSlotStatus == SlotStatus.SS_COMPUTER) then
      if (PreGame.GetCivilization(iSlot) == iCivType) then
        return true
      end
    end
  end
  
  return false
end

-- Halt Golden Age Progress with gold --
function GoldAboveLimit(iPlayer)
	local pPlayer = Players[iPlayer]
	if (pPlayer:GetCivilizationType() == civilizationID) and (pPlayer:GetGold() > 1000) then
		local iHappiness = pPlayer:GetExcessHappiness()
		if (pPlayer:GetGoldenAgeProgressMeter() > iHappiness) and (iHappiness > 0) then
			pPlayer:ChangeGoldenAgeProgressMeter(-iHappiness)
			if pPlayer:IsHuman() then
				Events.GameplayAlertMessage("You have reached 2000 [ICON_GOLD] gold. Unless you spend or gift your gold to worthy causes, you will not generate any [ICON_GOLDEN_AGE] Golden Age Points.")
			end
		end
	end
end

-- -25% gold costs ---
local iAbilityEffectDummy = GameInfoTypes.POLICY_DUMMY
function AddPolicyOnInit()
	for iPlayer = 0, GameDefines.MAX_MAJOR_CIVS - 1 do
		local pPlayer = Players[iPlayer]
		if pPlayer:IsEverAlive() and (pPlayer:GetCivilizationType() == CivilizationID) then
			if not pPlayer:HasPolicy(iAbilityEffectDummy) then
				pPlayer:SetNumFreePolicies(1)
				pPlayer:SetNumFreePolicies(0)
				pPlayer:SetHasPolicy(iAbilityEffectDummy, true)
			end
		end
	end
end
Events.SequenceGameInitComplete.Add(AddPolicyOnInit)

local bCivNameActive = IsCivInPlay(civilizationID)
if (bCivNameActive == true) then
	GameEvents.PlayerDoTurn.Add(GoldAboveLimit)
end
We really don't so much need to worry about placing "Events.SequenceGameInitComplete.Add(AddPolicyOnInit)" here as like this
Code:
local bCivNameActive = IsCivInPlay(civilizationID)
if (bCivNameActive == true) then
	Events.SequenceGameInitComplete.Add(AddPolicyOnInit)
	GameEvents.PlayerDoTurn.Add(GoldAboveLimit)
end
But we can dispense with the local for whether the civ is active if we want, and just directly do as
Code:
if (IsCivInPlay(civilizationID) == true) then
	GameEvents.PlayerDoTurn.Add(GoldAboveLimit)
end
Which would give us this final code
Spoiler :
Code:
local civilizationID = GameInfoTypes["CIVILIZATION_CIVNAME"]

------------------------------------------------------------
---- WilliamHoward's IsCivInPlay
------------------------------------------------------------
function IsCivInPlay(iCivType)
  for iSlot = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
    local iSlotStatus = PreGame.GetSlotStatus(iSlot)
    if (iSlotStatus == SlotStatus.SS_TAKEN or iSlotStatus == SlotStatus.SS_COMPUTER) then
      if (PreGame.GetCivilization(iSlot) == iCivType) then
        return true
      end
    end
  end
  
  return false
end

-- Halt Golden Age Progress with gold --
function GoldAboveLimit(iPlayer)
	local pPlayer = Players[iPlayer]
	if (pPlayer:GetCivilizationType() == civilizationID) and (pPlayer:GetGold() > 1000) then
		local iHappiness = pPlayer:GetExcessHappiness()
		if (pPlayer:GetGoldenAgeProgressMeter() > iHappiness) and (iHappiness > 0) then
			pPlayer:ChangeGoldenAgeProgressMeter(-iHappiness)
			if pPlayer:IsHuman() then
				Events.GameplayAlertMessage("You have reached 2000 [ICON_GOLD] gold. Unless you spend or gift your gold to worthy causes, you will not generate any [ICON_GOLDEN_AGE] Golden Age Points.")
			end
		end
	end
end

-- -25% gold costs ---
local iAbilityEffectDummy = GameInfoTypes.POLICY_DUMMY
function AddPolicyOnInit()
	for iPlayer = 0, GameDefines.MAX_MAJOR_CIVS - 1 do
		local pPlayer = Players[iPlayer]
		if pPlayer:IsEverAlive() and (pPlayer:GetCivilizationType() == CivilizationID) then
			if not pPlayer:HasPolicy(iAbilityEffectDummy) then
				pPlayer:SetNumFreePolicies(1)
				pPlayer:SetNumFreePolicies(0)
				pPlayer:SetHasPolicy(iAbilityEffectDummy, true)
			end
		end
	end
end
Events.SequenceGameInitComplete.Add(AddPolicyOnInit)

if (IsCivInPlay(civilizationID) == true) then
	GameEvents.PlayerDoTurn.Add(GoldAboveLimit)
end
 
Thanks for the help again.

I've been trying to write lua too for my Unique Unit for the following effect:
Replaces Great War Infantry. This unit gains +25% combat power when your chosen ideology is not popular and 10% when it is

I managed to find the following code which identifies my Civ's ideology:

Code:
local policyBranchAutocracyID    = GameInfoTypes["POLICY_BRANCH_AUTOCRACY"]
local policyBranchFreedomID     = GameInfoTypes["POLICY_BRANCH_FREEDOM"]
local policyBranchOrderID         = GameInfoTypes["POLICY_BRANCH_ORDER"]

function GetIdeologyID(playerID)
    local CivNameIdeologyID
    local player = Players[playerID]
    if player:IsPolicyBranchUnlocked(policyBranchAutocracyID) then
        CivNameIdeologyID = policyBranchAutocracyID
    elseif player:IsPolicyBranchUnlocked(policyBranchFreedomID) then
        CivNameIdeologyID = policyBranchFreedomID
    elseif player:IsPolicyBranchUnlocked(policyBranchOrderID) then
        CivNameIdeologyID = policyBranchOrderID
    end
    
    return CivNameIdeologyID
end

Which is fine, but my real question is... what are the actual numerical ideology ID integers?

The reason I ask is because I am attempting to use this method:

Player:GetPublicOpinionPreferredIdeology()

to compare with my current ideology and then gives boosts accordingly. But that method above only returns integers it seems. So the following:

Code:
-- UniqueUnit boosts
--
-- Logic: If preferred ideology = actual ideology then UniqueUnit boost is 10%. If preferred ideology /= actual ideology then UniqueUnit boost is 25%
local uniqueunitID = GameInfoTypes["UNIT_UNIQUEUNIT"]
function UniqueUnitBoosts()
    if --attacking unit is UniqueUnit and
        if CivNameIdeologyID = Player:GetPublicOpinionPreferredIdeology() then
            -- UniqueUnit boost 10%
            local UniqueUnit10boost = 10
            Unit:UnitCombatModifier(UniqueUnit10boost)
        elseif not CivNameIdeologyID = Player:GetPublicOpinionPreferredIdeology() then
            -- UniqueUnit boost 25%
            local UniqueUnit25boost = 25
            Unit:UnitCombatModifier(UniqueUnit25boost)
            -- rest of code

Will probably not work...
 
GetPublicOpinionPreferredIdeology ought to match one of the integer values for the GameInfoTypes of the three ideologies, and might be "-1" when nothing is active for GetPublicOpinionPreferredIdeology

You should also use pPlayer:GetLateGamePolicyTree() to determine which ideology a player has selected / active.

Example if I want to create an effect if a player has chosen Freedom:
Code:
if (pPlayer:GetLateGamePolicyTree() ~= -1) and (pPlayer:GetLateGamePolicyTree() == GameInfoTypes.POLICY_BRANCH_FREEDOM) then
   ---stuff
end
--------------------------------

I've never used UnitCombatModifier(data) but from looking at William Howard's lua API as xml-text sheets, it would not appear to work the way you are attempting to use it because it does not appear to be a "Set" method, but rather appears to be a "Get" method for the integer percentage combat modifier a unit owns against a specified UnitCombatType. I would use promotions myself, and give all units one of two promotions that would apply based upon the ideology requirements -- the promotions would then contain the code for creating the combat power modifications.
 
If I'm honest, I had wondered how I was going to link the effects to my UU's.

Are we talking about assigning 'dummy' promotions in this case?

Or like giving promotions during 'popular ideology' times only up to 'Shock I' and 'unpopular ideology' times up to 'Shock III'?

Also, will I need to run an iterator to identify the units that shall receive these promotions?
 
So... here's the code I think you were talking about. I was making a Uruguay civ...

Code:
-- Get Ideology info
local policyBranchAutocracyID    = GameInfoTypes["POLICY_BRANCH_AUTOCRACY"]
local policyBranchFreedomID     = GameInfoTypes["POLICY_BRANCH_FREEDOM"]
local policyBranchOrderID         = GameInfoTypes["POLICY_BRANCH_ORDER"]

function GetIdeology(playerID)
    local UruguayIdeologyID
    local player = Players[playerID]
    if (pPlayer:GetLateGamePolicyTree() ~= -1) and (pPlayer:GetLateGamePolicyTree() == GameInfoTypes.POLICY_BRANCH_AUTOCRACY) then
        UruguayIdeologyID = policyBranchAutocracyID
    elseif (pPlayer:GetLateGamePolicyTree() ~= -1) and (pPlayer:GetLateGamePolicyTree() == GameInfoTypes.POLICY_BRANCH_FREEDOM) then
        UruguayIdeologyID = policyBranchFreedomID
    elseif (pPlayer:GetLateGamePolicyTree() ~= -1) and (pPlayer:GetLateGamePolicyTree() == GameInfoTypes.POLICY_BRANCH_ORDER) then
        UruguayIdeologyID = policyBranchOrderID
        end

        return UruguayIdeologyID
end

local tupamarosID = GameInfoTypes["UNIT_TUPAMAROS"]
function TupamarosBoosts()
    for pUnit in pPlayer:Units() do
        for i = 0, pUnit:GetUnitType() - 1 do
            if GetUnitType() = tupamarosID then
                if UruguayIdeologyID = Player:GetPublicOpinionPreferredIdeology() then
                    -- Tupamaros boost Shock 1
                    local TupamaroShock1 = GameInfoTypes["PROMOTION_SHOCK_1"]
                    SetHasPromotion(TupamaroShock1)
                elseif not UruguayIdeologyID = Player:GetPublicOpinionPreferredIdeology() then
                    -- Tupamaros boost Shock 3
                    local TupamaroShock1 = GameInfoTypes["PROMOTION_SHOCK_1"]
                    local TupamaroShock2 = GameInfoTypes["PROMOTION_SHOCK_2"]
                    local TupamaroShock3 = GameInfoTypes["PROMOTION_SHOCK_3"]
                    SetHasPromotion(TupamaroShock1)
                    SetHasPromotion(TupamaroShock2)
                    SetHasPromotion(TupamaroShock3)
                end
            end
        end
    end
end

Event.CityTrained.Add(TupamarosBoosts)
 
They aren't going to be dummy promotions. They'd be real promotions that cannot be chosen, like the same way the Himeji Castle's promotion cannot be chosen, but the game gives it to all correct units when a player owns the Himeji Castle. Except you would be giving the promotion via lua rather than the way the game does it directly from the game-core.

You would not need to necessarily iterate through all your units. Since a building can give all appropriate units a <FreePromotion>, you can try assigning one of the promotions to a dummy, and the second promotion to a second dummy building, then just swap the dummy buildings in and out of the capital city as needed. I'm pretty sure (but it's been quite a while since I tried this method or verified it) the <FreePromotion> given by BUILDING_DUMMY_X will go poof from all units when BUILDING_DUMMY_X is removed from the capital city. I am saying capital city only because it would be easier to keep track of where the two dummies are being placed if your code always add/removes from the capital.

--------------------------------

As regards the lua code you posted in your last reply : erm .... sorry .... no
  1. function GetIdeology(playerID) is un-necessary.
  2. You have unit methods dangling in the air with no unit-object attached to them.
  3. UruguayIdeologyID within function TupamarosBoosts() would be a nil value, as would object pPlayer on this line
    Code:
    for pUnit in pPlayer:Units() do
  4. You are missing an "=" sign here
    Code:
    elseif not UruguayIdeologyID = Player:GetPublicOpinionPreferredIdeology() then
    Plus we never want to do code like
    Code:
    if not George == Sally then
    We want to use the ~= evaluation:
    Code:
    if George ~= Sally then
  5. You have mismatched variable names between Player and pPlayer.
Here's a hint on how to start re-drafting
Code:
if (pPlayer:GetLateGamePolicyTree() ~= -1) then
   --we have an ideology selected so we can do stuff
    if (pPlayer:GetLateGamePolicyTree() == pPlayer:GetPublicOpinionPreferredIdeology() ) then
        --our current ideology is our people's preferred one: stupid people
    else
        --our current ideology is not our people's preferred one: whatever shall we do?
    end
else
    --we have not yet selected an ideology so we should not do stuff
end
Note that I do not care within this example which ideology is selected or the preferred one, just whether or not the selected one is the same as the preferred one.

------------------------------------

Additional hint: if you try the dummy buildings that give <FreePromotion> route, you can probably just use PlayerDoTurn as your hook event to further simplify things.
 
Last edited:
((When you think you have it all figured out....))

I'm not 100% sure what this

Code:
                else
                    --we have not yet selected an ideology so we should not do stuff
                end

part of the code needs. I don't really want it to do anything, can I leave it blank?

Also, something else I was considering... if a Tupamaros unit is captured by another Civ (as in, it changes owner), I want to make sure that the boost only affects the unit if Uruguay is the owner. Now, I've had a look at the Unit:GetOwner method but it only seems to return an 'int' value, which I guess to mean 'integer'... but how would I set an integer to mean Uruguay? Or am I thinking too much into it? Would I simply need

Code:
local civilizationID = GameInfoTypes["CIVILIZATION_URUGUAY"]

function TupamarosBoosts()
    for pUnit in pPlayer:Units() do
        for i = 0, pUnit:GetUnitType() - 1 do
            if pUnit:GetUnitType() = tupamarosID then
                if pUnit:GetOwner() = civilizationID then
-- do stuff

even though civilizationID is a GameInfoType, not an integer?

In any case, is this more to what you were hinting at?:

Code:
-- Tupamaros boosts --
local tupamarosID = GameInfoTypes["UNIT_TUPAMAROS"]
function TupamarosBoosts()
    for pUnit in pPlayer:Units() do
        for i = 0, pUnit:GetUnitType() - 1 do
            if pUnit:GetUnitType() = tupamarosID then
                if (pPlayer:GetLateGamePolicyTree() ~= -1) then
                    if (pPlayer:GetLateGamePolicyTree() == pPlayer:GetPublicOpinionPreferredIdeology() ) then
                        -- Tupamaros boost Shock 1
                        local TupamaroShock1 = GameInfoTypes["PROMOTION_SHOCK_1"]
                        pUnit:SetHasPromotion(TupamaroShock1)
                    else
                        -- Tupamaros boost Shock 3
                        local TupamaroShock1 = GameInfoTypes["PROMOTION_SHOCK_1"]
                        local TupamaroShock2 = GameInfoTypes["PROMOTION_SHOCK_2"]
                        local TupamaroShock3 = GameInfoTypes["PROMOTION_SHOCK_3"]
                        pUnit:SetHasPromotion(TupamaroShock1)
                        pUnit:SetHasPromotion(TupamaroShock2)
                        pUnit:SetHasPromotion(TupamaroShock3)
                    end
                else
                    --we have not yet selected an ideology so we should not do stuff
                end
            end
        end
    end
end
Event.CityTrained.Add(TupamarosBoosts)
 
  1. GameInfoTypes are integer values. This:
    Code:
    local civilizationID = GameInfoTypes["CIVILIZATION_URUGUAY"]
    finds the row ID # from table <Civilizations> in the database and assigns it to variable civilizationID
  2. The city trained event needs all functions hooked to it to include argument variables in order for the gamecore to pass the appropriate info to any listener function hooked to the event. The definition here shows the available arguments and their positions as passed from the gamecore
    Code:
    <event name="CityTrained" type="Hook">
    	<arg pos="1" type="PlayerTypes" name="ePlayer"/>
    	<arg pos="2" type="int" name="iCity"/>
    	<arg pos="3" type="UnitTypes" name="eUnit"/>
    	<arg pos="4" type="bool" name="bGold"/>
    	<arg pos="5" type="bool" name="bFaithOrCulture"/>
    </event>
    • ePlayer refers to a player ID# from lua table "Players"
    • iCity refers to the ID # of the city
    • eUnit will be the unit's ID #
    • bGold is a boolean reflecting whether the unit was purchased with gold
    • bFaithOrCulture is a boolean reflecting whether the unit was purchased with faith or culture. For this GameEvents type, only Faith would apply.
    Without these arguments stated on the line defining the funcion, the function will either do nothing at all or merely generate nil value errors.
  3. In order to determine whether a given player is "CIVILIZATION_CHEESEBURGERS", we need to do as follows:
    Code:
    function CheeseburgerUnits(iPlayer, iCity, iUnit, bGold, bFaithOrCulture)
    	local pPlayer = Players[iPlayer]
    	if (pPlayer:GetCivilizationType() == GameInfoTypes.CIVILIZATION_CHEESEBURGERS) then
    		print("Cheeseburgers trained a unit!")
    	end
    end
    Both "pPlayer:GetCivilizationType()" and "GameInfoTypes.CIVILIZATION_CHEESEBURGERS" will be integer values.
  4. Double "=" signs are required for the "equal to" evaluation on a conditional line, so == is good, but = is bad on an "if condition then" line.
  5. Variable "civilizationID" would never be equal to a "pUnit:GetOwner()" value except in rare cases which would cause the code to act improperly anyway.
    Code:
    local civilizationID = GameInfoTypes["CIVILIZATION_URUGUAY"]
    
    function TupamarosBoosts()
        for pUnit in pPlayer:Units() do
            for i = 0, pUnit:GetUnitType() - 1 do
                if pUnit:GetUnitType() = tupamarosID then
                    if pUnit:GetOwner() = civilizationID then
    -- do stuff
    "civilizationID" as you have defined it is an xml row ID # from within xml-table <Civilizations>, whereas "pUnit:GetOwner()" is the player ID # from the current game for the owner of the unit. There can only be 64 civilization Players active within any one game, but theoretically there can be hundreds of civilizations registered within the <Civilizations> xml-table.
  6. In order to give a unit a promotion, you would need as
    Code:
    pUnit:SetHasPromotion(TupamaroShock1, true)
    In order to remove the promotion from a unit you need as
    Code:
    pUnit:SetHasPromotion(TupamaroShock1, false)
    Without the boolean command the game really does not know what you want to do.
  7. Setting aside the issues with not establishing variable-names to accept the needed data from the gamecore, you really would not want to do this
    Code:
        for pUnit in pPlayer:Units() do
            for i = 0, pUnit:GetUnitType() - 1 do
                if pUnit:GetUnitType() = tupamarosID then
                    if (pPlayer:GetLateGamePolicyTree() ~= -1) then
                        if (pPlayer:GetLateGamePolicyTree() == pPlayer:GetPublicOpinionPreferredIdeology() ) then
                            -- Tupamaros boost Shock 1
                            local TupamaroShock1 = GameInfoTypes["PROMOTION_SHOCK_1"]
                            pUnit:SetHasPromotion(TupamaroShock1)
                        else
                            -- Tupamaros boost Shock 3
                            local TupamaroShock1 = GameInfoTypes["PROMOTION_SHOCK_1"]
                            local TupamaroShock2 = GameInfoTypes["PROMOTION_SHOCK_2"]
                            local TupamaroShock3 = GameInfoTypes["PROMOTION_SHOCK_3"]
                            pUnit:SetHasPromotion(TupamaroShock1)
                            pUnit:SetHasPromotion(TupamaroShock2)
                            pUnit:SetHasPromotion(TupamaroShock3)
                        end
                    else
                        --we have not yet selected an ideology so we should not do stuff
                    end
                end
            end
        end
    1. It would run through all a player's units every time they train a unit, which would be terribly laggy in addition to the fact it would not be necessary at all.
    2. This is ... uhh ... interesting
      Code:
      for i = 0, pUnit:GetUnitType() - 1 do
      For every single unit the player owns you are starting with the settler unit and then iterating through all unit-types within table <Units> up to the row ID number of the unit just created attempting to find out if the unit currently being handled by the "for" iteration is a UNIT_TUPAMAROS
    3. If you want to run through all of a player's units looking for UNIT_TUPAMAROS units, you can far more simply just do as
      Code:
      for pUnit in pPlayer:Units() do
      	if pUnit:GetUnitType() == tupamarosID then
      		print("here we have a UNIT_TUPAMAROS")
      	end
      end
    4. I still don't get what you are trying to do with the Shock 1, Shock 2, and Shock 3 promotions. You are only ever giving these promotions, and never taking them away, so the effect will be the same as this case
      Code:
      local TupamaroShock1 = GameInfoTypes["PROMOTION_SHOCK_1"]
      local TupamaroShock2 = GameInfoTypes["PROMOTION_SHOCK_2"]
      local TupamaroShock3 = GameInfoTypes["PROMOTION_SHOCK_3"]
      pUnit:SetHasPromotion(TupamaroShock1)
      pUnit:SetHasPromotion(TupamaroShock2)
      pUnit:SetHasPromotion(TupamaroShock3)
      Even after the player transitions from the non-preferred ideology to the preferred one.
  8. The hook name is
    Code:
    GameEvents.CityTrained
    There is no
    Code:
    Event.CityTrained
Here is some further hint code of an example of GameEvents.CityTrained:
Code:
function CityTrainedUnitListener(iPlayer, iCity, iUnit, bGold, bFaithOrCulture)
	local pPlayer = Players[iPlayer]
	local pUnit = pPlayer:GetUnitByID(iUnit)
	if (pPlayer == nil) or (pUnit == nil) or pUnit:IsDead() then
		print("function CityTrainedUnitListener terminated because of nil player or unit data")
		return
	end
	local pCity = pPlayer:GetCityByID(iCity)
	if (pUnit:GetUnitType() == GameInfoTypes.UNIT_AMERICAN_MINUTEMAN) then
		print("Minuteman was trained in the city of " ..  pCity:GetName() )
	end
end
GameEvents.CityTrained.Add(CityTrainedUnitListener)
 
For point 7.4, I hadn't really thought about that...

I was unsure if I should have used PlayerDoTurn or CityTrained. I thought that CityTrained would set the promotions for the rest of the game. If I do PlayerDoTurn, would it continually check the Ideology vs Public Opinion every turn and set the promotions accordingly:

Code:
local tupamarosID = GameInfoTypes["UNIT_TUPAMAROS"]
function TupamarosBoosts(iPlayer, iCity, iUnit, bGold, bFaithOrCulture)
    for pUnit in pPlayer:Units() do
        if pUnit:GetUnitType() == tupamarosID then
            if (pPlayer:GetLateGamePolicyTree() ~= -1) then
                if (pPlayer:GetLateGamePolicyTree() == pPlayer:GetPublicOpinionPreferredIdeology() ) then
                    -- Tupamaros boost Shock 1
                    local TupamaroShock1 = GameInfoTypes["PROMOTION_SHOCK_1"]
                    local TupamaroShock2 = GameInfoTypes["PROMOTION_SHOCK_2"]
                    local TupamaroShock3 = GameInfoTypes["PROMOTION_SHOCK_3"]
                    pUnit:SetHasPromotion(TupamaroShock1, true)
                    pUnit:SetHasPromotion(TupamaroShock2, false)
                    pUnit:SetHasPromotion(TupamaroShock3, false)
                else
                    -- Tupamaros boost Shock 3
                    local TupamaroShock1 = GameInfoTypes["PROMOTION_SHOCK_1"]
                    local TupamaroShock2 = GameInfoTypes["PROMOTION_SHOCK_2"]
                    local TupamaroShock3 = GameInfoTypes["PROMOTION_SHOCK_3"]
                    pUnit:SetHasPromotion(TupamaroShock1, true)
                    pUnit:SetHasPromotion(TupamaroShock2, true)
                    pUnit:SetHasPromotion(TupamaroShock3, true)
 
I use William Howard's BNW lua API presented as if it were xml-code. He has it in the civ5 references and tutorials. It is up to date with all the BNW API for units, cities, players, teams, etc., as well as all the BNW hooks for the GameEvents stuff, and what arguments are passed.

He dumped all this info into a "mod" as it were with xml files for each of the object-types: one for Player methods, one for Unit methods, etc. You can use Notepad or Notepad++ to search through these files, or simply to browse through them.

The PlayerDoTurn hook, for example, only passes one argument from the gamecore to functions "hooked" to it:
Code:
  <event name="PlayerDoTurn" type="Hook">
    <arg pos="1" type="PlayerTypes" name="ePlayer"/>
  </event>
"e" in the name fields signifies that Firaxis is passing an integer value that will conform to a Player ID# from the lua "Players" table, an Improvement ID # from the database, etc.

Just remember that Player ID# as used in Civ5 lua does not equal the <Civilizations> table ID # for any given civilization registered within the game's SQL/XML database.

-----------------------------------

In your code you never defined pPlayer within your function, so you would get runtime errors from attempting to "index" a nil value. Otherwise you would be more or less on the right track with the last bit of code you posted, however I would also define the promotion variables outside the function, since you are going to be referring to the same info multiple times per turn and per game
Code:
local tupamarosID = GameInfoTypes["UNIT_TUPAMAROS"]
local TupamaroShock1 = GameInfoTypes["PROMOTION_SHOCK_1"]
local TupamaroShock2 = GameInfoTypes["PROMOTION_SHOCK_2"]
local TupamaroShock3 = GameInfoTypes["PROMOTION_SHOCK_3"]
function TupamarosBoosts(iPlayer)
	local pPlayer = Players[iPlayer]
	if (pPlayer:GetLateGamePolicyTree() ~= -1) then
		for pUnit in pPlayer:Units() do
			if pUnit:GetUnitType() == tupamarosID then
				if (pPlayer:GetLateGamePolicyTree() == pPlayer:GetPublicOpinionPreferredIdeology() ) then
					-- Tupamaros boost Shock 1
					pUnit:SetHasPromotion(TupamaroShock1, true)
					pUnit:SetHasPromotion(TupamaroShock2, false)
					pUnit:SetHasPromotion(TupamaroShock3, false)
				else
					-- Tupamaros boost Shock 3
					pUnit:SetHasPromotion(TupamaroShock1, true)
					pUnit:SetHasPromotion(TupamaroShock2, true)
					pUnit:SetHasPromotion(TupamaroShock3, true)
				end
			end
		end
	end
end
GameEvents.PlayerDoTurn.Add(TupamarosBoosts)
I would also add a check in there right after defining "pPlayer" for whether or not pPlayer:GetCivilizationType() matches up to GameInfoTypes["CIVILIZATION_WHATEVERS"] before even doing anything else.

----------------------------------------

The problem I see with using the Shock series of promotions is that these are standard selectable promotions, and you most likely in many cases be punishing the player by taking away earned and selected promotions form these units. When lua removes a promotion from a unit, this does not affect the unit's accumulated experience points or experience "level", so you can theorectically end up with units that had previousy "earned" their way to having Shock 3, and now must gain an inordinate number of additional XP points in order to select another promotion as compared to a unit that did not have Shock 2 and Shock 3 taken away from it.
 
Back
Top Bottom