Farming Patches

[JUST SKIP TILL THE END, PLEASE]

I had a problem under this comment, and to an extent, I solved the problem. I’m solving the next one now :smiley:

Anyway, here’s the ‘PlantGrowthHandler.cs’ before I made any changes:

using System;
using System.Collections;
using System.Linq;
using RPG.Crafting;
using UnityEngine;

namespace RPG.Farming
{
    public class PlantGrowthHandler : MonoBehaviour, ICraftingObserver
    {
        private ICraftingEventBus eventBus;
        private float[] slicePoints;

        public GameObject currentPlant;

        void Awake()
        {
            eventBus = GetComponentInParent<ICraftingEventBus>();
            eventBus.RegisterObserver(this);
        }

        void OnDestroy()
        {
            eventBus.DeregisterObserver(this);
        }

        public void OnCraftingStarted(Recipe recipe)
        {
            slicePoints = ComputeRandomGrowthSlices(recipe);
            StartCoroutine(GrowthRoutine(recipe, recipe.GetCraftDuration()));
        }

        public void OnCraftingResumed(Recipe recipe, float remaining)
        {
            slicePoints = ComputeRandomGrowthSlices(recipe);
            StartCoroutine(GrowthRoutine(recipe, remaining));
        }

        private float[] ComputeRandomGrowthSlices(Recipe recipe)
        {
            var stages = recipe.GetStages.Length - 1; // we don't want to wait for the final product (pickup) so we don't add a slice for it
            var duration = recipe.GetCraftDuration();

            var avg = duration / stages;
            var deviations = new float[stages];
            var slices = new float[stages];

            // Seed the randomiser for consistent slices
            var currentState = UnityEngine.Random.state; // cache the current state
            UnityEngine.Random.InitState(transform.position.GetHashCode()); // seed based on the position

            // Generate deviations within 30% of the average length
            for (int i = 0; i < stages; i++) 
            {
                deviations[i] = avg + (UnityEngine.Random.value * 2f - 1f) * avg * 0.3f;
            }

            // Restore the randomiser state
            UnityEngine.Random.state = currentState; // restore to the cached state

            // Normalize deviations to ensure the sum equals duration
            var totalDeviation = deviations.Sum();
            for (int i = 0; i < stages; i++) 
            {
                slices[i] = deviations[i] * duration / totalDeviation;
            }

            // Adjust the slices to ensure the sum is exactly duration
            const float deviation = 0.001f;
            var sumSlices = slices.Sum();
            var difference = duration - sumSlices;

            // Distribute the difference across the slices
            for (int i = 0; i < Mathf.Abs(difference / deviation); i++)
            {
                int index = i % stages;
                if (difference > 0) slices[index] += deviation;
                else slices[index] -= deviation;
            }

            // Accumulate the slices
            var accumulator = slices[0];
            for (var i = 1; i < slices.Length; i++)
            {
                accumulator += slices[i];
                slices[i] = accumulator;
            }

            return slices;
        }

        private IEnumerator GrowthRoutine(Recipe recipe, float duration)
        {
            // Determine where to start the timer
            var totalDuration = recipe.GetCraftDuration();
            var startTime = totalDuration - duration;

            // Determine the current stage, based on the elapsed time (if any)
            var currentStage = DetermineStage(startTime);
            var currentInstance = Instantiate(recipe.GetStages[currentStage], transform.position, Quaternion.identity);

            for (var timer = startTime; timer / totalDuration <= 1f; timer += Time.unscaledDeltaTime /* <-- using 'unscaledDeltaTime' instead of 'deltaTime' because our notification, crafting and all systems work based on real time, so it has to match */)
            {
                var newStage = DetermineStage(timer);
                if (currentStage != newStage)
                {
                    // Update the current stage index
                    currentStage = newStage;
                    // Destroy the old instance
                    Destroy(currentInstance);
                    // Instantiate the new instance
                    currentInstance = Instantiate(recipe.GetStages[currentStage], transform.position, Quaternion.identity);
                }

                yield return null;
            }

            // Destroy the plant
            Destroy(currentInstance);
            // Spawn the pickup (pickup should be the last stage of the growth)
            recipe.GetResult().Item.SpawnPickup(transform.position, 1); // Spawn 1 plant per spot
        }

