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:
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:
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:
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:
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:
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:
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:
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:
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:
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 doesabsolutely 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:
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:
=== 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:
And if you enter battle, the fighters will now be animated with one combat animation:
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:
This adds much more realism to our attacks. Your combat should now resemble this:
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:
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:
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.
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:
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:
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:
We now have animated combat that we can hear based on a default setting with random variables.