RealmCrafter Wiki
Advertisement

This tutorial was created by Redone on the original RealmCrafter Standard forums.

As the title states this is a tutorial on creating custom combat scripts using RC BVM scripting. I am not a pro at scripting and will never claim to be, and this post is just as much for me to learn as it is for anyone else who chooses to follow this topic. For this tutorial many screen shots will be from RC Pro, but everything will work for RC Standard 1.25. This script type seems to be challenging to a number of RC users for a few key reasons:

1) There are many unknown variables that are completely unique and different based on any given RC users project. 2) Using this script type forces you to create completely from scratch a numeric system that in its basic form allows your players and NPC meshes to animate, damage, kill or die, gain xp, gain or lose faction, all based upon the variables from above. A custom attack script also allows for numerous advanced AI functions depending upon your projects specific needs.

3) This is a script that generally intimidates some users because it removes the comfort of a built in system and plunges them into controlling the very heart of their projects combat system.

4) A good custom attack script can make, or break, a game.


There are many more reasons but the goal of this tutorial is to demystify and guide scripters towards gaining knowledge of and eventually scripting their own unique attack script. This tutorial will start from the very basics and, hopefully, will go in depth into the advanced uses that are possible in an attack script.

For the sake of the community and for the purpose of achieving this topics goals, please do not flame anyone that posts here, a secondary goal of this post is to get as many RC users as possible to participate in this topic to become familiar with BVM scripting (myself included). This is a diverse community with users of many skill levels, and what may seem to some as a seemingly worthless question by one user can appear to others as a very important question, i.e there are no stupid questions.

I urge anyone with questions, suggestions, ideas, answers, creative criticism, or anything of value to participate.

Redone's Basic Scripting Tips/Troubleshooting Section: I will be adding to this for current and future beginner scripters as I progress.

-It is essential to type out some lines of code to learn the basics of scripting. If you are unmotivated to type some script then I highly suggest you hire a scripter. If you always copy and paste other peoples script and simply change the values, I gaurantee that at some point you will need someone to fix your project. Copying and pasting does have it's purposes; in the beginning it gets you familiar to what script blocks look like, but you should formulate your own idea's at some point and translate them to script to gain full control of your project. For the purposes of this tutorial I will give you as much knowledge as I can to help you learn, but where possible I will use pictures of code instead of text. This is solely for your benefit.

- Always properly declare your variable types. If you do not, RC trys to guess what type of variable you are using, and it defaults to integer. 

% for integers 
# for floats
$ for strings

-You should group similiar variable declarations together. Player declarations should be grouped, Target declarations should be grouped, Damage declarations should be grouped, etc. , with the exception of Player and Target, these should always be your first declared variables in any RC script.

-You should be sure to declare Pure variables (Pure variables arent modified, such as "Damage = 10") above Formulated variables (Modified (mixed), such as "FinalDamage = Damage). This will solve many errors before they are even created. -Formatting your scripts properly is essential, this allows you and others to read and troubleshoot them.

-Large, complicated scripts are much easier to create if you plan them out heavily. List the functions you will need, then determine the variables you will use, and develop the functions into needed formulas and then script blocks one at a time to achieve your goal. This is the best way to create complicated, and working, scripts. This also allows you to isolate script errors easily. -Incorporate functions only when necesary, embedding them is preferred. If you incorporate a function and later need to revise it, you may find yourself rewriting the whole script, as opposed to only rewriting one function. -As we are working with variable numbers and functions, it makes perfect sense to let the calculator be your friend when planning, and can save you much time when results of a formula do not coincide with what you expected. -If things dont go as expected when you run your project, but you are sure your script is correct, try deleting both the npc spawn, and the player, and use new ones. This is sometimes necessary it seems, as we are changing things around the current actors and items can keep old settings.

-The line at the top of the default Attack.rsl that reads "You are responsible for applying any damage to the actor, but not for animating actors or blood effects" is incorrect. You will need to provide all of these functions when using the attack script.

Now, lets bust this custom attack script thing wide open!

First thing you will need to do is set up your GUE to use custom combat:

GUE

We will go slow at first, and build this script to a more complex form as we go. Ok, to make an attack script we first must develop a formula using actor attributes to determine 1)What is our damage potential 2) Do we hit or miss? 3)What happens if we or the NPC dies?

For the purpose of this tutorial we will use attributes that are Default in standard RC, and we will start very simple. If you choose you can add your own attributes and use them to formulate your own script. After all, in the end this script should be completely geared towards your individual project. 

Part 1 - Developing and flow charting a basic combat system.[]

We first need some numbers (attributes) to use to make our system do what we need:

Strength - will determine our damage potential (later we will add weapon damage 
as well, but not in this part) 
Dexterity - will determine our potential to dodge an attack
Speed - Will determine our potential to hit (later we will make this more complicated, for now we will be simple.)
(we will add armor to our system later as well)

So, using our simple variables, we now have a basis for a formula. As our damage

potential is easiest, we will start there:

Damage = Strength

Very simple indeed, and we will want much more to factor our damage for later -

but this will do for now.

Now, to state the conditions that determine either a hit or miss.

The factor that allows our target to dodge an

