Adding skills

From VbGORE Visual Basic Online RPG Engine

In vbGORE, skills must be fully coded - no scripting or anything of the sort. This is done because of flexibility. Scripted spells are very limited, while hard-coded spells will let you do what ever you want to do. The problem that comes with this, though, is difficulty.

Keep in mind that skills are so diverse in what they accomplish, from buffs and healing to damage attacks and maybe even map-altering effects. All the code displayed here is just suggestions on how certain tasks can be accomplished. Do not just "copy and paste" the code thinking it will work in all conditions.

Writing the spell code

Overview

The most important part of the spell code is the spell code itself. These are all held in the server's Skills module. Most of the default vbGORE spells offer support for casting from PC->NPC, NPC->NPC, and PC->PC. Also, constants are added to the top to easily define certain values. These are not required, but it is recommended for ease-of-use. Its all about personal style, though.

Checking cast conditions

The first part of the skill sub is checking for valid conditions. This depends on if the caster or target is a NPC or PC. Casting between two PCs will have the most checks.

Start with making sure the user is even in the state to cast the spell or be casted on, such as if they are online (or the NPC is alive). For example, from PC->PC:

<vb>

   If UserList(CasterIndex).flags.UserLogged = 0 Then Exit Sub
   If UserList(TargetIndex).flags.UserLogged = 0 Then Exit Sub
   If UserList(CasterIndex).Counters.SpellExhaustion > 0 Then Exit Sub

</vb>

The next step is to make sure the user who is casting the skill knows it. If it is a NPC, it is often safe to assume that if the routine is being called, they are supposed to be casting it, so checks on NPC for this is not required:

<vb>

   'Check if the caster knows the skill
   If UserList(CasterIndex).KnownSkills(SkID.Bless) = 0 Then
       Data_Send ToIndex, CasterIndex, cMessage(37).Data
       Exit Sub
   End If

</vb>

After this, make sure the caster (NPC or PC) has enough mana, stamina or health to use the skill if any is needed. For example:

<vb>

   'Check for enough mana to cast
   If UserList(CasterIndex).Stats.BaseStat(SID.MinMAN) < Int(UserList(CasterIndex).Stats.ModStat(SID.Mag) * Bless_Cost) Then
       Data_Send ToIndex, CasterIndex, cMessage(38).Data
       Exit Sub
   End If

</vb>

For users, it is often important to, just in case, put in a distance check before the skill is used if it is target-based:

<vb>

   'Check for a valid target distance
   If Server_CheckTargetedDistance(CasterIndex) = 0 Then Exit Sub

</vb>

Using the skill

We now know the user passes all conditions to use the skill. From here on, we assume the skill will be used successfully, so reduce the user's stats if needed (ie mana):

<vb>

   'Reduce the mana
   UserList(CasterIndex).Stats.BaseStat(SID.MinMAN) = UserList(CasterIndex).Stats.BaseStat(SID.MinMAN) - Int(UserList(CasterIndex).Stats.ModStat(SID.Mag) * Bless_Cost)

</vb>

For ailments or buff spells, like a Curse or Blessing, you will often want to make sure that the user doesn't already have the skill applied on them with a stronger power. The reason for this is you don't want someone to be able to have a weaker buff casted on them by their enemy to cancel theirs out. A good idea is to allow the skill to be casted on them either way if the one casted on them is about to run out - this will allow users to avoid that "unbuffed time" between when the old buff runs out, and the new one is casted. The below example doesn't do this, though:

<vb>

   'Cast on the target
   If UserList(TargetIndex).Counters.BlessCounter > 0 Then
       If UserList(TargetIndex).Skills.Bless > UserList(CasterIndex).Stats.ModStat(SID.Mag) Then

</vb>

Next is to deal with the icons displayed above the caster and target, if any. The most common on is the spell exhaustion (time that must be waited between casting skills). The below is an example that will display the bless icon:

<vb>

   'Display the bless icon (only if it isn't already displayed)
   If UserList(TargetIndex).Skills.Bless = 0 Then
       ConBuf.PreAllocate 4
       ConBuf.Put_Byte DataCode.Server_IconBlessed
       ConBuf.Put_Byte 1
       ConBuf.Put_Integer UserList(TargetIndex).Char.CharIndex
       Data_Send ToMap, CasterIndex, ConBuf.Get_Buffer, UserList(CasterIndex).Pos.Map, PP_StatusIcons
   End If

</vb>

And spell exhaustion (icon plus adding the spell exhaustion time):

<vb>

   'Add the spell exhaustion and display it
   UserList(CasterIndex).Counters.SpellExhaustion = timeGetTime   Bless_Exhaust
   ConBuf.PreAllocate 4
   ConBuf.Put_Byte DataCode.Server_IconSpellExhaustion
   ConBuf.Put_Byte 1
   ConBuf.Put_Integer UserList(CasterIndex).Char.CharIndex
   Data_Send ToMap, CasterIndex, ConBuf.Get_Buffer, UserList(CasterIndex).Pos.Map, PP_StatusIcons

</vb>

Keep in mind that times are calculated as CurrentTime Length (in milliseconds), not just length. This helps reduce CPU load for timer-based calculations, along with much more accuracy.

Theres a few more display tasks left. The below is an example of how to tell the caster and target that bless was casted:

<vb>

   'Send the message to the caster
   If TargetIndex <> CasterIndex Then
       ConBuf.PreAllocate 3   Len(UserList(TargetIndex).Name)
       ConBuf.Put_Byte DataCode.Server_Message
       ConBuf.Put_Byte 40
       ConBuf.Put_String UserList(TargetIndex).Name
       Data_Send ToIndex, CasterIndex, ConBuf.Get_Buffer
       
       'Face the caster to the target
       UserList(CasterIndex).Char.Heading = Server_FindDirection(UserList(CasterIndex).Pos, UserList(TargetIndex).Pos)
       UserList(CasterIndex).Char.HeadHeading = UserList(CasterIndex).Char.Heading
       ConBuf.PreAllocate 4
       ConBuf.Put_Byte DataCode.User_Rotate
       ConBuf.Put_Integer UserList(CasterIndex).Char.CharIndex
       ConBuf.Put_Byte UserList(CasterIndex).Char.Heading
       Data_Send ToMap, CasterIndex, ConBuf.Get_Buffer, UserList(CasterIndex).Pos.Map
       
   End If
   
   'Send the message to the target
   ConBuf.PreAllocate 5   Len(UserList(CasterIndex).Name)
   ConBuf.Put_Byte DataCode.Server_Message
   ConBuf.Put_Byte 41
   ConBuf.Put_String UserList(CasterIndex).Name
   ConBuf.Put_Integer UserList(CasterIndex).Skills.Bless
   Data_Send ToIndex, TargetIndex, ConBuf.Get_Buffer

</vb>

Now, for the display effects. This has to be handled more on the client which we will get to later. If you want to display a spell effect, you will have to pass the skill ID, then specific parameters for that skill ID. Often, this is just the character index of the target and caster:

<vb>

   'Display the effect
   ConBuf.PreAllocate 6
   ConBuf.Put_Byte DataCode.User_CastSkill
   ConBuf.Put_Byte SkID.Bless
   ConBuf.Put_Integer UserList(CasterIndex).Char.CharIndex
   ConBuf.Put_Integer UserList(TargetIndex).Char.CharIndex
   Data_Send ToMap, CasterIndex, ConBuf.Get_Buffer, UserList(CasterIndex).Pos.Map, PP_DisplaySpell

</vb>

Finally, the very last thing is the sound. The sound requires just sending the sound number (such as 6 for 6.wav) along with the tile position:

<vb>

   'Play sound effect
   ConBuf.PreAllocate 4
   ConBuf.Put_Byte DataCode.Server_PlaySound3D
   ConBuf.Put_Byte Bless_Sfx
   ConBuf.Put_Byte UserList(CasterIndex).Pos.X
   ConBuf.Put_Byte UserList(CasterIndex).Pos.Y
   Data_Send ToPCArea, CasterIndex, ConBuf.Get_Buffer, , PP_Sound

</vb>

Adding the skill ID

