private static void ServerCancelCraftingQueueItem( ICharacter character, CraftingQueueItem item, CraftingQueue craftingQueue, ref IItemsContainer groundContainer) { if (!item.Recipe.IsCancellable) { Logger.Info("Crafting queue item is not cancellable: " + item, character); return; } var index = craftingQueue.QueueItems.IndexOf(item); if (index < 0) { throw new Exception("Doesn't have the crafting queue item: " + item); } ServerReturnCharacterCraftingQueueItemInputItems(character, item, ref groundContainer); craftingQueue.QueueItems.RemoveAt(index); if (index == 0) { // first queue item was removed - reset crafting duration craftingQueue.SetDurationFromCurrentRecipe(); } Logger.Info("Crafting queue item successfully cancelled: " + item, character); }
private static void ServerDestroyInputItems( CraftingQueue craftingQueue, Recipe recipe, ushort countToCraft, bool isAdminMode) { switch (recipe.RecipeType) { case RecipeType.Manufacturing: // auto-manufacturers: do not destroy input items until crafting completed break; case RecipeType.ManufacturingByproduct: // station by-products doesn't require input items - it checks only for proper item prototype upon start break; default: // other recipes should instantly destroy the input items ServerDestroyInputItems(recipe, new AggregatedItemsContainers(craftingQueue.InputContainersArray), countToCraft, isAdminMode); break; } }
public override bool CanBeCrafted( IWorldObject characterOrStationObject, CraftingQueue craftingQueue, ushort countToCraft) { throw new Exception("Incorrect method for RecipeForStationByproduct."); }
private static void OnFuelChanged( IStaticWorldObject objectManufacturer, FuelBurningState state, CraftingQueue byproductsCraftQueue, ManufacturingConfig config) { if (!config.IsProduceByproducts) { return; } var currentByproductRecipe = byproductsCraftQueue.QueueItems.FirstOrDefault()?.Recipe as Recipe.RecipeForManufacturingByproduct; var newByproductRecipe = config.MatchRecipeForByproduct(state.CurrentFuelItemType); if (currentByproductRecipe == newByproductRecipe) { return; } byproductsCraftQueue.Clear(); if (newByproductRecipe != null) { CraftingMechanics.ServerStartCrafting( objectManufacturer, null, byproductsCraftQueue, newByproductRecipe, // unlimited count countToCraft: ushort.MaxValue); } }
public Recipe MatchRecipe(IStaticWorldObject objectManufacturer, CraftingQueue craftingQueue) { Recipe bestRecipe = null; foreach (var recipe in this.Recipes) { if (bestRecipe is not null // TODO: compare by "value" of recipes? && recipe.InputItems.Length < bestRecipe.InputItems.Length) { // no need to check this recipe - already has a good recipe with the similar amount of items continue; } if (recipe.CanBeCrafted(null, objectManufacturer, craftingQueue, countToCraft: 1)) { bestRecipe = recipe; } } return(bestRecipe); }
/// <summary> /// Consumes fuel and populate remaining fuel burning time when needed. /// </summary> private static void RefreshFuel( FuelBurningState state, CraftingQueue byproductsCraftQueue, ManufacturingConfig config, IStaticWorldObject objectManufacturer, bool isNeedFuelNow) { if (state.FuelUseTimeRemainsSeconds > 0 || !isNeedFuelNow || state.ContainerFuel.OccupiedSlotsCount == 0) { return; } // look for the fuel item with the lowest fuel value IItem fuelItem = null; var lowestFuelItemValue = double.MaxValue; foreach (var item in state.ContainerFuel.Items) { if (item.ProtoItem is IProtoItemFuelSolid fuelItemType) { var itemFuelAmount = fuelItemType.FuelAmount; if (itemFuelAmount < lowestFuelItemValue) { fuelItem = item; lowestFuelItemValue = itemFuelAmount; } } } var bestProtoFuel = fuelItem?.ProtoGameObject as IProtoItemFuelSolid; if (bestProtoFuel is null) { // no fuel placed in fuel container //if (state.CurrentFuelItemType is not null) //{ // state.CurrentFuelItemType = null; // Logger.Info($"Fuel depleted for manufacturing at {objectManufacturer}"); //} return; } Logger.Info($"Fuel will be used for manufacturing {bestProtoFuel.ShortId} at {objectManufacturer}"); // destroy one fuel item Api.Server.Items.SetCount(fuelItem, fuelItem.Count - 1); // set fuel burn time state.CurrentFuelItemType = bestProtoFuel; var secondsToBurn = bestProtoFuel.FuelAmount; state.FuelUseTimeRemainsSeconds = secondsToBurn; OnFuelChanged(objectManufacturer, state, byproductsCraftQueue, config); }
/// <summary> /// Updates fuel burning state. /// </summary> /// <param name="objectManufacturer">Instance of world object performing manufacturing.</param> /// <param name="state">Instance of fuel burning state.</param> /// <param name="byproductsCraftQueue"></param> /// <param name="config">Manufacturing config.</param> /// <param name="deltaTime">Delta time to progress on.</param> /// <param name="isNeedFuelNow">The new fuel item will be not burned if the fuel is not needed now.</param> public static void Update( IStaticWorldObject objectManufacturer, FuelBurningState state, CraftingQueue byproductsCraftQueue, ManufacturingConfig config, double deltaTime, double byproductsQueueRate, bool isNeedFuelNow, bool forceRefreshFuel = false) { if (isNeedFuelNow && (state.ContainerFuel.StateHash != state.ContainerFuelLastStateHash || forceRefreshFuel)) { RefreshFuel(state, byproductsCraftQueue, config, objectManufacturer, isNeedFuelNow); state.ContainerFuelLastStateHash = state.ContainerFuel.StateHash; } var fuelUseTimeRemainsSeconds = state.FuelUseTimeRemainsSeconds + state.FuelUseTimeAccumulatedRemainder - deltaTime; if (fuelUseTimeRemainsSeconds <= 0) { // fuel is burned state.FuelUseTimeAccumulatedRemainder += state.FuelUseTimeRemainsSeconds; state.FuelUseTimeRemainsSeconds = 0; if (isNeedFuelNow) { // refresh fuel RefreshFuel(state, byproductsCraftQueue, config, objectManufacturer, isNeedFuelNow); return; } return; } // subtract fuel burn time state.FuelUseTimeAccumulatedRemainder = 0; state.FuelUseTimeRemainsSeconds = fuelUseTimeRemainsSeconds; if (config.IsProduceByproducts) { if (byproductsCraftQueue is null) { throw new Exception("No byproductsCraftQueue"); } CraftingMechanics.ServerUpdate(byproductsCraftQueue, deltaTime * byproductsQueueRate); } }
/// <summary> /// Consumes fuel and populate remaining fuel burning time when needed. /// </summary> private static void RefreshFuel( FuelBurningState state, CraftingQueue byproductsCraftQueue, ManufacturingConfig config, IStaticWorldObject objectManufacturer, bool isNeedFuelNow) { if (state.FuelUseTimeRemainsSeconds > 0 || !isNeedFuelNow) { return; } // look for best fuel item from placed into the fuel container var bestFuelItem = state.ContainerFuel.Items.OrderBy( i => { var fuelItemType = i.ProtoItem as IProtoItemFuelSolid; return(fuelItemType?.FuelAmount ?? double.MaxValue); }) .FirstOrDefault(); var bestFuelItemType = bestFuelItem?.ProtoItem as IProtoItemFuelSolid; if (bestFuelItemType == null) { // no fuel placed in fuel container //if (state.CurrentFuelItemType != null) //{ // state.CurrentFuelItemType = null; // Logger.Info($"Fuel depleted for manufacturing at {objectManufacturer}"); //} return; } Logger.Info($"Fuel will be used for manufacturing {bestFuelItemType} at {objectManufacturer}"); // destroy one fuel item Api.Server.Items.SetCount(bestFuelItem, bestFuelItem.Count - 1); // set fuel burn time state.CurrentFuelItemType = bestFuelItemType; var secondsToBurn = bestFuelItemType.FuelAmount; state.FuelUseTimeRemainsSeconds = secondsToBurn; OnFuelChanged(objectManufacturer, state, byproductsCraftQueue, config); }
public virtual bool CanBeCrafted( IWorldObject characterOrStationObject, CraftingQueue craftingQueue, ushort countToCraft) { if (characterOrStationObject is ICharacter character) { if (!this.SharedIsTechUnlocked(character)) { // locked recipe return(false); } } foreach (var craftingInputItem in this.InputItems) { var requiredCount = countToCraft * (uint)craftingInputItem.Count; if (requiredCount > ushort.MaxValue) { throw new Exception( $"Too many items set to craft. {craftingInputItem}x{countToCraft}={requiredCount}"); } foreach (var inputItemsContainer in craftingQueue.InputContainersArray) { if (inputItemsContainer.ContainsItemsOfType( craftingInputItem.ProtoItem, requiredCount, out var containerAvailableCount)) { // item is available in required amount requiredCount = 0; break; } // item is available in none or some amount requiredCount -= containerAvailableCount; } if (requiredCount > 0) { // item is not available in required amount after checking all the containers return(false); } } // everything is available return(true); }
/// <summary> /// Update crafting queue. It will progress current crafting queue item. /// </summary> /// <param name="craftingQueue">Crafting queue item.</param> /// <param name="deltaTime">Delta time in seconds to progress on.</param> public static void ServerUpdate(CraftingQueue craftingQueue, double deltaTime) { do { var queue = craftingQueue.QueueItems; if (queue.Count == 0) { // the queue is empty return; } var queueItem = queue[0]; ServerProgressQueueItem(craftingQueue, queueItem, ref deltaTime); }while (deltaTime > 0 && !craftingQueue.IsContainerOutputFull); }
public override bool CanBeCrafted( IWorldObject characterOrStationObject, CraftingQueue craftingQueue, ushort countToCraft) { if (characterOrStationObject == null) { // require character or station return(false); } if (this.RecipeType != RecipeType.Hand && !this.StationTypes.Contains(characterOrStationObject.ProtoWorldObject)) { // requires station return(false); } return(base.CanBeCrafted(characterOrStationObject, craftingQueue, countToCraft)); }
private static bool ServerTryCreateOutputItems(CraftingQueue craftingQueue, CraftingQueueItem queueItem) { if (craftingQueue.IsContainerOutputFull && craftingQueue.ContainerOutputLastStateHash == craftingQueue.ContainerOutput.StateHash) { return(false); } var recipe = queueItem.Recipe; var result = recipe.OutputItems.TrySpawnToContainer(craftingQueue.ContainerOutput); if (!result.IsEverythingCreated) { // cannot create items at specified container result.Rollback(); craftingQueue.IsContainerOutputFull = true; craftingQueue.ContainerOutputLastStateHash = craftingQueue.ContainerOutput.StateHash; return(false); } // something is created, assume crafting success craftingQueue.IsContainerOutputFull = false; craftingQueue.ContainerOutputLastStateHash = craftingQueue.ContainerOutput.StateHash; if (recipe is Recipe.RecipeForManufacturing recipeManufacturing) { recipeManufacturing.ServerOnManufacturingCompleted( ((ManufacturingCraftingQueue)craftingQueue).WorldObject, craftingQueue); } if (craftingQueue is CharacterCraftingQueue characterCraftingQueue) { NotificationSystem.ServerSendItemsNotification( characterCraftingQueue.Character, result); } return(true); }
/// <summary> /// Enqueue crafting selected recipe. /// </summary> /// <param name="craftingQueue">Crafting queue instance.</param> /// <param name="recipe">Recipe instance.</param> /// <param name="countToCraft">Count to craft - must be greater than zero.</param> public static void ServerStartCrafting( [CanBeNull] IStaticWorldObject station, [CanBeNull] ICharacter character, [NotNull] CraftingQueue craftingQueue, [NotNull] Recipe recipe, ushort countToCraft, ushort?maxQueueSize = null) { var characterOrStation = (IWorldObject)station ?? character; if (characterOrStation == null) { throw new NullReferenceException("Character AND station cannot be null simultaneously"); } if (countToCraft == 0) { throw new Exception("Are you really want to craft zero items?"); } var isAdminMode = character != null && CreativeModeSystem.SharedIsInCreativeMode(character); if (!isAdminMode && recipe.RecipeType != RecipeType.ManufacturingByproduct && !recipe.CanBeCrafted( characterOrStation, craftingQueue, countToCraft: recipe.RecipeType == RecipeType.Manufacturing ? (ushort)1 : countToCraft)) { Logger.Error($"Recipe cannot be crafted - check failed: {recipe} at {characterOrStation}."); return; } var queueCount = craftingQueue.QueueItems.Count; if (queueCount > 0) { foreach (var existingQueueItem in craftingQueue.QueueItems) { if (!existingQueueItem.CanCombineWith(recipe)) { continue; } // try to increase the count to craft var lastQueueItemCountBefore = existingQueueItem.CountToCraftRemains; var maxCountToCraft = ushort.MaxValue; var isManufacturingRecipe = recipe.RecipeType != RecipeType.Hand && recipe.RecipeType != RecipeType.StationCrafting; if (!isManufacturingRecipe) { maxCountToCraft = recipe.OutputItems.Items[0].ProtoItem.MaxItemsPerStack; } var originalRequestedCountToCraft = countToCraft; var lastQueueItemCountNew = (ushort)Math.Min(maxCountToCraft, lastQueueItemCountBefore + countToCraft); var canAddCount = lastQueueItemCountNew - lastQueueItemCountBefore; if (canAddCount > 0) { existingQueueItem.CountToCraftRemains = lastQueueItemCountNew; Logger.Info( $"Recipe count extended for crafting: {recipe} at {characterOrStation}: from x{lastQueueItemCountBefore} to x{countToCraft}"); ServerDestroyInputItems(craftingQueue, recipe, countToCraft: (ushort)canAddCount, isAdminMode); if (isManufacturingRecipe) { return; } var remainingCountToCraft = originalRequestedCountToCraft - canAddCount; if (remainingCountToCraft <= 0) { // the last queue item took all the requested items count to craft return; } // last queue item cannot accomodate all the requested count to craft // let's try to add to previous queue item or a new queue item countToCraft = (ushort)remainingCountToCraft; } } } if (maxQueueSize.HasValue && craftingQueue.QueueItems.Count + 1 >= maxQueueSize.Value) { Logger.Info( $"Recipe cannot be queue for crafting due to max queue size limitation: {recipe} at {characterOrStation} with max queue size {maxQueueSize.Value}."); return; } var queueItem = new CraftingQueueItem(recipe, countToCraft, craftingQueue.ServerLastQueueItemLocalId++); ServerDestroyInputItems(craftingQueue, recipe, countToCraft: queueItem.CountToCraftRemains, isAdminMode); craftingQueue.QueueItems.Add(queueItem); if (craftingQueue.QueueItems.Count == 1) { // the added recipe is first in queue - so we will craft it right now craftingQueue.SetDurationFromCurrentRecipe(); } Logger.Info($"Recipe queued for crafting: {recipe} at {characterOrStation}."); }
private static void ServerProgressQueueItem( CraftingQueue craftingQueue, CraftingQueueItem queueItem, ref double deltaTime) { if (craftingQueue.TimeRemainsToComplete > deltaTime) { // consume deltaTime craftingQueue.TimeRemainsToComplete -= deltaTime; deltaTime = 0; // need more time to complete crafting return; } // crafting of item completed deltaTime -= craftingQueue.TimeRemainsToComplete; craftingQueue.TimeRemainsToComplete = 0; if (!ServerTryCreateOutputItems(craftingQueue, queueItem)) { // need more space in output container return; } Logger.Info($"Crafting of {queueItem} completed."); var recipe = queueItem.Recipe; if (recipe.RecipeType == RecipeType.Manufacturing) { // auto-manufacturers: destroy input items when crafting completed ServerDestroyInputItems( recipe, new AggregatedItemsContainers(craftingQueue.InputContainersArray), countToCraft: 1, isAdminMode: false); } Api.SafeInvoke(() => ServerNonManufacturingRecipeCrafted?.Invoke(queueItem)); if (recipe.RecipeType == RecipeType.Manufacturing || recipe.RecipeType == RecipeType.ManufacturingByproduct) { // do not reduce count to craft as this is a manufacturing recipe var station = (IWorldObject)craftingQueue.GameObject; if (recipe.RecipeType == RecipeType.Manufacturing && !recipe.CanBeCrafted( station, craftingQueue, countToCraft: 1)) { Logger.Info( $"Manufacturing recipe cannot be crafted anymore - check failed: {recipe} at {station}."); queueItem.CountToCraftRemains = 0; } } else // if this is a non-manufacturing recipe { queueItem.CountToCraftRemains--; } if (queueItem.CountToCraftRemains == 0) { // remove from queue craftingQueue.QueueItems.Remove(queueItem); } craftingQueue.SetDurationFromCurrentRecipe(); }
public static void ServerRecalculateTimeToFinish(CraftingQueue craftingQueue) { craftingQueue.ServerRecalculateTimeToFinish(); }
public virtual void ServerOnManufacturingCompleted( IStaticWorldObject objectManufacturer, CraftingQueue craftingQueue) { // do nothing (but could be overriden) }