using System.Collections; using System.Collections.Generic; using UnityEngine; using RPGCreationKit; using RPGCreationKit.BehaviourTree; using System.Linq; using RPGCreationKit.Player; namespace RPGCreationKit.AI { /// /// Adds the Combat System to the AI. /// public class AICombatSystem : AIPerception, IMeleeAttacker, IRangedAttacker { [Header("References")] public RuntimeAnimatorController defaultAnimatorController; // The RuntimeAnimatorController that needs to be played when the entity is not in combat public WeaponItem defaultWeapon; // This is the weapon that will be equipped if there's none (usually it is fists) public WeaponOnHand defaultWeaponOnHand; public SpellsKnowledge spellsKnowledge; public GameObject blockingArea; public int YNewProperty { get; set; } public float CurrentHealth { get { return attributes.CurHealth; } } public Projectile currentProjectile; public AudioSource combatAudioSource; // Spells Related GameObject spellProjectile; SpellProjectile currentSpellProjectile; [Space(10)] [Header("Status")] public float targetDistance2D = 99999.9f; public bool weaponDrawn = false; public bool canAttack = true; public bool isAttacking = false; // Is playing attack animation public bool isCastingSpell = false; public bool spellsLimitedByMana = false; public bool isBlocking = false; public bool isUnbalanced = false; public bool lastAttackWasCharged; public int curAttackType = 0; public int curChargedAttackType = 0; [HideInInspector] public int lastAttackIndex = 0; [HideInInspector] public int lastChargedAttackIndex = 0; bool wantsToAttack; public bool hasToBlock = false; public int combatType = 0; public bool helpsMembersOfSameFactions = true; public EntityAttributes mainTargetAttributes; // BehaviourTrees public override void Update() { base.Update(); if (isAlive && !isInOfflineMode) { BlockCheck(); CheckForEnemies(); if (m_isInCombat) WhileInCombat(); } else if(!isAlive) { // Force health to be negative if dead attributes.CurHealth = -1; } } // Use this for initialization public override void Start() { YNewProperty = 10000; base.Start(); if (!equipment.currentWeapon && defaultWeapon) { equipment.currentWeapon = defaultWeapon; currentWeaponOnHand = defaultWeaponOnHand; defaultWeaponOnHand.gameObject.SetActive(true); equipment.currentWeaponObject = null; canAttack = true; if (weaponDrawn) { m_Anim.runtimeAnimatorController = equipment.currentWeapon.tpsAnimatorController; m_Anim.SetTrigger("Draw"); } m_Anim.SetBool("isDrawn", weaponDrawn); } } [ContextMenu("Draw Weapon"), AIInvokable] public void DrawWeapon() { combatType = (int)equipment.currentWeapon.weaponType; m_Anim.SetInteger("CombatType", combatType); //m_Anim.runtimeAnimatorController = currentWeapon.tpsAnimatorController; m_Anim.SetTrigger("Draw"); m_Anim.SetBool("isDrawn", true); //FullComboChainReset(); wantsToAttack = false; isDrawingWeapon = true; weaponDrawn = true; } [ContextMenu("Undraw Weapon")] public void UndrawWeapon() { if (weaponDrawn == false || !isAlive) return; combatType = (int)equipment.currentWeapon.weaponType; m_Anim.SetInteger("CombatType", combatType); weaponDrawn = false; m_Anim.SetBool("isDrawn", false); m_Anim.SetTrigger("Undraw"); wantsToAttack = false; } /// /// Called by animation event, resets the animator runtime controller to default /// public void UndrawWeaponEvent() { //m_Anim.runtimeAnimatorController = defaultAnimatorController; isInCombat = false; m_Anim.SetBool("InCombat", false); FullComboChainReset(); } /// /// Called by animation event, resets the animator runtime controller to default /// public void DrawWeaponEvent() { //m_Anim.runtimeAnimatorController = defaultAnimatorController; isInCombat = true; m_Anim.SetBool("InCombat", true); FullComboChainReset(); isSwitchingWeapon = false; isDrawingWeapon = false; } public bool m_isInCombat = false; public void EnterInCombatAgainst(Entity _entiy) { if (m_isInCombat || !isAlive) return; enterInCombatCalled = true; StartCoroutine(EnterInCombatTask(_entiy)); } bool doonce = false; public IEnumerator EnterInCombatTask(Entity _entiy) { yield return new WaitForEndOfFrame(); if (isUsingActionPoint) { if (!doonce) { shouldUseNPCActionPoint = false; isReachingActionPoint = false; StopUsingNPCActionPoint(); doonce = true; } while (isUsingActionPoint) { yield return null; } agent.enabled = true; } if (isInConversation) while (isInConversation) yield return null; doonce = false; // Movements StopFollowingPath(); ResetActionPointAgentState(); // Stop following the current NavMeshPath if (navmeshPath == null) navmeshPath = new UnityEngine.AI.NavMeshPath(); if (agent.isActiveAndEnabled) agent.CalculatePath(agent.transform.position + (transform.forward * 0.1f), navmeshPath); // Set the main target SetTarget(_entiy.gameObject); // Try to get the target attributes mainTargetAttributes = mainTarget.GetComponent(); enemyTargets.Add(new VisibleEnemy(_entiy, mainTargetAttributes, new AggroInfo(50))); m_isInCombat = true; OnEnterInCombat(); yield break; } /// TO ALLOW OVERRIDE THE TASK OR AT LEAST RUN CODE AT THE END public virtual void OnEnterInCombat() { } /// /// Returns true if the Entity given is an Enemy Target /// /// public bool IsFightingEntity(Entity _entity) { for (int i = 0; i < enemyTargets.Count; i++) if (enemyTargets[i].m_entity == _entity) return true; return false; } public int SortByAggro(VisibleEnemy a1, VisibleEnemy a2) { return a1.m_aggro.aggroValue.CompareTo(a2.m_aggro.aggroValue); } public virtual void LeaveCombat() { if (!isAlive) return; ClearTarget(); mainTargetAttributes = null; SetLookAtTarget(false); hasToBlock = false; //if (setMainTarget) // mainTarget = setMainTarget; // Stop following the current NavMeshPath try { if(agent.isOnNavMesh) agent.CalculatePath(agent.transform.position + (transform.forward * 0.1f), navmeshPath); } catch { } m_isInCombat = false; enterInCombatCalled = false; // Undraw UndrawWeapon(); // Default is seek selectedSteeringBehaviour = SteeringBehaviours.Seek; } public float curCombatPulse = 999999; // initialize at this value to have an instant pulse public void WhileInCombat() { if (!m_isInCombat) return; curCombatPulse += AggroSettings.COMBAT_PULSE_RATE * Time.deltaTime; if (curCombatPulse >= AggroSettings.COMBAT_PULSE_RATE) { // Update aggro pulse and list for (int i = 0; i < enemyTargets.Count; i++) { if (enemyTargets[i].m_entityAttributes.CurHealth <= 0) { enemyTargets.RemoveAt(i); continue; } enemyTargets[i].m_aggro.CombatPulse(); enemyTargets[i].m_aggro.AlterAggroValue(AggroSettings.Modifier.Distance, Vector3.Distance(transform.position, enemyTargets[i].m_entity.transform.position)); } if(enemyTargets.Count > 0) enemyTargets = enemyTargets.OrderByDescending(a => a.m_aggro.aggroValue).ToList(); curCombatPulse = 0; // Always set the most threating enemy as mainTarget if (enemyTargets.Count > 0) { SetTarget(enemyTargets[0].m_entity.gameObject); if (aiLookAt != null && enemyTargets[0].m_entity.entityFocusPart != null) aiLookAt.ForceToLookAtTarget(enemyTargets[0].m_entity.entityFocusPart.transform); mainTargetAttributes = enemyTargets[0].m_entityAttributes; } else { if (aiLookAt != null) aiLookAt.StopForcingLookAtTarget(); LeaveCombat(); } } // if is using bow and is standing still always face target if(currentWeaponOnHand.weaponItem.weaponType == WeaponType.Bow || currentWeaponOnHand.weaponItem.weaponType == WeaponType.Crossbow) { if(isStopped && mainTarget != null) { // Rotate torwards target var lookAt = Quaternion.LookRotation(mainTarget.position - transform.position); transform.rotation = Quaternion.Slerp(transform.rotation, lookAt, Time.deltaTime * generalRotationSpeed); } } } public void CheckForEnemies() { if (!isLoaded || isInConversation) // if this is not loaded skip until it is return; for(int i = 0; i < visibleAI.Count; i++) { if (visibleAI[i] == null || !visibleAI[i].isLoaded) // If the entity is not loaded we have no way to tell if he's dead or not continue; if (visibleAI[i].isAlive && !enemyTargets.Any(t => t.m_entity == visibleAI[i]) && Faction.AreHostile(this.belongsToFactions, visibleAI[i].belongsToFactions)) { if (!m_isInCombat) EnterInCombatAgainst(visibleAI[i]); enemyTargets.Add(new VisibleEnemy(visibleAI[i], visibleAI[i].GetComponent(), new AggroInfo(50))); } else if (!m_isInCombat && enemyTargets.Any(t => t.m_entity == visibleAI[i])) EnterInCombatAgainst(visibleAI[i]); // check if we're not in combat and if a memeber of our faction is fighting against an enemy if(helpsMembersOfSameFactions) { if(visibleAI[i].isInCombat && !isInCombat && visibleAI[i].belongsToFactions.Any(t => belongsToFactions.Contains(FactionsDatabase.GetFaction(t.ID)))) { if (visibleAI[i].enemyTargets.Count > 1) { EnterInCombatAgainst(visibleAI[i].enemyTargets[0].m_entity); } } } } try { if (!m_isInCombat && enemyTargets.Any(t => t.m_entity.CompareTag("Player")) && visibleTargets.Any(t => t.tr.CompareTag("Player")) || visibleTargets.Any(t => t.tr.CompareTag("Player") && !enemyTargets.Any(t => t.m_entity.CompareTag("Player")) && Faction.AreHostile(this.belongsToFactions, RckPlayer.instance.belongsToFactions) && RckPlayer.instance.isAlive)) { if (enemyTargets.Any(t => t.m_entity == Entity.GetPlayerEntity())) return; enemyTargets.Add(new VisibleEnemy(Entity.GetPlayerEntity(), RckPlayer.instance.playerAttributes, new AggroInfo(50))); EnterInCombatAgainst(Entity.GetPlayerEntity()); } } catch { } } public void ResetComboChain() { curAttackType = 0; } public void ResetComboChainCharged() { curChargedAttackType = 0; } public void FullComboChainReset() { ResetComboChain(); ResetComboChainCharged(); CancelInvoke("ResetComboChain"); CancelInvoke("ResetComboChainCharged"); } public void AttackAnimationEvent() { currentWeaponOnHand.StartCasting(false, lastAttackIndex); } public void ChargedAttackAnimationEvent() { currentWeaponOnHand.StartCasting(true, lastChargedAttackIndex); } public void EndAttackAnimationEvent() { currentWeaponOnHand.StopCasting(); } public void PlayAttackSound() { currentWeaponOnHand.PlayOneShot(currentWeaponOnHand.weaponItem.weaponAttacks[curAttackType].attackSound); } public void PlayChargedAttackSound() { currentWeaponOnHand.PlayOneShot(currentWeaponOnHand.weaponItem.weaponChargedAttacks[curChargedAttackType].attackSound); } public void ResetAttackStateEvent() { isAttacking = false; canAttack = true; } public void DrawWeaponAnimationEvent() { equipment.currentWeaponOnHip.SetActive(false); currentWeaponOnHand.gameObject.SetActive(true); } public void UndrawWeaponAnimationEvent() { currentWeaponOnHand.gameObject.SetActive(false); equipment.currentWeaponOnHip.SetActive(true); } public void MeleeChargedAttack() { // Perform a charged attack isAttacking = true; // Check if the fatigue is enough for the current attack if (attributes.CurStamina < equipment.currentWeapon.weaponChargedAttacks[curChargedAttackType].StaminaAmount) curChargedAttackType = 0; m_Anim.ResetTrigger("Attack"); m_Anim.ResetTrigger("ChargedAttack"); // Set the animation from the AnimatorController, the AnimationsEvents on the 'Swing' animation will do the job m_Anim.SetTrigger("ChargedAttack"); m_Anim.SetInteger("ChargedAttackType", curChargedAttackType); lastChargedAttackIndex = curChargedAttackType; //attributes.AttackFatigue(currentWeapon.weaponChargedAttacks[curChargedAttackType].StaminaAmount); remove stamina!! // To reset the combo if the attack is not done fast if (curChargedAttackType + 1 < equipment.currentWeapon.chargedAttackTypes) curChargedAttackType++; else curChargedAttackType = 0; CancelInvoke("ResetComboChainCharged"); if (curChargedAttackType != 0) Invoke("ResetComboChainCharged", equipment.currentWeapon.chargedAttackChainTime); // audio if (equipment.currentWeapon.weaponChargedAttacks[curChargedAttackType].attackSound != null) { combatAudioSource.clip = equipment.currentWeapon.weaponChargedAttacks[curChargedAttackType].attackSound; combatAudioSource.Play(); } canAttack = false; wantsToAttack = false; lastAttackWasCharged = true; } [AIInvokable] public void MeleeAttack() { isAttacking = true; // Check if the fatigue is enough for the current attack //if (attributes.CurStamina < equipment.currentWeapon.weaponAttacks[curAttackType].StaminaAmount) // curAttackType = 0; m_Anim.ResetTrigger("Attack"); // Set the animation from the AnimatorController, the AnimationsEvents on the 'Swing' animation will do the job m_Anim.SetTrigger("Attack"); m_Anim.SetInteger("AttackType", curAttackType); lastAttackIndex = curAttackType; attributes.DamageStamina(equipment.currentWeapon.weaponAttacks[lastAttackIndex].StaminaAmount, true, RCKSettings.DRAIN_STAMINA_ON_ATTACK_SPEEDAMOUNT); StopRecoveringStamina(); InvokeResetRecover(); // To reset the combo if the attack is not done fast if (curAttackType + 1 < equipment.currentWeapon.AttackTypes) curAttackType++; else curAttackType = 0; CancelInvoke("ResetComboChain"); if (curAttackType != 0) Invoke("ResetComboChain", equipment.currentWeapon.attackChainTime); canAttack = false; wantsToAttack = false; lastAttackWasCharged = false; } [AIInvokable] public void CastSpell() { if (spellsKnowledge != null) { if(spellsKnowledge.spellInUse != null) { if((spellsLimitedByMana && RckPlayer.instance.playerAttributes.CurMana >= spellsKnowledge.spellInUse.manaCost) || !spellsLimitedByMana) { isAttacking = true; isCastingSpell = true; int castHand = (equipment.isUsingShield || (equipment.currentWeapon != null && equipment.currentWeapon.weaponType == WeaponType.Bow)) ? 1 : 0; m_Anim.ResetTrigger("Attack"); m_Anim.ResetTrigger("CastSpell"); m_Anim.SetTrigger("CastSpell"); m_Anim.SetInteger("CastType", (int)spellsKnowledge.spellInUse.mode); m_Anim.SetInteger("CastHand", castHand); attributes.DamageMana(spellsKnowledge.spellInUse.manaCost, true, RCKSettings.DRAIN_MANA_ON_CAST_SPEEDAMOUNT); canAttack = false; wantsToAttack = false; lastAttackWasCharged = false; } // else not enough mana } //else no spell equipped } else Debug.LogWarning("AI " + entityID + " tried to cast spell, but no Spell Knowledge was set. You may have to assign Spells Knowledge in the Combat Tab."); } public void RangedAttack() { // Check if there is ammo (and if it's of correct type if (equipment.itemsEquipped[(int)EquipmentSlots.Ammo] != null && equipment.itemsEquipped[(int)EquipmentSlots.Ammo].item != null && equipment.itemsEquipped[(int)EquipmentSlots.Ammo].Amount >= 1) { // Player wants to nock an arrow wantsToAttack = true; isAttacking = true; curAttackType = 0; // you may choose different shot types (ex. for the crouch) m_Anim.ResetTrigger("Attack"); // Set the animation from the AnimatorController, the AnimationsEvents on the 'Swing' animation will do the job m_Anim.SetTrigger("Attack"); m_Anim.SetInteger("AttackType", curAttackType); lastAttackIndex = curAttackType; //cameraAnim.AttackAnimation(currentWeapon.weaponAttacks[curAttackType].cameraAnim); lastAttackIndex = curAttackType; //attributes.AttackFatigue(currentWeapon.weaponAttacks[curAttackType].StaminaAmount); // audio if (equipment.currentWeapon.weaponAttacks[curAttackType].attackSound != null) { combatAudioSource.clip = equipment.currentWeapon.weaponAttacks[curAttackType].attackSound; combatAudioSource.Play(); } canAttack = false; lastAttackWasCharged = false; } } [AIInvokable] public void SetBlocking(bool startBlocking) { hasToBlock = startBlocking; } public void BlockToggle() { hasToBlock = !hasToBlock; } public bool wasBlocking = false; public void BlockCheck() { isBlocking = hasToBlock; m_Anim.SetBool("isBlocking", isBlocking); //fpcAnim.SetBool("isUnbalanced", isUnbalanced); if (isBlocking && !wasBlocking) { m_Anim.ResetTrigger("hasBlockedAttack"); m_Anim.SetTrigger("StartBlocking"); } if (isBlocking) { if (!wasBlocking) { //attributes.BlockingFatigue(); TODO wasBlocking = true; } } else { if (blockingArea.activeSelf) blockingArea.SetActive(false); if (wasBlocking) { //playerVitals.StopBlockingFatigue(); wasBlocking = false; isBlocking = false; } } } /// /// Called by an Animation event on the block idle that enables the blocking area /// public void BlockAnimationEvent() { blockingArea.SetActive(true); } public override void DamageBlocked(DamageContext damageContext) { base.DamageBlocked(damageContext); // If the attack was blocked reduce the damage float blockingMultiplier = (equipment.isUsingShield) ? ((ArmorItem)equipment.itemsEquipped[(int)EquipmentSlots.LHand].item).blockingMultiplier : equipment.currentWeapon.BlockingMultiplier; AudioClip blockingSound = (equipment.isUsingShield) ? null : equipment.currentWeapon.blockSound; if (blockingSound != null) currentWeaponOnHand.PlayOneShot(blockingSound); damageContext.amount *= blockingMultiplier; attributes.DamageStamina(damageContext.amount * blockingMultiplier, true, RCKSettings.DRAIN_STAMINA_ON_ATTACKBLOCKED_SPEEDAMOUNT); if (attributes.CurStamina <= 0) { attributes.CurStamina = 0; isUnbalanced = true; isBlocking = false; m_Anim.SetTrigger("hasBeenUnbalanced"); } else { m_Anim.SetTrigger("hasBlockedAttack"); } Damage(damageContext); } public override void Die() { hasToBlock = false; // If the AI had a shield equipped if(equipment.isUsingShield) { // Spawn the item in the world ItemInWorld itemInWorld = Instantiate(equipment.currentShield.itemInWorld, bodyData.lHand.transform.position, bodyData.lHand.transform.rotation).GetComponent(); itemInWorld.isCreatedItem = true; var allItems = SaveSystem.SaveSystemManager.instance.saveFile.CreatedItemsInWorldData.allCreatedItemsInWorld; // Add this created item if (allItems.ContainsKey(WorldManager.instance.currentCenterCell.ID)) allItems[WorldManager.instance.currentCenterCell.ID].itemsInThis.Add(itemInWorld.ToCreatedItemSaveData()); else { allItems.Add(WorldManager.instance.currentCenterCell.ID, new SaveSystem.CreatedItemInWorldCollection()); allItems[WorldManager.instance.currentCenterCell.ID].itemsInThis.Add(itemInWorld.ToCreatedItemSaveData()); } equipment.currentShieldObject.SetActive(false); equipment.Unequip(EquipmentSlots.LHand); inventory.RemoveItem(equipment.currentShield.ItemID, 1); itemInWorld.Metadata = inventory.GetItem(equipment.currentShield.ItemID).metadata; } // If the AI weapon was drawn if (isInCombat && currentWeaponOnHand != null) { // Stop casting (entity may have died while attacking) currentWeaponOnHand.StopCasting(); // Instantiate the InWorld equivalent if(equipment.currentWeapon.itemInWorld != null) { // Spawn the item in the world ItemInWorld itemInWorld = Instantiate(equipment.currentWeapon.itemInWorld, bodyData.lHand.transform.position, bodyData.lHand.transform.rotation).GetComponent(); itemInWorld.isCreatedItem = true; var allItems = SaveSystem.SaveSystemManager.instance.saveFile.CreatedItemsInWorldData.allCreatedItemsInWorld; // Add this created item if (allItems.ContainsKey(WorldManager.instance.currentCenterCell.ID)) allItems[WorldManager.instance.currentCenterCell.ID].itemsInThis.Add(itemInWorld.ToCreatedItemSaveData()); else { allItems.Add(WorldManager.instance.currentCenterCell.ID, new SaveSystem.CreatedItemInWorldCollection()); allItems[WorldManager.instance.currentCenterCell.ID].itemsInThis.Add(itemInWorld.ToCreatedItemSaveData()); } itemInWorld.Metadata = inventory.GetItem(equipment.currentWeapon.ItemID).metadata; } currentWeaponOnHand.gameObject.SetActive(false); inventory.RemoveItem(equipment.currentWeapon.ItemID, 1); } StopAllCoroutines(); m_isInCombat = false; isInCombat = false; if (currentSpellProjectile != null && !currentSpellProjectile.shot) DestroyImmediate(currentSpellProjectile.gameObject); base.Die(); blockingArea.SetActive(false); } public bool isSwitchingWeapon = false; public void ChangeWeapon(WeaponItem _weaponToEquip) { isSwitchingWeapon = true; ItemInInventory itemToEquip; // Check if item exists in inventory if ((itemToEquip = inventory.GetItem(_weaponToEquip)) != null) StartCoroutine(ChangeWeaponTask(itemToEquip)); } public void ChangeWeapon(string weaponID) { isSwitchingWeapon = true; ItemInInventory itemToEquip; // Check if item exists in inventory if( (itemToEquip = inventory.GetItem(weaponID)) != null) { StartCoroutine(ChangeWeaponTask(itemToEquip)); } } public IEnumerator ChangeWeaponTask(ItemInInventory _itemToEquip) { hasToBlock = false; bool wasDrawn = weaponDrawn; bool wasInCombat = m_isInCombat; if (isInCombat) { while (!canAttack || isDrawingWeapon) yield return new WaitForEndOfFrame(); // Undraw the weapon UndrawWeapon(); } while (isInCombat || isDrawingWeapon) yield return new WaitForEndOfFrame(); equipment.Equip(_itemToEquip.item); OnEquipmentChangesHands(); OnEquipmentChangesAmmo(); if (wasInCombat) DrawWeapon(); else isSwitchingWeapon = false; yield return null; } /// /// Returns true if the caller has ammo for the equipped weapon and equips it, otherwise it returns false without equipping anything. /// /// public bool EquipAmmoForCurrentWeapon() { AmmoItem curAmmo = null; switch(currentWeaponOnHand.weaponItem.weaponType) { case WeaponType.Bow: for(int i = 0; i < inventory.subLists[(int)ItemTypes.AmmoItem].Count; i++) { curAmmo = inventory.subLists[(int)ItemTypes.AmmoItem][i].item as AmmoItem; if (curAmmo.type == AmmoItem.AmmoType.Arrow) { equipment.Equip(curAmmo); return true; } } break; case WeaponType.Crossbow: for (int i = 0; i < inventory.subLists[(int)ItemTypes.AmmoItem].Count; i++) { curAmmo = inventory.subLists[(int)ItemTypes.AmmoItem][i].item as AmmoItem; if (curAmmo.type == AmmoItem.AmmoType.Dart) { equipment.Equip(curAmmo); return true; } } break; default: return false; } return false; } /// /// Returns true if the caller has ammo for the equipped weapon and equips it, otherwise it returns false without equipping anything. /// /// public bool EquipAmmoForWeapon(WeaponItem _weapon) { AmmoItem curAmmo = null; switch (_weapon.weaponType) { case WeaponType.Bow: for (int i = 0; i < inventory.subLists[(int)ItemTypes.AmmoItem].Count; i++) { curAmmo = inventory.subLists[(int)ItemTypes.AmmoItem][i].item as AmmoItem; if (curAmmo.type.HasFlag(AmmoItem.AmmoType.Arrow)) { equipment.Equip(curAmmo); return true; } } break; case WeaponType.Crossbow: for (int i = 0; i < inventory.subLists[(int)ItemTypes.AmmoItem].Count; i++) { curAmmo = inventory.subLists[(int)ItemTypes.AmmoItem][i].item as AmmoItem; if (curAmmo.type.HasFlag(AmmoItem.AmmoType.Dart)) { equipment.Equip(curAmmo); return true; } } break; default: return false; } return false; } /// /// Returns the AmmoItem if the caller has ammo for the equipped weapon, otherwise it returns null. /// /// public AmmoItem HasAmmoForCurrentWeapon() { AmmoItem curAmmo = null; switch (currentWeaponOnHand.weaponItem.weaponType) { case WeaponType.Bow: for (int i = 0; i < inventory.subLists[(int)ItemTypes.AmmoItem].Count; i++) { curAmmo = inventory.subLists[(int)ItemTypes.AmmoItem][i].item as AmmoItem; if (curAmmo.type == AmmoItem.AmmoType.Arrow) { return curAmmo; } } break; case WeaponType.Crossbow: for (int i = 0; i < inventory.subLists[(int)ItemTypes.AmmoItem].Count; i++) { curAmmo = inventory.subLists[(int)ItemTypes.AmmoItem][i].item as AmmoItem; if (curAmmo.type == AmmoItem.AmmoType.Dart) { return(curAmmo); } } break; default: return null; } return null; } /// /// Returns the AmmoItem if the caller has ammo for the weapon passed, otherwise it returns null. /// /// public AmmoItem HasAmmoForWeapon(WeaponItem _weapon) { AmmoItem curAmmo = null; switch (_weapon.weaponType) { case WeaponType.Bow: for (int i = 0; i < inventory.subLists[(int)ItemTypes.AmmoItem].Count; i++) { curAmmo = inventory.subLists[(int)ItemTypes.AmmoItem][i].item as AmmoItem; if (curAmmo.type.HasFlag(AmmoItem.AmmoType.Arrow)) { return curAmmo; } } break; case WeaponType.Crossbow: for (int i = 0; i < inventory.subLists[(int)ItemTypes.AmmoItem].Count; i++) { curAmmo = inventory.subLists[(int)ItemTypes.AmmoItem][i].item as AmmoItem; if (curAmmo.type.HasFlag(AmmoItem.AmmoType.Dart)) { return (curAmmo); } } break; default: return null; } return null; } /// TODO public bool SwitchToBestMeleeWeapon() { return false; } public bool SwitchToBestRangedWeapon() { return false; } public bool SwitchToBestAmmo() { return false; } public bool EquipShieldIfHeCan() { return false; } GameObject projectile; public void SpawnAmmoOnRHand() { projectile = Instantiate(((AmmoItem)equipment.currentAmmo).projectile, bodyData.rHand); currentProjectile = projectile.GetComponent(); currentProjectile.shooterEntity = this; } public bool projectileNocked = false; public void OnAmmoIsReady() { projectileNocked = true; } [AIInvokable] public void RangedLoadAndAim() { StopCoroutine(RangedLoadAndAimTask()); StartCoroutine(RangedLoadAndAimTask()); } private IEnumerator RangedLoadAndAimTask() { // Player wants to nock an arrow wantsToAttack = true; isAttacking = true; curAttackType = 0; // you may choose different shot types (ex. for the crouch) m_Anim.ResetTrigger("Attack"); // Set the animation from the AnimatorController m_Anim.SetTrigger("Attack"); m_Anim.SetInteger("AttackType", curAttackType); equipment.currentWeaponAnimator.ResetTrigger("Load"); equipment.currentWeaponAnimator.Play("Load"); //currentWeaponAnimator.SetTrigger("Load"); //fpcAnim.Play("Load"); //cameraAnim.AttackAnimation(currentWeapon.weaponAttacks[curAttackType].cameraAnim); lastAttackIndex = curAttackType; //playerVitals.AttackFatigue(currentWeapon.weaponAttacks[curAttackType].StaminaAmount); // audio //if (currentWeapon.weaponAttacks[curAttackType].attackSound != null) //{ // combatAudioSource.clip = currentWeapon.weaponAttacks[curAttackType].attackSound; // combatAudioSource.Play(); //} canAttack = false; lastAttackWasCharged = false; //aiLookAt.ForceToLookAtTarget(mainTarget); Vector3 _shootDir = (mainTarget.transform.position - this.transform.position).normalized; Vector3 noiseDir = Vector3.zero; bool abort = false; while (!projectileNocked && !abort) { // Calculate some noise noiseDir = new Vector3(Random.Range(-1f, 1f) / attributes.derivedAttributes.rangedCombatAccuracy * 100, Random.Range(-1f, 1f) / attributes.derivedAttributes.rangedCombatAccuracy * 100, 0); // Aim preditct etc if (mainTarget == null || !m_isInCombat) { abort = true; break; } if (mainTarget.CompareTag("Player")) { _shootDir = ((mainTarget.transform.position - this.transform.position) + noiseDir + (Vector3.down * (Player.RckPlayer.instance.IsCrouching ? 2.25f : 1.5f))).normalized; } else _shootDir = ((mainTarget.transform.position - this.transform.position) + noiseDir).normalized; yield return new WaitForSeconds(0.1f); } if(abort) { m_Anim.SetTrigger("AbortShoot"); if(currentProjectile != null) Destroy(currentProjectile.gameObject); projectileNocked = false; wantsToAttack = false; isAttacking = false; canAttack = true; } // Aim RangedShoot(_shootDir); } public void RangedShoot(Vector3 _direction) { m_Anim.ResetTrigger("Attack"); m_Anim.SetTrigger("Attack"); m_Anim.SetInteger("AttackType", 1); equipment.currentWeaponAnimator.ResetTrigger("Shoot"); equipment.currentWeaponAnimator.SetTrigger("Shoot"); projectileNocked = false; wantsToAttack = false; isAttacking = false; // Shoot the arrow currentProjectile.Shoot(equipment.currentWeapon, (AmmoItem)equipment.itemsEquipped[(int)EquipmentSlots.Ammo].item, this, _direction, false); //inventory.RemoveItem() aiLookAt.Invoke("StopForcingLookAtTarget", 1f); } /// /// Those callbacks will be called by the functionalities of the BehaviourTree. /// The actual implementations and behaviours of those must be defined per each entity. /// void IMeleeAttacker.BlockEvent() { throw new System.NotImplementedException(); } void IRangedAttacker.BlockEvent() { throw new System.NotImplementedException(); } void IMeleeAttacker.DrawWeapon() { throw new System.NotImplementedException(); } void IRangedAttacker.DrawWeapon() { throw new System.NotImplementedException(); } void IMeleeAttacker.MeleeAttackCheck() { throw new System.NotImplementedException(); } void IMeleeAttacker.MeleeAttackEvent() { throw new System.NotImplementedException(); } void IMeleeAttacker.OnEnterCombat() { throw new System.NotImplementedException(); } void IRangedAttacker.OnEnterCombat() { throw new System.NotImplementedException(); } void IRangedAttacker.RangedAttackCheck() { throw new System.NotImplementedException(); } void IRangedAttacker.RangedAttackEvent() { throw new System.NotImplementedException(); } void IMeleeAttacker.UndrawWeapon() { throw new System.NotImplementedException(); } void IRangedAttacker.UndrawWeapon() { throw new System.NotImplementedException(); } void IMeleeAttacker.UseMelee() { throw new System.NotImplementedException(); } void IRangedAttacker.UseRanged() { throw new System.NotImplementedException(); } public void OnAttackIsBlocked() { canAttack = false; wantsToAttack = false; isAttacking = true; isBlocking = false; FullComboChainReset(); m_Anim.SetTrigger("hasBeenBlocked"); } [AIInvokable] public void Get2DTargetDistance() { if (mainTarget != null) targetDistance2D = Calculate2DTargetDistance(transform.position, mainTarget.transform.position); else targetDistance2D = 999999.9f; } private float Calculate2DTargetDistance(Vector3 v1, Vector3 v2) { float xDiff = v1.x - v2.x; float zDiff = v1.z - v2.z; return Mathf.Sqrt((xDiff * xDiff) + (zDiff * zDiff)); } // Spells, Update 1.3 - Anim callbacks public void CastSpellInit() { Spell curSpell = spellsKnowledge.spellInUse; Transform handT = (equipment.isUsingShield || (equipment.currentWeapon != null && equipment.currentWeapon.weaponType == WeaponType.Bow)) ? bodyData.rHand : bodyData.lHand; switch (curSpell.mode) { case SpellModes.Projectile: spellProjectile = Instantiate(curSpell.magicProjectile, handT); spellProjectile.SetLayer(RCKLayers.Default, true); currentSpellProjectile = spellProjectile.GetComponent(); break; case SpellModes.Touch: spellProjectile = Instantiate(curSpell.magicProjectile, handT); spellProjectile.SetLayer(RCKLayers.Default, true); currentSpellProjectile = spellProjectile.GetComponent(); currentSpellProjectile.Init(this, false); break; case SpellModes.Self: spellProjectile = Instantiate(curSpell.magicProjectile, handT); spellProjectile.SetLayer(RCKLayers.Default, true); currentSpellProjectile = spellProjectile.GetComponent(); currentSpellProjectile.Init(this, false); break; } } public void CastSpellAttackEvent() { // Instantiate/Do Stuff Spell curSpell = spellsKnowledge.spellInUse; Vector3 _shootDir; switch (curSpell.mode) { case SpellModes.Projectile: _shootDir = ((mainTarget.transform.position - this.transform.position) + (Vector3.down * (Player.RckPlayer.instance.IsCrouching ? 2.25f : 1.5f))).normalized; currentSpellProjectile.Shoot(this, _shootDir, false); currentSpellProjectile.ExecuteOnCasterEffects(); break; case SpellModes.Touch: _shootDir = ((mainTarget.transform.position - this.transform.position) + (Vector3.down * (Player.RckPlayer.instance.IsCrouching ? 2.25f : 1.5f))).normalized; if (currentSpellProjectile != null) { currentSpellProjectile.Shoot(this, _shootDir, false); currentSpellProjectile.ExecuteOnCasterEffects(); } break; case SpellModes.Self: currentSpellProjectile.Explode(); currentSpellProjectile.ExecuteOnCasterEffects(); break; } } public void CastSpellResetEvent() { ResetAttackStateEvent(); isAttacking = false; isCastingSpell = false; } /// /// Ignores Faction and just engages with the player if player is visible /// public void TryEngageWithPlayer() { if (!isLoaded || isInConversation) // if this is not loaded skip until it is return; try { if (!m_isInCombat && visibleTargets.Any(t => t.tr.CompareTag("Player"))) { if (enemyTargets.Any(t => t.m_entity == Entity.GetPlayerEntity())) return; enemyTargets.Add(new VisibleEnemy(Entity.GetPlayerEntity(), RckPlayer.instance.playerAttributes, new AggroInfo(50))); EnterInCombatAgainst(Entity.GetPlayerEntity()); } } catch { } } } }