ID:1462192
 
DATUMS ARE AWESOME!!! ...but what are they?

This tutorial is aimed at anyone who understands the basic concepts of BYOND, mainly the atom (area, turf, object, mob) concept. If you don't know what they are, read up on them first (or at least check out "My First World", a basic yet excellent starting point for this tutorial).

You probably have seen the enigmatic Datum floating around, or have heard about it somewhere. But, you either haven't cared for it or was probably confused by it. What is a datum? What does it exactly do? How do I use it's awesomeness? Well, I am here to show you how!

So, what is a Datum? A datum is an anything and an everything.

...

No, really. That's exactly what it is.

...

Okay, before hitting that back button, let me clarify what I mean for both programmers and people who want to just have a game up and running.

A datum, is an object—no, not the BYOND /obj, but an object in programming. An object in programming can be anything and everything, provided you tell it what to do! Your object is a chair? Tell it to stay put. Your object is a cat? Tell it to purr. Your object is a pie? Make it throwable or edible! Datums also have the added benefit of making code easier to read and understand, and can also be used for fun tricks that wizards of the DM language like to keep secret.

So, let's start by stepping back a bit and look at something that we are really familiar with: the object (the BYOND /obj this time!).

The almighty parent_type var

So you probably did this at one point or another:

obj
wall
density = 1
window
density = 1
opacity = 0
coin
var value = 1
Crossed(mob/m)
m.coins += value //Adds a coin to the players who touch it
if (m.coins >= 100)
m.lives += 1


We have a wall, a window, and a coin. Naturally, we can't walk through the wall because of it being dense; we can't walk through the window but can see through it because it's not opaque, and we have a coin that gives the mob a coin. What's wrong with this code? Nothing! But what if we added a blue coin?

obj
wall
density = 1
window
density = 1
opacity = 0
coin
icon = 'coin.dmi'
icon_state = "normal_coin"
var value
Crossed(mob/m)
m.coins += value //Adds a coin to the players who touch it
if (m.coins >= 100)
m.lives += 1
blue_coin
value = 5
icon_state = "bluecoin"


Is there anything wrong? No, not really. You can even put in another “Crossed” proc for an explosive coin, or a coin that gives you coins and explodes would be under obj/coin/exploding_coin, or if you want a blue_coin that explodes you'd do something like obj/coin/blue_/exploding_coin (You will need the ..() this time around or else it won't execute the code that gives you the coins!).

obj
wall
density = 1
window
density = 1
opacity = 0
coin
icon = 'coin.dmi'
icon_state = "normal_coin"
var value
Crossed(mob/m)
m.coins += value //Adds a coin to the players who touch it
if (m.coins >= 100)
m.lives += 1
blue_coin
value = 5
icon_state = "bluecoin"

exploding_coin //A really risky treasure!
Crossed(mob/m)
m.damage(1)
..() //Call the parent, or we won't get any coins!


So you're probably wondering "What's the point of that"? If you read through all of that kerfuffle and understood everything then you are ready to understand the enigma that is the datum.

So again, what is a datum? Let's start by making a coin datum instead of a coin /obj.

Coin


Congratulations, you made your first datum!

...

Now, you're probably thinking “What”, but that is essentially how you can declare a datum. If you compile it, you will get no errors. Now, let's add in (read: copy and paste) our code for handling the coin when a player touches it:


Coin
icon = 'coin.dmi'
icon_state = "normal_coin"
var value
Crossed(mob/m)
m.coins += value //Adds a coin to the players who touch it
if (m.cions >= 100)
m.lives += 1


Now compile the code, and you will get compilation errors. Confused as to why you can't use icon_state and icon? Well here's the reason: Coin does not have the icon and icon_state vars by default. Even if you declare them with var icon = 'coin.dmi' and var icon_state = “normal_coin” and compile, you'll notice something else: You can't place instances of it in the map editor!

There is a very good reason for this, but for now let's look at the thing that will make our coin work as before: the parent_type var.

The parent_type can make your object (datum) inherit properties from other objects (datums or etc.). Keep in mind that parent_type cannot be changed when the code is running, so you have to declare it outside of any procs or verbs. If you try to change parent_type inside of a proc or verb, your procedure will crash!


Coin
parent_type = /obj //Behold! Now the coin is useable!
icon = 'coin.dmi'
icon_state = "normal_coin"
var value
Crossed(mob/m)
m.coins += value //Adds a coin to the players who touch it
if (m.cions >= 100)
m.lives += 1


With the parent_type var it officially is transmogrified into a usable coin, and is just like the object that you are familiar with before! You can even add instances of it in the map editor, and code that looks for general objects will see Coin. However, there is one catch: the type will be /Coin instead of /obj/Coin. If you create a verb that tells you the object type...

Coin
parent_type = /obj //Behold! Now the coin is useable!
icon = 'coin.dmi'
icon_state = "normal_coin"
var value
Crossed(mob/m)
m.coins += value //Adds a coin to the players who touch it
if (m.cions >= 100)
m.lives += 1

verb
Tell_Me
set src in view()
usr << type //It will return "/Coin"


...you will get /Coin. Not only does it save keystrokes, but it's easier to read and understand. You can even take it a step further and add in the blue and explosive coins, and it will work just like before. Let's add in the other coins:

Coin
parent_type = /obj //Behold! Now the coin is useable!
icon = 'coin.dmi'
icon_state = "normal_coin"
var value
Crossed(mob/m)
m.coins += value //Adds a coin to the players who touch it
if (m.cions >= 100)
m.lives += 1
Blue_Coin
icon_state = 'bluecoin'
value = 5
Exploding_Coin
Crossed(mob/m)
m.damage(1)
..() //BOOOOM!


And it works like before. However, let's use some of that wizardry I mentioned earlier and make the code a but easier to read:

Coin
parent_type = /obj //Behold! Now the coin is useable!
icon = 'coin.dmi'
icon_state = "normal_coin"
var value
Crossed(mob/m)
m.coins += value //Adds a coin to the players who touch it
if (m.cions >= 100)
m.lives += 1
Blue_Coin
parent_type = /Coin
icon_state = "bluecoin"
value = 5

Exploding_Coin
parent_type = /Blue_Coin
Crossed(mob/m)
m.damage(1)
..() //BOOOOM!


What changed? Not much, but it's much easier to read! Of particular interest is Exploding_Coin, since we see the power of the parent_type var; we can make it inherit almost anything we want (Except for /world and /area). Keep in mind that its type won't be /Coin/Blue_Coin/Exploding_Coin, but simply /Exploding_Coin. Another cool thing you can do is declare parent_type inside of datums that inherit from other datums.

Coin
parent_type = /obj //Behold! Now the coin is useable!
icon = 'coin.dmi'
icon_state = "normal_coin"
var value
Crossed(mob/m)
m.coins += value //Adds a coin to the players who touch it
if (m.cions >= 100)
m.lives += 1
Blue_Coin
parent_type = /Coin
icon_state = "bluecoin"
value = 5

Deceitful_Jerkarse_Coin
value = 15000
parent_type = /Exploding_Coin

Exploding_Coin
parent_type = /Blue_Coin
Crossed(mob/m)
m.damage(1)
..() //BOOOOM!


But there is a catch to it, and it's a bit more subtle. If we give Exploding_Coin a value of 500 coins and the player touches Deceitful_Jerkarse_Coin, it will ignore the value of the Exploding_Coin (500) and instead give the value of the Deceitful_Jerkarse_Coin (15000).

Play around with it, but come back! There's also the subject of what to do when you're not using the parent_type var.

Datums without parent_type

So what about a datum without a parent_type var? Is it useless? No, but it handles a bit differently. From here on, let's look at the datum as an object (programming), and not as an object like the Coin. A datum can be used for a container (Hey! That's a good way to look at it!) of data that can be accessed when you need it (provided it is accessible!). So how can we use it?

How about something like... mmm... Achievements!

Now yes, I know that it is supported in the form of Medals with the BYOND hub and procs do exist for them. However, let's pretend that the hub didn't support them.

First, let us create our achievement datum, with a proc for adding and removing achievements.

Achievements
var list/achievements = list()
var gamerscore
proc
Add_Achievement(Achievement/a)
for (var/Achievement/b in achievements) //Find our achievement in the list.
if (a.name == b.name)
return //If the achievement exists already, end and walk away.
achievements += a
gamerscore += a.score_value

Remove_Achievement(Achievement/a)
//For those annoying cheaters!
achievements -= a
gamerscore -= a.score_value


And while we're at it, let's make an Achievement to put into our bucket of Achievements:

Achievement
var name //Since this is not an /obj, we have to make new variables!
var desc //Desccription
var icon //The icon for the picture
var icon_state //The icon_state.
var score_value //The value we add to our gamerscore


Now since this is not an /obj ...how do we use it?

Remember what I said about a container for data? Well, we're going to put Achievement objects in our Achievements container, which we are going to make our /mob (player) hold.

mob
player
var/Achievements/Achievements_Bucket


Now, the /mob has a bucket of achievements at his disposal! We can add and remove Achievements as we see fit. Now let's put our new bucket to use!

Let's say reaching the end of a course in under a minute grants you an achievement.
Jello_Canyon
parent_type = /Level
Course_Complete(mob/m)
if (time_remaining >= 2400)
var/Achievement/a = new //Create a new achievement
a.name = "Jello Bouncer"
a.desc = "Complete the course, \"Jello Canyon\", in under sixty seconds."
a.icon = 'achievement_pictures.dmi'
a.icon_state = "jello_canyon_clock"
a.score_value = 10
m.Achievement_Bucket.Add_Achievement(a) //Add the achievement (or attempt to, based on our code above!)
..() //My Level object has code for returning you to your spot on the world map and etc., so this is needed.


What this does is create an achievement object and adds it to our /mob's Achievement Bucket. And if we want to get the achievement data for some reason (i.e. displaying achievements already obtained), we can create something that outputs the achievement data. We already have our icon, icon_state, name, and desc variables handily stored in /Achievement and ready to use; you just have to write the logic to look into the bucket and display it!

I hope that clears things up for you with datums. I too didn't get them at first (despite playing with object-oriented languages and the fact that BYOND is a bit weird in some areas), but once you understand them you will be a BYOND-coding monster. <3

I hope this helps you out well and helps you on your coding adventures!
This was actually quite helpful to me.

I already know the use of parent_type and have seen it in action, but I didn't really quite get all the info on its usefulness, until I somewhat read this post :p
This is pretty good, but you may want to proofread it.
In response to Albro1
And done. Hopefully I got everything, but if I didn't then feel free to let me know.