using Beyond; using DG.Tweening; using PixelCrushers.Wrappers; using Sirenix.OdinInspector; using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.Serialization; using UnityEngine.UI; namespace Beyond { public enum TreeState { Empty, FreshlySeeded, SmallTree, MediumTree, FullyGrown, Sick } public enum TreeType //may not be needed, the fruit is being added from b trigger generic action { Apple, Orange, Banana } [System.Serializable] public class TreeStateData { public TreeState treeState; public float health = 100; public bool isEnabled = true; public TreeStateData(TreeState treeState, float health, bool isEnabled) { this.treeState = treeState; this.health = health; this.isEnabled = isEnabled; } } [System.Serializable] public class TreeSpotController : MonoBehaviour { public float health = 100, maxHealth = 100, fruitSicknessThreshold = 90; public TreeState currentTreeState; private float effectDelay = 0.80f; public List fruits; [Tooltip("double growth, so less steps are required ")] [SerializeField] private bool doubleGrowthStep = true; [SerializeField] private SphereCollider validatorCollider; [SerializeField] private TriggerEvent validatorEventReceiver; [SerializeField] private SphereCollider plantingCollider, fastGrowingCollider, healingCollider; [SerializeField] private bTriggerGenericAction fastGrowingTrigger, healingTrigger; [SerializeField] private GameObject infoTextObject; [SerializeField] private Text infoText; [SerializeField] private Animator treeAnimator; [SerializeField] private AnimationCurve speedCurve; [SerializeField] private AudioSource growAudio, healAudio; private Color targetColor = Color.black, targetColorLeaves = new Color(1, 1, 1, 0); private Color initialColor = Color.white; private float bPGainOnAction = 1f; [SerializeField] private List woodRenderers, leavesRenderers; private List woodMaterials = new(), leavesMaterials = new(); private string woodColorName = "_BaseColor", leavesColorName = "_BaseColor"; [SerializeField] public bItemListData items; [FormerlySerializedAs("SetEmptyStateOnAwake")] [SerializeField] private bool setEmptyStateOnEnable = true; [Header("")] [SerializeField] private Skills skillRequiredToPlant; [HideInInspector] public bItem itemRequiredToPlant, itemRequiredToGrow, itemRequiredToHeal; private const string growinAnimation = "Growing", fruitsAnimation = "Fruits ", sickAnimation = "Sick"; private const float freshlySeededSize = 0.3f, smallTreeSize = 0.55f, mediumTreeSize = 0.8f, fullyGrownSize = 1f, sickTree = 1f; [Header("Events")] public UnityEvent onHealTree; [Header("Events")] public UnityEvent onStateChanged; [Header("Events")] public UnityEvent onFullyGrown; private bool canPlayCantDoClip = true; private void Awake() { StartCoroutine(AddOnInventoryUpdateEventCoroutine()); woodRenderers.ForEach(renderer => woodMaterials.Add(renderer.material)); leavesRenderers.ForEach(renderer => leavesMaterials.Add(renderer.material)); } private void OnEnable() { if (setEmptyStateOnEnable) { ResetTreeInstant(); } else { SetTreeStateInstant(currentTreeState); } canPlayCantDoClip = true; } private IEnumerator AddOnInventoryUpdateEventCoroutine() { yield return new WaitForEndOfFrame(); Player.Instance.ItemManager.onFinishItemDestroy += UpdateInteractionsOnInventoryUpdate; UpdateInteractionsOnInventoryUpdate(); } public void UpdateInteractionsOnInventoryUpdate() { if (currentTreeState == TreeState.Empty || currentTreeState == TreeState.FullyGrown) { return; } bItemManager itemManager = Player.Instance.ItemManager; if (!itemManager.ContainItem(itemRequiredToGrow.id)) { fastGrowingCollider.enabled = false; // ResetInteractionButtonImage(); if (currentTreeState == TreeState.FreshlySeeded || currentTreeState == TreeState.SmallTree || currentTreeState == TreeState.MediumTree) { ActivateGrowSpeedUpValidator(); } else if (currentTreeState == TreeState.Sick) { ActivateHealingValidator(); } } } public void ResetTreeInstant() { SetTreeStateInstant(TreeState.Empty); } public void SetTreeStateInstant(TreeState treeState) { SetTreeState(treeState, true); } public void SetTreeState(TreeState treeState, bool instant) { DisableAllColliders(); StopAllCoroutines(); float animationValue = 0; string animationName = ""; int animLayer = 0; //empty-can plant //freshly-medium can switch (treeState) { case TreeState.Empty: ActivatePlantingValidator(); break; case TreeState.FreshlySeeded: animationValue = freshlySeededSize; animationName = growinAnimation; ActivateGrowSpeedUpValidator(); break; case TreeState.SmallTree: animationValue = smallTreeSize; animationName = growinAnimation; ActivateGrowSpeedUpValidator(); break; case TreeState.MediumTree: animationValue = mediumTreeSize; animationName = growinAnimation; ActivateGrowSpeedUpValidator(); break; case TreeState.FullyGrown: //no interaction available //assume 90hp or less = fruits growing backwards // 80-0 plaY sick animation // health = 100; //normally growing tree here if (currentTreeState == TreeState.Sick) { animationValue = 0; //reverse sick animation animationName = sickAnimation; animLayer = 0; LightenTreeColor(); } else { animationValue = fullyGrownSize; animationName = growinAnimation; leavesMaterials.ForEach(material => material.DOColor(initialColor, leavesColorName, 1f)); } StartCoroutine(ShowFruitsCoroutine()); //animLayer = 0; break; case TreeState.Sick: animationValue = (maxHealth - health) / maxHealth; animationName = sickAnimation; animLayer = 0; if (fruits[0].transform.localScale.x > 0) { StartCoroutine(HideFruitsCoroutine()); } if (woodMaterials[0].GetColor(woodColorName).a > 0) { DarkenTreeColor(); } ActivateHealingValidator(); //enable healing break; default: break; } if (IsSettingValidForGrowAudio(treeState)) { growAudio.Play(); } currentTreeState = treeState; //TODO make sure that is enough to deal with saved state and disabled trees if (gameObject.activeInHierarchy) { if (instant) { StartCoroutine(SetInstantTreeSizeCoroutine(animationValue, animationName, animLayer)); } else { StartCoroutine(SetAnimationTreeCoroutine(animationValue, animationName, animLayer)); } } onStateChanged?.Invoke(currentTreeState); if (currentTreeState == TreeState.FullyGrown) { onFullyGrown?.Invoke(); } } private bool IsSettingValidForGrowAudio(TreeState treeState) { return treeState != TreeState.Empty && treeState != TreeState.Sick && currentTreeState != TreeState.Sick && growAudio; } private void DarkenTreeColor() { woodMaterials.ForEach(material => material.DOColor(targetColor, woodColorName, 1f)); leavesMaterials.ForEach(material => material.DOColor(targetColorLeaves, leavesColorName, 1f)); } private void LightenTreeColor() { woodMaterials.ForEach(material => material.DOColor(initialColor, woodColorName, 1f)); leavesMaterials.ForEach(material => material.DOColor(initialColor, leavesColorName, 1f)); } private IEnumerator ShowFruitsCoroutine() { yield return new WaitForSeconds(effectDelay); float currentSize = 0; float desiredSize = 1; Vector3 scale = Vector3.zero; while (currentSize < desiredSize) { currentSize += Time.deltaTime; scale = new Vector3(currentSize, currentSize, currentSize); fruits.ForEach(fruit => fruit.transform.localScale = scale); yield return null; } } private IEnumerator HideFruitsCoroutine() { float currentSize = 1; float desiredSize = 0; Vector3 scale = Vector3.zero; while (currentSize > desiredSize) { currentSize -= Time.deltaTime; scale = new Vector3(currentSize, currentSize, currentSize); fruits.ForEach(fruit => fruit.transform.localScale = scale); yield return null; } } private void DisableAllColliders() { validatorCollider.enabled = false; plantingCollider.enabled = false; //ResetInteractionButtonImage(); fastGrowingCollider.enabled = false; healingCollider.enabled = false; } private void ActivatePlantingValidator() { EnablePlantingValidatorCollider(); validatorEventReceiver.onTriggerEnter.RemoveAllListeners(); validatorEventReceiver.onTriggerEnter.AddListener(ValidateRequiredItemsToPlant); validatorEventReceiver.onTriggerExit.RemoveAllListeners(); validatorEventReceiver.onTriggerExit.AddListener(EnablePlantingValidatorCollider); } public void EnablePlantingValidatorCollider(GameObject gameObject = null) { EnablePlantingValidatorCollider(); } public void EnablePlantingValidatorCollider() { infoTextObject.SetActive(false); validatorCollider.enabled = true; plantingCollider.enabled = false; } private void ActivateGrowSpeedUpValidator() { EnableGrowSpeedValidatorCollider(); validatorEventReceiver.onTriggerEnter.RemoveAllListeners(); validatorEventReceiver.onTriggerEnter.AddListener( ValidateRequiredItemToSpeedGrow); //validate different thing validatorEventReceiver.onTriggerExit.RemoveAllListeners(); validatorEventReceiver.onTriggerExit.AddListener(EnableGrowSpeedValidatorCollider); } public void EnableGrowSpeedValidatorCollider(GameObject gameObject = null) { EnableGrowSpeedValidatorCollider(); } public void EnableGrowSpeedValidatorCollider() { // infoTextObject.SetActive(false); // fastGrowingCollider.enabled = false; // validatorCollider.enabled = true; StartCoroutine(EnableGrowingValidatorColliderCoroutine()); } private IEnumerator EnableGrowingValidatorColliderCoroutine() { validatorCollider.enabled = false; yield return new WaitForEndOfFrame(); infoTextObject.SetActive(false); fastGrowingCollider.enabled = false; validatorCollider.enabled = true; // EnableGrowSpeedValidatorCollider(); } private void ActivateHealingValidator() { DisableAllColliders(); StartCoroutine(EnableHealingColliderCoroutine()); validatorEventReceiver.onTriggerEnter.RemoveAllListeners(); validatorEventReceiver.onTriggerExit.RemoveAllListeners(); validatorEventReceiver.onTriggerEnter.AddListener(ValidateHealingRequirements); validatorEventReceiver.onTriggerExit.AddListener(EnableHealingValidatorCollider); } public void EnableHealingValidatorCollider(GameObject gameObject = null) { EnableHealingValidatorCollider(); } public void EnableHealingValidatorCollider() { StartCoroutine(EnableHealingValidatorColliderCoroutine()); } private IEnumerator EnableHealingValidatorColliderCoroutine() { healingCollider.enabled = false; validatorCollider.enabled = false; infoTextObject.SetActive(false); yield return new WaitForSeconds(0.6f); validatorCollider.enabled = true; healingCollider.enabled = false; } public void ValidateRequiredItemsToPlant(GameObject gameObject = null) { if (currentTreeState != TreeState.Empty) { infoTextObject.SetActive(false); // ResetInteractionButtonImage(); validatorCollider.enabled = false; plantingCollider.enabled = false; return; } infoTextObject.SetActive(true); if (SkillsManager.instance.GetSkillLevelOf(skillRequiredToPlant) > 0) { bItemManager itemManager = Player.Instance.ItemManager; if (itemManager.ContainItem(itemRequiredToPlant.id)) { validatorCollider.enabled = false; infoText.text = "Plant seed"; plantingCollider.enabled = true; } else { plantingCollider.enabled = false; infoTextObject.SetActive(true); infoText.text = "Requires " + itemRequiredToPlant.name; } } else { infoTextObject.SetActive(true); infoText.text = "Requires " + SkillsManager.instance.GetStringNameOf(skillRequiredToPlant); } } public void ValidateRequiredItemToSpeedGrow(GameObject gameObject = null) { if (currentTreeState == TreeState.FullyGrown) { ResetInteractionButtonImage(); infoTextObject.SetActive(false); validatorCollider.enabled = false; fastGrowingCollider.enabled = false; return; } infoTextObject.SetActive(true); int level = SkillsManager.instance.GetSkillLevelOf(Skills.MasterOfScrolls); if (level <= 0) { infoText.text = "Requires " + SkillsManager.instance.GetStringNameOf(Skills.MasterOfScrolls); return; } bItemManager itemManager = Player.Instance.ItemManager; if (itemManager.ContainItem(itemRequiredToGrow.id)) { validatorCollider.enabled = false; infoText.text = "Accelerated growth"; fastGrowingCollider.enabled = true; // StartCoroutine(EnableFastGrowingColliderCoroutine()); } else { ResetInteractionButtonImage(); fastGrowingCollider.enabled = false; validatorCollider.enabled = true; infoTextObject.SetActive(true); infoText.text = "Requires " + itemRequiredToGrow.name; } } private IEnumerator EnableFastGrowingColliderCoroutine() { fastGrowingCollider.enabled = false; yield return new WaitForSeconds(0.95f); validatorCollider.enabled = false; infoText.text = "Accelerated growth"; fastGrowingCollider.enabled = true; } public void ValidateHealingRequirements(GameObject gameObject = null) { if (currentTreeState != TreeState.Sick) { infoTextObject.SetActive(false); ResetInteractionButtonImage(); validatorCollider.enabled = false; fastGrowingCollider.enabled = false; return; } infoTextObject.SetActive(true); int level = SkillsManager.instance.GetSkillLevelOf(Skills.MasterOfScrolls); if (level <= 0) { infoText.text = "Requires " + SkillsManager.instance.GetStringNameOf(Skills.MasterOfScrolls); return; } bItemManager itemManager = Player.Instance.ItemManager; if (itemManager.ContainItem(itemRequiredToHeal.id)) { validatorCollider.enabled = false; infoText.text = "Heal tree"; healingCollider.enabled = true; } else { infoText.text = "Requires " + itemRequiredToHeal.name; } } private IEnumerator EnableHealingColliderCoroutine() { validatorCollider.enabled = false; yield return new WaitForEndOfFrame(); yield return new WaitForEndOfFrame(); yield return new WaitForSeconds(0.3f); validatorCollider.enabled = true; } public void DisableValidationTexts(GameObject gameObject = null) { //useless infoTextObject.SetActive(false); } public void SetPlantInteractionButtonImage() { Player.Instance.SetInteractableButtonImage(Resources.Load(itemRequiredToPlant.secondaryIconPath)); } public void SetFastGrowInteractionButtonImage() { Player.Instance.SetInteractableButtonImage(Resources.Load(itemRequiredToGrow.iconPath)); } public void ResetInteractionButtonImage() { Player.Instance.ResetIntaractableButtonImage(); } private IEnumerator SetInstantTreeSizeCoroutine(float size, string animationName, int layer = 0) { treeAnimator.enabled = true; treeAnimator.Play(animationName, layer, size); yield return new WaitUntil(() => treeAnimator.GetCurrentAnimatorStateInfo(0).IsName(animationName)); //could use while with no gc yield return new WaitUntil(() => treeAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime >= size); treeAnimator.enabled = false; } private IEnumerator SetAnimationTreeCoroutine(float size, string animationName, int layer = 0) { treeAnimator.enabled = true; treeAnimator.Play(animationName, layer); yield return new WaitUntil(() => treeAnimator.GetCurrentAnimatorStateInfo(0).IsName(animationName)); float startingSize = treeAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime; if (size < startingSize) { treeAnimator.SetFloat("speedMultiplier", -1); // = -1; while (treeAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime > size) { yield return null; treeAnimator.speed = speedCurve.Evaluate( (-treeAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime + startingSize) / (-size + startingSize)); } } else { treeAnimator.SetFloat("speedMultiplier", 1); // = -1; while (treeAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime <= size) { yield return null; treeAnimator.speed = speedCurve.Evaluate( (treeAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime - startingSize) / (size - startingSize)); } } treeAnimator.enabled = false; } /// /// spammming in editor will break animator /// public void GrowTree() { switch (currentTreeState) { case TreeState.Empty: if (!doubleGrowthStep) SetTreeStateAnimated(TreeState.FreshlySeeded); else SetTreeStateAnimated(TreeState.SmallTree); break; case TreeState.FreshlySeeded: if (!doubleGrowthStep) SetTreeStateAnimated(TreeState.SmallTree); else SetTreeStateAnimated(TreeState.MediumTree); break; case TreeState.SmallTree: if (!doubleGrowthStep) SetTreeStateAnimated(TreeState.MediumTree); else SetTreeStateAnimated(TreeState.FullyGrown); break; case TreeState.MediumTree: SetTreeStateAnimated(TreeState.FullyGrown); break; case TreeState.FullyGrown: break; default: break; } } public void SetTreeStateAnimated(TreeState plantState) { SetTreeState(plantState, false); } public void PlantTree() { bItemManager itemManager = Player.Instance.ItemManager; bItem itemToDestroy = itemManager.GetItem(itemRequiredToPlant.id); itemManager.UseItem(itemToDestroy); Player.Instance.SetBrightness(Player.Instance.GetBrightness() + bPGainOnAction); //GrowTree(); StartCoroutine(PlantTreeCoroutine()); } private IEnumerator PlantTreeCoroutine() { yield return new WaitForSeconds(effectDelay); // fastGrowingTrigger.OnInvalidate?.Invoke(gameObject); GrowTree(); } public void TryToGrowTree() { PlayerAttribute faithAttribute = Player.Instance.GetAttribute("Faith"); bItemAttribute itemFaithCost = itemRequiredToGrow.GetItemAttribute(bItemAttributes.Faith); //EnableGrowSpeedValidatorCollider(); if (itemFaithCost.value > faithAttribute.AttributeCurrentValue) { TryToPlayNotEnoughFaithClip(); fastGrowingTrigger.playAnimation = ""; return; } //fastGrowingTrigger.playAnimation = "Pick_Up"; fastGrowingTrigger.playAnimation = "Grow Tree"; // fastGrowingTrigger.animatorLayer = 9; faithAttribute.AttributeCurrentValue -= itemFaithCost.value; bItemManager itemManager = Player.Instance.ItemManager; bItem itemToDestroy = itemManager.GetItem(itemRequiredToGrow.id); itemManager.UseItem(itemToDestroy); Player.Instance.SetBrightness(Player.Instance.GetBrightness() + bPGainOnAction); // GrowTree(); StartCoroutine(GrowTreeCoroutine()); } private IEnumerator GrowTreeCoroutine() { yield return new WaitForSeconds(effectDelay); fastGrowingTrigger.OnInvalidate?.Invoke(gameObject); GrowTree(); } public void SetHealingInteractionButtonImage() { Player.Instance.SetInteractableButtonImage(Resources.Load(itemRequiredToHeal.iconPath)); } public void DisableValidations(GameObject gameObject = null) { infoTextObject.SetActive(false); DisableAllColliders(); } public void DisableInfoText(GameObject gameObject = null) { infoTextObject.SetActive(false); } public void TryToHeal() { bItemAttribute itemFaithCost = itemRequiredToHeal.GetItemAttribute(bItemAttributes.Faith); if (itemFaithCost.value > Player.Instance.GetCurrentFaithValue()) { TryToPlayNotEnoughFaithClip(); // healingTrigger.OnInvalidate?.Invoke(gameObject); healingTrigger.playAnimation = ""; // healingTrigger.endExitTimeAnimation = 0f; // EnableHealingValidatorCollider(); return; } // healingTrigger.endExitTimeAnimation = 0.8f; // healingTrigger.playAnimation = "Pick_Up"; healingTrigger.playAnimation = "Heal Tree"; // healingTrigger.OnInvalidate?.Invoke(gameObject); Player.Instance.UpdateFaithCurrentValue(-itemFaithCost.value); // faithAttribute.AttributeCurrentValue -= itemFaithCost.value; bItemManager itemManager = Player.Instance.ItemManager; bItem itemToDestroy = itemManager.GetItem(itemRequiredToHeal.id); itemManager.UseItem(itemToDestroy); Player.Instance.SetBrightness(Player.Instance.GetBrightness() + bPGainOnAction); // SetTreeState(TreeState.FullyGrown, false); //StartCoroutine(HealTreeCoroutine()); HealTree(); } private void TryToPlayNotEnoughFaithClip() { if (!canPlayCantDoClip) { return; } canPlayCantDoClip = false; DOVirtual.DelayedCall(1, () => canPlayCantDoClip = true); var text = $"Not enough Faith"; bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f); Player.Instance.PlayNoFaithClip(); } private IEnumerator HealTreeCoroutine() { yield return new WaitForSeconds(effectDelay); infoTextObject.SetActive(false); if (healAudio) { healAudio.Play(); } onHealTree?.Invoke(); SetTreeState(TreeState.FullyGrown, false); } public void HealTree() { health = 100; healingCollider.enabled = false; StartCoroutine(HealTreeCoroutine()); } public void SickenTree(float sickValue) { health -= sickValue; // SetTreeState(TreeState.Sick, false); StartCoroutine(SickenTreeCoroutine()); } private IEnumerator SickenTreeCoroutine() { yield return new WaitForSeconds(effectDelay); SetTreeState(TreeState.Sick, false); } public void RotateGrowUpColliderForProperAnimation() { Vector3 heading = fastGrowingCollider.transform.position - Player.Instance.transform.position; float distance = heading.magnitude; Vector3 direction = heading / distance; // This is now the normalized direction. fastGrowingCollider.transform.forward = direction; } public void RotatePlantingColliderForProperAnimation() { Vector3 heading = plantingCollider.transform.position - Player.Instance.transform.position; float distance = heading.magnitude; Vector3 direction = heading / distance; // This is now the normalized direction. plantingCollider.transform.forward = direction; } public void RotateHealingColliderForProperAnimation() { Vector3 heading = healingCollider.transform.position - Player.Instance.transform.position; float distance = heading.magnitude; Vector3 direction = heading / distance; // This is now the normalized direction. healingCollider.transform.forward = direction; } private void OnDestroy() { if (validatorEventReceiver) { validatorEventReceiver.onTriggerEnter.RemoveAllListeners(); validatorEventReceiver.onTriggerExit.RemoveAllListeners(); } } } }