namespace RPG.Stats
{
public class BaseStats : MonoBehaviour
{
// Range will add a slider in unity where we can set our starting level.
[Range(1,99)]
[SerializeField] int startingLevel = 1;
// the character of our class, we choose our class from the enum, inside unity.
[SerializeField] CharacterClass characterClass;
// start it as null, if there is none, we dont get any compilation problems. we add the progression scriptable object.
[SerializeField] Progression progression = null;
// the particle effect when we level up, prefab set in unity.
[SerializeField] GameObject levelUpParticleEffect = null;
// enemies should not use modifiers.
[SerializeField] bool shouldUseModifiers = false;
// lazyvalue, we dont set it to a number since its a container for initialization.
LazyValue<int> currentLevel;
Experience experience;
// delegate that has no arguments.
public event Action onLevelUp;
// set up state in awake and not start. want state to be set by the time other start
// methods might be calling into basestats.
private void Awake()
{
Experience experience = GetComponent<Experience>();
currentLevel = new LazyValue<int>(CalculateLevel);
}
private void Start()
{
// make sure we initialize healhpoints if its not called before this start.
currentLevel.ForceInit();
}
// when basestats is enabled we suscribe to update level.
// register callbacks in onenable function, its best practise, dont do it in start. gets called about same time as awake.
// but always after it. Dont get race condition with awake. same rules as for awake
// you cannot set up external functions because you dont know if their state has been set up yet.
// here we just register a call back, its not an external function since we are not updating any states.
private void OnEnable()
{
if (experience != null)
{
// add update level to delegate list.
experience.onExperienceGained += UpdateLevel;
}
}
// if basestats is disabled we remove the subscription to updatelevel.
// if something disables basestats it could still continue to get notifications from experience when experience
// is gained. so we dont get callbacks when basestats is disabled.
private void OnDisable()
{
if (experience != null)
{
// remove update level from delegate list.
experience.onExperienceGained -= UpdateLevel;
}
}
// update our level.
public void UpdateLevel()
{
int newLevel = CalculateLevel();
if(newLevel > currentLevel.value)
{
currentLevel.value = newLevel;
LevelUpEffect();
// set health points to max when we level up. we add regeneration function in health script.
// If there are no listeners to an event, it is null. When invoking events you should always check if they are null first.
// so we dont get a null refference error.
if (onLevelUp != null)
{
onLevelUp();
}
}
}
// instantiate level up effect around player.
private void LevelUpEffect()
{
Instantiate(levelUpParticleEffect, transform);
}
public int GetLevel()
{
// will initialize it if it healthpoins isnt already initialized
return currentLevel.value;
}
// Gets basestat + added modifiers * percentage bonus(if 10% we make it 1.1 = 110 percent).
public float GetStat(Stat stat)
{
return (GetBaseStat(stat) + GetAdditiveModifier(stat)) * (1 + GetPercentageModifier(stat) / 100);
}
// returns stats level value from progression.
private float GetBaseStat(Stat stat)
{
return progression.GetStat(stat, characterClass, GetLevel());
}
// add upp all the modifiers that we have stored for a particular stat in the GetAdditiveModifier function
// in the IModifierProvider.
private float GetAdditiveModifier(Stat stat)
{
// enemies dont get modifiers.
if (!shouldUseModifiers) return 0;
float total = 0;
foreach (IModifierProvider provider in GetComponents<IModifierProvider>())
{
foreach (float modifier in provider.GetAdditiveModifiers(stat))
{
total += modifier;
}
}
return total;
}
// add up all percentage modifiers.
private float GetPercentageModifier(Stat stat)
{
// enemies dont get modifiers.
if (!shouldUseModifiers) return 0;
float total = 0;
foreach (IModifierProvider provider in GetComponents<IModifierProvider>())
{
foreach (float bonus in provider.GetPercentageModifiers(stat))
{
total += bonus;
}
}
return total;
}
// get players xp level.
private int CalculateLevel()
{
Experience experience = GetComponent<Experience>();
// if its an enemy just return its startinglevel which is its level.
if (experience == null) return startingLevel;
float currentXP = experience.GetPoints();
int penultimateLevel = progression.GetLevels(Stat.ExperienceToLevelUp, characterClass);
// loops over all the levels we have in experiencetolevelup stat. starts at level 1.
// start at 1 since we use level - 1 in getstat to reach level arrays 0 index.
for (int level = 1; level <= penultimateLevel ; level++)
{
// get xp you need to level up for each level in experiencetolevelup.
float xpToLevelUp = progression.GetStat(Stat.ExperienceToLevelUp, characterClass, level);
// if we find a level with xp greater than our current xp we return this level.
if (xpToLevelUp > currentXP)
{
return level;
}
}
// else if we have xp greater than the penultimatelevel, add 1 to reach max level, starts at 0.
return penultimateLevel + 1;
}
}
}