• Our friends from AlphaCentauri2.info are in need of technical assistance. If you have experience with the LAMP stack and some hours to spare, please help them out and post here.

LUA Trait Help

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.

If I was to change the XP as well with lua, would the units keep their 'gained' promotions?
 
if you take away a promotion you take away a promotion. but the unit's 'level' remains the same. altering the unit's XP at the same time as taking away the promotion is certainly possible, but units select their next available promotion based on their current 'level' and their accumulated XP, where the additional XP needed for the next opportunity to select a promotion scales-up with each level. A unit that previously selected Shock 2 would suddenly have that promotion removed, for example, and now to get Shock 2 again the unit would have to accumulate the additional XP needed to meet its current requirment to "level-up". In essence, such a unit would pay twice for the Shock 2 promotion, and the second time around (or third or fourth or fifth times around) would have to "spend" an inflated amout of XP to re-select the promotion it had already acquired through the normal XP mechanics of the game.
 
The method "Unit:SetLevel" may be more prudent then. For example, I could set Shock 1 and the unit level as 1 too. And then set Shock 3 with unit level 3?

Another option I was considering was to just use "Unit:SetBaseCombatStrength" instead. It's meant to replace the Great War Infantry which has a base strength of 50.

So if I physically set the base strength to 55 (for a 10% boost based on public opinion equalling the current ideology) and 60 (a 20% boost based on public opinion and ideology being different), could that work instead? That would solve the promotion issues as was as preserve any gained XP over the course of the match
 
I think promotions would still work better, but you seem to have missed my point of creating your own promotions that cannot be chosen (and therefore cannot normally be otherwise acquired) one with the 10% and a second with the 20% and add or take away these promotions as needed. Since they cannot be chosen they will never affect the normal promotion-choosing mechanics of the game, or the unit's "level" or otherwise create unwanted side-effects as would giving and taking away game-standard normally-selectable promotions.

My issue with using the Shock line of promotions is that they are standard selectable promotions from gaining combat experience.
 
I see what you mean now. I've created new xml file and inserted this:

Code:
<GameData>
    <UnitPromotions>
        <Row>
            <Type>PROMOTION_TUPAMAROS_25</Type>
            <Description>TXT_KEY_PROMOTION_TUPAMAROS_25</Description>
            <Help>TXT_KEY_PROMOTION_TUPAMAROS_25_HELP</Help>
            <Sound>AS2D_IF_LEVELUP</Sound>
            <OrderPriority>1</OrderPriority>
            <CombatPercent>25</CombatPercent>
            <PortraitIndex>10</PortraitIndex>
            <IconAtlas>PROMOTION_ATLAS</IconAtlas>
            <PediaType>PEDIA_MELEE</PediaType>
            <PediaEntry>TXT_KEY_PROMOTION_SHOCK_1</PediaEntry>
        </Row>
        <Row>
            <Type>PROMOTION_TUPAMAROS_50</Type>
            <Description>TXT_KEY_PROMOTION_TUPAMAROS_50</Description>
            <Help>TXT_KEY_PROMOTION_TUPAMAROS_50_HELP</Help>
            <Sound>AS2D_IF_LEVELUP</Sound>
            <OrderPriority>1</OrderPriority>
            <CombatPercent>50</CombatPercent>
            <PortraitIndex>12</PortraitIndex>
            <IconAtlas>PROMOTION_ATLAS</IconAtlas>
            <PediaType>PEDIA_MELEE</PediaType>
            <PediaEntry>TXT_KEY_PROMOTION_SHOCK_1</PediaEntry>
        </Row>
    </UnitPromotions>
</GameData>

I've given the icons as ones that normal melee units can't have (Bombardment 1 and 3), as I cannot be bothered with making new icons at this stage.

So this now leaves me with:

Code:
-- UA: Stay Humble - Stop generating Golden Age Points when you reach 2000 gold. All gold purchases are 25% cheaper.
-- UU: Tupamaros - +25% combat strength when your ideology is favoured by your citizens, +50% combat strength if not.
local civilizationID = GameInfoTypes["CIVILIZATION_URUGUAY"]
local tupamarosID = GameInfoTypes["UNIT_TUPAMAROS"]
local Tupamaros25 = GameInfoTypes["PROMOTION_TUPAMAROS_25"]
local Tupamaros50 = GameInfoTypes["PROMOTION_TUPAMAROS_50"]

------------------------------------------------------------
---- 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() > 2000) 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

if (IsCivInPlay(civilizationID) == true) then
    GameEvents.PlayerDoTurn.Add(GoldAboveLimit)
end

-- -25% gold costs ---
local iAbilityEffectDummy = GameInfoTypes.POLICY_STAY_HUMBLE
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)

-- Tupamaros boosts --
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(Tupamaros25, true)
                    pUnit:SetHasPromotion(Tupamaros50, false)
                else
                    -- Tupamaros boost Shock 3
                    pUnit:SetHasPromotion(Tupamaros25, true)
                    pUnit:SetHasPromotion(Tupamaros50, true)
                end
            end
        end
    end
end
GameEvents.PlayerDoTurn.Add(TupamarosBoosts)
 
I also have a quick question unrelated to lua.

I have a mod that I use which sets a city-state to the name of my capital city. I obviously don't want this to happen.

I've tried writing xml code to 'pip' that mod to the post by changing the city-state name first. But I suspect that's not how it works, because I believe the city-state will have the name of whichever mod acts last on it in the initial setup, which is never guaranteed.

If I'm honest, I'd rather delete the city-state entirely if Uruguay is in the game, but I can't find the relevant code to do it. Any pointers?
 
If you know the mod ID # of the other mod you can add a reference to it in your mod, which will make the other mod load 1st when both are enabled. You can then do as needed to the other mod's code within your mod to make the other mod not interfere with your mod.

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

You need to add this to each of your rows in table <UnitPromotions>:
Code:
 <CannotBeChosen>true</CannotBeChosen>
------------------------------------------

I also use
Code:
			<PortraitIndex>59</PortraitIndex>
			<IconAtlas>ABILITY_ATLAS</IconAtlas>
			<PediaType>PEDIA_SHARED</PediaType>
For "generic" promotions such as you are adding, but you do not have to as a general rule.

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

The designations you are using for IconAtlas and PediaType shouldn't cause any bizarre behavior. Although PediaType determines where (ie, under which section) in the civilopedia Unit Promotions page the promotion is listed.
 
Is there a reference sheet for the ABILITY_ATLAS icons anywhere? It's just I had a one for the PROMOTION_ATLAS already...

I saw this thread a couple of days ago where you mention the same thing:

https://forums.civfanatics.com/threads/deleting-city-states.606627/

Your code looks like xml tags, so I believe I'll have to make another xml file and include the two mods like so:
Code:
<Mod id="e25d2dc0-87dc-4bec-af3c-ca2e05788010" version="14">
  <References>
    <Mod id="bunch of numbers and stuff" minversion="0" maxversion="999" title="the other mod" />
    <Mod id="more hex codes and stuff" minversion="0" maxversion="999" title="my mod" />
  </References>

Will there need to be an lua element to this as well? Or is the xml enough?
 
The references go in the modinfo file that controls how the game implements the files within a mod.

There would not need to be an lua element since what you are going to do is over-write what the other mod added to the XML/SQL database.

You do not add a reference line within your own mod to your own mod, so you do not want this line
Code:
<Mod id="more hex codes and stuff" minversion="0" maxversion="999" title="my mod" />

References can and normally should be added to the mod project within modbuddy, and then modbuddy will create the needed referencing code in the modinfo file. See https://forums.civfanatics.com/threads/the-enlightenment-era.551446/page-19#post-14159965

You just need to add an extra XML file or an SQL file to your mod and only put the code necessary to over-write the other mod's stuff in the database. Activate this file as you normally would any other standard-type XML/SQL file : as OnModActivated > UpdateDatabase. Place nothing else whatever in this additional file, so that when the other mod is not enabled there are no issues that would cause the main code of your mod to have errors.
 
