MattCaspermeyer
12-03-2011, 10:31 PM
Okay, I've finally gotten to a point where I can fight Bone Dragons and so have had a chance to work on improving them.
So I thought this would be a great opportunity to provide a mini LUA scripting tutorial about how to add the capability of a post-hit affect to a unit.
Note that this post is very long and you are encouraged to take your time reading it and visit the references provided here and duplicated at the end of this post:
LUA Documentation (note that King's Bounty uses LUA version 5.0) (http://www.lua.org/manual/5.0/)
King's Bounty Libraries Translated to English (http://translate.googleusercontent.com/translate_c?hl=en&ie=UTF8&prev=_t&rurl=translate.google.com&sl=ru&tl=en&twu=1&u=http://kingsbounty.ru/docs/scripting/index.htm&usg=ALkJrhjNvc-2eVONGm3XcVb1S9FDIBT-8Q)
So let's clearly state what we want to do:
When a Bone Dragon attacks, we'd like to have it apply a post-hit affect to the unit it is attacking.
If the post-hit affect is successful, then it is to apply either:
Fear
Plague
Weakness
Doom
Sheep
Curse
In order to be able to do this, we need the following files (all files are typed in CAPS to distinguish them):
BONEDRAGON.ATOM - located in DATA.KFS
UNIT_FEATURES.LUA - located in SES.KFS
SPELLS.LUA - located in SES.KFS
ENG_UNITS_FEATURES.LNG (or your equivalent language file) - located in LOC_SES.KFS
The first file to edit is BONEDRAGON.ATOM:
main {
class=chesspiece
receiver=bones
model {
0=bonedragon.bma
1=bonedragondeath.bma
}
cullcat=0
bboxanim=1
}
arena_params {
features_label=cpi_bonedragon_feat
features_hints=undead_header/undead_hint,bone_header/bone_hint,flies_header/flies_hint
posthitslave=features_bone_creature
race=undead
cost=3200
level=5
leadership=1300
attack=43
defense=43
defenseup=6
initiative=6
speed=7
hitpoint=600
krit=10
hitback=1
hitbackprotect=0
movetype=2
attacks=moveattack,poison_cloud
features=bone,undead,dragon
resistances {
physical=0
poison=50
magic=0
fire=0
}
moveattack {
ad_factor=1
usefly=4
damage {
poison=50,65
}
custom_params {
poison=0
}
}
.
.
.
(file continues...)
Just a brief discussion of the sections in this file (this is a guide topic in itself):
main {...} - in this section you can see that you identify the class of the ATOM and associated models to go with it.
arena_params {...} - this is the section that we're going to focus on where it has the ATOM's statistics, attacks, abilities, etc.
scripts {...} - I have not edited this part of an ATOM before, but I think these control its behavior on the map.
animations {...} - these are where you can define new animations for an ATOM or change existing ones.
editor {...} - I have not used this section either, so I'm not sure what it does.
attachments {...} - this is where you can create neat graphics effects for the ATOM's abilities.
prefetch {...} - I have not used this section either, but I think it simply lists the animation files that the ATOM is going to use and must place them in a cache or something like that.
The area we're going to focus on is the arena_params {...} section.
We basically only need to do a couple of things to this file to enable a new post-attack ability:
Add: posthitmaster=script_name
Add: new features header and hint to the features_hints= item so that people can see the new ability.
For the file above, look at the start of the arena_params {...} section and you'll see where the features_hints= item starts (repeated here):
.
.
.
arena_params {
features_label=cpi_bonedragon_feat
features_hints=undead_header/undead_hint,bone_header/bone_hint,flies_header/flies_hint
posthitslave=features_bone_creature
.
.
.
Select a name for your new ability - this is the "code" name for the ability and is internal; people playing the game will not see this name, but it will be the name you use when adding it to the various other files you need to change. I decided to call the new ability: bonedragon_attack. This name will have appended to it the suffixes: _header and _hint to describe what this new feature does. So we can add this to the comma-separated list of features_hints that the unit uses:
.
.
.
arena_params {
features_label=cpi_bonedragon_feat
features_hints=undead_header/undead_hint,bone_header/bone_hint,flies_header/flies_hint,bonedragon_attack_header/bonedragon_attack_hint
posthitslave=features_bone_creature
.
.
.
Note that we have added "bonedragon_attack_header/bonedragon_attack_hint" to the end of the features_hints= list. I'll explain what both of these are a little bit later. This is the format that you use, though, when adding a new feature that you want displayed in the unit's feature list for its unit card.
The next thing we need to do is add the capability of the unit to be able to call a script when performing its attack. To enable this ability, we need to add a posthitmaster=script_name to the arena_params {...} section. I'm not sure if it matters where you add this so long as it is in the arena_params {...} section, but I usually add it just before the resistances section since that's where I've seen it mostly placed (you can always search on posthitmaster= to see if a script already exists for that ATOM with your text editor).
Once again, you need to think of what you want to call your script, once again, this is an internal name that you are going to use. I'm just going to call my new script: features_bonedragon_attack. Here is the implementation in the ATOM:
.
.
.
features=bone,undead,dragon
posthitmaster=features_bonedragon_attack
resistances {
physical=0
poison=50
magic=0
fire=0
}
.
.
.
This is all we need to do to the ATOM! We can save and close this file.
The next part we need to work on is the script. You could create a new LUA file with your new script in it if you wish or edit an existing file. In the case here, most of the unit features are included in the UNIT_FEATURES.LUA script. So we're going to add the posthitmaster script to this file, the name of the function must be the same name as script_name identified with posthitmaster= - in this case the function name must be called features_bonedragon_attack.
This function is shown in the code block below and I'll go through each section. I added this to the top of UNIT_FEATURES.LUA, but you can add it anywhere to that file so long as it is outside any other function block:
-- New function for adding a bonus to % of chance to inflict an effect
function effect_chance( value, effect_or_feature, kind )
local bonus = tonumber( Logic.hero_lu_item( "sp_chance_" .. effect_or_feature .. "_" .. kind, "count" ) )
if bonus ~= nil then
value = value + bonus
end
return value
end
-- New Bone Dragon Attack
function features_bonedragon_attack( damage, addrage, attacker, receiver, minmax )
if ( minmax == 0 ) then
local receiver_level = Attack.act_level( receiver )
local receiver_chance = ( 5 - receiver_level ) * 25
local rnd = Game.Random( 100 )
local poison = tonumber( Attack.get_custom_param( "poison" ) )
if rnd < receiver_chance
and damage > 0
and poison == 0
and not Attack.act_feature( receiver, "magic_immunitet" )
and not Attack.act_feature( receiver, "golem" )
and not Attack.act_feature( receiver, "pawn" )
and not Attack.act_feature( receiver, "boss" ) then
local tmp_spells = {}
if not Attack.act_is_spell( receiver, "spell_scare" )
and not Attack.act_feature( receiver, "mind_immunitet" )
and not Attack.act_feature( receiver, "undead" ) then
table.insert( tmp_spells, spell_scare_attack )
end
if not Attack.act_is_spell( receiver, "spell_plague" )
and not Attack.act_feature( receiver, "demon" )
and not Attack.act_feature( receiver, "plant" ) then
table.insert( tmp_spells, spell_plague_attack )
end
if not Attack.act_is_spell( receiver, "spell_weakness" )
and not Attack.act_feature( receiver, "plant" )
and not Attack.act_feature( receiver, "undead" ) then
table.insert( tmp_spells, spell_weakness_attack )
end
if not Attack.act_is_spell( receiver, "spell_crue_fate" )
and Attack.act_name( receiver ) ~= "vampire2" then
table.insert( tmp_spells, spell_crue_fate_attack )
end
if not Attack.act_is_spell( receiver, "spell_ram" )
and not Attack.act_feature( receiver, "plant" )
and not Attack.act_feature( receiver, "undead" ) then
table.insert( tmp_spells, spell_ram_attack )
end
if not Attack.act_is_spell( receiver, "effect_curse" )
and not Attack.act_feature( receiver, "plant" )
and not Attack.act_feature( receiver, "undead" ) then
table.insert( tmp_spells, effect_curse_attack )
end
if table.getn( tmp_spells ) > 0 then
Attack.act_aseq( 0, "cast" )
local dmgts = Attack.aseq_time( 0, "x" )
local cast = Game.Random( 1, table.getn( tmp_spells ) )
local spell_level = 3
if tmp_spells[ cast ] == spell_plague_attack then
tmp_spells[ cast ]( receiver, spell_level, dmgts )
elseif tmp_spells[ cast ] == effect_curse_attack then
tmp_spells[ cast ]( receiver, 1, 3 )
else
tmp_spells[ cast ]( spell_level, dmgts, receiver )
end
Attack.log_label( '' )
end
end
end
return damage, addrage
end
-- New Ent Entangle
function features_entangle( damage, addrage, attacker, receiver, minmax )
.
.
.
(file continues)
You can see here that I've included the previous function and the start of the next function to show you that the function for the new posthitmaster script is outside any other functions. Let's focus on the new function: features_bonedragon_attack.
-- New Bone Dragon Attack
function features_bonedragon_attack( damage, addrage, attacker, receiver, minmax )
.
.
.
Reference the LUA documentation for more specifics, but essentially you use the keyword function to identify a new function name and then the variables in () are the parameter list that the function uses. For posthitmaster scripts, there is a standard argument list. You'll need to look at an existing posthitmaster function identified in the various ATOM's for units that have an effect applied after their attack (for example, Werewolf Elves Bleeding) to see what the argument list is, then you can copy it and use it as a template for creating new functions. That is all I did when I first started to learn this stuff was to look at existing functions as an example and use them as a template for a new function that I wanted to create.
For this function, the argument list is:
damage - how much damage the attack has done
addrage - how much rage to add to the hero
attacker - the attacker unit ID from the C/C++ code
receiver - the receiver unit ID from the C/C++ code
minmax - this is an integer used to identify whether this was the attack that was just performed
Continuing with the code:
.
.
.
if ( minmax == 0 ) then
.
.
.
end
You'll notice that there is an if statement that checks minmax. Note that all if statements must have a matching end statement to terminate their code block. If minmax is 0 then it means that this was the attack that was just executed. You need to check this to make sure that it is not another attack script being executed (a unit could possibly have more than one posthitmaster script applied to it).
Let's continue:
.
.
.
local receiver_level = Attack.act_level( receiver )
local receiver_chance = ( 5 - receiver_level ) * 25
local rnd = Game.Random( 100 )
local poison = tonumber( Attack.get_custom_param( "poison" ) )
.
.
.
The keyword local is used to identify local variables that this function uses. If you omit the local keyword (I sometimes forget to use the local keyword) then the variable will be global and accessible from any other LUA function in the game. So try to remember to use the local keyword when you don't want the variable to be available outside this function (and the memory it uses to be freed up).
You can see here that this code block gets the level of the receiving unit (reference the King's Bounty library) and computes the chance of the effect based on a formula. Next it uses another King's Bounty library to generate a random number and lastly gets a custom parameter from the attacking unit's ATOM file.
Next is another if statement to check if the unit is to have the posthitmaster effect applied:
.
.
.
if rnd < receiver_chance
and damage > 0
and poison == 0
and not Attack.act_feature( receiver, "magic_immunitet" )
and not Attack.act_feature( receiver, "golem" )
and not Attack.act_feature( receiver, "pawn" )
and not Attack.act_feature( receiver, "boss" ) then
.
.
.
end
The first part of the if statement determines if the effect has occured due to random chance. You then note that you can apply as many additional conditions as you'd like to check to see if the effect should be applied. Note that there is a poison == 0 check in the list. Reference back to the ATOM above and note that for the Bone Dragon's moveattack {...} section there is a custom_params {...} section where you see that poison=0. This is used to identify that it is the Bone Dragon's main attack that is being used, but another attack (like it's poison_cloud) also executes the posthitmaster script and so you need a way to distinguish which attack it is. It is important to note that all of a unit's attacks (including all its special attacking abilities) will use this posthitmaster script. So you need a way of distinguishing between them if you want the script to only apply in certain situations.
All the other checks have to do with checking to make sure that the receiving unit does not have a certain feature.
Let's continue with the next code section:
.
.
.
local tmp_spells = {}
if not Attack.act_is_spell( receiver, "spell_scare" )
and not Attack.act_feature( receiver, "mind_immunitet" )
and not Attack.act_feature( receiver, "undead" ) then
table.insert( tmp_spells, spell_scare_attack )
end
.
.
.
This next section is starting to get a bit more advanced, and I'm not a complete expert in it, but here goes.
The local tmp_spells = {} statement initializes an LUA table called tmp_spells to contain some type of table data.
The if statement uses a King's Bounty library to check to see if the receiver allready has the Fear spell. Note that the code name for the Fear spell is used: spell_scare. Once again there are more checks to see if that spell can be cast on that unit and then the last statement table.insert inserts the name into the tmp_spells table. It is important to note here that the value inserted is an actual function name for the spell: spell_scare_attack. This is the actual function call for the Fear spell when you cast this spell from your spellbook.
I had a little bit of difficulty grasping this when I was learning LUA, but you can see it is very powerful in that you can create a list of functions you want to call or a list of possible functions that you want to call.
The next code sections are checking for similar spells with their appropriate function names, and you'll note the last one is not a spell but an effect:
.
.
.
if not Attack.act_is_spell( receiver, "effect_curse" )
and not Attack.act_feature( receiver, "plant" )
and not Attack.act_feature( receiver, "undead" ) then
table.insert( tmp_spells, effect_curse_attack )
end
.
.
.
This last one is an effect, but note that it behaves the same way as the spell checks.
The next section of code:
.
.
.
if table.getn( tmp_spells ) > 0 then
.
.
.
end
.
.
.
Checks to see that the tmp_spells function table was populated with at least one spell. It is quite possible that the receiving unit will not meet any of the criteria for the Bone Dragon's post spell attack. In this case, there is no else path so it would mean that if the unit is immune to all the Bone Dragon's spells then it won't get an effect applied to it.
Next is the code inside the check to select an effect to apply and then apply it:
.
.
.
Attack.act_aseq( 0, "cast" )
local dmgts = Attack.aseq_time( 0, "x" )
local cast = Game.Random( 1, table.getn( tmp_spells ) )
local spell_level = 3
.
.
.
The first part, Attack.act_aseq is a call to a King's Bounty library to setup an animation for the effect. There is a time shift, dmgts, for sequencing the event and then cast is used to randomly pick those effects that are valid for the target and lastly spell_level is used to cast that effect at that spell level. I'm not an expert in the animation effects (I mostly copy them and leave them as is) so you'll have to reference the King's Bounty Attack, act_aseq function and experiment with it. Note that the "cast" in act_aseq refers to an animation in the ATOM's animations {...} section and the "x" is what is known as a key for the animation to "key" off of. That's about the best way I can describe it.
The next section of code:
.
.
.
if tmp_spells[ cast ] == spell_plague_attack then
tmp_spells[ cast ]( receiver, spell_level, dmgts )
elseif tmp_spells[ cast ] == effect_curse_attack then
tmp_spells[ cast ]( receiver, 1, 3 )
else
tmp_spells[ cast ]( spell_level, dmgts, receiver )
end
.
.
.
Checks to see which spell or effect is to be cast. If it is either spell_plague_attack or effect_curse_attack the calling parameter list is different for those spells while all other spells have the same function call parameter list. This section is a little difficult to understand, so I'll do my best to explain it. Let me break this down into what this means more:
tmp_spells[ cast ] - in this case, cast is the index into the function table, tmp_spells. Note that since the table elements of tmp_spells are function calls, that this evaluates to a function call statement.
The next part of the statement includes a parameter list in (). So let's say that tmp_spells[ cast ] evaluates to spell_scare_attack. Then the function call would look like:
spell_scare_attack( spell_level, dmgts, receiver )
So this would execute a call to the spell_scare_attack function with the spell_level, dmgts, and receiver as the arguments to that function.
You can see here that this is very powerful in that you can make a table of function calls and call one randomly, or in sequence, etc. The important point to note is that you can distinguish from the various functions with an if statement and create a customized argument list for each function.
Think about this and let it sink in some more.
The next statement:
.
.
.
Attack.log_label( '' )
.
.
.
Is a call to the King's Bounty Attack, log_label function that in this case overrides the standard battle log you see at the bottom of the screen. You can also override the default log entry with your own custom log here if you wanted.
The last portion of code in this function is:
.
.
.
end
end
end
return damage, addrage
end
.
.
.
You'll note that there are the corresponding end statements to go with their ifs and the return statement is pretty standard in most langauges, but allows the function to return the parameters stated. Note that LUA allows any number of return values (not limited to 1 like C/C++). Finally the last end ends the function, features_bonedragon_attack.
Okay, this is probably a lot to digest, but let it sink in a bit and re-read it if you'd like before continuing.
Normally at this point you'd probably be complete, but in this case we still have a little more work to do. Note that we're calling some spells from within this function. So we need to ensure that the calls will work for the spells we're trying to call.
So for the next file we need to edit is to find where such functions as spell_scare_attack, etc. exist and make sure that we have a compatible argument list and see if the spell will work as is.
The function spell_scare_attack (and pretty much all spell function calls) is located in SPELLS.LUA. Let's go to the code for the spell_scare_attack:
.
.
.
-- ***********************************************
-- * Scare
-- ***********************************************
function spell_scare_attack( lvl, dmgts )
if dmgts == nil then dmgts = 0 end
local target = Attack.get_target()
if ( target ~= nil ) then
local level = common_get_spell_level( lvl )
local ehero_level
if Attack.act_belligerent() == 4 then
ehero_level, level = get_enemy_hero_stuff( level )
end
local duration = int_dur( "spell_scare", level, "sp_duration_scare" )
Attack.act_del_spell( target, "effect_fear" )
Attack.act_apply_spell_begin( target, "effect_fear", duration, false )
Attack.act_apply_par_spell("autofight", 1, 0, 0, duration, false)
Attack.act_apply_spell_end()
Attack.atom_spawn(target, 0, "magic_scare", Attack.angleto(target))
Attack.act_damage_addlog(target,"add_blog_fear_")
end
return true
end
.
.
.
So this is the code for the spell_scare_attack function. One thing you'll note right away is that the argument list does not, have a receiver variable in its argument list.
There is also this statement:
local target = Attack.get_target()
So this code uses the King's Bounty libary Attack get_target function to get the target. Unfortunately, as I learned as I was debugging this function, this statement will actually get the Attacker as the target! This is not our intended effect, so what we need to do is change the code as such:
.
.
.
function spell_scare_attack( lvl, dmgts, target )
if dmgts == nil then dmgts = 0 end
if target == nil then target = Attack.get_target() end.
.
.
Note that we've added a new argument to spell_scare_attack's argument list: target. Note that this name does not have to be the same as the statement with the function call, but like standard function calls for most languages work, arguments are assigned in order (spell_level -> lvl, dmgts -> dmgts, and receiver -> target).
Note how we changed the local target... statement. We now check if target has been assigned a value (nil is a special LUA value for checking if the variable has been initialized) and then only set it to Attack.get_target() if it hasn't.
What you'll notice here is that by adding the new parameter, target, to the end of the function argument list, we're not changing any other function calls that used only the first two arguments (lvl and dmgts) and by checking if target is not assigned before assigning it to Attack.get_target() that all those other function call statements throughout all the other LUA files will not be affected.
This is a pretty advanced concept and I want you to think about it for a bit. The spell_scare_attack function may have other calls and we want to ensure that we don't "break" it by our changes. The changes above allow the existing function to work with our new features_bonedragon_attack function call while maintaining its ability to work with all previous function calls.
In some cases, it may not be possible to alter an existing function call to work with a new way of calling it. In that case you'd have to make a new function (based on the old one preferably) to handle the different way that you want to use it.
So you just have to be careful (and I've gone through this a lot) to make sure that you don't break anything existing when changing it. You'll notice that if you go through the AP / CW LUA files that they simply created new functions for the Witch Hunter positive spell effects rather than use existing ones and try to alter them.
I prefer to try to use the existing one whenever possible since it eliminates redundant code which may be changed in one place but not the other if you decide to change how a function works; however, from a validated code viewpoint (for quality assurance) you may not want to change code that you've already validated because then you have to re-validate it.
The rest of the code, I'm going to leave as an exercise for you to study the LUA documentation and King's Bounty libraries to figure out how it works.
The last file we need to change goes back to the changes we made to the ATOM file. Remember that we added new features_hints= values? We now need to add what those descriptions are. For unit features, they are located in ENG_UNIT_FEATURES.LNG (or your equivalent local language file).
Here is a code snippet:
.
.
.
cpi_vampire_feat=Undead, Regeneraion, No retaliation.
cpi_vampire2_feat=Undead, Regeneraion, No retaliation, Death's Deception.
cpi_bat_feat=Undead, Soaring, No retaliation, Drain Life.
cpi_zombie_feat=Undead.
cpi_zombie2_feat=Undead, Decay.
cpi_archer_feat=Undead, Archer, Bone.
cpi_skeleton_feat=Undead, Bone.
cpi_bonedragon_feat=Undead, Bone, Flight.
cpi_ghost_feat=Undead, Soaring, Phantom, Soul Draining.
cpi_ghost2_feat=Undead, Soaring, Phantom, Soul Draining.
cpi_blackknight_feat=Undead, Steel Armor, Dark Commander, Rising Anger.
cpi_necromant_feat=Undead, Cloud of Darkness.
.
.
.
You'll notice that the developers nicely organized all the Undead units together so that they are easy to find. Note that these are the codes for the features for a specific unit and that the unit name is the same as its ATOM file name. We need to find the one for Bone Dragons - here it is!
.
.
.
cpi_bonedragon_feat=Undead, Bone, Flight.
.
.
.
You'll note that these are the exact names of the features that appear on the unit card. So we need to think of what we want to call this new Bone Dragon ability and what the player should see when they pull up the unit card for that unit. Since this attack adds a chance to cast a Chaos spell on the receiving unit, I'm simply going to call this new ability: Chaos.
So then add it to the comma-separated list of abilities like so:
.
.
.
cpi_bonedragon_feat=Undead, Bone, Flight, Chaos.
.
.
.
There we are! But we're not done, yet! We still need to add the header and hint from what we put in the ATOM file. These names must match the code name we used in the ATOM. You can pretty much put it anywhere you want, but I like to try to put similar abilities together and the developers have it loosely organized where most of the Undead abilities are together.
So here's a code snippet:
// ÃÂÃÂÃÂÃÂâì
dead_evasion_header=^def_hint_t0^Death's Deception
dead_evasion_hint=^def_hint_t1^Avoids critical attacks by vanishing in the shadows.
undead_header=^def_hint_t0^Undead
undead_hint=^def_hint_t1^Being raised from the dead has the following features:<br>50% Poison Protection<br>+50% Attack during nighttime and underground combat.<br>+1 to Morale if combat takes place in the cemetery.<br>Takes 200% of damage from Holy Attacks.<br>Immune to Mind spells and some other spells.
vampirism_header=^def_hint_t0^Drain Life
vampirism_hint=^def_hint_t1^Sucks a part of the health points from its enemy, recovering his own health. Dead creatures arise.
So you'll notice here that the format is as follows:
feature_name_header=^def_hint_t0^Header Name
feature_name_hint=^def_hint_t1^Description Hint
Where feature_name is the name of your feature. In our case it is called bonedragon_attack. The ^def_hint_t0^ and ^def_hint_t1^ are template names located inside TEMPLATES.LNG (note that this file name is the same for all localizations). Templates are codes that you can use for using a color, a label, calling a function to generate a value, etc.
When you see the various colors in the text descriptions, they are using colors specified in the template name. You'll also see where you may have a common description (i.e. like Defense: ) so you can make labels with common names and refer to their shorthand template name if it is identified in the template. Let's look at ^def_hint_t0^ for example in TEMPLATES.LNG:
.
.
.
def_hint_t0=<shadow=off><align=center><font=ft1_14><color=255,243,179>$
// ÓÌÅÍÈß
// Ïîäïèñü â îêíå ãåðîÿ
skills_tl=<align=center><color=0,0,0><font=ft1_12>$
skills_t0=<align=center><font=ft1_12><gen=skill_caption><gen=skill_name>
skills_tN=$
skills_tN {
closed=<color=20,147,182>
open=<color=255,243,179>
}
.
.
.
Note that def_hint_t0 has some formatting applied to its use and then it has templates:
skills_tl
skills_t0
skills_tN
You can use these templates in labeled items that use the ^def_hint_t0^ template. Note that skills_tN is also its own sub-template. I've never used these before, but I imagine their use is the same. To use a template, you can put brackets around it, for example: [skills_tl] and then that template will be replaced with whatever that code refers to. In this case:
skills_tl=<align=center><color=0,0,0><font=ft1_12>$
Which looks like it aligns text following it and sets it to a certain color and font.
This is just the tip of the iceberg and talking about templates is a whole other guide topic.
So let's get back to adding our Bone Dragon's Chaos ability header and hint:
.
.
.
// ÃÂÃÂÃÂÃÂâì
// New! Bone Dragon's Chaos
bonedragon_attack_header=^def_hint_t0^Chaos
bonedragon_attack_hint=^def_hint_t1^Casts a random Chaos Spell or Effect (Fear, Plague, Weakness, Doom, Sheep, or Curse) on the target (if applicable) when attacking. Chance to cast the spell is +25% for each level the unit is below the Bone Dragon's (i.e. level 1 = 100%, level 4 = 25%). The power of the spell is affected by the hero's spell power if the Bone Dragon is lead by one and casts the spell at third level.
dead_evasion_header=^def_hint_t0^Death's Deception
dead_evasion_hint=^def_hint_t1^Avoids critical attacks by vanishing in the shadows.
.
.
.
Note that any lines with //'s are comments. You can see here that I use the standard header and hint templates that other features use and I call my new ability the same name, Chaos, as you see in its ability list. I then describe what it does. Note that I'd really like to use generated values for +25% and for 100% and 25% in the description, but I have not figured out how to access the ATOM library from within unit feature templates. You can access the ATOM library from within unit special templates. If anyone knows how to do this then please let me know.
So that's it! You can now save and close ENG_UNITS_FEATURES.LNG and the other files that you edited and place them into your mods folder.
The next step is to debug your code by running King's Bounty in Development mode. There are plenty of posts that describe how to do that, but it is encouraged to always debug new code you create to help minimize errors and make sure it works!
In making the changes above, when I initially created the features_bone_dragon_attack function, I did not realize that I had to check to make sure that it was the Bone Dragon's main attack. So at first, the Bone Dragon's main attack and Poison Cloud would cause the effect to be applied. So to limit it to just the main attack I needed to add the check for poison == 0, which is in the Bone Dragon's custom parameters for its main attack.
Also note that you might want to specify the base percent chance to the ATOM and allow for people to change it. I actually may end up implementing it that way, but if you did you'd do this:
For BONEDRAGON.ATOM:
.
.
.
moveattack {
ad_factor=1
usefly=4
damage {
poison=50,65
}
custom_params {
poison=0
chance=20
}
}
.
.
.
Note that I added chance and I changed it from 25% to 20%.
Next UNIT_FEATURES.LUA:
local receiver_level = Attack.act_level( receiver )
local chance = tonumber( Attack.get_custom_param( "chance" ) )
local receiver_chance = ( 5 - receiver_level ) * chance
local rnd = Game.Random( 100 )
local poison = tonumber( Attack.get_custom_param( "poison" ) )
Note how receiver_chance is now computed from the base chance acquired from the ATOM's custom parameter, chance.
Lastly, ENG_UNITS_FEATURES.LNG:
.
.
.
// ÃÂÃÂÃÂÃÂâì
// New! Bone Dragon's Chaos
bonedragon_attack_header=^def_hint_t0^Chaos
bonedragon_attack_hint=^def_hint_t1^Casts a random Chaos Spell or Effect (Fear, Plague, Weakness, Doom, Sheep, or Curse) on the target (if applicable) when attacking. Chance to cast the spell is +20% for each level the unit is below the Bone Dragon's (i.e. level 1 = 80%, level 4 = 20%). The power of the spell is affected by the hero's spell power if the Bone Dragon is lead by one and casts the spell at third level.
.
.
.
I wish I new how to access the ATOM so that I could have the values in ENG_UNIT_FEATURES.LNG auto generated, but I don't know how. So you have to manually change them for now.
Then if in the future you decide to change it, you just have to change BONEDRAGON.ATOM and ENG_UNITS_FEATURES.LNG - both are text files, which are a little bit easier for people to edit and makes the change more mod friendly.
Please take a while to review what I've written here and I'm hoping to do more of these tutorials / guides as time permits.
Here are the references again that I mentioned above:
LUA Documentation (note that King's Bounty uses LUA version 5.0) (http://www.lua.org/manual/5.0/)
King's Bounty Libraries Translated to English (http://translate.googleusercontent.com/translate_c?hl=en&ie=UTF8&prev=_t&rurl=translate.google.com&sl=ru&tl=en&twu=1&u=http://kingsbounty.ru/docs/scripting/index.htm&usg=ALkJrhjNvc-2eVONGm3XcVb1S9FDIBT-8Q)
Feel free to ask any questions you'd like if you don't understand anything about this post - and good luck!:grin:
/C\/C\
So I thought this would be a great opportunity to provide a mini LUA scripting tutorial about how to add the capability of a post-hit affect to a unit.
Note that this post is very long and you are encouraged to take your time reading it and visit the references provided here and duplicated at the end of this post:
LUA Documentation (note that King's Bounty uses LUA version 5.0) (http://www.lua.org/manual/5.0/)
King's Bounty Libraries Translated to English (http://translate.googleusercontent.com/translate_c?hl=en&ie=UTF8&prev=_t&rurl=translate.google.com&sl=ru&tl=en&twu=1&u=http://kingsbounty.ru/docs/scripting/index.htm&usg=ALkJrhjNvc-2eVONGm3XcVb1S9FDIBT-8Q)
So let's clearly state what we want to do:
When a Bone Dragon attacks, we'd like to have it apply a post-hit affect to the unit it is attacking.
If the post-hit affect is successful, then it is to apply either:
Fear
Plague
Weakness
Doom
Sheep
Curse
In order to be able to do this, we need the following files (all files are typed in CAPS to distinguish them):
BONEDRAGON.ATOM - located in DATA.KFS
UNIT_FEATURES.LUA - located in SES.KFS
SPELLS.LUA - located in SES.KFS
ENG_UNITS_FEATURES.LNG (or your equivalent language file) - located in LOC_SES.KFS
The first file to edit is BONEDRAGON.ATOM:
main {
class=chesspiece
receiver=bones
model {
0=bonedragon.bma
1=bonedragondeath.bma
}
cullcat=0
bboxanim=1
}
arena_params {
features_label=cpi_bonedragon_feat
features_hints=undead_header/undead_hint,bone_header/bone_hint,flies_header/flies_hint
posthitslave=features_bone_creature
race=undead
cost=3200
level=5
leadership=1300
attack=43
defense=43
defenseup=6
initiative=6
speed=7
hitpoint=600
krit=10
hitback=1
hitbackprotect=0
movetype=2
attacks=moveattack,poison_cloud
features=bone,undead,dragon
resistances {
physical=0
poison=50
magic=0
fire=0
}
moveattack {
ad_factor=1
usefly=4
damage {
poison=50,65
}
custom_params {
poison=0
}
}
.
.
.
(file continues...)
Just a brief discussion of the sections in this file (this is a guide topic in itself):
main {...} - in this section you can see that you identify the class of the ATOM and associated models to go with it.
arena_params {...} - this is the section that we're going to focus on where it has the ATOM's statistics, attacks, abilities, etc.
scripts {...} - I have not edited this part of an ATOM before, but I think these control its behavior on the map.
animations {...} - these are where you can define new animations for an ATOM or change existing ones.
editor {...} - I have not used this section either, so I'm not sure what it does.
attachments {...} - this is where you can create neat graphics effects for the ATOM's abilities.
prefetch {...} - I have not used this section either, but I think it simply lists the animation files that the ATOM is going to use and must place them in a cache or something like that.
The area we're going to focus on is the arena_params {...} section.
We basically only need to do a couple of things to this file to enable a new post-attack ability:
Add: posthitmaster=script_name
Add: new features header and hint to the features_hints= item so that people can see the new ability.
For the file above, look at the start of the arena_params {...} section and you'll see where the features_hints= item starts (repeated here):
.
.
.
arena_params {
features_label=cpi_bonedragon_feat
features_hints=undead_header/undead_hint,bone_header/bone_hint,flies_header/flies_hint
posthitslave=features_bone_creature
.
.
.
Select a name for your new ability - this is the "code" name for the ability and is internal; people playing the game will not see this name, but it will be the name you use when adding it to the various other files you need to change. I decided to call the new ability: bonedragon_attack. This name will have appended to it the suffixes: _header and _hint to describe what this new feature does. So we can add this to the comma-separated list of features_hints that the unit uses:
.
.
.
arena_params {
features_label=cpi_bonedragon_feat
features_hints=undead_header/undead_hint,bone_header/bone_hint,flies_header/flies_hint,bonedragon_attack_header/bonedragon_attack_hint
posthitslave=features_bone_creature
.
.
.
Note that we have added "bonedragon_attack_header/bonedragon_attack_hint" to the end of the features_hints= list. I'll explain what both of these are a little bit later. This is the format that you use, though, when adding a new feature that you want displayed in the unit's feature list for its unit card.
The next thing we need to do is add the capability of the unit to be able to call a script when performing its attack. To enable this ability, we need to add a posthitmaster=script_name to the arena_params {...} section. I'm not sure if it matters where you add this so long as it is in the arena_params {...} section, but I usually add it just before the resistances section since that's where I've seen it mostly placed (you can always search on posthitmaster= to see if a script already exists for that ATOM with your text editor).
Once again, you need to think of what you want to call your script, once again, this is an internal name that you are going to use. I'm just going to call my new script: features_bonedragon_attack. Here is the implementation in the ATOM:
.
.
.
features=bone,undead,dragon
posthitmaster=features_bonedragon_attack
resistances {
physical=0
poison=50
magic=0
fire=0
}
.
.
.
This is all we need to do to the ATOM! We can save and close this file.
The next part we need to work on is the script. You could create a new LUA file with your new script in it if you wish or edit an existing file. In the case here, most of the unit features are included in the UNIT_FEATURES.LUA script. So we're going to add the posthitmaster script to this file, the name of the function must be the same name as script_name identified with posthitmaster= - in this case the function name must be called features_bonedragon_attack.
This function is shown in the code block below and I'll go through each section. I added this to the top of UNIT_FEATURES.LUA, but you can add it anywhere to that file so long as it is outside any other function block:
-- New function for adding a bonus to % of chance to inflict an effect
function effect_chance( value, effect_or_feature, kind )
local bonus = tonumber( Logic.hero_lu_item( "sp_chance_" .. effect_or_feature .. "_" .. kind, "count" ) )
if bonus ~= nil then
value = value + bonus
end
return value
end
-- New Bone Dragon Attack
function features_bonedragon_attack( damage, addrage, attacker, receiver, minmax )
if ( minmax == 0 ) then
local receiver_level = Attack.act_level( receiver )
local receiver_chance = ( 5 - receiver_level ) * 25
local rnd = Game.Random( 100 )
local poison = tonumber( Attack.get_custom_param( "poison" ) )
if rnd < receiver_chance
and damage > 0
and poison == 0
and not Attack.act_feature( receiver, "magic_immunitet" )
and not Attack.act_feature( receiver, "golem" )
and not Attack.act_feature( receiver, "pawn" )
and not Attack.act_feature( receiver, "boss" ) then
local tmp_spells = {}
if not Attack.act_is_spell( receiver, "spell_scare" )
and not Attack.act_feature( receiver, "mind_immunitet" )
and not Attack.act_feature( receiver, "undead" ) then
table.insert( tmp_spells, spell_scare_attack )
end
if not Attack.act_is_spell( receiver, "spell_plague" )
and not Attack.act_feature( receiver, "demon" )
and not Attack.act_feature( receiver, "plant" ) then
table.insert( tmp_spells, spell_plague_attack )
end
if not Attack.act_is_spell( receiver, "spell_weakness" )
and not Attack.act_feature( receiver, "plant" )
and not Attack.act_feature( receiver, "undead" ) then
table.insert( tmp_spells, spell_weakness_attack )
end
if not Attack.act_is_spell( receiver, "spell_crue_fate" )
and Attack.act_name( receiver ) ~= "vampire2" then
table.insert( tmp_spells, spell_crue_fate_attack )
end
if not Attack.act_is_spell( receiver, "spell_ram" )
and not Attack.act_feature( receiver, "plant" )
and not Attack.act_feature( receiver, "undead" ) then
table.insert( tmp_spells, spell_ram_attack )
end
if not Attack.act_is_spell( receiver, "effect_curse" )
and not Attack.act_feature( receiver, "plant" )
and not Attack.act_feature( receiver, "undead" ) then
table.insert( tmp_spells, effect_curse_attack )
end
if table.getn( tmp_spells ) > 0 then
Attack.act_aseq( 0, "cast" )
local dmgts = Attack.aseq_time( 0, "x" )
local cast = Game.Random( 1, table.getn( tmp_spells ) )
local spell_level = 3
if tmp_spells[ cast ] == spell_plague_attack then
tmp_spells[ cast ]( receiver, spell_level, dmgts )
elseif tmp_spells[ cast ] == effect_curse_attack then
tmp_spells[ cast ]( receiver, 1, 3 )
else
tmp_spells[ cast ]( spell_level, dmgts, receiver )
end
Attack.log_label( '' )
end
end
end
return damage, addrage
end
-- New Ent Entangle
function features_entangle( damage, addrage, attacker, receiver, minmax )
.
.
.
(file continues)
You can see here that I've included the previous function and the start of the next function to show you that the function for the new posthitmaster script is outside any other functions. Let's focus on the new function: features_bonedragon_attack.
-- New Bone Dragon Attack
function features_bonedragon_attack( damage, addrage, attacker, receiver, minmax )
.
.
.
Reference the LUA documentation for more specifics, but essentially you use the keyword function to identify a new function name and then the variables in () are the parameter list that the function uses. For posthitmaster scripts, there is a standard argument list. You'll need to look at an existing posthitmaster function identified in the various ATOM's for units that have an effect applied after their attack (for example, Werewolf Elves Bleeding) to see what the argument list is, then you can copy it and use it as a template for creating new functions. That is all I did when I first started to learn this stuff was to look at existing functions as an example and use them as a template for a new function that I wanted to create.
For this function, the argument list is:
damage - how much damage the attack has done
addrage - how much rage to add to the hero
attacker - the attacker unit ID from the C/C++ code
receiver - the receiver unit ID from the C/C++ code
minmax - this is an integer used to identify whether this was the attack that was just performed
Continuing with the code:
.
.
.
if ( minmax == 0 ) then
.
.
.
end
You'll notice that there is an if statement that checks minmax. Note that all if statements must have a matching end statement to terminate their code block. If minmax is 0 then it means that this was the attack that was just executed. You need to check this to make sure that it is not another attack script being executed (a unit could possibly have more than one posthitmaster script applied to it).
Let's continue:
.
.
.
local receiver_level = Attack.act_level( receiver )
local receiver_chance = ( 5 - receiver_level ) * 25
local rnd = Game.Random( 100 )
local poison = tonumber( Attack.get_custom_param( "poison" ) )
.
.
.
The keyword local is used to identify local variables that this function uses. If you omit the local keyword (I sometimes forget to use the local keyword) then the variable will be global and accessible from any other LUA function in the game. So try to remember to use the local keyword when you don't want the variable to be available outside this function (and the memory it uses to be freed up).
You can see here that this code block gets the level of the receiving unit (reference the King's Bounty library) and computes the chance of the effect based on a formula. Next it uses another King's Bounty library to generate a random number and lastly gets a custom parameter from the attacking unit's ATOM file.
Next is another if statement to check if the unit is to have the posthitmaster effect applied:
.
.
.
if rnd < receiver_chance
and damage > 0
and poison == 0
and not Attack.act_feature( receiver, "magic_immunitet" )
and not Attack.act_feature( receiver, "golem" )
and not Attack.act_feature( receiver, "pawn" )
and not Attack.act_feature( receiver, "boss" ) then
.
.
.
end
The first part of the if statement determines if the effect has occured due to random chance. You then note that you can apply as many additional conditions as you'd like to check to see if the effect should be applied. Note that there is a poison == 0 check in the list. Reference back to the ATOM above and note that for the Bone Dragon's moveattack {...} section there is a custom_params {...} section where you see that poison=0. This is used to identify that it is the Bone Dragon's main attack that is being used, but another attack (like it's poison_cloud) also executes the posthitmaster script and so you need a way to distinguish which attack it is. It is important to note that all of a unit's attacks (including all its special attacking abilities) will use this posthitmaster script. So you need a way of distinguishing between them if you want the script to only apply in certain situations.
All the other checks have to do with checking to make sure that the receiving unit does not have a certain feature.
Let's continue with the next code section:
.
.
.
local tmp_spells = {}
if not Attack.act_is_spell( receiver, "spell_scare" )
and not Attack.act_feature( receiver, "mind_immunitet" )
and not Attack.act_feature( receiver, "undead" ) then
table.insert( tmp_spells, spell_scare_attack )
end
.
.
.
This next section is starting to get a bit more advanced, and I'm not a complete expert in it, but here goes.
The local tmp_spells = {} statement initializes an LUA table called tmp_spells to contain some type of table data.
The if statement uses a King's Bounty library to check to see if the receiver allready has the Fear spell. Note that the code name for the Fear spell is used: spell_scare. Once again there are more checks to see if that spell can be cast on that unit and then the last statement table.insert inserts the name into the tmp_spells table. It is important to note here that the value inserted is an actual function name for the spell: spell_scare_attack. This is the actual function call for the Fear spell when you cast this spell from your spellbook.
I had a little bit of difficulty grasping this when I was learning LUA, but you can see it is very powerful in that you can create a list of functions you want to call or a list of possible functions that you want to call.
The next code sections are checking for similar spells with their appropriate function names, and you'll note the last one is not a spell but an effect:
.
.
.
if not Attack.act_is_spell( receiver, "effect_curse" )
and not Attack.act_feature( receiver, "plant" )
and not Attack.act_feature( receiver, "undead" ) then
table.insert( tmp_spells, effect_curse_attack )
end
.
.
.
This last one is an effect, but note that it behaves the same way as the spell checks.
The next section of code:
.
.
.
if table.getn( tmp_spells ) > 0 then
.
.
.
end
.
.
.
Checks to see that the tmp_spells function table was populated with at least one spell. It is quite possible that the receiving unit will not meet any of the criteria for the Bone Dragon's post spell attack. In this case, there is no else path so it would mean that if the unit is immune to all the Bone Dragon's spells then it won't get an effect applied to it.
Next is the code inside the check to select an effect to apply and then apply it:
.
.
.
Attack.act_aseq( 0, "cast" )
local dmgts = Attack.aseq_time( 0, "x" )
local cast = Game.Random( 1, table.getn( tmp_spells ) )
local spell_level = 3
.
.
.
The first part, Attack.act_aseq is a call to a King's Bounty library to setup an animation for the effect. There is a time shift, dmgts, for sequencing the event and then cast is used to randomly pick those effects that are valid for the target and lastly spell_level is used to cast that effect at that spell level. I'm not an expert in the animation effects (I mostly copy them and leave them as is) so you'll have to reference the King's Bounty Attack, act_aseq function and experiment with it. Note that the "cast" in act_aseq refers to an animation in the ATOM's animations {...} section and the "x" is what is known as a key for the animation to "key" off of. That's about the best way I can describe it.
The next section of code:
.
.
.
if tmp_spells[ cast ] == spell_plague_attack then
tmp_spells[ cast ]( receiver, spell_level, dmgts )
elseif tmp_spells[ cast ] == effect_curse_attack then
tmp_spells[ cast ]( receiver, 1, 3 )
else
tmp_spells[ cast ]( spell_level, dmgts, receiver )
end
.
.
.
Checks to see which spell or effect is to be cast. If it is either spell_plague_attack or effect_curse_attack the calling parameter list is different for those spells while all other spells have the same function call parameter list. This section is a little difficult to understand, so I'll do my best to explain it. Let me break this down into what this means more:
tmp_spells[ cast ] - in this case, cast is the index into the function table, tmp_spells. Note that since the table elements of tmp_spells are function calls, that this evaluates to a function call statement.
The next part of the statement includes a parameter list in (). So let's say that tmp_spells[ cast ] evaluates to spell_scare_attack. Then the function call would look like:
spell_scare_attack( spell_level, dmgts, receiver )
So this would execute a call to the spell_scare_attack function with the spell_level, dmgts, and receiver as the arguments to that function.
You can see here that this is very powerful in that you can make a table of function calls and call one randomly, or in sequence, etc. The important point to note is that you can distinguish from the various functions with an if statement and create a customized argument list for each function.
Think about this and let it sink in some more.
The next statement:
.
.
.
Attack.log_label( '' )
.
.
.
Is a call to the King's Bounty Attack, log_label function that in this case overrides the standard battle log you see at the bottom of the screen. You can also override the default log entry with your own custom log here if you wanted.
The last portion of code in this function is:
.
.
.
end
end
end
return damage, addrage
end
.
.
.
You'll note that there are the corresponding end statements to go with their ifs and the return statement is pretty standard in most langauges, but allows the function to return the parameters stated. Note that LUA allows any number of return values (not limited to 1 like C/C++). Finally the last end ends the function, features_bonedragon_attack.
Okay, this is probably a lot to digest, but let it sink in a bit and re-read it if you'd like before continuing.
Normally at this point you'd probably be complete, but in this case we still have a little more work to do. Note that we're calling some spells from within this function. So we need to ensure that the calls will work for the spells we're trying to call.
So for the next file we need to edit is to find where such functions as spell_scare_attack, etc. exist and make sure that we have a compatible argument list and see if the spell will work as is.
The function spell_scare_attack (and pretty much all spell function calls) is located in SPELLS.LUA. Let's go to the code for the spell_scare_attack:
.
.
.
-- ***********************************************
-- * Scare
-- ***********************************************
function spell_scare_attack( lvl, dmgts )
if dmgts == nil then dmgts = 0 end
local target = Attack.get_target()
if ( target ~= nil ) then
local level = common_get_spell_level( lvl )
local ehero_level
if Attack.act_belligerent() == 4 then
ehero_level, level = get_enemy_hero_stuff( level )
end
local duration = int_dur( "spell_scare", level, "sp_duration_scare" )
Attack.act_del_spell( target, "effect_fear" )
Attack.act_apply_spell_begin( target, "effect_fear", duration, false )
Attack.act_apply_par_spell("autofight", 1, 0, 0, duration, false)
Attack.act_apply_spell_end()
Attack.atom_spawn(target, 0, "magic_scare", Attack.angleto(target))
Attack.act_damage_addlog(target,"add_blog_fear_")
end
return true
end
.
.
.
So this is the code for the spell_scare_attack function. One thing you'll note right away is that the argument list does not, have a receiver variable in its argument list.
There is also this statement:
local target = Attack.get_target()
So this code uses the King's Bounty libary Attack get_target function to get the target. Unfortunately, as I learned as I was debugging this function, this statement will actually get the Attacker as the target! This is not our intended effect, so what we need to do is change the code as such:
.
.
.
function spell_scare_attack( lvl, dmgts, target )
if dmgts == nil then dmgts = 0 end
if target == nil then target = Attack.get_target() end.
.
.
Note that we've added a new argument to spell_scare_attack's argument list: target. Note that this name does not have to be the same as the statement with the function call, but like standard function calls for most languages work, arguments are assigned in order (spell_level -> lvl, dmgts -> dmgts, and receiver -> target).
Note how we changed the local target... statement. We now check if target has been assigned a value (nil is a special LUA value for checking if the variable has been initialized) and then only set it to Attack.get_target() if it hasn't.
What you'll notice here is that by adding the new parameter, target, to the end of the function argument list, we're not changing any other function calls that used only the first two arguments (lvl and dmgts) and by checking if target is not assigned before assigning it to Attack.get_target() that all those other function call statements throughout all the other LUA files will not be affected.
This is a pretty advanced concept and I want you to think about it for a bit. The spell_scare_attack function may have other calls and we want to ensure that we don't "break" it by our changes. The changes above allow the existing function to work with our new features_bonedragon_attack function call while maintaining its ability to work with all previous function calls.
In some cases, it may not be possible to alter an existing function call to work with a new way of calling it. In that case you'd have to make a new function (based on the old one preferably) to handle the different way that you want to use it.
So you just have to be careful (and I've gone through this a lot) to make sure that you don't break anything existing when changing it. You'll notice that if you go through the AP / CW LUA files that they simply created new functions for the Witch Hunter positive spell effects rather than use existing ones and try to alter them.
I prefer to try to use the existing one whenever possible since it eliminates redundant code which may be changed in one place but not the other if you decide to change how a function works; however, from a validated code viewpoint (for quality assurance) you may not want to change code that you've already validated because then you have to re-validate it.
The rest of the code, I'm going to leave as an exercise for you to study the LUA documentation and King's Bounty libraries to figure out how it works.
The last file we need to change goes back to the changes we made to the ATOM file. Remember that we added new features_hints= values? We now need to add what those descriptions are. For unit features, they are located in ENG_UNIT_FEATURES.LNG (or your equivalent local language file).
Here is a code snippet:
.
.
.
cpi_vampire_feat=Undead, Regeneraion, No retaliation.
cpi_vampire2_feat=Undead, Regeneraion, No retaliation, Death's Deception.
cpi_bat_feat=Undead, Soaring, No retaliation, Drain Life.
cpi_zombie_feat=Undead.
cpi_zombie2_feat=Undead, Decay.
cpi_archer_feat=Undead, Archer, Bone.
cpi_skeleton_feat=Undead, Bone.
cpi_bonedragon_feat=Undead, Bone, Flight.
cpi_ghost_feat=Undead, Soaring, Phantom, Soul Draining.
cpi_ghost2_feat=Undead, Soaring, Phantom, Soul Draining.
cpi_blackknight_feat=Undead, Steel Armor, Dark Commander, Rising Anger.
cpi_necromant_feat=Undead, Cloud of Darkness.
.
.
.
You'll notice that the developers nicely organized all the Undead units together so that they are easy to find. Note that these are the codes for the features for a specific unit and that the unit name is the same as its ATOM file name. We need to find the one for Bone Dragons - here it is!
.
.
.
cpi_bonedragon_feat=Undead, Bone, Flight.
.
.
.
You'll note that these are the exact names of the features that appear on the unit card. So we need to think of what we want to call this new Bone Dragon ability and what the player should see when they pull up the unit card for that unit. Since this attack adds a chance to cast a Chaos spell on the receiving unit, I'm simply going to call this new ability: Chaos.
So then add it to the comma-separated list of abilities like so:
.
.
.
cpi_bonedragon_feat=Undead, Bone, Flight, Chaos.
.
.
.
There we are! But we're not done, yet! We still need to add the header and hint from what we put in the ATOM file. These names must match the code name we used in the ATOM. You can pretty much put it anywhere you want, but I like to try to put similar abilities together and the developers have it loosely organized where most of the Undead abilities are together.
So here's a code snippet:
// ÃÂÃÂÃÂÃÂâì
dead_evasion_header=^def_hint_t0^Death's Deception
dead_evasion_hint=^def_hint_t1^Avoids critical attacks by vanishing in the shadows.
undead_header=^def_hint_t0^Undead
undead_hint=^def_hint_t1^Being raised from the dead has the following features:<br>50% Poison Protection<br>+50% Attack during nighttime and underground combat.<br>+1 to Morale if combat takes place in the cemetery.<br>Takes 200% of damage from Holy Attacks.<br>Immune to Mind spells and some other spells.
vampirism_header=^def_hint_t0^Drain Life
vampirism_hint=^def_hint_t1^Sucks a part of the health points from its enemy, recovering his own health. Dead creatures arise.
So you'll notice here that the format is as follows:
feature_name_header=^def_hint_t0^Header Name
feature_name_hint=^def_hint_t1^Description Hint
Where feature_name is the name of your feature. In our case it is called bonedragon_attack. The ^def_hint_t0^ and ^def_hint_t1^ are template names located inside TEMPLATES.LNG (note that this file name is the same for all localizations). Templates are codes that you can use for using a color, a label, calling a function to generate a value, etc.
When you see the various colors in the text descriptions, they are using colors specified in the template name. You'll also see where you may have a common description (i.e. like Defense: ) so you can make labels with common names and refer to their shorthand template name if it is identified in the template. Let's look at ^def_hint_t0^ for example in TEMPLATES.LNG:
.
.
.
def_hint_t0=<shadow=off><align=center><font=ft1_14><color=255,243,179>$
// ÓÌÅÍÈß
// Ïîäïèñü â îêíå ãåðîÿ
skills_tl=<align=center><color=0,0,0><font=ft1_12>$
skills_t0=<align=center><font=ft1_12><gen=skill_caption><gen=skill_name>
skills_tN=$
skills_tN {
closed=<color=20,147,182>
open=<color=255,243,179>
}
.
.
.
Note that def_hint_t0 has some formatting applied to its use and then it has templates:
skills_tl
skills_t0
skills_tN
You can use these templates in labeled items that use the ^def_hint_t0^ template. Note that skills_tN is also its own sub-template. I've never used these before, but I imagine their use is the same. To use a template, you can put brackets around it, for example: [skills_tl] and then that template will be replaced with whatever that code refers to. In this case:
skills_tl=<align=center><color=0,0,0><font=ft1_12>$
Which looks like it aligns text following it and sets it to a certain color and font.
This is just the tip of the iceberg and talking about templates is a whole other guide topic.
So let's get back to adding our Bone Dragon's Chaos ability header and hint:
.
.
.
// ÃÂÃÂÃÂÃÂâì
// New! Bone Dragon's Chaos
bonedragon_attack_header=^def_hint_t0^Chaos
bonedragon_attack_hint=^def_hint_t1^Casts a random Chaos Spell or Effect (Fear, Plague, Weakness, Doom, Sheep, or Curse) on the target (if applicable) when attacking. Chance to cast the spell is +25% for each level the unit is below the Bone Dragon's (i.e. level 1 = 100%, level 4 = 25%). The power of the spell is affected by the hero's spell power if the Bone Dragon is lead by one and casts the spell at third level.
dead_evasion_header=^def_hint_t0^Death's Deception
dead_evasion_hint=^def_hint_t1^Avoids critical attacks by vanishing in the shadows.
.
.
.
Note that any lines with //'s are comments. You can see here that I use the standard header and hint templates that other features use and I call my new ability the same name, Chaos, as you see in its ability list. I then describe what it does. Note that I'd really like to use generated values for +25% and for 100% and 25% in the description, but I have not figured out how to access the ATOM library from within unit feature templates. You can access the ATOM library from within unit special templates. If anyone knows how to do this then please let me know.
So that's it! You can now save and close ENG_UNITS_FEATURES.LNG and the other files that you edited and place them into your mods folder.
The next step is to debug your code by running King's Bounty in Development mode. There are plenty of posts that describe how to do that, but it is encouraged to always debug new code you create to help minimize errors and make sure it works!
In making the changes above, when I initially created the features_bone_dragon_attack function, I did not realize that I had to check to make sure that it was the Bone Dragon's main attack. So at first, the Bone Dragon's main attack and Poison Cloud would cause the effect to be applied. So to limit it to just the main attack I needed to add the check for poison == 0, which is in the Bone Dragon's custom parameters for its main attack.
Also note that you might want to specify the base percent chance to the ATOM and allow for people to change it. I actually may end up implementing it that way, but if you did you'd do this:
For BONEDRAGON.ATOM:
.
.
.
moveattack {
ad_factor=1
usefly=4
damage {
poison=50,65
}
custom_params {
poison=0
chance=20
}
}
.
.
.
Note that I added chance and I changed it from 25% to 20%.
Next UNIT_FEATURES.LUA:
local receiver_level = Attack.act_level( receiver )
local chance = tonumber( Attack.get_custom_param( "chance" ) )
local receiver_chance = ( 5 - receiver_level ) * chance
local rnd = Game.Random( 100 )
local poison = tonumber( Attack.get_custom_param( "poison" ) )
Note how receiver_chance is now computed from the base chance acquired from the ATOM's custom parameter, chance.
Lastly, ENG_UNITS_FEATURES.LNG:
.
.
.
// ÃÂÃÂÃÂÃÂâì
// New! Bone Dragon's Chaos
bonedragon_attack_header=^def_hint_t0^Chaos
bonedragon_attack_hint=^def_hint_t1^Casts a random Chaos Spell or Effect (Fear, Plague, Weakness, Doom, Sheep, or Curse) on the target (if applicable) when attacking. Chance to cast the spell is +20% for each level the unit is below the Bone Dragon's (i.e. level 1 = 80%, level 4 = 20%). The power of the spell is affected by the hero's spell power if the Bone Dragon is lead by one and casts the spell at third level.
.
.
.
I wish I new how to access the ATOM so that I could have the values in ENG_UNIT_FEATURES.LNG auto generated, but I don't know how. So you have to manually change them for now.
Then if in the future you decide to change it, you just have to change BONEDRAGON.ATOM and ENG_UNITS_FEATURES.LNG - both are text files, which are a little bit easier for people to edit and makes the change more mod friendly.
Please take a while to review what I've written here and I'm hoping to do more of these tutorials / guides as time permits.
Here are the references again that I mentioned above:
LUA Documentation (note that King's Bounty uses LUA version 5.0) (http://www.lua.org/manual/5.0/)
King's Bounty Libraries Translated to English (http://translate.googleusercontent.com/translate_c?hl=en&ie=UTF8&prev=_t&rurl=translate.google.com&sl=ru&tl=en&twu=1&u=http://kingsbounty.ru/docs/scripting/index.htm&usg=ALkJrhjNvc-2eVONGm3XcVb1S9FDIBT-8Q)
Feel free to ask any questions you'd like if you don't understand anything about this post - and good luck!:grin:
/C\/C\