Beispiel #1
0
        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);
        }
Beispiel #2
0
        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;
            }
        }
Beispiel #3
0
 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);
            }
        }
Beispiel #5
0
        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);
        }
Beispiel #6
0
        /// <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);
        }
Beispiel #7
0
        /// <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);
        }
Beispiel #9
0
        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);
        }
Beispiel #10
0
        /// <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);
        }
Beispiel #11
0
            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));
            }
Beispiel #12
0
        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);
        }
Beispiel #13
0
        /// <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}.");
        }
Beispiel #14
0
        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();
        }
Beispiel #15
0
 public static void ServerRecalculateTimeToFinish(CraftingQueue craftingQueue)
 {
     craftingQueue.ServerRecalculateTimeToFinish();
 }
Beispiel #16
0
 public virtual void ServerOnManufacturingCompleted(
     IStaticWorldObject objectManufacturer,
     CraftingQueue craftingQueue)
 {
     // do nothing (but could be overriden)
 }