attack: 

Dodge Chance = Targets Dexterity

And the factor that allows us to hit our target: 

Attack Chance = Players Speed

Again, very simple and we will elaborate upon this as we go.


We now

have enough to flowchart a formula: 

if player's attack (speed) is greater than targets dodge(dexterity), we hit for damage(value of our strength), otherwise we miss

Very basic, and slightly unfair to all involved in the fight, but we do in fact

have a basic system charted.

Part 2 - Assembling our formula into a script.[]

Now that we have a formula we need to get it into a script. At the very top of the script we must declare our variables. One key to good script is having all variables clearly defined. We need the basics, so we add the essentials first, to our file

"Attack.rsl": 

Using "RC_Core.rcm" 
; Attack script, used if you set the combat damage formula to "scripted"

; You are responsible for applying any damage to the actor, and for animating actors and blood effects
; The actor is the attacker, and the context actor is the actor being attacked
Function Main()

We now declare our variables:

Player = Actor() 
Target = Contextactor()

The above declares the attacker and the target. Player and Contextactor should always be defined clearly in scripts, with little exception.

TargetHealth = Attribute(Target,"Health") 
PlayerName$ = Name(Player)
TargetName$ = Name(Target)

Every fight needs at least two combatants, right? Also, we only need (for this part of tut) our targets health, as this is a two way script (the npc runs this script as well during battle and the player is the target). We use the name declarations to debug, and to tell us our damage(until we add floating damage numbers).

Next, we will need to declare our combat formula factors: 

PlayerStrength = Attribute(Player,"Strength") 
PlayerSpeed = Attribute(Player,"Speed")
TargetDex = Attribute(Target,"Dexterity") ;we only need target dexterity for now in this simple form

Now that the core attributes are declared, we declare variables for our chance to attack, the targets chance to dodge, and the potential damage: 

AttackChance = PlayerSpeed 
DodgeChance = TargetDex
Damage = PlayerStrength

Ok, so we declared. Now lets make the formula. First, a note on damage. It sounds mundane, but, we must insure that at least "0" damage is made by everyone: 

If (Damage <= 0) ;Make sure everyone does a minimum of 0 damage 
Damage = 0
EndIf

Now that we have made sure everyone has a damage potential of at least 0, and not less, we formulate our combat idea.


Pseudo code:if player's attack (speed) is greater than targets dodge(dexterity), we hit for damage(value of our strength), otherwise we miss.

Script formula: 

If (AttackChance >= DodgeChance) 

TargetHealth = TargetHealth - Damage
Output(Player, "" + PlayerName + "Has Hit" + TargetName + "for" + Damage + "Damage", 0, 255, 255)
DoEvents (100)
SetAttribute(Target, "Health", TargetHealth)

Else Output(Player, "" + PlayerName + " has missed" + TargetName +"", 255, 255, 255)
EndIf

I added outputs for debug purposes. Later we will add visual floating numbers so we can see the damage in combat.

We now have a very limited working attack script, that uses very little to accomplish it's goal. The damage is exactly equal to the players strength attribute. The script does not animate combat yet, this will be covered in another lesson. In fact what we have is a very limited system that barely functions and needs enhancing to make it worth its weight in copper


If you have been following along word for word, your script should now look something like this: 

Example1

Part 3 - Randomizing the formula just a little.[]

We will add one more aspect for this part, we will add a random factor to our variables. Later we will add new attributes to our combat formula, but first we will learn how to randomize what we have.

Declare a new variable, FinalDamage at the top, just below the line "Damage = PlayerStrength": 

FinalDamage = Damage + Rand(0,10)

Then replace all instances of the word "damage" within the If statements to "FinalDamage". This will add 0 - 10 extra Damage, varying each time. If you change the random calculation to reflect a negative value, such as FinalDamage = Damage + Rand(-10,10), it is possible to allow the attacker to create less damage than their beginning damage potential.

Now edit the declarations "AttackChance = PlayerSpeed" and "DodgeChance = TargetDex" to the following (or experiment with your own random formula): 

AttackChance = PlayerSpeed + Rand (-20,20) 
DodgeChance = TargetDex + Rand (-20,20)

If you have been following along, your script should now look something like this:

Example2

We now have a little more variety to work with, and gives both attacker and target a fighting chance (pun completely intended).

And now a screenshot of this very simple script working: 

Test1

Part 4 - Adding armor and weapon bonuses to our formula.[]

Now that we have actors that are damaging one another in combat, we will add 2 more factors to our original formula, weapon damage and armor bonus. First, lets declare some new variables for our targets armor bonus.

Directly above your declare statement "AttackChance = PlayerSpeed + Rand (-20,20)" add these armour variables: 

HatDef = ItemArmor(ActorHat(Target)) 
ChestDef = ItemArmor(ActorChest(Target))
ShieldDef = ItemArmor (ActorShield(Target)) FeetDef = ItemArmor(ActorFeet(Target))

The ItemArmor command reads the armor level value that you set in GUE when you create the item.

Now we will add them together to get a total armor bonus value by declaring this below the individual armor item variables: 

ArmorBonus = HatDef + ChestDef + ShieldDef + FeetDef

