Fulqrum Publishing Home   |   Register   |   Today Posts   |   Members   |   UserCP   |   Calendar   |   Search   |   FAQ

Go Back   Official Fulqrum Publishing forum > Fulqrum Publishing > King's Bounty > King's Bounty: The Legend > Mods

Mods Everything about mods

Reply
 
Thread Tools Display Modes
  #1  
Old 12-03-2011, 11:31 PM
MattCaspermeyer MattCaspermeyer is offline
Approved Member
 
Join Date: Aug 2010
Posts: 553
Default Modding Tutorial - Adding an Ability to Apply after a Unit's Attack

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)
King's Bounty Libraries Translated to English

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:

Code:
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):

Code:
.
.
.
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:

Code:
.
.
.
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:

Code:
.
.
.
  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:

Code:
-- 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.

Code:
-- 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:

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:

Code:
.
.
.
    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:

Code:
.
.
.
    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:

Code:
.
.
.
      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
Code:
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:

Code:
.
.
.
      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:

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:

Code:
.
.
.
        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,
Code:
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:

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:

Code:
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
Code:
tmp_spells[ cast ]
evaluates to spell_scare_attack. Then the function call would look like:

Code:
          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:

Code:
.
.
.
        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:

Code:
.
.
.
      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:

Code:
.
.
.
-- ***********************************************
-- * 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:

Code:
  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:

Code:
.
.
.
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
Code:
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
Code:
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
Code:
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:

Code:
.
.
.
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!

Code:
.
.
.
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:

Code:
.
.
.
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:

Code:
// НЕЖИТЬ
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:

Code:
.
.
.
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:

Code:
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:

Code:
.
.
.
// НЕЖИТЬ
// 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:

Code:
.
.
.
  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:

Code:
    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:

Code:
.
.
.
// НЕЖИТЬ
// 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)
King's Bounty Libraries Translated to English

Feel free to ask any questions you'd like if you don't understand anything about this post - and good luck!

/C\/C\
Reply With Quote
  #2  
Old 11-18-2012, 01:09 PM
jukeey jukeey is offline
Approved Member
 
Join Date: Aug 2011
Posts: 16
Default

Ok, this cosmetic a unit.

But what can i do, if i want to walk around my army with a for cycle?
I think something like this: for i=1 to 5 do writeln army[1].name

name, buffs, defences, etcetera
Ok, Au.name(unit); but where the hell specify the "unit"

Or what can i do, if i want to show a multitext on screen, with the return values of my function?

Pls, if its possible, and you know, help me at least with a small suggestion, or some lines code.
Reply With Quote
  #3  
Old 11-18-2012, 06:56 PM
MattCaspermeyer MattCaspermeyer is offline
Approved Member
 
Join Date: Aug 2010
Posts: 553
Question I'll try...

I'm not quite sure what you're asking, but I'll try to help you...

Quote:
Originally Posted by jukeey View Post
Ok, this cosmetic a unit.

But what can i do, if i want to walk around my army with a for cycle?
I think something like this: for i=1 to 5 do writeln army[1].name
I don't understand what you mean here - "walk around my army with a for cycle". I'm not sure what the rest of it means either - are you talking about a specific section of code?

By the way, is English your first language? If not, then we may be running into an issue where you have a slightly different vocabulary than mine - you'll probably have to get me up to speed on what you mean...

By the way, if you speak Russion, the Russian developers frequent the Russian forums and they answer questions directly...

Quote:
Originally Posted by jukeey View Post
name, buffs, defences, etcetera
Ok, Au.name(unit); but where the hell specify the "unit"
Are you referring to a specific file and section of code? If you give me the file name and the line numbers I may be able to help you decipher what is going on...

Quote:
Originally Posted by jukeey View Post
Or what can i do, if i want to show a multitext on screen, with the return values of my function?

Pls, if its possible, and you know, help me at least with a small suggestion, or some lines code.
What do you want to show? Do you have a picture or something to communicate your idea? You can certainly use a return value (or values) in text display functions - I worked with that quite a bit in my mod.

By the way, are you intested in modding The Legend (TL), Armored Princess (AP), Crossworlds (CW), Warriors of the North (WotN), or all of them? Do you understand LUA? By the way, the WotN Bug Fixes Thread serves as a mini-tutorial on changing sections of the LUA code to fix problems - that is a great start to get small bits of code to study...

