Construction System

there’s a mistake in this code… which you fixed before I can say something :sweat_smile: (I could’ve sworn that CraftingItem cast would’ve failed…)

Something on the side though, because this was a fairly complex Integration (for me), is this function for ‘HasAllRequiredItems()’ correct? Here is his attempt:

public bool HasAllRequiredItems(RPGBuilderBuildingEntity.RequiredItemData[] requiredItems)
        {
            if (Character.Instance == null)
            {
                return false;
            }

            bool hasEnought = true;

            for (int i = 0; i < requiredItems.Length; i++)
            {
                if (!HasRequiredItem(requiredItems[i]))
                {
                    hasEnought = false;
                }
            }

            return hasEnought;
        }

and here is my attempt:

// Test Function (modify if failed):
        public bool HasAllRequiredItems(CraftingItem[] getIngredients)
        {
            bool hasEnough = true;

            for (int i = 0; i < getIngredients.Length; i++) 
            {
                if (!HasRequiredItem(getIngredients[i])) 
                {
                    hasEnough = false;
                }
            }

            return hasEnough;

        }

for god knows what reason, he has 2 functions… I need to spend more time understanding his script (and idk why he needs to check for a Character Instance either…)

Loads of copying and pasting from different places…

It looks fine, but there’s a lot of unnecessary checking happening. You cannot build (I suspect) if you don’t have all the ingredients. So if one ingredient is missing you may as well stop checking because the result will be false, even if all the remaining ingredients are available. There’s no need to keep checking ingredients. The code can be

public bool HasAllRequiredItems(CraftingItem[] getIngredients)
{
    foreach(CraftingItem ingredient in getIngredients)
    {
        if (!HasRequiredItem(ingredient))
        {
            return false;
        }
    }
    return true;
}

Edit
Silly, but here’s a Linq version

public bool HasAllRequiredItems(CraftingItem[] getIngredients)
{
    return getIngredients.All(ingredient => HasRequiredItem(ingredient));
}

That’s EXACTLY what I was thinking of at the start, but then I don’t know why I decided not to do that…

there may be a few more scripts here and there if I run into issues… for now, I don’t know what’s coming up :sweat_smile:

well that’s one surefire way to confuse me really quickly… :sweat_smile:

Anyway, for the moment with my limited resources, until I can edit the GUI Editor, checking seems to be fine

OK so after a little bit of tuning in the ‘BuildingRecipe.cs’, to make the output a ‘BuildingPart.cs’ (Unlike the Crafting, we don’t need an output here… something else is handling that), the test works well… the building component does indeed check your inventory for the required items, and if you have them, it transitions to previewing the part. If not, the mouse click is ignored on that slot

The problem now is, it does not consume the items. I’m guessing a ‘foreach’ loop at the right place, checking for the ingredients should do the trick

and after that, I will have 2 more things to do:

  1. Update the Description to contain what we need to be able to build the part

  2. ONLY Consume if the player actually places the item. Sure, it can check before he can click, but only consume the ingredients IF AND ONLY IF the player places the components (I found the function for that, I just don’t know what to code inside of it… It’s called ‘BuildingManager.PlaceBuildingPart()’)

  3. Update the system to require a skill level, this one should be easy

  4. I was thinking of introducing an Upgrading system, but no idea how…

At this point in time, I’m just keeping record of what I need to do here :sweat_smile:

@bixarrio how do we consume items from the inventory based on the recipe? What changes did you do in the Crafting System to do that? :sweat_smile:

It’s all in the code; go through the recipe and remove the items from the inventory

Well, there’s one small problem… the function that places the stuff down is in the ‘BuildingManager.cs’ script, which… I have no idea (yet) how it’s connected to the UI Circle (I’m sure it is, I just don’t know how…)

So what do we need to eliminate the stuff? :sweat_smile:

