Пример #1
0
        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"));
            }
        }
Пример #2
0
 /// <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());
 }
Пример #3
0
        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]
                };
            }
        }
Пример #4
0
        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])
                };
            }
        }
Пример #5
0
        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);
        }
Пример #6
0
        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);
        }
Пример #7
0
        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"));
            }
        }
Пример #8
0
        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);
        }
Пример #9
0
        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);
        }
Пример #10
0
        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"));
            }
        }
Пример #11
0
        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);
        }
Пример #12
0
        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);
        }