ID:151627
 
Even though I've worked with abilities ever since I started working with BYOND, I still haven't come up with a method that I believe is both all-encompassing and effecient (for both processing AND programming).

First, a definition of an ability in this context: any usable THING that a player or NPC can use during or outside of combat, often known as attacks, skills or spells.

Abilities can take on many different forms - attack, healing, buff, debuff, DOT, HOT, transportation, enchantment, mechanic-bending, and just about anything else you can think of.

The question is: how do you adaquately represent abilities within the code, without duplicating code, remaining as efficient as possible, and still not a complete pain to program against?

Early on when I was new to development, I used a giant set of variables - damage, debuff1, debuff2, etc. (actually called maladict1/2 at the time) Over time I learned that not only was this not efficient to program with, but it was also extremely poor design from an object-oriented perspective.

Lately I've been using a single list in my code to manage the different properties of an ability. Generic things (like casting cost, time delay, or cooldown) still receive their own variables - because those are broad, they matter for 98% of all abilities. Specific mechanics however, such as damaging an opponent, healing yourself, healing your party, slowing time, creating natural disasters, or teleporting your enemy 50 yards away - all receive an entry in the list.

A self-damaging powerful attack might be:
effects[] = list(DAMAGE_ENEMY = 120, DAMAGE_SELF = 10, DAMAGE_TYPE = PHYSICAL)


Which would cause 120% physical damage to the enemy and 10% to yourself.

A debuff with a DOT component might be:
effects[] = list(APPLY_SLOW = user.GetInflictedCondition(SLOW), DAMAGE_OVER_TIME=20, DAMAGE_TYPE = MAGICAL)


(you'll notice the method call above, these lists are created within New() for substantially more flexibility, and since only one copy of each ability is instantiated it's not a load)

However even this system has it's limitations. Handling damage abilities that use multiple elements, for example, is hackish at best (looping through the list and executing each element in order, for example) and handling complex abilities that may require multiple usages of similar types is also poor.

What are your strategies for handling abilities in your own games - and why? I find that, no matter how often I revisit this topic, I don't tend to make much ground in solving the solution with the "best" method that feels the least like hacking, and still maintains the above criteria: fast to execute and fast to program.


~Polatrite~
The automatic, almost obvious answer for this is to represent each ability by a datum. Different behavior would be accomplished by simply overriding the effecting proc on subtypes, and the same behavior could be shared by multiple objects if deemed necessary.
I once posted an example of an "effects system", look at that for an example: [link]

In a complex game, you would have each effect handled by an /effect object, and one of the gameplay methods to apply effects would be spells, which would be implemented by /spell objects, and since those are abilities, they could be invoked from /ability objects (and non-mage characters would have other /ability objects, such as physical attacks or whatnot). So you might have an /ability object that governs a /spell (or a different) object(s), which in turn contains a list of /effect objects relating to it.

Note I keep saying 'objects' because it really doesn't matter if you pick to make them basic datums, or say, atoms. It works all the same. Basic datums are simply used for objects that don't need the extra baggage of atoms, but if, say, your /spell and /effect objects can be represented visually by a graphical display, then it could make sense to make them objs with an icon and all, rather than datums (that may need to keep track of a dummy obj in order to display themselves).

So your code tree could look like this:
mob/character
var/list
abilities
spells

obj/ability
proc/Use(mob/character/user)
push_north/Use(mob/character/user,mob/character/target)
if(!target)
if(!user.client) CRASH("not good")
target = input(user,"Push who?") as null|mob in oview(user,1)
if(!target) return 0
viewers(target) << "[user] pushes [target]!"
target.Move(get_step(target,NORTH),target.dir)
return 1
mage //subtypes don't have to mean much; maybe a non-mage can still get access to a mage ability
icon = 'mage abilities.dmi'
cast_spell
icon_state = "castspell"
Use(obj/spell/S,mob/character/user)
if(user.Hands_Are_Broken() || user.IsParalyzed() || user.UnderCurse())
user << "You cannot cast spells right now."
return 0
if(!S)
if(!user.client) CRASH("not good")
if(!user.spells || !user.spells.len)
user << "You know no spells!"
return 0
S = input(user,"Use what spell?") as null|anything in user.spells
if(!S) return 0

//extra args can be forwarded to spell
if(args.len > 2)
var/list/arguments = args.Copy(2)
S.Cast(arglist(arguments))
else S.Cast(user)
create_spell
mix_potion
draw_magic_stuff_on_the_floor
warrior
impale
decapitate
throw
jump
full_nelson
thief
sneak
run
cheat
steal
pickpocket

