Removing promotions from units?

Evalis

Prince
Joined
Mar 2, 2009
Messages
496
I'm really new to lua, and programming in general, but I've gathered together a sampling of what I think I will need to get this to work. There are most likely monstrous issues with the code so I would like some assistance in putting it together before I test this.

Spoiler :
Code:
function Remove_Mounted_Penalty(Player, unitCombatType, Unit)
	local iUnitCombat = unitCombatType
	local pPlayer = Player
	local pUnit = Unit
	if pPlayer:GetTraitType == "No_Mounted_Penalty" and iUnitCombat ==  "UNITCOMBAT_MOUNTED" then
		pUnit:RemovePromotion('PROMOTION_CITY_PENALTY')
		pUnit:RemovePromotion('PROMOTION_NO_DEFENSIVE_BONUSES')
	end
end

Events.SerialEventUnitCreated.Add(Remove_Mounted_Penalty)

Obviously I will create the aforementioned trait in xml. Is this doable?
 
Is this doable?

I'm assuming this is just pseudocode while you'd look up the actual variables and functions to use. In that case, yes, it can be done.

If you really, really don't want any units for that player to have the mounted penalty promotion, then the code would be more like this:
Code:
function Remove_Mounted_Penalty(iPlayer,iUnit,hexVec,unitType,cultureType,civID,primaryColor,secondaryColor,unitFlagIndex,fogState,selected,military,notInvisible)
	if(iPlayer ~= -1) then
		pPlayer = Players[iPlayer];
		if(iUnit ~= -1) then
			pUnit = pPlayer:GetUnitByID(iUnit);
			pType = pUnit:GetUnitType();
			pCombatClass = GameInfo.Units[pType].CombatClass;
			iLeader = pPlayer:GetLeaderType();
			condition = "LeaderType = '" .. GameInfo.Leaders[iLeader].Type .. "'";
			for row in GameInfo.Leader_Traits(condition) do
				iTrait = row.TraitType;
			end
			if iTrait == "TRAIT_NO_MOUNTED_PENALTY" and pCombatClass == "UNITCOMBAT_MOUNTED" then
				pUnit:SetHasPromotion(GameInfoTypes.PROMOTION_CITY_PENALTY,false);
				pUnit:SetHasPromotion(GameInfoTypes.PROMOTION_NO_DEFENSIVE_BONUSES',false);
			end
		end
	end
end
Events.SerialEventUnitCreated.Add(Remove_Mounted_Penalty)

And by "it'd be more like this", I actually mean "here's exactly how you do it", although I can't actually test this right now due to some problems with my own mods. The only headache you'll run into is that that particular serial event doesn't just trigger when a unit is created, but also every time it embarks, disembarks, or rebases. Since you're only removing a penalty, this won't cause any balance issues, but it WILL slow things down a bit.

Of course, then you run into a huge balance issue. There's only one unit in the entire game with that particular penalty (Cavalry or its Cossack UU), and it only gives a penalty against other mounted units. Even with the removal of the "No Defensive Bonuses" promotion as well, it'd make for an incredibly weak Trait. Basically, you'd be better off just creating a Cavalry-replacing UU that doesn't have those penalties; no Lua needed.
 
This code doesn't appear to work, though I really appreciate the effort! :trophy2nd:

^^ Anyway.. I copied it word for word. I confirmed all the xml is working, both before and after this lua script was loaded. I made sure import to VF = true. I've tried using firetuner to log what was happening, but the unitcreated event doesn't appear to show up for some reason. I named the file something the game was certain not to use. Is there a specific file name this has to be?

Is there something else I'm doing wrong?
 
I made sure import to VF = true.

This is your problem, or at least part of it. Executable Lua code (stuff tied to events) is NOT supposed to use VFS to load. Instead, you need to activate it through an InGameUIAddin declaration, and leave the VFS on False.
 
Yay! :trophy:

This totally works now. I completely missed that extra ' at the end of the defensive bonuses part. Okay so now I can go about finishing up art and text and releasing this mod. I do still have a few questions though..

I'll start with the ones I think I understand:

1. if(iPlayer ~= -1) then
Basically we are doing this to confirm it is a player and not a barbarian or city state?
2. pPlayer:GetUnitByID(iUnit);
We are doing this because the function returns a unit ID only and not a specific unit?

Things I don't understand:
1. GameInfo.Units[pType].CombatClass
Why are we using a bracket here and not parenthesis?
2. condition = "LeaderType = '" .. GameInfo.Leaders[iLeader].Type .. "'";
What are the brackets, dots and quotations for?
3.
Spoiler :
Code:
	for row in GameInfo.Leader_Traits(condition) do
				iTrait = row.TraitType;
			end
Why is this a loop? Can non-array variables store more than value? This probably has to do with my not understanding #2

4. function Remove_Mounted_Penalty(iPlayer,iUnit,hexVec,unitType,cultureType,civID,primaryColor,secondaryColor,unitFlagIndex,fogState,selected,military,notInvisible)
Why are we calling so many arguments in this function? Is there some reason we need more than the ones we are using, or are you just showing me everything that can be done?

In any case, thanks a ton for your help!
 
1. if(iPlayer ~= -1) then
Basically we are doing this to confirm it is a player and not a barbarian or city state?

No, those will still have actual player values. If you want to exclude barbarians or city-states, you need to do a check on pPlayer:IsBarbarian() or pPlayer:IsMinorCiv(), both of which are boolean values. In this case I didn't bother, but you can easily slide another IF check in there after the iPlayer check and before the iUnit check. Honestly, I'm not sure what pPlayer:GetLeaderType() does for barbarians or minor civs, so it'd probably be good to put that If check in, regardless.

The reason for the -1 check is that it's possible, on some occasions, for these events to get called without valid arguments. If that happens, you don't want to go any further, because there's no player with ID of -1 and so the pPlayer generation would fail. You should always sanitize your inputs this way to avoid crashing the functions.

2. pPlayer:GetUnitByID(iUnit);
We are doing this because the function returns a unit ID only and not a specific unit?

No, we're doing it because IDs are not unique; two players can each have a unit with ID of 42, so the iUnit argument by itself isn't enough to identify the unit. It's the combination of player ID and unit ID that uniquely identifies the unit structure.
GetUnitByID does return a unit structure (which I named pUnit), but you have to access it through that command on the pPlayer structure. It's just how it works.

1. GameInfo.Units[pType].CombatClass
Why are we using a bracket here and not parenthesis?

Brackets are for array indices. Parentheses are for function arguments. GetUnitByID() is a function, with iUnit as its argument; GameInfo.Units is an array (well, technically it's a two-tier structure, but no big difference), with pType as its index; the CombatClass part just tells it which part of the structure for that specific unit to use. While they look similar, they're not the same thing, so you have to be sure to use the right one.

2. condition = "LeaderType = '" .. GameInfo.Leaders[iLeader].Type .. "'";
What are the brackets, dots and quotations for?

The brackets are, again, because it's an array index. A ".." is a string append command, so that you can insert a variable inside a string. Effectively, what we're trying to create is a string that looks like
LeaderType = 'LEADER_ALEXANDER'
for the string variable named "condition", with the LEADER_ part changing depending on your civ. The strange double-quoting is because we need to have our string (the part inside the double "s) include a set of quotes of its own (the single quotes), while still dynamically assigning the leader type. It's a bit messy to read, but it's how you do programming like this.

3.
Code:
	for row in GameInfo.Leader_Traits(condition) do
				iTrait = row.TraitType;
			end
Why is this a loop? Can non-array variables store more than value?

They can (although not without changing it to iTrait[index] and adding an index increment command), but that's not actually what it's doing. There's no way to explicitly say "go get me the trait for leader X" in a single command; all of those minor tables that crosslink the major ones require this sort of access. So what you do is set the condition variable to something that only matches one single table entry; then, you loop over the table, using that condition, knowing that it'll only execute once because no other entry will be valid.

I could have just as easily written it as
Code:
for row in GameInfo.Leader_Traits() do
  if row.LeaderType == GameInfo.Leaders[iLeader].Type then
    iTrait = row.TraitType;
  end
end
and it'd have done the same thing, although it would take longer since it'd now be loading each entry in the Leader_Traits table into memory before checking. It's better to do it the way I did, even though it's harder to read.
Of course, this assumes that there's only one entry in the Leader_Traits table for each leader. If you allow multiple traits to be assigned to each leader, then you'll need to change iTrait to an array and add an index variable as well.

4. function Remove_Mounted_Penalty(iPlayer,iUnit,hexVec,unitType,cultureType,civID,primaryColor,secondaryColor,unitFlagIndex,fogState,selected,military,notInvisible)
Why are we calling so many arguments in this function?

You don't have a choice. SerialEventUnitCreated (like any other Lua event) has a very specific argument list; you can't just arbitrarily list a few of them and drop the rest. You see, it doesn't ACTUALLY know that iPlayer means player ID and so would put that in the appropriate variable; the function's first argument just happens to be player ID, the second just happens to be unit ID, and so on, and you can use whatever names you want for the variables.

The wiki has the argument lists for some of the events. Others, you can find by looking through the existing game's Lua; that argument list I gave was taken directly from one of the UI functions (UnitPanel, I think). And if all else fails, there's a trick for creating a "listener" function, using the unpack{...} command, but I don't have the syntax for that available offhand (I'm at work).
 
One xml only approach is to have the trait include a FreeBuilding and give this building FreePromotionRemoved. This approach won't work in your current situation because it would remove the promotion from all units not just mounted units. You could work around that by creating a functionally identical but differently named penalty promotion and alter the promotions mounted units get. But since you already have the lua you probably don't want to go down that path.
 
Spatzimus thanks so much for the explanation. I think I will be trying to wrap my head around all that for a while.

And Machiavelli, I already considered this. But there is an additional issue - when units are gifted, given for free from some event, or upgraded they would (re)gain the penalty. Now that I think about it, I suppose I could give all mounted units a promotion that is exclusive with those penalties, but I'm not sure if that would work. I might still need to redo the promotions and remove them, and I don't know what order it would do it in. There would still be the (albeit rare) issue of being gifted, or gifting something, that would bypass these effects.

In short I'm pretty certain I need lua to pull this off. Hmm I haven't checked what happens when I gift units away yet. Perhaps I should go do that.
 
Top Bottom