Farming Patches

I was on the idea that this just multiplies the quantities by whatever ‘currentQuantity’ is, and will magically refresh the crafting UI to correspond to that (which apparently does not)

Anyway, so… delete the line, got it. What should I do then? I genuinely can’t figure this one out (and I’m scared of integrating crafting system into the crafting quantity because it’ll result in a circular dependency)

Edit: I erased my previous attempt. I’m trying something different for the time being

OK so I got the Ingredients amount UI to change according to the value that it sees, and honestly speaking, it was a bit of an ingenious attempt as well, but I still haven’t gotten the entire UI to refresh based on that.

Anyway, here’s what I did:

  1. Delete everything I wrote here and here

  2. Start from fresh. Create a new script that only contains an event (so I don’t end up in a circular dependency). Let’s call it ‘FarmingQuantityEvent.cs’, for simplicity’s sake, that only contains an event, and a way to trigger it from outside the script:

using System;

public static class FarmingQuantityEvent 
{
    public static event Action<int> OnQuantityChanged;

    public static void RaiseOnQuantityChanged(int quantity) 
    {
        // Quantity invoked in a function because other scripts can't invoke an event in a different class otherwise
        OnQuantityChanged?.Invoke(quantity);
    }
}
  1. in ‘CraftingQuantity.cs’, you invoke that event:
    public void OnQuantityChanged() 
    {
        // Connected to the Buttons in the hierarchy
        FarmingQuantityEvent.RaiseOnQuantityChanged(currentQuantity);
    }

the ‘OnQuantityChanged’ is called in the inspector, on the plus and minus buttons’ OnClick functions

  1. in ‘CraftingSystem.cs’, you do the subscription and unsubscription to that event, as follows:
        void OnEnable() 
        {
            FarmingQuantityEvent.OnQuantityChanged += RefreshUIWithQuantity;
        }

        void OnDisable() 
        {
            FarmingQuantityEvent.OnQuantityChanged -= RefreshUIWithQuantity;
        }

along with the function that actually contains what happens (and here’s where the real magic happens):

        private void RefreshUIWithQuantity(int quantity) 
        {
            PopulateIngredientsList(recipe.GetIngredients(), quantity);
        }

I introduced a second parameter in ‘PopulateIngredientsList’ that acts as a multiplier to make this work (I’ll get to refreshing everything for this little change soon)

  1. in ‘PopulateIngredientsList’, I made a few small changes, of just making 'ingredientUI accept a value:
        private void PopulateIngredientsList(CraftingItem[] ingredients, int quantity = 1 /* <-- TEST */)
        {
            // Remove all children from the ingredient list container
            CleanupIngredientsList();

            // Go through each ingredient, create it's representation and add it to the list
            foreach (var ingredient in ingredients)
            {
                    var ingredientUI = Instantiate(ingredientPrefab, ingredientsListContainer);
                    ingredientUI.Setup(ingredient, quantity /* <-- Quantity is a test */);
            }
        }

  1. in ‘IngredientsUI.Setup()’, I reflected on those changes:
        // Set up the ingredient
        public void Setup(CraftingItem ingredient, int quantity /* <-- TEST */)
        {
            // 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
            ingredientAmount.text = (ingredient.Amount * quantity /* <-- quantity IS A TEST */).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());
        }

The next step will be to get the entire Crafting UI System to reflect on that, which means making stuff blink if you don’t have enough resources, disabling the crafting/farming button, etc.

I personally have no intentions of making my minimum quantity go below 1, so I won’t deal with the zero cases

  1. Change Step 4 to this (I figured this is much easier a little late):
        private void RefreshUIWithQuantity(int quantity) 
        {
            RefreshDetails(quantity);
        }