If you can cite some specific code examples in specific files that you don't understand, I (or others) may be able to help you.

Also let me know what your goals are (i.e. add new features, change units, make a campaign, etc.).

If you have patience and time, you should be able to understand how the code works and start making changes...

/C\/C\
Reply With Quote
  #4  
Old 11-19-2012, 11:26 AM
jukeey jukeey is offline
Approved Member
 
Join Date: Aug 2011
Posts: 16
Default

Sorry for my poor english; and i dont speak Russian.
Thx for advices, but im afraid, my goals impossible.

I will try to express myself more accurately, example:
I want to unit resistances should always visible on dlg_chesspiece, in new lines, not only hint. It is possible? And if yes, how?

I tried many ways, and failed. Logic....() is not enough. Cant fix hint as simple_text. Resistances available with Au....(unit); but the unit parameter how works? Can i set a unit from hero army? The best solution would be a new, own script. I studied game and mod files, i see the relationships, and i can write some scripts, but this task not feasible for me.

In warcraft3 editor, i was able to accomplish pickpocketing five minutes with triggers. 5 years ago, when programming was chinese for me. Code sipmply, logical; events, objects clearly. Easy to display my own textboxes with custom texts. Easy to insert my own models and textures. And great documentation with examples. Now i have experienced with delphi (+minimal javascript, actionscript, php knowledge). Lua syntax not too much of a problem; but kings bounty scripting very strange, and im afraid, very limited. And documentations not available or incomplete.
Reply With Quote
  #5  
Old 11-20-2012, 02:57 AM
MattCaspermeyer MattCaspermeyer is offline
Approved Member
 
Join Date: Aug 2010
Posts: 553
Lightbulb Give this a try...

Quote:
Originally Posted by jukeey View Post
Sorry for my poor english; and i dont speak Russian.
Thx for advices, but im afraid, my goals impossible.
Okay - that's not a problem, you'll just have to be patient with me when I ask you a bunch of questions...

Quote:
Originally Posted by jukeey View Post
I will try to express myself more accurately, example:
I want to unit resistances should always visible on dlg_chesspiece, in new lines, not only hint. It is possible? And if yes, how?
I think this may be possible...

I think the files you need to start with are: 1) ENG_UNITS.LNG (or your equivalent LNG files) and 2) TEMPLATES.LNG.

