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; }