public static LogicPaths GetImportantItems(ItemList itemList, GameplaySettings settings, Item item, List <ItemLogic> itemLogic, List <Item> logicPath = null, Dictionary <Item, LogicPaths> checkedItems = null, params Item[] exclude) { if (settings.CustomStartingItemList.Contains(item)) { return(new LogicPaths()); } if (logicPath == null) { logicPath = new List <Item>(); } if (logicPath.Contains(item)) { return(null); } if (exclude.Contains(item)) { if (settings.AddSongs || !ItemUtils.IsSong(item) || logicPath.Any(i => !i.IsFake() && itemList[i].IsRandomized && !ItemUtils.IsSong(i))) { return(null); } } logicPath.Add(item); if (checkedItems == null) { checkedItems = new Dictionary <Item, LogicPaths>(); } if (checkedItems.ContainsKey(item)) { if (logicPath.Intersect(checkedItems[item].Required).Any()) { return(null); } return(checkedItems[item]); } var itemObject = itemList[item]; var locationId = itemObject.NewLocation.HasValue ? itemObject.NewLocation : item; var locationLogic = itemLogic[(int)locationId]; var required = new List <Item>(); var important = new List <Item>(); if (locationLogic.RequiredItemIds != null && locationLogic.RequiredItemIds.Any()) { foreach (var requiredItemId in locationLogic.RequiredItemIds) { var childPaths = GetImportantItems(itemList, settings, (Item)requiredItemId, itemLogic, logicPath.ToList(), checkedItems, exclude); if (childPaths == null) { return(null); } required.Add((Item)requiredItemId); if (childPaths.Required != null) { required.AddRange(childPaths.Required); } if (childPaths.Important != null) { important.AddRange(childPaths.Important); } } } if (locationLogic.ConditionalItemIds != null && locationLogic.ConditionalItemIds.Any()) { var logicPaths = new List <LogicPaths>(); foreach (var conditions in locationLogic.ConditionalItemIds) { var conditionalRequired = new List <Item>(); var conditionalImportant = new List <Item>(); foreach (var conditionalItemId in conditions) { var childPaths = GetImportantItems(itemList, settings, (Item)conditionalItemId, itemLogic, logicPath.ToList(), checkedItems, exclude); if (childPaths == null) { conditionalRequired = null; conditionalImportant = null; break; } conditionalRequired.Add((Item)conditionalItemId); if (childPaths.Required != null) { conditionalRequired.AddRange(childPaths.Required); } if (childPaths.Important != null) { conditionalImportant.AddRange(childPaths.Important); } } if (conditionalRequired != null && conditionalImportant != null) { logicPaths.Add(new LogicPaths { Required = conditionalRequired.AsReadOnly(), Important = conditionalImportant.AsReadOnly() }); } } if (!logicPaths.Any()) { return(null); } required.AddRange(logicPaths.Select(lp => lp.Required.AsEnumerable()).Aggregate((a, b) => a.Intersect(b))); important.AddRange(logicPaths.SelectMany(lp => lp.Required.Union(lp.Important)).Distinct()); } var result = new LogicPaths { Required = required.Distinct().ToList().AsReadOnly(), Important = important.Union(required).Distinct().ToList().AsReadOnly() }; if (!item.IsFake()) { checkedItems[item] = result; } return(result); }
public static List <MessageEntry> MakeGossipQuotes(RandomizedResult randomizedResult) { if (randomizedResult.Settings.GossipHintStyle == GossipHintStyle.Default) { return(new List <MessageEntry>()); } var random = new Random(randomizedResult.Seed); var randomizedItems = new List <ItemObject>(); var competitiveHints = new List <string>(); var itemsInRegions = new Dictionary <Region, List <ItemObject> >(); foreach (var item in randomizedResult.ItemList) { if (item.NewLocation == null) { continue; } if (randomizedResult.Settings.ClearHints) { // skip free items if (ItemUtils.IsStartingLocation(item.NewLocation.Value)) { continue; } } if (!item.IsRandomized) { continue; } var itemName = item.Item.Name(); if (randomizedResult.Settings.GossipHintStyle != GossipHintStyle.Competitive && (itemName.Contains("Heart") || itemName.Contains("Rupee")) && (randomizedResult.Settings.ClearHints || random.Next(8) != 0)) { continue; } if (randomizedResult.Settings.GossipHintStyle == GossipHintStyle.Competitive) { var preventRegions = new List <Region> { Region.TheMoon, Region.BottleCatch, Region.Misc }; var itemRegion = item.NewLocation.Value.Region(); if (itemRegion.HasValue && !preventRegions.Contains(itemRegion.Value) && !randomizedResult.Settings.CustomJunkLocations.Contains(item.NewLocation.Value)) { if (!itemsInRegions.ContainsKey(itemRegion.Value)) { itemsInRegions[itemRegion.Value] = new List <ItemObject>(); } itemsInRegions[itemRegion.Value].Add(item); } var competitiveHintInfo = item.NewLocation.Value.GetAttribute <GossipCompetitiveHintAttribute>(); if (competitiveHintInfo == null) { continue; } if (randomizedResult.Settings.CustomJunkLocations.Contains(item.NewLocation.Value)) { continue; } if (competitiveHintInfo.Condition != null && competitiveHintInfo.Condition(randomizedResult.Settings)) { continue; } } randomizedItems.Add(item); } var unusedItems = randomizedItems.ToList(); if (randomizedResult.Settings.GossipHintStyle == GossipHintStyle.Competitive) { var totalUniqueGossipHints = Enum.GetValues(typeof(GossipQuote)).Cast <GossipQuote>().Count(gq => !gq.IsMoonGossipStone()) / 2; var numberOfRequiredHints = randomizedResult.Settings.AddSongs ? 4 : 3; var numberOfNonRequiredHints = 3; var maxNumberOfSongOnlyHints = 3; var maxNumberOfClockTownHints = 2; var numberOfLocationHints = totalUniqueGossipHints - numberOfRequiredHints - numberOfNonRequiredHints; unusedItems = randomizedItems.GroupBy(io => io.NewLocation.Value.GetAttribute <GossipCompetitiveHintAttribute>().Priority) .OrderByDescending(g => g.Key) .Select(g => g.OrderBy(_ => random.Next()).AsEnumerable()) .Aggregate((g1, g2) => g1.Concat(g2)) .Take(numberOfLocationHints) .ToList(); unusedItems.AddRange(unusedItems); var importantRegionCounts = new Dictionary <Region, int>(); var nonImportantRegionCounts = new Dictionary <Region, int>(); var songOnlyRegionCounts = new Dictionary <Region, int>(); var clockTownRegionCounts = new Dictionary <Region, int>(); foreach (var kvp in itemsInRegions) { var numberOfRequiredItems = kvp.Value.Count(io => ItemUtils.IsRequired(io.Item, randomizedResult) && !unusedItems.Contains(io)); var numberOfImportantItems = kvp.Value.Count(io => ItemUtils.IsImportant(io.Item, randomizedResult)); if (numberOfRequiredItems == 0 && numberOfImportantItems > 0) { continue; } Dictionary <Region, int> dict; if (numberOfRequiredItems == 0) { dict = nonImportantRegionCounts; } else if (Gossip.ClockTownRegions.Contains(kvp.Key)) { dict = clockTownRegionCounts; } else if (!randomizedResult.Settings.AddSongs && kvp.Value.Count(io => ItemUtils.IsRequired(io.Item, randomizedResult) && !ItemUtils.IsSong(io.Item) && !unusedItems.Contains(io)) == 0) { dict = songOnlyRegionCounts; } else { dict = importantRegionCounts; } dict[kvp.Key] = numberOfRequiredItems; } var chosenSongOnlyRegions = 0; var chosenClockTownRegions = 0; for (var i = 0; i < numberOfRequiredHints; i++) { var regionCounts = importantRegionCounts.AsEnumerable(); if (chosenClockTownRegions < maxNumberOfClockTownHints) { regionCounts = regionCounts.Concat(clockTownRegionCounts); } if (chosenSongOnlyRegions < maxNumberOfSongOnlyHints) { regionCounts = regionCounts.Concat(songOnlyRegionCounts); } if (!regionCounts.Any()) { regionCounts = regionCounts.Concat(clockTownRegionCounts); //} //if (!regionCounts.Any()) //{ regionCounts = regionCounts.Concat(songOnlyRegionCounts); } if (regionCounts.Any()) { var chosen = regionCounts.ToList().Random(random); competitiveHints.Add(BuildRegionHint(chosen, random)); competitiveHints.Add(BuildRegionHint(chosen, random)); if (songOnlyRegionCounts.Remove(chosen.Key)) { chosenSongOnlyRegions++; } else if (clockTownRegionCounts.Remove(chosen.Key)) { chosenClockTownRegions++; } else { importantRegionCounts.Remove(chosen.Key); } } } for (var i = 0; i < numberOfNonRequiredHints; i++) { if (nonImportantRegionCounts.Any()) { var chosen = nonImportantRegionCounts.ToList().Random(random); competitiveHints.Add(BuildRegionHint(chosen, random)); competitiveHints.Add(BuildRegionHint(chosen, random)); nonImportantRegionCounts.Remove(chosen.Key); } } } List <MessageEntry> finalHints = new List <MessageEntry>(); foreach (var gossipQuote in Enum.GetValues(typeof(GossipQuote)).Cast <GossipQuote>().OrderBy(gq => random.Next())) { string messageText = null; var isMoonGossipStone = gossipQuote.IsMoonGossipStone(); if (!isMoonGossipStone && competitiveHints.Any()) { messageText = competitiveHints.Random(random); competitiveHints.Remove(messageText); } if (messageText == null) { var restrictionAttributes = gossipQuote.GetAttributes <GossipRestrictAttribute>().ToList(); ItemObject item = null; var forceClear = false; while (item == null) { if (restrictionAttributes.Any() && (isMoonGossipStone || randomizedResult.Settings.GossipHintStyle == GossipHintStyle.Relevant)) { var chosen = restrictionAttributes.Random(random); var candidateItem = chosen.Type == GossipRestrictAttribute.RestrictionType.Item ? randomizedResult.ItemList.Single(io => io.Item == chosen.Item) : randomizedResult.ItemList.Single(io => io.NewLocation == chosen.Item); if (isMoonGossipStone || unusedItems.Contains(candidateItem)) { item = candidateItem; forceClear = chosen.ForceClear; } else { restrictionAttributes.Remove(chosen); } } else if (unusedItems.Any()) { if (randomizedResult.Settings.GossipHintStyle == GossipHintStyle.Competitive) { item = unusedItems.FirstOrDefault(io => unusedItems.Count(x => x.Item == io.Item) == 1); if (item == null) { item = unusedItems.Random(random); } } else { item = unusedItems.Random(random); } } else { break; } } if (!isMoonGossipStone) { unusedItems.Remove(item); } if (item != null) { ushort soundEffectId = 0x690C; // grandma curious string itemName = null; string locationName = null; if (forceClear || randomizedResult.Settings.ClearHints) { itemName = item.Item.Name(); locationName = item.NewLocation.Value.Location(); } else { if (isMoonGossipStone || randomizedResult.Settings.GossipHintStyle == GossipHintStyle.Competitive || random.Next(100) >= 5) // 5% chance of fake/junk hint if it's not a moon gossip stone or competitive style { itemName = item.Item.ItemHints().Random(random); locationName = item.NewLocation.Value.LocationHints().Random(random); } else { if (random.Next(2) == 0) // 50% chance for fake hint. otherwise default to junk hint. { soundEffectId = 0x690A; // grandma laugh itemName = item.Item.ItemHints().Random(random); locationName = randomizedItems.Random(random).Item.LocationHints().Random(random); } } } if (itemName != null && locationName != null) { messageText = BuildGossipQuote(soundEffectId, locationName, itemName, random); } } } if (messageText == null) { messageText = Gossip.JunkMessages.Random(random); } finalHints.Add(new MessageEntry() { Id = (ushort)gossipQuote, Message = messageText, Header = MessageHeader.ToArray() }); } return(finalHints); }
public static List <MessageEntry> MakeGossipQuotes(RandomizedResult randomizedResult) { if (randomizedResult.Settings.GossipHintStyle == GossipHintStyle.Default) { return(new List <MessageEntry>()); } var random = new Random(randomizedResult.Seed); var randomizedItems = new List <ItemObject>(); var hintableItems = new List <ItemObject>(); var itemsInRegions = new Dictionary <Region, List <(ItemObject io, Item locationForImportance)> >(); foreach (var io in randomizedResult.ItemList) { if ((!io.IsRandomized || !io.NewLocation.Value.Region().HasValue) && (!io.Item.MainLocation().HasValue || !randomizedResult.ItemList[io.Item.MainLocation().Value].IsRandomized)) { continue; } // TODO make this less hard-coded if (io.NewLocation == Item.UpgradeRoyalWallet) { continue; } var item = io.Item.MainLocation().HasValue ? randomizedResult.ItemList.Find(x => x.NewLocation == io.Item.MainLocation().Value) : io; if (randomizedResult.Settings.ClearHints && !io.Item.MainLocation().HasValue) { // skip free items if (ItemUtils.IsStartingLocation(io.NewLocation.Value)) { continue; } } if (ItemUtils.IsRegionRestricted(randomizedResult.Settings, item.Item)) { continue; } randomizedItems.Add(item); if (randomizedResult.Settings.GossipHintStyle == GossipHintStyle.Random) { if (ItemUtils.IsJunk(item.Item) && (randomizedResult.Settings.ClearHints || random.Next(8) != 0)) { continue; } } if (randomizedResult.Settings.GossipHintStyle == GossipHintStyle.Competitive) { var preventRegions = new List <Region> { Region.TheMoon, Region.BottleCatch, Region.Misc }; var locationForImportance = io.Item.MainLocation().HasValue ? io.Item : io.NewLocation.Value; var itemRegion = locationForImportance.Region(); if (itemRegion.HasValue && !preventRegions.Contains(itemRegion.Value) && !randomizedResult.Settings.CustomJunkLocations.Contains(item.NewLocation.Value)) { if (!itemsInRegions.ContainsKey(itemRegion.Value)) { itemsInRegions[itemRegion.Value] = new List <(ItemObject, Item)>(); } itemsInRegions[itemRegion.Value].Add((item, locationForImportance)); } var competitiveHintInfo = item.NewLocation.Value.GetAttribute <GossipCompetitiveHintAttribute>(); if (competitiveHintInfo == null) { continue; } if (randomizedResult.Settings.CustomJunkLocations.Contains(io.NewLocation.Value)) { randomizedItems.Remove(item); continue; } if (competitiveHintInfo.Condition != null && !competitiveHintInfo.Condition(randomizedResult.Settings)) { randomizedItems.Remove(item); continue; } } hintableItems.Add(item); } var unusedItems = hintableItems.ToList(); var itemsToCombineWith = new List <ItemObject>(); var competitiveHints = new List <(string message, List <GossipQuote> allowedGossipQuotes)>(); if (randomizedResult.Settings.GossipHintStyle == GossipHintStyle.Competitive) { var gossipStoneRequirements = LogicUtils.GetGossipStoneRequirements(randomizedResult.ItemList, randomizedResult.Logic, randomizedResult.Settings); var totalUniqueGossipHints = Enum.GetValues(typeof(GossipQuote)).Cast <GossipQuote>().Count(gq => !gq.IsMoonGossipStone()) / 2; var numberOfRequiredHints = randomizedResult.Settings.AddSongs ? 4 : 3; var numberOfNonRequiredHints = 3; var maxNumberOfClockTownHints = 2; var numberOfLocationHints = totalUniqueGossipHints - numberOfRequiredHints - numberOfNonRequiredHints; unusedItems = hintableItems.GroupBy(io => io.NewLocation.Value.GetAttribute <GossipCompetitiveHintAttribute>().Priority) .OrderByDescending(g => g.Key) .Select(g => g.OrderBy(_ => random.Next()).AsEnumerable()) .Aggregate(Enumerable.Empty <ItemObject>(), (g1, g2) => g1.Concat(g2)) .Take(numberOfLocationHints) .ToList(); var combinedItems = unusedItems .SelectMany(io => io.NewLocation.Value.GetAttributes <GossipCombineAttribute>().Select(gca => gca.OtherItem)) .Where(item => randomizedItems.Any(io => io.NewLocation == item)) .Select(item => randomizedItems.Single(io => io.NewLocation == item)) .Where(io => !unusedItems.Contains(io)) ; itemsToCombineWith.AddRange(combinedItems); unusedItems.AddRange(unusedItems); foreach (var unusedItem in unusedItems) { (var messageText, var combined) = BuildItemHint( unusedItem, randomizedResult.Settings.GossipHintStyle, false, randomizedResult.Settings.ClearHints, false, randomizedResult.Settings.ProgressiveUpgrades, itemsToCombineWith, hintableItems, random ); var allowedGossipQuotes = combined .Select(io => gossipStoneRequirements.Where(kvp => !kvp.Value.Contains(io.NewLocation.Value)).Select(kvp => kvp.Key)) .Aggregate((list1, list2) => list1.Intersect(list2)) .ToList(); competitiveHints.Add((messageText, allowedGossipQuotes)); } var importantRegionCounts = new Dictionary <Region, List <(ItemObject io, Item locationForImportance)> >(); var nonImportantRegionCounts = new Dictionary <Region, List <(ItemObject, Item)> >(); var songOnlyRegionCounts = new Dictionary <Region, List <(ItemObject, Item)> >(); var clockTownRegionCounts = new Dictionary <Region, List <(ItemObject, Item)> >(); foreach (var kvp in itemsInRegions) { var requiredItems = kvp.Value.Where(io => ItemUtils.IsRequired(io.io.Item, io.locationForImportance, randomizedResult) && !unusedItems.Contains(io.io) && !itemsToCombineWith.Contains(io.io)).ToList(); var importantItems = kvp.Value.Where(io => ItemUtils.IsImportant(io.io.Item, io.locationForImportance, randomizedResult)).ToList(); Dictionary <Region, List <(ItemObject, Item)> > dict; if (requiredItems.Count == 0 && importantItems.Count > 0) { if (!randomizedResult.Settings.AddSongs && importantItems.All(io => ItemUtils.IsSong(io.io.Item) && !randomizedResult.ImportantSongLocations.Contains(io.locationForImportance))) { dict = songOnlyRegionCounts; } else { continue; } } else if (requiredItems.Count == 0) { dict = nonImportantRegionCounts; } else if (Gossip.ClockTownRegions.Contains(kvp.Key)) { dict = clockTownRegionCounts; } else { dict = importantRegionCounts; } dict[kvp.Key] = requiredItems; } var chosenClockTownRegions = 0; for (var i = 0; i < numberOfRequiredHints; i++) { var regionCounts = importantRegionCounts.AsEnumerable(); if (chosenClockTownRegions < maxNumberOfClockTownHints) { regionCounts = regionCounts.Concat(clockTownRegionCounts); } if (!regionCounts.Any()) { regionCounts = regionCounts.Concat(clockTownRegionCounts); } if (regionCounts.Any()) { var chosen = regionCounts.ToList().Random(random); var allowedGossipQuotes = chosen.Value .Select(io => gossipStoneRequirements.Where(kvp => !kvp.Value.Contains(io.locationForImportance)).Select(kvp => kvp.Key)) .Aggregate((list1, list2) => list1.Intersect(list2)) .ToList(); competitiveHints.Add((BuildRegionHint(chosen, RegionHintType.SomeRequired, random), allowedGossipQuotes)); competitiveHints.Add((BuildRegionHint(chosen, RegionHintType.SomeRequired, random), allowedGossipQuotes)); if (clockTownRegionCounts.Remove(chosen.Key)) { chosenClockTownRegions++; } else { importantRegionCounts.Remove(chosen.Key); } } } for (var i = 0; i < numberOfNonRequiredHints; i++) { var regionCounts = nonImportantRegionCounts.AsEnumerable(); regionCounts = regionCounts.Concat(songOnlyRegionCounts); if (regionCounts.Any()) { var chosen = regionCounts.ToList().Random(random); RegionHintType regionHintType; if (songOnlyRegionCounts.Remove(chosen.Key)) { regionHintType = RegionHintType.OnlyImportantSong; } else { nonImportantRegionCounts.Remove(chosen.Key); regionHintType = RegionHintType.NoneRequired; } competitiveHints.Add((BuildRegionHint(chosen, regionHintType, random), new List <GossipQuote>())); competitiveHints.Add((BuildRegionHint(chosen, regionHintType, random), new List <GossipQuote>())); } } unusedItems.Clear(); } List <MessageEntry> finalHints = new List <MessageEntry>(); while (competitiveHints.Any(ch => ch.allowedGossipQuotes.Count > 0)) { var competitiveHint = competitiveHints .Where(ch => ch.allowedGossipQuotes.Count > 0) .OrderBy(ch => ch.allowedGossipQuotes.Count) .ThenBy(ch => random.Next()) .First(); var gossipQuote = competitiveHint.allowedGossipQuotes.Random(random); finalHints.Add(new MessageEntry() { Id = (ushort)gossipQuote, Message = competitiveHint.message, Header = MessageHeader.ToArray() }); competitiveHints.Remove(competitiveHint); foreach (var ch in competitiveHints) { ch.allowedGossipQuotes.Remove(gossipQuote); } } foreach (var gossipQuote in Enum.GetValues <GossipQuote>().OrderBy(gq => random.Next())) { if (finalHints.Any(me => me.Id == (ushort)gossipQuote)) { continue; } string messageText = null; var isMoonGossipStone = gossipQuote.IsMoonGossipStone(); if (!isMoonGossipStone && competitiveHints.Any()) { var competitiveHint = competitiveHints.Random(random); messageText = competitiveHint.message; competitiveHints.Remove(competitiveHint); } if (messageText == null) { var restrictionAttributes = gossipQuote.GetAttributes <GossipRestrictAttribute>().ToList(); ItemObject item = null; var forceClear = false; while (item == null) { if (restrictionAttributes.Any() && (isMoonGossipStone || randomizedResult.Settings.GossipHintStyle == GossipHintStyle.Relevant)) { var chosen = restrictionAttributes.Random(random); var candidateItem = chosen.Type == GossipRestrictAttribute.RestrictionType.Item ? randomizedResult.ItemList.Single(io => io.ID == (int)chosen.Item) : randomizedResult.ItemList.Single(io => io.NewLocation == chosen.Item); if (isMoonGossipStone || unusedItems.Contains(candidateItem)) { item = candidateItem; forceClear = chosen.ForceClear; } else { restrictionAttributes.Remove(chosen); } } else if (unusedItems.Any()) { if (randomizedResult.Settings.GossipHintStyle == GossipHintStyle.Competitive) { item = unusedItems.FirstOrDefault(io => unusedItems.Count(x => x.ID == io.ID) == 1); if (item == null) { item = unusedItems.Random(random); } } else { item = unusedItems.Random(random); } } else { break; } } if (!isMoonGossipStone) { unusedItems.Remove(item); } if (item != null) { (var hint, var combined) = BuildItemHint( item, randomizedResult.Settings.GossipHintStyle, forceClear, randomizedResult.Settings.ClearHints, isMoonGossipStone, randomizedResult.Settings.ProgressiveUpgrades, itemsToCombineWith, hintableItems, random ); messageText = hint; } } if (messageText == null) { messageText = Gossip.JunkMessages.Random(random); } finalHints.Add(new MessageEntry() { Id = (ushort)gossipQuote, Message = messageText, Header = MessageHeader.ToArray() }); } return(finalHints); }
public static LogicPaths GetImportantLocations(ItemList itemList, GameplaySettings settings, Item location, List <ItemLogic> itemLogic, List <Item> logicPath = null, Dictionary <Item, LogicPaths> checkedLocations = null, params Item[] exclude) { var itemObject = itemList.Find(io => io.NewLocation == location) ?? itemList[location]; if (settings.CustomStartingItemList.Contains(itemObject.Item)) { return(new LogicPaths()); } if (logicPath == null) { logicPath = new List <Item>(); } if (logicPath.Contains(location)) { return(null); } if (exclude.Contains(location)) { if (settings.AddSongs || !ItemUtils.IsSong(location) || logicPath.Any(i => !i.IsFake() && itemList[i].IsRandomized && !ItemUtils.IsRegionRestricted(settings, i) && !ItemUtils.IsSong(i))) { return(null); } } var importantSongLocations = new List <Item>(); if (!settings.AddSongs && ItemUtils.IsSong(location) && logicPath.Any(i => !i.IsFake() && itemList[i].IsRandomized && !ItemUtils.IsRegionRestricted(settings, i))) { importantSongLocations.Add(location); } logicPath.Add(location); if (checkedLocations == null) { checkedLocations = new Dictionary <Item, LogicPaths>(); } if (checkedLocations.ContainsKey(location)) { if (logicPath.Intersect(checkedLocations[location].Required).Any()) { return(null); } if (!exclude.Intersect(checkedLocations[location].Required).Any()) { return(checkedLocations[location]); } } var locationLogic = itemLogic[(int)location]; var required = new List <Item>(); var important = new List <Item>(); if (locationLogic.RequiredItemIds != null && locationLogic.RequiredItemIds.Any()) { foreach (var requiredItemId in locationLogic.RequiredItemIds.Cast <Item>()) { if (itemList[requiredItemId].Item != requiredItemId) { continue; } var requiredLocation = itemList[requiredItemId].NewLocation ?? requiredItemId; var childPaths = GetImportantLocations(itemList, settings, requiredLocation, itemLogic, logicPath.ToList(), checkedLocations, exclude); if (childPaths == null) { return(null); } required.Add(requiredLocation); important.Add(requiredLocation); if (childPaths.Required != null) { required.AddRange(childPaths.Required); } if (childPaths.Important != null) { important.AddRange(childPaths.Important); } if (childPaths.ImportantSongLocations != null) { importantSongLocations.AddRange(childPaths.ImportantSongLocations); } } } if (locationLogic.ConditionalItemIds != null && locationLogic.ConditionalItemIds.Any()) { var logicPaths = new List <LogicPaths>(); foreach (var conditions in locationLogic.ConditionalItemIds) { var conditionalRequired = new List <Item>(); var conditionalImportant = new List <Item>(); var conditionalImportantSongLocations = new List <Item>(); foreach (var conditionalItemId in conditions.Cast <Item>()) { if (itemList[conditionalItemId].Item != conditionalItemId) { continue; } var conditionalLocation = itemList[conditionalItemId].NewLocation ?? conditionalItemId; var childPaths = GetImportantLocations(itemList, settings, conditionalLocation, itemLogic, logicPath.ToList(), checkedLocations, exclude); if (childPaths == null) { conditionalRequired = null; conditionalImportant = null; break; } conditionalRequired.Add(conditionalLocation); conditionalImportant.Add(conditionalLocation); if (childPaths.Required != null) { conditionalRequired.AddRange(childPaths.Required); } if (childPaths.Important != null) { conditionalImportant.AddRange(childPaths.Important); } if (childPaths.ImportantSongLocations != null) { conditionalImportantSongLocations.AddRange(childPaths.ImportantSongLocations); } } if (conditionalRequired != null && conditionalImportant != null) { logicPaths.Add(new LogicPaths { Required = conditionalRequired.AsReadOnly(), Important = conditionalImportant.AsReadOnly(), ImportantSongLocations = conditionalImportantSongLocations.AsReadOnly() }); } } if (!logicPaths.Any()) { return(null); } // Hopefully this makes item importance a little smarter. var shouldRemove = new List <int>(); for (var i = 0; i < logicPaths.Count; i++) { var currentLogicPath = logicPaths[i]; var currentLogicImportant = currentLogicPath.Important.Except(important); for (var j = 0; j < logicPaths.Count; j++) { if (i != j && !shouldRemove.Contains(i) && !shouldRemove.Contains(j)) { var otherLogicPath = logicPaths[j]; var otherLogicImportant = otherLogicPath.Important.Except(important); if (!currentLogicImportant.Except(otherLogicImportant).Any() && otherLogicImportant.Except(currentLogicImportant).Any()) { shouldRemove.Add(j); } } } } foreach (var index in shouldRemove.OrderByDescending(x => x)) { logicPaths.RemoveAt(index); } required.AddRange(logicPaths.Select(lp => lp.Required.AsEnumerable()).Aggregate((a, b) => a.Intersect(b))); important.AddRange(logicPaths.SelectMany(lp => lp.Required.Union(lp.Important)).Distinct()); importantSongLocations.AddRange(logicPaths.SelectMany(lp => lp.ImportantSongLocations).Distinct()); } var result = new LogicPaths { Required = required.Distinct().ToList().AsReadOnly(), Important = important.Union(required).Distinct().ToList().AsReadOnly(), ImportantSongLocations = importantSongLocations.Distinct().ToList().AsReadOnly() }; if (!location.IsFake()) { checkedLocations[location] = result; } return(result); }
public static List <MessageEntry> MakeGossipQuotes( IEnumerable <GossipQuote> gossipQuotes, GossipHintStyle hintStyle, RandomizedResult randomizedResult, int numberOfRequiredHints, int numberOfNonRequiredHints, int maxNumberOfClockTownHints, List <Region> hintedRegions, List <ItemObject> hintedItems) { if (hintStyle == GossipHintStyle.Default) { return(new List <MessageEntry>()); } var random = new Random(randomizedResult.Seed); var randomizedItems = new List <ItemObject>(); var hintableItems = new List <ItemObject>(); var itemsInRegions = new Dictionary <Region, List <(ItemObject io, Item locationForImportance)> >(); foreach (var io in randomizedResult.ItemList) { if ((!io.IsRandomized || !io.NewLocation.Value.Region().HasValue) && (!io.Item.MainLocation().HasValue || !randomizedResult.ItemList[io.Item.MainLocation().Value].IsRandomized)) { continue; } // TODO make this less hard-coded if (io.NewLocation == Item.UpgradeRoyalWallet) { continue; } var item = io.Item.MainLocation().HasValue ? randomizedResult.ItemList.Find(x => x.NewLocation == io.Item.MainLocation().Value) : io; if (!io.Item.MainLocation().HasValue) { // skip free items if (ItemUtils.IsStartingLocation(io.NewLocation.Value)) { continue; } } if (ItemUtils.IsRegionRestricted(randomizedResult.Settings, item.Item)) { continue; } randomizedItems.Add(item); if (hintStyle == GossipHintStyle.Competitive) { var preventRegions = new List <Region> { Region.TheMoon, Region.BottleCatch, Region.Misc }; var locationForImportance = io.Item.MainLocation().HasValue ? io.Item : io.NewLocation.Value; var itemRegion = locationForImportance.Region(); if (itemRegion.HasValue && !preventRegions.Contains(itemRegion.Value) && !randomizedResult.Settings.CustomJunkLocations.Contains(item.NewLocation.Value)) { if (!itemsInRegions.ContainsKey(itemRegion.Value)) { itemsInRegions[itemRegion.Value] = new List <(ItemObject, Item)>(); } itemsInRegions[itemRegion.Value].Add((item, locationForImportance)); } if (hintedItems.Contains(item)) { continue; } if (randomizedResult.Settings.OverrideHintPriorities != null) { if (!randomizedResult.Settings.OverrideHintPriorities.Any(items => items.Contains(item.NewLocation.Value))) { continue; } } else { var competitiveHintInfo = item.NewLocation.Value.GetAttribute <GossipCompetitiveHintAttribute>(); if (competitiveHintInfo == null) { continue; } if (competitiveHintInfo.Condition != null && !competitiveHintInfo.Condition(randomizedResult.Settings)) { randomizedItems.Remove(item); continue; } } if (randomizedResult.Settings.CustomJunkLocations.Contains(io.NewLocation.Value)) { randomizedItems.Remove(item); continue; } } hintableItems.Add(item); } var unusedItems = hintableItems.ToList(); var itemsToCombineWith = new List <ItemObject>(); var competitiveHints = new List <(string message, string clearMessage, List <GossipQuote> allowedGossipQuotes)>(); if (hintStyle == GossipHintStyle.Competitive) { var gossipStoneRequirements = LogicUtils.GetGossipStoneRequirements(gossipQuotes, randomizedResult.ItemList, randomizedResult.Logic, randomizedResult.Settings, randomizedResult.CheckedImportanceLocations); var totalUniqueGossipHints = gossipQuotes.Count() / 2; var numberOfLocationHints = totalUniqueGossipHints - numberOfRequiredHints - numberOfNonRequiredHints; Func <ItemObject, int> getPriority = randomizedResult.Settings.OverrideHintPriorities != null ? (io) => randomizedResult.Settings.OverrideHintPriorities.FindIndex(locations => locations.Contains(io.NewLocation.Value)) : (io) => - io.NewLocation.Value.GetAttribute <GossipCompetitiveHintAttribute>().Priority; unusedItems = hintableItems.GroupBy(io => io.NewLocation.Value.GetAttribute <GossipCombineAttribute>()?.CombinedName ?? io.NewLocation.Value.ToString()) .Select(g => g.OrderBy(getPriority).First()) .GroupBy(getPriority) .OrderBy(g => g.Key) .Select(g => g.OrderBy(_ => random.Next()).AsEnumerable()) .Aggregate(Enumerable.Empty <ItemObject>(), (g1, g2) => g1.Concat(g2)) .Take(numberOfLocationHints) .ToList(); var combinedItems = hintableItems.Where(io => !unusedItems.Contains(io)); itemsToCombineWith.AddRange(combinedItems); unusedItems.AddRange(unusedItems); Func <ItemObject, bool> shouldIndicatePriority = randomizedResult.Settings.OverrideHintPriorities != null && randomizedResult.Settings.OverrideImportanceIndicatorTiers != null ? io => randomizedResult.Settings.OverrideImportanceIndicatorTiers.Contains(getPriority(io)) : io => true; foreach (var unusedItem in unusedItems) { (var messageText, var clearMessageText, var combined) = BuildItemHint( unusedItem, randomizedResult, hintStyle, itemsToCombineWith, hintableItems, shouldIndicatePriority, random ); var allowedGossipQuotes = combined .Select(io => gossipStoneRequirements.Where(kvp => kvp.Value?.Contains(io.NewLocation.Value) == false).Select(kvp => kvp.Key)) .Aggregate((list1, list2) => list1.Intersect(list2)) .ToList(); competitiveHints.Add((messageText, clearMessageText, allowedGossipQuotes)); hintedItems.AddRange(combined); } var importantRegionCounts = new Dictionary <Region, List <(ItemObject io, Item locationForImportance)> >(); var nonImportantRegionCounts = new Dictionary <Region, List <(ItemObject, Item)> >(); var songOnlyRegionCounts = new Dictionary <Region, List <(ItemObject, Item)> >(); var clockTownRegionCounts = new Dictionary <Region, List <(ItemObject, Item)> >(); foreach (var kvp in itemsInRegions) { var requiredItems = kvp.Value.Where(io => ItemUtils.IsRequired(io.io.Item, io.locationForImportance, randomizedResult) && !unusedItems.Contains(io.io) && !itemsToCombineWith.Contains(io.io)).ToList(); var importantItems = kvp.Value.Where(io => ItemUtils.IsImportant(io.io.Item, io.locationForImportance, randomizedResult)).ToList(); Dictionary <Region, List <(ItemObject, Item)> > dict; if (requiredItems.Count == 0 && importantItems.Count > 0) { if (!randomizedResult.Settings.AddSongs && importantItems.All(io => ItemUtils.IsSong(io.io.Item))) { dict = songOnlyRegionCounts; } else { continue; } } else if (requiredItems.Count == 0) { dict = nonImportantRegionCounts; } else if (Gossip.ClockTownRegions.Contains(kvp.Key)) { dict = clockTownRegionCounts; } else { dict = importantRegionCounts; } dict[kvp.Key] = requiredItems; if (!randomizedResult.Settings.AddSongs && requiredItems.Count > 0 && requiredItems.All(io => ItemUtils.IsSong(io.io.Item)) && importantItems.All(io => ItemUtils.IsSong(io.io.Item))) { songOnlyRegionCounts[kvp.Key] = requiredItems; } } for (var i = 0; i < numberOfNonRequiredHints; i++) { var regionCounts = nonImportantRegionCounts.AsEnumerable(); regionCounts = regionCounts.Concat(songOnlyRegionCounts); regionCounts = regionCounts.Where(kvp => !hintedRegions.Contains(kvp.Key)); if (regionCounts.Any()) { var chosen = regionCounts.ToList().Random(random, kvp => itemsInRegions[kvp.Key].Count); RegionHintType regionHintType; if (songOnlyRegionCounts.Remove(chosen.Key)) { regionHintType = RegionHintType.OnlyImportantSong; } else { nonImportantRegionCounts.Remove(chosen.Key); regionHintType = RegionHintType.NoneRequired; } competitiveHints.Add((BuildRegionHint(chosen, regionHintType, random), null, new List <GossipQuote>())); competitiveHints.Add((BuildRegionHint(chosen, regionHintType, random), null, new List <GossipQuote>())); hintedRegions.Add(chosen.Key); } } var chosenClockTownRegions = 0; for (var i = 0; i < numberOfRequiredHints; i++) { var regionCounts = importantRegionCounts.AsEnumerable(); if (chosenClockTownRegions < maxNumberOfClockTownHints) { regionCounts = regionCounts.Concat(clockTownRegionCounts); } if (!regionCounts.Any()) { regionCounts = regionCounts.Concat(clockTownRegionCounts); } regionCounts = regionCounts.Where(kvp => !hintedRegions.Contains(kvp.Key)); if (regionCounts.Any()) { var chosen = regionCounts.ToList().Random(random); var allowedGossipQuotes = chosen.Value .Select(io => gossipStoneRequirements.Where(kvp => kvp.Value?.Contains(io.locationForImportance) == false).Select(kvp => kvp.Key)) .Aggregate((list1, list2) => list1.Intersect(list2)) .ToList(); competitiveHints.Add((BuildRegionHint(chosen, RegionHintType.SomeRequired, random), null, allowedGossipQuotes)); competitiveHints.Add((BuildRegionHint(chosen, RegionHintType.SomeRequired, random), null, allowedGossipQuotes)); if (clockTownRegionCounts.Remove(chosen.Key)) { chosenClockTownRegions++; } else { importantRegionCounts.Remove(chosen.Key); } hintedRegions.Add(chosen.Key); } } unusedItems.Clear(); } List <MessageEntry> finalHints = new List <MessageEntry>(); void addHint(GossipQuote gossipQuote, string message) { var header = MessageHeader.ToArray(); if (gossipQuote.IsGaroHint()) { header[0] = 0; header[1] = 1; message = message.Replace("\xBF", "\x19\xBF"); } finalHints.Add(new MessageEntry() { Id = (ushort)gossipQuote, Message = message, Header = header }); } while (competitiveHints.Any(ch => ch.allowedGossipQuotes.Count > 0)) { var competitiveHint = competitiveHints .Where(ch => ch.allowedGossipQuotes.Count > 0) .OrderBy(ch => ch.allowedGossipQuotes.Count) .ThenBy(ch => random.Next()) .First(); var gossipQuote = competitiveHint.allowedGossipQuotes.Random(random); var clearHintsEnabled = gossipQuote.IsGaroHint() ? randomizedResult.Settings.ClearGaroHints : randomizedResult.Settings.ClearHints; addHint(gossipQuote, clearHintsEnabled && competitiveHint.clearMessage != null ? competitiveHint.clearMessage : competitiveHint.message); competitiveHints.Remove(competitiveHint); foreach (var ch in competitiveHints) { ch.allowedGossipQuotes.Remove(gossipQuote); } } foreach (var gossipQuote in gossipQuotes.OrderBy(gq => random.Next())) { if (finalHints.Any(me => me.Id == (ushort)gossipQuote)) { continue; } string messageText = null; var isMoonGossipStone = gossipQuote.IsMoonGossipStone(); var clearHintsEnabled = gossipQuote.IsGaroHint() ? randomizedResult.Settings.ClearGaroHints : randomizedResult.Settings.ClearHints; if (competitiveHints.Any()) { var competitiveHint = competitiveHints.Random(random); messageText = clearHintsEnabled && competitiveHint.clearMessage != null ? competitiveHint.clearMessage : competitiveHint.message; competitiveHints.Remove(competitiveHint); } if (messageText == null) { var restrictionAttributes = gossipQuote.GetAttributes <GossipRestrictAttribute>().ToList(); ItemObject item = null; var forceClear = false; while (item == null) { if (restrictionAttributes.Any() && hintStyle == GossipHintStyle.Relevant) { var chosen = restrictionAttributes.Random(random); var candidateItem = chosen.Type == GossipRestrictAttribute.RestrictionType.Item ? randomizedResult.ItemList.Single(io => io.ID == (int)chosen.Item) : randomizedResult.ItemList.Single(io => io.NewLocation == chosen.Item); if (unusedItems.Contains(candidateItem)) { item = candidateItem; forceClear = chosen.ForceClear; } else { restrictionAttributes.Remove(chosen); } } else if (unusedItems.Any()) { if (hintStyle == GossipHintStyle.Competitive) { item = unusedItems.FirstOrDefault(io => unusedItems.Count(x => x.ID == io.ID) == 1); if (item == null) { item = unusedItems.Random(random); } } else { item = unusedItems.Random(random); if (ItemUtils.IsJunk(item.Item) && (clearHintsEnabled || random.Next(8) != 0)) { item = null; } } } else { break; } } if (!isMoonGossipStone) { unusedItems.Remove(item); } if (item != null) { (var hint, var clearHint, var combined) = BuildItemHint( item, randomizedResult, hintStyle, itemsToCombineWith, hintableItems, null, random ); messageText = hint; if (clearHint != null && clearHintsEnabled) { messageText = clearHint; } } } if (messageText == null) { messageText = Gossip.JunkMessages.Random(random); } addHint(gossipQuote, messageText); } return(finalHints); }