Trying to Create a "Show Weapon Damage" script

I’ve been trying to create a script that shows a weapon’s damage, isolated from the player’s base damage. Currently it reads:

namespace RPG.Attributes
{
    public class ShowWeaponPhysical : MonoBehaviour
    {
        Fighter playerFighter;
        WeaponConfig currentWeaponConfig;
        float weaponPhysicalDamage = 0;

        private void Awake()
        {
            // playerFighter = GameObject.FindWithTag("Player").GetComponent<Fighter>();
            // currentWeaponConfig = playerFighter.currentWeaponConfig;
            // weaponPhysicalDamage = currentWeaponConfig.GetDamage();
        }

        private void Update()
        {
            // weaponPhysicalDamage = currentWeaponConfig.GetDamage();
            // GetComponent<TMP_Text>().text = String.Format("{0:0}", weaponPhysicalDamage);
        }
    }
}

This throws a null error, however, and I was wondering if anyone know what I might do better to get it to work. Also the "physical damage" part is just because I eventually want to split damage into types (I haven't done this yet so it shouldn't be part of the problem). I believe the scripts this references should be pretty much as given in the lectures.

The best practice here is to get a helper method in Fighter

public float GetRawWeaponDamage()
{
    return currentWeaponConfig.GetDamage();
}

and when you need the raw damage, call playerFighter.GetRawDamage();

That seems like it would make a lot of sense in isolation but wouldn’t there be a better approach if I have other damage types in the future? For example, a weapon may eventually have “Physical” damage (which is for now what I have called the default damage) and then “Lightning” damage. I’d like to work towards a script that can either be easily retooled or even just pull from an array or something to turn various numbers into different types of damage attached to a weapon.

My solution to this in the past has been to have a Damage class

public class Damage
{
    public DamageType damageType;
    public float amount;
}

And then in WeaponConfig, making damage “conventional” damage, and keeping a List<Damage> with specialized damage types.
These are easily exposed in Fighter either as a list directly or as an IEnumerable<Damage>

I know this is basic stuff so feel free to refer me to some basic tutorial or just have me look into it on my own but can you explain this a bit more?

Like are you saying first I may a DamageType script that holds a list? Sort of like the Stats script we made? I’m not totally clear on how a weapon would deal non-standard damage. And would the way you describe allow for a weapon that dealt mix damage?

I don’t have any specific tutorials on this. I was basing this off of your own descriptoin of what you’re looking for (Physical damage and then “Lightening” damage).

Many games have multiple damage types attached to a given weapon. The Physical damage is blocked by conventional means (your defense score), while elemental damage is mitigated by resistance to that element.

In my setup, DamageType is an Enum

public enum DamageType
{
     Earth,
     Air,
     Fire,
     Water,
     Lightning,
     Light,
     Dark
}

WeaponConfigs have direct conventional damage. They also have a List of Damages which correspond to elemental damages.
Armor can have stats that reduce that damage.

I created a parallel system for elemental damage alongside the IModifierProvider system, to make it easier to match up damages and resistances.

using System.Collections.Generic;

namespace RPG.Stats
{
    [System.Serializable]
    public struct ElementalDamage
    {
        public DamageType damageType;
        public float amount;
    }

    public enum DamageType
    {
        Earth,
        Air,
        Fire,
        Water,
        Lightning,
        Light,
        Dark
    }

    public interface IElementalResistanceProvider
    {
        public IEnumerable<float> GetElementalResistance(DamageType damageType);
    }

    public interface IElementalDamageProvider
    {
        public IEnumerable<float> GetElementalDamageBoost(DamageType damageType);
    }
}

With these helper classes/enums in place, then I add a few tidbits to StatsEquipableItem, adding IElementalDamageProvider and IElementalResistanceProfider to the interfaces.

        [SerializeField] private ElementalDamage[] elementDamages;
        [SerializeField] private ElementalDamage[] elementResistances;
        public IEnumerable<float> GetElementalDamageBoost(DamageType damageType)
        {
            foreach (ElementalDamage damage in elementDamages)
            {
                if (damage.damageType == damageType) yield return damage.amount;
            }
        }

        public IEnumerable<float> GetElementalResistance(DamageType damageType)
        {
            foreach (ElementalDamage damage in elementResistances)
            {
                if (damage.damageType == damageType) yield return damage.amount;
            }
        }