        private int DetermineStage(float time)
        {
            var stage = Array.BinarySearch(slicePoints, time); // Blazingly-fast solution of determine the growth stage of the Farming Plant
            if (stage < 0)
            {
                stage = ~stage;
            }

            return stage;
        }

        public GameObject GetCurrentPlant() 
        {
            return currentPlant;
        }

        public void SetCurrentPlant(GameObject currentPlant) 
        {
            this.currentPlant = currentPlant;
        }
    }
}

and here is my new ‘PlantGrowthHandler.GrowthRoutine()’ function, after making a few functions:

        private IEnumerator GrowthRoutine(Recipe recipe, float duration)
        {
            // Determine where to start the timer
            var totalDuration = recipe.GetCraftDuration();
            var startTime = totalDuration - duration;

            // Determine the current stage, based on the elapsed time (if any)
            var currentStage = DetermineStage(startTime);
            // var currentInstance = Instantiate(recipe.GetStages[currentStage], transform.position, Quaternion.identity);

            GameObject currentInstance = null;
            if (currentPlant != null) 
            {
                currentInstance = Instantiate(recipe.GetStages[currentStage], transform.position, Quaternion.identity);
            }

            for (var timer = startTime; timer / totalDuration <= 1f; timer += Time.unscaledDeltaTime /* <-- using 'unscaledDeltaTime' instead of 'deltaTime' because our notification, crafting and all systems work based on real time, so it has to match */)
            {
                var newStage = DetermineStage(timer);
                if (currentStage != newStage)
                {
                    // Update the current stage index
                    currentStage = newStage;
                    // Destroy the old instance
                    Destroy(currentInstance);
                    // Instantiate the new instance
                    // currentInstance = Instantiate(recipe.GetStages[currentStage], transform.position, Quaternion.identity);

                    if (currentPlant != null) 
                    {
                        currentInstance = Instantiate(recipe.GetStages[currentStage], transform.position, Quaternion.identity);
                        currentPlant = currentInstance;
                    }
                }

                yield return null;
            }

            // Destroy the plant
            Destroy(currentInstance);
            // Spawn the pickup (pickup should be the last stage of the growth)
            // recipe.GetResult().Item.SpawnPickup(transform.position, 1); // Spawn 1 plant per spot

            if (currentPlant != null) 
            {
                recipe.GetResult().Item.SpawnPickup(transform.position, 1 /* <-- one output per slot */);
                currentPlant = null;
            }
        }

Essentially, these changes make sure that we don’t plant a new plant in an occupied spot

Well… I do have a bit of a small problem, that will probably be troublesome to solve and solve a very minor detail that probably nobody truly cares about, but… let’s say I want to clear off the ‘currentPlant’ variable only after I picked it up, what’s the best way to do that? (I can see myself completely messing the pickup system if I try this one alone)

Again, it’s a nice little touch, but I can absolutely skip this one!


Anyway, I dealt with the Crafting Max Quantity handler in ‘CraftingTable.CraftRecipe()’, and a few details of the Crafting Quantity in ‘PlantGrowthHandler’, and then to get the initial max quantity, I dealt with it in ‘CraftingSystem.OnCraftingInteraction’

So now, we get modifications in the total value when we open the crafting table (CraftingSystem.OnCraftingInteraction()):

// I stronly recommend you read the interface part at the bottom of this comment first

            // Crafting Quantity
            if (craftingQuantity.gameObject.activeSelf) 
            {
                var availableSlots = currentCraftingTable.GetGameObject().GetComponentsInChildren<PlantGrowthHandler>().Where(handler => handler.GetCurrentPlant() == null);
                if (availableSlots.Count() > 1) 
                {
                    craftingQuantity.SetMaxQuantity(availableSlots.Count());
                }
            }

