ID:100440
 
I am sure we are all familiar with the generic "skill".
mob
verb
FireBall()
usr.Do_FireBall_Stuff()

That is often a better example than most cases.

Many times people do the whole skill process via their verbs. Why do that, or even do an extremely long procedure, when you could use a datum and simplify everything? In this post, I will show you how to do a simple skill system using a datum.

Creating the datum
Here is the start, the simplest part. Create the datum.
skill

Simple as that. Now, let's do some defining. But wait; you need to plan out how you want things to work before you make them, to reduce changes later. In this case, we want a simple system that will activate and deactivate skills, and do a few checks in there too.

Making Variables
skill
var
using=FALSE
cooldown=0
default_cooldown=0
tmp/mob/owner

There we go. Now we can see if the skill is being used, and what it's cool down is, what it's default cool down is(we will set the cool down to this when they use the skill) and we can also see whose skill this is later.

Default Procedures
skill
var
using=FALSE
cooldown=0
default_cooldown=0
tmp/mob/owner

New(mob/_owner,_cooldown)
owner=_owner
default_cooldown=_cooldown

proc
Use(mob/target)

Activate(mob/target)

DeActivate(mob/target)

Using()
if(using)
return 1
return 0

Default_Cooldown()
return default_cooldown

Cooldown()
return cooldown

Start_CD(mob/_owner)
while(_owner&&_owner.loc&&cooldown)
--cooldown
sleep(1)
if(cooldown<0) cooldown=0

Ok, there is a lot here. So I will go step by step.

These are just default procedures, that each skill will define. Or, you can have a subclass of skills(For example, transformations) that define these procedures, then the skills under the transformations class will call those procedures.(Quick Note: If you do that, and also change the procedure in the subclass from transformations, you need to call ..() so it will do the previous definition.)

Using() will return 1 if the skill is being used. You can use this to see if the skill is being used, obviously. Cooldown() returns the current cool down of the skill. Default_Cooldown() returns the default cool down of the skill.

Start_CD() makes the cool down start going down. Call this after the skill is used.

Activate() is to be called before Use(). It does the checks, and Use() does the actions.

We also used New() in here, but I added arguments to it. I added mob/_owner and _cooldown. I used underscores so I could use a variable similar to the variables being assigned values to avoid confusion later down the road. mob/ requires the owner to only be a mob, to avoid errors.

Your First Skill
skill
var
using=FALSE
cooldown=0
default_cooldown=0
tmp/mob/owner

New(mob/_owner,_cooldown)
owner=_owner
default_cooldown=_cooldown

proc
Use(mob/target)

Activate(mob/target)

DeActivate(mob/target)

Using()
if(using)
return 1
return 0

Default_Cooldown()
return default_cooldown

Cooldown()
return cooldown

Start_CD(mob/_owner)
while(_owner&&_owner.loc&&cooldown)
--cooldown
sleep(1)
if(cooldown<0) cooldown=0
Transform
Activate(mob/target)
if(using) DeActivate(target)
//In here, just check for things that will make the user NOT be able to use the skill.
else Use(target)

Use(mob/target)
view(target)<<"[target] yells: Raaaaagh!"
sleep(15)
target.icon='Transformed_Icon.dmi'
view(target)<<"[target] transformed!"
using=TRUE

DeActivate(mob/target)
view(target)<<"[target] starts reverting..."
sleep(15)
target.icon = 'Normal_Icon.dmi'
view(target)<<"[target] reverted to his original form."
using=FALSE
cooldown=default_cooldown
Start_CD(target)


Alright. We defined a Transform subclass, and defined the Use(), Activate(), and DeActivate() procedures.

As said in the code, Activate() is used to check for limitations. In this example, it just checks if it is already being used, if so then DeActivate() it, preventing them from stacking. The Use() and DeActivate() procedures just do a simple process. It tells people around them a message, sleeps for a second and a half, then tells them another message, changes the icon, sets the using to FALSE, and (in the case of DeActivate()) sets and starts the cool down.

