• 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 - Remove insta heal promotion using LUA?

sman1975

Emperor
Joined
Aug 27, 2016
Messages
1,376
Location
Dallas, TX
Hey,

Quick question - is there a "straightforward" way to remove the insta-heal promotion using only LUA?

I know there's a brute-force way of checking every unit at the start of the game turn and use something like this:
Code:
if( player:IsAlive() ) then           
      for pUnit in player:Units() do
               pUnit:SetHasPromotion(0, false)
     end
end

Is there a less computationally-intense method?

Thanks,

sman
 
I don't think your method will work (although I haven't checked it), because SetHasPromotion can remove a promotion that a unit already has, not a promotion that is available, and a unit never has the Insta Heal promotion, as it's just an instant effect.

There is a simple method of removing it using XML or SQL, but I guess it's not what you're looking for because you want to be able to change its availability during the game.

So if you really need to do it using Lua, I think you should use the following event:
Code:
GameEvents.CanHavePromotion(ownerId, unitId, ePromotion)

I'm too lazy to write the complete code for you now, but if you don't know how to do it, and nobody else does it for you, I'll do it later.
 
PawelS - no worries - I can look at that more closely. Played around with the other techniques all of about 2 minutes but didn't seem like it was going to work well. Will take a stab at your suggestion and let you know if it works.

Thanks for the tip!
 
PawelS - it's working! Shock - and it worked the first time I tried it.... Which is quite unusual for me and LUA... :crazyeye:

Final code looks like this:
Code:
function OnCanHavePromotion (iPlayer, iUnit, iPromotion)                 -- Function will disable "insta heal" promotion
    if iPromotion == 0 then                                              -- insta heal ID = 0
        return false
    else
        return true
    end
end

And this is added to the GameEvents queue using:
Code:
GameEvents.CanHavePromotion.Add(OnCanHavePromotion)

I didn't see this LUA hook in the website that I use. But there are a few posts that helped decipher how to use it. Anyways, many thanks again!!
 
PawelS - it's working! Shock - and it worked the first time I tried it.... Which is quite unusual for me and LUA... :crazyeye:

Final code looks like this:
Code:
function OnCanHavePromotion (iPlayer, iUnit, iPromotion)                 -- Function will disable "insta heal" promotion
    if iPromotion == 0 then                                              -- insta heal ID = 0
        return false
    else
        return true
    end
end

And this is added to the GameEvents queue using:
Code:
GameEvents.CanHavePromotion.Add(OnCanHavePromotion)

I didn't see this LUA hook in the website that I use. But there are a few posts that helped decipher how to use it. Anyways, many thanks again!!
Using a direct value of 0 is quite a 'dangerous' thing to do, since other mods may remove PROMOTION_INSTA_HEAL completely, giving another promotion an ID of 0! This means that you might unintentionally make another promotion unobtainable!
What you should do is 'retrieve' the ID from the database, such as this:
Code:
local iPromotionInstaHeal = GameInfoTypes.PROMOTION_INSTA_HEAL
and then replace 'if iPromotion == 0 then' by:
Code:
if iPromotion == iPromotionInstaHeal then
We could also directly use 'if iPromotion == GameInfoTypes.PROMOTION_INSTA_HEAL', but by storing it in a variable at the start of the code we only need to look at the Database once, which saves some time in the long run. It's generally a good habit to do.

The GameInfoTypes.PROMOTION_INSTA_HEAL takes care of possible changes to the ID for PROMOTION_INSTA_HEAL.
If it gets removes we will compare iPromotion against nil, which will result in false. (I assume that iPromotion will never be passed as 'nil', since that would not make sense for this event as a whole :p)
 
@Troller - thanks - that is definitely an improvement. I was looking for something QUAD (quick and dirty) yesterday to solve a problem I was having on a few of the maps in the next mod where the insta heal just didn't belong. But on other maps in the mod it was a good thing. For example, the mod has several "strategic" maps with weekly game turns. Insta heal works there as replacements are assigned to make up for battle losses. However, there are several "tactical" or battlefield maps with 30 minute game turns. In this case, the use of insta heal throws the entire balance off.