obj/spell
icon = 'spells.dmi'
var
spell_school
mp_cost
mob/character/caster
mob/character/target //could be a list
list/effects
proc/CalculateCost()
return src.mp_cost - (caster.int / 10 + caster.vars[src.spell_school] / 10) //you'd want a better formula and check for below 0, etc
proc/TakeCost()
caster.mp -= src.CalculateCost()
proc/SetupEffects()
proc/Cast()
lightning_rage
icon_state = "thunder"
SetupEffects()
src.effects = list(new /effect/damage/lightning(10,source = src))
Cast(mob/character/caster,mob/character/target)
//bla bla, check if target was given, \
check if caster can pay cost, make him pay cost, setup effects...


//activate spell
for(var/effect/E in src.effects)
E.Add(target)

effect
//...see other post for this part

Note the above was done rather crudely and late at night, and is intended more as an illustration of how the general structure and as such could look. You could implement stuff differently from how it's implemented in the example if you're so inclined.
list() is tipically only used for things like, random choosers and such. the code your using looks very VERY unstable at best (though whos to say it isnt). i handle codes for spells like this:
mob
spells
flame_burst //or any random spell/skill name
var
level = 3 //the level of the spell
verb
cast(mob/enemy/e in oview()) /*you need to have all enemies under
one main name*/

var/mob/spell/s
if(!src.used)
return /*if the spell has been used then it just returns until
the cooloff is finished*/

flick("spellcasting", src) //shows you casting the spell
src.used = 1 /*a player var telling weather he/she has casted a
spell*/

e.hp -= 120 //the enemy takes damage
src.hp -= 10 //so do you
src.spellcast()/*activates the spell cooldown sequence depending
on the spell level*/

e.death()
src.death() /*activates the proc that checks if the damaged mob
and you are dead*/

proc
spellcast(mob/spell/s) /*if a spell has been cast, no other spells can be
cast until the spell cooldown is finished*/

var/cooldown = s.level * 10
if(src.used == 0)
return
else
sleep(s.cooldown) /*being that s.coodown is an integer, it should
work*/

src.used = 0

This is just a test of what i would use. though list() can also be used to define spells/skills that where prepared after the player has rested. hope you like it and tell me if you find anything wrong with it.
In response to Dragonblix
Dragonblix wrote:
list() is tipically only used for things like, random choosers and such. the code your using looks very VERY unstable at best (though whos to say it isnt). i handle codes for spells like this:

Lists certainly aren't only used for "random choosers". Lists are one of the most powerful tools in BYOND for accomplishing a wide variety of tasks - including what I detailed above.

This is just a test of what i would use. though list() can also be used to define spells/skills that where prepared after the player has rested. hope you like it and tell me if you find anything wrong with it.

While your method certainly works, I don't think it captures anything at all that I am asking in my initial post. This system works, but it doesn't handle configuration elegantly and it doesn't support dynamic and interchangeable elements. I'm not really looking for a code snippet - I'm fine with programming - I'm looking for concepts and ideas that might shed more light on the end goal.


~Polatrite~
A few comments about your list method:

It's certainly not the worst way to handle things. You have a single function that processes a list and performs the actions. This is good because you're not repeating code. For many abilities (assuming this is the kind of game you're making) will need to check that you have sufficient MP to use the ability. If this check is repeated in every ability you cannot easily change it. For example:

Ability
proc
cast()
Fireball
cast(mob/caster)
if(caster.mp > 10)
// cast fireball
Frostbolt
cast(mob/caster)
if(caster.mp > 5)
// cast frostbolt


If you add a bonus effect that reduces the cost of all spells a player casts by 50%, you'd have to implement those changes in the cast() proc for both spells. Increase the number of spells you have and you'd need to make a lot of changes. If it's a hassle, you won't do it. That's why we set things up in such a way that it won't be a hassle to change.

Which brings me back to the original point, something you are doing correctly: By having a single proc to process the list and perform the effects of the spell, the MP check (among other things) would be performed in only one place. Good job! =)

I have noticed some problems with your approach. Your proc has to process the list and perform the actions. If the ability was implemented is a custom proc there would be no list and no list processing, the actions would just be performed. This would give you a minor performance boost, but probably wouldn't do much.

The bigger problem I see is that your lists can get quite complicated, making them difficult to edit. If they're difficult to edit (and if it's difficult to add new feaures), you won't do it.