Now all we have to do is make the datum usable by people. This is quite easy. In the next example, I will be making a verb that uses the skill, and making the mob receive the verb upon login.

Making Your Skill Usable
skill
var
using=FALSE
cooldown=0
default_cooldown=0
tmp/mob/owner

New(mob/_owner,_cooldown)
owner=_owner
default_cooldown=_cooldown

proc
Use(mob/target)

Activate(mob/target)

DeActivate(mob/target)

Using()
if(using)
return 1
return 0

Default_Cooldown()
return default_cooldown

Cooldown()
return cooldown

Start_CD(mob/_owner)
while(_owner&&_owner.loc&&cooldown)
--cooldown
sleep(1)
if(cooldown<0) cooldown=0
Transform
Activate(mob/target)
if(using) DeActivate(target)
//In here, just check for things that will make the user NOT be able to use the skill.
else Use(target)

Use(mob/target)
view(target)<<"[target] yells: Raaaaagh!"
sleep(15)
target.icon='Transformed_Icon.dmi'
view(target)<<"[target] transformed!"
using=TRUE

DeActivate(mob/target)
view(target)<<"[target] starts reverting..."
sleep(15)
target.icon = 'Normal_Icon.dmi'
view(target)<<"[target] reverted to his original form."
using=FALSE
cooldown=default_cooldown
Start_CD(target)

mob
var
skill
Super_Saiyan

Saiyan/verb
Super_Saiyan()
usr.Super_Saiyan.Activate(usr)
Login()
. = ..()
src.Super_Saiyan=new/skill/Transform
src.verbs+=/mob/Saiyan/verb/Super_Saiyan


Here, we just gave the mob a variable named Super_Saiyan. You have to define it under skill/, or it won't call Activate() when you use the skill. Then I made a verb that calls the user's Super_Saiyan variable's Activate() procedure. I then gave the mob this verb upon login and made the mob's variable be assigned to a new skill datum. And that is all there is to it.

-----------------------------------------------
I hope you learned from this, any questions, comments, or concerns are welcome in the comments. The more helpful comments I get, the better the next article I can make next time! Also check out my topic in the forums if you have any suggestions for next week!
On one hand, it's good to put a lot of premeditation into the needs your game will have on your code. On the other hand, you can take it too far, which renders it not very K.I.S.S..
[Edit:]
Skill datums can be handy ways to keep your skills sorted out or portable so you can distribute them to other mob types. However, the old fashioned way of putting them directly on the player mob has advantages to simplicity. It depends on what you plan to do.
Geldonyetich wrote:
On one hand, it's good to put a lot of premeditation into the needs your game will have on your code. On the other hand, you can take it too far, which renders it not very K.I.S.S..

True, but for things as important in an Anime game as skills, you need to make it as easy as possible to edit it later down the road.
while(_owner&&_owner.loc&&cooldown)
--cooldown
sleep(1)



dont you mean sleep(10)?
Jon is Awesome wrote:
while(_owner&&_owner.loc&&cooldown)
> --cooldown
> sleep(1)

dont you mean sleep(10)?

No. --cooldown takes 1 away from the cooldown. 1 is 1/10 of a second. In my example, you set it in ticks, not seconds. Of course, making it sleep for 1 second wouldn't cause many/any issues.
well usually people go by seconds but whatever
Jon is Awesome wrote:
well usually people go by seconds but whatever

You can easily show the things in seconds. In code, people usually go by ticks. In fact, they have to go by ticks. To go by seconds, you have to type 10. 10 ticks.

A simple:
owner<<"[round(cooldown/10)] second cooldown left."

Will show the cooldown in seconds.
Geldonyetich wrote:
Skill datums can be handy ways to keep your skills sorted out. However, it might actually be a better idea to put the skill variables directly into the player mob as procs and use verb arithmetic to determine of the player mob should access them. This is if it's directly on the player mob, it's easier to mentally associate that this is a player mob and this is what a player mob can do, and have handy access to player mob variables, ect.