I guess I was thinking that my mod as a "replacement game" is in itself more or less stand alone, and I can control anything that adjusts ID numbers. But that's not a good assumption since it limits players who may want to use their favorite mods with my mod that could break the code. I have a few other hard coded ID's that I really should take a look at and fix using GameInfoTypes. It's already there in most cases, but sometimes as you're coding along trying to get something working, temp solutions get plastered into the code and once it's working, you tend to move on the next challenge on the gig sheet and never go back to fix the "temporary" changes. Guilty...

When you say "at the start of the code" - you mean at the very top of the "file" that contains the function? Right now, I'm using a "ScenarioBuilder.lua" file that looks at the map name then makes adjustments based on what the scenario/map needs. Currently it's a list of about 20 various and sundry functions top to bottom, including the one that removes the insta heal. Is the top of the code literally at the top of this file, before the first "function" ?
 
When you say "at the start of the code" - you mean at the very top of the "file" that contains the function? Right now, I'm using a "ScenarioBuilder.lua" file that looks at the map name then makes adjustments based on what the scenario/map needs. Currently it's a list of about 20 various and sundry functions top to bottom, including the one that removes the insta heal. Is the top of the code literally at the top of this file, before the first "function" ?

Yup.

P.s. I like to use william's excel cheatsheets to look for hooks and/or functions. I lost the link towards the post in which he provided those, which is why I posted them in the attachment below

(I recently added VMC-added functions to one of the excel files by looking into the github DLL code for VMC, and I hope he doesn't mind me sharing that file as well.)
(The VMC-hook excel file was also provided by whoward, and I did not change/add anything in that!)

tldr; All credits go to whoward for his amazing work on VMC and the cheatsheets :D


EDIT: You seem to be pumping out those scenarios/mods quite fast, kudos to that ;)
 

Attachments

In the immortal words of Barney Stinson, "AWESOME!" :woohoo:

Thanks for the reading assignment! Man, with info like this, I'm going to have to pick up my game a few notches!
 
Last edited:
You can also do as like this to keep stuff that goes together with each other because the game reads and implements all "stand-alone" code not placed within a function when the file is loaded.
Code:
-- insta heal ID #
local iPromotionInstaHeal = GameInfoTypes.PROMOTION_INSTA_HEAL
-- Function will disable "insta heal" promotion
function OnCanHavePromotion (iPlayer, iUnit, iPromotion)
    if iPromotion == iPromotionInstaHeal then
        return false
    else
        return true
    end
end
if (iPromotionInstaHeal ~= nil) and (iPromotionInstaHeal ~= -1) then
	GameEvents.CanHavePromotion.Add(OnCanHavePromotion)
end
All you care about in terms of placement of the variable name is that this line
Code:
local iPromotionInstaHeal = GameInfoTypes.PROMOTION_INSTA_HEAL
Is placed higher in the code than this chunk of code
Code:
if (iPromotionInstaHeal ~= nil) and (iPromotionInstaHeal ~= -1) then
	GameEvents.CanHavePromotion.Add(OnCanHavePromotion)
end
Because if you reverse the order of the two within the lua file the variable won't have been initialized with a value as yet so will be regarded by lua to have a value of "nil".

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

And you always need a function to be defined higher in the code than any line that adds that function to a game event hook. So you would never do as this:
Code:
-- insta heal ID #
local iPromotionInstaHeal = GameInfoTypes.PROMOTION_INSTA_HEAL
if (iPromotionInstaHeal ~= nil) and (iPromotionInstaHeal ~= -1) then
	GameEvents.CanHavePromotion.Add(OnCanHavePromotion)
end
-- Function will disable "insta heal" promotion
function OnCanHavePromotion (iPlayer, iUnit, iPromotion)
    if iPromotion == iPromotionInstaHeal then
        return false
    else
        return true
    end
