ID:265971
 
Instead of making a multitude of variables for certain buffs and debuffs, I want to do a datum (possibly a single datum for both, but if not, two; one for each). But I'm having issues in my head about how to handle the effects of the buffs and debuffs, and then how to check for them in a robust way to see if its effects should be applied.

mob/var/list/buffs
buffs
proc
passive_effects(mob/m)
visuals(mob/m)
New(mob/m,dur)
src.duration = dur
src.effects(m)
src.visuals(m)
if(!m.buffs) buffs = new
m.buffs += src
spawn(dur) del src


I'd then continue to make buffs children for each and every buff/debuff like so.

buffs
attack_up
passive_effects(mob/m)
m.atk_multiplier += 0.5 //adds 50% damage
exploding_punches
//no passive effects
//is checked when mobs "punch"?


This is where I run into my issue. What about buffs that don't have a global passiveness? Things that are only applied to specific situations. Should I check them like so?

mob/verb/punch()
var/dmg = 10
if(src.buffs)
var/buffs/exploding_punches/ep = locate() in src.buffs
if(ep) dmg += 10
Here's one idea. I don't really feel keen on explaining since I've typed this up while playing LRS, but if you have any questions feel free to ask.
// The /event datum really serves no purpose other than data encapsulation
event
var
value

static/const
// event types
ON_PUNCH = 1
ON_KICK = 2

New(value)
src.value = value

buff
proc
/*
CanHandle(event/e)
Tests if e.value can be handled by this buff
*/

CanHandle(event/e)

/*
Effect(val)
Should only be called if CanHandle() returned TRUE,
operates on val and returns a new val
*/

Effect(val)

exploding_punch
CanHandle(event/e)
if(e.value == e.ON_PUNCH) // buff can handle punch events
return TRUE
else
return FALSE

Effect(val)
return val+10 // exploding_punch buff only adds 10 to val



exploding_kick
CanHandle(event/e)
if(e.value == e.ON_KICK)
return TRUE
else
return FALSE

Effect(val)
return val*2.25 // exploding_kick causes kick to do 225% damage


mob
var/list/buffs
proc
punch()
var/dmg = 10

var/event/e // declare like so to access static var on next line
e = new/event(e.ON_PUNCH)

for(var/buff/b in buffs)
if(b.CanHandle(e))
dmg = b.Effect(dmg)
world << "[name] punches for [dmg] damage!"

kick()
var/dmg = 10

var/event/e // declare like so to access static var on next line
e = new/event(e.ON_KICK)

for(var/buff/b in buffs)
if(b.CanHandle(e))
dmg = b.Effect(dmg)
world << "[name] kicks for [dmg] damage!"

verb
Test_Punch()
buffs = new
usr << "Without buff:\t\..."
punch()

buffs += new/buff/exploding_punch
usr << "With buff:\t\..."
punch()

Test_Kick()
buffs = new
usr << "Without buff:\t\..."
kick()

buffs += new/buff/exploding_kick
usr << "With buff:\t\..."
kick()

You could vary this in several ways by changing, for example, the way you handle event information and how your buffs apply that information. Like I said, this is just one idea.

Edit:
I also wanted to note that if you're going to be using these procs frequently, it might also be worthwhile to create cache lists of which buffs could handle the event. For example:
mob
var/list/buffs
var/list
buff_punch_cache
buff_kick_cache
proc
InvalidateBuffCaches()
buff_punch_cache = null
buff_kick_cache = null

AddBuff(buff/b)
if(buffs)
buffs += b
else
buffs = list(b)
InvalidateBuffCaches()

RemoveBuff(buff/b)
if(b in buffs)
buffs -= b
if(!buffs.len)
buffs = null
InvalidateBuffCaches()

BuffFilter(event/e, val, list/cache)
if(cache) // now we only apply applicable buffs from cache, no need for CanHandle()
for(var/buff/b in cache)
val = b.Effect(val)
else
cache = new
for(var/buff/b in buffs)
if(b.CanHandle(e))
cache += b
val = b.Effect(val)
if(!cache.len)
cache = null // invalidate if no applicable buffs found

return val

Punch()
var/dmg = 10

var/event/e // declare like so to access static var on next line
e = new/event(e.ON_PUNCH)

dmg = BuffFilter(e, dmg, buff_punch_cache)

world << "[name] punches for [dmg] damage!"
That is basically the system I use. With minor differences.

Creating them is basically the same as you would do, but they have several procs for events.

Countdown(), which simply counts the proc down, loops once every second.