We can now use the value of ArmorBonus to lessen the damage potential of our formula. Next, we will add weapon damage to enhance the damage potential by declaring the following above our Damage declaration: 

WeaponDamage = ItemDamage(ActorWeapon(Player)) 

The ItemDamage command reads the damage value that you set in GUE when you create a weapon. Used directly, the damage potential is always constant.


THIS STEP NOT REQUIRED BUT IS USEFUL TECHNIQUE: We can enhance the damage span of weapons further by using a controlled random value to produce a range of the damage. For instance, if you want a weapon set to 5 damage in GUE to have a damage potential of from 1 to 5, you can add 2 more declared values to achieve this.

To span the range of the damage potential using a controlled random value, declare these variables above the FinalDamage declaration. If you do not need this feature, leave it out of your formula: 

WeaponModifier = Weapondamage - 1 
FinalWeaponDamage = WeaponDamage + Rand (- WeaponModifier, 0.0)

And then change your "FinalDamage" declaration to use the FinalWeaponDamage modifier: 

FinalDamage = Damage + FinalWeaponDamage 

We will now add the ArmorBonus value to our FinalDamage factor for use: 

FinalDamage = Damage + Rand(0,10) - ArmorBonus + WeaponDamage

NOTE: In this formula, if a target has more armor than the damage potential created, the target will not be able to be damaged, so be sure to set your armor level and weapon damage values in GUE with this in mind. We will add a critical hit factor that will bypass the armor variable to address this problem directly in the next step.

So far your script should be close to this: 

Example3

Part 5 - Developing and implementing a Critical Hit system to enhance the formula.[]

This system will be completely distinct from our main formula, and it will use different factors than the main formula to determine crit success, targets block chance, players damage potential, and any modifying variables. We need to declare different attributes for use in this seperate system.

In your own unique project you can of course develop this into the main formula if you choose, but for this tutorial it will be a seperate system, embedded within the main combat formula. This allows us to modify our critical hit system later without having to recalculate the main formula.


Declare your new variables near the others at top: 

PlayerDex = Attribute(Player,"Dexterity") 
TargetStrength = Attribute(Target,"Strength")

We can now declare our variables that will be the basis of our new system, again at the top with the others: 

PlayerCrit = PlayerStrength + PlayerDex 
TargetCrit = TargetStrength + TargetDex
PlayerCritRoll = PlayerCrit + Rand (-10, 0)
TargetCritRoll = TargetCrit + Rand (-10, 10)
CritDamage = PlayerStrength + PlayerDex + Rand (-10,10)

We will now formulate the players chance to hit critical versus the targets chance to dodge, using a simple If/Else structure: 

CritSuccess = 0 
If PlayerCritRoll >= TargetCritRoll
CritSuccess = 1 Else CritSuccess = 0
EndIf

Place this block directly below your declarations. We can declare CritSuccess here in place because we will only use it here.

As we have a new damage potential, we need to again insure that this potential is at least 0 in value for both player and target, so again we add this check to the script, directly below our CritSuccss block: 

If (CritDamage <= 0) ;Make sure everyone does a minimal of 0 crit 
CritDamage = 0 
EndIf

Now we create our formula, as we did with the main formula, and we script it into an If statement:

If (CritSuccess > 0) 

TargetHealth = TargetHealth - CritDamage

Output(Player, "You Hit For " + CritDamage + " Critical Damage ", 255, 0, 0)
DoEvents (100)
SetAttribute(Target, "Health", TargetHealth)

End If

This is a seperate utility to inflict Critical damage, but we want to embed this If statement into our main hit/miss block. This ensures that a player must first hit normal to gain a chance to hit critical. If this block was embedded outside of our main hit check a player could miss, and crit at the same time. This may be useful for some projects, you can experiment with this some to see different results.

NOTE: As you look at this new formula, you will notice that it bypasses the armor bonus value. This is a useful, but slow way, to resolve situations where the combatants are stalemated. The script is now ready to deal with all combat situations.

Your script should now be layed out similiar to this example:

Example4

Part 6 - Actor death, experience point rewards, death scripts and simple AI.[]

NOTE: For this next section you will want to have working Player death, Monster death, and levelup scripts in your project. I will provide some basic examples in the post directly below the tutorial.


To this point we have learned how to implement the basics of controlled combat using script, but we have still relied on RC to kill the actors, and to use It's predetermined deathscripts. We will now take complete control of the system.

It is now time to incorporate Player and Target levels to our formula, and to develop a system to reward players with Experience points (XP from here out), and to direct the results of a death to a second (and sometimes 3rd and 4th) script, for use in advanced techniques such as controlling and assigning a Targets loot drops to the Targets killer.

There are a lot of ways to achieve these goals, and we will go through many of the significant ways. We will start with ways to reward XP once a target is dead.

Preparation: We now will embed an If statement to check whether or not the target has died into our AttackChance statement. Embedding here will ensure that the Target will only be killed if the player hits.

Add the following lines directly into your Attackchance statement: 

If (TargetHealth <= FinalDamage) 

Output(Player, "Targets Health was less than damage ", 255, 0, 0);debug output
Return

EndIf

