public ITrackedStack GetOutput() { ITrackedStack overrideOutput = OverridingMachine.GetOutput(); if (overrideOutput != null) { return(overrideOutput); } else { MassProductionMachineDefinition mpm = ModEntry.GetMPMMachine(OriginalMachineObject.name, OriginalMachineObject.GetMassProducerKey()); if (mpm != null) { // Check for special overriding logic, if any is required string originalClassName = OriginalMachine.GetType().FullName; if (VanillaOverrideList.GetFor(originalClassName) != null) { IVanillaOverride vanillaOverride = VanillaOverrideList.GetFor(originalClassName); overrideOutput = vanillaOverride.Automate_GetOutput(mpm, OriginalMachine, OriginalMachineObject); return(overrideOutput); } else { return(null); } } else { return(OriginalMachine.GetOutput()); } } }
/// <summary>Returns an enumerator that iterates through the collection.</summary> /// <returns>An enumerator that can be used to iterate through the collection.</returns> public IEnumerator <ITrackedStack> GetEnumerator() { foreach (Item item in this.Chest.items.ToArray()) { if (item != null) { ITrackedStack stack = null; try { stack = this.GetTrackedItem(item); } catch (Exception ex) { string error = $"Failed to retrieve item #{item.ParentSheetIndex} ('{item.Name}'"; if (item is SObject obj && obj.preservedParentSheetIndex.Value >= 0) { error += $", preserved item #{obj.preservedParentSheetIndex.Value}"; } error += $") from container '{this.Chest.Name}' at {this.Location.Name} (tile: {this.TileArea.X}, {this.TileArea.Y})."; throw new InvalidOperationException(error, ex); } if (stack != null) { yield return(stack); } } } }
/// <summary>Get an ingredient needed for a recipe.</summary> /// <param name="recipes">The items to match.</param> /// <param name="consumable">The matching consumables.</param> /// <param name="recipe">The matched requisition.</param> /// <returns>Returns whether the requirement is met.</returns> public bool TryGetIngredient(IRecipe[] recipes, out IConsumable consumable, out IRecipe recipe) { IDictionary <IRecipe, StackAccumulator> accumulator = recipes.ToDictionary(req => req, req => new StackAccumulator()); foreach (ITrackedStack input in this.GetItems()) { foreach (var entry in accumulator) { recipe = entry.Key; StackAccumulator stacks = entry.Value; if (recipe.AcceptsInput(input)) { ITrackedStack stack = stacks.Add(input); if (stack.Count >= recipe.InputCount) { consumable = new Consumable(stack, entry.Key.InputCount); return(true); } } } } consumable = null; recipe = null; return(false); }
/// <summary>Provide input to the machine.</summary> /// <param name="input">The available items.</param> /// <returns>Returns whether the machine started processing an item.</returns> public override bool SetInput(IStorage input) { // get next item ITrackedStack tracker = input.GetItems().FirstOrDefault(p => p.Sample is SObject obj && obj.canBeShipped()); if (tracker == null) { return(false); } // ship item SObject item = (SObject)tracker.Take(tracker.Count); var binList = (this.Location as Farm ?? Game1.getFarm()).getShippingBin(Game1.MasterPlayer); Utility.addItemToThisInventoryList(item, binList, listMaxSpace: int.MaxValue); // play animation/sound if (this.Bin != null) { this.Bin.showShipment(item, false); } else if (this.Location is IslandWest islandFarm) { islandFarm.showShipment(item, false); } else if (this.Location is Farm farm) { farm.showShipment(item, false); } return(true); }
/********* ** Private methods *********/ /// <summary>Get whether a given item is a crop compatible with the seed marker.</summary> /// <param name="item">The item to check.</param> private bool IsValidCrop(ITrackedStack item) { return (item.Type == ItemType.Object && item.Sample.ParentSheetIndex != 433 && // seed maker doesn't allow coffee beans SeedMakerMachine.SeedLookup.ContainsKey(item.Sample.ParentSheetIndex)); }
public ITrackedStack GetOutput() { this.PfmMachine.GetOutput(); ITrackedStack trackedStack = this.ClonerMachine.GetOutput(); this.PfmMachine.Reset(_machine.heldObject.Value); return(trackedStack); }
/// <summary> /// Checks if this recipe accepts the given input. /// </summary> /// <returns><c>true</c>, if input is accepted, <c>false</c> otherwise.</returns> /// <param name="stack">Stack.</param> public bool AcceptsInput(ITrackedStack stack) { if (stack.Sample is SObject @object) { return(_underlying.AcceptsInput(@object)); } return(false); }
public void Store(ITrackedStack stack) { if (targetMachineGroup == null) { return; } targetMachineGroup?.StorageManager.TryPush(stack); }
/// <summary>Get whether the underlying items can stack with the items in another stack, based on their respective <see cref="Sample"/> values.</summary> /// <param name="stack">The other stack to check.</param> public bool CanStackWith(ITrackedStack stack) { if (stack?.Sample == null) { return(false); } return(this.Sample == null || this.Sample.canStackWith(stack.Sample)); }
/**** ** TryPush ****/ /// <summary>Add the given item stack to the pipes if there's space.</summary> /// <param name="item">The item stack to push.</param> public bool TryPush(ITrackedStack item) { if (item == null || item.Count <= 0) { return(false); } int originalCount = item.Count; // push into 'output' chests foreach (IContainer container in this.Containers) { if (container.Name.IndexOf("output", StringComparison.InvariantCultureIgnoreCase) < 0) { continue; } container.Store(item); if (item.Count <= 0) { return(true); } } // push into chests that already have this item string itemKey = this.GetItemKey(item.Sample); foreach (IContainer container in this.Containers) { if (container.All(p => this.GetItemKey(p.Sample) != itemKey)) { continue; } container.Store(item); if (item.Count <= 0) { return(true); } } // push into first available chest if (item.Count >= 0) { foreach (IContainer container in this.Containers) { container.Store(item); if (item.Count <= 0) { return(true); } } } return(item.Count < originalCount); }
/**** ** TryPush ****/ /// <summary>Add the given item stack to the pipes if there's space.</summary> /// <param name="item">The item stack to push.</param> public bool TryPush(ITrackedStack item) { if (item == null || item.Count <= 0) { return(false); } int originalCount = item.Count; var preferOutputContainers = this.Containers.Where(p => p.AllowsInput() && p.PreferForOutput()); var otherContainers = this.Containers.Where(p => p.AllowsInput() && !p.PreferForOutput()); // push into 'output' chests foreach (IContainer container in preferOutputContainers) { container.Store(item); if (item.Count <= 0) { return(true); } } // push into chests that already have this item string itemKey = this.GetItemKey(item.Sample); foreach (IContainer container in otherContainers) { if (container.All(p => this.GetItemKey(p.Sample) != itemKey)) { continue; } container.Store(item); if (item.Count <= 0) { return(true); } } // push into first available chest if (item.Count >= 0) { foreach (IContainer container in otherContainers) { container.Store(item); if (item.Count <= 0) { return(true); } } } return(item.Count < originalCount); }
/**** ** TryPush ****/ /// <summary>Add the given item stack to the pipes if there's space.</summary> /// <param name="item">The item stack to push.</param> public bool TryPush(ITrackedStack item) { if (item == null || item.Count <= 0) { return(false); } int originalCount = item.Count; IContainer[] preferredContainers = this.InputContainers.TakeWhile(p => p.StoragePreferred()).ToArray(); IContainer[] otherContainers = this.InputContainers.Skip(preferredContainers.Length).ToArray(); // push into 'output' chests foreach (IContainer container in preferredContainers) { container.Store(item); if (item.Count <= 0) { return(true); } } // push into chests that already have this item string itemKey = this.GetItemKey(item.Sample); foreach (IContainer container in otherContainers) { if (container.All(p => this.GetItemKey(p.Sample) != itemKey)) { continue; } container.Store(item); if (item.Count <= 0) { return(true); } } // push into first available chest if (item.Count >= 0) { foreach (IContainer container in otherContainers) { container.Store(item); if (item.Count <= 0) { return(true); } } } return(item.Count < originalCount); }
/// <summary>Returns an enumerator that iterates through the collection.</summary> /// <returns>An enumerator that can be used to iterate through the collection.</returns> public IEnumerator <ITrackedStack> GetEnumerator() { foreach (Item item in this.GetInventory().ToArray()) { ITrackedStack stack = this.GetTrackedItem(item); if (stack != null) { yield return(stack); } } }
/// <summary>Add a stack to the collection.</summary> /// <param name="stack">The item stack to add.</param> public void Add(ITrackedStack stack) { if (stack?.Sample == null) { throw new InvalidOperationException("Can't track an item with no underlying item."); } this.Stacks.Add(stack); if (this.Sample == null) { this.Sample = stack.Sample; } }
/// <summary>Add a stack to the collection.</summary> /// <param name="stack">The item stack to add.</param> public void Add(ITrackedStack stack) { if (stack == null) { throw new ArgumentNullException(nameof(stack)); } if (stack.Sample == null) { throw new InvalidOperationException("Can't track an item with no underlying item."); } this.Stacks.Add(stack); }
/// <summary>Pull items from the connected pipes.</summary> /// <param name="pipes">The connected IO pipes.</param> /// <returns>Returns whether the machine started processing an item.</returns> public bool Pull(IPipe[] pipes) { ITrackedStack tracker = pipes.GetItems(p => p.Sample is SObject obj && obj.canBeShipped()).Take(1).FirstOrDefault(); if (tracker != null) { SObject item = (SObject)tracker.Take(tracker.Count); this.Farm.shippingBin.Add(item); this.Farm.lastItemShipped = item; this.Farm.showShipment(item, false); return(true); } return(false); }
/// <summary>Provide input to the machine.</summary> /// <param name="input">The available items.</param> /// <returns>Returns whether the machine started processing an item.</returns> public override bool SetInput(IStorage input) { ITrackedStack tracker = input.GetItems().Where(p => p.Sample is SObject obj && obj.canBeShipped()).Take(1).FirstOrDefault(); if (tracker != null) { SObject item = (SObject)tracker.Take(tracker.Count); this.Farm.getShippingBin(Game1.MasterPlayer).Add(item); this.Farm.lastItemShipped = item; this.Farm.showShipment(item, false); return(true); } return(false); }
/// <summary>Add an item to its corresponding stack or add a new stack.</summary> /// <param name="input">The input item to add.</param> /// <returns>Returns the affected stack.</returns> public TrackedItemCollection Add(ITrackedStack input) { TrackedItemCollection?stack = this.FirstOrDefault(p => p.CanStackWith(input)); if (stack != null) { stack.Add(input); } else { base.Add(stack = new TrackedItemCollection(input)); } return(stack); }
/********* ** Private methods *********/ /// <summary>Try to add an item to the input queue, and adjust its stack size accordingly.</summary> /// <param name="item">The item stack to add.</param> /// <returns>Returns whether any items were taken from the stack.</returns> private bool TryAddInput(ITrackedStack item) { // nothing to add if (item.Count <= 0) { return(false); } // clean up input bin this.Input.clearNulls(); // try adding to input int originalSize = item.Count; IList <Item> slots = this.Input.items; int maxStackSize = this.GetMaxInputStackSize(item.Sample); for (int i = 0; i < Chest.capacity; i++) { // done if (item.Count <= 0) { break; } // add to existing slot if (slots.Count > i) { Item slot = slots[i]; if (item.Sample.canStackWith(slot) && slot.Stack < maxStackSize) { var sample = item.Sample.getOne(); sample.Stack = Math.Min(item.Count, maxStackSize - slot.Stack); // the most items we can add to the stack (in theory) int actualAdded = sample.Stack - slot.addToStack(sample); // how many items were actually added to the stack item.Reduce(actualAdded); } continue; } // add to new slot slots.Add(item.Take(Math.Min(item.Count, maxStackSize))); } return(item.Count < originalSize); }
/// <summary>Store an item stack.</summary> /// <param name="stack">The item stack to store.</param> /// <remarks>If the storage can't hold the entire stack, it should reduce the tracked stack accordingly.</remarks> public void Store(ITrackedStack stack) { if (stack.Count <= 0 || this.Chest.SpecialChestType == Chest.SpecialChestTypes.AutoLoader) { return; } IList <Item> inventory = this.GetInventory(); // try stack into existing slot foreach (Item slot in inventory) { if (slot != null && stack.Sample.canStackWith(slot)) { Item sample = stack.Sample.getOne(); sample.Stack = stack.Count; int added = stack.Count - slot.addToStack(sample); stack.Reduce(added); if (stack.Count <= 0) { return; } } } // try add to empty slot int capacity = this.Chest.GetActualCapacity(); for (int i = 0; i < capacity && i < inventory.Count; i++) { if (inventory[i] == null) { inventory[i] = stack.Take(stack.Count); return; } } // try add new slot if (inventory.Count < capacity) { inventory.Add(stack.Take(stack.Count)); } }
/// <summary>Add the given item stack to the pipes if there's space.</summary> /// <param name="pipes">The pipes to fill.</param> /// <param name="item">The item stack to push.</param> public static bool TryPush(this IPipe[] pipes, ITrackedStack item) { if (item == null || item.Count <= 0) { return(false); } int originalCount = item.Count; foreach (IPipe pipe in pipes) { pipe.Store(item); if (item.Count <= 0) { break; } } return(item.Count < originalCount); }
/**** ** TryPush ****/ /// <summary>Add the given item stack to the pipes if there's space.</summary> /// <param name="item">The item stack to push.</param> public bool TryPush(ITrackedStack item) { if (item == null || item.Count <= 0) { return(false); } int originalCount = item.Count; foreach (IContainer container in this.Containers) { container.Store(item); if (item.Count <= 0) { break; } } return(item.Count < originalCount); }
/// <summary>Store an item stack.</summary> /// <param name="stack">The item stack to store.</param> /// <remarks>If the storage can't hold the entire stack, it should reduce the tracked stack accordingly.</remarks> public void Store(ITrackedStack stack) { if (stack.Count <= 0) { return; } IList <Item> inventory = this.Chest.items; // try stack into existing slot foreach (Item slot in inventory) { if (slot != null && stack.Sample.canStackWith(slot)) { int added = stack.Count - slot.addToStack(stack.Count); stack.Reduce(added); if (stack.Count <= 0) { return; } } } // try add to empty slot for (int i = 0; i < Chest.capacity && i < inventory.Count; i++) { if (inventory[i] == null) { inventory[i] = stack.Take(stack.Count); return; } } // try add new slot if (inventory.Count < Chest.capacity) { inventory.Add(stack.Take(stack.Count)); } }
/// <summary>Find items in the pipe matching a predicate.</summary> /// <param name="predicate">Matches items that should be returned.</param> /// <param name="count">The number of items to find.</param> /// <remarks>If there aren't enough items in the pipe, it should return those it has.</remarks> private IEnumerable <ITrackedStack> GetImpl(Func <Item, bool> predicate, int count) { int countFound = 0; foreach (Item item in this.GetInventory()) { if (item != null && predicate(item)) { ITrackedStack stack = this.GetTrackedItem(item); if (stack == null) { continue; } countFound += item.Stack; yield return(stack); if (countFound >= count) { yield break; } } } }
/// <summary>Get whether the recipe can accept a given item as input (regardless of stack size).</summary> /// <param name="stack">The item to check.</param> public bool AcceptsInput(ITrackedStack stack) { return ((this.Type == null || stack.Type == this.Type) && (stack.Sample.ParentSheetIndex == this.InputID || stack.Sample.Category == this.InputID)); }
/// <inheritdoc/> public bool Automate_SetInput(IStorage input, MassProductionMachineDefinition mpm, IMachine originalMachine, SObject originalMachineObject) { if (mpm != null) { try { ITrackedStack crop = null; InputInfo cropInfo = null; int inputQuantity = 0; foreach (ITrackedStack item in input.GetItems()) { if (IsValidCrop(item)) { InputInfo inputInfo = new InputInfo() { ID = item.Sample.ParentSheetIndex, Name = item.Sample.Name, Quality = 0, IsFuel = false, BaseQuantity = 1 }; if (item.Sample is SObject obj) { inputInfo.Quality = obj.Quality; } inputQuantity = mpm.Settings.CalculateInputRequired(inputInfo); if (item.Count >= inputQuantity) { crop = item; cropInfo = inputInfo; break; } } } if (crop != null) { crop.Reduce(inputQuantity); int seedID = SEED_LOOKUP[crop.Sample.ParentSheetIndex]; Random random = new Random((int)Game1.stats.DaysPlayed + (int)Game1.uniqueIDForThisGame / 2 + (int)originalMachineObject.TileLocation.X + (int)originalMachineObject.TileLocation.Y * 77 + Game1.timeOfDay); int outputID = seedID; int outputQuantity = mpm.Settings.CalculateOutputProduced(random.Next(1, 4), cropInfo); if (random.NextDouble() < 0.005) { outputID = 499; outputQuantity = mpm.Settings.CalculateOutputProduced(1, cropInfo); } else if (random.NextDouble() < 0.02) { outputID = 770; outputQuantity = mpm.Settings.CalculateOutputProduced(random.Next(1, 5), cropInfo); } originalMachineObject.heldObject.Value = new SObject(outputID, outputQuantity); originalMachineObject.MinutesUntilReady = mpm.Settings.CalculateTimeRequired(20); return(true); } } catch (Exception e) { ModEntry.Instance.Monitor.Log($"{e}", StardewModdingAPI.LogLevel.Error); } } else { return(originalMachine.SetInput(input)); } return(false); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="consumables">The matching items available to consume.</param> /// <param name="countNeeded">The number of items needed for the recipe.</param> public Consumable(ITrackedStack consumables, int countNeeded) { this.Consumables = consumables; this.CountNeeded = countNeeded; this.IsMet = consumables.Count >= countNeeded; }
/********* ** Public methods *********/ /// <summary>Get whether a given item is a crop compatible with the seed marker.</summary> /// <param name="item">The item to check.</param> public bool IsValidCrop(ITrackedStack item) { return (item.Sample.ParentSheetIndex != 433 && // seed maker doesn't allow coffee beans SeedMakerMachine.CropSeedIDs.ContainsKey(item.Sample.ParentSheetIndex)); }
/// <summary>Get whether the recipe can accept a given item as input (regardless of stack size).</summary> /// <param name="stack">The item to check.</param> public bool AcceptsInput(ITrackedStack stack) { return(stack.Sample.parentSheetIndex == this.InputID || stack.Sample.category == this.InputID); }
/// <inheritdoc /> public void Automate() { IStorage storage = this.StorageManager; double curTime = Game1.currentGameTime.TotalGameTime.TotalMilliseconds; // clear expired timers if (this.MachinePauseExpiries.Count > 0) { foreach (var entry in this.MachinePauseExpiries.ToArray()) { if (curTime >= entry.Value) { this.MachinePauseExpiries.Remove(entry.Key); } } } // get machines ready for input/output IList <IMachine> outputReady = new List <IMachine>(); IList <IMachine> inputReady = new List <IMachine>(); foreach (IMachine machine in this.Machines) { if (this.MachinePauseExpiries.ContainsKey(machine)) { continue; } switch (machine.GetState()) { case MachineState.Done: outputReady.Add(machine); break; case MachineState.Empty: inputReady.Add(machine); break; } } if (!outputReady.Any() && !inputReady.Any()) { return; } // process output foreach (IMachine machine in outputReady) { ITrackedStack output = null; try { output = machine.GetOutput(); if (storage.TryPush(output) && machine.GetState() == MachineState.Empty) { inputReady.Add(machine); } } catch (Exception ex) { string error = $"Failed to automate machine '{machine.MachineTypeID}' at {machine.Location?.Name} (tile: {machine.TileArea.X}, {machine.TileArea.Y}). An error occurred while "; if (output == null) { error += "retrieving its output."; } else { error += $"storing its output item #{output.Sample.ParentSheetIndex} ('{output.Sample.Name}'"; if (output.Sample is SObject outputObj && outputObj.preservedParentSheetIndex.Value >= 0) { error += $", preserved item #{outputObj.preservedParentSheetIndex.Value}"; } error += ")."; } error += $" Machine paused for {this.ErrorPauseMilliseconds / 1000}s."; this.MachinePauseExpiries[machine] = curTime + this.ErrorPauseMilliseconds; throw new InvalidOperationException(error, ex); } } // process input HashSet <string> ignoreMachines = new HashSet <string>(); foreach (IMachine machine in inputReady) { if (ignoreMachines.Contains(machine.MachineTypeID)) { continue; } if (!machine.SetInput(storage)) { ignoreMachines.Add(machine.MachineTypeID); // if the machine can't process available input, no need to ask every instance of its type } } }