At the top of ENG_UNITS.LNG, you'll see where the text descriptions of the abilities are (i.e. you'll see such labels as CPI_LEADERSHIP, CPI_ATTACK, etc. (note that I've capitalized them here so they standout, but they are lower case in my LNG file)). Then you'll see equals (=) and usually a template surrounded by the caret, i.e. ^int_cpi_t0^. Such templates are in TEMPLATES.LNG (search in there for that template) and essentially serve as keywords for a "macro" that can do many different things to display text such as change the color, display a text string, run an LUA function, etc.

Now if you compare the unit cards between TL and WotN, you'll notice that Crit has been added and the unit's Current Health removed from the unit card.

Now what you want to do is add more lines to the the unit card. For this, I think the files you need to edit are DLG_CHESSPIECE.UI and INFO_CHESSPIECE.UI. From your other posts, I can see that you've been modding the UI files pretty extensively and so I think you already know how to edit these files. As far as I can tell, DLG_... is used for outside of the arena and INFO_... is used inside the arena (i.e. when you are not and are fighting battles, respectively).

For all the "begin simple text" statements, the text strings should be in the *.LNG files. For example, "cpi_initiative2" is in ENG_UNITS.LNG. You probably know a lot more about the UI files than I do, but there are other text strings (for example "text_15" or all the "text_##" strings for that matter) that I don't know where they are.

Now if you want to add the resistances, you need to look at the [res] macro under the cpi_defense_h label (in ENG_UNITS.LNG) using the def_hint_t1 template with the res macro (these are located in TEMPLATES.LNG): gen=gen_unit_res. This refers to the gen_unit_res function located in TEXTGEN.LUA. What you probably want to do, since you want the resistances to be spelled out separately, is you're going to want to create variants of that function (i.e. gen_unit_res_physical, ..._astral, ..._fire, etc.) that simply list a value for one resistance type that display the resistance values (as well as the base values). You'll then want to add the lines to display new text lines to the UI files and you can add the new labels to either ENG_UNIT.LNG or your can make a new LNG file with your new labels.

After that, you need to edit the picture of the dialogue to extend its space, but I think you already know how to do this.

I think that is pretty much it - hopefully I've been clear and if not ask more questions!

Quote:
Originally Posted by jukeey View Post
I tried many ways, and failed. Logic....() is not enough. Cant fix hint as simple_text. Resistances available with Au....(unit); but the unit parameter how works? Can i set a unit from hero army? The best solution would be a new, own script. I studied game and mod files, i see the relationships, and i can write some scripts, but this task not feasible for me.
The calls to gen_unit_res use the "data" variable, here is the code for the gen_unit_res function (note that there are two of them, so I'm not sure what happens in LUA in this situation, but they look identical to me):

TL TEXTGEN.LUA (lines 121-136):

Code:
function gen_unit_res(data)
local text=""

        local res_count=AU.rescount()
        for i=0,res_count-2 do
          local res = AU.resistance( data, i )
          local t=""
          if res>95 then res=95 end
          if res> 0 then t="<label=cpi_defense_res_D>" end
          if res< 0 then t="<label=cpi_defense_res_U>" end
        text=text.."<br> -<label=cpi_defense_res_"..(i+1)..">: "..res.."% "..t

    end

return text
end
TL TEXTGEN.LUA (lines 137-151):
Code:
function gen_unit_res(data)
local text=""
        local res_count=AU.rescount()
        for i=0,res_count-2 do
          local res = AU.resistance( data, i )
          if res>95 then res = 95 end
          local t=""
          if res> 0 then t="<label=cpi_defense_res_D>" end
          if res< 0 then t="<label=cpi_defense_res_U>" end
        text=text.."<br> -<label=cpi_defense_res_"..(i+1)..">: "..res.."% "..t

    end

return text
end
Note that this function runs at the end of the cpi_defense_h label via the def_hint_t1 template using the res macro. You may have to add new macros (i.e. res_physical macro that makes a call to your gen_unit_res_physical function, etc.) to the def_hint_t1 template in TEMPLATES.LNG to get AU to work correctly (I'm not sure when this library is available as I've tried to use it sometimes and it wasn't available for use). Another library that you can use if AU is giving you trouble is Logic with the cp_ functions (although it is more for getting base values of the chesspieces).

Let me know if any of this works for you - if you're still having trouble, I'll try to see if I can get it to work, although my time is really limited right now.

Good luck!

/C\/C\

P.S. One thing I'd like to do is to be able to allow units other than Priests to be upgraded via the DLG_CHESSPIECE.UI. I'm not sure how to do this, but I thought it would be neat to allow Beholders to be upgraded to Evil Beholders, etc. when you get the Paladin Inquisition skill. It seems like there has to be a way to get it to work with units other than just Priests, but maybe this is a limitation...
Reply With Quote
  #6  
Old 11-20-2012, 06:20 PM
jukeey jukeey is offline
Approved Member
 
Join Date: Aug 2011
Posts: 16
Default

Thank you very much for detailed advices.

Info_chesspiece.ui opening only on main window as infobox, when right click on unit in hero army.
All other cases the dlg_chesspiece.ui opening when click on unit, as dialog form.

Ui files easy editable, i wrote a program which can read this files, and show me visually with names and coordinates, no problem (i adjusted some ui files and png interface-textures for better appearance).

But resistances:
I think, the first "gen_unit_res" function maybe a trash, only the second works.
Your advices was my first idea, Tried and failed. Maybe i did something wrong; but i think, its simply dont work.

My second idea: creating 6 global variables, and including to loop in "gen_unit_res" this:
if i==0 then fiz=res end
if i==1 then mer=res end
if i==2 then mag=res end
if i==3 then tuz=res end
if i==4 then fag=res end
if i==5 then ast=res end

function gen_unit_res(data)
local text=""
local res_count=AU.rescount()
for i=0,res_count-1 do
local res = AU.resistance( data, i )
if res>95 then res = 95 end
local t=""
if res> 0 then t="<label=cpi_defense_res_D>" end
if res< 0 then t="<label=cpi_defense_res_U>" end
if i==0 then fiz=res end
if i==1 then mer=res end
if i==2 then mag=res end
if i==3 then tuz=res end
if i==4 then fag=res end
if i==5 then ast=res end

text=text.."<br> -<label=cpi_defense_res_"..(i+1)..">: "..res.."% "..t
end

if Game.LocIsArena() then
if Attack.act_feature(data, "humanoid") then
local count = Attack.val_restore(data,"contrstrike")
if count~=nil and count~="" then
text = text.."<br><br>Îňâĺňíűő ŕňŕę îńňŕëîńü: "..count
end
end
end

return text
end

This is work, new variables got the correct resistance values, when function execute, but problems:
This function execute when hint appear (onmouseover event on gui_object_name "defense" on chesspiece dialog).
So not enough to open the dialog, must move the cursor over "defense" text.
I can adjust this invisible object over cursor, or fullscreen
Variables got the values, but new texts not refreshed actually, only when opening next time the dialog...
Events and other essential things seems absolutely unavailable. Its serious?

Other:
I understand what do you want, great idea. But im afraid, impossible with this way. Too limited, this few modifiable thing is chaotic. With a decent editor or api, we can make castles, buildings for races, write pathfinding algorithm for ai, etcetera. Better than homm. But with this pile of diffuse code, i say, waste of time. Great, beautiful, funny and tactical game with terrible mechanism. Maybe converting models and write a new program, easiest and time-saving modding.
Reply With Quote
  #7  
Old 11-20-2012, 10:31 PM
Sir Whiskers Sir Whiskers is offline
Approved Member
 
Join Date: Dec 2008
Posts: 149
Default

Quote:
Originally Posted by MattCaspermeyer View Post

P.S. One thing I'd like to do is to be able to allow units other than Priests to be upgraded via the DLG_CHESSPIECE.UI. I'm not sure how to do this, but I thought it would be neat to allow Beholders to be upgraded to Evil Beholders, etc. when you get the Paladin Inquisition skill. It seems like there has to be a way to get it to work with units other than just Priests, but maybe this is a limitation...
There is a mod for KB:TL called "army shop" or some such. It includes a mod (training camp) that allows you to upgrade troops, including the example you gave of Beholders to Evil Beholders. The choices in the mod seem somehwat arbitrary, e.g., I believe you could upgrade Inquisitors to Archmages.

Not sure if you can still find the mod on these boards. If not, let me know and I'll try to upload it to this thread.

Note: the mod I mentioned requires KBMaster mod.
Reply With Quote
  #8  
Old 11-21-2012, 02:34 AM
MattCaspermeyer MattCaspermeyer is offline
Approved Member
 
Join Date: Aug 2010
Posts: 553
Default

Yah - I know about that mod (and I'm pretty sure I have it), it would just be nice to use the UI on the unit info card to do it, though, since it works so well. Plus, I kind of wanted to make the Inquisition skill apply to units other than just Priests / Inquisitors, but work with similar pairs (i.e. Robbers -> Marauders, Barbarians -> Berserkers, Bear -> Ancient Bears, etc.).

It must be hard-coded, though, since the developers basically used the "army shop" style to perform the upgrades (especially with CW), but it seems like it is a simple logic switch to say let this unit be upgraded to another. Oh well, thought I'd mention it...

/C\/C\

Quote:
Originally Posted by Sir Whiskers View Post
There is a mod for KB:TL called "army shop" or some such. It includes a mod (training camp) that allows you to upgrade troops, including the example you gave of Beholders to Evil Beholders. The choices in the mod seem somehwat arbitrary, e.g., I believe you could upgrade Inquisitors to Archmages.

Not sure if you can still find the mod on these boards. If not, let me know and I'll try to upload it to this thread.

Note: the mod I mentioned requires KBMaster mod.
Reply With Quote
  #9  
Old 11-21-2012, 03:03 AM
MattCaspermeyer MattCaspermeyer is offline
Approved Member
 
Join Date: Aug 2010
Posts: 553
Default

Quote:
Originally Posted by jukeey View Post
Thank you very much for detailed advices.

Info_chesspiece.ui opening only on main window as infobox, when right click on unit in hero army.
All other cases the dlg_chesspiece.ui opening when click on unit, as dialog form.

Ui files easy editable, i wrote a program which can read this files, and show me visually with names and coordinates, no problem (i adjusted some ui files and png interface-textures for better appearance).
Wow - you wouldn't have this available for other people to use would you?

I've actually been updating my HOMM3 Babies (H3B) mod to extend the spirit upgrade UI and have been doing it by hand. I've been making some changes and I was running out of room and so decided to start fiddling with the interface - your mod that did this helped me out, although when I replied to you at first I didn't realize you were the one who created this mod.

Quote:
Originally Posted by jukeey View Post
But resistances:
I think, the first "gen_unit_res" function maybe a trash, only the second works.
Your advices was my first idea, Tried and failed. Maybe i did something wrong; but i think, its simply dont work.
Was it giving you an error with the AU library? It seems like you should be able to display whatever you want on these info cards, although maybe the limitation is that you have to use CPI parameters. Hmmm...

Quote:
Originally Posted by jukeey View Post
My second idea: creating 6 global variables, and including to loop in "gen_unit_res" this:
if i==0 then fiz=res end
if i==1 then mer=res end
if i==2 then mag=res end
if i==3 then tuz=res end
if i==4 then fag=res end
if i==5 then ast=res end

function gen_unit_res(data)
local text=""
local res_count=AU.rescount()
for i=0,res_count-1 do
local res = AU.resistance( data, i )
if res>95 then res = 95 end
local t=""
if res> 0 then t="<label=cpi_defense_res_D>" end
if res< 0 then t="<label=cpi_defense_res_U>" end
if i==0 then fiz=res end
if i==1 then mer=res end
if i==2 then mag=res end
if i==3 then tuz=res end
if i==4 then fag=res end
if i==5 then ast=res end

text=text.."<br> -<label=cpi_defense_res_"..(i+1)..">: "..res.."% "..t
end

if Game.LocIsArena() then
if Attack.act_feature(data, "humanoid") then
local count = Attack.val_restore(data,"contrstrike")
if count~=nil and count~="" then
text = text.."<br><br>Îňâĺňíűő ŕňŕę îńňŕëîńü: "..count
end
end
end

return text
end

This is work, new variables got the correct resistance values, when function execute, but problems:
This function execute when hint appear (onmouseover event on gui_object_name "defense" on chesspiece dialog).
So not enough to open the dialog, must move the cursor over "defense" text.
I can adjust this invisible object over cursor, or fullscreen
Variables got the values, but new texts not refreshed actually, only when opening next time the dialog...
Events and other essential things seems absolutely unavailable. Its serious?
Hmmm... I wonder if this is once again because of the CPI values. I think I'll experiment with this and see if I can provide any new insight into the problem. Maybe if we put our heads together we can figure it out...

I know that a lot of stuff in TL is hard coded, but when they went to AP and beyond they opened stuff up.

Case in point, I'm still working on my H3B mod for TL and have made a change to the Ice Thorns ability where not only do they block passage, but they also hit and damage units if they are within the fall radius (the hexes surrouding the triad of 3, i.e. if the unit wasn't there a thorn would be created in that hex).

I've been able to get everything working except having the damage hint show up correctly. It seems like to get it to calculate the cells properly, you have to use Attack.multiselect(3), but when you do that, if you turn on hints, the units have to be within the multiselect triad to have damage displayed even though the calccells function selects the cells surrounding that triad.

So it is silly - the highlighted hexes will be the ones where the thorns go and are around the triad, but I have to place the triad on top of units (where the thorns don't go) to get it to display the damage hint, which is not correct.

I haven't given up, yet, but I know what you mean when it comes to dealing with limitations of the game engine / system. If you look at Fishes and Rage Drain, though, these abilities don't show the damage either and so I think that it is most likely a limitation of TL or it was too convoluted to implement so I might give up soon and have it not display the hint just like Fishes and Rage Drain (although if I figure out how to do it for Ice Thorns, then maybe I can try to do it for these two as well).

/C\/C\
Reply With Quote
  #10  
Old 12-01-2012, 07:58 PM
saroumana saroumana is offline
Approved Member
 
Join Date: Jan 2010
Posts: 103
Default

Hello matt,

You're the most advanced modder for KB (except may be russian one ). I have a question for you : it's possible to transfert new addition for warrior of the north to armored princess ? I mean : new unit, new skill, new spell and new ability. Rage system of WotN is crap like his campaign.

There so many change in LUA files that it's not realistic to transfert them one by one, only if we can import whole files....and what files ? LUA, .TXT, all loc_ses ok but how to integrate them into the Armored princess campaign with Amelie for Hero ?

My question is ridiculous, no one know.
Reply With Quote
Reply


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT. The time now is 03:41 PM.


Powered by vBulletin® Version 3.8.4
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Copyright © 2007 Fulqrum Publishing. All rights reserved.