, when we craft something (CraftingTable.CraftRecipe()):

            if (UseQuantityUI) // If you're using the Quantity UI (Farming only for this case)
            {
                var availableSlots = GetComponentsInChildren<PlantGrowthHandler>().Where(handler => handler.GetCurrentPlant() == null);
                Debug.Log($"Total number of available slots is: {availableSlots.Count()}");
                if (availableSlots.Count() > 1) 
                {
                    var firstAvailableSlot = availableSlots.First();
                    firstAvailableSlot.SetCurrentPlant(recipe.GetResult().Item.GetPickup().gameObject);

                    var newAvailableSlots = GetComponentsInChildren<PlantGrowthHandler>().Where(handler => handler.GetCurrentPlant() == null);
                    Debug.Log($"New number of available slots is: {newAvailableSlots.Count()}");
                    craftingQuantity.SetMaxQuantity(newAvailableSlots.Count()); // Decrement, based on how many slots were used
                }
            }

, and then the product is done (PlantGrowthHandler.GrowthRoutine(), in the end when we instantiate a pickup item):

// in 'OnCraftingStarted()' and 'OnCraftingResumed()', we get the 'craftingQuantity' gameObject:
            craftingQuantity = FindObjectOfType<CraftingQuantity>(); // when you start crafting (I can't get it to link to 'NotifyCrafting' without resulting in a circular dependency, so this is my next best option)

// and in the end of 'GrowthRoutine()':

            if (currentPlant != null) 
            {
                recipe.GetResult().Item.SpawnPickup(transform.position, 1 /* <-- one output per slot */);
                currentPlant = null;
                craftingQuantity.SetMaxQuantity(craftingQuantity.GetMaxQuantity() + 1); // Increment the max quantity to indicate that this plant spot is free to use
            } 

I still need to test for saving though, before we can call this system done

I know it’s all over the place, but if it works it works I suppose. I haven’t played around with the system enough, but I think this setup has no circular dependencies. If you got any better ideas, please let me know


and here comes the huge problem @bixarrio - how do I get access to the crafting table’s children from ‘CraftingSystem.cs’? We had a similar problem yesterday, but we solved it in it’s own way. It’s not the same now, for this part:

If I can access the table, I can access it’s kids, and then I can get the right maximum value for that

Edit: I figured it out. @Brian_Trotter taught me a trick to be able to get the gameObject of an interface not too long ago (when we were turning our pickups from an animation-based system to an inventory-based system), where all you gotta do really is include it in your interface, and include it of course in the inheritors of the interface. So here’s what I did:

// get the UnityEngine library for the interface script (in this case, 'ICraftingTable.cs'):
using UnityEngine;

// in 'ICraftingTable':
GameObject GetGameObject();

// in 'CraftingTable.cs', the inheritor of the Interface:
public GameObject GetGameObject() 
{
return this.gameObject;
}

// and now you can use it wherever you like :)

Anyway, all seems to be working fine now

There’s a lot of things in these posts that don’t make sense but if it works for you, I guess it works.

Don’t know why you’d want to do this, it doesn’t make sense. The whole currentPlant thing doesn’t make sense either. The PlantGrowthHandler has a child - which is the current plant. You could just have checked that. Also not sure why the routine is checking if it’s got a plant, the routine should only be running when it’s growing a plant - meaning it has a plant.
If you really want to prevent planting until the item on the ground is picked up (again, doesn’t make sense) you could make the Pickup that was spawned a child of the PlantGrowthHandler (SpawnPickup() returns the Pickup it spawned) and then prevent new plants while there’s a child in the PlantGrowthHandler.

You don’t even have to do that.

GameObject gameObject { get; }

This is all you need. You don’t need to implement it because MonoBehaviour already does that. It just needs to match the one from MonoBehaviour (like this one does).

This one is essential because it tells the Crafting Table (the PlantGrowthHandler is a child of the Farming Crafting Table/Dirt Rows) that this spot is occupied, so you can’t use it to farm anything else here. Essentially, it’s a place holder, until that plant is done

I’ll have a second look at this part

I’ll pass on that one I guess… :sweat_smile:

Can you provide me an example of how this can work, let’s say in ‘CraftingTable.cs’? I was working with what I knew for that moment

For now I still need to find a way to multiply the recipe requirements, and the crafting cost (if any) with the quantity needed, and then find a way to plant that exact number on the ground

