Research_Buff_Manager

Research into Buff Manager

Welcome!

I am Josep Lleal Sirvent, student of the Bachelor’s Degree in Video Games by UPC at CITM. This content is generated for the second year’s subject Project 2, under supervision of lecturer Ricard Pillosu.

I understand that if you are reading this is because you are interested in this topic, one of the cores of the MMORPGs genre. Before I start explaining how to implement a Buff Manager with C++, it is essential to clearly understand how do Buffs work and to know all the different existing types. So, with this being said, lets start:

What is a Buff?

Before explaining what a Buff really is, I want to introduce the therm status effect:

“A status effect is a modification to a game character’s original set of stats that usually comes into play when special powers and abilities are used. The term status effect can be applied both to changes that provide a character an advantage (increased attributes, defensive barriers, regeneration), and those that hinder the character (decreased attributes, incapability, degeneration).” Wikipedia

With this definition we can clearly distinguish two types of status effects, the ones that provide an advantage and the others that provide a disadvantage:

Healing in Apex Legends

Poison affecting Apex Legends players


Types of effects

When talking about types of effects the most commonly classification is dividing them into two main groups: buffs and debuffs, as I stated before, but indeed, we can organize them in different ways depending on how we approach them. For instance, the classification of buffs and debuffs is approached by looking how an effect affects an entity. The other approaches are looking the durability of an effect and how an effect is applied(referring to the mathematical method used).

Durability of an effect

When looking at the durability of an effect we can classify them in four groups: temporary, per tick, permanent and conditional effects.

Janna Eye of the Storm ability

Health potion healing League of Legends champion

Nasus passive

Garen's passive

Notice that in this case Garen's passive is a mixture of a per tick and conditional effect

Method used

If we approach effects looking how these are applied mathematically to the stats of an entity, we can classify them in three types: Effect applied by adding, by multiplying and by adding a percentage.


Buff Manager implementation

Well, now that the topic has been properly introduced, let’s head to a very simple implementation of a Buff manager. Our goal is simple, create effects with XML, read and pass them into code and apply them correctly.

Creation of effects

The first step to do in order to create effects is to create an struct containing all the data that an effect will need:

struct Effect 
{
	std::string		name;

	EffectType		type;
	EffectTime		duration_type;
	EffectMethod		method;
	Attribute		attribute_to_change; 

	int			bonus;
	int			duration_value;
};

XML File

To create one or more effects with an XML file this structure has to be followed:

<?xml version="1.0" encoding="utf-8"?>
<buff_manager>
  
  <effect id ="0" name="healing" type="BUFF" duration_type="PERMANENT" method="ADD" att_to_change="HEALTH" bonus="20" duration_value="0"></effect>

</buff_manager>

As we can see, all the variables are the same as the ones in the effect struct… Oh wait! There is a variable called id, what’s that? Ok, calm down, just for now the only thing you need to know about this variable is that whenever a new effect is created in this file, the new effect id will be latest effect id + 1. So for instance if we want to create a new effect, his id would be 1 (0+1).

Read XML File & Store Effects

In order to read the XML file I have made a function called LoadEffect(). This function basically iterates all the < effect > nodes and stores all his attributes in a temporary Effect created in the same function. Once all the variables of the effect are set, we store this effect in an array called effects (created in the BuffManager.h). Remember that id I talked about? This is the moment to use it! To store the effect in the array we will do the following: effects[new_effect_id] = new_effect;

IMPORTANT: with all the enum-type variables of the effect, in order to set properly their type I have created a function called SetValue(&effect, string) that basically “converts” the string passed into the corresponding enum-type.

Click here to see the function (IF YOU HAVEN'T DONE THE TODO's DO NOT CLICK)

Apply Effect to an Entity

Before I start explaining how to apply an effect, I’m going to explain briefly two things: how to acces to the desired effect (without having to remember each effect’s id) and how to “manage” effect’s timers and attributes of an entity.

Acces to desired Effect

Let’s say, for instance, that you have created 4 effects that are stored in the effects array. If you want to acces to the third created effect you would do this effects[2]. But what if you had 20 effects? it would be pretty tough to remember each effect position, right? Be happy, there is a simple solution!

In BuffManager.h I have created an enum called Effects. Each time you create a new effect from the XML, go to this enum and put the effect’s name (it is essential to put all the effects in the same order as the XML). So now, to acces to the desired effect just do this: effects[EFFECT_NAME].

Entity’s Attributes & Timers

ATTRIBUTES:

For the attributes there is only one thing to remark, each attribute has two integer variables: the original value and the actual value.

TIMERS:

We will need to create a timer for every TEMPORARY and PER_TICK effect. In addition, we will also create a boolean to check if the effect is active or not. For a PER_TICK effect, we will create an integer that will be used to upgrade an attribute each tick.