Am I having to create a whole new xml file just for the city-state name update? I have embedded it in the Civilization.xml file instead.
 
Not sure if you're still interested in helping me further. I'm working on another Civ (Senegal), which has the following trait:

+1 Griot point is earned when your units kill, or are killed. If you recapture a city that you founded but lost, the culture output of that city increases by 25%.

(The Griot is a UU that replaces the Great Musician)

I'm not sure how to proceed with the first part.

I remember in the Costa Rica mod when you defined 'bApplyWhenCostaRicaIsInvolved', and I thought about doing the same here, but the arguments for my hook are different:

Code:
function GriotPointsWhenUnitDie(iPlayer, iKilledPlayer, iKilledUnit)

GameEvents.UnitsKilledInCombat.Add(GriotPointsWhenUnitDie)

Is it simple case of defining 'iPlayer as GameInfoTypes["CIVILIZATION_SENEGAL"]'?
 
no. you have to get the player object first
Code:
local pPlayer = Players[iPlayer]
then compare for whether
Code:
pPlayer:GetCivilizationType() == GameInfoTypes["CIVILIZATION_SENEGAL"]
 
iPlayer as you have it assigned is the ID# of the player who did the kiling
iKilledPlayer is the ID # of the player whose unit did the dying

So yes if you use those to get a player object for the victorious player and for the defeated player you can then check whether either of these is your civilization, and then apply a great musician point. Just remember that you have to do so within a city, and great people point manipulation is done in "100eths" of a point rather than whole points, so is a bit PITA. As I recall a change of "100" to a city's great-person class "score" is actually one great person point. But it's been literally years since I futzed with Great People Points via lua and I don't have an example I can look at really all that handy to remind myself how that obtuse system all works.
 