Edit: I tried create a ‘ConsumeRecipeResources()’ void function, under ‘BuildingManager.cs’, which is responsible for consuming the resources, as shown below:

        private void ConsumeRecipeResources(CraftingItem[] getIngredients)
        {
            foreach (CraftingItem ingredient in getIngredients) 
            {
                var playerInventory = Inventory.GetPlayerInventory();
                playerInventory.RemoveItem(ingredient.Item, ingredient.Amount);
            }
        }

There are no syntax errors, so… Assuming this is correct, the question now is, how do I get the CraftingItem Ingredients to actually consume that? Like… how do I know which recipe am I scanning through?

If it helps in anyway (this is just my idea), the connected recipe can be found here (I added it when getting the inventory integration to work), under “UICircularBuildingMenu.cs”:

[Serializable]
        public class CircularButtonSettings
        {
            [SerializeField] string m_Name;
            public string Name { get { return m_Name; } set { m_Name = value; } }

            [SerializeField] string m_Description;
            public string Description { get { return m_Description; } set { m_Description = value; } }

            [SerializeField] Texture2D m_Icon;
            public Texture2D Icon { get { return m_Icon; } set { m_Icon = value; } }

            // Test (line 58 and 59 ONLY):
            [SerializeField] BuildingRecipe m_BuildingRecipe;
            public BuildingRecipe BuildingRecipe { get { return m_BuildingRecipe; } set { m_BuildingRecipe = value; } }

            [SerializeField] UnityEvent m_Action;
            public UnityEvent Action { get { return m_Action; } set { m_Action = value; } }

            [SerializeField] BuildingPart m_BuildingPart;
            public BuildingPart BuildingPart { get { return m_BuildingPart; } set { m_BuildingPart = value; } }
        }

My idea is that I want to connect “BuildingRecipe” under that to the input, but since the expected input is a ‘CraftingItem’ and the recipe is of type “BuildingRecipe”, now I dug my own hole… :sweat_smile:

Any idea how to solve this?

After that, I’ll just need a way to update the description of ‘CircularButtonSettings’ with what the ingredients are, and then update it to link to the Skill Store, and I’ll be done with this system (until the real nightmare begins, of designing the game…)

Edit 2: Here’s another attempt, but this one somehow forces the game to not place the item on the ground for some reason… (and naturally, now no resources are consumed either). In ‘BuildingManager.cs’:

// VARIABLE
private CircularButtonSettings circularButtonSettings;

// AWAKE():
circularButtonSettings = GetComponent<CircularButtonSettings>();

// in 'BuildingManager.BuildingPart.PlaceBuildingPart(), at the start of the function, under the null checker':

            // TEST
            if (circularButtonSettings.BuildingRecipe != null) 
            {
                var playerInventory = Inventory.GetPlayerInventory();

                foreach (var ingredient in circularButtonSettings.BuildingRecipe.GetIngredients()) 
                {
                    playerInventory.RemoveItem(ingredient.Item, ingredient.Amount);
                }
            }

the original ‘BuildingManager.PlaceBuildingPart()’ function, the only function I can actually think of, that takes care of placing the built objects in the game world (if it helps in anyway). I have to point out though, I can include code here and it’ll work as expected, but my specific script is causing some issues:

public BuildingPart PlaceBuildingPart(BuildingPart buildingPart, Vector3 position, Vector3 rotation, Vector3 scale, bool createNewGroup = true)
        {
            if (buildingPart == null)
            {
                return null;
            }

            BuildingPart instancedBuildingPart = Instantiate(buildingPart.gameObject,
                position, Quaternion.Euler(rotation)).GetComponent<BuildingPart>();

            instancedBuildingPart.transform.localScale = scale;

            instancedBuildingPart.ChangeState(BuildingPart.StateType.PLACED);

            BuildingArea closestArea = GetClosestBuildingArea(buildingPart);

            if (closestArea != null)
            {
                closestArea.RegisterBuildingPart(instancedBuildingPart);
            }

            if (createNewGroup != false)
            {
                BuildingGroup closestGroup = GetClosestBuildingGroup(instancedBuildingPart);

                if (closestGroup != null)
                {
                    closestGroup.RegisterBuildingPart(instancedBuildingPart);
                }
                else
                {
                    BuildingGroup instancedGroup = CreateBuildingGroup();
                    instancedGroup.RegisterBuildingPart(instancedBuildingPart);
                }
            }

            OnPlacingBuildingPartEvent.Invoke(instancedBuildingPart);

            return instancedBuildingPart;
        }