See these two effects examples to help the understanding:

//POISON --- PER TICK EFFECT --- reduces live each tick
j1Timer		poison_tick;
bool		poison_tick_active = false; 
int		poison_tick_iterator = 0;

// WAR CRY --- TEMPORARY --- gain extra strength for a limited time
j1Timer		war_cry;
bool		war_cry_active = false;

How to Apply our effects?

To apply effects I have created two main functions: ApplyEffect() and DoMath(). ApplyEffect() is the function that you will call when applying an effect and DoMath() is the responsible, as his name says, to do all the operations. This second function is called inside ApplyEffects().

When calling the function ApplyEffects() you will need to pass two parameters: the effect you want to apply and to which entity, so for instance, it would look something like this: ApplyEffects(&effects[EFFECT_NAME], Entity);

I’m not going to explain entirely how ApplyEfefcts() works but I do have to remark a few things:

ApplyEffect() firstly checks the duration_type [PERMANENT, TEMPORARY or PER_TICK] of the effect and depending on the type it does different things. For PERMANENT effects is 100% automatic, however for the other two types that require a timer the function has to be updated every time a new effect is created.

See this 2 pieces of code inside ApplyEffect() to understand it clearly:

else if (effect->duration_type == TEMPORARY) // we have to put manually every NEW EFFECT that has a TIMER (and create the timer in entity.h or in this case in Player.h)
	{
		switch (effect->attribute_to_change) // check what attribute modifies
		{
		case HEALTH:
			if (effect->name == effects[HEAL].name)
			{
				if (entity->heal_active == false)
				{
					DoMath(entity->health, effect->bonus, effect->method, effect->type);
					entity->heal_active = true;
				}
				entity->healing.Start(); // timer starts
			}
			break;
		}
	}
else if (effect->duration_type == PER_TICK)// we have to put manually every NEW EFFECT that has a TIMER (and create the timer in entity.h or in this case Player.h)
	{
		switch (effect->attribute_to_change)
		{
		case HEALTH:
			if (effect->name == effects[POISON].name)
			{
				if (entity->poison_tick_active == false)
				{
					entity->poison_tick_active = true;
				}
				entity->poison_tick.Start(); // start or restart timer
				entity->poison_tick_iterator = 0; //restart iterator
			}
			break;

To finish with this section, I’m going to introduce two more functions that are used in Update, these are RestartAttribute() and ApplyEachTick(). The first is only used for TEMPORARY effects while the second is only used for PER_TICK effects.

RestartAttribute(effect, entity) compares the timer with the duration_value of the effect, and when the timer is bigger, the attribute value of the entity is restarted using the og_attribute value. This function also has to be updated whenever a new PERMANENT effect is added.

ApplyEachTick(effect, entity) is in charge of calling the DoMath() function every tick (notice on the code above that when we apply a PER_TICK effect the DoMath() function is not called). This function also has to be updated whenever a new PER_TICK effect is added.


TODOs

OBJECTIVE: You have to create a new effect called Ghost which has to be a TEMPORARY BUFF. When Ghost is applied (clicking button 1) the player’ SPEED should increase +10 lasting 3 seconds. Good luck!

Download the release to see the final result HERE.

CLICK HERE to download my repository, inside the folder Exercise open the Handout to start the doing TODOs.

TODO 0:

Create the effect in the XML file with all his correct properties.

Click to see TODO 0 solution

TODO 1:

Add the loaded effect to the effects[] array located in BuffManager.

Click to see TODO 1 solution

TODO 2:

Insert the new effect into the enum of all the current existing effects.

Click to see TODO 2 solution

TODO 3:

You have to create tqo variables: ghost’s j1Timer and a bool ghost_active initialized as false.

Click to see TODO 3 solution

TODO 4:

Inside the function ApplyEffect(), you need to check if the effect’s name that is passed is equal to the GHOST effect’s name (REMEMBER: to acces to your created effects use the array effects[EFFECT_ENUM]. Then, if the effect is not active make it active and apply the function DoMath() correctly. To finish start the timer.

Click to see TODO 4 solution

TODO 5:

Once again, inside the function RestartAttribute(), you need to check if the effect’s name that is passed is equal to the GHOST effect’s name. Now, if the entity has the effect active && the effect has ended: reset the speed value (use the og attributes ;D) and put the effect as non-active.

Click to see TODO 5 solution

TODO 6:

In the Update of the entity call the necessary function to, when the effect finishes, restart the upgraded attribute.

Click to see TODO 6 solution

TODO 7:

To finish, apply the GHOST effect to the player when pressing button 1.

Click to see TODO 7 solution

If you have done all TODOs correctly, your final result when clicking button 1 should be this:

Applying Ghost effect


Improvements on the system

Webgraphy