diff --git a/Assets/Character Stats/CharacterStat.cs b/Assets/Character Stats/CharacterStat.cs index 1a501f78..e6da8ffc 100644 --- a/Assets/Character Stats/CharacterStat.cs +++ b/Assets/Character Stats/CharacterStat.cs @@ -4,122 +4,227 @@ using System.Collections.ObjectModel; using System.Linq; using UnityEngine.Events; +public enum CharacterStatType +{ + Cunning, + Flow, + Presence, + + AttackDamage, + SpellDamage, + AttackSpeed, + CritChance, + CritDamage, + AuraPower, + + MaxHealth, + HealthRegen, + MaxMana, + ManaRegen, + + Armor, + MagicResistance, + DodgeChance, + BlockChance, + BlockEffectiveness, + + AreaEffectiveness, + CooldownReduction, + MovementSpeed, + + ReputationGainIncrease, + GoldCostReduction +} + +public static class CharacterStatHelper +{ + public static List Attributes = new List() + { + CharacterStatType.Cunning, + CharacterStatType.Flow, + CharacterStatType.Presence + }; + public static bool IsAttribute(CharacterStatType type) + { + return Attributes.Contains(type); + } + public static List Offensive = new List() + { + CharacterStatType.AttackDamage, + CharacterStatType.SpellDamage, + CharacterStatType.AttackSpeed, + CharacterStatType.CritChance, + CharacterStatType.CritDamage, + CharacterStatType.AuraPower, + }; + public static bool IsOffensive(CharacterStatType type) + { + return Offensive.Contains(type); + } + public static List Defensive = new List() + { + CharacterStatType.Armor, + CharacterStatType.MagicResistance, + CharacterStatType.DodgeChance, + CharacterStatType.BlockChance, + CharacterStatType.BlockEffectiveness, + }; + public static bool IsDefensive(CharacterStatType type) + { + return Defensive.Contains(type); + } + public static List Resource = new List() + { + CharacterStatType.MaxHealth, + CharacterStatType.HealthRegen, + CharacterStatType.MaxMana, + CharacterStatType.ManaRegen, + }; + public static bool IsResource(CharacterStatType type) + { + return Resource.Contains(type); + } + public static List Control = new List() + { + CharacterStatType.AreaEffectiveness, + CharacterStatType.CooldownReduction, + CharacterStatType.MovementSpeed, + }; + public static bool IsControl(CharacterStatType type) + { + return Control.Contains(type); + } + public static List Misc = new List() + { + CharacterStatType.ReputationGainIncrease, + CharacterStatType.GoldCostReduction, + }; + public static bool IsMisc(CharacterStatType type) + { + return Misc.Contains(type); + } +} + namespace Kryz.CharacterStats { - [Serializable] - public class CharacterStat - { - public GameTag statTag; + [Serializable] + public class CharacterStat + { + public GameTag statTag; + public CharacterStatType statType; - public float BaseValue; + public float BaseValue; - protected bool isDirty = true; - protected float lastBaseValue; + protected bool isDirty = true; + protected float lastBaseValue; - protected float _value; - public virtual float Value { - get { - if(isDirty || lastBaseValue != BaseValue) { - lastBaseValue = BaseValue; - _value = CalculateFinalValue(); - isDirty = false; - } - return _value; - } - } - - protected readonly List statModifiers; - public readonly ReadOnlyCollection StatModifiers; - - public CharacterStat() - { - statModifiers = new List(); - StatModifiers = statModifiers.AsReadOnly(); - } - - public CharacterStat(float baseValue) : this() - { - isDirty = true; - BaseValue = baseValue; - } - - public virtual void AddModifier(StatModifier mod) - { - isDirty = true; - statModifiers.Add(mod); - } - - public virtual bool RemoveModifier(StatModifier mod) - { - if (statModifiers.Remove(mod)) - { - isDirty = true; - return true; - } - return false; - } - - public virtual bool HasModifiersFromSource(object source) + protected float _value; + public virtual float Value { - return StatModifiers.Any(mod => mod.Source == source); - } + get + { + if (isDirty || lastBaseValue != BaseValue) + { + lastBaseValue = BaseValue; + _value = CalculateFinalValue(); + isDirty = false; + } + return _value; + } + } - public virtual bool RemoveAllModifiersFromSource(object source) - { - int numRemovals = statModifiers.RemoveAll(mod => mod.Source == source); + protected readonly List statModifiers; + public readonly ReadOnlyCollection StatModifiers; - if (numRemovals > 0) - { - isDirty = true; - return true; - } - return false; - } + public CharacterStat() + { + statModifiers = new List(); + StatModifiers = statModifiers.AsReadOnly(); + } - protected virtual int CompareModifierOrder(StatModifier a, StatModifier b) - { - if (a.Order < b.Order) - return -1; - else if (a.Order > b.Order) - return 1; - return 0; //if (a.Order == b.Order) - } - - protected virtual float CalculateFinalValue() - { - float finalValue = BaseValue; - float sumPercentAdd = 0; + public CharacterStat(float baseValue) : this() + { + isDirty = true; + BaseValue = baseValue; + } - statModifiers.Sort(CompareModifierOrder); + public virtual void AddModifier(StatModifier mod) + { + isDirty = true; + statModifiers.Add(mod); + } - for (int i = 0; i < statModifiers.Count; i++) - { - StatModifier mod = statModifiers[i]; + public virtual bool RemoveModifier(StatModifier mod) + { + if (statModifiers.Remove(mod)) + { + isDirty = true; + return true; + } + return false; + } - if (mod.Type == StatModType.Flat) - { - finalValue += mod.Value; - } - else if (mod.Type == StatModType.PercentAdd) - { - sumPercentAdd += mod.Value; + public virtual bool HasModifiersFromSource(object source) + { + return StatModifiers.Any(mod => mod.Source == source); + } - if (i + 1 >= statModifiers.Count || statModifiers[i + 1].Type != StatModType.PercentAdd) - { - finalValue *= 1 + sumPercentAdd; - sumPercentAdd = 0; - } - } - else if (mod.Type == StatModType.PercentMult) - { - finalValue *= 1 + mod.Value; - } - } + public virtual bool RemoveAllModifiersFromSource(object source) + { + int numRemovals = statModifiers.RemoveAll(mod => mod.Source == source); - // Workaround for float calculation errors, like displaying 12.00001 instead of 12 - return (float)Math.Round(finalValue, 4); - } - } + if (numRemovals > 0) + { + isDirty = true; + return true; + } + return false; + } + + protected virtual int CompareModifierOrder(StatModifier a, StatModifier b) + { + if (a.Order < b.Order) + return -1; + else if (a.Order > b.Order) + return 1; + return 0; //if (a.Order == b.Order) + } + + protected virtual float CalculateFinalValue() + { + float finalValue = BaseValue; + float sumPercentAdd = 0; + + statModifiers.Sort(CompareModifierOrder); + + for (int i = 0; i < statModifiers.Count; i++) + { + StatModifier mod = statModifiers[i]; + + if (mod.Type == StatModType.Flat) + { + finalValue += mod.Value; + } + else if (mod.Type == StatModType.PercentAdd) + { + sumPercentAdd += mod.Value; + + if (i + 1 >= statModifiers.Count || statModifiers[i + 1].Type != StatModType.PercentAdd) + { + finalValue *= 1 + sumPercentAdd; + sumPercentAdd = 0; + } + } + else if (mod.Type == StatModType.PercentMult) + { + finalValue *= 1 + mod.Value; + } + } + + // Workaround for float calculation errors, like displaying 12.00001 instead of 12 + return (float)Math.Round(finalValue, 4); + } + } } diff --git a/Assets/Character Stats/Examples/Items & Inventory/Prefabs/Character Panel.prefab b/Assets/Character Stats/Examples/Items & Inventory/Prefabs/Character Panel.prefab index fb0469cc..d661acb8 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Prefabs/Character Panel.prefab +++ b/Assets/Character Stats/Examples/Items & Inventory/Prefabs/Character Panel.prefab @@ -8027,7 +8027,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 2440268520758508678, guid: 1372ea2f917805749a87f6c81e9938cf, type: 3} propertyPath: m_Name - value: OffWeapon Slot + value: OffHand Slot objectReference: {fileID: 0} - target: {fileID: 2440268520765564492, guid: 1372ea2f917805749a87f6c81e9938cf, type: 3} propertyPath: m_Sprite @@ -8149,7 +8149,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 2440268520758508678, guid: 1372ea2f917805749a87f6c81e9938cf, type: 3} propertyPath: m_Name - value: MainWeapon Slot + value: MainHand Slot objectReference: {fileID: 0} - target: {fileID: 2440268520765564492, guid: 1372ea2f917805749a87f6c81e9938cf, type: 3} propertyPath: m_Sprite diff --git a/Assets/Character Stats/Examples/Items & Inventory/Scripts/CharacterStats.cs b/Assets/Character Stats/Examples/Items & Inventory/Scripts/CharacterStats.cs index 29ffcc34..3fec893a 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Scripts/CharacterStats.cs +++ b/Assets/Character Stats/Examples/Items & Inventory/Scripts/CharacterStats.cs @@ -71,6 +71,36 @@ namespace Kryz.CharacterStats.Examples protected virtual void Awake() { + Cunning.statType = CharacterStatType.Cunning; + Flow.statType = CharacterStatType.Flow; + Presence.statType = CharacterStatType.Presence; + + AttackDamage.statType = CharacterStatType.AttackDamage; + SpellDamage.statType = CharacterStatType.SpellDamage; + AttackSpeed.statType = CharacterStatType.AttackSpeed; + CritChance.statType = CharacterStatType.CritChance; + CritDamage.statType = CharacterStatType.CritDamage; + AuraPower.statType = CharacterStatType.AuraPower; + + MaxHealth.statType = CharacterStatType.MaxHealth; + HealthRegen.statType = CharacterStatType.HealthRegen; + MaxMana.statType = CharacterStatType.MaxMana; + ManaRegen.statType = CharacterStatType.ManaRegen; + + Armor.statType = CharacterStatType.Armor; + MagicResistance.statType = CharacterStatType.MagicResistance; + DodgeChance.statType = CharacterStatType.DodgeChance; + BlockChance.statType = CharacterStatType.BlockChance; + BlockEffectiveness.statType = CharacterStatType.BlockEffectiveness; + + AreaEffectiveness.statType = CharacterStatType.AreaEffectiveness; + CooldownReduction.statType = CharacterStatType.CooldownReduction; + MovementSpeed.statType = CharacterStatType.MovementSpeed; + + ReputationGainIncrease.statType = CharacterStatType.ReputationGainIncrease; + GoldCostReduction.statType = CharacterStatType.GoldCostReduction; + + primaryStatsDictionary.Add(nameof(Cunning).ToLower(), Cunning); primaryStatsDictionary.Add(nameof(Flow).ToLower(), Flow); primaryStatsDictionary.Add(nameof(Presence).ToLower(), Presence); diff --git a/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquipmentPanel.cs b/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquipmentPanel.cs index ce8ddd1a..06e5fcd1 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquipmentPanel.cs +++ b/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquipmentPanel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using UnityEngine; namespace Kryz.CharacterStats.Examples @@ -55,7 +56,7 @@ namespace Kryz.CharacterStats.Examples { for (int i = 0; i < equipmentSlots.Length; i++) { - if(equipmentSlots[i].EquipmentType == slot) + if (equipmentSlots[i].EquipmentType == slot) { return (EquippableItemInstance)equipmentSlots[i].Item; } @@ -63,22 +64,185 @@ namespace Kryz.CharacterStats.Examples return null; } - public bool AddItem(EquippableItemInstance item, out EquippableItemInstance previousItem) + // NEW: Helper method to get weapon slots + public (EquipmentSlot mainHand, EquipmentSlot offHand) GetWeaponSlots() + { + EquipmentSlot mainHand = null; + EquipmentSlot offHand = null; + + for (int i = 0; i < equipmentSlots.Length; i++) + { + if (equipmentSlots[i].EquipmentType == EquipmentType.MainHand) + mainHand = equipmentSlots[i]; + else if (equipmentSlots[i].EquipmentType == EquipmentType.OffHand) + offHand = equipmentSlots[i]; + } + + return (mainHand, offHand); + } + + /// + /// Calculates how many items would be displaced if the given item were equipped + /// + public int GetDisplacedItemCount(EquippableItemInstance item) + { + if (!item.IsWeapon) + { + // Non-weapons: at most 1 item displaced (standard replacement) + var existingItem = GetEquippedItemOnSpecificSlot(item.EquipmentType); + return (existingItem != null && !string.IsNullOrEmpty(existingItem.ItemName)) ? 1 : 0; + } + + // Handle weapons + var (mainHandSlot, offHandSlot) = GetWeaponSlots(); + if (mainHandSlot == null || offHandSlot == null) + return 0; + + var currentMainHand = (EquippableItemInstance)mainHandSlot.Item; + var currentOffHand = (EquippableItemInstance)offHandSlot.Item; + + if (item.IsTwoHandedWeapon) + { + // Two-handed weapon displaces both current weapons + int count = 0; + if (currentMainHand != null && !string.IsNullOrEmpty(currentMainHand.ItemName)) + count++; + if (currentOffHand != null && !string.IsNullOrEmpty(currentOffHand.ItemName)) + count++; + return count; + } + else // One-handed weapon + { + if (currentMainHand != null && currentMainHand.IsTwoHandedWeapon) + { + // Replacing two-handed with one-handed + return 1; + } + else + { + // Normal one-handed logic + if (currentMainHand == null || currentOffHand == null) + return 0; // Filling empty slot + else + return 1; // Will replace main hand + } + } + } + + // NEW: Helper method to get slot index for saving + private int GetSlotIndex(EquipmentSlot slot) { for (int i = 0; i < equipmentSlots.Length; i++) { - if (equipmentSlots[i].EquipmentType == item.EquipmentType) - { - previousItem = (EquippableItemInstance)equipmentSlots[i].Item; - equipmentSlots[i].Item = item; + if (equipmentSlots[i] == slot) + return i; + } + return -1; + } + + // UPDATED: Modified to handle two-handed weapon logic + public bool AddItem(EquippableItemInstance item, out EquippableItemInstance previousItem) + { + return AddItem(item, out previousItem, out _); + } + + // NEW: Overloaded method that returns multiple previous items for two-handed weapon swapping + public bool AddItem(EquippableItemInstance item, out EquippableItemInstance previousItem, out EquippableItemInstance secondPreviousItem) + { + previousItem = null; + secondPreviousItem = null; + + // Handle non-weapon items with original logic + if (!item.IsWeapon) + { + for (int i = 0; i < equipmentSlots.Length; i++) + { + if (equipmentSlots[i].EquipmentType == item.EquipmentType) + { + previousItem = (EquippableItemInstance)equipmentSlots[i].Item; + equipmentSlots[i].Item = item; + + equipmentData.equippedItems[i] = item; + PlayerDataHandler.Instance.SaveCharacterEquipmentData(PlayerDataHandler.Instance.currentPlayerName.Value, PlayerDataHandler.Instance.currentCharacterName.Value, equipmentData); + return true; + } + } + return false; + } + + // Handle weapon items + var (mainHandSlot, offHandSlot) = GetWeaponSlots(); + if (mainHandSlot == null || offHandSlot == null) + return false; + + var currentMainHand = (EquippableItemInstance)mainHandSlot.Item; + var currentOffHand = (EquippableItemInstance)offHandSlot.Item; + + if (item.IsTwoHandedWeapon) + { + // Equipping a two-handed weapon + previousItem = currentMainHand; + secondPreviousItem = currentOffHand; + + // Set the two-handed weapon in main hand, clear off-hand + mainHandSlot.Item = item; + offHandSlot.Item = null; + + // Update data arrays + int mainHandIndex = GetSlotIndex(mainHandSlot); + int offHandIndex = GetSlotIndex(offHandSlot); + equipmentData.equippedItems[mainHandIndex] = item; + equipmentData.equippedItems[offHandIndex] = null; + + PlayerDataHandler.Instance.SaveCharacterEquipmentData(PlayerDataHandler.Instance.currentPlayerName.Value, PlayerDataHandler.Instance.currentCharacterName.Value, equipmentData); + return true; + } + else // One-handed weapon + { + // Check if currently have a two-handed weapon equipped + if (currentMainHand != null && currentMainHand.IsTwoHandedWeapon) + { + // Replace two-handed with one-handed in main hand + previousItem = currentMainHand; + mainHandSlot.Item = item; + + int mainHandIndex = GetSlotIndex(mainHandSlot); + equipmentData.equippedItems[mainHandIndex] = item; + + PlayerDataHandler.Instance.SaveCharacterEquipmentData(PlayerDataHandler.Instance.currentPlayerName.Value, PlayerDataHandler.Instance.currentCharacterName.Value, equipmentData); + return true; + } + else + { + // Normal one-handed weapon equipping logic + // Prioritize main hand, then off-hand if main hand is occupied + if (currentMainHand == null) + { + // Equip in main hand + mainHandSlot.Item = item; + int mainHandIndex = GetSlotIndex(mainHandSlot); + equipmentData.equippedItems[mainHandIndex] = item; + } + else if (currentOffHand == null) + { + // Equip in off-hand for dual wielding + offHandSlot.Item = item; + int offHandIndex = GetSlotIndex(offHandSlot); + equipmentData.equippedItems[offHandIndex] = item; + } + else + { + // Both slots occupied, replace main hand + previousItem = currentMainHand; + mainHandSlot.Item = item; + int mainHandIndex = GetSlotIndex(mainHandSlot); + equipmentData.equippedItems[mainHandIndex] = item; + } - equipmentData.equippedItems[i] = item; PlayerDataHandler.Instance.SaveCharacterEquipmentData(PlayerDataHandler.Instance.currentPlayerName.Value, PlayerDataHandler.Instance.currentCharacterName.Value, equipmentData); return true; } } - previousItem = null; - return false; } public bool RemoveItem(EquippableItemInstance item) @@ -135,4 +299,4 @@ namespace Kryz.CharacterStats.Examples } } -} +} \ No newline at end of file diff --git a/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItem.cs b/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItem.cs index b713e6ae..edf04e82 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItem.cs +++ b/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItem.cs @@ -1,4 +1,55 @@ -using UnityEngine; +using System.Collections.Generic; +using UnityEngine; + +public enum ItemRarity +{ + Common, + Rare, + Epic, + Unique, + Legendary +} +public enum WeaponType +{ + // Two-Handed Weapons (Build-defining) + Staff, + Spear, + Scythe, + Hammer, + Bow, + Crossbow, + Axe, + + // One-Handed Weapons (Flexible) + Sword, + Shield, + Dagger, + Book +} + +public static class WeaponTypeExtensions +{ + private static readonly HashSet TwoHandedWeapons = new() + { + WeaponType.Staff, + WeaponType.Spear, + WeaponType.Scythe, + WeaponType.Hammer, + WeaponType.Bow, + WeaponType.Crossbow, + WeaponType.Axe + }; + + public static bool IsTwoHanded(this WeaponType weaponType) + { + return TwoHandedWeapons.Contains(weaponType); + } + + public static bool IsOneHanded(this WeaponType weaponType) + { + return !IsTwoHanded(weaponType); + } +} namespace Kryz.CharacterStats.Examples { @@ -13,13 +64,14 @@ namespace Kryz.CharacterStats.Examples Gloves, Boots, - MainWeapon, - OffWeapon, + MainHand, + OffHand, - Accessory, - Amulet, + //Accessory, + //Amulet, } + [CreateAssetMenu] public class EquippableItem : Item { @@ -77,6 +129,19 @@ namespace Kryz.CharacterStats.Examples public EquipmentType EquipmentType; + [Space] + // NEW: Add WeaponType for weapon items + public WeaponType WeaponType; + + // NEW: Helper property to check if this item is a weapon + public bool IsWeapon => EquipmentType == EquipmentType.MainHand || EquipmentType == EquipmentType.OffHand; + + // NEW: Helper property to check if this weapon is two-handed + public bool IsTwoHandedWeapon => IsWeapon && WeaponType.IsTwoHanded(); + + // NEW: Helper property to check if this weapon is one-handed + public bool IsOneHandedWeapon => IsWeapon && WeaponType.IsOneHanded(); + [Space] public bool CraftableBase = false; [Space(20f)] diff --git a/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItemInstance.cs b/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItemInstance.cs index 87ed6af9..7bc3e6de 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItemInstance.cs +++ b/Assets/Character Stats/Examples/Items & Inventory/Scripts/EquippableItemInstance.cs @@ -62,6 +62,9 @@ public class EquippableItemInstance : ItemInstance [Space] public EquipmentType EquipmentType; [Space] + // NEW: Add WeaponType for weapon items + public WeaponType WeaponType; + [Space] public bool CraftableBase = false; [Space] /// @@ -72,6 +75,15 @@ public class EquippableItemInstance : ItemInstance public int MaxTotalUniqueStatsIncreasedByStones; public List AddedStoneStats = new List(); + // NEW: Helper property to check if this item is a weapon + public bool IsWeapon => EquipmentType == EquipmentType.MainHand || EquipmentType == EquipmentType.OffHand; + + // NEW: Helper property to check if this weapon is two-handed + public bool IsTwoHandedWeapon => IsWeapon && WeaponType.IsTwoHanded(); + + // NEW: Helper property to check if this weapon is one-handed + public bool IsOneHandedWeapon => IsWeapon && WeaponType.IsOneHanded(); + public void Equip(PlayerCharacterStats c) { if (AttackDamageBonus != 0) @@ -276,6 +288,7 @@ public class EquippableItemInstance : ItemInstance MagicResistancePercentBonus = 0; EquipmentType = EquipmentType.Helmet; + WeaponType = WeaponType.Sword; // Default weapon type CraftableBase = false; @@ -307,6 +320,8 @@ public class EquippableItemInstance : ItemInstance MagicResistancePercentBonus = template.MagicResistancePercentBonus; EquipmentType = template.EquipmentType; + // NEW: Copy WeaponType from template (assuming EquippableItem has this field) + WeaponType = template.WeaponType; CraftableBase = template.CraftableBase; MaxTotalUniqueStatsIncreasedByStones = template.MaxTotalUniqueStatsIncreasedByStones; @@ -364,9 +379,10 @@ public class EquippableItemInstance : ItemInstance EquipmentType = template.EquipmentType; + // NEW: Copy WeaponType from template (assuming EquippableItem has this field) + WeaponType = template.WeaponType; CraftableBase = template.CraftableBase; MaxTotalUniqueStatsIncreasedByStones = template.MaxTotalUniqueStatsIncreasedByStones; } -} - +} \ No newline at end of file diff --git a/Assets/Character Stats/Examples/Items & Inventory/Scripts/Inventory.cs b/Assets/Character Stats/Examples/Items & Inventory/Scripts/Inventory.cs index 4c32a396..c9c2c1bd 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Scripts/Inventory.cs +++ b/Assets/Character Stats/Examples/Items & Inventory/Scripts/Inventory.cs @@ -137,6 +137,10 @@ namespace Kryz.CharacterStats.Examples return items.Count >= itemSlots.Length; //return items.FindAll(x => string.IsNullOrEmpty(x.ItemName)).Count <= 0; } + public int AvailableSlots() + { + return itemSlots.Length - items.Count; + } public void LoadInventory() { diff --git a/Assets/Character Stats/Examples/Items & Inventory/Scripts/PlayerCharacterStats.cs b/Assets/Character Stats/Examples/Items & Inventory/Scripts/PlayerCharacterStats.cs index f4eb092b..d5110301 100644 --- a/Assets/Character Stats/Examples/Items & Inventory/Scripts/PlayerCharacterStats.cs +++ b/Assets/Character Stats/Examples/Items & Inventory/Scripts/PlayerCharacterStats.cs @@ -204,20 +204,38 @@ namespace Kryz.CharacterStats.Examples public void Equip(EquippableItemInstance item) { + // First, check if we have enough inventory space for displaced items + if (!CanEquipItem(item)) + { + // Could show a message to player here: "Not enough inventory space" + return; + } + if (inventory.RemoveItem(item)) { ItemTooltip.Instance.HideTooltip(); EquippedItemTooltip.Instance.HideTooltip(); EquippableItemInstance previousItem; - if (equipmentPanel.AddItem(item, out previousItem)) + EquippableItemInstance secondPreviousItem; + + if (equipmentPanel.AddItem(item, out previousItem, out secondPreviousItem)) { + // Handle first previous item if (previousItem != null && !string.IsNullOrEmpty(previousItem.ItemName)) { inventory.AddItem(previousItem); previousItem.Unequip(this); - statPanel.UpdateStatValues(); } + + // Handle second previous item (for two-handed weapon replacement) + if (secondPreviousItem != null && !string.IsNullOrEmpty(secondPreviousItem.ItemName)) + { + inventory.AddItem(secondPreviousItem); + secondPreviousItem.Unequip(this); + } + + // Equip the new item item.Equip(this); statPanel.UpdateStatValues(); onUpdateStatValues.Invoke(); @@ -229,6 +247,18 @@ namespace Kryz.CharacterStats.Examples } } + // Simplified version using the EquipmentPanel helper method + private bool CanEquipItem(EquippableItemInstance item) + { + int itemsToDisplace = equipmentPanel.GetDisplacedItemCount(item); + + // Calculate available inventory slots + // We get +1 slot from removing the item we're equipping + int availableSlots = inventory.AvailableSlots() + 1; + + return availableSlots >= itemsToDisplace; + } + public void Unequip(EquippableItemInstance item) { if (!inventory.IsFull() && equipmentPanel.RemoveItem(item)) diff --git a/Assets/Developer/Prefabs/Persistent Objects.prefab b/Assets/Developer/Prefabs/Persistent Objects.prefab index 77510a8c..7e579b38 100644 --- a/Assets/Developer/Prefabs/Persistent Objects.prefab +++ b/Assets/Developer/Prefabs/Persistent Objects.prefab @@ -3702,6 +3702,69 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!1 &2097151069981005797 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7335229491322327302} + - component: {fileID: 4411192570680935153} + m_Layer: 0 + m_Name: EquippableItemInstanceGenerator + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7335229491322327302 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2097151069981005797} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7475116342638198534} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &4411192570680935153 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2097151069981005797} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a85a7f34d000be84fa64ef31a99b20b4, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::EquippableItemGenerator + HelmetIcons: [] + ShoulderIcons: [] + ChestIcons: [] + BeltIcons: [] + LegsIcons: [] + BracersIcons: [] + GlovesIcons: [] + BootsIcons: [] + StaffIcons: [] + SpearIcons: [] + ScytheIcons: [] + HammerIcons: [] + BowIcons: [] + CrossbowIcons: [] + AxeIcons: [] + SwordIcons: [] + ShieldIcons: [] + DaggerIcons: [] + BookIcons: [] --- !u!1 &2105173639354992659 GameObject: m_ObjectHideFlags: 0 @@ -7803,6 +7866,7 @@ MonoBehaviour: ReputationGainIncreasePercentBonus: 0 GoldCostReductionPercentBonus: 0 EquipmentType: 0 + WeaponType: 7 CraftableBase: 0 MaxTotalUniqueStatsIncreasedByStones: 0 AddedStoneStats: [] @@ -14453,6 +14517,7 @@ Transform: - {fileID: 315999698282489082} - {fileID: 2800905167933415666} - {fileID: 1867733016440364756} + - {fileID: 7335229491322327302} m_Father: {fileID: 7475116341965418816} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &7475116342667879665 diff --git a/Assets/Fantasy Skybox FREE/Panoramics/FS002/FS002_Night.mat b/Assets/Fantasy Skybox FREE/Panoramics/FS002/FS002_Night.mat index c941290c..4e0d8a84 100644 --- a/Assets/Fantasy Skybox FREE/Panoramics/FS002/FS002_Night.mat +++ b/Assets/Fantasy Skybox FREE/Panoramics/FS002/FS002_Night.mat @@ -78,7 +78,7 @@ Material: - _Mode: 0 - _OcclusionStrength: 1 - _Parallax: 0.02 - - _Rotation: 8.570184 + - _Rotation: 89.72914 - _SmoothnessTextureChannel: 0 - _SpecularHighlights: 1 - _SrcBlend: 1 diff --git a/Assets/Resources/Enemies/BasicEnemyPrefabTemplate.prefab b/Assets/Resources/Enemies/BasicEnemyPrefabTemplate.prefab index cf24bbc2..e073faf8 100644 --- a/Assets/Resources/Enemies/BasicEnemyPrefabTemplate.prefab +++ b/Assets/Resources/Enemies/BasicEnemyPrefabTemplate.prefab @@ -1004,8 +1004,8 @@ MonoBehaviour: m_Calls: [] m_text: Skelly Mage m_isRightToLeft: 0 - m_fontAsset: {fileID: 11400000, guid: 01ed77f6af1621a4cb0e3ee10d3b7147, type: 2} - m_sharedMaterial: {fileID: -7243662068360240803, guid: 01ed77f6af1621a4cb0e3ee10d3b7147, type: 2} + m_fontAsset: {fileID: 11400000, guid: 41f9368fce25cb44ba223ac55f4e69e1, type: 2} + m_sharedMaterial: {fileID: -1192090701822111264, guid: 41f9368fce25cb44ba223ac55f4e69e1, type: 2} m_fontSharedMaterials: [] m_fontMaterial: {fileID: 0} m_fontMaterials: [] diff --git a/Assets/Scriptables/Data/Resources/Items/Equippables/RevampedItems/0-Early-Items/Faithfull-Spirit Set/Faithfull-Spirit Orb.asset b/Assets/Scriptables/Data/Resources/Items/Equippables/RevampedItems/0-Early-Items/Faithfull-Spirit Set/Faithfull-Spirit Orb.asset index e3ac947a..2a0c176d 100644 --- a/Assets/Scriptables/Data/Resources/Items/Equippables/RevampedItems/0-Early-Items/Faithfull-Spirit Set/Faithfull-Spirit Orb.asset +++ b/Assets/Scriptables/Data/Resources/Items/Equippables/RevampedItems/0-Early-Items/Faithfull-Spirit Set/Faithfull-Spirit Orb.asset @@ -17,52 +17,37 @@ MonoBehaviour: sellPricePlayer: 100 sellPriceVendor: 2000 description: - StrengthBonus: 0 - AgilityBonus: 0 - IntelligenceBonus: 1 - SpiritBonus: 1 - VitalityBonus: 0 - StrengthPercentBonus: 0 - AgilityPercentBonus: 0 - IntelligencePercentBonus: 0 - SpiritPercentBonus: 0 - VitalityPercentBonus: 0 AttackDamageBonus: 0 SpellDamageBonus: 1 CritChanceBonus: 0 CritDamageBonus: 0 MaxHealthBonus: 0 + HealthRegenBonus: 0 + MaxManaBonus: 0 + ManaRegenBonus: 0 ArmorBonus: 0 MagicResistanceBonus: 0 AttackDamagePercentBonus: 0 SpellDamagePercentBonus: 0 + AttackSpeedPercentBonus: 0 CritChancePercentBonus: 0 CritDamagePercentBonus: 0 MaxHealthPercentBonus: 0 + HealthRegenPercentBonus: 0 + MaxManaPercentBonus: 0 + ManaRegenPercentBonus: 0 ArmorPercentBonus: 0 MagicResistancePercentBonus: 0 - EquipmentType: 5 + DodgeChancePercentBonus: 0 + BlockChancePercentBonus: 0 + BlockEffectivenessPercentBonus: 0 + AreaEffectivenessPercentBonus: 0 + CooldownReductionPercentBonus: 0 + MovementSpeedPercentBonus: 0 + ReputationGainIncreasePercentBonus: 0 + GoldCostReductionPercentBonus: 0 + EquipmentType: 9 CraftableBase: 0 - MinStrengthBonus: 0 - MaxStrengthBonus: 0 - MinAgilityBonus: 0 - MaxAgilityBonus: 0 - MinIntelligenceBonus: 0 - MaxIntelligenceBonus: 0 - MinSpiritBonus: 0 - MaxSpiritBonus: 0 - MinVitalityBonus: 0 - MaxVitalityBonus: 0 - MinStrengthPercentBonus: 0 - MaxStrengthPercentBonus: 0 - MinAgilityPercentBonus: 0 - MaxAgilityPercentBonus: 0 - MinIntelligencePercentBonus: 0 - MaxIntelligencePercentBonus: 0 - MinSpiritPercentBonus: 0 - MaxSpiritPercentBonus: 0 - MinVitalityPercentBonus: 0 - MaxVitalityPercentBonus: 0 MinAttackDamageBonus: 0 MaxAttackDamageBonus: 0 MinSpellDamageBonus: 0 diff --git a/Assets/Scripts/AbilitySystem/Base/CastBarHandler.cs b/Assets/Scripts/AbilitySystem/Base/CastBarHandler.cs index 2d24b672..68da115c 100644 --- a/Assets/Scripts/AbilitySystem/Base/CastBarHandler.cs +++ b/Assets/Scripts/AbilitySystem/Base/CastBarHandler.cs @@ -67,11 +67,11 @@ public class CastBarHandler : MonoBehaviour ToggleCastBarVisibility(false); } - public void ShowCastBar(BaseAbility ability, Action abilityExecution) + public void ShowCastBar(BaseAbility ability, Action abilityExecution, float castTime) { currentlyWaitingAbilityExecution = abilityExecution; currentAbility = ability; - currentCastTime = ability.castTime; + currentCastTime = castTime; castBarIcon.sprite = ability.Icon; StartCoroutine(FillImageOverTime(ability.castTime + 0.05f)); } diff --git a/Assets/Scripts/AbilitySystem/Base/CastingStateController.cs b/Assets/Scripts/AbilitySystem/Base/CastingStateController.cs index bfc82c2d..16c41a9f 100644 --- a/Assets/Scripts/AbilitySystem/Base/CastingStateController.cs +++ b/Assets/Scripts/AbilitySystem/Base/CastingStateController.cs @@ -1,3 +1,4 @@ +using Kryz.CharacterStats.Examples; using System; using System.Collections; using System.Collections.Generic; @@ -12,24 +13,38 @@ public class CastingStateController : MonoBehaviour public LayerMask movementMask; private MovementSpeedModifierEffectInstance movementSpeedModifier; - Camera cam; - Ray ray; - RaycastHit hit; - - ProjectileSpawnLocationController projectileSpawnLocationController; + private Camera cam; + private Ray ray; + private RaycastHit hit; + private ProjectileSpawnLocationController projectileSpawnLocationController; + private CharacterStats stats; public UnityEvent OnAbilityQueued = new UnityEvent(); - private void Awake() { cam = Camera.main; movementSpeedModifier = playerMovement.GetComponent(); + stats = playerMovement.GetComponent(); projectileSpawnLocationController = transform.root.GetComponentInChildren(); } - private void Start() - { + /// + /// Calculate current attack interval based on base speed and modifiers + /// + private float GetCurrentAttackInterval() + { + float totalAttackSpeed = GameConstants.CharacterStatsBalancing.BaseAttacksPerSecond * (1f + (MathHelpers.NormalizePercentage(stats.AttackSpeed.Value) / 100f)); + float interval = 1f / totalAttackSpeed; + return Mathf.Max(interval, GameConstants.CharacterStatsBalancing.FastestAttacksPerSecond); + } + + /// + /// Get current attacks per second + /// + public float GetCurrentAttacksPerSecond() + { + return 1f / GetCurrentAttackInterval(); } public void RequestAbilityCast(BaseAbility ability, Action abilityExecution) @@ -39,12 +54,12 @@ public class CastingStateController : MonoBehaviour if (!ability.castableWhileMoving) { movementSpeedModifier.ToggleCastPenalty(true); - //playerMovement.ToggleAgentMoving(true); } OnAbilityQueued.Invoke(ability); StartCoroutine(Casting(ability, abilityExecution)); } + public void RequestAbilityChannel(BaseAbility channeledAbility, Action abilityChannelExecution) { if (isCasting) return; @@ -52,7 +67,6 @@ public class CastingStateController : MonoBehaviour if (!channeledAbility.castableWhileMoving) { movementSpeedModifier.ToggleCastPenalty(true); - //playerMovement.ToggleAgentMoving(true); } OnAbilityQueued.Invoke(channeledAbility); @@ -62,12 +76,15 @@ public class CastingStateController : MonoBehaviour private IEnumerator Casting(BaseAbility ability, Action abilityExecution) { isCasting = true; - CastBarHandler.Instance.ShowCastBar(ability, abilityExecution); + // Use player attack speed instead of ability cast time + float castTime = GetCurrentAttackInterval(); + + CastBarHandler.Instance.ShowCastBar(ability, abilityExecution, castTime); playerMovement.InstantFaceCast(projectileSpawnLocationController.GetLookat()); - //cast animation - yield return new WaitForSeconds(ability.castTime); + // Wait for the calculated attack interval + yield return new WaitForSeconds(castTime); if (!GameConstants.Animation.IsAnimationEventBasedAbility(ability.animationType)) { @@ -75,29 +92,26 @@ public class CastingStateController : MonoBehaviour } isCasting = false; + if (!ability.castableWhileMoving) { movementSpeedModifier.ToggleCastPenalty(false); - //playerMovement.ToggleAgentMoving(false); } - - - //Debug.Log("$$sCastbar Done casting"); } private IEnumerator Channeling(BaseAbility channeledAbility, Action abilityChannelExecution) { isCasting = true; + + // For channeled abilities, you might want different behavior + // This maintains the original channeling logic CastBarHandler.Instance.ShowChannelingBar(channeledAbility, abilityChannelExecution); - playerMovement.InstantFaceCast(projectileSpawnLocationController.GetLookat()); - abilityChannelExecution.Invoke(); if (!channeledAbility.castableWhileMoving) { movementSpeedModifier.ToggleCastPenalty(true); - //playerMovement.ToggleAgentMoving(true); } yield return null; @@ -107,15 +121,14 @@ public class CastingStateController : MonoBehaviour { isCasting = false; movementSpeedModifier.ToggleCastPenalty(false); - //playerMovement.ToggleAgentMoving(false); } public void ResetCastingOnMeleeAnimationEvent() { isCasting = false; movementSpeedModifier.ToggleCastPenalty(false); - //playerMovement.ToggleAgentMoving(false); StopAllCoroutines(); - //Debug.Log("$$ResetCastingState On Event"); } -} + + +} \ No newline at end of file diff --git a/Assets/Scripts/AbilitySystem/Base/StatInfluence.cs b/Assets/Scripts/AbilitySystem/Base/StatInfluence.cs index 668e9bb9..dd4fbdcd 100644 --- a/Assets/Scripts/AbilitySystem/Base/StatInfluence.cs +++ b/Assets/Scripts/AbilitySystem/Base/StatInfluence.cs @@ -6,5 +6,6 @@ using UnityEngine; public class StatInfluence { public GameTag statTag; + public CharacterStatType statType; public float percentInfluence; } \ No newline at end of file diff --git a/Assets/Scripts/Game/GameConstants.cs b/Assets/Scripts/Game/GameConstants.cs index 8663ed31..f8d82e85 100644 --- a/Assets/Scripts/Game/GameConstants.cs +++ b/Assets/Scripts/Game/GameConstants.cs @@ -1,3 +1,4 @@ +using Kryz.CharacterStats; using UnityEngine; using static GameConstants.EnemySpawning; @@ -167,6 +168,8 @@ public static class GameConstants public const float MaximumPercentDamageReductionFromMagicResistance = 0.75f; public const float MaximumPercentCooldownReduction = 0.9f; public const float MaximumPercentDamageReductionFromBlock = 0.75f; + public const float BaseAttacksPerSecond = 1f; + public const float FastestAttacksPerSecond = 0.25f; public const float BaseMaxHealthGrowthPerLevel = 0.2f; @@ -240,6 +243,7 @@ public static class GameConstants public const float MovementSpeedCastingPenalty = -0.75f; public const float MovementSpeedLowestCap = 0.25f; public const float BossMovementSpeedBaseLowestPercentCap = 0.8f; //bosses can only be lowered to 80% of their normal speed: 2.85 base speed == 2.28 minimum speed + } public static class GameBalancing { @@ -435,4 +439,11 @@ public static class GameConstants return HuntersInn; } } + + public static class EquipmentStatRules + { + public const int ItemTotalStats = 4; + + + } } \ No newline at end of file diff --git a/Assets/Scripts/Items/Generator.meta b/Assets/Scripts/Items/Generator.meta new file mode 100644 index 00000000..6f639e8d --- /dev/null +++ b/Assets/Scripts/Items/Generator.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1b226c360854c4b4f8bdce0961d34302 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Items/Generator/EquippableItemGenerator.cs b/Assets/Scripts/Items/Generator/EquippableItemGenerator.cs new file mode 100644 index 00000000..8a595f57 --- /dev/null +++ b/Assets/Scripts/Items/Generator/EquippableItemGenerator.cs @@ -0,0 +1,166 @@ +using Kryz.CharacterStats.Examples; +using System.Collections.Generic; +using UnityEngine; + +public class EquippableItemGenerator : MonoBehaviour +{ + public List HelmetIcons = new List(); + public List ShoulderIcons = new List(); + public List ChestIcons = new List(); + public List BeltIcons = new List(); + public List LegsIcons = new List(); + public List BracersIcons = new List(); + public List GlovesIcons = new List(); + public List BootsIcons = new List(); + public List StaffIcons = new List(); + public List SpearIcons = new List(); + public List ScytheIcons = new List(); + public List HammerIcons = new List(); + public List BowIcons = new List(); + public List CrossbowIcons = new List(); + public List AxeIcons = new List(); + public List SwordIcons = new List(); + public List ShieldIcons = new List(); + public List DaggerIcons = new List(); + public List BookIcons = new List(); + + [ContextMenu("Update Lists From Resources")] + private void UpdateListsFromResources() + { + // Armor pieces + HelmetIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Armor/Helmets")) + { + HelmetIcons.Add(sprite); + } + + ShoulderIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Armor/Shoulders")) + { + ShoulderIcons.Add(sprite); + } + + ChestIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Armor/Chests")) + { + ChestIcons.Add(sprite); + } + + BeltIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Armor/Belts")) + { + BeltIcons.Add(sprite); + } + + LegsIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Armor/Legs")) + { + LegsIcons.Add(sprite); + } + + BracersIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Armor/Bracers")) + { + BracersIcons.Add(sprite); + } + + GlovesIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Armor/Gloves")) + { + GlovesIcons.Add(sprite); + } + + BootsIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Armor/Boots")) + { + BootsIcons.Add(sprite); + } + + // Two-handed weapons + StaffIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Weapons/Staffs")) + { + StaffIcons.Add(sprite); + } + + SpearIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Weapons/Spears")) + { + SpearIcons.Add(sprite); + } + + ScytheIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Weapons/Scythes")) + { + ScytheIcons.Add(sprite); + } + + HammerIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Weapons/Hammers")) + { + HammerIcons.Add(sprite); + } + + BowIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Weapons/Bows")) + { + BowIcons.Add(sprite); + } + + CrossbowIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Weapons/Crossbows")) + { + CrossbowIcons.Add(sprite); + } + + AxeIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Weapons/Axes")) + { + AxeIcons.Add(sprite); + } + + // One-handed weapons + SwordIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Weapons/Swords")) + { + SwordIcons.Add(sprite); + } + + ShieldIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Weapons/Shields")) + { + ShieldIcons.Add(sprite); + } + + DaggerIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Weapons/Daggers")) + { + DaggerIcons.Add(sprite); + } + + BookIcons.Clear(); + foreach (Sprite sprite in Resources.LoadAll("Weapons/Books")) + { + BookIcons.Add(sprite); + } + + Debug.Log("Updated all icon lists from Resources folders"); + } + + public EquippableItemInstance GenerateEquippableItemInstance() + { + EquippableItemInstance generatedItem = new EquippableItemInstance(); + + //Do stuff + + return generatedItem; + } + public EquippableItemInstance GenerateEquippableItemInstance(EquipmentType equipmentType) + { + EquippableItemInstance generatedItem = new EquippableItemInstance(); + + //Do stuff + + return generatedItem; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Items/Generator/EquippableItemGenerator.cs.meta b/Assets/Scripts/Items/Generator/EquippableItemGenerator.cs.meta new file mode 100644 index 00000000..4800337e --- /dev/null +++ b/Assets/Scripts/Items/Generator/EquippableItemGenerator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a85a7f34d000be84fa64ef31a99b20b4 \ No newline at end of file