This statement simply checks to see if players damage dealt was larger than the targets health. If the FinalDamage was greater than TargetHealth, we now have isolated a death condition for use in our formula. Notice that we do use the Return command in this statement; If the player is dead the script will now end.


Your AttackChance statement should now resemble this example: 

Example5

We will now use this new statement to explain a few of the different ways to reward players XP after killing a target. 

XP Section I: Allowing XP to be auto calculated by using the GiveKillXP command[]

We can use RC's internal XP formula to auto calculate our XP reward value for us by using the GiveKillXp(Player,Target) command. The command reads the XP multiplier that you set in GUE when you create your actors, and rewards the player by using the Target's XP multiplier value: 

If (TargetHealth <= FinalDamage) 

Output(Player, "Targets Health was less than damage ", 255, 0, 0);debug output
GiveKillXp(Player,Target)
DoEvents (50)
SetAttribute(Target, "Health", 0)
Return

EndIf

Using the GiveKillXp command will reward Xp to the killer using RC's internal XP formula, which is based on level.

We use the SetAttribute(Target,"Health",0) command to ensure that the actor is dead and the script goes to the Return(without it, you may get two or more xp rewards, and the actor may not stop fighting although it is technically dead).

The script will now produce results like the following based on the Xp multiplyer you set when you added your Actor(s) in GUE: 

Xp

This is the easiest way to reward XP, but is less configurable to your projects unique needs than using the GiveXp command. 

XP Section II: Assigning XP value using the GiveXP command.[]

Using the GiveXp command we can assign XP reward points geared towards our unique project, using a system to specify exactly how much XP to reward for a kill. To do this we will use a level based formula to calculate our XP, so we will need to declare 2 new variables to use, at the top near our other declares.


Place this line directly below your existing declared target variables:

TargetLevel = ActorLevel(Target)

Place this line directly below your declarations block, below the other variables: 

AwardXp = TargetLevel * 10 

Now to use our Xp formula instead of allowing RC to calculate , change the script to the following: 

If (TargetHealth <= FinalDamage) 

Output(Player, "Targets Health was less than damage ", 255, 0, 0);debug output
GiveXp(Player,AwardXp)
DoEvents (50)
SetAttribute(Target, "Health", 0)
Return

EndIf

Using this formula, the Xp awarded will always be The Targets level times 10.  

XP Section III: Assigning XP value using the GiveXP command using level conditions.[]

We can further control our Xp reward by creating a system of conditions that will help determine our Xp value, using a level span to reward players that are fighting targets that are close or slightly higher in level, and not reward players that are fighting targets that are out of their appropriate level range. We will now declare our players level, and the level difference of our player and target for use in this system.

Place this line directly below your existing declared player variables: 

PlayerLevel = ActorLevel(Player)

Then add the following declare below all of the other declares: 

LevelDifference = TargetLevel - PlayerLevel

We can now use the difference between PlayerLevel and TargetLevel to calculate our Xp value. For this tutorial, we will use a formula that punishes players who attack targets that are 10 levels higher or lower, will reward heavily players who attack up to nine levels higher, and humbly rewards players who are fighting targets that are the same level. This system also ensures that the Xp will not be less than 25 each if the target is the same level as player.

We will achieve this using an If statement to check our targets level difference to us. You may also want to experiment using the select command structure to do this, as it would also work very well for this purpose.

Delete the variable AwardXp = TargetLevel * 10 we used in the last step, and add the following formula directly below your top variable declares:

If (LevelDifference > 10) ;player is 10 lvls+ lower than target 


AwardXp = 0

ElseIf (LevelDifference < -10) ;player is 10 lvls+ higher target

AwardXp = 0

ElseIf (LevelDifference < 0);player and target are same lvl

AwardXp = TargetLevel * PlayerLevel + 24

ElseIf (LevelDifference < 9) ;player is up to 9 lvls lower than target

AwardXp = TargetLevel * PlayerLevel + LevelDifference

EndIf

You can use a much more detailed system for your own project. This system enables you to even spell out the Xp reward for every level difference, you would just need to add more checks to it for every condition you wanted Xp set precisely for.

Using the above formula, you could easily assign Xp based on Faction,Monster type (by name or tag, for a boss monster possibly), weapon type, Zone, etc. You would simply need to declare the variables you wanted to use and add the conditions to the if statement, or create new if statements to further test extra conditions. The possibilities are only limited by the variables available in RC, your projects specific needs, and your creativity.


Note: If you have been following close, you may have noticed that only the main attack block will actually provide a condition of "dead". So far we haven't added this feature to our critical hit system. To add this to our Crit hit system so that actors can die on a critical hit, and players are awarded Xp on a critical, we need to copy, adapt, and embed the death check within our critical hit system (remember, it is a seperate system).

Using the script example below, you can see how I duplicated this same system to check if target has died to my critical hit system:

Example6

For this tutorials purpose we will use the above described system for our death and XP needs. 

Deathscripts: Who killed who? Adding seeds of AI.[]