Effect(), is applied every x seconds, if the effect is something like poison and deals periodic damage.

Start(), when the effect starts.
End(), when the effect ends.

Then there is also on event procs, like OnAttack() (when you attack), OnHit() (when you are hit) and so on.
Whenever that event occurs, I simply loop through all status effects the mob has, and call the proc for that event.

Also, other things you may wish to take into consideration are defining who caused the effect to happen. If you poison someone, and it kills them, how does the game know it was you who poisoned the person unless you keep track of it?

Other than that, the system I use is exactly the same as that.

The system is actually so flexible, I use it for status effects, passive effects and item/equipment effects.
[link] contains some relevant info.
i'm not sure it's all that robust but usually I create a obj skill and define it with 4 variables. Buff
Duration, Effect and Value . It also has 2 procs. Apply() and Remove()

Duration is the length of time it lasts. Effect is the attribute affected and Value is by how much it is affected. Buff is a var that is either 1 or 0. If buff apply positive, if not apply negative.

Use of the skill will create an object.
When the object is created, it's Duration + world.time is stored in a var. This object will then apply the buff or debuff based on Effect and Value to the stat in question(Apply()). Next begin a loop.

This object will start a loop on itself upon creation with a sleep of 1 second between each iteration(or whatever the shortest amount of time is for your buff/debuff)

When this var is equal to world.time, undo the applied effects to the target(Remove()) and delete the object.

By using switch statements within the apply and remove procs you can automatically set up which stats are affected. The value you will affect how much those stats are affected and since apply and remove are called automatically the stats will always return to their previous amount.

You can even make more than one stat affected by making a special skill that creates more than one buff/debuff object to affect different skills.

Hope that wasn't too confusing. Sometimes I think so much that i get ahead of my own communications. o_O
In response to Dariuc
Looping every tick until world.time = whatever is really silly. Just sleep once. If you need to end it sooner, end it sooner. If you need to end it later, then a while() loop that only sleeps as long as needed is in order:

while(endtime < world.time)
sleep(endtime - world.time)
del(src)
In response to Garthor
or what he said ;)
In response to Kuraudo
I just noticed your use of "static" in your event datum. Is it the same as const? If it isn't, how is it different?
In response to Spunky_Girl
static works the same way as <code>global</code>.
In response to Kuraudo
I decided to go with yours as a model. I did a little tweaking to fit my game, but for the most part, it's just like yours.

event
var
v
static/const
onBuff = 0 //when a buff is casted
onDebuff = 1 //when a buff ends
onHit = 2 //when hitting someone
onCast = 3 //when casting
onStruck = 4 //when being hit
//and of course there's lots of other events
//each type of event response type may or may not have different arguments in their "triggerEffects" proc compared to the others

buff
proc
canHandle()
castEffects() //to do things such as visuals and any immediate effects
triggerEffects() //to respond to any possible triggers, such as adding to damage when striking someone
New(mob/m)
var/event/e
e = new /event(e.onBuff)
for(var/buffs/b in m.buffs)
if(b.canHandle(e))
b.triggerEffects(m)
In response to Spunky_Girl
As Metamorphman said, you're pretty much looking at the same modifier as "global" would be. In DM terms, "global" might be more proper, as it is what appears in the DM Reference, but they are interchangeable as far as I'm aware and other languages that I use, such as C++, use the "static" keyword.

Essentially, consider this case:
datum1/var/a

datum2/var/static/a

Now consider creating 5 objects of /datum1 and 5 of /datum2. Each /datum1 object will have its own variable, a. However, each datum2 shares their "a" variable, meaning only one copy is in memory instead of 5. Any changes to this variable affect all 5 /datum2 objects, however.

With const variables encapsulated by a datum, I feel it's good practice to also declare the variables static as they never change, nor do they need separate memory spaces.

Edit: It's also worth noting that with static member variables, you also don't have to initialize an object of the class. For example:
obj/var/static/staticVar

//........

var/obj/O
world << O.staticVar

In the above example, though O is null, you can still access staticVar because static/global variables belong to the type, and not to the individual instances. Keep in mind, though, that DM's compiler likes to issue "unused variable" warnings for things like this if you don't turn around and use O for something other than global variables.
Personally, I just make mine increase stats on New(), and then remove them on Del(). That way the bonus is added (in the form of stat increases), and then removed very easily (removing it comes down to deleting it, and you can delete it whenever you want; for example, in order to prevent it from being saved, delete it before saving, or something).