Ingredient Bugs

How weird things happen in the game

Mega Burger

Some people found this funny bug where you can make a mega-burger:

Example video: https://www.bilibili.com/video/BV1ZX4y157Ra/

Let’s see what happened.

ServerPreparationContainer is the primary component of a burger bun; here’s the code that is invoked when trying to place something onto it:

public bool CanHandlePlacement(ICarrier _carrier, Vector2 _directionXZ, PlacementContext _context)
{
    // Get the thing the chef is holding (a plate of ingredients, in this case).
    GameObject gameObject = _carrier.InspectCarriedItem();
    // Gets the definition of the plate's contents. In this case it would be 3 separate items
    // (cooked meat, cooked meat, pineapple). See below.
    AssembledDefinitionNode[] orderDefinitionOfCarriedItem = this.GetOrderDefinitionOfCarriedItem(gameObject);
    // This is the plate's ServerIngredientContainer component.
    ServerIngredientContainer component = gameObject.GetComponent<ServerIngredientContainer>();
    // It indeed is a plate.
    Plate component2 = gameObject.GetComponent<Plate>();
    // See below. Passes if individual ingredients are each eligible, and also container is not
    // overfilled in capacity. 
    if (!this.CanAddOrderContents(orderDefinitionOfCarriedItem))
    {
        // Tray is the rectangular tray, not the case here.
        Tray component3 = gameObject.GetComponent<Tray>();
        if (component3 != null)
        {
            return true;
        }
        // If the check above fails, and the thing is not a plate, or its ingredients can't
        // individually be inserted, then fail.
        if (!(component2 != null) || !(component != null) || !this.CanTransferToContainer(component))
        {
            return false;
        }
    }
    // Finally check if the thing we're inserting is a plate (true), or the two plating steps are the same (plating steps are like,
    // cup vs plate vs tray).
    return !(component2 != null) || !(this.m_preparationContainer.m_ingredientOrderNode.m_platingStep != component2.m_platingStep);

    // To summarize, if the thing we're transferring from is a plate, we succeed if either:
    //  - The plates' contents are individually eligible for the target, and the target wouldn't be overfilled; OR
    //  - the plate's ServerIngredientContainer's individual elements are transferrable to the target (but without checking
    //    target's capacity!)
}
// ServerPreparationContainer::GetOrderDefinitionOfCarriedItem
private AssembledDefinitionNode[] GetOrderDefinitionOfCarriedItem(GameObject _carriedItem)
{
    IBaseCookable cookingHandler = null;
    // A plate is not cookable, so doesn't apply here.
    ServerCookableContainer component = _carriedItem.GetComponent<ServerCookableContainer>();
    if (component != null)
    {
        cookingHandler = component.GetCookingHandler();
    }
    return this.m_preparationContainer.GetOrderDefinitionOfCarriedItem(_carriedItem, this.m_itemContainer, cookingHandler);
}