Up to now we have relied on RC's internal workings to launch a seperate script to handle the condition of death. This is ok if you will never need to know who killed who, parameters that you can pass to other scripts so they can utilize the target and the killer for their purpose(s). We can take full control of this as well however, now that we have scripted our own death condition into our attack.rsl.
As with our other systems, we have a few choices in the way we can pass the value of player and target to external scripts. The examples in this section can be scripted to into separate systems, so you can use a combination of ways using separate death conditions (i.e. use one way in main formula, use another in critical hit formula). They can also be scripted to different conditions of one formula using a check system.

We can also create different results for both Player and NPC actors.

PREPARATION: It is now time that we enable the script to determine whether the combatants are controlled by a human (player actor) or by the server (NPC actor). This will allow us to start incorporating simple AI into our formula.

For logic purposes it will now become very essential that the scripter clearly knows who the player (the killer) and who the target (the killed) is, because we will start to pass these values to other scripts for use now.

We will declare a new variable to determine if ActorIsHuman:

PlayerIsHuman = ActorIsHuman(Player)

As this is a pure variable it should be declared directly under Player and Target at the top of the script.

If the player is human, the result of this variable will be the value 1. If the actor is an NPC, it will return a 0. Using this value we can now make different events occur based on if the player or target are human controlled actors or NPC characters. 

Death Script I: Using the KillActor command to use an assigned death script.[]

To use an assigned death script for your target, and pass the player and target variables to the death script for use, we can use the KillActor command. This will allow RC to autocalculate the faction increase or decrease after a kill, and will autocalculate and distribute the Xp value of a kill if used in conjunction with the GiveKillXp command.

If the target is an NPC this will pass the Player (the killer) and Target (the killed) values to the script that was assigned as its death script in its spawn point, and if the Target was a human player, the Player and Target values will be passed to the default player death script, "Death.rsl"

Consider the following example: 

If (TargetHealth <= FinalDamage) 

KillActor(Target,Player) ;pass Target and Player to assigned death script
DoEvents(50) GiveKillXp(Player,Target) ;auto assign Xp and Faction hit

Return
EndIf

Death Script II: Using the ThreadExecute command to call an external death script.[]

By using ThreadExecute we can call external scripts for use, and pass the commands parameters to them for use. This can be effective for many purposes. For example, our XP calculation formula could be called and used from an external script, because we can send the value of the target and player to a seperate script that can then pass the XP value to the actors.

For now, we will use this method to call a deathscript from our Attack.rsl.


Consider this code snippett: 

If (PlayerIsHuman = 1) ;If Killer is Human 
If (TargetHealth <= FinalDamage)

GiveXp(Player,AwardXp) ;give our formulated Xp DoEvents(50)
SetAttribute(Target, "Health", 0) ;used with GiveXp, when NOT using KillActor
DoEvents (500)
ThreadExecute("MonsterDeath", "Main", Player, Target, 0) ;Call MonsterDeath to handle the kill
Output(Player, "Targets Health was less than damage ", 255, 0, 0) ;debug output
Return

EndIf
Else

By using this technique we can easily call different death scripts for different monsters, by setting up a system using attributes, or by using the monsters unique class to call different scripts when our death condition was meant.


You of course would need to prepare your script for this using newly declared variables, and for the purpose of this tutorial we will only discuss it.

The script snippett below clearly shows this technique in action:  

If (PlayerIsHuman = 1) 
         If (TargetHealth <= FinalDamage)
            
            GiveXp(Player,AwardXp)
            DoEvents(50)
          SetAttribute(Target, "Health", 0)
            DoEvents (500)
            If (MonsterType = 1)
            ThreadExecute("MonsterDeath", "Main", Player, Target, 0) ;death script for regular mobs
            ElseIf (MonsterType = 2)
            ThreadExecute("BossDeath", "Main", Player, Target, 0) ;death script for boss mobs
            EndIf
            Output(Player, "Targets Health was less than damage ", 255, 0, 0) ;debug output
            Return

         EndIf   
      Else

Death Script III: Calling different Death Scripts for NPC and Player[]

Using the variable we added in the beginning of this section, we can now reliably call different death scripts for NPC's and Players. We will incorporate a simple If/Else check in our death condition. In your own script you can use an elaborate system to achieve this, but for this tutorial a simple check is sufficient.

Consider the following example: 

If (PlayerIsHuman = 1) ;Killer is human 
         If (TargetHealth <= FinalDamage)
            
            GiveXp(Player,AwardXp)
            DoEvents(50)
            SetAttribute(Target, "Health", 0)
            DoEvents (500)
            If (MonsterType = 1)
            ThreadExecute("MonsterDeath", "Main", Player, Target, 0) ;death script for regular mobs
            ElseIf (MonsterType = 2)
            ThreadExecute("BossDeath", "Main", Player, Target, 0) ;death script for boss mobs
            EndIf
            Return

         EndIf   
      Else ;Killer is NPC
            
         If (TargetHealth <= FinalDamage)
            
            KillActor(Target,Player)
            DoEvents(50)
            GiveKillXp(Player,Target)
            DoEvents (500)
            Return
         EndIf
      EndIf

NOTE: You may have noticed that this example only specifies deathscripts for NPC actors.

Although it is possible to call different deathscripts for player actors as well, this can easily confuse the engine if not done very precisely. Using the KillActor command ensures that the values of target and player iare passed to the default player death script, Death.rsl.