and this is the ‘BuildingRecipe.cs’ script that I created (which I want to deduct ingredients from the inventory, based on):

// Similar to the 'RPGBuilderBuildingEntity.cs', this
// is my version of my own Building Recipe

using System;
using System.Collections.Generic;
using EasyBuildSystem.Features.Runtime.Buildings.Part;
using RPG.Crafting;
using UnityEngine;

namespace EasyBuildSystem.Features.Runtime.Buildings.Recipe 
{
    [CreateAssetMenu(menuName = "Construction/Recipe", order = 0)]
    public class BuildingRecipe : ScriptableObject, ISerializationCallbackReceiver 
    {
        // Unique ID for recipes
        [SerializeField] string recipeID;
        // Building Ingredients
        [SerializeField] CraftingItem[] ingredients;
        // Resulting Item
        // [SerializeField] BuildingPart resultingItem;
        // Level required
        [SerializeField] int levelRequired;
        // XP Reward
        [SerializeField] int XPReward;

        static Dictionary<string, BuildingRecipe> recipeLookupCache = default;

        public static BuildingRecipe GetFromID(string recipeID) 
        {
            if (recipeLookupCache == null) 
            {
                recipeLookupCache = new Dictionary<string, BuildingRecipe>();
                var recipeList = Resources.LoadAll<BuildingRecipe>("Recipes");

                foreach (var buildingRecipe in recipeList) 
                {
                    if (recipeLookupCache.ContainsKey(buildingRecipe.recipeID)) 
                    {
                        Debug.LogError($"Duplicate found: {recipeLookupCache[buildingRecipe.recipeID]} and {buildingRecipe}");
                        continue;
                    }
                    recipeLookupCache[buildingRecipe.recipeID] = buildingRecipe;
                }
            }

            if (string.IsNullOrWhiteSpace(recipeID) || !recipeLookupCache.ContainsKey(recipeID)) 
            {
                return null;
            }

            return recipeLookupCache[recipeID];

        }

        public string GetRecipeID() 
        {
            return recipeID;
        }

        public CraftingItem[] GetIngredients() 
        {
            return ingredients;
        }

        public float GetRequiredLevel() 
        {
            return levelRequired;
        }

        public int GetXPReward()
        {
            return XPReward;
        }

        public void OnBeforeSerialize() 
        {
            // Empty function, needed for Interface to be quiet
        }

        public void OnAfterDeserialize()
        {
            if (string.IsNullOrWhiteSpace(recipeID))
            {
                recipeID = Guid.NewGuid().ToString();
            }
        }
    }
}

I also have another ‘PlaceBuildingPart()’ in ‘BuildingPlacer.cs’, the script responsible for placing the building in the game (I did not use or try tuning this one, I’m not sure which one to use…):

/// <summary>
        /// Places the Building Part.
        /// </summary>
        /// <returns>True if the Building Part was successfully placed, false otherwise.</returns>
        public virtual bool PlacingBuildingPart()
        {
            if (!HasPreview())
            {
                return false;
            }

            if (!CanPlacing)
            {
                return false;
            }

            BuildingManager.Instance.PlaceBuildingPart(GetSelectedBuildingPart,
                m_CurrentPreview.transform.position,
                m_CurrentPreview.transform.eulerAngles,
                m_CurrentPreview.transform.localScale);

            if (m_LastBuildMode == BuildMode.EDIT)
            {
                ChangeBuildMode(BuildMode.EDIT, true);
            }
            else
            {
                CancelPreview();
            }

            if (m_AudioSettings.AudioSource != null)
            {
                if (m_AudioSettings.PlacingAudioClips.Length != 0)
                {
                    m_AudioSettings.AudioSource.PlayOneShot(m_AudioSettings.PlacingAudioClips[Random.Range(0,
                        m_AudioSettings.PlacingAudioClips.Length)]);
                }
            }

            return true;
        }

