using TMPro; using UnityEngine; public enum ActionDecisionState { NoAction, ActionSelected, ActionExecuted, EnemyAction } [System.Serializable] public class CharacterActionData { public ActionDecisionState state = ActionDecisionState.NoAction; public Vector3 targetPosition; public GameObject targetEnemy; public bool hasTarget => targetEnemy != null || targetPosition != Vector3.zero; public void Reset() { state = ActionDecisionState.NoAction; targetPosition = Vector3.zero; targetEnemy = null; } public void SetMoveTarget(Vector3 position) { targetPosition = position; targetEnemy = null; state = ActionDecisionState.ActionSelected; } public void SetAttackTarget(GameObject enemy) { targetEnemy = enemy; targetPosition = Vector3.zero; state = ActionDecisionState.ActionSelected; } } public abstract class Character : MonoBehaviour { private string characterName; private int maxHealth; private int currentHealth; private int attack; private int strength; private int constitution; private int dexterity; private int wisdom; private int perception; private Sprite characterSprite; private int level; private int experiencePoints; private int experienceToLevelUp; private int initModifier; private int damageModifier; private int spellModifier; private int movementSpeed; private int armorClass; private bool isDead = false; private bool isTargetable = true; private Bank bank; private Inventory inventory; private TextMeshProUGUI nameText; // Health bar components private UnityEngine.UI.Slider healthSlider; private Canvas healthBarCanvas; // Add action system components [Header("Action System")] public CharacterActionData actionData = new CharacterActionData(); [Header("Visual Feedback Settings")] public Color noActionColor = Color.yellow; public Color actionSelectedColor = Color.green; public Color normalColor = Color.white; public float outlineWidth = 0.1f; // Visual feedback components private Renderer characterRenderer; private Material characterMaterial; private Color originalColor; private GameObject outlineObject; public string CharacterName { get => characterName; set { characterName = value; UpdateNameDisplay(); } } public int MaxHealth { get => maxHealth; set => maxHealth = value; } public int CurrentHealth { get => currentHealth; set => currentHealth = value; } public int Attack { get => attack; set => attack = value; } public int Strength { get => strength; set => strength = value; } public int Constitution { get => constitution; set => constitution = value; } public int Dexterity { get => dexterity; set => dexterity = value; } public int Wisdom { get => wisdom; set => wisdom = value; } public int Perception { get => perception; set => perception = value; } public Sprite CharacterSprite { get => characterSprite; set => characterSprite = value; } public int Level { get => level; set => level = value; } public int ExperiencePoints { get => experiencePoints; set => experiencePoints = value; } public int ExperienceToLevelUp { get => experienceToLevelUp; set => experienceToLevelUp = value; } public int InitModifier { get => initModifier; set => initModifier = value; } public int DamageModifier { get => damageModifier; set => damageModifier = value; } public int SpellModifier { get => spellModifier; set => spellModifier = value; } public int MovementSpeed { get => movementSpeed; set => movementSpeed = value; } public int ArmorClass { get => armorClass; set => armorClass = value; } public bool IsDead { get => isDead; } public bool IsTargetable { get => isTargetable; } // Enhanced action data - dynamically typed to avoid compilation dependencies private object _enhancedActionData; public object enhancedActionData { get => _enhancedActionData; set => _enhancedActionData = value; } // Helper method to get typed action data public T GetEnhancedActionData() where T : class { return _enhancedActionData as T; } // Helper method to set enhanced action data safely public void SetEnhancedActionData(object data) { _enhancedActionData = data; } protected Weapon spawnedWeapon; public Weapon Weapon { get => spawnedWeapon; set => spawnedWeapon = value; } public abstract Character Spawn(int count); public abstract Weapon GetWeapon(); public Character() { } void Start() { InitializeStats(); // SpawnAndEquipWeapon(); InitializeActionSystem(); Debug.Log($"Character Start() completed for {CharacterName}"); healthSlider = GetComponentInChildren(); } protected virtual void InitializeStats() { // Override in subclasses to set character stats } /// /// Apply stats from combat data to this character /// public virtual void ApplyStatsFromCombatData(object combatData) { // Handle TeamCharacterCombatData if (combatData is CombatDataTransfer.TeamCharacterCombatData teamData) { CharacterName = teamData.characterName; Strength = teamData.strength; Dexterity = teamData.dexterity; Constitution = teamData.constitution; Wisdom = teamData.wisdom; Perception = teamData.perception; // Calculate derived stats based on new base stats MaxHealth = Mathf.Max(10, 10 + (Constitution - 10) * 2 + (Strength - 10) + (Dexterity - 10) / 2); CurrentHealth = MaxHealth; InitModifier = Dexterity - 10; DamageModifier = (Strength - 10) / 2; SpellModifier = Wisdom - 10; MovementSpeed = 30 + (int)(Mathf.Ceil((Dexterity - 10) / 5.0f) * 5); ArmorClass = 10 + (Dexterity - 10) / 2 + (Constitution - 10) / 3; Debug.Log($"Applied team stats to {CharacterName}: STR:{Strength} DEX:{Dexterity} CON:{Constitution} WIS:{Wisdom} PER:{Perception}"); } // Handle EnemyCombatData else if (combatData is CombatDataTransfer.EnemyCombatData enemyData) { CharacterName = enemyData.enemyName; // Apply enemy stats (you may want to add these fields to EnemyCombatData) // For now, keep the initialized stats but update the name Debug.Log($"Applied enemy data to {CharacterName}"); } else { Debug.LogWarning($"Unknown combat data type for {CharacterName}"); } } public Weapon GetEquippedWeapon() { return spawnedWeapon; } public virtual void SpawnAndEquipWeapon() { Debug.Log($"SpawnAndEquipWeapon called for {CharacterName}"); Weapon weaponPrefab = GetWeapon(); Debug.Log($"GetWeapon returned: {(weaponPrefab != null ? weaponPrefab.name : "null")}"); if (weaponPrefab != null) { GameObject weaponInstance = Instantiate(weaponPrefab.gameObject); weaponInstance.transform.SetParent(this.transform, false); Debug.Log($"Instantiated weapon: {weaponInstance.name}"); spawnedWeapon = weaponInstance.GetComponent(); Debug.Log($"spawnedWeapon component: {(spawnedWeapon != null ? spawnedWeapon.GetType().Name : "null")}"); if (spawnedWeapon != null) { // Set the wielder reference directly to avoid timing issues spawnedWeapon.SetWielder(this); Debug.Log($"{CharacterName} equipped {spawnedWeapon.weaponName}"); // Position the weapon appropriately (you may want to customize this) PositionWeapon(weaponInstance); } else { Debug.LogError($"Weapon prefab for {CharacterName} doesn't have a Weapon component!"); Destroy(weaponInstance); } } else { Debug.Log($"{CharacterName} no weapon prefab, trying direct creation..."); CreateDirectWeapon(); } } protected virtual Weapon CreateDirectWeapon() { Debug.LogWarning($"{CharacterName} - CreateDirectWeapon not implemented in subclass!"); return null; } // Virtual method to position the weapon - can be overridden by specific character types protected virtual void PositionWeapon(GameObject weaponInstance) { // Default positioning - you can customize this based on your character setup weaponInstance.transform.localPosition = Vector3.zero; weaponInstance.transform.localRotation = Quaternion.identity; weaponInstance.transform.localScale = Vector3.one; // Ensure proper scale // Optional: Look for specific attachment points Transform weaponAttachPoint = transform.Find("WeaponAttachPoint"); if (weaponAttachPoint != null) { weaponInstance.transform.SetParent(weaponAttachPoint, false); weaponInstance.transform.localPosition = Vector3.zero; weaponInstance.transform.localRotation = Quaternion.identity; Debug.Log($"Weapon attached to {weaponAttachPoint.name}"); } else { // If no specific attach point, just position relative to character weaponInstance.transform.localPosition = new Vector3(0.5f, 0, 0); // Slightly to the right Debug.Log($"Weapon positioned at default location relative to {CharacterName}"); } } void Awake() { nameText = GetComponentInChildren(); if (nameText == null) { Debug.LogWarning($"No TextMeshProUGUI component found in children of {gameObject.name}"); } // Initialize visual feedback system InitializeVisualFeedback(); UpdateNameDisplay(); } // Initialize visual feedback components private void InitializeVisualFeedback() { characterRenderer = GetComponent(); if (characterRenderer != null) { // Create a unique material instance to avoid modifying shared materials characterMaterial = new Material(characterRenderer.material); characterRenderer.material = characterMaterial; originalColor = characterMaterial.color; } else { Debug.LogWarning($"No Renderer found on {gameObject.name}. Visual feedback will not work."); } // Ensure we have a collider for mouse detection Collider col = GetComponent(); if (col == null) { col = gameObject.AddComponent(); Debug.Log($"Added CapsuleCollider to {gameObject.name} for mouse detection"); } } private void UpdateNameDisplay() { if (nameText != null && !string.IsNullOrEmpty(characterName)) { nameText.text = characterName; } } public void RefreshNameDisplay() { UpdateNameDisplay(); } public virtual float GetAttackRange() { return spawnedWeapon != null ? spawnedWeapon.Range : 1.0f; // Default range if no weapon is equipped } private void InitializeActionSystem() { actionData.Reset(); SetVisualState(ActionDecisionState.NoAction); } public void SetVisualState(ActionDecisionState state) { if (characterRenderer == null) return; switch (state) { case ActionDecisionState.NoAction: ShowNoActionState(); break; case ActionDecisionState.ActionSelected: ShowActionSelectedState(); break; default: ShowNormalState(); break; } } private void ShowNoActionState() { if (characterMaterial != null) characterMaterial.color = noActionColor; CreateOutline(); } private void ShowActionSelectedState() { if (characterMaterial != null) characterMaterial.color = actionSelectedColor; RemoveOutline(); } private void ShowNormalState() { if (characterMaterial != null) characterMaterial.color = originalColor; RemoveOutline(); } private void CreateOutline() { if (outlineObject != null) return; outlineObject = new GameObject("Outline"); outlineObject.transform.SetParent(transform); outlineObject.transform.localPosition = Vector3.zero; outlineObject.transform.localScale = Vector3.one * (1f + outlineWidth); // For 3D characters, we'll create a simple colored outline using a MeshRenderer MeshFilter meshFilter = outlineObject.AddComponent(); MeshRenderer outlineRenderer = outlineObject.AddComponent(); // Copy the mesh from the character MeshFilter characterMesh = GetComponent(); if (characterMesh != null) { meshFilter.mesh = characterMesh.mesh; } // Create a simple black material for the outline Material outlineMaterial = new Material(Shader.Find("Standard")); outlineMaterial.color = Color.black; outlineRenderer.material = outlineMaterial; // Set rendering order to appear behind the character outlineRenderer.sortingOrder = -1; } private void RemoveOutline() { if (outlineObject != null) { DestroyImmediate(outlineObject); outlineObject = null; } } public void ExecuteAction() { Debug.Log($"=== ExecuteAction called for {CharacterName} ==="); Debug.Log($"Action state: {actionData.state}"); Debug.Log($"Has target enemy: {actionData.targetEnemy != null}"); Debug.Log($"Target enemy name: {(actionData.targetEnemy != null ? actionData.targetEnemy.name : "none")}"); if (actionData.state != ActionDecisionState.ActionSelected) { Debug.Log($"Action not selected, returning. State: {actionData.state}"); return; } if (actionData.targetEnemy != null) { Debug.Log($"{CharacterName} executing attack on {actionData.targetEnemy.name}"); // Get equipped weapon and perform attack Debug.Log($"Getting equipped weapon for {CharacterName}..."); Debug.Log($"spawnedWeapon is: {(spawnedWeapon != null ? spawnedWeapon.GetType().Name : "null")}"); Weapon equippedWeapon = GetEquippedWeapon(); Debug.Log($"GetEquippedWeapon returned: {(equippedWeapon != null ? equippedWeapon.GetType().Name : "null")}"); if (equippedWeapon != null) { Debug.Log($"Using weapon: {equippedWeapon.weaponName}"); equippedWeapon.Attack(actionData.targetEnemy); Character targetCharacter = actionData.targetEnemy.GetComponent(); if (targetCharacter != null && targetCharacter.IsDead) { actionData.state = ActionDecisionState.ActionExecuted; SetVisualState(actionData.state); } } else { Debug.LogError($"{CharacterName} has no weapon equipped for attack!"); Debug.LogError($"spawnedWeapon field is: {(spawnedWeapon != null ? "not null" : "null")}"); Debug.LogError($"Weapon property is: {(Weapon != null ? "not null" : "null")}"); } } else if (actionData.targetPosition != Vector3.zero) { Debug.Log($"{CharacterName} moves to {actionData.targetPosition}"); // Add your movement logic here actionData.state = ActionDecisionState.ActionExecuted; SetVisualState(actionData.state); } } public bool HasActionSelected() { return actionData.state == ActionDecisionState.ActionSelected; } public bool IsActionComplete() { // If action is already executed or no action, it's complete if (actionData.state != ActionDecisionState.ActionSelected) return true; if (actionData.targetEnemy != null) { // Check if we're within attack range of the target float distance = Vector3.Distance(transform.position, actionData.targetEnemy.transform.position); float attackRange = GetAttackRange(); return distance <= attackRange; } else if (actionData.targetPosition != Vector3.zero) { // Check if we've reached the movement target using horizontal distance Vector3 currentPos = transform.position; Vector3 targetPos = actionData.targetPosition; Vector2 currentPos2D = new Vector2(currentPos.x, currentPos.z); Vector2 targetPos2D = new Vector2(targetPos.x, targetPos.z); float horizontalDistance = Vector2.Distance(currentPos2D, targetPos2D); return horizontalDistance <= 1.2f; // Arrival threshold } return true; // No target means completed } public void ResetAction() { actionData.Reset(); SetVisualState(actionData.state); } public void TakeDamage(int damage) { if (isDead) return; currentHealth -= damage; Debug.Log($"{CharacterName} takes {damage} damage. Health: {currentHealth}/{maxHealth}"); // Update health bar UpdateHealthBar(); if (currentHealth <= 0) { Die(); } } public void Heal(int healAmount) { if (isDead) return; currentHealth = Mathf.Min(currentHealth + healAmount, maxHealth); Debug.Log($"{CharacterName} healed for {healAmount} HP. Health: {currentHealth}/{maxHealth}"); // Update health bar UpdateHealthBar(); } public void AttackTarget(Character target) { if (target == null || target.IsDead) { Debug.LogWarning($"{CharacterName} tried to attack invalid target"); return; } // Calculate damage based on weapon and stats int damage = CalculateDamage(); Debug.Log($"⚔️ {CharacterName} attacks {target.CharacterName} for {damage} damage"); target.TakeDamage(damage); } private int CalculateDamage() { // Base damage calculation int baseDamage = attack + damageModifier; // Add weapon damage if equipped if (spawnedWeapon != null) { baseDamage += spawnedWeapon.damage; } // Add some randomness (±20%) float randomMultiplier = UnityEngine.Random.Range(0.8f, 1.2f); return Mathf.Max(1, Mathf.RoundToInt(baseDamage * randomMultiplier)); } // Debug method to test attacks manually public void DebugAttack(GameObject target) { Weapon equippedWeapon = GetEquippedWeapon(); if (equippedWeapon != null) { Debug.Log($"DEBUG: {CharacterName} manually attacking {target.name}"); equippedWeapon.Attack(target); } else { Debug.LogError($"DEBUG: {CharacterName} has no weapon equipped!"); } } private void Die() { isDead = true; isTargetable = false; currentHealth = 0; gameObject.SetActive(false); // Disable the character GameObject characterRenderer.enabled = false; // Make character gray if (characterMaterial != null) { characterMaterial.color = Color.gray; } // Update health bar to show death UpdateHealthBar(); Debug.Log($"{CharacterName} has died!"); } private void UpdateHealthBar() { if (healthSlider != null) { healthSlider.value = currentHealth; healthSlider.maxValue = maxHealth; // Change color based on health percentage UnityEngine.UI.Image fillImage = healthSlider.fillRect.GetComponent(); if (fillImage != null) { float healthPercent = (float)currentHealth / maxHealth; if (healthPercent > 0.6f) fillImage.color = Color.green; else if (healthPercent > 0.3f) fillImage.color = Color.yellow; else fillImage.color = Color.red; } Debug.Log($"Updated health bar for {CharacterName}: {currentHealth}/{maxHealth}"); } } }