end
But you can do as this:
Code:
-- Function will disable "insta heal" promotion
function OnCanHavePromotion (iPlayer, iUnit, iPromotion)
    if iPromotion == iPromotionInstaHeal then
        return false
    else
        return true
    end
end
-- insta heal ID #
local iPromotionInstaHeal = GameInfoTypes.PROMOTION_INSTA_HEAL
if (iPromotionInstaHeal ~= nil) and (iPromotionInstaHeal ~= -1) then
	GameEvents.CanHavePromotion.Add(OnCanHavePromotion)
end
 
@LeeS and Troller - y'all ought to charge money for all the tutoring you're doing! I know I've been getting my money's worth for the last few weeks! :lol:

@LeeS - your suggestion will make me need to look at a few changes, but all-in-all worth it if it makes the code more stable.

Quick question - my mod is working, based on Troller's recommendations earlier today. In the code, the function OnCanHavePromotion is lower than the GameEvents.CanHavePromotion.Add(OnCanHavePromotion) command (ya know, that arrangement you told me to never use). My deductive logical patterns are hard for me to escape, so of course I'd code it backwards...

As it's working, I'm assuming that leaving the event adder above the function code could result in those nefarious gremlins that cause intermittent CTD's on me from time to time that take forever to not find the cause?

Thanks again for all the suggestions. I'll rearrange the function order and tighten up the code based on these great suggestions. Y'all are the best!

sman
 
You'll find as you start to use more advanced techniques, such as this, that the placement does indeed matter:
Code:
function GreatPeopleGameInit()
	LuaEvents.SerialEventUnitCreatedGood.Add(GreatPeopleNames);
end
Events.LoadScreenClose.Add(GreatPeopleGameInit)
So just learn the habits that will cover you in all cases from the very beginning of your mod-making efforts, rather than spending half a day head-bashing against that condition where it matters.

Every time I try to get clever and violate the rule, I am reminded of why the rule is a rule.
Spoiler :
there is actually all kinds of stuff that the game will actually let you do but we generally advise people not to because bad habits once formed & etc

for example this is valid code:
Code:
<GameData>
	<Unit_UniqueNames>
		<Row>
			<unittype>UNIT_GREAT_GENERAL</UnitType>
			<UniqueName>TXT_KEY_GREAT_PERSON_GARIBALDI</uniquename>
		</Row>
	</Unit_UniqueNames>
</GameData>

But <Row> and <row> are not interchangeable for example and once you get in the habit of not using uppercase exactly as Firaxis did you run into the condition where it matters and now all of a sudden your code does not work.

This is also valid code but William Howard freaked out the one and only other time I mentioned this in passing as in fact being valid for lua even though not a good idea:
Code:
pPlayer.ChangeGold(pPlayer, 100)
 
Last edited:
@LeeS - yes, I see your point. I'm quite the noob when it comes to LUA - have learned a bunch though the past month or so - mostly thanks to the generosity of folks like yourself. And as a noob, there's a lot more to learn than just how the different objects and methods work. Those good programming practices like you're talking about are really the foundation that the house it built on. Like most noobs, I start working on the roof before the foundation is laid.... :crazyeye:

But it's also related to "capability creep" - I start with a plan to build a mod that does "A" - by the time I'm done, the mod does "A", "B", "A.2", "C", "Z". The initial design/layout of the mod changes a lot during development and the code can look like a jumbled mess in ModBuddy. Appreciate your advise on how best "structure" the code in addition to what the code should look like.

I am a little confused about the order of things, though. Say for example I want to built a "library" of utilities - generic functions I can use when/wherever is needed. I put those functions in a "genericFunctions.lua" file. Now I want to build the file that will use these functions. Do I use the "include" statement or is that not necessary? I've poured all over the RED mod files, and I believe that's what/why they're doing it. Just wasn't sure if that was they "proper" way to arrange functions/events or not.
 