What went wrong here…? (Please guys, I promise this annoying topic is almost over)

This won’t work. From what you’ve posted above, CircularButtonSettings is not a component so GetComponent<CircularButtonSettings> will never find it. I don’t even know how the code can run with it like that.

I don’t know anything about the building system asset and these random snippets of code aren’t helping.

Here’s how I think the system is set up;
When you click on whatever (the bits that open the circular menu), that menu is populated with data from the whatever. This is where you need to give it your CircularButtonSettings, which means the whatever needs to know what those settings are. From there, you should be able to have access to it so that you can do the things you need to do to place the building and consume the resources.

any other snippets I can share to get help? I’m a little baffled here… :sweat_smile: (about to start my research into that though, still don’t get how you managed to figure all that out pretty quickly…)

If only Andrew (the EBS developer) would be a bit more helpful and actually respond to the messages…

I’ll look at your code and get back to you

Sure thing man, take your time :slight_smile: (P.S: Anything in ‘UICircularBuildingMenu.cs’ labelled/commented on top of as ‘Test’, or any ‘Test’ you find in the code, is my code)

OK, So here’s the first couple of notes:

  • You need to create building parts for whatever it is you want to build. Just follow the documentation for this.
  • Once you have building parts, you can create a BuildingCondition that can check if the player has the required ingredients and add this condition to the building part. The condition is simple. It would be something like this
public class RequiredIngredientsBuildingCondition : BuildingCondition
{
    [SerializeField] BuildingRecipe buildingRecipe; // the recipe for this building part

    public override bool CheckPlacingCondition()
    {
        // Check if we have the required items in our inventory
        var inventory = Inventory.GetPlayerInventory();

        foreach (var craftingItem in buildingRecipe.GetIngredients())
        {
            if (!inventory.HasItem(craftingItem.Item, out int amount)) return false;
            if (craftingItem.Amount < amount) return false;
        }

        return true;
    }
}

Add this component to the building part. I have done a test on one of the sample building parts


The player will now not be able to place the building if they don’t have the required ingredients.

I’m still looking at the stuff to see where you can remove the ingredients

so… do I delete the tests I did in ‘UICircularBuildingMenu.cs’ script? The way they used to work, was if you didn’t have the requirements, you can’t click the button. It was exactly how I wanted it to be…

I guess. The system has this built-in, you just need to implement the above. No need to go editing the system code. Not for this, at least. We may need to for the circular menu.

Obviously BuildingRecipe is your own code, so that is still required

fair enough I suppose, I trust your judgement :slight_smile:

keeping it :stuck_out_tongue_winking_eye: - I was only speaking of the Circular UI Script

The system has a bunch of conditions that is checked to see if it can place the building, etc., like ‘Can it be placed on water?’ (I don’t know if this is there, but it’s possible). All you need to do is add another condition that will check if the player has the ingredients. From my tests I can see that it shows the red preview if I don’t have the ingredients, but it shows the green one if I do have it (and the other conditions are met)

Placing is also done via a UnityEvent, so it may very well be possible to remove the ingredients without changing the system code. I’m still checking, though

whilst I do agree with your approach, I had other plans in mind

My plan was, after fixing the inventory, I wanted to update the Description in the UI to go red if ingredients are missing, or remain white if all is fulfilled, and block the button click if the ingredients were missing

so it would flow as follows: if you got the ingredients, you can click the button on the circle (I might play with the transparency a bit as well, later…), and only if you finally decide to click on the green highlighted to-be-constructed part, then you do lose the resources and build the part

The time that the checking happens, and the time that the player actually loses their ingredients, sounded a bit more natural for the player that way, which matters to me :stuck_out_tongue_winking_eye:

Yeah, I’m helping you integrate the system. After that, you can do what you want with it.

fair enough, I’ll comment them out and integrate yours as well :slight_smile:

Privacy & Terms