ID:1226936
 
I've used this for a while, and it seems that people share code quite freely anyway so to my knowledge it's already circulating out there anyway.

I use a system similiar to this to create simple or complex character systems. This is based off of a tutorial by LummoxJr, modified for my own needs over time.

For those unfamiliar with a datum, it is an "blank slate" so to speak. An atom that has no real qualities of it's own except what you give it.

This is by no means the only use for datums, this is just a use that I've adopted and would like to share.

To create a datum you simply decide what you'd like to call it.
MyDatum//datum type declared


From there it's like using any other atom, you can apply procs, variables and such as needed. Moving on we will use datums to create a basic character that could be used in an rpg type game.

MainStat
var
current=100
maximum=100
Attribute
var
current=10


This creates two datums which shall be used for the health and magic, as well as any attributes for our character.

To apply them to the character they are used like this:

mob
var
MainStat/Health=new
MainStat/Mana=new
Attribute/Strength=new
Attribute/Defense=new


The code above gives all mobs a datum variable called Health, Mana, Strength and Defense.

Which you can then use like so.
mob.Health.current-=5
mob.Strength.current=5


How is this any different from just declaring a normal variable? Well read on.

Let's get into the procs needed for our adventure. Let's start with some basic ones for Losing health, gaining attributes and the like.

Normally you would have to write a proc for each , lose health, lose mana, and so on- with datum based character development you no longer have to do that.

mob
var
MainStat/Health=new
MainStat/Mana=new
Attribute/Strength=new
Attribute/Defense=new
proc
LoseMS(var/MainStat/M,var/amt)
M.current=max(0,M.current-amt)

GainMS(var/MainStat/M,var/amt)
M.current=min(M.maximum,M.current+amt)

GainAttr(var/Attribute/A,var/amt)
A.current+=amt


As you can see the code now has 3 new procs which will handled losing health, mana and gaining strength and defense altogether which saves a bit of typing and simplifies things.

To use them you would do something like this-
mob.GainMS(Health,200)
mob.GainMS(Mana,50)
mob.GainAttr(Defense,2)



For ease of use you can wrap them up into a proc like this in a very common example.

LevelUp()
GainAttr(Strength,2)
GainAttr(Defense,2)


This also reaches to examples of dealing damage, you could also easily expand this, by editting the datums. Let's say you want to do away with traditional levelling, and apply stats that level up independent of one another. You could easily do that with a datum based character.

MainStat
var
current=100
maximum=100
experience=0
exp_needed=100

Attribute
var
current=10
experience=0
exp_needed=100



mob
var
MainStat/Health=new
MainStat/Mana=new
Attribute/Strength=new
Attribute/Defense=new
proc
LoseMS(var/MainStat/M,var/amt)
M.current=max(0,M.current-amt)

GainMS(var/MainStat/M,var/amt)
M.current=min(M.maximum,M.current+amt)

GainAttr(var/Attribute/A,var/amt)
A.current+=amt

LevelUp()
GainAttr(Strength,2)
GainAttr(Defense,2)

IncreaseMS(var/MainStat/M,var/amt)
M.maximum+=amt

GainMSExp(var/MainStat/M,var/amt)
M.experience+=amt
if(M.experience>=M.exp_needed)
LevelMS(M)

GainAttrExp(var/Attribute/A,var/amt)
A.experience+=amt
while(A.experience>=A.exp_needed)
A.experience-=A.exp_needed
GainAttr(A,1)
sleep(1)

LevelMS(var/MainStat/M)
while(M.experience>=M.exp_needed)
M.experience-=M.exp_needed
M.maximum+=10
sleep(1)


In this updated code you can now apply experience points to individual attributes and main stats, allowing you to create health that raises as you take damage, or strength that increases as you attack foes or shatter rocks for example.


The benefits of this type of system can be applied to many things, skills like blacksmithing, potion making or as you have seen here. If you expanded this to give your character more than two main stats and 7 to 8 different attributes, you'd still only need to use the base procs that are declared here to manipulate them, or you could continue to add even more functionality to them by elaborating on the datums.

An example of that would be using it to hold buff/debuff values, regeneration rate specific to each main stat or modifiers to the effect of each attribute to name a few.


As many benefits as there are to using something like this there some things to look out for:

If you fail to create a new /datum when you attach it to a mob it will give an error thus when you apply a datum as a variable, or any atom for that matter, you always need to check if it exists using isnull() or initialize it by doing like above:
mob
var
MainStat/Stamina=new


One other thing of note is that when handling more than one mob, you must be observant that you don't mix them up and apply unwanted effects.
verb
SmackYaOne()//this would hit yourself.
for(var/mob/M in oview(1))
M.LoseMS(Health,55)

SmackYaOneV1()//this would hit anyone in view of 1.
for(var/mob/M in oview(1))
M.LoseMS(M.Health,55)


Normally, I'd give the stat datum its own "gain" and "lose" procs.
MainStat
var value = 100

var min_value = 0
var max_value = 100

proc/gain(n) value = min(value + n, max_value)
proc/lose(n) value = max(value - n, min_value)


mob
var MainStat/health = new

verb/Smack()
for(var/mob/m in ohearers(1, src))
m.health.lose(55)

I'd also let the stat know what mob it's attached to, which also lets you do things like:
mob
var MainStat/Health/health

New()
..()
health = new (src)


MainStat
var mob/owner

New(mob/owner)
src.owner = owner

Health
lose()
if(value > min_value)
..()
if(value == min_value)
owner.die()
I see this done a lot, and I don't understand it myself. It just adds overhead and memory consumption for little to no gain.
In response to Kaiochao
That's mostly why I don't use that approach, so I don't have to link each datum to the mob it's set to. It's sort of a change up, I just see it as more convenient to work with.

I prefer to go ahead and write a wrapper proc like LoseHealth(var/amt), with the LoseMS inside of it, it also prevents the problem I mentioned near the end with mixing up which mob loses health or whatever.
In response to Ter13
The gain is essentially uniform bounded behaviour (energy never moves out of a range of 0 - max_energy or some such), which is well worth the more or less non-existent memory and performance overheads. It's all just about making you not care about the potential of even making the rookie mistake of bringing energy to -50 or some similar non-sense value.

I think also I had written on this subject some time before? http://www.byond.com/forum/?post=98823
Regardless, that can be achieved by inserting handler functions into the mob itself. I'm not saying that it has no benefit... just that the benefits don't compel me to believe that this approach is worth spending datums on.
In response to Ter13
Ter13 wrote:
Regardless, that can be achieved by inserting handler functions into the mob itself. I'm not saying that it has no benefit... just that the benefits don't compel me to believe that this approach is worth spending datums on.

There is next to no overhead. Datums don't have anything additional unless you attach it. No icons, no procs,vars or built in functionality. It's actually way less overhead intensive than the hooks tutorial you posted yesterday. In fact it's also easier to find and keep up with variables this way because they are associated with something by a "root", thus you know where to look.

With anything though there is design to keep in mind so of course if a person were to go nuts with this type of design- it could be detrimental to a project, but the same applies for basic programming practices like loops, for instance.


This reminds me of a debate over the Stat() proc. Someone mentioning that calling procs inside state cause cpu to go extremely high, something I know for a fact isn't true because it's done all the time when people make stat panels that people are so fond of. Not to mention I use it pretty often and have been working with it for a long time. Any code can be optimized if you have proper design theory in mind and in some cases this is much more beneficial, rather than making 4-5 procs to do something you in most cases have 1-2. Win-Win-Win
I'm not teetotaling here. I'm pointing out that there is an upper limit on the number of datums you can use.

Second, the hook tutorial is a different animal. It implements behavior that otherwise could not be implemented efficiently. This is more of a design preference, and not a particularly compelling one.
Well, in an OO language it's the preferred way to model it, the OO way, as it were. It's a distinct behaviour with associated data, and would be encompassed as such, so that it can be applied via composition to any object (or function) you quite fancy having any range-restricted value.

Cost-wise, you have 16.7 million datums at run-time, and realistically, most games people are making don't hit that, or really the 65 thousand compile-time limit either.

I would probably only look to shifting that functionality back onto the obj/mob hierarchy once I'd hit an issue with said limits, as opposed to pre-maturely optimising. As a design preference, I'd actually need a compelling reason (like a genuine risk of hitting or actual hitting of said object limits) to take a more functional approach and that would be on a case by case, project by project basis.

In the absence of any such issues, I'd defer to the object approach, hands down, every time, no question about that.