I am currently trying to generate a “ShowDefenses” script after working with @BrianTrotter to make an elemental damage and defenses system but have hit a bit of a roadblock in terms of my skill set.
Some relevant scripts include this ElementalDamage script:
using System.Collections.Generic;
namespace RPG.Attributes
{
[System.Serializable] public struct ElementalDamage
{
public DamageType damageType;
public float amount;
}
public enum DamageType
{
Physical,
Fire,
Lightning,
Acid,
Divine,
Myth
}
public interface IElementalResistanceProvider
{
public IEnumerable<float> GetElementalResistance(DamageType damageType);
}
public interface IElementalDamageProvider
{
public IEnumerable<float> GetElementalDamageBoost(DamageType damageType);
}
}
The Fighter script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Movement;
using RPG.Core;
using System;
using RPG.Saving;
using RPG.Attributes;
using RPG.Saving.Utils;
using RPG.Saving.Inventories;
namespace RPG.Combat
{
public class Fighter : MonoBehaviour, IAction//Not Dark Souls combat; more like Baldur's Gate or Diablo
{
[SerializeField] float timeBetweenAttacks = 1f;
[SerializeField] float autoattackRange = 4f;
[SerializeField] Transform rightHandTransform = null;
[SerializeField] Transform leftHandTransform = null;
[SerializeField] Transform spellbookTransform = null;
[SerializeField] WeaponConfig defaultWeapon = null;
Health target;
Equipment equipment;
public WeaponConfig currentWeaponConfig;
float timeSinceLastAttack = Mathf.Infinity;//Number is infinitely high, so you always start ready to attack
LazyValue<Weapon> currentWeapon;
private void Awake()
{
currentWeaponConfig = defaultWeapon;
currentWeapon = new LazyValue<Weapon>(SetupDefaultWeapon);
equipment = GetComponent<Equipment>();
if(equipment)
{
equipment.equipmentUpdated += UpdateWeapon;
}
}
private void Start()
{
currentWeapon.ForceInit();
}
private void Update()
{
timeSinceLastAttack += Time.deltaTime;
if(target == null) return;
if(target.IsDead())
{
target = FindNewTargetInRange();//needed for autoattacking
if(target == null) return;
}
if (!GetIsInRange(target.transform))
{
GetComponent<Mover>().MoveTo(target.transform.position, 1f);//1f for 100% max speed
}
else
{
GetComponent<Mover>().Cancel();
AttackBehavior();
}
}
private void UpdateWeapon()
{
var weapon = equipment.GetItemInSlot(EquipLocation.Weapon) as WeaponConfig;
if(weapon == null)
{
EquipWeapon(defaultWeapon);
}
else
{
EquipWeapon(weapon);
}
}
private Weapon SetupDefaultWeapon()
{
return AttachWeapon(defaultWeapon);
}
public void EquipWeapon(WeaponConfig weapon)
{
currentWeaponConfig = weapon;
currentWeapon.value = AttachWeapon(weapon);
}
public Weapon AttachWeapon(WeaponConfig weapon)
{
Animator animator = GetComponent<Animator>();
return weapon.SpawnWeapon(rightHandTransform, leftHandTransform, spellbookTransform, animator);
}
private void AttackBehavior()
{
transform.LookAt(target.transform);
if(timeSinceLastAttack > timeBetweenAttacks)
{
TriggerAttack();
timeSinceLastAttack = 0f;
}
}
private Health FindNewTargetInRange()
{
Health best = null;
float bestDistance = Mathf.Infinity;
foreach (var candidate in FindAllTargetsInRange())
{
float candidateDistance = Vector3.Distance(
transform.position, candidate.transform.position);
if (candidateDistance < bestDistance)
{
best = candidate;
bestDistance = candidateDistance;
}
}
return best;
}
private IEnumerable<Health> FindAllTargetsInRange()
{
RaycastHit[] raycastHits = Physics.SphereCastAll(transform.position, autoattackRange, Vector3.up, 0);
foreach(var hit in raycastHits)
{
Health health = hit.transform.GetComponent<Health>();
if(health == null) continue;
if(health.IsDead()) continue;
if(health.gameObject == gameObject) continue;
yield return health;
}
}
private void TriggerAttack()
{
GetComponent<Animator>().ResetTrigger("StopAttack");
GetComponent<Animator>().SetTrigger("Attack");//Triggers hit event, which deals damage
}
void Hit() // Anim Event
{
if(target == null) {return;}
float damage = GetComponent<BaseStats>().GetStat(Stats.PhysicalDamage);//eventually can check weapon for type and the modifiers the weapon should pull from via bools
BaseStats targetBaseStats = target.GetComponent<BaseStats>();
if (targetBaseStats != null)
{
float defense = targetBaseStats.GetStat(Stats.PhysicalDefense);
damage /= 1 + defense / damage;
}
if(currentWeapon.value != null)
{
currentWeapon.value.OnHit();
}
if(currentWeaponConfig.HasProjectile() != false)
{
currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, spellbookTransform, target, gameObject, damage);
return;
}
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;
//Line below changed to add damage type for elemental damage.
target.TakeDamage(gameObject, damage, elementalDamage.damageType.ToString());
}
}
}
void Shoot() // Anim Event, probably unneeded
{
Hit();
}
private bool GetIsInRange(Transform targetTransform)
{
return Vector3.Distance(transform.position, targetTransform.transform.position) < currentWeaponConfig.GetRange();
}
public bool CanAttack(GameObject combatTarget)
{
if(combatTarget == null) {return false;}
if(GetComponent<Mover>().CanMoveTo(combatTarget.transform.position) == false && !GetIsInRange(combatTarget.transform))
{
return false;
}
Health targetToTest = combatTarget.GetComponent<Health>();
return targetToTest != null && !targetToTest.IsDead();
}
public void Attack(GameObject combatTarget)
{
GetComponent<ActionScheduler>().StartAction(this);
target = combatTarget.GetComponent<Health>();
}
public Health GetTarget()
{
return target;
}
public Transform GetHandTransform(bool isRightHand)
{
if(isRightHand)
{
return rightHandTransform;
}
else{
return leftHandTransform;
}
}
public void Cancel()
{
StopAttack();
target = null;
GetComponent<Mover>().Cancel();
}
private void StopAttack()
{
GetComponent<Animator>().ResetTrigger("Attack");
GetComponent<Animator>().SetTrigger("StopAttack");
}
}
}
And my attempt so far at a ShowDefenses script (which is busted right now):
using System.Collections;
using System.Collections.Generic;
using RPG.Attributes;
using RPG.Combat;
using RPG.Saving.Inventories;
using TMPro;
using UnityEngine;
public class ShowDefenses : MonoBehaviour
{
public float physical = 0;
public float fire = 0;
public float lightning = 0;
public float acid = 0;
public float divine = 0;
public float myth = 0;
[SerializeField] TMP_Text physicalText;
[SerializeField] TMP_Text fireText;
[SerializeField] TMP_Text lightningText;
[SerializeField] TMP_Text acidText;
[SerializeField] TMP_Text divineText;
[SerializeField] TMP_Text mythText;
IEnumerable<ElementalDamage> elementalDamages;
GameObject player;
Fighter playerFighter;
StatsEquipment currentEquipment;
float damage;
void Start()
{
player = GameObject.FindWithTag("Player");
currentEquipment = player.GetComponent<StatsEquipment>();
}
void Update()
{
//currentEquipment = currentEquipment;
elementalDamages = currentEquipment.GetElementalResistance();
CheckDamages();
SetNumbers();
}
private void SetNumbers()
{
physicalText.SetText(physical.ToString());//should be same as default damage
fireText.SetText(fire.ToString());
lightningText.SetText(lightning.ToString());
acidText.SetText(acid.ToString());
divineText.SetText(divine.ToString());
mythText.SetText(myth.ToString());
}
public void CheckDamages()
{
physical = currentWeaponConfig.GetDamage();
fire = currentEquipment.
lightning = elementalDamages.FirstOrDefault(x => x.damageType == DamageType.Lightning).amount;
acid = elementalDamages.FirstOrDefault(x => x.damageType == DamageType.Acid).amount;
divine = elementalDamages.FirstOrDefault(x => x.damageType == DamageType.Divine).amount;
myth = elementalDamages.FirstOrDefault(x => x.damageType == DamageType.Myth).amount;
}
}