The skill ID (SkillID) is what is used to let the server and client tell each other what skill is being used without passing the name. The skill ID is held in the DataIDs module, which is part of the Common Code, which means the client and server use the same module. If you change the code in the server, for example, it will be shown in the client when you load it up.

Scroll down to:

<vb> Public Type SkillID

   Bless As Byte
   Protection As Byte
   Strengthen As Byte
   Warcry As Byte
   Heal As Byte
   IronSkin As Byte
   SpikeField As Byte
   SummonBandit As Byte

End Type Public SkID As SkillID 'Skill IDs Public Const NumSkills As Byte = 8 </vb>

Anywhere in the SkillID UDT, add the name of your skill, such as:

<vb>

   MySkill As Byte

</vb>

Make sure you update the NumSkills constant with the number of skills you have. In most cases, this will be the same number as there are variables in the SkillID UDT.

<vb> Public Const NumSkills As Byte = 9 'Change to highest skill ID </vb>

Now apply the value to the new skill by finding:

<vb>

   With SkID
       .Bless = 1
       .Heal = 2
       .IronSkin = 3
       .Protection = 4
       .Strengthen = 5
       .Warcry = 6
       .SpikeField = 7
       .SummonBandit = 8
   End With

</vb>

And adding in the next free number for your skill, such as:

<vb>

       .MySkill = 9

</vb>

SkillIDto...

While the server only needs to know the SkillID number and what skill subs it relates to, the client needs to be able to convert the skill ID into the skill name and grh to display the skill information on the client. These subs are Engine_SkillIDtoGRHID and Engine_SkillIDtoSkillName. The usage for them are very self explanatory, you just have to enter the Grh number and skill name.

After following these steps, if your user knows the skill, they should be able to see it in their skill selection list, which can be brought up by Shift LeftClick on a quick bar slot.

Extending the skills

Status effects and timers

A huge part about spells is adding ailments and timers. For example, if you want to have a Curse spell that lowers all of the user's stats and lasts for 5 minutes, will need a timer variable to say it lasts for 5 minutes, and a variable to say to tell the server the spell is on the user, and how powerful the spell is.

On the server, look for:

<vb> Type UserCounters </vb>

You will see a lot of skill counters in here already. As stated before, the value stored in this counter is not how long the skill lasts, but at what time the skill will run out, which is found by timeGetTime Length, and is represented in milliseconds. Keep all counters as a Long.

Next, for adding the information of the skill to the user, search for:

<vb> Type Skills </vb>

This holds the power of the skill. How you use and calculate this value is up to you. Some skills, like Iron Skill, is just either on or off, while the others are held by value, where a higher value = higher power.

Next, you want to make sure you are checking to see if the counter runs out. Plenty of examples of this can be found if you look for the code:

<vb>

                   '*** Update the counters ***

</vb>

For example, here is bless:

<vb>

                       If UserList(UserIndex).Counters.BlessCounter > 0 Then
                           If UserList(UserIndex).Counters.BlessCounter < timeGetTime Then
                               UserList(UserIndex).Skills.Bless = 0
                               ConBuf.PreAllocate 4
                               ConBuf.Put_Byte DataCode.Server_IconBlessed
                               ConBuf.Put_Byte 0
                               ConBuf.Put_Integer UserList(UserIndex).Char.CharIndex
                               Data_Send ToMap, UserIndex, ConBuf.Get_Buffer, UserList(UserIndex).Pos.Map, PP_StatusIcons
                               User_UpdateModStats UserIndex
                           End If
                       End If

</vb>

The basic concept of this is if the spell ran out, set the skill value to 0 (turn off the ailment), turn off the icon and update their mod stats.

The final step is to make use of the ailment. This is done in User_UpdateModStats. For example:

<vb>

       'Protection
       If UserList(UserIndex).Skills.Protect > 0 Then
           Log "User_UpdateModStats: Updating effects of skill/spell Protection", CodeTracker '//\\LOGLINE//\\
           .ModStat(SID.DEF) = .ModStat(SID.DEF)   UserList(UserIndex).Skills.Protect
       End If

</vb>

This should be self explanatory. :) Make sure you use ModStat and BaseStat appropriately.

Personal tools