Now, it’ll refresh everything, but this also means ‘RefreshDetails()’ will take a parameter reference, and some functions in there will do the same too. Long story short, here’s the new ‘RefreshDetails()’ function:

        private void RefreshDetails(int quantity = 1 /* <-- TEST by Bahaa */)
        {
            // Remove all children from the ingredient list container
            CleanupIngredientsList();

            // Hide the image
            recipeIcon.gameObject.SetActive(false);
            // Hide the name
            recipeName.gameObject.SetActive(false);
            // Hide the description
            recipeDescription.gameObject.SetActive(false);
            // Initially, disable crafting
            InitialiseCrafting();

            // If we have no recipe, we're done
            if (recipe == null)
            {
                return;
            }

            // Get the resulting item for this recipe
            var resultingItem = recipe.GetResult();
            // Set the resulting item icon
            recipeIcon.sprite = resultingItem.Item.GetIcon();
            // Set the resulting item name (and amount)
            recipeName.text = resultingItem.GetRecipeName();
            // Set the resulting item description
            recipeDescription.text = resultingItem.Item.GetDescription();

            // Populate the ingredients list
            PopulateIngredientsList(recipe.GetIngredients(), quantity /* <-- TEST by BAHAA */);

            // Show the recipe icon
            recipeIcon.gameObject.SetActive(true);
            // Show the recipe name (and amount)
            recipeName.gameObject.SetActive(true);
            // Show the recipe description
            recipeDescription.gameObject.SetActive(true);

            // Set the level requirement text
            // recipeLevelRequirement.text = $"Requires Crafting Level {recipe.GetRequiredLevel()}";
            recipeLevelRequirement.text = recipeLevelRequirementText(recipe);
            // If the required level is greater than the player's level, show the required level text
            recipeLevelRequirement.gameObject.SetActive(!PlayerMeetsLevelCriteria(recipe));

            // Show the craft button
            craftButton.gameObject.SetActive(true);
            // Make the craft button interactable _if_ the player can craft this recipe
            craftButton.interactable = CanCraftRecipe(recipe, quantity);
        }

and then ‘CanCraftRecipe()’ will change too:

        private bool CanCraftRecipe(Recipe recipe, int quantity = 1 /* <-- TEST by Bahaa */)
        {
            // Ask the crafting table if we can craft this recipe
            return currentCraftingTable.CanCraftRecipe(recipe, quantity);
        }

