CharacterCarryCapacity.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. /// <summary>
  5. /// Manages character carry capacity and item weights
  6. /// Provides penalties for exceeding carry limits
  7. /// </summary>
  8. [System.Serializable]
  9. public class CarryCapacitySystem
  10. {
  11. [Header("Base Capacity Settings")]
  12. [Tooltip("Base carry capacity in pounds")]
  13. public int baseCarryCapacity = 50;
  14. [Tooltip("How much strength affects carry capacity")]
  15. public float strengthMultiplier = 5f;
  16. [Header("Encumbrance Thresholds")]
  17. [Tooltip("Light load threshold (fraction of max capacity)")]
  18. [Range(0f, 1f)]
  19. public float lightLoadThreshold = 0.33f;
  20. [Tooltip("Medium load threshold (fraction of max capacity)")]
  21. [Range(0f, 1f)]
  22. public float mediumLoadThreshold = 0.66f;
  23. [Header("Encumbrance Penalties")]
  24. [Tooltip("Movement speed penalty for medium load (%)")]
  25. [Range(0f, 1f)]
  26. public float mediumLoadMovementPenalty = 0.25f;
  27. [Tooltip("Movement speed penalty for heavy load (%)")]
  28. [Range(0f, 1f)]
  29. public float heavyLoadMovementPenalty = 0.5f;
  30. [Tooltip("Dexterity penalty for heavy load")]
  31. public int heavyLoadDexterityPenalty = 3;
  32. public enum EncumbranceLevel
  33. {
  34. Light, // 0-33% capacity
  35. Medium, // 34-66% capacity
  36. Heavy, // 67-100% capacity
  37. Overloaded // 100%+ capacity
  38. }
  39. /// <summary>
  40. /// Calculate maximum carry capacity for a character based on strength
  41. /// </summary>
  42. public int CalculateMaxCapacity(int strength)
  43. {
  44. return baseCarryCapacity + Mathf.RoundToInt(strength * strengthMultiplier);
  45. }
  46. /// <summary>
  47. /// Get current encumbrance level based on carried weight and capacity
  48. /// </summary>
  49. public EncumbranceLevel GetEncumbranceLevel(int currentWeight, int maxCapacity)
  50. {
  51. if (currentWeight > maxCapacity)
  52. return EncumbranceLevel.Overloaded;
  53. float loadRatio = (float)currentWeight / maxCapacity;
  54. if (loadRatio <= lightLoadThreshold)
  55. return EncumbranceLevel.Light;
  56. else if (loadRatio <= mediumLoadThreshold)
  57. return EncumbranceLevel.Medium;
  58. else
  59. return EncumbranceLevel.Heavy;
  60. }
  61. /// <summary>
  62. /// Get movement speed modifier based on encumbrance
  63. /// </summary>
  64. public float GetMovementSpeedModifier(EncumbranceLevel encumbrance)
  65. {
  66. switch (encumbrance)
  67. {
  68. case EncumbranceLevel.Light:
  69. return 1.0f; // No penalty
  70. case EncumbranceLevel.Medium:
  71. return 1.0f - mediumLoadMovementPenalty;
  72. case EncumbranceLevel.Heavy:
  73. return 1.0f - heavyLoadMovementPenalty;
  74. case EncumbranceLevel.Overloaded:
  75. return 0.25f; // Severe penalty for being overloaded
  76. default:
  77. return 1.0f;
  78. }
  79. }
  80. /// <summary>
  81. /// Get dexterity modifier based on encumbrance
  82. /// </summary>
  83. public int GetDexterityModifier(EncumbranceLevel encumbrance)
  84. {
  85. switch (encumbrance)
  86. {
  87. case EncumbranceLevel.Light:
  88. case EncumbranceLevel.Medium:
  89. return 0; // No penalty
  90. case EncumbranceLevel.Heavy:
  91. return -heavyLoadDexterityPenalty;
  92. case EncumbranceLevel.Overloaded:
  93. return -heavyLoadDexterityPenalty * 2; // Double penalty
  94. default:
  95. return 0;
  96. }
  97. }
  98. /// <summary>
  99. /// Get encumbrance status description
  100. /// </summary>
  101. public string GetEncumbranceDescription(EncumbranceLevel encumbrance)
  102. {
  103. switch (encumbrance)
  104. {
  105. case EncumbranceLevel.Light:
  106. return "Light Load - No penalties";
  107. case EncumbranceLevel.Medium:
  108. return $"Medium Load - {mediumLoadMovementPenalty * 100:F0}% movement penalty";
  109. case EncumbranceLevel.Heavy:
  110. return $"Heavy Load - {heavyLoadMovementPenalty * 100:F0}% movement, -{heavyLoadDexterityPenalty} DEX penalty";
  111. case EncumbranceLevel.Overloaded:
  112. return $"Overloaded! - Severe penalties, consider dropping items";
  113. default:
  114. return "Unknown";
  115. }
  116. }
  117. }
  118. /// <summary>
  119. /// Component for managing a character's carry capacity and current load
  120. /// Attach to Character objects that need inventory weight management
  121. /// </summary>
  122. public class CharacterCarryCapacity : MonoBehaviour
  123. {
  124. [Header("Carry Capacity System")]
  125. public CarryCapacitySystem carrySystem = new CarryCapacitySystem();
  126. [Header("Current Load Info (Read-Only)")]
  127. [SerializeField] private int currentWeight = 0;
  128. [SerializeField] private int maxCapacity = 0;
  129. [SerializeField] private CarryCapacitySystem.EncumbranceLevel currentEncumbrance = CarryCapacitySystem.EncumbranceLevel.Light;
  130. [Header("Debug")]
  131. public bool showDebugInfo = false;
  132. private Character character;
  133. private Inventory inventory;
  134. // Events
  135. public event System.Action<CarryCapacitySystem.EncumbranceLevel> OnEncumbranceChanged;
  136. void Start()
  137. {
  138. character = GetComponent<Character>();
  139. inventory = GetComponent<Inventory>();
  140. if (character == null)
  141. {
  142. Debug.LogError($"CharacterCarryCapacity requires a Character component on {gameObject.name}");
  143. enabled = false;
  144. return;
  145. }
  146. UpdateCapacity();
  147. }
  148. /// <summary>
  149. /// Update maximum capacity based on current strength
  150. /// </summary>
  151. public void UpdateCapacity()
  152. {
  153. if (character == null) return;
  154. maxCapacity = carrySystem.CalculateMaxCapacity(character.Strength);
  155. UpdateCurrentWeight();
  156. if (showDebugInfo)
  157. Debug.Log($"📦 {character.CharacterName} capacity: {currentWeight}/{maxCapacity} lbs ({currentEncumbrance})");
  158. }
  159. /// <summary>
  160. /// Update current weight by calculating all carried items
  161. /// </summary>
  162. public void UpdateCurrentWeight()
  163. {
  164. currentWeight = CalculateTotalWeight();
  165. var previousEncumbrance = currentEncumbrance;
  166. currentEncumbrance = carrySystem.GetEncumbranceLevel(currentWeight, maxCapacity);
  167. if (previousEncumbrance != currentEncumbrance)
  168. {
  169. OnEncumbranceChanged?.Invoke(currentEncumbrance);
  170. if (showDebugInfo)
  171. Debug.Log($"📦 {character.CharacterName} encumbrance changed to: {carrySystem.GetEncumbranceDescription(currentEncumbrance)}");
  172. }
  173. }
  174. /// <summary>
  175. /// Calculate total weight of all carried items
  176. /// </summary>
  177. private int CalculateTotalWeight()
  178. {
  179. int totalWeight = 0;
  180. // Calculate inventory weight if available
  181. if (inventory != null)
  182. {
  183. totalWeight += CalculateInventoryWeight();
  184. }
  185. // Calculate equipped item weight
  186. totalWeight += CalculateEquippedWeight();
  187. return totalWeight;
  188. }
  189. /// <summary>
  190. /// Calculate weight of items in inventory
  191. /// </summary>
  192. private int CalculateInventoryWeight()
  193. {
  194. int weight = 0;
  195. // Weapons
  196. foreach (var slot in inventory.Weapons)
  197. {
  198. if (slot.item is WeaponItem weapon)
  199. {
  200. weight += GetItemWeight(weapon) * slot.quantity;
  201. }
  202. }
  203. // Armor
  204. foreach (var slot in inventory.Armor)
  205. {
  206. if (slot.item is ArmorItem armor && !slot.isEquipped) // Don't count equipped armor twice
  207. {
  208. weight += GetItemWeight(armor) * slot.quantity;
  209. }
  210. }
  211. // Miscellaneous items
  212. foreach (var slot in inventory.Miscellaneous)
  213. {
  214. if (slot.item is MiscellaneousItem misc)
  215. {
  216. weight += GetItemWeight(misc) * slot.quantity;
  217. }
  218. }
  219. return weight;
  220. }
  221. /// <summary>
  222. /// Calculate weight of equipped items
  223. /// </summary>
  224. private int CalculateEquippedWeight()
  225. {
  226. int weight = 0;
  227. if (inventory != null)
  228. {
  229. // Equipped weapon
  230. if (inventory.EquippedWeapon != null)
  231. {
  232. weight += GetItemWeight(inventory.EquippedWeapon);
  233. }
  234. // Equipped armor
  235. var equippedArmor = inventory.GetEquippedArmor();
  236. foreach (var armor in equippedArmor)
  237. {
  238. weight += GetItemWeight(armor);
  239. }
  240. }
  241. return weight;
  242. }
  243. /// <summary>
  244. /// Get weight of a specific item (with fallback values)
  245. /// </summary>
  246. private int GetItemWeight(Item item)
  247. {
  248. // TODO: When items get weight properties, use those
  249. // For now, use reasonable defaults based on item type
  250. if (item is WeaponItem weapon)
  251. {
  252. return GetWeaponWeight(weapon.itemName);
  253. }
  254. else if (item is ArmorItem armor)
  255. {
  256. return GetArmorWeight(armor.itemName, armor.armorType);
  257. }
  258. else if (item is MiscellaneousItem misc)
  259. {
  260. return GetMiscWeight(misc.itemName);
  261. }
  262. return 1; // Default weight
  263. }
  264. /// <summary>
  265. /// Get weapon weight based on name/type
  266. /// </summary>
  267. private int GetWeaponWeight(string weaponName)
  268. {
  269. string lower = weaponName.ToLower();
  270. if (lower.Contains("dagger") || lower.Contains("knife"))
  271. return 1;
  272. else if (lower.Contains("sword") || lower.Contains("axe"))
  273. return 3;
  274. else if (lower.Contains("bow"))
  275. return 2;
  276. else if (lower.Contains("staff") || lower.Contains("wand"))
  277. return 2;
  278. else if (lower.Contains("hammer") || lower.Contains("mace"))
  279. return 4;
  280. else if (lower.Contains("fist") || lower.Contains("unarmed"))
  281. return 0;
  282. return 3; // Default sword weight
  283. }
  284. /// <summary>
  285. /// Get armor weight based on type
  286. /// </summary>
  287. private int GetArmorWeight(string armorName, ArmorType armorType)
  288. {
  289. switch (armorType)
  290. {
  291. case ArmorType.Light:
  292. return 5;
  293. case ArmorType.Medium:
  294. return 15;
  295. case ArmorType.Heavy:
  296. return 25;
  297. case ArmorType.Shield:
  298. return 3;
  299. default:
  300. return 10;
  301. }
  302. }
  303. /// <summary>
  304. /// Get miscellaneous item weight
  305. /// </summary>
  306. private int GetMiscWeight(string itemName)
  307. {
  308. string lower = itemName.ToLower();
  309. if (lower.Contains("potion") || lower.Contains("scroll"))
  310. return 0; // Negligible weight
  311. else if (lower.Contains("rope"))
  312. return 10;
  313. else if (lower.Contains("torch"))
  314. return 1;
  315. else if (lower.Contains("ration") || lower.Contains("bread"))
  316. return 2;
  317. else if (lower.Contains("bandage"))
  318. return 0;
  319. else if (lower.Contains("tool"))
  320. return 5;
  321. return 1; // Default misc weight
  322. }
  323. /// <summary>
  324. /// Check if character can carry additional weight
  325. /// </summary>
  326. public bool CanCarryWeight(int additionalWeight)
  327. {
  328. return (currentWeight + additionalWeight) <= maxCapacity;
  329. }
  330. /// <summary>
  331. /// Get available carrying capacity
  332. /// </summary>
  333. public int GetAvailableCapacity()
  334. {
  335. return Mathf.Max(0, maxCapacity - currentWeight);
  336. }
  337. /// <summary>
  338. /// Get current movement speed modifier due to encumbrance
  339. /// </summary>
  340. public float GetMovementSpeedModifier()
  341. {
  342. return carrySystem.GetMovementSpeedModifier(currentEncumbrance);
  343. }
  344. /// <summary>
  345. /// Get current dexterity modifier due to encumbrance
  346. /// </summary>
  347. public int GetDexterityModifier()
  348. {
  349. return carrySystem.GetDexterityModifier(currentEncumbrance);
  350. }
  351. /// <summary>
  352. /// Get encumbrance info for UI display
  353. /// </summary>
  354. public string GetEncumbranceInfo()
  355. {
  356. return $"{currentWeight}/{maxCapacity} lbs - {carrySystem.GetEncumbranceDescription(currentEncumbrance)}";
  357. }
  358. // Properties for external access
  359. public int CurrentWeight => currentWeight;
  360. public int MaxCapacity => maxCapacity;
  361. public CarryCapacitySystem.EncumbranceLevel CurrentEncumbrance => currentEncumbrance;
  362. // Manual weight update for when inventory changes
  363. public void RefreshWeight()
  364. {
  365. UpdateCurrentWeight();
  366. }
  367. }