How to add promotions that persist after unit upgrades?

for my mod, I've created a new PromotionClassType and all lua handled promotions are of that class.
 
I have found a workaround how to grant unit abilities using Lua, because now for some reasons I am really interested to do that for abilities and not just for promotions.

The solution is that you have to create a new unit that has the ability to grant the desired ability to every unit on the same plot. The ability modifier should be set to permanent so the target unit can keep it.
Then you can easily use a Lua script to spawn the ability granting unit on any valid plot. If there is another unit on this plot, then it will get the desired ability permanently, so you can remove the ability granting unit afterwards using Lua.

Here is an SQL example that shows how to create a unit called "UNIT_TIRAMOD_DUMMY_SUPPORT", which can grant other units the ABILITY_GREAT_GENERAL_MOVEMENT:
Code:
--First define ability:
INSERT INTO Tags (Tag, Vocabulary) VALUES ('CLASS_TIRAMOD_GRANT_ABILITY', 'ABILITY_CLASS');

INSERT INTO Types (Type, Kind) VALUES ('ABILITY_TIRAMOD_GRANT_SOME_ABILITY', 'KIND_ABILITY');
INSERT INTO TypeTags (Type, Tag) VALUES ('ABILITY_TIRAMOD_GRANT_SOME_ABILITY', 'CLASS_TIRAMOD_GRANT_ABILITY');

INSERT INTO UnitAbilities (UnitAbilityType, Name, Description) VALUES ('ABILITY_TIRAMOD_GRANT_SOME_ABILITY', 'LOC_ABILITY_TIRAMOD_GRANT_SOME_ABILITY_NAME', 'LOC_ABILITY_TIRAMOD_GRANT_SOME_ABILITY_DESCRIPTION');
INSERT INTO UnitAbilityModifiers (UnitAbilityType, ModifierId) VALUES ('ABILITY_TIRAMOD_GRANT_SOME_ABILITY', 'TIRAMOD_SOME_MODIFIER');

INSERT INTO Modifiers (ModifierId, ModifierType, Permanent, SubjectRequirementSetId) VALUES ('TIRAMOD_SOME_MODIFIER', 'MODIFIER_ALL_UNITS_GRANT_ABILITY', 1, 'TIRAMOD_ON_SAME_PLOT_REQUIREMENTS');
INSERT INTO ModifierArguments (ModifierId, Name, Value) VALUES ('TIRAMOD_SOME_MODIFIER', 'AbilityType', 'ABILITY_GREAT_GENERAL_MOVEMENT');

INSERT INTO RequirementSetRequirements (RequirementSetId, RequirementId) VALUES ('TIRAMOD_ON_SAME_PLOT_REQUIREMENTS', 'TIRAMOD_ADJACENT_UNIT_REQUIREMENT');
INSERT INTO RequirementSets (RequirementSetId, RequirementSetType) VALUES ('TIRAMOD_ON_SAME_PLOT_REQUIREMENTS', 'REQUIREMENTSET_TEST_ALL');

INSERT INTO Requirements (RequirementId, RequirementType) VALUES ('TIRAMOD_ADJACENT_UNIT_REQUIREMENT', 'REQUIREMENT_PLOT_ADJACENT_TO_OWNER');
INSERT INTO RequirementArguments (RequirementId, Name, Value) VALUES ('TIRAMOD_ADJACENT_UNIT_REQUIREMENT', 'MinDistance', 0);
INSERT INTO RequirementArguments (RequirementId, Name, Value) VALUES ('TIRAMOD_ADJACENT_UNIT_REQUIREMENT', 'MaxDistance', 0);


--Now define new unit:
INSERT INTO Types (Type, Kind) VALUES ('UNIT_TIRAMOD_DUMMY_SUPPORT', 'KIND_UNIT');
INSERT INTO TypeTags (Type, Tag) VALUES ('UNIT_TIRAMOD_DUMMY_SUPPORT', 'CLASS_TIRAMOD_GRANT_ABILITY');

INSERT INTO Units (UnitType, Name, Description, BaseSightRange, BaseMoves, Domain, FormationClass, Cost) VALUES ('UNIT_TIRAMOD_DUMMY_SUPPORT', 'LOC_UNIT_TIRAMOD_DUMMY_SUPPORT_NAME', 'LOC_UNIT_TIRAMOD_DUMMY_SUPPORT_DESCRIPTION', 3, 2, 'DOMAIN_LAND', 'FORMATION_CLASS_CIVILIAN', 10 );