You can easily incorporate a system into your Death.rsl to handle varying death actions for different player actors, and is in my opinion a much more effective usage. However, this topic may be elaborated upon once I open the advanced discussion portion of this thread, to guide scripters that choose to use this method.

It is also more efficient to have one single "monsterdeath" script, and incorporate and utilize functions to call the various death actions. This will help you once you have many monsters in game, you will only need to locate and edit one central file to control the various results for all of your monsters.

This can be achieved with the Death.rsl as well, but differently. Functions are of no use in Death.rsl, its entry point is always "main", so you would need to add formulas to check the player type instead of using functions. 

Incorporating simple AI: Adding Basic AI to our formula.[]

Human controlled actors need no AI (Artificial Intelligence). They are capable of making decisions such as when to attack, when to heal, when to call other players for help, when to cast a spell, when to move out of the way, when to run, or when to stop attacking.

NPC actors must have scripting to emulate this uniquely human behaviour. RC scripting utilizes some commands that are very specific to AI. In the preparation of the previous section, we learned about the ActorIsHuman command. We will now learn some other related commands that we can use if our Human check tells us that our actor is an npc (ActorIsHuman = 0)

ActorCallForHelp - This command causes all NPCs with a faction rating of over 90% with the specified actor's faction, who are also within range of the actor, to join in attacking the actor's target. 

If (TargetHealth < 15) 
      ActorCallForHelp(Target)
   EndIf

The above example would be embedded into your Hit success block, and would make the target call surrounding NPC actors that are in good faction to it for help if it's health dropped below 15.

Using a random value would make it do this only sometimes, to emulate an actor who was able to call for help versus an actor that was killed before the call for help could go out, as this example shows: 

If (TargetHP < 25) 
Arand=Rand(1,2)
Case 1
ActorCallForHelp(Target)
Case 2
Output(Player, "" + TargetName$ + " tried to call for help but failed", 255, 0, 0)
End Select
EndIf

ActorAIState and SetActorAIState[]

ActorAIState(Actor, State) SetActorAIState(Actor, State) ActorAIState is

a command that uses RC's hard coded NPC states. The states are as follows: 

AI State Values

The ActorAIState command returns the current AI mode of an actor. There are various possible states:
0 - Wait in current position, attack any valid targets who come into range
1 - Patrol - walk to destination, then set destination to new waypoint if available
2 - Run - same as Patrol but running instead of walking
3 - Chase and attack target
4 - For actors who are paused at a waypoint while on patrol
5 - Pet mode - actor will follow leader and attack leader's target (actor MUST have a leader to use this)
6 - Pet attack mode - actor is chasing and attacking the leader's target
7 - Pet wait mode - actor waits in current position and does

absolutely nothing 

Consider the following example, from the RC documents: 

;Give an NPC a boost if it is attacking 
         State = ActorAIState(Actor())
         If (State = 3 | State = 6) ;if actor State is 3 or 6
            SetAttribute(Actor(), "Health", Attribute(Actor(), "Health") + 20)
         EndIf

ActorTarget and SetActorTarget[]

ActorTarget(ActorHandle) SetActorTarget(Actor, [TargetActor])


ActorTarget is used to determine 1) does the actor have a target, and if so 2) what is the Target. Using SetActorTarget you can manipulate the actors target by switching its target to another valid target, or setting it's target to 0 so it no longer has a Target. This can be very useful in the Attack.rsl, and can be used for various AI results.

USAGE: When we enabled the script to call external death scripts, you may have noticed that the NPC may begin attacking the player even after it killed it, immediately after the player respawns after death. This is because the engine now expects us to control the AI state for our NPC's. Before it was using RC's internal system to handle this, but we have bypassed the default settings. We can use SetAIStae and SetActorTarget to control this result.

Consider this example: 

If (PlayerIsHuman = 1) 
                  
                  GiveXp(Player,AwardXp)
                  SetAttribute(Target, "Health", 0)
                  DoEvents (500)
                  ThreadExecute("MonsterDeath", "Main", Player, Target, 0)
                  Output(Player, "Targets Health was less than damage ", 255, 0, 0) ;debug output
                  Return
               Else
                  GiveXp(Player,AwardXp)
                  SetAttribute(Target, "Health", 0)
                  CreateFloatingNumber(Target, AwardXp, 255, 255, 255)
                  DoEvents (500)
                  SetActorTarget(Player, 0)
                  SetActorAIState(Player, 1)
                  Doevents (60000)
                  SetActorTarget(Player, 0)
                  Return
               EndIf

In this example, if the Target was a human player, we change the NPC's target to 0, stopping it from attacking the player any further, since if the script ran to this point, the player is now dead. We then set the NPC's AI state to 1, which will make the NPC patrol waypoints, for 60 seconds (60000 clicks). We then set the NPC AI state to 0, which is the default state and means that the NPC will stand there and attack any new valid target that gets to close to it.


This effectively stops the battle on a player death.

ActorAggressiveness[]

ActorAggressiveness(Actor)