My solution would be to use an object for an ability. Each ability has a proc that performs its actions. The procs make use of many helper functions to keep the procs simple. For example:

mob
var
mp
health
proc
mp_cost(c)
if(mp < c) return 0
mp -= c
return 1

inflict(damage, kind, mob/target)
// in theory this would be more complex, this is just an example.
target.health -= damage

Ability
proc
use()

Powerful_Attack
use(mob/user, mob/target)
if(!user.mp_cost(10)) return
user.inflict(120, PHYSICAL, target)
user.inflict(10, PHYSICAL, user)

// effects[] = list(DAMAGE_ENEMY = 120, DAMAGE_SELF = 10, DAMAGE_TYPE = PHYSICAL)


The use() proc of Powerful_Attack is about as simple as your list and the proc also enforces an MP requirement. It also gives us a lot of flexibility. Looking at that code, you know how to change the proc to inflict 10 fire damage to yourself instead of 10 physical damage. Looking at your list (included in the snippet for reference), I don't know how to make that change.

You may have one ability with really strange effects that require a lot of code. Doing things this way, the weird code is contained in that ability's use() proc instead of being part of the giant ability list processing proc.

You can create additional helper functions and make use of existing ones to create more complicated abilities.

Ability
AoE_Attack
use(mob/user, mob/target)
if(!user.mp_cost(20)) return

for(var/mob/m in range(2, target))
user.inflict(50, FIRE, m)


The generic mechanics can be handled by child classes. For example:

Ability
AoE_Attack
var
cost
range
damage
kind

use(mob/user, mob/target)
if(!user.mp_cost(cost)) return

for(var/mob/m in range(range, target))
user.inflict(damage, kind, m)

Weak
cost = 10
range = 1
damage = 30
kind = FIRE
Strong
cost = 25
range = 3
damage = 80
kind = FIRE


You also talk about more complicated abilities, DOTs (damage over time) for example. There is a distinction to be made here that doesn't simplify the work you need to do but it does simplify how you think about it. The ability merely places a debuff on a target, the debuff handles the effect of the DOT. For example:

Ability
Immolate
use(mob/user, mob/target)
if(!user.mp_cost(15)) return

user.inflict(30, FIRE, target)
target.add_debuff(new /Debuff/Immolate_DOT(user))


If you put too much responsibility on the ability it seems like an overwhelming thing to implement. You can simplify this by pushing responsibility to the debuff objects. Similarly, most spells have visual effects. By using helper functions you can create a complex visual effect with one line in the ability's use() proc. The ability creates the visual effects and debuffs, it doesn't manage them. Make the visual effects and debuffs manage themselves.

From a theoretical standpoint, on some level the behavior of an ability has to be hard-coded. Whether you're hard-coding it in a list or in the code of an object's proc, the behavior has to be there. You may think it's bad to hard-code the behavior in procs but you're doing the same thing with your lists. Just keep in mind that you were doing many good things with the lists and you'll eventually find an implementation you like.
In response to Polatrite
when i said "random choosers" i was just picking something right off the top of my head, and being BYOND is an object based progam, list() is absolutly one of the most powerful procs someone can use. yet i havnt really thought on creating an engine for the perpose of using spells with list(). i just dont really use spells in my games so ive found no use to create a list() engine for them. and explain exactly how you mean "it doesnt support dynamic & interchangeable elements". i'll need a little more detail here.
One thing I discovered when using a system similar to that, where I just defined a list of variables and had the game use them to make a spell was this.
It worked fine, it was relatively easy to make and adding spells was easy. It also allowed spells to be dynamic and they could be edited in game.

But the downside was that all the spells basically ended up boring. I could not make them unique in any significant way. And to add in conditions for all the unique effects I wanted to do ended up with it being longer, and harder to do that if I had just hand coded each individual spell myself.

What I ended up doing was making a few generic spells which could be customized. Such as a projectile, inflicting a status effect and so on. (They all used the same proc, just with different arguments, such as damage, damage type, graphical effect and so on)
But for more unique spells, I made them myself. (Though I had a lot of other procs which did a lot of the work, all I needed to do was code the unique effect and nothing else)

It is probably not the best system to use, but I found it to be quick and easy (in terms of speed, I added some, 312 spells in the period of 2 months, while working on the game on average maybe 2-3 hours a day, and I'd estimate only half of that time was spent on actually adding the spells) while being efficient (processor wise) and as flexible as I wanted it to be.