It’s not. The PlantGrowthHandler has a child when it’s growing a plant, and it doesn’t when it’s not growing a plant. But now the actual grower (the routine) is also looking at this - like I said, the routine will only be running if it’s busy growing a plant.

That was the sample. Put it in the interface and you’re done.

An interface is a thing that says; this object you have here have these things. You can trust that it has. It is that object’s responsibility to make sure it has those things, and the compiler will make sure that happens. In this case you have to do nothing more than add it to the interface because MonoBehaviour already has a GameObject gameObject { get; } so it is automatically implemented. If CraftingTable was not a MonoBehaviour (or something that derives from MonoBehaviour) you would have had to implement it yourself

before that check, it would plant on literally all positions available on the patch, and that’s not what I want, so it had to be there I guess. Let me re-check (yup, that’s the case, and for some reason, without that check, if the game is paused, the plant disappears midway through)

the “PlantGrowthHandler” IS the child when growing the plant :sweat_smile: - without that variable, the field won’t know it’s occupied

How? Does each position not have a PlantGrowthHandler. That’s what I thought you had because that makes sense. It cannot grow a plant in each position because it makes the plant its child and it cannot be in multiple places at once. Sounds more like you are starting all the PlantGrowthHandlers instead of just one

Of the CraftingTable, and it grows a plant as its own child.

Each patch has a number of children, and each child has a ‘PlantGrowthHandler.cs’ script on it, which individually handles the plant being grown on it, independent of anything but the patch that is parenting it

Hence why the if statement exists, to prevent this from happening. Only the field that has the Plant GameObject setup to it should get the farming started on it. The rest, should not

not really, the plant gets instantiated on the position of that empty gameObject holding the ‘PlantGrowthHandler.cs’ script

Here’s a diagram to illustrate my architecture (CT = Crafting Table, PGH = Plant Growth Handler)

Yes, this is what I thought
image

No, a PlantGrowthHandler knows about it’s position. It knows nothing of other positions. It’s growing a plant or not. There is only it - nothing else. You don’t tell all the PlantGrowthHandlers to start growing a plant if it can, you find one that can and tell it to grow a plant.

Yeah, I see now we did it wrong from the start. I should’ve seen it on Sunday when we built this. I was under the impression we did it right, my bad. It should be

var currentInstance = Instantiate(recipe.GetStages[currentStage], transform);

Now each PlantGrowthHolder has the plant it’s growing as a child. This also clears up a lot of my confusion

PS. ‘We did it wrong’ is not correct. ‘It could’ve been better’ is the right statement

that’s exactly what I did, xD

Anyway, this was my plan the entire time, to give each child position of the patch a ‘PlantGrowthHandler.cs’ script, so it can handle it’s own child independent of anyone else

I might even cancel the crafting process in the player’s face down the line (ONLY FOR FARMING), so he can plant whatever he wants, whenever he wants, if the patches are going to be big and handle multiple children. It makes no sense for you to go hunt for stuff and find your patch occupied down the line, where you have to wait for everything previously assigned to finish up first, before you can try again

For now though, I still need to make sure these buttons work, because the quantity button is not 100% functional just yet

The last step for that is when the crafting starts, it should automatically check if the current value is more than the available slots or not. If so, it should bring the number down to that maximum number, which is the new amount of available slots, all happening only when we hit that “FARM” button. I’m doing these changes in ‘CraftingTable.CraftRecipe()’

And of course, it needs to affect how everything else works based on that number (i.e: quantity needed, price multiplication, how many farm slots are occupied, etc)

Then why did it plant something everywhere? This still sounds like you told every handler to grow a plant

because when I installed 3 spots, instead of just 1 until yesterday, it did not know just yet that it only needs to plant where ‘currentPlant’ is not null. How many areas have a ‘currentPlant != null’ must rely on how many did you order in the ‘quantity’ field

The system should be able to find the non-empty slots using the Linq library functions

Sure, all my comments were just that you don’t need currentPlant because the handler can check its child. Until just now I didn’t realise we never set the child, we just plonked down a plant where we are.