This command reads the actors aggressiveness that is set in the actor setup in GUE. We can not use this value to change the aggression of an actor, but we can get it's value, and use that value to make our script react differently to actors based on their aggression level. The

aggression values are as follows:

0 - Passive

1 - Defensive
2 - Aggressive
3 - Non-combatant 

Consider this example of usage from the documents: 

AggLvl = ActorAggressiveness(Actor()) 
         Output(Actor(), "Your aggression level: " + AggLvl)

Part 7 - Adding floating damage numbers and particle emitter effects to the battle[]

Note:I have found one issue regarding floating damage numbers and RCPro: RCP would not create floating numbers for me. This is yet uncomfirmed, but if you are using Pro and get a client crash from this command we may have to wait for another release to use it in pro

We are now ready to implement floating numbers to the combat which will allow us to see visually on screen both players and targets damage. This is not essential for your combat script, and you do not need it if your project plan does not use them.

We will add a line directly above our line "DoEvents (100) ;wait 100" contained within our if statement: 

CreateFloatingNumber(Target, FinalDamage, 255, 255, 255)

This will create a floating number of our FinalDamage variable given to the target(s) on screen during combat, in white. If you have been following along your script should now look something like this:  

Example7

NOTE: Once you have the floating numbers working in your script it is no longer necesary to have the text outputs that we added in the beginning sections, and you can remove them at this point if your project won't use them. We used them to debug our script, but we now can see the damage with floating numbers.

Save your changes, if you are using 1.25+ launch your GUE and build your project, if you are using pro just build your project. Launch your server, unlock it, launch your game and go battle an enemy to test. If you have done it right your combat will look similiar to this: 

Floatingtest1

=== Attaching a blood emitter to the target when hit.

===

To attach a blood emitter (or any particle emitter you like) to your actors when they are hit, we will use the command CreateEmitter (Target, "EmitterName", TextureId, Time). Place the following command within your Hit check If statement block, directly below where you want it to occur.

I place my CreateEmitter commands directly underneath my floating number commands: 

CreateEmitter(Target, "Blood", 12, 1000)

This will attach a blood emitter to the target for 1 second (1000 clicks) everytime it is succesfully hit. You can of course use a longer or shorter time, or a different texture or emitter. 

Part 8 - Animating your combatants.[]

At this point we are ready to animate the combatants so they visually represent the battle on screen. For the purpose of this part of the tutorial we will use default animations that are standard to RC default models, but if you like you may use your own custom animations and or models.

To simply use one animation for all combat types, we only need to add 2 lines to our script, directly below your first EndIf statement:

AnimateActor(Player, "Default Attack", 1.2) 
DoEvents(100)

We will elaborate on this and create a system that will choose our attack animation based on our weapon type in a few steps, but for now your code should resemble this:

Example8

And if you enter battle, the fighters will now be animated with one combat animation: 

Combatanimationtest

We are now going to turn our limited combat script into a fully functioning attack.rsl, using some advanced scripting techniques and commands.

Part 9 - Animating your combatants using the Select command.[]

In this part we will implement random attack animations using the Select command and structure. First, locate the lines of script we added in the previous section:

AnimateActor(Player, "Default Attack", 1.2) 
DoEvents(100)

and remove them. Starting at that same place we will make a new script block that will contain our select command and its structure.

First we need to declare a new variable, Arand. We do not need to declare this variable at the top of the script with our other declares because this variable will be used only here, for our select structure, so we add it in place in the script: 

Arand = Rand(1, 3)

Next we add the select command:

Select Arand

Now we need to set up what will be executed when the script calls for the value of Arand, using the Case command:

Case 1 ;Executes if 1 is randomly chosen 

AnimateActor(Player, "Default Attack", 1.0)
DoEvents(500)

Case 2 ;Executes if 2 is randomly chosen

AnimateActor(Player, "Right Hand Attack", 1.0)
DoEvents(500)

Case 3 ;Executes if 3 is randomly chosen

AnimateActor(Player, "Two Hand Attack", 1.0)
DoEvents(500)

NOTE: The value at the end of each AnimateActor command, "1.0" controls the speed of your animations playback time, and the value from the DoEvents command controls the wait time until the script animates the actor again. Experiment with these settings to fine tune the speed of your animation. This will not however control the speed of the actual battle formula.


Once we have all of our animation choices in place, we close the select structure: 

End Select

You can have as many choices as you like in your select structure. If you have been following along your script should now resemble this:

Example9

This adds much more realism to our attacks. Your combat should now resemble this: 

Weaponcombat

The above added script block chooses an attack animation completely at random, but we can set a default attack animation and combine it with the random selection by adding this line directly above your End Select command: 

Default 

AnimateActor(Player, "Default attack", 1.0)
DoEvents(500)  

Then change your command of Arand = Rand(1, 3) to reflect the new choice: 

Arand = Rand(1, 4)

This will allow for some basic normality to the animation chain. Your select block should now resemble this: 

Example10

We can elaborate this step further, by embedding another select block within our current select structure, underneath any Case command or Default command. For this tutorial we will vary our attack animation playback speed using this technique, and we will place our second select structure within our Default choice.

