public static void CreateSpoilerLog(RandomizedResult randomized, GameplaySettings settings, OutputSettings outputSettings) { var itemList = randomized.ItemList .Where(io => !io.Item.IsFake()) .Select(u => new SpoilerItem(u, ItemUtils.IsRequired(u.Item, randomized), ItemUtils.IsImportant(u.Item, randomized))); var settingsString = settings.ToString(); var directory = Path.GetDirectoryName(outputSettings.OutputROMFilename); var filename = $"{Path.GetFileNameWithoutExtension(outputSettings.OutputROMFilename)}"; var plainTextRegex = new Regex("[^a-zA-Z0-9' .\\-]+"); Spoiler spoiler = new Spoiler() { Version = Randomizer.AssemblyVersion, SettingsString = settingsString, Seed = randomized.Seed, RandomizeDungeonEntrances = settings.RandomizeDungeonEntrances, ItemList = itemList.Where(u => !u.Item.IsFake()).ToList(), NewDestinationIndices = randomized.NewDestinationIndices, Logic = randomized.Logic, GossipHints = randomized.GossipQuotes?.ToDictionary(me => (GossipQuote)me.Id, (me) => { var message = me.Message.Substring(1); var soundEffect = message.Substring(0, 2); message = message.Substring(2); if (soundEffect == "\x69\x0C") { // real } else if (soundEffect == "\x69\x0A") { // fake message = "FAKE - " + message; } else { // junk message = "JUNK - " + message; } return(plainTextRegex.Replace(message.Replace("\x11", " "), "")); }), }; if (outputSettings.GenerateHTMLLog) { using (StreamWriter newlog = new StreamWriter(Path.Combine(directory, filename + "_Tracker.html"))) { Templates.HtmlSpoiler htmlspoiler = new Templates.HtmlSpoiler(spoiler); newlog.Write(htmlspoiler.TransformText()); } } if (outputSettings.GenerateSpoilerLog) { CreateTextSpoilerLog(spoiler, Path.Combine(directory, filename + "_SpoilerLog.txt")); } }
/// <summary> /// Get all junk items which are replaceable by ice traps. /// </summary> /// <param name="itemList">Source items</param> /// <param name="onslaught">Whether or not non-randomized junk items should be selected as well</param> /// <returns>Replaceable junk items.</returns> public static ItemObject[] GetJunkItems(ItemList itemList, bool onslaught = false) { // Use JunkItem list, but exclude Heart Pieces and Heart Containers. return(itemList.FindAll(x => { return (x.IsRandomized || onslaught) && x.NewLocation != null && ItemUtils.IsJunk(x.Item) && !x.Item.IsFake() && !ItemUtils.IsStartingLocation(x.NewLocation.Value); }).ToArray()); }
private static void InitGetBottleList() { RomData.BottleList = new Dictionary <int, BottleCatchEntry>(); int f = RomUtils.GetFileIndexForWriting(BOTTLE_CATCH_TABLE); int baseaddr = BOTTLE_CATCH_TABLE - RomData.MMFileList[f].Addr; var fileData = RomData.MMFileList[f].Data; foreach (var getBottleItemIndex in ItemUtils.AllGetBottleItemIndices()) { int offset = getBottleItemIndex * 6 + baseaddr; RomData.BottleList[getBottleItemIndex] = new BottleCatchEntry { ItemGained = fileData[offset + 3], Index = fileData[offset + 4], Message = fileData[offset + 5] }; } }
private static void InitGetItemList() { RomData.GetItemList = new Dictionary <int, GetItemEntry>(); int f = RomUtils.GetFileIndexForWriting(GET_ITEM_TABLE); int baseaddr = GET_ITEM_TABLE - RomData.MMFileList[f].Addr; var fileData = RomData.MMFileList[f].Data; foreach (var getItemIndex in ItemUtils.AllGetItemIndices()) { int offset = (getItemIndex - 1) * 8 + baseaddr; RomData.GetItemList[getItemIndex] = new GetItemEntry { ItemGained = fileData[offset], Flag = fileData[offset + 1], Index = fileData[offset + 2], Type = fileData[offset + 3], Message = (short)((fileData[offset + 4] << 8) | fileData[offset + 5]), Object = (short)((fileData[offset + 6] << 8) | fileData[offset + 7]) }; } }
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 void CreateSpoilerLog(RandomizedResult randomized, GameplaySettings settings, OutputSettings outputSettings) { var itemList = randomized.ItemList .Where(io => (io.IsRandomized && io.NewLocation.Value.Region().HasValue) || (io.Item.MainLocation().HasValue&& randomized.ItemList[io.Item.MainLocation().Value].IsRandomized)) .Select(io => new { ItemObject = io.Item.MainLocation().HasValue ? randomized.ItemList.Find(x => x.NewLocation == io.Item.MainLocation().Value) : io, LocationForImportance = io.NewLocation ?? io.Item, Region = io.IsRandomized ? io.NewLocation.Value.Region().Value : io.Item.Region().Value, }) .Select(u => new SpoilerItem( u.ItemObject, u.Region, ItemUtils.IsRequired(u.ItemObject.Item, u.LocationForImportance, randomized), ItemUtils.IsImportant(u.ItemObject.Item, u.LocationForImportance, randomized), randomized.ImportantSongLocations.Contains(u.LocationForImportance), settings.ProgressiveUpgrades )); randomized.Logic.ForEach((il) => { if (il.ItemId >= 0) { var io = randomized.ItemList[il.ItemId]; il.IsFakeItem = (io.Item.IsFake() && io.Item.Entrance() == null) || !io.IsRandomized; } }); Dictionary <Item, Item> dungeonEntrances = new Dictionary <Item, Item>(); if (settings.RandomizeDungeonEntrances) { var entrances = new List <Item> { Item.AreaWoodFallTempleAccess, Item.AreaSnowheadTempleAccess, Item.AreaGreatBayTempleAccess, Item.AreaInvertedStoneTowerTempleAccess, }; foreach (var entrance in entrances.OrderBy(e => entrances.IndexOf(randomized.ItemList[e].NewLocation.Value))) { dungeonEntrances.Add(randomized.ItemList[entrance].NewLocation.Value, entrance); } } var settingsString = settings.ToString(); var directory = Path.GetDirectoryName(outputSettings.OutputROMFilename); var filename = $"{Path.GetFileNameWithoutExtension(outputSettings.OutputROMFilename)}"; var plainTextRegex = new Regex("[^a-zA-Z0-9' .\\-]+"); Spoiler spoiler = new Spoiler() { Version = Randomizer.AssemblyVersion, SettingsString = settingsString, Seed = randomized.Seed, DungeonEntrances = dungeonEntrances, ItemList = itemList.ToList(), Logic = randomized.Logic, GossipHints = randomized.GossipQuotes?.ToDictionary(me => (GossipQuote)me.Id, (me) => { var message = me.Message.Substring(1); var soundEffect = message.Substring(0, 2); message = message.Substring(2); if (soundEffect == "\x69\x0C") { // real } else if (soundEffect == "\x69\x0A") { // fake message = "FAKE - " + message; } else { // junk message = "JUNK - " + message; } return(plainTextRegex.Replace(message.Replace("\x11", " "), "")); }), MessageCosts = randomized.MessageCosts.Select((mc, i) => { if (!mc.HasValue) { return(((string, ushort)?)null); } var messageCost = MessageCost.MessageCosts[i]; var name = messageCost.Name; if (string.IsNullOrWhiteSpace(name)) { if (messageCost.LocationsAffected.Count > 0) { var location = messageCost.LocationsAffected[0]; var mainLocation = location.MainLocation(); if (mainLocation.HasValue) { name = $"{mainLocation.Value.Location()} ({location.ToString().Replace(mainLocation.Value.ToString(), "")})"; } else { name = location.Location(); } } else { name = $"Message Cost [{i}]"; } } return(name, mc.Value); }).Where(mc => mc != null).Select(mc => mc.Value).ToList(), }; if (outputSettings.GenerateHTMLLog) { using (StreamWriter newlog = new StreamWriter(Path.Combine(directory, filename + "_Tracker.html"))) { Templates.HtmlSpoiler htmlspoiler = new Templates.HtmlSpoiler(spoiler); newlog.Write(htmlspoiler.TransformText()); } } if (outputSettings.GenerateSpoilerLog) { CreateTextSpoilerLog(spoiler, Path.Combine(directory, filename + "_SpoilerLog.txt")); } }
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(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 void CreateSpoilerLog(RandomizedResult randomized, GameplaySettings settings, OutputSettings outputSettings) { var itemList = randomized.ItemList .Where(io => !io.Item.IsFake() && io.NewLocation.HasValue) .Select(u => new SpoilerItem(u, ItemUtils.IsRequired(u.Item, randomized), ItemUtils.IsImportant(u.Item, randomized), settings.ProgressiveUpgrades)); Dictionary <Item, Item> dungeonEntrances = new Dictionary <Item, Item>(); if (settings.RandomizeDungeonEntrances) { var entrances = new List <Item> { Item.AreaWoodFallTempleAccess, Item.AreaSnowheadTempleAccess, Item.AreaGreatBayTempleAccess, Item.AreaInvertedStoneTowerTempleAccess, }; foreach (var entrance in entrances.OrderBy(e => entrances.IndexOf(randomized.ItemList[e].NewLocation.Value))) { dungeonEntrances.Add(randomized.ItemList[entrance].NewLocation.Value, entrance); } } var settingsString = settings.ToString(); var directory = Path.GetDirectoryName(outputSettings.OutputROMFilename); var filename = $"{Path.GetFileNameWithoutExtension(outputSettings.OutputROMFilename)}"; var plainTextRegex = new Regex("[^a-zA-Z0-9' .\\-]+"); Spoiler spoiler = new Spoiler() { Version = Randomizer.AssemblyVersion, SettingsString = settingsString, Seed = randomized.Seed, DungeonEntrances = dungeonEntrances, ItemList = itemList.ToList(), Logic = randomized.Logic, GossipHints = randomized.GossipQuotes?.ToDictionary(me => (GossipQuote)me.Id, (me) => { var message = me.Message.Substring(1); var soundEffect = message.Substring(0, 2); message = message.Substring(2); if (soundEffect == "\x69\x0C") { // real } else if (soundEffect == "\x69\x0A") { // fake message = "FAKE - " + message; } else { // junk message = "JUNK - " + message; } return(plainTextRegex.Replace(message.Replace("\x11", " "), "")); }), }; if (outputSettings.GenerateHTMLLog) { using (StreamWriter newlog = new StreamWriter(Path.Combine(directory, filename + "_Tracker.html"))) { Templates.HtmlSpoiler htmlspoiler = new Templates.HtmlSpoiler(spoiler); newlog.Write(htmlspoiler.TransformText()); } } if (outputSettings.GenerateSpoilerLog) { CreateTextSpoilerLog(spoiler, Path.Combine(directory, filename + "_SpoilerLog.txt")); } }
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); }
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); }