well, we are dealing with a patch that can handle multiple children now, xD - otherwise it’ll be a huge waste of space in my opinion if one patch can only handle one child

Right now, this is the idea my code revolves around

We were always dealing with a patch that can handle multiple children

then I guess we are on the same page now, and hopefully no confusion there :smiley: - let’s continue developing this thing

before I lose track, this is what needs to happen next. I’m making the change over in ‘CraftingTable.CraftRecipe()’ (I know it could’ve probably been done in ‘CraftingSystem.cs’ as well, but I don’t know why I didn’t think of it earlier tbh)

            if (UseQuantityUI) // If you're using the Quantity UI (Farming only for this case)
            {
                var availableSlots = GetComponentsInChildren<PlantGrowthHandler>().Where(handler => handler.GetCurrentPlant() == null);
                Debug.Log($"Total number of available slots is: {availableSlots.Count()}");
                var firstAvailableSlot = availableSlots.First();
                firstAvailableSlot.SetCurrentPlant(recipe.GetResult().Item.GetPickup().gameObject); // if it works, it works :)

                var newAvailableSlots = GetComponentsInChildren<PlantGrowthHandler>().Where(handler => handler.GetCurrentPlant() == null);
                Debug.Log($"New number of available slots is: {newAvailableSlots.Count()}");
                craftingQuantity.SetMaxQuantity(newAvailableSlots.Count()); // Decrement, based on how many slots were used
            }

Edit: Fixed it. I forgot to update the UI:

                if (craftingQuantity.GetCurrentQuantity() > craftingQuantity.GetMaxQuantity()) 
                {
                    Debug.Log($"Current Quantity is {craftingQuantity.GetCurrentQuantity()}, Max Quantity is {craftingQuantity.GetMaxQuantity()}");
                    craftingQuantity.SetCurrentQuantity(craftingQuantity.GetMaxQuantity());
                    craftingQuantity.UpdateQuantityText(); // Don't forget to Update the UI
                }

@bixarrio question: how do I get the required ingredients, in the ‘IngredientUI.cs’ to multiply the number of ingredients, for the farming, with the quantity that we have chosen in ‘CraftingQuantity.GetCurrentQuantity()’, each time I press the button, which is called on the inspector’s ‘OnButtonClick()’ function?

I already managed to get the picked number of plants to get planted on the dirt rows, as follows (the only difference is there’s a new for loop, nothing more):

            if (UseQuantityUI) // If you're using the Quantity UI (Farming only for this case)
            {
                for (int i = 0; i < craftingQuantity.GetCurrentQuantity(); i++)
                {
                    // Plant quantity based on what the 'GetCurrentQuantity()' has available, 
                    // which is controlled in 'CraftingSystem.OnCraftingInteraction()', and the 
                    // value is also updated below, after you hit the 'Craft' button on the UI
                    var availableSlots = GetComponentsInChildren<PlantGrowthHandler>().Where(handler => handler.GetCurrentPlant() == null);
                    var firstAvailableSlot = availableSlots.First();
                    firstAvailableSlot.SetCurrentPlant(recipe.GetResult().Item.GetPickup().gameObject);
                }

                var newAvailableSlots = GetComponentsInChildren<PlantGrowthHandler>().Where(handler => handler.GetCurrentPlant() == null);
                craftingQuantity.SetMaxQuantity(newAvailableSlots.Count()); // Decrement, based on how many slots were used

                if (craftingQuantity.GetCurrentQuantity() > craftingQuantity.GetMaxQuantity())
                {
                    craftingQuantity.SetCurrentQuantity(craftingQuantity.GetMaxQuantity());
                    craftingQuantity.UpdateQuantityText(); // Don't forget to Update the UI
                }
            }