Similarly, in StatsEquipment, I add the providers

        public IEnumerable<float> GetElementalResistance(DamageType damageType)
        {
            foreach (var slot in GetAllPopulatedSlots())
            {
                var item = GetItemInSlot(slot) as IElementalResistanceProvider;
                if (item == null) continue;
                foreach (float modifier in item.GetElementalResistance(damageType))
                {
                    yield return modifier;
                }
            }
        }

        public IEnumerable<float> GetElementalDamageBoost(DamageType damageType)
        {
            foreach (var slot in GetAllPopulatedSlots())
            {
                var item = GetItemInSlot(slot) as IElementalDamageProvider;
                if (item == null) continue;
                foreach (float modifier in item.GetElementalDamageBoost(damageType))
                {
                    yield return modifier;
                }
            });
        }

In WeaponConfig, I add

[SerializeField] ElementalDamage[] elementalDamages;

        public IEnumerable<ElementalDamage> GetElementalDamages()
        {
            foreach (ElementalDamage elementalDamage in elementalDamages)
            {
                yield return elementalDamage;
            }
        }

And finally in Fighter.cs, in the Hit() method, after doing the standard damage, we need to add the elemental damages:

 else
            {
                target.TakeDamage(gameObject, damage);
                foreach (ElementalDamage elementalDamage in currentWeaponConfig.GetElementalDamages())
                {
                    damage = elementalDamage.amount;
                    float boosts = 0;
                    foreach (IElementalDamageProvider provider in GetComponents<IElementalDamageProvider>())
                    {
                        foreach (float amount in provider.GetElementalDamageBoost(elementalDamage.damageType))
                        {
                            boosts += amount;
                        }
                    }
                    boosts /= 100f;
                    float resistances = 0;
                    foreach (IElementalResistanceProvider provider in target.GetComponents<IElementalResistanceProvider>())
                    {
                        foreach (float amount in provider.GetElementalResistance(elementalDamage.damageType))
                        {
                            resistances += amount;
                        }
                    }

                    resistances /= 100f;
                    damage += damage * boosts;
                    damage -= damage * resistances;
                    if (damage <= 0) continue;
                    target.TakeDamage(gameObject, damage);
                }
            }

What this does is crawl through each set of damages in the WeaponConfig. It then gets any boosts from the ElementalDamagerBoostProviders (put in boosts as it it was xx%, so a 10% boost is 10, not .1. The method scales it back down to fractional values before applying the damage.

Then it crawls through the target’s ElementalResistanceProviders to get the resistances. You might, for enemies, for example, give them elemental resistances in a separate class.

The damage gets the added damage boosts (damage += damage*boosts;) and then gets the resistances removed (damage -= damage * resistances), and finally the damage is applied to the enemy if there is any damage left to deliver.

1 Like

This was extremely thorough, thank you very much.

Will negatives work without needed changes in this system? For example, a “boost” can be a negative number, right?

Yes, because if the boost is say -50 then boosts would be -.50
damage += damage * -.5f would flip the sign of the right hand of the equation and would subtract damage.
Likewise, if resistance was -50, then
damage -= damage * -5f would flip the sign and through math would become damage += damage * 5f.

So you might have a shield that grants 50% resistance to fire, but -50% resistance to water. I.e. string against fire attacks, and weak against water attacks.

1 Like

So I think I mostly understand what you’ve suggested here but am a little unclear on where the public enum DamageType fits in. Does that go somewhere in code in addition to everything else or is it a separate thing? Because I’ve been implementing your suggestions and things seem to at least compile without that.

Somewhere in the code (outside of a class declaration) should be this code.
It’s common to put it in it’s own file DamageType.cs, but this is not mandatory. It’s likely you have it in one of your scripts already, or it shouldn’t compile.

Privacy & Terms