There's a few nice properties of keeping them as datums that verb-skills don't have. For instance, you can have per-skill variables without having to hardcode them in the verb, or make a separate variable on the mob for each skill.

It also has an advantage when you consider the interaction of how people will tend to implement verb skill saving -- most of the time they simply save the verbs list -- with things like admin systems that also change the list of verbs.

And finally, getting an AI to call verb skills relies on things most people don't seem to know how to do:
var/skill/s = pick(skills)
s.Activate()
versus
var/skill_name = pick(skills)
call(src, skill_name)()


I will note that the implementation in the post has a bit of an issue with needing to use usr because there's no arguments to the procs to say 'this is the mob that is using this skill!' but that is something that is fairly easy to correct.
Destroy wrote:
I will note that the implementation in the post has a bit of an issue with needing to use usr because there's no arguments to the procs to say 'this is the mob that is using this skill!' but that is something that is fairly easy to correct.

What do you mean here? I used mob/target to say 'this is the mob that is using this skill'. And it in turn calls other procs that use mob/target. It all starts with the usr.Super_Saiyan.Activate(usr). I had to use usr twice.

Please elaborate on what you mean?
Albro1 wrote:
Destroy wrote:
I will note that the implementation in the post has a bit of an issue with needing to use usr because there's no arguments to the procs to say 'this is the mob that is using this skill!' but that is something that is fairly easy to correct.

What do you mean here? I used mob/target to say 'this is the mob that is using this skill'. And it in turn calls other procs that use mob/target. It all starts with the usr.Super_Saiyan.Activate().

Please elaborate on what you mean?

'Target' sounds like 'this is the person the skill is being used on', not 'this is the person using the skill.' Reading it again it does seem like you used it that way, but it is rather confusingly named.
Geldonyetich wrote:
Skill datums can be handy ways to keep your skills sorted out. However, it might actually be a better idea to put the skill variables directly into the player mob as procs and use verb arithmetic to determine of the player mob should access them. This is if it's directly on the player mob, it's easier to mentally associate that this is a player mob and this is what a player mob can do, and have handy access to player mob variables, ect.

In some ways it's a little clunky having to refer to the user of the skill as "owner" all the time but this can help to avoid some problems. When src is the mob using the ability you can accidentally reference the user's variables when you mean to reference the target's variables. Being in a datum the proc does not belong to a mob and you always have to specify "owner" or "target" to reference a variable.

Using objects lets you give variables to skills. The Super_Saiyan skill might have a variable-length cooldown. This makes more sense as a property of the skill than of the mob who has the skill.

Most importantly, using objects lets you use inheritance. Most skills have many things in common (cooldowns, MP cost, damage done, target type, etc.). Using separate verbs for each ability limits how much code you can re-use.
You'd be better off using objects for this than datums.
Using objects would work exactly the same, but objects can have physical locations within the game, and can be directly interacted with by players.

This allows you to give the skills an icon, and put them on screen for quicker access and so on (you could also do things like right click on it, and get information about the skill and what it does and so on). Rather than just having a list of verbs.
The Magic Man wrote:
You'd be better off using objects for this than datums.
Using objects would work exactly the same, but objects can have physical locations within the game, and can be directly interacted with by players.

This allows you to give the skills an icon, and put them on screen for quicker access and so on (you could also do things like right click on it, and get information about the skill and what it does and so on). Rather than just having a list of verbs.

Couldn't most of this be achieved by setting parent_type to /obj?
Albro1 wrote:
Couldn't most of this be achieved by setting parent_type to /obj?

You could do, but it's easier to type in "obj/" that it is to type in "parent_type=/obj".
The Magic Man wrote:
Albro1 wrote:
Couldn't most of this be achieved by setting parent_type to /obj?

You could do, but it's easier to type in "obj/" that it is to type in "parent_type=/obj".

You only have to set the parent type once. If you have:

Skill
parent_type = /obj


To instantiate it you type:

var/Skill/s = new /Skill()


This saves you from typing "/obj" every time you type out the Skill type path.