I believe JFD's Afghanistan mod has a good example of it tbh. I've been using it as a framework for coding. (I deleted my comment above because I thought I'd actually found the answer, sorry...)

Although in that mod, they use GameEvents.UnitPrekill.Add instead.
 
GameEvents.UnitPrekill really ought to be called

Code:
GameEvents.UnitRemovedFromGameForAnyReasonWhateverAndWeAreGoingToMakeItFireMultipleTimesForTheSameUnitRemovalAndSendDifferentArgumentDataEachTimeSoYouCanBashYourHeadAgainstTheLogicNeededToMakeThisEventWorkForYourNeed
It's arguably the least easy hook to use of all those Firaxis provided.
 
Hah, I had noticed that there was quite a lot of arguments involved....

I have a unique unit too which has the following effect:

Gendarmerie - Replaces Infantry. Higher defence. When garrisoned in a city, spy stealing rate reduced 10%, +25% defence and +2 culture for the city.

After reviewing the necessary methods, I decided that it would probably make more sense if the cities had a dummy building which was activated every time the unit was garrisoned in the city. Or maybe buy and sell a costless dummy building? Would this be an advisable way to do this?

Plus, I have the following code written up for the UA:

Code:
local senegalID = GameInfoTypes["CIVILIZATION_SENEGAL"]
local specialistGriotID = GameInfoTypes["SPECIALIST_MUSICIAN"]
local gendarmerieID = GameInfoTypes["UNIT_GENDARMERIE"]
local griotID = GameInfoTypes["UNIT_GRIOT"]

function GriotPointsWhenUnitDie(iPlayer, iKilledPlayer, iKilledUnit)
    local pKillingPlayer = Players[iPlayer]
    if pKillingPlayer:GetCivilizationType() == senegalID
        local griotCity = pKillingPlayer:GetCapitalCity()
        local griotPoints = 2
        griotCity:ChangeSpecialistGreatPersonProgressTimes100(specialistGriotID, griotPoints*100)
        if pKillingPlayer:IsHuman() then
            Events.GameplayAlertMessage("Your unit has killed an enemy! Your capital gains 2 [ICON_GREAT_PEOPLE] Great Griot Points!")
        end
    end
    local pKilledPlayer = Players[iKilledPlayer]
    if pKilledPlayer:GetCivilizationType() == senegalID then
        local griotCity = pKilledPlayer:GetCapitalCity()
        local griotPoints = 2
        griotCity:ChangeSpecialistGreatPersonProgressTimes100(specialistGriotrID, griotPoints*100)
        if pKilledPlayer:IsHuman() then
            Events.GameplayAlertMessage("Your unit was killed by an enemy. Your capital gains 2 [ICON_GREAT_PEOPLE] Great Griot Points!")
        end
    end
end
GameEvents.UnitsKilledInCombat.Add(GriotPointsWhenUnitDie)

I'm sure there is a way I can condense it further, because it almost looks like I've written the same code twice... any recommendations here?
 
Also, are there any unit flavours etc, to make the AI want to garrison these kinds of units more?
 
This is the finished code I have written so far for the Uniques and UA. Please let me know if you have any suggestions:

Code:
--------------------------------------------------------------
-- UA: Négritude - +2 Griot points are earned for your capital when your units kill, or are killed.
-- UU: Gendarmerie - Replaces Infantry. Higher defence. When garrisoned in a city, spy stealing rate reduced 10%, +5 defence and +2 culture for the city.
-- UU: Griot - Replaces Great Musician. Garrisoning them in a city produces +2 happiness, +2 culture and +10% tourism for that city.


----------------------------------------------------------------------------------------------------------------------------
-- GLOBAL VARIABLES
----------------------------------------------------------------------------------------------------------------------------
local senegalID = GameInfoTypes["CIVILIZATION_SENEGAL"]
local specialistGriotID = GameInfoTypes["SPECIALIST_MUSICIAN"]
local gendarmerieID = GameInfoTypes["UNIT_GENDARMERIE"]
local griotID = GameInfoTypes["UNIT_GRIOT"]
local genDummyBID = GameInfoTypes["BUILDING_GENDARMERIE_DUMMY"]
local griDummyBID = GameInfoTypes["BUILDING_GRIOT_DUMMY"]

----------------------------------------------------------------------------------------------------------------------------
---- 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

----------------------------------------------------------------------------------------------------------------------------
-- UA: NÉGRITUDE
----------------------------------------------------------------------------------------------------------------------------
function GriotPointsWhenUnitDie(iPlayer, iKilledPlayer, iKilledUnit)
    local pKillingPlayer = Players[iPlayer]
    if pKillingPlayer:GetCivilizationType() == senegalID
        local griotCity = pKillingPlayer:GetCapitalCity()
        local griotPoints = 2
        griotCity:ChangeSpecialistGreatPersonProgressTimes100(specialistGriotID, griotPoints*100)
        if pKillingPlayer:IsHuman() then
            Events.GameplayAlertMessage("Your unit has killed an enemy! Your capital gains 2 [ICON_GREAT_PEOPLE] Great Griot Points!")
        end
    end
    local pKilledPlayer = Players[iKilledPlayer]
    if pKilledPlayer:GetCivilizationType() == senegalID then
        local griotCity = pKilledPlayer:GetCapitalCity()
        local griotPoints = 2
        griotCity:ChangeSpecialistGreatPersonProgressTimes100(specialistGriotrID, griotPoints*100)
        if pKilledPlayer:IsHuman() then
            Events.GameplayAlertMessage("Your unit was killed by an enemy. Your capital gains 2 [ICON_GREAT_PEOPLE] Great Griot Points!")
        end
    end
end

if (IsCivInPlay(senegalID) == true) then
    GameEvents.UnitsKilledInCombat.Add(GriotPointsWhenUnitDie)
end

----------------------------------------------------------------------------------------------------------------------------
-- UU: GENDARMERIE
----------------------------------------------------------------------------------------------------------------------------
function GendarmerieBoosts(iPlayer)
    local pPlayer = Players[iPlayer]
    if pPlayer:GetCivilizationType() == senegal ID then   
        for pCity in pPlayer:Cities() do
            if pCity:GetGarrisonedUnit() == gendarmerieID then
                pCity:SetNumRealBuilding(genDummyBID, 1)
            else
                pCity:SetNumRealBuilding(genDummyBID, 0)
            end
        end
    end
end
GameEvents.PlayerDoTurn.Add(GendarmerieBoosts)

----------------------------------------------------------------------------------------------------------------------------
-- UU: GRIOT
----------------------------------------------------------------------------------------------------------------------------
function GriotBoosts(iPlayer)
    local pPlayer = Players[iPlayer]
    if pPlayer:GetCivilizationType() == senegal ID then   
        for pCity in pPlayer:Cities() do
            if pCity:GetGarrisonedUnit() == griotID then
                pCity:SetNumRealBuilding(griDummyBID, 1)
            else
                pCity:SetNumRealBuilding(griDummyBID, 0)
            end
        end
    end
end
GameEvents.PlayerDoTurn.Add(GriotBoosts)
 
There is no "s" in UnitKilledInCombat

Whoops! Syntax!
Code:
if pKillingPlayer:GetCivilizationType() == senegalID
Here's one example of ways to "squeeze" down the code a little in the unit killed function:
Code:
function GriotPointsWhenUnitDie(iPlayer, iKilledPlayer, iKilledUnit)
	local pKillingPlayer = Players[iPlayer]
	local pKilledPlayer = Players[iKilledPlayer]
	if (pKillingPlayer:GetCivilizationType() == senegalID) or (pKilledPlayer:GetCivilizationType() == senegalID) then
		local griotPoints = 2
		local griotCity = pKillingPlayer:GetCapitalCity()
		if (pKillingPlayer:GetCivilizationType() ~= senegalID) then
			griotCity = pKilledPlayer:GetCapitalCity()
		end
		griotCity:ChangeSpecialistGreatPersonProgressTimes100(specialistGriotID, griotPoints*100)
		if pKillingPlayer:IsHuman() then
			Events.GameplayAlertMessage("Your unit has killed an enemy! Your capital gains 2 [ICON_GREAT_PEOPLE] Great Griot Points!")
		elseif pKilledPlayer:IsHuman() then
			Events.GameplayAlertMessage("Your unit was killed by an enemy. Your capital gains 2 [ICON_GREAT_PEOPLE] Great Griot Points!")
		end
	end
end
Theoretically there ought to also be a check in there for whether the resulting "griotCity" variable is holding a value of "nil", but that will only occur in corner-cases such as a player using complete kills option or the civilization hasn't yet founded a capital city.

You can also "squeeze" your PlayerDoTurn into one unified function. As you scan through each city in the list of player cities, set both dummy buildings to "0" quantity in that city, and then add them back as appropriate for wether the garrison unit is an X or a Y unit. Altho I don't actually like to use the GetGarrisonedUnit() method because in BNW units are never really "garrisoned", they are just there on the city plot. And if there is both a land combat unit and the Great Musician unit in the same city at the same time, you will get the land combat unit as the "garrisoned" unit. Also as I recall the method gives you the unit-object rather than the unit's ID # from table <Units>, so you would have to get the garrisoned unit, then use this object to use pUnit:GetUnitType() before you can compare against your variables for what type of unit is garrisoning the city.

There are also some methods to "squeeze" the code I re-wrote even more, but I decided to leave that for another time since the first time when William Howard showed them to me it took me a bit of a while to wrap my head around using it properly.

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

Also, what are using to make the building bump the city's tourism by 10% ?
 
Last edited:
And no there aren't any flavors to assign to a unit to make the AI want to use them as a garrison more than they would otherwise except for the basic Defense flavoring.

For great people the AI only ever considers what the great people can do, like create a great work, and if they can't do that at that moment then as I recall they will only ever consider whether they can use them to create a Holy Site, or an Academy, or what-have-you.
 
Back
Top Bottom