I think rewriting the ‘IngredientUI.Setup()’ would be a step forward, to make it look as follows. Not sure, just a wild guess:

        // Set up the ingredient
        public void Setup(CraftingItem ingredient, int quantity = 0)
        {
            // Keep a reference to this ingredient
            this.ingredient = ingredient;
            // Set the ingredient icon
            ingredientIcon.sprite = ingredient.Item.GetIcon();
            // Set the ingredient name text
            ingredientName.text = ingredient.Item.GetDisplayName();
            // Set the ingredient amount text
            if (quantity != 0)
            {
                ingredientAmount.text = (ingredient.Amount * quantity).ToString();
            }
            else ingredientAmount.text = ingredient.Amount.ToString();
            // Stop any coroutines
            StopAllCoroutines();
            // If the player has the ingredient, we are done
            if (PlayerHasIngredient(ingredient))
            {
                return;
            }
            // If the player does not have the ingredient, nor does he have enough money 
            // (handled in 'CraftingSystem.PopulateIngredientsList()'),
            // start a coroutine to flash the ingredient amount
            StartCoroutine(FlashAmountRoutine());
        }

(there’s a new ‘quantity’ parameter, which modifies how the ‘text’ of the recipe is updated)

And the same will need to happen in ‘RefreshCost()’ as well, if these recipes will be costly (they won’t, but it’s a backup system). As simple as this one sounds, as complicated as it actually is on the back-end

OK so I came with a bit of an ingenious solution to this one. It’s not working fully yet, but I managed to get a hold of the amount of ingredients of the recipes:

  1. Create a new ‘RecipeManager.cs’ script. This will avoid circular dependencies:
using RPG.Crafting;
using UnityEngine;

public class RecipeManager : MonoBehaviour
{
    [Header("Automatically Updated Variable:")]
    [SerializeField] Recipe currentRecipe;

    public Recipe GetCurrentRecipe() 
    {
        return currentRecipe;
    }

    public void SetCurrentRecipe(Recipe currentRecipe) 
    {
        this.currentRecipe = currentRecipe;
    }
}

And place it in the inspector, and connect it to ‘CraftingSystem.cs’ below, after following step 2

Drag that on to the same gameObject holder of ‘CraftingSystem.cs’, and create a reference for it in ‘CraftingSystem.cs’

  1. In ‘CraftingSystem.cs’, find ‘RecipeSelected.cs’:
        private void RecipeSelected(Recipe recipe)
        {
            // Keep a reference to the selected recipe
            this.recipe = recipe;
            // Show/Hide the details
            // recipeDetailsPanel.SetActive(PlayerMeetsLevelCriteria(recipe));
            // Reset
            RefreshDetails();
            // Reset the Cost
            RefreshCost(); 
            // Reset the XP Reward, per recipe
            RefreshXPReward();
            // Make this game object active
            gameObject.SetActive(true);
            // Crafting Duration
            TimeSpan craftingTime = TimeSpan.FromSeconds(recipe.GetCraftDuration());
            craftingTimeText.text = HumanReadableTime(craftingTime);
            // Setup the Recipe Manager
            recipeManager.SetCurrentRecipe(recipe);
        }

(oh, and in ‘CraftingSystem.CloseCrafting()’, set the ‘recipeManager.SetCurrentRecipe(null)’)

And set the current recipe in there. Now when you go to the inspector, you can see it change when you change recipes, so that’s a huge step forward

  1. in ‘craftingQuantity.cs’, create a new ‘OnQuantityChanged()’ function, which will be connected to ‘OnClick()’ in the hierarchy:
    public void OnQuantityChanged() 
    {
        Recipe currentRecipe = recipeManager.GetCurrentRecipe();
        if (currentRecipe != null) 
        {
            var ingredients = currentRecipe.GetIngredients();
            foreach (var ingredient in ingredients) 
            {
                // Calculate the total amount needed, based on the current quantity
                int totalAmount = ingredient.Amount * currentQuantity;
                ingredient.Amount = totalAmount;
            }
            UpdateIngredientUI();
        }
    }

Over there, you can change the amount of ingredients based on the ‘currentQuantity’ of ‘CraftingQuantity.cs’

As we speak right now, it’s still mathematically inaccurate, and the UI does not update yet, so I’m fixing these issues, but at least I got a hold of the quantity of the ingredients, which was a major challenge for me

Alright so I came up with a mathematical formula to accurately change the amount:

using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using RPG.Crafting;

public class CraftingQuantity : MonoBehaviour
{
    public int maxQuantity;
    public int minQuantity = 1;
    private int currentQuantity;
    private int previousQuantity;