and the same goes for ‘CanCraftRecipe()’ in ‘CraftingTable.cs’ (except that this one is from the interface ICraftingTable, which doesn’t support optional parameters, so it will ALWAYS expect a ‘quantity’ parameter:

        // Checks if the player has all the
        // required ingredients of a recipe in their inventory
        bool ICraftingTable.CanCraftRecipe(Recipe recipe, int quantity/* <-- (TEST) FOR FARMING, OR ANYTHING THAT HAS A QUANTITY MEASURE */)
        {
            // If we receive no recipe, return false
            if (recipe == null)
            {
                return false;
            }

            // Get the player inventory
            var playerInventory = Inventory.GetPlayerInventory();
            // If we found no inventory, return false
            if (playerInventory == null)
            {
                return false;
            }

            // Assuming the Players' Inventory and 'SkillStore' are on the same 'Player' gameObject (which they are, by the way), then get his 'SkillStore'
            var skillStore = playerInventory.GetComponent<SkillStore>();
            // If he doesn't have a skillStore (basically for NPCs... idk why this is even here), return false (i.e: Can't craft)
            if (skillStore == null) return false;
            // If the players' Skill level is below the required level, return false:
            // if (skillStore.GetSkillLevel(Skill.Crafting) < recipe.GetRequiredLevel()) return false;
            // 1. For Crafting:
            if (recipe.GetRequiredSkill() == Skill.Crafting && skillStore.GetSkillLevel(Skill.Crafting) < recipe.GetRequiredLevel()) 
            {
                Debug.Log("Cannot Craft this Recipe");
                return false;
            }
            // 2. For Smithing:
            else if (recipe.GetRequiredSkill() == Skill.Smithing && skillStore.GetSkillLevel(Skill.Smithing) < recipe.GetRequiredLevel()) 
            {
                Debug.Log("Cannot Smith this Recipe");
                return false;
            }
            // 3. For Cooking:
            else if (recipe.GetRequiredSkill() == Skill.Cooking && skillStore.GetSkillLevel(Skill.Cooking) < recipe.GetRequiredLevel()) 
            {
                Debug.Log("Cannot Cook this Recipe");
                return false;
            }
            // 4. For Farming:
            else if (recipe.GetRequiredSkill() == Skill.Farming && skillStore.GetSkillLevel(Skill.Farming) < recipe.GetRequiredLevel()) 
            {
                Debug.Log($"Cannot Farm this Recipe");
                return false;
            }

            // Check if the player has enough money to pay for the recipe (if he's broke, he can't craft the recipe)
            var purse = playerInventory.GetComponent<Purse>();
            // If you don't have enough money, you can't craft the item
            if (purse.GetBalance() < recipe.GetRecipeCost()) return false;

            // Go through each ingredient and check the player's inventory for that ingredient
            foreach (var craftingItem in recipe.GetIngredients())
            {
                var hasIngredient = playerInventory.HasItem(craftingItem.Item, out int amountInInventory);
                // If the player does not have the ingredient, return false
                if (!hasIngredient)
                {
                    return false;
                }
                // If the player does not have enough of an ingredient, return false
                if (amountInInventory < craftingItem.Amount * quantity /* <-- TEST by Bahaa */)
                {
                    return false;
                }
            }

            // If we got to here, the player has all the ingredients required.
            // Check if we have matching output
            if (CraftedOutput == null || CraftedOutput.Item == null)
            {
                // We have no output, return true
                return true;
            }

            // Compare the output to the recipe's output
            var recipeOutput = recipe.GetResult();
            if (!object.ReferenceEquals(CraftedOutput.Item, recipeOutput.Item))
            {
                return false;
            }

            // Check if we can stack the items
            if (!CraftedOutput.Item.IsStackable())
            {
                return false;
            }

            return true;
        }

And… my laptop just became insanely slow (it happens from time to time) and it needs a break, so I can’t perform any tests because of that just yet (I know it works though. I tested it on it’s insanely slow condition and all was fine. I just don’t know what the consequences on the rest of the crafting system are just yet)

Once this machine is recovered, I want to check for the cost as well and do other operations before this crazy concept is over (and the consumed quantity, becuase right now it’s a complete mess)

and… somewhere down the line, I broke the crafting functionality by accident… (pressing the button right now does nothing)

Edit: and as it turns out, the ‘Craft’ button no longer works because ‘CraftRecipe’, the function in ‘CraftingSystem.cs’ attached to the button, went missing after I added a parameter to the function. Now I need a way around this (and my laptop is still insanely slow)

Here’s the new problem with the hierarchy assignment of the Crafting Button, after giving ‘CraftRecipe.cs’ an integer parameter:

So I’ll have to assign it through in-code listeners I suppose

Edit number idk: I ended up taking a different approach. Create a new script (I called mine ‘CurrentQuantityHolder.cs’:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CurrentQuantityHolder : MonoBehaviour
{
    private int currentQuantity;

    public int GetCurrentQuantity() 
    {
        return currentQuantity;
    }

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

assign the value from ‘CraftingQuantity.cs’, which both the new and ‘CraftingSystem.cs’ rely on, so no circular dependencies (CraftingSystem → CurrentQuantityHolder → CraftingQuantity), and get the value in ‘CraftingSystem.CraftRecipe()’ itself. Problem solved (but still prototyping)

And of course, don’t forget to assign this function to your button’s ‘OnClick()’ function in the hierarchy, otherwise you won’t get any updates:

    public void SetCurrentQuantityHolder() 
    {
        currentQuantityHolder.SetCurrentQuantity(currentQuantity);
    }

(This is in ‘CraftingQuantity.cs’)

  1. Last, but not least, make sure to classify your ‘CraftingRecipe.cs’ between stuff that has ‘UseQuantityUI’ (I implemented this in the ‘ICraftingTable’ interface), and stuff that doesn’t, as follows:
// in 'CraftingSystem.cs'

        // Bound to the craft button
        public void CraftRecipe()
        {
            // Tell the crafting table to start the crafting process
            if (currentCraftingTable.UseQuantityUI) 
            {
            currentCraftingTable.CraftRecipe(recipe, currentQuantityHolder.GetCurrentQuantity()); // for farming, or stuff with quantity UI
            }
            else 
            {
            currentCraftingTable.CraftRecipe(recipe, 1); // for non-farming, or stuff that doesn't have quantity UI
            }
        }

Now we can safely do the same multiplication for the pricing, and make sure to get the amount of XP based on how many plants have grown. The more, the higher the XP

Quite the ride with this system tbh… This is, by far, one of the most complicated systems I have personally ever dealt with on my own (this part at least, of getting the values to act right), and I am extremely proud of my own growth :smiley:

Now I have a brand new challenge… how do I multiply the gained skill, which is in an individual ‘CraftingNotification.cs’ script (on the Crafting Table itself), with the ‘quantity’ value, which can be gained from ‘CurrentQuantityHolder.cs’, which is exactly where ‘CraftingSystem.cs’ is at…?!

In simple language, how can I get a hold of something on the same gameObject as the ‘CraftingSystem.cs’, from something on the same gameObject as the ‘CraftingTable.cs’ script?

Edit: I figured it out. Here’s what I did, in ‘CraftingNotification.cs’:

    private CurrentQuantityHolder currentQuantityHolder;

    void OnEnable() 
    {
        currentQuantityHolder = FindObjectOfType<CurrentQuantityHolder>(); // Find the 'currentQuantityHolder' when you spawn the farming patch (hence 'OnEnable()'), so we can use its value to get the correct xp
    }

    private IEnumerator CraftingCompleteEstimation(Recipe recipe, float delay) 
    {
        yield return new WaitForSecondsRealtime(delay);

        // FARMING ONLY, as farming is the only skill that will have 'recipe.GetStages' not be equal to zero
        // (Other Crafting-UI Based skills will be covered in 'CraftingSystem.cs')
        if (recipe.GetStages.Length != 0) 
        {
            GameObject.FindWithTag("Player").GetComponent<SkillExperience>().GainExperience(recipe.GetRequiredSkill(), recipe.GetXPReward() * currentQuantityHolder.GetCurrentQuantity());
        }

        MalbersAnimations.InventorySystem.NotificationManager.Instance.OpenNotification($"Crafting Complete:\n{recipe.name}");
    }

and I also added this in ‘CraftingSystem.cs’, in ‘OnEnable()’, otherwise things can go for single quantity units quite wrong:

            currentQuantityHolder.SetCurrentQuantity(1); // without this line, and 'CraftingQuantity' 's minimum quantity being 1, this thing will be a mess (because opening up value is a bit of a mess)

Tomorrow I’ll work on multiplying the price as well (if there is any… just a nice little addon), both the UI and the actual value consumption, and then find a way to lock the quantity UI if it’s one unit, and also lock the quantity UI if it’s in the crafting progress

I didn’t really have much I could add to this conversation… by the time I got to it, you’d already figured out the problem with enabling the current gameObject within OnDisable (or vice versa). BTW, the reason you get that error is because in the old days if you did that, you would simply crash Unity with a stack overflow that God Himself couldn’t track down. Unity caught that you were doing it and spanked you with an error message instead.

It took a little bit of logic to figure it out though, and that’s when I realized it didn’t work, xD (but the solution was nearly impossible for me without @bixarrio 's help. Before getting back here, we had a long Direct Message conversation)

Anyway, anything looking suspicious in my solutions so far? at least this lengthy response? I currently can’t work because the laptop is back to it’s “I’ll be extremely slow for a few days for absolutely no logical reason, and my owner can’t replace me anytime soon anyway, so let’s troll him” phase :stuck_out_tongue: (so now I’m patiently waiting for the scan to end. It’s at 3%)

Then I can move on to the next concept. I’m almost done with these concepts, hopefully in less than 3 months

In the meanwhile I’m just playing GTA San Andreas on my phone for the first time to pass time until it recovers, but these Russian Baguettes in some mission are consistently killing me… :sweat_smile:

Somehow, against my laptop’s insanely low performance numbers, and god knows why, I successfully got the money counter to work as expected (although I’ll eventually turn it off. For the most part, I’m starting to believe this entire pricing thing makes no sense):

  1. in ‘CraftingTable.CraftRecipe()’:
// new 'quantity' multiplication:

            // If the recipe has a Crafting cost, remove the gold from the player
            var purse = playerInventory.GetComponent<Purse>();
            purse.UpdateBalance(-recipe.GetRecipeCost() * quantity);

in ‘CraftingTable.CanCraftRecipe()’, check if it can be crafted or not:

            // Check if the player has enough money to pay for the recipe (if he's broke, he can't craft the recipe)
            var purse = playerInventory.GetComponent<Purse>();
            // If you don't have enough money, you can't craft the item
            if (purse.GetBalance() < recipe.GetRecipeCost() * quantity) return false;
  1. in ‘CraftingSystem.RefreshCost()’:
// again, just integrate a 'quantity' integer:

        private void RefreshCost(int quantity = 1)
        {
            craftingPrice.enabled = recipe.GetRecipeCost() > 0; // only display recipes that have a price

            if (playerPurse.GetBalance() < recipe.GetRecipeCost() * quantity)
            {
                craftingPrice.text = $"<color=red>{FindNearestTargetCostText()} {recipe.GetRecipeCost() * quantity}</color>";
            }
            else craftingPrice.text = FindNearestTargetCostText() + recipe.GetRecipeCost() * quantity;
        }
  1. Make sure ‘RefreshCost()’ is integrated when you refresh the UI when you’re switching the quantity number up:
        private void RefreshUIWithQuantity(int quantity) 
        {
            RefreshDetails(quantity);
            RefreshCost(quantity);
        }

Anyway, I’m off this project until the miracle finally happens and the computer recovers on speed. I just wanted this out of the way so when I return, I return to a new concept

and laptop is somewhat back on track now. I left my performance mode on ultra for so long, it was eventually going to have to take a toll on it’s health (worst part is, I didn’t even know it was on this entire time)

Privacy & Terms