// PreparationContainer::GetOrderDefinitionOfCarriedItem
public AssembledDefinitionNode[] GetOrderDefinitionOfCarriedItem(GameObject _carriedItem, IIngredientContents _itemContainer, IBaseCookable _cookingHandler)
{
    AssembledDefinitionNode[] result = null;
    if (_carriedItem.GetComponent<CookableContainer>() != null)
    {
        ClientIngredientContainer component = _carriedItem.GetComponent<ClientIngredientContainer>();
        IContainerTransferBehaviour containerTransferBehaviour = _carriedItem.RequireInterface<IContainerTransferBehaviour>();
        if (component.HasContents() && containerTransferBehaviour.CanTransferToContainer(_itemContainer))
        {
            CookableContainer component2 = _carriedItem.GetComponent<CookableContainer>();
            ClientMixableContainer component3 = _carriedItem.GetComponent<ClientMixableContainer>();
            AssembledDefinitionNode cookableMixableContents = null;
            bool isMixed = false;
            if (component3 != null)
            {
                cookableMixableContents = component3.GetOrderComposition();
                isMixed = component3.GetMixingHandler().IsMixed();
            }
            CookedCompositeAssembledNode cookedCompositeAssembledNode = component2.GetOrderComposition(_itemContainer, _cookingHandler, cookableMixableContents, isMixed) as CookedCompositeAssembledNode;
            cookedCompositeAssembledNode.m_composition = new AssembledDefinitionNode[]
            {
                component.GetContentsElement(0)
            };
            result = new AssembledDefinitionNode[]
            {
                cookedCompositeAssembledNode
            };
        }
    }
    else if (_carriedItem.GetComponent<IngredientPropertiesComponent>() != null)
    {
        IngredientPropertiesComponent component4 = _carriedItem.GetComponent<IngredientPropertiesComponent>();
        result = new AssembledDefinitionNode[]
        {
            component4.GetOrderComposition()
        };
    }
    // This case applies.
    else if (_carriedItem.GetComponent<Plate>() != null)
    {
        ClientIngredientContainer component5 = _carriedItem.GetComponent<ClientIngredientContainer>();
        result = component5.GetContents();
    }
    return result;
}

// ClientIngredientContainer::GetContents. Why Client and not Server? I have no idea.
public AssembledDefinitionNode[] GetContents()
{
    return this.m_contents.ToArray();
}
// ServerPreparationContainer::CanAddOrderContents - can we add these things into the bun?
public bool CanAddOrderContents(AssembledDefinitionNode[] _contents)
{
    if (_contents != null)
    {
        foreach (AssembledDefinitionNode toAdd in _contents)
        {
            // Check each individual ingredient.
            if (!this.CanAddIngredient(toAdd))
            {
                return false;
            }
        }
        // Then check the whole container.
        // This does nothing more than checking the capacity.
        return this.m_itemContainer.CanTakeContents(_contents);
    }
    return false;
}

// ServerPreparationContainer::CanAddIngredient
protected virtual bool CanAddIngredient(AssembledDefinitionNode _toAdd)
{
    CompositeAssembledNode asOrderComposite = this.GetAsOrderComposite();
    return asOrderComposite.CanAddOrderNode(_toAdd, true);
}

// CompositeAssembledNode::CanAddOrderNode
// Basically checks that each ingredient is allowable for this container,
// and that we don't already have too many (e.g. can't insert two lettuces).
public bool CanAddOrderNode(AssembledDefinitionNode _toAdd, bool _raw = false)
{
    if (!_raw && this.m_freeObject != null && this.m_freeObject.RequestInterface<IHandleOrderModification>() != null)
    {
        IHandleOrderModification handleOrderModification = this.m_freeObject.RequestInterface<IHandleOrderModification>();
        return handleOrderModification.CanAddOrderContents(new AssembledDefinitionNode[]
        {
            _toAdd
        });
    }
    int num = 0;
    foreach (AssembledDefinitionNode node in this.m_composition)
    {
        if (AssembledDefinitionNode.Matching(node, _toAdd))
        {
            num++;
        }
    }
    Predicate<OrderContentRestriction> match = (OrderContentRestriction _specifier) => AssembledDefinitionNode.Matching(_specifier.m_content, _toAdd);
    OrderContentRestriction orderContentRestriction = this.m_permittedEntries.Find(match);
    return orderContentRestriction != null && num < orderContentRestriction.m_amountAllowed && !this.CompositionContainsRestrictedNode(_toAdd, orderContentRestriction);
}

Also:

// ServerPreparationContainer::CanTransferToContainer. It basically checks every component of
// the container's contents.
public virtual bool CanTransferToContainer(IIngredientContents _container)
{
    if (_container.HasContents())
    {
        for (int i = 0; i < _container.GetContentsCount(); i++)
        {
            AssembledDefinitionNode contentsElement = _container.GetContentsElement(i);
            if (!this.CanAddIngredient(contentsElement))
            {
                return false;
            }
        }
    }
    return true;
}