One of the biggest problems with the Demigods modding community is that there is
no central depository of information. This results in much of the work being
very redundant. The Stardock forum, the GPG forum, the Supreme Commander Wikis,
and the currently available mods are only methods of transfering this knowledge
from one modder to the next, and they don't tend to be very good about it. In
following I'm going to record how I went about tracking down the Poisoned Blood
problem with Lord Erebus.
The first part in making a mod, or in this case fixing a bug, is to specify the
current behavior, the desired behaviors, and how the two are different. In this
case the problem is:
Lord Erebus possesses an ability that allows him to drop poisoned health potions
when he dies. When a demigod consumes this potion, he loses 750 or 1500 health
instead of healing. If Lord Erebus kills a demigod with this damage, he receives
credit and gold for the kill. However, there is an additional complication. When
a Lord Erebus consumes his own potion, he also gets credit for killing himself.
Likewise, when he kills a teammate, his gets credit.
The problem with this is that this is abuseable and not fair.
It is abuseable because a Lord Erebus can use the potions to farm himself. He
gains 1500 gold every time he commit suicide. By repeatedly committing suicide,
he could gain a huge gold advantage. It is not fair
because a Lord Erebus can kill himself voluntarily by drinking his own potion,
cheating his enemies out of the rewards for killing him. It also penalizes his
own teammates for having him die, in contrast to other demigods which drop a
useful item for their teammates upon death.
The desired behavior would be for the poisoned potions to kill enemies. However,
what effect should the potion have on allies? This is a tougher question that
moves from bugfixing to balancing. The effect of Poisoned Blood drinking is
trivial, the amount of times this will effect games is very small. However, it
does have an effect. The effect from the original Demigods is definitely
incorrect. At this point, I'm going to tentatively say that our goal is to
have the potions be normal potions of healing for the allies of the Lord Erebus
and be normal poisoned blood potions for his enemies. This allows for the
intended effect of hurting his enemies while not poisoning his teammates.
Since I have now defined the effect I'm trying to achieve, I can now try to
impliment the effect. In this case I need to be able to have the Poisoned Blood
Potion have different effects based upon the team that consumes the potion.
To do this, I need to find the code that relates to the Poisoned Blood Ability.
A search of the contents of dgdata.zip reveals the following files contain
"PoisonedBlood":
- EffectTemplates.lua
- LootDefinitions.lua
- PowerUpDefinitions.lua
- HVAMPIRE_AchievementData.lua
- HOculus_AIBlueprint.lua
- HVampire_AIBlueprint.lua
- HVampire_Abilities.lua
- HVampire_SkillLayout.lua
- HVampire_unit.bp
EffectTemplates.lua
This file contains the emmiters or graphics specifications. There are two
effects that are used by Poisoned Blood. The first is when a potion is consumed.
This is the effect: PoisonedBlood01. The second is the effect that occurs
around a potion while it is on the game playing field: Health02. Effects contain
entries for Small, Medium, Large, and Huge targets to allow different graphics
for each target of the effect. Each entry is further divided between low,
medium, and high. These refer to the level of graphics that are currently being
displayed. This file is of no interest for changing how potions work.
LootDefinitions.lua
This file contains the Blueprints for the Potions. The Poisoned Blood potions
correspond to HVampirePoisonedBlood01 and HVampirePoisonedBlood02. The blueprint
specifies which PowerUp is associated with the item and also contains a weight.
The weight determines how likely a particular type of potion is to be created.
For Poisoned Blood Potions there is only one choice, therefore weight's value is
meaningless. However, it is present because it is referenced by the code that
creates loot drops.
PowerUpDefinitions.lua
This file contains the Blueprints for the Poisoned Blood Potion ability. The
entry resembles that of an item or ability. This entry specifies the
characteristics of the Poisoned Blood Potion. Two versions of the Potion exist,
one each level of the Poisoned Blood Potion. I will now go over the entry for
Poisoned Blood Potion 1 line by line. Poisoned Blood Potion 2 is very similar,
the differences are trivial.
Code: c++
- PowerUpBlueprint { #Declares the Blueprint name and opens the table
- Name = 'HVampirePoisonedBlood01', #The name of the Powerup Blueprint
- DisplayName = '<LOC ITEM_Powerup_0000>Health I', #The name that is displayed when a player places his cursor on the potion. It references the string_db file using the index ITEM_Powerup_0000. If it fails to find this string, it will use this as a default. The string_db file is used to specify the language used by the game.
- Description = '<LOC ITEM_Powerup_0001>Restores [GetHealth] Health.', #The effect of the item displayed when a player places his cursor on the potion. It also references the string_db file. Note that in this case, the string includes [GetHealth]. GetHealth is a function that is declared in the next line. By defining it and including it in the string, the text display will automatically update to the correct value if the value it references is ever changed. This reduces the amount documentation that must be updated when values are changed. It is used very frequently in the definition of strings.
- GetHealth = function(self) return Buffs['LootHealth01'].Affects.Health.Add end, #This declares the function GetHealth. It defines the function as referencing the value of Affects.Health.Add of the Buff LootHealth01 (the basic healing potion). This is the value that the basic healing potion heals. It then returns this value to whatever function called the function GetHealth. In short, it reports the amount of healing a basic potion does. Note, that in the Poisoned Blood potions case, this is a lie.
- Mesh = '/meshes/items/potions/potion01_mesh', #This is the potion's mesh or model. This is what you see when the potion is on the ground.
- Animation = '/meshes/items/potions/Animations/Potion01_Idle_anim.gr2', #This is the how the potion mesh is animated while on the ground.
- MeshScale = 0.1, #This is a scaling parameter that controls the size of the mesh displayed on screen.
- Effects = 'Health01', #This is the ambiant graphics displayed by a potion on the field.
- EffectsBone = -2, #Bones are specific parts of the mesh that are selected as locations. Typically they are defined and named. In this case, the source is -2. I don't know what this does exactly. However, -1 and -2 are both common values. What I do know is this specifies the origin location for Health01.
- EffectScale = 2, #This scales the size of the effect Health01.
- Audio = { #This opens up the table for the Audio effects
- # Ambient = 'Forge/DEMIGODS/Torch_Bearer/snd_dg_torch_idle_ice_lp', #PLACEHOLDER #This is a line from the original templates used in demigods. It is a placeholder and is commented out. It can be removed without any negative effect.
- OnActivate = 'Forge/ITEMS/Lootdrops/snd_item_lootdrops_poisoned', #This specifies a variable, OnActivate and a sound. When a function references the Blueprint, it may look for the Audio effects generated by the item. It may then also look for the OnActivate variable. This variable is referenced when the potion is used (activated), but can also be referenced by other functions. When the function that controls potion activation is called is also references and plays the sound.
- }, #Closes the Audio table
- Buffs = { #Opens the Buff table
- BuffBlueprint { #Open the BuffBlueprint table
- Name = 'HVampirePoisonedBlood01', #The name of the Buff
- Debuff = true, #Is this a debuff? Some abilities render demigods immune to debuffs.
- BuffType = 'POISONEDBLOOD', #This is the type of the Buff. This allows buffs with different names and sources to be treated in similar ways. It allows deals with how buffs stack.
- Stacks = 'ALWAYS', #This combined with BuffType determine stacking behavior. In this case, if a demigod drinks two potions of Poisoned Blood simultaneously. He would take damage from both potions.
- DamageSelf = true, #This is one of the sources of the problem with Poisoned Blood. This line allows the potion to damage the Lord Erebus who drops the potion.
- CanBeEvaded = false, #When a damage effect occurs, some effects can be dodged. Typically only regular melee attacks are dodgeable.
- Affects = { #This opens the affect table for the buff
- Health = {Add = -750}, #This modifies the Health of the user. In this case, the user gains -750 or takes 750 points of damage.
- }, #close affect table
- Effects = 'PoisonedBlood01', #This defines the effect that occur when the buff is activated.
- EffectsBone = -2, #This is the location of the Effects.
- }, #Close BuffBlueprint
- },#Close Buff
- } #Close PowerupBlueprint
HVAMPIRE_AchievementData.luaThis file contains the achievements for each of the demigods and how they are
awarded. Lord Erebus has an achievement for having 50 Poisoned Blood potions
consumed. This file is of no interest for changing how potions work.
HOculus_AIBlueprint.luaThis file contains the AI's ability builds for Oculus. The Oculus builds were built using
the template provided by Lord Erebus. It still contains commented strings that
refer to the template. This file is of no interest for changing how potions work.
HVampire_AIBlueprint.luaThis file contains the AI's ability builds for Lord Erebus. Some builds select
the ability. This file is of no interest for changing how potions work.
HVampire_Abilities.luaThis file contains the abilities for Lord Erebus. The Poisoned Blood ability has
two parts. The first is a passive buff to the regeneration value of Lord Erebus.
The second is a change to the loot drop table of Lord Erebus. When a demigod is
killed an item is selected from this drop table to appear. In this case, the
entire drop table is replaced by Poisoned Blood potions. This means that once a
Lord Erebus takes this ability, all of his death drops will be poisoned potions.
Loot drops are somewhat complicated. The drop table is defined in game.lua. When
a demigod death occurs, LootBlueprints.lua determines what item drops based upon
the weights in lootDefintions.lua.
HVampire_SkillLayout.luaThis file creates the skill layout. It specifies the visual display created when
a demigods skill tree is viewed. This file is of no interest for changing how
potions work.
HVampire_unit.bpDefines when and how abilities are gained. This file is of no interest for
changing how potions work.
In the log when a Poisoned Blood Potion kills a demigod the following entry
appears: "info: Time : Kill V0". Doing a search for various parts of this does
not return anything. (Editor's Note: The origin of this is probably interesting,
but I never traced it down.)
How a loot drop occurs.
- DeathThread (HeroUnit.lua) calls SpawnLoot (HeroUnit.lua).
- SpawnLoot performs GetLootTable (HeroUnit.lua) to get the drop table.
- GetLootTable returns the LootTable from the Demigod
- SpawnLoot gets the Demigod's level.
- SpawnLoot calls CreateLoot (LootBlueprints.lua) with a list of loots from
- the loot table indexed by the Demigod's level.
- CreateLoot selects a loot item from the Items it was given.
- CreateLoot calls CreateDroppedItem on the loot item.
Now, one important question, is how does the game known who created the potion.
Looking back upon the code, there is no immediate obvious answer to this question.
However, whenever a Demigod dies to Poisoned Blood Potion, the Lord Erebus gets
the kill and the gold. This suggests that there is information being carried along
with the potion, that isn't obvious. When your investigating a charcter
can select its properties can be seen in game by opening the console and
pressing shift+F7 (I think). However, items are unselectable, so that doesn't work.
The best solution I have is to include LOG comments in functions that are used
by the object. (Editor's Note: Another curious find is that when Lord Erebus is
killed by his own Poisoned Blood potion, the game does not count it as suicide.
However, when a Demigod consumes a potion that gives them -100000 health, it is
counted as a suicide. This is interesting, but I haven't check out why it occurs)
In this case, we have another lead. This lead comes from the awarding of credit
for kills. This requires tracing what happens when demigods die. There are
(at least) three places that contain this information.
DeathThread (heroUtil.lua) is the function that deals with removing the demigod.
ForgeUnit.lua contains the damage and death functions
Conquest.lua contains the rewards for killing units and heros.
It seems that the Poisoned Blood potion may be bring along an instigator field.
This would explain how the Poisoned Blood potion credits the Lord Erebus.
Given this information, the route I chose to fix the problem was to used nested
buffs. The declaration for the effect of the Poisoned Blood potion is in the
PowerUpBlueprints. PowerUps do not have all of the inbuilt functions that Buffs
have. It would be more elegant to add features to PowerUps, however with the
UberFix I'm trying to avoid changing things too drastically. The feature that
I am looking for would be called OnApplyPowerUp. However, since it doesn't
exist, I'm going to use Buffs.
The nest Buff solution is to apply the initial buff and then have that buff
apply a new buff. The first buff will do nothing besides specify the second
buff. First I'm going to show you the completed code, then I'll discuss it in
more detail.
Code: c++
- PowerUpBlueprint {
- Name = 'HVampirePoisonedBlood01',
- DisplayName = '<LOC ITEM_Powerup_0000>Health I',
- Description = '<LOC ITEM_Powerup_0001>Restores [GetHealth] Health.',
- GetHealth = function(self) return Buffs['LootHealth01'].Affects.Health.Add end,
- Mesh = '/meshes/items/potions/potion01_mesh',
- Animation = '/meshes/items/potions/Animations/Potion01_Idle_anim.gr2',
- MeshScale = 0.1,
- Effects = 'Health01',
- EffectsBone = -2,
- EffectScale = 2,
- Audio = {
- # Ambient = 'Forge/DEMIGODS/Torch_Bearer/snd_dg_torch_idle_ice_lp', #PLACEHOLDER
- OnActivate = 'Forge/ITEMS/Lootdrops/snd_item_lootdrops_poisoned',
- },
- Buffs = {
- BuffBlueprint {
- Name = 'HVampirePoisonedBlood01',
- Debuff = false,
- BuffType = 'POISONEDBLOOD',
- Stacks = 'ALWAYS',
- Affects = {
- Health = {Add = 0},
- },
- OnApplyBuff = function(self, unit, instigator)
- if IsEnemy( unit:GetArmy(), instigator:GetArmy()) then
- #LOG("IsEnemy")
- Buff.ApplyBuff( unit, 'HVampirePoisonedBlood01Damage', instigator )
- else
- #LOG("IsFriend")
- Buff.ApplyBuff( unit, 'HVampirePoisonedBlood01Heal', instigator )
- end
- end,
- },
- },
- }
- BuffBlueprint {
- Name = 'HVampirePoisonedBlood01Damage',
- Debuff = true,
- BuffType = 'POISONEDBLOOD',
- Stacks = 'ALWAYS',
- DamageSelf = false,
- CanBeEvaded = false,
- DamageFriendly = false,
- cancrit = false,
- Affects = {
- Health = {Add = -750},
- },
- Effects = 'PoisonedBlood01',
- EffectsBone = -1,
- }
- BuffBlueprint {
- Name = 'HVampirePoisonedBlood01Heal',
- Debuff = false,
- BuffType = 'POISONEDBLOOD',
- Stacks = 'ALWAYS',
- DamageSelf = true,
- CanBeEvaded = false,
- cancrit = false,
- DamageFriendly = true,
- Affects = {
- Health = {Add = 750},
- },
- Effects = 'Heal02',
- EffectsBone = -1,
- }
There are two features of note in this code. The first is the OnApplyBuff function. This function allows for other functions to occur when the buff is first applied to the target. There is another counterpart function, OnBuffAffect. I believe this occurs each time the buff pulses. (Editor's Note: Buffs can be set to pulse every X seconds. When this occurs a specific effect occurs. This is displayed in abilities like Lord Erebus's Vampire Creation Aura and the Queen of Thorn's Compost Aura).
The first buff, HVampirePoisonedBlood01, when applied checks to see if the
consumer of the potion (unit) is a teammate of the Erebus who dropped the
potion (instigator). If it is an ally, the HVampirePoisonedBlood01Heal buff is
applied. If it is an enemy, the HVampirePoisonedBlood01Damage is applied.
The only other tricky part is:
Affects = {
Health = {Add = 0},
},
Looking at the code, you should note that it doesn't do anything. In fact, the
Buff code terminates early because there the value is 0. However, this snippet
is important for the AI. To detect and use potions, the AI relies on the
PowerUpBlueprint having a buff that contains an affectType (Health, Energy, etc)
and an Add or Mult value. In moving to a nested buff solution I removed this
line from the Buff declared in the PowerUpBlueprint. I couldn't move the
secondary buffs into the PowerUpBlueprint because they would then all be
performed when the PowerUp was activated. (Editor's Note: Credit goes to Peppe
for helping me figure out this part). In this snippet is not present, the AI
will ignore the Poisoned Blood potions.
That should be about it. If you have questions or suggestions on how to make all
of this more clear, please post them. Hopefully we can start making more
tutorials and editorials on how the LUA in Demigods works.