If I have a file with functions I commonly use, and I name that file LeeS_Toolkit.lua, then I set VFS=true for file LeeS_Toolkit.lua, and then within my mod's regular lua file (that is set as an InGameUIAddin) I place this line at the top of the file:
Code:
include("LeeS_Toolkit.lua")

Like I am doing here within one of the lua files in the Scipio's Rome mod where I am "including" the contents of file ProconsulMiscFunctions.lua into another file:
Code:
-- Proconsul_City_Functions V14
-- Author: LeeS (lshipp)
-- DateCreated: 1/25/2015 8:44:38 PM
--------------------------------------------------------------
include("ProconsulMiscFunctions.lua")
local bPrintForThisFile = false
local iYieldGold = GameInfoTypes.YIELD_GOLD
The include command does not literally need to be the very first line of a file but it needs to be pretty much the first thing appearing in the code because otherwise you run the risk that something in the "included" file (in this case, ProconsulMiscFunctions.lua) will actually be needed before the placement of the include line.

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

Also, don't use local for any variable created within your toolkit file. This causes the variable's assigned value to be invalid when the contents of the toolkit file are loaded into another file via the "include" command, and causes the variable itself not to be recognized.
 
@LeeS - good tips. I'm collecting all of these into a "best practices" document for later reference. Am already starting to re-examine current mod code to add these kinds of improvements. Will save a lot of time in the future. And that tip about "local values" in the toolkit - I haven't seen that before anywhere. It explains a problem I was having earlier when I tried to use a similar "include" approach.

Really appreciate your taking time to help with this.
 
Forgive me for necro'ing an old thread, but something's come up as the mod has evolved, but as the issue is an extension of this thread it didn't seem right to open a new one.

Here is the offending function in question:

Code:
function OnCanHavePromotion (iPlayer, iUnit, iPromotion)                                -- Function will disable "insta heal" & "embarkation" promotions (tactical maps only)
    if (iPromotion == iPromotionInstaHeal) or (iPromotion == iPromotionEmbarkation) then                                                  
        return false
    else
        return true
    end
end

With the iPromotions defined by an included "globals.lua" file like this:
Code:
iPromotionInstaHeal = GameInfoTypes.PROMOTION_INSTA_HEAL
iPromotionEmbarkation = GameInfoTypes.PROMOTION_EMBARKATION

It works well for a human player (neither promotion is ever available to choose).

During my frequent Live Tuner test runs, the AI never selects the "instant heal" promotion during gameplay.

However, when I break/pause the run and examine the individual units, many of them will possess the "embarkation" promotion.

Is this one of those Live Tuner "anomolies" that I shouldn't worry about fixing, or is there something about the function or embarkation promotion that would require different trapping?

Thanks again!
 
The embarkation promotions are given out at the dll level based on discovery of the tech that allows embarkation.

I don't believe therefore the game event for "CanHavePromotion" ever fires for that promotion.

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

CanDoThis events only fire for those units, buildings, promotions, policies, techs, etc, the player can otherwise have or select. If something in the database disallows it for a player(city/unit), the event never fires for that player(city/unit). If something in the dll disallows it for a player(city/unit), the event never fires for that player(city/unit). If a unit already has a promotion, the event is never going to fire for that unit for that promotion.

IE, the game never asks the question "can this be done?" by firing the lua event if the answer is already "false" or already is "the thing already has it".
 
Last edited:
@LeeS - thanks - you always seem to have a handle on the issues I'm encountering. I know I can remove the promotion at game start, but it sounds like there's nothing I can effectively do to prevent the AI from selecting it during game play - based on your analysis of CanDo events above.

I think I now understand better what you're saying about how I would use LUA to accomplish something (can it be done?), and how that is something the DLL doesn't need to do, as its logic is already in place so it knows exactly what it can do and how it's done and proceeds accordingly. Lucky b******...

I suppose I could remove embarkation at the start every turn for each player/all land units, but that seems like a lot of extra overhead for such a little problem. Ah the trade-offs that impact effective game design.... :/

Thanks again - you're on top of this as always!
 
Back
Top Bottom