standardized buffs
A lot of discussion has happened over the last week or so on how to deal with overlapping buffs.
At present, there aren’t very many objects that give stat buffs - and there aren’t any that give skill buffs. Aside from my strong desire to avoid unbalanced +con buffs floating all over the place, the biggest reason for this sort of thing is probably just how horribly difficult it is to implement reliably.
The current method for giving someone a temporary stat buff is by giving an object a query_str_bonus() method (or con, dex, spd, int, etc…) and dropping it in the player’s inventory. You then wait until recalc() is called on the living - it is called very frequently. The result of all query_str_bonus() calls on the player’s entire inventory is added together in a stat_bonus[] hash that’s stored on all living objects and the contents of stat_bonus[] are then added to all stat queries…
there is a better way
In stead of requiring individual objects to track every buff and in stead of requiring frequent iteration over a player’s inventory and calling 10 methods on every object they’re holding… I think a single buff manager is in order.
The buff manager should be able to track bonuses to both stats and skills. While I will only address the topic of stats here, almost everything should be the same when it comes time to add skill buffs to the mud.
The buff manager should be able to handle both “permanent” and “temporary” buffs. A temporary buff is one that has a set duration measured in heartbeats - such as the result of drinking a potion. Permanent buffs are those that don’t necessarily have a measurable duration - like a bonus that lasts as long as you are wearing a pair of magic gloves.
The interface for dealing with buff manager should be trivial.
A potion that gives you +5 strength for 100 heartbeats might simply implement the following:
int on_consume( int max ) {
object manager = this_player()->get_buff_manager();
manager->add_buff( "alchemy", "str", 5, 100 );
return ::on_consume( max );
}
Gloves of +2 dexterity, a “permanent” buff might have code on them similar to this:
string buff_id;
int on_wear() {
object manager = this_player()->get_buff_manager();
buff_id = manager->add_buff( "equipment", "dex", 2 );
return 0;
}
int on_remove() {
object manager = this_player()->get_buff_manager();
manager->remove_buff( buff_id );
buff_id = "";
return 0;
}
The buff_id’s in this case would be necessary to distinguish between different “+2 dex from equipment” buffs, and would only be important for eventually expiring permanent buffs.
stacking bonuses
We are going to take a nod from d20 for how buffs interact. Every bonus is assigned a category. If multiple bonuses from any given category improve the same stat, the largest value is used.
Thus, if you drink two of the aforementioned strength potions, you will only get +5 str since they both provide an “alchemy” bonus. However, if the gloves were changed to provide +2 str, the “equipment” bonus would stack with the alchemical one for a total of +7 points.
- alchemy - potions
- divine - prayers and priest/druid style spells
- equipment - bonuses from worn equipment
- food - see the hunger system post for more here
- magic - scrolls and other normal channeled planeswalker style magic
- skill - bonuses generated by performing special abilities (like a ’sprint’ command that may give you +speed)
99% of all buffs should fall into one of these categories.
the opposite of buffs
The system should also handle stat penalties. Similarly, I think penalties from similar sources should combine - with only the worst one taking effect.
The list of categories for debuffs should include all of the buff categories already mentioned (boots that give you -dexterity, etc…) as well as a few others that are unlikely to be used for positive effects:
- curse - different than a normal ‘magic’ debuff, this one likely involves spirits
- disease - dragon pox!
- poison - poisons should be distinct than both alchemy and disease
- wound - take a nasty bump to the head? -1 int for a while…
In the event that both positive and negative effects of an identical source are active at the same time, the resultant bonus is the sum of the most positive value and the most negative value.
Example:
- One of my party members has an aura that grants +1 magic bonus to every stat to all nearby group members.
- I cast a spell on myself for +3 con.
- I am hit by a weakness spell that gives me -1 con for its duration.
Assuming all of these effects are of the “magic” variety, this gives me +1+3-1 con as well as +1 in all other stats. The +3 bonus overrides the +1 to con from the aura, but doesn’t affect the aura’s ability to improve my other numbers. The debuff is then added. Since there’s only one of them, the math is pretty easy - the final result is that I am at +2 con and +1 in my other stats.
future expansion
In addition to expanding the system to include skill modifiers, we might be able to get away with other modifiers as well: armour, damage absorption, hp/mana/end regeneration, carrying capacity, etc…
It should be possible to dispel some types of buffs and debuffs.
The command to show you the list of all of your current active buffs should simply be ‘buffs’.
I think it’s a good idea to centralize the buff system a bit more. However, the old system had some important properties that you have lost:
1. Interface simplicity. I think this can largely be solved by simply making the living object and the buff manager the same thing, and avoiding the “get_buff_manager” thunking annoyance. You’re adding a function to living *anyway*.
2. Updatability. Your system here relies very heavily on there never being a buggy object, ever. This is terrible design. The old system guaranteed that the buffs were always correct after a refresh, no matter how buggy the previous buffs were. This system has no way of saying “recalculate all my buffs to make sure they’re right.” You’re one badly-behaved object away from weird, sticky, evil permanent buffs.
I think the solution to this is easy — make sure the buff manager knows WHICH OBJECTS are providing the buffs, and allow it to ask what the current amount of the buff is. This avoids the ugliness of looping through the inventory, although IMO the correct answer to that is to start making players carry backpacks if they want to carry lots of stuff, thus avoiding the multi-hundred-object inventory problem. I do agree that having more than one query function for different buff types was a terrible idea — I would do it like this:
query_buff() { return ([ "str" : -2, "dex" : 5 ]); }
I think simplifying the buff system is a good idea, but I believe you’ve discarded some important and valuable properties with your proposal here.