Let us assume we have a warrior on a plot with coordinates 5;18. Then we can grant him the +1 movement ability in Lua using
Code:
local iPlayerID = 0
local iX, iY = 5, 18
local pUnit = UnitManager.InitUnit(iPlayerID  , "UNIT_TIRAMOD_DUMMY_SUPPORT", iX, iY)
UnitManager.Kill(pUnit, false)
Then you will see the additional movement of the warrior once the UI got updated correctly.
(Note that the +1 movement ability is also granted even when a unit only passes a plot with the UNIT_TIRAMOD_DUMMY_SUPPORT unit, but this should be no problem, because after spawning we kill this unit immediately before any unit can move.)



My only problem is that it would be very cumbersome to write the SQL code for each existing ability in the game. So now I wonder, whether it is possible to create the SQL rows automatically for each ability using something like a while loop in SQL.
 
Last edited:
My only problem is that it would be very cumbersome to write the SQL code for each existing ability in the game. So now I wonder, whether it is possible to create the SQL rows automatically for each ability using something like a while loop in SQL.


I don't know how much of this you need to do, but for examples of "looping" code you can do stuff like below. Note the TraitModifiers piece below could be simplified; its just long here because it also contains a reference to a game option players can turn on or off.

Code:
-- Add a Culture Bomb to every wonder
INSERT INTO Modifiers
(ModifierID,    ModifierType,      RunOnce,  Permanent,  OwnerRequirementSetId,  SubjectRequirementSetId)
SELECT 'QUO_WONDER_CULTURE_BOMB_' || BuildingType,              'MODIFIER_PLAYER_ADD_CULTURE_BOMB_TRIGGER',  0,   0,   NULL,   NULL
FROM Buildings WHERE Buildings.IsWonder=1 ;

INSERT INTO ModifierArguments
(ModifierID,   Name,  Type,    Value, Extra, SecondExtra)
SELECT 'QUO_WONDER_CULTURE_BOMB_' || BuildingType,              'BuildingType',  'ARGTYPE_IDENTITY',  BuildingType, NULL, NULL
FROM Buildings WHERE Buildings.IsWonder=1 ;
-- Only apply the modifiers if the culture bomb ability is enabled in MyOptions

INSERT INTO TraitModifiers
(TraitType,  ModifierID)
SELECT 'TRAIT_LEADER_MAJOR_CIV', 'QUO_WONDER_CULTURE_BOMB_' || BuildingType
FROM Buildings WHERE Buildings.IsWonder=1 AND (SELECT tblquoOptions.Value FROM tblQuoOptions WHERE tblQuoOptions.OptionID='QUO_OPTION_CAN_WONDERS_CULTURE_BOMB') >0 ;

There are additional ways to do this as well, such as creating temporary tables first and then using them to create repeating lines of code. You can see examples of that in the Biomes code for my Combined Tweaks mod where I create temporary tables to create lists of units that are locked out for a certain civ based on various characteristics, and then auto-generate the Modifiers that block out each unit.
 
Yes, this works. Thank you very much!

So now the SQL code looks like this:
Code:
--Hook modifiers to abilities:
INSERT INTO UnitAbilityModifiers (UnitAbilityType, ModifierId)
SELECT  'ABILITY_ZZZ_TIRAMOD_GRANT_'  || Type,
        'TIRAMOD_GRANT_'  || Type || '_MODIFIER'
FROM Types WHERE Types.Kind='KIND_ABILITY' ;

INSERT INTO Modifiers (ModifierId, ModifierType, Permanent, SubjectRequirementSetId)
SELECT  'TIRAMOD_GRANT_'  || Type || '_MODIFIER',
        'MODIFIER_ALL_UNITS_GRANT_ABILITY',
        1,
        'TIRAMOD_ON_SAME_PLOT_REQUIREMENTS'
FROM Types WHERE Types.Kind='KIND_ABILITY' ;

INSERT INTO ModifierArguments (ModifierId, Name, Value)
SELECT  'TIRAMOD_GRANT_'  || Type || '_MODIFIER',
        'AbilityType',
        Type
FROM Types WHERE Types.Kind='KIND_ABILITY' ;

--Define Requirements:
INSERT INTO RequirementSetRequirements (RequirementSetId, RequirementId)
VALUES ('TIRAMOD_ON_SAME_PLOT_REQUIREMENTS',
        'TIRAMOD_ADJACENT_UNIT_REQUIREMENT');
INSERT INTO RequirementSets (RequirementSetId, RequirementSetType)
VALUES ('TIRAMOD_ON_SAME_PLOT_REQUIREMENTS',
        'REQUIREMENTSET_TEST_ALL');

INSERT INTO Requirements (RequirementId, RequirementType)
VALUES ('TIRAMOD_ADJACENT_UNIT_REQUIREMENT',
        'REQUIREMENT_PLOT_ADJACENT_TO_OWNER');