    public TextMeshProUGUI quantityText;
    public Button plusButton;
    public Button minusButton;

    private Image plusButtonImage;
    private Image minusButtonImage;

    private RecipeManager recipeManager;
    [SerializeField] TextMeshProUGUI ingredientAmount;

    void Awake() 
    {
        recipeManager = GetComponentInParent<RecipeManager>();
    }

    void OnEnable() 
    {
        // Initialize the Quantity to the minimum value
        currentQuantity = minQuantity;
        UpdateQuantityText();

        // Add listeners to the buttons (Done on the button in the inspector itself, to avoid a weird incrementation bug)

        // Buttons' Image Components
        plusButtonImage = plusButton.GetComponent<Image>();
        minusButtonImage = minusButton.GetComponent<Image>();

        // Update button transparency
        UpdateButtonState();
    }

    void OnDisable() 
    {
        // Remove listeners from the buttons (Done on the button in the inspector itself, to avoid a weird incrementation bug)
    }

    public void OnQuantityChanged() 
    {
        // Connected to the Buttons in the hierarchy
        if (previousQuantity == 0) 
        {
            previousQuantity = 1; // Avoid Division by Zero
        }

        Recipe currentRecipe = recipeManager.GetCurrentRecipe();
        if (currentRecipe != null) 
        {
            var ingredients = currentRecipe.GetIngredients();
            foreach (var ingredient in ingredients) 
            {
                int totalAmount = (ingredient.Amount / previousQuantity /* <-- bring the value to its original form first */) * currentQuantity;
                ingredient.Amount = totalAmount;
                ingredientAmount.text = ingredient.Amount.ToString();
            }
        }
        previousQuantity = currentQuantity;
    }

    public void AddQuantity() 
    {
        if (currentQuantity < maxQuantity) 
        {
            currentQuantity++;
            UpdateQuantityText();
            UpdateButtonState();
        }
    }

    public void SubtractQuantity() 
    {
        if (currentQuantity > minQuantity) 
        {
            currentQuantity--;
            UpdateQuantityText();
            UpdateButtonState();
        }
    }

    public void UpdateQuantityText() 
    {
        quantityText.text = currentQuantity.ToString();
    }

    void UpdateButtonState() 
    {
        // Update the Plus Button state
        if (currentQuantity >= maxQuantity) 
        {
            plusButton.interactable = false;
            Color plusColor = plusButtonImage.color;
            plusColor.a = 0.5f;
            plusButtonImage.color = plusColor;
        }
        else 
        {
            plusButton.interactable = true;
            var plusColor = plusButtonImage.color;
            plusColor.a = 1f;
            plusButtonImage.color = plusColor;
        }

        // Update the Minus Button state
        if (currentQuantity <= minQuantity) 
        {
            minusButton.interactable = false;
            var minusColor = minusButtonImage.color;
            minusColor.a = 0.5f;
            minusButtonImage.color = minusColor;
        }
        else 
        {
            minusButton.interactable = true;
            var minusColor = minusButtonImage.color;
            minusColor.a = 1f;
            minusButtonImage.color = minusColor;
        }
    }

    public int GetMaxQuantity() 
    {
        return maxQuantity;
    }

    public void SetMaxQuantity(int maxQuantity) 
    {
        this.maxQuantity = maxQuantity;
    }

    public int GetCurrentQuantity() 
    {
        return currentQuantity;
    }

    public void SetCurrentQuantity(int currentQuantity) 
    {
        this.currentQuantity = currentQuantity;
    }
}

But now I have two major challenges @bixarrio :

  1. The ingredient amount text mesh pro won’t update for some reason, although I assigned it in the hierarchy

  2. How do I refresh the entire Crafting UI for that recipe if the amount is surpassed, the same way we do when we choose other recipes and return to that one for example? (Otherwise you can craft something when you don’t even have the needed quantity). I didn’t think of this one before…

You’re changing the recipe. Don’t do that. I believe anything else that looks at this recipe now (like another farming patch) will have wrong ingredient amounts.

Privacy & Terms