private static (string message, string clearMessage, List <ItemObject> combined) BuildItemHint(ItemObject item, RandomizedResult randomizedResult, GossipHintStyle hintStyle, List <ItemObject> itemsToCombineWith, List <ItemObject> hintableItems, Func <ItemObject, bool> shouldIndicateImportance, Random random) { ushort soundEffectId = 0x690C; // grandma curious var itemNames = new List <string>(); var locationNames = new List <string>(); bool hasOrder = item.NewLocation.Value.HasAttribute <GossipCombineOrderAttribute>(); var combined = new List <ItemObject>(); combined.Add(item); var article = randomizedResult.Settings.ProgressiveUpgrades && item.Item.HasAttribute <ProgressiveAttribute>() ? "a " : GetArticle(item.Item); var color = TextCommands.ColorPink; var importance = ""; if (randomizedResult.Settings.HintsIndicateImportance && shouldIndicateImportance?.Invoke(item) == true) { var locationForImportance = item.Item.MainLocation().HasValue ? item.Item : item.NewLocation.Value; var isRequired = ItemUtils.IsRequired(item.Item, locationForImportance, randomizedResult, true); if (!ItemUtils.IsLogicallyJunk(item.Item)) { importance = isRequired ? " (required)" : " (not required)"; } color = isRequired ? TextCommands.ColorYellow : TextCommands.ColorSilver; } itemNames.Add(article + color + item.Item.ProgressiveUpgradeName(randomizedResult.Settings.ProgressiveUpgrades) + TextCommands.ColorWhite + importance); locationNames.Add(item.NewLocation.Value.Location()); if (hintStyle != GossipHintStyle.Relevant) { var gossipCombineAttribute = item.NewLocation.Value.GetAttribute <GossipCombineAttribute>(); combined = itemsToCombineWith.Where(io => gossipCombineAttribute?.OtherItems.Contains(io.NewLocation.Value) == true).ToList(); if (combined.Any()) { combined.Add(item); combined = combined.OrderBy(io => io.NewLocation.Value.GetAttribute <GossipCombineOrderAttribute>()?.Order ?? random.Next()).ToList(); locationNames.Clear(); itemNames.Clear(); var combinedName = gossipCombineAttribute.CombinedName; if (!string.IsNullOrWhiteSpace(combinedName)) { locationNames.Add(combinedName); } else { locationNames.AddRange(combined.Select(io => io.NewLocation.Value.Location())); } itemNames.AddRange(combined.Select(io => { article = randomizedResult.Settings.ProgressiveUpgrades && io.Item.HasAttribute <ProgressiveAttribute>() ? "a " : GetArticle(io.Item); color = TextCommands.ColorPink; importance = ""; if (randomizedResult.Settings.HintsIndicateImportance && shouldIndicateImportance?.Invoke(io) == true) { var locationForImportance = io.Item.MainLocation().HasValue ? io.Item : io.NewLocation.Value; var isRequired = ItemUtils.IsRequired(io.Item, locationForImportance, randomizedResult, true); if (!ItemUtils.IsLogicallyJunk(io.Item)) { importance = isRequired ? " (required)" : " (not required)"; } color = isRequired ? TextCommands.ColorYellow : TextCommands.ColorSilver; } return(article + color + io.Item.ProgressiveUpgradeName(randomizedResult.Settings.ProgressiveUpgrades) + TextCommands.ColorWhite + importance); })); } else { combined.Add(item); } } string clearMessage = null; if (itemNames.Any() && locationNames.Any()) { clearMessage = BuildGossipQuote(soundEffectId, locationNames, itemNames, hasOrder, random); } itemNames.Clear(); locationNames.Clear(); if (item.Mimic != null) { // If item has a mimic and not using clear hints, always use a fake hint. soundEffectId = 0x690A; // grandma laugh itemNames.Add(item.Mimic.Item.ItemHints().Random(random)); locationNames.Add(item.NewLocation.Value.LocationHints().Random(random)); } else if (hintStyle != GossipHintStyle.Random || random.Next(100) >= 5) // 5% chance of fake/junk hint if it's not a moon gossip stone or competitive style { itemNames.Add(item.Item.ItemHints().Random(random)); locationNames.Add(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 itemNames.Add(item.Item.ItemHints().Random(random)); locationNames.Add(hintableItems.Random(random).NewLocation.Value.LocationHints().Random(random)); } } if (itemNames.Any()) { itemNames[0] = $"{TextCommands.ColorPink}{itemNames[0]}{TextCommands.ColorWhite}"; } string message = null; if (itemNames.Any() && locationNames.Any()) { message = BuildGossipQuote(soundEffectId, locationNames, itemNames, hasOrder, random); //return (BuildGossipQuote(soundEffectId, locationNames, itemNames, hasOrder, random), combined); } if (message != null || clearMessage != null) { return(message, clearMessage, combined); } return(null, null, null); }
private static (string, List <ItemObject>) BuildItemHint(ItemObject item, GossipHintStyle gossipHintStyle, bool forceClear, bool clearHints, bool isMoonGossipStone, bool progressiveUpgradesEnabled, List <ItemObject> itemsToCombineWith, List <ItemObject> hintableItems, Random random) { ushort soundEffectId = 0x690C; // grandma curious var itemNames = new List <string>(); var locationNames = new List <string>(); bool hasOrder = item.NewLocation.Value.HasAttribute <GossipCombineOrderAttribute>(); var combined = new List <ItemObject>(); combined.Add(item); if (forceClear || clearHints) { itemNames.Add(item.Item.ProgressiveUpgradeName(progressiveUpgradesEnabled)); locationNames.Add(item.NewLocation.Value.Location()); if (!isMoonGossipStone) { var gossipCombineAttributes = item.NewLocation.Value.GetAttributes <GossipCombineAttribute>(); combined = itemsToCombineWith.Where(io => gossipCombineAttributes.Any(gca => gca.OtherItem == io.NewLocation)).ToList(); if (combined.Any()) { combined.Add(item); combined = combined.OrderBy(io => io.NewLocation.Value.GetAttribute <GossipCombineOrderAttribute>()?.Order ?? random.Next()).ToList(); locationNames.Clear(); itemNames.Clear(); var combinedName = gossipCombineAttributes.First().CombinedName; if (!string.IsNullOrWhiteSpace(combinedName)) { locationNames.Add(combinedName); } else { locationNames.AddRange(combined.Select(io => io.NewLocation.Value.Location())); } itemNames.AddRange(combined.Select(io => io.Item.ProgressiveUpgradeName(progressiveUpgradesEnabled))); } else { combined.Add(item); } } } else { if (item.Mimic != null) { // If item has a mimic and not using clear hints, always use a fake hint. soundEffectId = 0x690A; // grandma laugh itemNames.Add(item.Mimic.Item.ItemHints().Random(random)); locationNames.Add(item.NewLocation.Value.LocationHints().Random(random)); } else if (isMoonGossipStone || gossipHintStyle == GossipHintStyle.Competitive || random.Next(100) >= 5) // 5% chance of fake/junk hint if it's not a moon gossip stone or competitive style { itemNames.Add(item.Item.ItemHints().Random(random)); locationNames.Add(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 itemNames.Add(item.Item.ItemHints().Random(random)); locationNames.Add(hintableItems.Random(random).NewLocation.Value.LocationHints().Random(random)); } } } if (itemNames.Any() && locationNames.Any()) { return(BuildGossipQuote(soundEffectId, locationNames, itemNames, hasOrder, random), combined); } return(null, null); }
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); }