internal override void UnConsumeResources(CraftPath path) { UnConsumeItems(path); base.UnConsumeResources(path); }
// Multithreading to prevent lag? private static void FindCraftPaths(List <CraftPath> paths, CraftPath inProgress, CancellationToken token) { //if(token.CanBeCanceled && watch.ElapsedMilliseconds > 1000) //{ // Main.NewText("timed out" + watch.ElapsedMilliseconds); // return; //} if (token.IsCancellationRequested) { return; } // Some notion of limiting depth of tree might help. //int count = inProgress.root.GetAllChildrenPreOrder().Count(); //.OfType<CraftPath.RecipeNode>() //if (count > 20) //{ // return; //} // Current will always be an unfulfilled CraftPath.UnfulfilledNode current = inProgress.GetCurrent(); if (current == null) { paths.Add(inProgress.Clone()); // Clone probably. return; } // Assume current is UnfulfulledItem class...could change later. Kill boss, other conditions maybe? think more about this. var ViableIngredients = current.item; var neededStack = current.stack; // reduce VialbleIngredients to only items not seen above. current.CheckParentsForRecipeLoopViaIngredients(ViableIngredients); if (ViableIngredients.Count == 0) { //Console.WriteLine(); return; } // if(VialbleIngredients.length == 1) optimization? //List<Recipe> recipes; //if (!recipeDictionary.TryGetValue(needItem.Key, out recipes)) // recipes = new List<Recipe>(); // Find all Recipes that can fulfill current, which might be a RecipeGroup var recipeOptions = recipeDictionary.Where(x => ViableIngredients.Contains(x.Key)).SelectMany(x => x.Value).ToList(); // inefficient maybe? foreach (var recipe in recipeOptions) { // Better to do this before. //if (current.CheckParentsForRecipeLoop(recipe)) // continue; //if(inProgress.root.recipe.) if (inProgress.root is CraftPath.RecipeNode) { var rootRecipe = (inProgress.root as CraftPath.RecipeNode).recipe; if (recipe.requiredItem.Any(x => x.type == rootRecipe.createItem.type && x.stack >= rootRecipe.createItem.stack)) { continue; // Prevents Wood->Platform->Wood loops. Other code checks similar loops but didn't catch these. TODO: Think about RecipeGroups. } } // RecipeAvailable here. // Allow missing tiles? // If createItem/ingredients exists in Tree? int craftMultiple = (neededStack - 1) / recipe.createItem.stack + 1; // Push recipe consumes Items while populating Children with Unfulfilled or Fulfilled. var recipeNode = inProgress.Push(current, recipe, craftMultiple); if (RecipePathTester.print) { inProgress.Print(); } int pathsBefore = paths.Count; FindCraftPaths(paths, inProgress, token); // Handles everything from here recursively. int pathsAfter = paths.Count; // Pop restores Unfulfilled and restores consumed Items. inProgress.Pop(current, recipeNode); // 3 Iron Ore 6 Lead Ore problem...if paths same size, try with craftMultiple of 1 maybe? if (pathsBefore == pathsAfter) // and craftMultiple < some number for performance. { // And 1 is possible? } } if (allowPurchasable) { // TODO recipe groups. var buyAble = ViableIngredients.Intersect(purchasable.Keys); //if (ViableIngredients.Any(x => purchasable.Contains(x))) if (buyAble.Count() > 0) { // TODO: If Can afford. // TODO: Take into account incomplete already owned items? // UnfulfilledNode should probably be nested under HaveItemNode, or merged together. CraftPath.BuyItemNode buyItemNode = new CraftPath.BuyItemNode(buyAble.First(), neededStack, current.ChildNumber, current.parent, current.craftPath); inProgress.Push(current, buyItemNode); if (RecipePathTester.print) { inProgress.Print(); } FindCraftPaths(paths, inProgress, token); inProgress.Pop(current, buyItemNode); } } if (allowLoots) { //if (VialbleIngredients.Intersect(loots).Any()) var lootable = ViableIngredients.Intersect(loots.Keys); // TODO recipe groups. --> For loop?? if (lootable.Count() > 0) { bool encountered = false; int npc = loots[lootable.First()].First(); // Only checks 1 item in Group, and first NPC that drops it. Fix later. int bannerID = Item.NPCtoBanner(npc); if (bannerID > 0) { if (NPC.killCount[bannerID] > 0) { encountered = true; } } if (encountered) { CraftPath.LootItemNode lootItemNode = new CraftPath.LootItemNode(lootable.First(), neededStack, current.ChildNumber, current.parent, current.craftPath); inProgress.Push(current, lootItemNode); if (RecipePathTester.print) { inProgress.Print(); } FindCraftPaths(paths, inProgress, token); inProgress.Pop(current, lootItemNode); } } } // Other sources? // Fishing? // Mining/Harvesting? // Extractinator // Chests // Statue // Traveling Shop // ItemChecklist? If not any of the others, I must have had it once before. // returning will result in Popping. }
public HaveItemNode(int itemid, int stack, int ChildNumber, CraftPathNode parent, CraftPath craftPath) : base(ChildNumber, parent, craftPath) { this.itemid = itemid; this.stack = stack; }
public UnfulfilledNode(RecipeGroup recipeGroup, int stack, int ChildNumber, CraftPathNode parent, CraftPath craftPath) : base(ChildNumber, parent, craftPath) { this.recipeGroup = recipeGroup; this.item = recipeGroup.ValidItems; this.stack = stack; // recipeGroup.ContainsItem probably faster than iterating over item? }
// TODO: GetCraftPaths but without a Recipe? Just an item? Buy/Loot // Bestiary Option? All New Items you can craft if you farm this npc? internal static List <CraftPath> GetCraftPaths(Recipe recipe, CancellationToken token) { //Main.NewText("GetCraftPaths"); //if(harvestable == null) //{ // // prevent network spam? // harvestable = new HashSet<int>(); // for (int i = 0; i < Main.maxTilesX; i++) // { // for (int j = 0; j < Main.maxTilesY; j++) // { // } // } //} //if (RecipePath.isCraftableOptimization) //{ // //if (ItemCatalogueUI.instance.craftResults == null) // throw new Exception(); //} // TODO: Track money? or just use inventory items. Bank money easily query-able. // Special Currencies? var haveItems = CalculateHaveItems(); List <CraftPath> paths = new List <CraftPath>(); CraftPath craftPath = new CraftPath(recipe, haveItems); // Push. Can't pop. // not calling ConsumeResources(recipeNode); if (RecipePathTester.print) { craftPath.Print(); } FindCraftPaths(paths, craftPath, token); //watch.Stop(); //var elapsedMs = watch.ElapsedMilliseconds; //if (elapsedMs > 1000) //{ // StringBuilder sb = new StringBuilder(); // foreach (var item in recipe.requiredItem) // { // if (!item.IsAir) // sb.Append(ItemTagHandler.GenerateTag(item)); // } // sb.Append("-->"); // sb.Append(ItemTagHandler.GenerateTag(recipe.createItem)); // Main.NewText(elapsedMs + ": " + sb.ToString()); //} if (!allowMissingStations) { for (int i = paths.Count - 1; i >= 0; i--) { // imkSushisMod caused thousands of paths to be culled here. Cull earlier or limit paths count. Investigate more. if (paths[i].root.GetAllChildrenPreOrder().OfType <CraftPath.RecipeNode>().Any(x => x.recipe.requiredTile.Any(tile => tile > -1 && !RecipeBrowserPlayer.seenTiles[tile]))) { paths.RemoveAt(i); } } } return(paths); }
internal void UnConsumeMoney(CraftPath path) { //path.haveItems.Adjust(itemid, stack); }
public UnfulfilledNode(int item, int stack, int ChildNumber, CraftPathNode parent, CraftPath craftPath) : base(ChildNumber, parent, craftPath) { this.stack = stack; this.item = new List <int>() { item }; }
internal void ConsumeMoney(CraftPath path) { // TODO. For now assume infinite money. //path.haveItems.Adjust(itemid, -stack); }
internal override void ConsumeResources(CraftPath path) { ConsumeMoney(path); base.ConsumeResources(path); }
public HaveItemsNode(RecipeGroup recipeGroup, List <Tuple <int, int> > listOfItems, int ChildNumber, CraftPathNode parent, CraftPath craftPath) : base(ChildNumber, parent, craftPath) { this.recipeGroup = recipeGroup; this.listOfItems = listOfItems; }
internal void ConsumeItems(CraftPath path) { path.haveItems.Adjust(itemid, -stack); }
public RecipeNode(Recipe recipe, int multiplier, int ChildNumber, CraftPathNode parent, CraftPath craftPath) : base(ChildNumber, parent, craftPath) { this.recipe = recipe; this.multiplier = multiplier; children = new CraftPathNode[recipe.requiredItem.Count(x => !x.IsAir)]; List <int> groups = RecipePath.GetAcceptedGroups(recipe); for (int i = 0; i < children.Length; i++) // For Each Ingredient. { bool itemIsRecipeGroupItem = false; foreach (var groupid in groups) { // 6 wood, 4 shadewood works for 10 any wood. // multiplier assumes all same Item in ItemGroup used for all Recipes. if (recipe.requiredItem[i].type == RecipeGroup.recipeGroups[groupid].ValidItems[RecipeGroup.recipeGroups[groupid].IconicItemIndex]) { bool foundValidItem = false; bool foundPartialItem = false; foreach (var validItemID in RecipeGroup.recipeGroups[groupid].ValidItems) { if (craftPath.haveItems.ContainsKey(validItemID) && craftPath.haveItems[validItemID] >= recipe.requiredItem[i].stack * multiplier) { // Any Wood on left, Wood on Right problem. Wood could be consumed before Wood node, when ShadeWood would be better option. children[i] = new HaveItemNode(validItemID, recipe.requiredItem[i].stack * multiplier, i, this, craftPath); foundValidItem = true; break; } else if (craftPath.haveItems.ContainsKey(validItemID)) { foundPartialItem = true; } } if (!foundValidItem && foundPartialItem) { List <Tuple <int, int> > listOfItems = new List <Tuple <int, int> >(); int remaining = recipe.requiredItem[i].stack * multiplier; foreach (var validItemID in RecipeGroup.recipeGroups[groupid].ValidItems) { if (remaining > 0 && craftPath.haveItems.ContainsKey(validItemID)) { int taken = Math.Min(remaining, craftPath.haveItems[validItemID]); listOfItems.Add(new Tuple <int, int>(validItemID, taken)); remaining -= taken; } } children[i] = new HaveItemsNode(RecipeGroup.recipeGroups[groupid], listOfItems, i, this, craftPath); if (remaining > 0) { children[i].children = new CraftPathNode[1]; children[i].children[0] = new UnfulfilledNode(RecipeGroup.recipeGroups[groupid], remaining, 0, children[i], craftPath); } } else if (!foundValidItem) { children[i] = new UnfulfilledNode(RecipeGroup.recipeGroups[groupid], recipe.requiredItem[i].stack * multiplier, i, this, craftPath); } itemIsRecipeGroupItem = true; break; } } // Does it make more sense to nest these, or add more children slots? Hm, Children match up to recipe ingredient index.... Make a BranchNode? if (!itemIsRecipeGroupItem) { // Recipe Groups can have stacks-size different inputs if needed. Ignore for now and handle: 10 wood needed, 9 wood held and 2 platforms held. if (craftPath.haveItems.ContainsKey(recipe.requiredItem[i].type) && craftPath.haveItems[recipe.requiredItem[i].type] >= recipe.requiredItem[i].stack * multiplier) { // Potential problem: Recipe with multiple of same item. Or Item and ItemGroup that share. // Could implement consumed flag and attempt to consume immediately. children[i] = new HaveItemNode(recipe.requiredItem[i].type, recipe.requiredItem[i].stack * multiplier, i, this, craftPath); } else { if (craftPath.haveItems.ContainsKey(recipe.requiredItem[i].type)) { int remainder = recipe.requiredItem[i].stack * multiplier - craftPath.haveItems[recipe.requiredItem[i].type]; children[i] = new HaveItemNode(recipe.requiredItem[i].type, craftPath.haveItems[recipe.requiredItem[i].type], i, this, craftPath); children[i].children = new CraftPathNode[1]; children[i].children[0] = new UnfulfilledNode(recipe.requiredItem[i].type, remainder, 0, children[i], craftPath); } else { children[i] = new UnfulfilledNode(recipe.requiredItem[i].type, recipe.requiredItem[i].stack * multiplier, i, this, craftPath); // assign current? } } } // TODO: Assign CraftPath.Current to 1st or last unfulfilled // TODO: If Loot and Shop and Missing disabled, check // if (RecipePath.isCraftableOptimization && !ItemCatalogueUI.instance.craftResults[item.Key]) } }