using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using EvoDbManager;
public class FruitHarvesting : MonoBehaviour
{
[System.Serializable]
public class FruitGroup
{
public List<GameObject> fruitList;
private List<Vector3> initialFruitSizes = new List<Vector3>();
public List<Vector3> InitialFruitSizes { get { return initialFruitSizes; } set { initialFruitSizes = value; } }
}
//Amount of times we need to click on the tree before we can harvest the fruit
public int HarvestShakesRequired = 3;
public bool UseTestTime = false;
//Test wait time in seconds
public float TestWaitTime = 10;
public ParticleSystem LeafParticleSystem;
public Sprite FruitSprite;
//Tracks if we are able to shake the tree
//Is true by default. Set to false while the tree is already shaking
private bool _canShake = true;
//Tracks completed shakes during harvesting
private int _harvestShakesCompleted = 0;
//Tracks if we are able to harvest the tree
//Is false by default, only true when the timer has reached 0 and the fruit can be harvested
private bool _canHarvest = false;
public string EnvironmentItemID;
public List<FruitGroup> FruitGroups;
private int currentFruitGroupIndex = 0;
//List that stores initial y values of harvestable fruit
//so we can reset the y value after the fruit falls
private List<float> harvestableFruitInitialY;
private SpriteRenderer spriteRenderer;
MaterialPropertyBlock mpb;
float outlineAlpha = 0;
Sequence outlinePulseSequence;
EnvironmentItem_Logic environmentItem_Logic;
float timeLeftToGrow;
List<float> percentagesComplete;
bool updateOutlineShader = false;
private EVODbEnvironmentItem dbEnvironmentItem;
private float waitTime;
//Set up everything and run GrowFruit coroutine so fruit starts growing right away
private void Start()
{
dbEnvironmentItem = EVODbManager.Shared.environmentItems[EnvironmentItemID];
EnvironmentItemID = StringHelper.ReplaceWhitespace(EnvironmentItemID, "");
environmentItem_Logic = new EnvironmentItem_Logic();
if(UseTestTime && GameEnvironmentInfo.IsEditorOrDevelopmentBuild())
{
waitTime = TestWaitTime;
}
else
{
waitTime = (float)dbEnvironmentItem.waitTime;
}
//Start logic, will use wait time later for tweening
environmentItem_Logic.Start(dbEnvironmentItem.uniqueId, waitTime);
spriteRenderer = this.GetComponent<SpriteRenderer>();
mpb = new MaterialPropertyBlock();
spriteRenderer.GetPropertyBlock(mpb);
harvestableFruitInitialY = new List<float>();
percentagesComplete = new List<float>();
for (int i = 0; i < FruitGroups.Count; i++)
{
SetInitialSizes(FruitGroups[i]);
if (i == FruitGroups.Count - 1)
{
//Set inital positions for only the last group of fruit (The harvesteable fruit),
//as this is the only fruit that will fall and will need it's position reset.
SetInitialFruitPosition(FruitGroups[i].fruitList);
}
}
StartFruitGrowing();
}
void SetInitialSizes(FruitGroup fruitGroup)
{
for (int i = 0; i < fruitGroup.fruitList.Count; i++)
{
fruitGroup.InitialFruitSizes.Add(fruitGroup.fruitList[i].transform.localScale);
}
}
private void StartFruitGrowing()
{
double timeLeft = environmentItem_Logic.SetLastInteraction_TotalSeconds();
if (timeLeft < 0)
{
timeLeft = 0;
}
//First we calculate the percentage of time we have left to wait
//(time remaining / total time to wait) * 100
float percentageLeft = (Convert.ToSingle(timeLeft / waitTime)) * 100;
//We get the inverse by doing 100 - time remaining
//so we have the percentage amount of time that has passed so far.
float percentageCompleted = 100 - percentageLeft;
//We calculate the percentage out of 100 of each fruit group
//Normally we have 4 fruit groups so 100 / 4 = 25
float oneGroupPercent = 100 / FruitGroups.Count;
//Loops through all of the object lists
for (int i = 0; i < FruitGroups.Count; i++)
{
percentagesComplete.Add(percentageCompleted);
//Get min and max percentage of each fruit group
float min = oneGroupPercent * i;
float max = min + oneGroupPercent;
//If our percentage completed is within the min and max of this fruit group
//It means this is the fruit group that is currently growing and therefore we set its
//size based on the percentWithinRange
if (percentageCompleted > min && percentageCompleted <= max)
{
//Here we get the percentage out of 100 that the percentage completed is within each fruit groups min/max range
//((input - min) * 100) / (max - min)
//Example: If our time completed is 15%, we calculate ((15 - 0) * 100) / (25 - 0) = 60%
//We can then use this percentage to calculate the size the fruit in the group should be
//with 0% being at a size of 0, and 100% being at its largest size
float percentWithinRange = ((percentageCompleted - min) * 100) / (max - min);
SetInitialFruitSizes(FruitGroups[i], percentWithinRange);
currentFruitGroupIndex = i;
}
//All other fruits are not growing and should therefore not be showing,
//so we set their sizes to 0
else
{
SetInitialFruitSizes(FruitGroups[i], 0);
}
}
timeLeftToGrow = Convert.ToSingle(timeLeft);
StartCoroutine(GrowFruit());
}
//Save list of initial fruit sizes so we know what their end size should be when scaling them up
private void SetInitialFruitSizes(FruitGroup fruitGroup, float scale)
{
for (int i = 0; i < fruitGroup.fruitList.Count; i++)
{
//If we pass in a scale of zero, set scale to zero with no special logic
if (scale == 0)
{
fruitGroup.fruitList[i].transform.localScale = new Vector3(0, 0, 1);
}
//Otherwise, we will calculate the fruit's size based on the percentage we pass in
else
{
//Here we calculate each fruits size based on percentWithinRange and the fruits maximum/starting size
//Value = percentage * max / 100
//Example: If our fruit is 60% grown, then our fruit size would be 60 * .5f / 100 = .3f,
//so .3 is 60% in a range of 0-.5f
float currentScaleValueX = scale * fruitGroup.fruitList[i].transform.localScale.x / 100;
float currentScaleValueY = scale * fruitGroup.fruitList[i].transform.localScale.y / 100;
fruitGroup.fruitList[i].transform.localScale = new Vector3(currentScaleValueX, currentScaleValueY, 1);
}
}
}
//Save list of initial harvesteable fruit y positions so we can reset them later after they fall
private void SetInitialFruitPosition(List<GameObject> objList)
{
for (int i = 0; i < objList.Count; i++)
{
harvestableFruitInitialY.Add(objList[i].transform.localPosition.y);
}
}
//Used to loop through objects and call a function for each object.
//We pass in a tween function to be run
private void TweenFruitGroup(Func<GameObject, float, Tween> TweenFunction, FruitGroup fruitGroup, Sequence s, float duration)
{
for (int i = 0; i < fruitGroup.fruitList.Count; i++)
{
s.Join(TweenFunction.Invoke(fruitGroup.fruitList[i], duration));
}
}
//Override with object sizes
private void TweenFruitGroup(Func<Vector2, GameObject, float, Tween> TweenFunction, FruitGroup fruitGroup, Sequence s, float duration)
{
for (int i = 0; i < fruitGroup.fruitList.Count; i++)
{
s.Join(TweenFunction.Invoke(fruitGroup.InitialFruitSizes[i], fruitGroup.fruitList[i], duration));
}
}
//Reset all fruits after you harvest them
private void ResetFruit(FruitGroup fruitGroup)
{
bool resetY = false;
//If this is the last fruit group (which is the harvesteable group), reset the y pos for that group
if (FruitGroups.IndexOf(fruitGroup) == FruitGroups.Count - 1)
{
resetY = true;
}
for (int i = 0; i < fruitGroup.fruitList.Count; i++)
{
if (resetY)
{
//Reset y pos back to initial
Vector3 pos = Vector3.zero;
pos.x = fruitGroup.fruitList[i].transform.localPosition.x;
pos.y = harvestableFruitInitialY[i];
pos.z = fruitGroup.fruitList[i].transform.localPosition.z;
fruitGroup.fruitList[i].transform.localPosition = pos;
}
//Reset size to zero
fruitGroup.fruitList[i].transform.localScale = Vector3.zero;
//Reset sprite alpha to 1
SpriteRenderer sRenderer = fruitGroup.fruitList[i].GetComponent<SpriteRenderer>();
Color c = sRenderer.color;
c.a = 1;
sRenderer.color = c;
}
}
//Coroutine where we "grow" the fruits by scaling them based on the environment item's wait time
private IEnumerator GrowFruit()
{
//Create new Last fruit group list that will hold the previous list
FruitGroup LastFruitGroup = null;
//Loop through all object lists and call tween logic for each list
//pass in our sequence so we can append/join tweens to our sequence
for (int i = currentFruitGroupIndex; i < FruitGroups.Count; i++)
{
//Create a new sequence. We will use this sequence for all of the object scale tweens
//so we control when the objects tween and what to do when they are all finished tweening.
Sequence growFruitSequence = DOTween.Sequence();
currentFruitGroupIndex = i;
if (LastFruitGroup != null)
{
TweenFruitGroup(TweenHelper.FadeOut, LastFruitGroup, growFruitSequence, 1.3f);
}
float timeCompleted = Convert.ToSingle(waitTime - timeLeftToGrow);
float timeToCompleteOneGroup = Convert.ToSingle(waitTime) / FruitGroups.Count;
//Get min and max time to grow for each fruit group
float min = timeToCompleteOneGroup * i;
float max = min + timeToCompleteOneGroup;
TweenFruitGroup(TweenHelper.Scale, FruitGroups[i], growFruitSequence, max - timeCompleted);
//Set last fruit group
LastFruitGroup = FruitGroups[i];
//If next index is = to the count, we are currently on our last group
if (i + 1 == FruitGroups.Count)
{
//After all of our tweens are finished scaling,
//we set our _canHarvest bool to true which means we are now able to start clicking on the tree to harvest it
growFruitSequence.OnComplete(() =>
{
//Create the outline pulsing in and out effect
//This will loop infinitely until the sequence is killed after the tree has been harvested
CreateOutlineTween();
updateOutlineShader = true;
_canHarvest = true;
});
}
//Wait for all objects in current group to finish tweening
//Once finished we can continue the loop and move onto the next group list
yield return growFruitSequence.WaitForCompletion();
}
}
void CreateOutlineTween()
{
//Create the outline pulsing in and out effect
//This will loop infinitely until the sequence is killed after the tree has been harvested
outlinePulseSequence = DOTween.Sequence();
outlinePulseSequence.SetAutoKill(false);
outlinePulseSequence.Append(DOTween.To(() => outlineAlpha, x => outlineAlpha = x, 1, .6f).SetEase(Ease.InQuad));
outlinePulseSequence.AppendInterval(.12f);
outlinePulseSequence.Append(DOTween.To(() => outlineAlpha, x => outlineAlpha = x, 0, .6f).SetEase(Ease.InQuad));
outlinePulseSequence.AppendInterval(.12f);
outlinePulseSequence.OnComplete(() => outlinePulseSequence.Restart());
}
//ShakePlant is called by the OnTouch event triggered by the DistanceTouchEventTrigger script
//When we are allowed to tap on the item based on our distance from it, this function is called
public void ShakePlant()
{
//The ShakePlant function is run from the OnTouch() event handler in the DistanceTouchEventTrigger script
//Only run shake logic when the tweens aren't currently running
if (_canShake)
{
//Can not shake the tree while fruits are tweening
_canShake = false;
if (_canHarvest)
{
InputManager.Instance.DisableRigidbodyMovement();
_ = QuestionSceneLoader.LoadQuestionScene(DoPostQuestionLogic, QuestionContext.Vegetation);
}
else
{
Sequence shakeSequence = DoShakeTweenSequence();
shakeSequence.OnComplete(() =>
{
//If fruit is not falling to the ground and this is just a transition tween,
//player can shake tree again after tweening is finished
_canShake = true;
});
}
}
}
private Sequence DoShakeTweenSequence()
{
//Add tweens to tween sequence
Sequence shakeSequence = TweenHelper.ShakeSequence(this.gameObject, .8f, new Vector2(.02f, .02f), 3);
LeafParticleSystem.Play();
//Shake tree
//Tween fruit shake for all the current group
TweenFruitGroup(TweenHelper.ShakeRot, FruitGroups[currentFruitGroupIndex], shakeSequence, .8f);
return shakeSequence;
}
void DoPostQuestionLogic(QuestionAnswerInfo callback)
{
DoShakeTweenSequence();
InputManager.Instance.EnableRigidbodyMovement();
//increment the amount of shakes we have completed so far
_harvestShakesCompleted++;
//Check for which TweenFall we should run
CheckFruitFall();
}
//Set Fruit fall logic for harvesting based on if this is our final shake or not
void CheckFruitFall()
{
Sequence fruitFallSequence = DOTween.Sequence();
//if we are not on our final shake,
//tween the fruits without the fall to ground effect
if (_harvestShakesCompleted != HarvestShakesRequired)
{
//Tween fruit small fall for all the current group
TweenFruitGroup(TweenHelper.SmallFall, FruitGroups[currentFruitGroupIndex], fruitFallSequence, .5f);
fruitFallSequence.OnComplete(() =>
{
_canShake = true;
});
}
//if we ARE on our final shake,
//tween the fruits WITH the fall to ground effect
else
{
//Tween fruit fall to ground for all the current group
TweenFruitGroup(TweenHelper.GroundFall, FruitGroups[currentFruitGroupIndex], fruitFallSequence, .4f);
//after fruit falls to the group, show rewards
fruitFallSequence.OnComplete(() =>
{
NavigationUIController.Instance.ShowVegetationRewardsUI(environmentItem_Logic, dbEnvironmentItem, FruitSprite);
RestartTreeHarvesting();
});
}
}
//Restart logic to restart everything after you have finished harvesting
void RestartTreeHarvesting()
{
//Tree has been harvested and apples have fallen to ground
//reset _canHarvest bool and reset values so fruit can start growing again
_canHarvest = false;
_harvestShakesCompleted = 0;
outlinePulseSequence.OnComplete(() => updateOutlineShader = false);
outlinePulseSequence.SetAutoKill(true);
//Fade out apples on ground
Sequence fadeOutSequence = DOTween.Sequence();
TweenFruitGroup(TweenHelper.FadeOut, FruitGroups[currentFruitGroupIndex], fadeOutSequence, 1.3f);
//After apples have faded out, set their positions back to their initial positions,
//set all groups scale back to zero, and set all groups alpha back to 1. This gets them ready for growing again
fadeOutSequence.OnComplete(() =>
{
_canShake = true;
//Loop through all groups
for (int i = 0; i < FruitGroups.Count; i++)
{
//Reset y pos of current group (harvestable group)
//Reset sizes and alpha of all items in all groups
ResetFruit(FruitGroups[i]);
}
environmentItem_Logic.UpdateInteractionTime();
StartFruitGrowing();
});
}
private void Update()
{
if (updateOutlineShader)
{
UpdateOutlineAlpha();
}
}
//Set alpha property of outline in our shader to the alpha we are tweening to create a phasing effect
void UpdateOutlineAlpha()
{
mpb.SetFloat("_OutlineAlpha", outlineAlpha);
spriteRenderer.SetPropertyBlock(mpb);
}
}