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 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); }