![]() |
|
Mods Everything about mods |
|
Thread Tools | Display Modes |
#1
|
|||
|
|||
![]()
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:
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...)
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 . . . 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 . . . 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 } . . . 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) Code:
-- New Bone Dragon Attack function features_bonedragon_attack( damage, addrage, attacker, receiver, minmax ) . . . For this function, the argument list is:
Code:
. . . if ( minmax == 0 ) then . . . end 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" ) ) . . . 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 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 . . . The Code:
local tmp_spells = {} 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 . . . The next section of code: Code:
. . . if table.getn( tmp_spells ) > 0 then . . . end . . . 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 . . . Code:
Attack.act_aseq 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 . . . Code:
tmp_spells[ cast ] The next part of the statement includes a parameter list in (). So let's say that Code:
tmp_spells[ cast ] Code:
spell_scare_attack( spell_level, dmgts, receiver ) 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( '' ) . . . The last portion of code in this function is: Code:
. . . end end end return damage, addrage end . . . 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 . . . There is also this statement: Code:
local target = Attack.get_target() 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 how we changed the Code:
local target... Code:
Attack.get_target() 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() 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. . . . Code:
. . . cpi_bonedragon_feat=Undead, Bone, Flight. . . . So then add it to the comma-separated list of abilities like so: Code:
. . . cpi_bonedragon_feat=Undead, Bone, Flight, Chaos. . . . 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. 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> } . . .
Code:
skills_tl=<align=center><color=0,0,0><font=ft1_12>$ 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. . . . 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 } } . . . 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" ) ) 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. . . . 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\ |
|
|