Erase the contents under your default choice, copy your entire Select structure from the top to bottom, and paste it underneath your main structre's Default choice. At this point you can experiment with decreasing or increasing the values for your new select blocks animation playback and DoEvents wait time.

For this tutorial I have set my animations playback to 0.2 and increased the wait time between animating by 100 for the new select block.


If you have been following, your script should now resemble this: 

Example12

We now have what appears to a game player to be some simple AI in regards to combat. Although it is random at this point, using these techniques we can control the randomness some.

In the previous section we learned how to add random animations using a Select command structure, and we learned to use the Default command as a basis for our overall animation formula.

In this section we will learn how to utilize this structure to completely and accurately control all of our combat animations and sounds, using a system we create based on the class of weapon that your player is using.

It is in this section that you will also learn how to animate your ranged attacks using the default RC models. 

Part 10 - Animating your combatants based on weapon type.[]

Preparation: We first need to prepare our project to use the system we are about to develop. Open your project, locate the attributes tab, and create a new attribute, WeaponClass.This is what we will name our new attribute for this tutorial, but you can name it appropriately for your project if you like.

If you haven't already done so, import some weapon meshes into your project and then use the gubbins tool or armoury tool to position them to your actor(s). Once you have these in your project properly, create new items, weapons, and set them up completely, i.e Scimitar, 15 kg weight, 150 copper value, damage of 15, one handed, slashing etc, etc. Do this for a variety of weapon types.

NOTE: The Fire Bow and Fire Crossbow animation frames for the default RC models are not included in the default animation sets. You will need to add the following animations to your Player (male) and Player (female) animation sets for the (New) actors, and use a different animation set for the (Old) actors

Bow and crossbow animation range for default actors (New) Human, elf, gnome, dwarf,

elf:

Fire bow - 755 - 810
Fire crossbow - 812 - 900

Bow and crossbow animation range for default actors (Old) Dwarf, Barbarian, Elf, Human, and LizardFolk:

Fire bow - 818 - 940
Fire crossbow - 818 - 940 

Open the Items>Attributes tabs and select your new attribute. For each weapon

that will use the same animation types, set the attribute to the same value.


I will use the value 2 for all of my bow type weapons, and any bow that I add later will use the value 2 for their WeaponClass attribute, i'e all 1 handed weapons will use the value 1, all two handed weapons will use 3, etc.

You of course can use other values but they need to begin at 1 and up, in succession. The number 0 will be reserved for our unarmed combat, as default.

GUE Attributes

In your script, we will now need to declare our new variable, at the top of your script just below your other declared variables. We will declare this with the others this time because this can be a multiuse variable that you may use in other formulas

AttackType = ItemAttribute(ActorWeapon(Player), "WeaponClass")

Now we move to our Select block, locate the line that states Arand = Rand(1, 4) and delete it. Then just below it be sure to change the line that reads Select Arand to:

Select AttackType

Now, look over your animations defined in your Select block, and adjust the corresponding Case commands to define the appropriate animation for your WeaponClass, i.e Case 1 has the animation command to trigger 1 handed sword animation, and your 1 handed sword uses the value 1 as its WeaponClass.


If you have been following, your script should now resemble this: 

Example13

Remember that you can embed select structures within other select structures, and at this point you should experiment with this technique to create very unique variations for your projects combat animation needs.

Now we know reliably where to place our combat sounds, we just need to place the correct sounds with the correct animations for our weapon type.

If you have an arrow projectile and emmiter setup, you now can add the command FireProjectile(Player, Target, "Arrow") directly above the DoEvents command within the script block you defined your Bow and Crossbow animations in.


If you run your project your combat should resemble this:

Particleeffecttest
Particleeffecttest2

NOTE: If your projectile is set in GUE to create damage, it will be added to the combat damage, as demonstrated by the green floating point damage numbers in the screenshots above. If this is not a result that your project requires, you can set the projectiles damage and or chance to hit to 0% in GUE. Also, the range setting is functional as well, so be sure to set the range of projectile weapons directly in the item setup properties in GUE.

Special Thanks Go To NBT for introducing me to this type of use with a select structure.

Part 11 - Adding some combat sound to the mix.[]

This next section discusses adding attack and hit sounds to what we have so far. There are many ways to do this using if statements or utilizing yet another select structure, but for this tutorial we will use the select structures that we have in place to quickly add varying combat sounds to go with our varying combat animations.


You will need to import custom soundfiles to your project at this point, and document the media ID numbers for them to take full use of this step.

We will add a command directly above each DoEvents in our Select commands: 

PlaySound(Player, 162, 1)

We place it above the DoEvents because otherwise the sound file would be affected by the delay. This may be useful to some, if you want to fine tune the exact moment during your animation that a sound is played.

The first factor in the command is the media ID and sets which sound file is played, the second factor determines if anyone can hear the sound besides the player. If this factor is set to 0 then only the player will hear the sound, and no one else. This could be useful if you have sound files that give players personal messages, such as "way to go" or "critical hit".

We can also add a Playsound command near the "player has missed" dialog output. This can be used as a universal miss sound.

If you have been following your select block should now resemble this, but with your specific media ID numbers in place: 

Example14

We now have animated combat that we can hear based on a default setting with random variables.

Advertisement