INSERT INTO RequirementArguments (RequirementId, Name, Value)
VALUES ('TIRAMOD_ADJACENT_UNIT_REQUIREMENT',
        'MinDistance',
        0);
INSERT INTO RequirementArguments (RequirementId, Name, Value)
VALUES ('TIRAMOD_ADJACENT_UNIT_REQUIREMENT',
        'MaxDistance',
        0);


--Now define new units:
INSERT INTO Types (Type, Kind)
SELECT  'UNIT_ZZZ_TIRAMOD_DUMMY_UNIT_GRANT_' || Type,
        'KIND_UNIT'
FROM Types WHERE Types.Kind='KIND_ABILITY' ;

INSERT INTO TypeTags (Type, Tag)
SELECT  'UNIT_ZZZ_TIRAMOD_DUMMY_UNIT_GRANT_' || Type,
        'CLASS_TIRAMOD_GRANT_' || Type
FROM Types WHERE Types.Kind='KIND_ABILITY' ;

INSERT INTO Units (UnitType, Name, Description, BaseSightRange, BaseMoves, Domain, FormationClass, Cost, EnabledByReligion)
SELECT  'UNIT_ZZZ_TIRAMOD_DUMMY_UNIT_GRANT_' || Type,
        'LOC_' || 'UNIT_ZZZ_TIRAMOD_DUMMY_UNIT_GRANT_' || Type || '_NAME',
        'LOC_' || 'UNIT_ZZZ_TIRAMOD_DUMMY_UNIT_GRANT_' || Type || '_DESCRIPTION',
        3,
        2,
        'DOMAIN_LAND',
        'FORMATION_CLASS_CIVILIAN',
        10,
        1
FROM Types WHERE Types.Kind='KIND_ABILITY' ;


--At last populate ability rows:
INSERT INTO Tags (Tag, Vocabulary)
SELECT  'CLASS_TIRAMOD_GRANT_' || Type,
        'ABILITY_CLASS'
FROM Types WHERE Types.Kind='KIND_ABILITY' ;

INSERT INTO TypeTags (Type, Tag)
SELECT  'ABILITY_ZZZ_TIRAMOD_GRANT_' || Type,
        'CLASS_TIRAMOD_GRANT_' || Type
FROM Types WHERE Types.Kind='KIND_ABILITY' ;

INSERT INTO UnitAbilities (UnitAbilityType, Name, Description)
SELECT  'ABILITY_ZZZ_TIRAMOD_GRANT_' || Type,
        'LOC_ABILITY_ZZZ_TIRAMOD_GRANT_' || Type || '_NAME',
        'LOC_ABILITY_ZZZ_TIRAMOD_GRANT_' || Type || '_DESCRIPTION'
FROM Types WHERE Types.Kind='KIND_ABILITY' ;

--we define the new abilities at last to make sure they do not interfer with the previous lines "FROM Types WHERE Types.Kind='KIND_ABILITY' ;"
INSERT INTO Types (Type, Kind)
SELECT  'ABILITY_ZZZ_TIRAMOD_GRANT_' || Type,
        'KIND_ABILITY'
FROM Types WHERE Types.Kind='KIND_ABILITY' ;

I have saved this code in a file called "Units.sql".
This SQL file should get a high load order in the .modinfo file to make sure it is also taking into account the abilities of other modifications:
Code:
        <UpdateDatabase id="UnitsTest">
            <Properties>
                <LoadOrder>9999</LoadOrder> <!-- high load order -->
            </Properties>
            <Items>           
                <File>Units.sql</File>
            </Items>
        </UpdateDatabase>
Then you will be able to grant unit abilities in Lua using my GrantAbilityToUnit function:
Code:
function GrantAbilityToUnit( pTargetUnit, sAbilityType )
    local iPlayerID = pTargetUnit:GetOwner()
    local iX, iY = pTargetUnit:GetX(), pTargetUnit:GetY()
    local sFoundString = string.match(sAbilityType, "ABILITY_ZZZ_TIRAMOD_GRANT_")
    local sUnit = "UNIT_ZZZ_TIRAMOD_DUMMY_UNIT_GRANT_" .. sAbilityType
    if( iX and iY and sAbilityType and not sFoundString ) then
        local pUnit = UnitManager.InitUnit(iPlayerID  , sUnit, iX, iY)
        UnitManager.Kill(pUnit, false)
    end
end
The function argument sAbilityType is the string of the ability you want to use (e.g.
sAbilityType = "ABILITY_GREAT_GENERAL_MOVEMENT")
 
Top Bottom