Exemplo n.º 1
0
        public static void Work(IItemPlacementFlags flags, List <Item> treasurePool, MT19337 rng)
        {
            int count = treasurePool.Count;

            var consumableChestSet    = ConsumableChestSets[flags.MoreConsumableChests];
            var extConsumableChestSet = flags.ExtConsumableSet != ExtConsumableSet.None ? ExtConsumableChestSets[flags.ExtConsumableChests] : ExtConsumableChestSets[ExtConsumableChestSet.None];

            if (flags.MoreConsumableChests == ConsumableChestSet.Random || flags.MoreConsumableChests == ConsumableChestSet.RandomLow)
            {
                consumableChestSet =
                    (
                        Tents : rng.Between(0, consumableChestSet.Tents),
                        Cabins : rng.Between(0, consumableChestSet.Cabins),
                        Houses : rng.Between(0, consumableChestSet.Houses),
                        Heals : rng.Between(0, consumableChestSet.Heals),
                        Pures : rng.Between(0, consumableChestSet.Pures),
                        Softs : rng.Between(0, consumableChestSet.Softs)
                    );
            }

            if (flags.ExtConsumableSet != ExtConsumableSet.None && (flags.ExtConsumableChests == ExtConsumableChestSet.Random || flags.ExtConsumableChests == ExtConsumableChestSet.RandomLow))
            {
                extConsumableChestSet =
                    (
                        WoodenNunchucks : rng.Between(0, extConsumableChestSet.WoodenNunchucks),
                        SmallKnives : rng.Between(0, extConsumableChestSet.SmallKnives),
                        WoodenRods : rng.Between(0, extConsumableChestSet.WoodenRods),
                        Rapiers : rng.Between(0, extConsumableChestSet.Rapiers)
                    );
            }

            int requestedchests = consumableChestSet.Tents + consumableChestSet.Cabins + consumableChestSet.Houses + consumableChestSet.Heals + consumableChestSet.Pures + consumableChestSet.Softs + extConsumableChestSet.WoodenNunchucks + extConsumableChestSet.SmallKnives + extConsumableChestSet.WoodenRods + extConsumableChestSet.Rapiers;

            int removedchests = 0;

            RemoveConsumableChests(flags, treasurePool, ref removedchests);
            RemoveGoldChests(treasurePool, requestedchests, ref removedchests);

            if (flags.ExtConsumableSet != ExtConsumableSet.None)
            {
                AddConsumableChests(treasurePool, extConsumableChestSet.WoodenNunchucks, Item.WoodenNunchucks, ref removedchests);
                AddConsumableChests(treasurePool, extConsumableChestSet.SmallKnives, Item.SmallKnife, ref removedchests);
                AddConsumableChests(treasurePool, extConsumableChestSet.WoodenRods, Item.WoodenRod, ref removedchests);
                AddConsumableChests(treasurePool, extConsumableChestSet.Rapiers, Item.Rapier, ref removedchests);
            }

            AddConsumableChests(treasurePool, consumableChestSet.Tents, Item.Tent, ref removedchests);
            AddConsumableChests(treasurePool, consumableChestSet.Cabins, Item.Cabin, ref removedchests);
            AddConsumableChests(treasurePool, consumableChestSet.Houses, Item.House, ref removedchests);
            AddConsumableChests(treasurePool, consumableChestSet.Heals, Item.Heal, ref removedchests);
            AddConsumableChests(treasurePool, consumableChestSet.Pures, Item.Pure, ref removedchests);
            AddConsumableChests(treasurePool, consumableChestSet.Softs, Item.Soft, ref removedchests);

            AddGoldChests(treasurePool, removedchests);

            if (treasurePool.Count != count)
            {
                throw new Exception("Sorry, I f****d it up!");
            }
        }
Exemplo n.º 2
0
        public static readonly List <int> UsedTreasureIndices = Enumerable.Range(0, 256).Except(UnusedTreasureIndices).ToList();        // This maps a compacted list back to the game's array, skipping the unused slots.

        public List <IRewardSource> ShuffleTreasures(MT19337 rng,
                                                     IItemPlacementFlags flags,
                                                     IncentiveData incentivesData,
                                                     ItemShopSlot caravanItemLocation,
                                                     Dictionary <MapLocation, List <MapChange> > mapLocationRequirements)
        {
            var vanillaNPCs = !flags.NPCItems && !flags.NPCFetchItems;

            if (!vanillaNPCs)
            {
                EnableBridgeShipCanalAnywhere();
                EnableNPCsGiveAnyItem();
                // This extends Vampire's routine to set a flag for Sarda, but it also clobers Sarda's routine
                if (!flags.EarlySarda)
                {
                    Put(0x393E1, Blob.FromHex("207F90A51160"));
                }
            }

            var treasureBlob = Get(TreasureOffset, TreasureSize * TreasureCount);
            var treasurePool = UsedTreasureIndices.Select(x => (Item)treasureBlob[x])
                               .Concat(ItemLists.AllNonTreasureChestItems).ToList();

            if (flags.ShardHunt)
            {
                treasurePool = treasurePool.Select(ShardHuntTreasureSelector).ToList();
                int shardsAdded = treasurePool.Count(item => item == Item.Shard);
                Debug.Assert(shardsAdded == TotalOrbsToInsert);
            }

            var placedItems =
                ItemPlacement.PlaceSaneItems(rng,
                                             flags,
                                             incentivesData,
                                             treasurePool,
                                             caravanItemLocation,
                                             mapLocationRequirements);

            if (flags.FreeBridge)
            {
                placedItems = placedItems.Select(x => x.Item != Item.Bridge ? x : ItemPlacement.NewItemPlacement(x, ReplacementItem)).ToList();
            }
            if (flags.FreeAirship)
            {
                placedItems = placedItems.Select(x => x.Item != Item.Floater ? x : ItemPlacement.NewItemPlacement(x, ReplacementItem)).ToList();
            }

            // Output the results to the ROM
            foreach (var item in placedItems.Where(x => !x.IsUnused && x.Address < 0x80000 && (!vanillaNPCs || x is TreasureChest)))
            {
                //Debug.WriteLine(item.SpoilerText);
                item.Put(this);
            }

            MoveShipToRewardSource(placedItems.Find(reward => reward.Item == Item.Ship));
            return(placedItems);
        }
Exemplo n.º 3
0
        public static readonly List <int> UsedTreasureIndices = Enumerable.Range(0, 256).Except(UnusedTreasureIndices).ToList();        // This maps a compacted list back to the game's array, skipping the unused slots.

        public List <IRewardSource> ShuffleTreasures(MT19337 rng,
                                                     IItemPlacementFlags flags,
                                                     IncentiveData incentivesData,
                                                     ItemShopSlot caravanItemLocation,
                                                     OverworldMap overworldMap,
                                                     TeleportShuffle teleporters)
        {
            Dictionary <MapLocation, Tuple <List <MapChange>, AccessRequirement> > fullFloorRequirements = overworldMap.FullLocationRequirements;
            Dictionary <MapLocation, OverworldTeleportIndex> overridenOverworld = overworldMap.OverriddenOverworldLocations;

            var vanillaNPCs = !(flags.NPCItems ?? false) && !(flags.NPCFetchItems ?? false);

            if (!vanillaNPCs)
            {
                EnableBridgeShipCanalAnywhere();
                EnableNPCsGiveAnyItem();
                // This extends Vampire's routine to set a flag for Sarda, but it also clobers Sarda's routine
                if (!(bool)flags.EarlySarda)
                {
                    Put(0x393E1, Blob.FromHex("207F90A51160"));
                }
            }

            var treasureBlob = Get(TreasureOffset, TreasureSize * TreasureCount);
            var treasurePool = UsedTreasureIndices.Select(x => (Item)treasureBlob[x])
                               .Concat(ItemLists.AllNonTreasureChestItems).ToList();

            if (flags.ShardHunt)
            {
                treasurePool = treasurePool.Select(ShardHuntTreasureSelector).ToList();
                int shardsAdded = treasurePool.Count(item => item == Item.Shard);
                Debug.Assert(shardsAdded == TotalOrbsToInsert);
            }

            ItemPlacement placement   = ItemPlacement.Create(flags, incentivesData, treasurePool, caravanItemLocation, overworldMap);
            var           placedItems = placement.PlaceSaneItems(rng);

            // Output the results to the ROM
            foreach (var item in placedItems.Where(x => !x.IsUnused && x.Address < 0x80000 && (!vanillaNPCs || x is TreasureChest)))
            {
                //Debug.WriteLine(item.SpoilerText);
                item.Put(this);
            }
            // Move the ship someplace closer to where it really ends up.
            if (!(flags.FreeShip ?? false))
            {
                MapLocation shipLocation = placedItems.Find(reward => reward.Item == Item.Ship).MapLocation;
                if (overridenOverworld != null && overridenOverworld.TryGetValue(shipLocation, out var overworldIndex))
                {
                    shipLocation = teleporters.OverworldMapLocations.TryGetValue(overworldIndex, out var vanillaShipLocation) ? vanillaShipLocation : shipLocation;
                }
                MoveShipToRewardSource(shipLocation);
            }
            return(placedItems);
        }
Exemplo n.º 4
0
        public static ItemPlacement Create(IItemPlacementFlags flags, IncentiveData incentivesData, List <Item> allTreasures, ItemShopSlot caravanItemLocation, OverworldMap overworldMap)
        {
            ItemPlacement placement;

            placement = new GuidedItemPlacement();

            placement._flags               = flags;
            placement._incentivesData      = incentivesData;
            placement._allTreasures        = allTreasures;
            placement._caravanItemLocation = caravanItemLocation;
            placement._overworldMap        = overworldMap;

            return(placement);
        }
        public static ItemPlacement Create(IItemPlacementFlags flags, IncentiveData incentivesData, List <Item> allTreasures, ItemShopSlot caravanItemLocation, OverworldMap overworldMap, ISanityChecker checker)
        {
            ItemPlacement placement;

            placement = flags.PredictivePlacement && checker is SanityCheckerV2 ? new PredictivePlacement() : new GuidedItemPlacement();

            placement._flags               = flags;
            placement._incentivesData      = incentivesData;
            placement._allTreasures        = allTreasures;
            placement._caravanItemLocation = caravanItemLocation;
            placement._overworldMap        = overworldMap;
            placement._checker             = checker;

            return(placement);
        }
        public static void Work(IItemPlacementFlags flags, List <Item> treasurePool, MT19337 rng)
        {
            //var x = treasurePool.GroupBy(i => i).Select(g => (g.Key, g.Count())).OrderBy(g => (int)g.Key).ToList();

            if (flags.MoreConsumableChests == ConsumableChestSet.Vanilla)
            {
                return;
            }

            int count = treasurePool.Count;

            var consumableChestSet = ConsumableChestSets[flags.MoreConsumableChests];

            if (flags.MoreConsumableChests == ConsumableChestSet.Random || flags.MoreConsumableChests == ConsumableChestSet.RandomLow)
            {
                consumableChestSet =
                    (
                        Tents : rng.Between(0, consumableChestSet.Tents),
                        Cabins : rng.Between(0, consumableChestSet.Cabins),
                        Houses : rng.Between(0, consumableChestSet.Houses),
                        Heals : rng.Between(0, consumableChestSet.Heals),
                        Pures : rng.Between(0, consumableChestSet.Pures),
                        Softs : rng.Between(0, consumableChestSet.Softs)
                    );
            }

            int requestedchests = consumableChestSet.Tents + consumableChestSet.Cabins + consumableChestSet.Houses + consumableChestSet.Heals + consumableChestSet.Pures + consumableChestSet.Softs;

            int removedchests = 0;

            RemoveConsumableChests(treasurePool, ref removedchests);
            RemoveGoldChests(treasurePool, requestedchests, ref removedchests);

            AddConsumableChests(treasurePool, consumableChestSet.Tents, Item.Tent, ref removedchests);
            AddConsumableChests(treasurePool, consumableChestSet.Cabins, Item.Cabin, ref removedchests);
            AddConsumableChests(treasurePool, consumableChestSet.Houses, Item.House, ref removedchests);
            AddConsumableChests(treasurePool, consumableChestSet.Heals, Item.Heal, ref removedchests);
            AddConsumableChests(treasurePool, consumableChestSet.Pures, Item.Pure, ref removedchests);
            AddConsumableChests(treasurePool, consumableChestSet.Softs, Item.Soft, ref removedchests);

            AddGoldChests(treasurePool, removedchests);

            if (treasurePool.Count != count)
            {
                throw new Exception("Sorry, I f****d it up!");
            }
        }
Exemplo n.º 7
0
        private static void RemoveConsumableChests(IItemPlacementFlags flags, List <Item> treasurePool, ref int removedchests)
        {
            var consumableChests = treasurePool.Where(i => i == Item.Tent || i == Item.Cabin || i == Item.House || i == Item.Heal || i == Item.Pure || i == Item.Soft).ToList();

            if (flags.ExtConsumableSet != ExtConsumableSet.None)
            {
                consumableChests.Add(Item.WoodenNunchucks);
                consumableChests.Add(Item.SmallKnife);
                consumableChests.Add(Item.WoodenRod);
                consumableChests.Add(Item.Rapier);
            }

            foreach (var item in consumableChests)
            {
                if (treasurePool.Remove(item))
                {
                    removedchests++;
                }
            }
        }
Exemplo n.º 8
0
        public static ItemPlacement Create(IItemPlacementFlags flags, IncentiveData incentivesData, List <Item> allTreasures, ItemShopSlot caravanItemLocation, OverworldMap overworldMap)
        {
            ItemPlacement placement;

            if (flags.AllowObsoleteVehicles)
            {
                placement = new RandomItemPlacement();
            }
            else
            {
                placement = new GuidedItemPlacement();
            };

            placement._flags               = flags;
            placement._incentivesData      = incentivesData;
            placement._allTreasures        = allTreasures;
            placement._caravanItemLocation = caravanItemLocation;
            placement._overworldMap        = overworldMap;

            return(placement);
        }
Exemplo n.º 9
0
        public static List <IRewardSource> PlaceSaneItems(MT19337 rng,
                                                          IItemPlacementFlags flags,
                                                          IncentiveData incentivesData,
                                                          List <Item> allTreasures,
                                                          ItemShopSlot caravanItemLocation,
                                                          Dictionary <MapLocation, List <MapChange> > mapLocationRequirements)
        {
            long sanityCounter = 0;
            List <IRewardSource> placedItems;

            var canoeObsoletesBridge  = flags.MapCanalBridge && flags.MapConeriaDwarves;
            var canoeObsoletesShip    = flags.MapCanalBridge ? (flags.MapConeriaDwarves || flags.MapVolcanoIceRiver) : (flags.MapConeriaDwarves && flags.MapVolcanoIceRiver);
            var incentiveLocationPool = incentivesData.IncentiveLocations.ToList();
            var incentivePool         = incentivesData.IncentiveItems.Where(x => allTreasures.Contains(x)).ToList();
            var forcedItems           = incentivesData.ForcedItemPlacements.ToList();
            var keyLocations          = incentivesData.KeyLocations.ToList();
            var canoeLocations        = incentivesData.CanoeLocations.ToList();
            var bridgeLocations       = incentivesData.BridgeLocations.ToList();
            var shipLocations         = incentivesData.ShipLocations.ToList();
            var itemLocationPool      = incentivesData.AllValidItemLocations.ToList();
            var startingMapLocations  = mapLocationRequirements.Where(x => x.Value.Any(y => y == MapChange.None)).Select(x => x.Key);
            var earlyMapLocations     = mapLocationRequirements.Where(x => x.Value.Any(y => MapChange.Bridge.HasFlag(y))).Select(x => x.Key);

            var unincentivizedQuestItems =
                ItemLists.AllQuestItems
                .Where(x => !incentivePool.Contains(x) &&
                       allTreasures.Contains(x) &&
                       !forcedItems.Any(y => y.Item == x))
                .ToList();

            var treasurePool = allTreasures.ToList();

            foreach (var incentive in incentivePool)
            {
                treasurePool.Remove(incentive);
            }
            foreach (var placement in forcedItems)
            {
                treasurePool.Remove(placement.Item);
            }
            foreach (var questItem in unincentivizedQuestItems)
            {
                treasurePool.Remove(questItem);
            }

            var itemShopItem = Item.Bottle;

            while (treasurePool.Remove(Item.Shard))
            {
                unincentivizedQuestItems.Add(Item.Shard);
            }
            do
            {
                sanityCounter++;
                if (sanityCounter > 10000)
                {
                    throw new InvalidOperationException("Invalid flag set");
                }
                // 1. (Re)Initialize lists inside of loop
                placedItems = forcedItems.ToList();
                var incentives    = incentivePool.ToList();
                var nonincentives = unincentivizedQuestItems.ToList();
                incentives.Shuffle(rng);
                nonincentives.Shuffle(rng);

                if (flags.NPCItems)
                {
                    // 2. Place caravan item first because among incentive locations it has the smallest set of possible items
                    var validShopIncentives = incentives
                                              .Where(x => x > Item.None && x <= Item.Soft)
                                              .ToList();
                    if (validShopIncentives.Any() && incentiveLocationPool.Any(x => x.Address == ItemLocations.CaravanItemShop1.Address))
                    {
                        itemShopItem = validShopIncentives.PickRandom(rng);
                        incentives.Remove(itemShopItem);
                    }
                    else
                    {
                        itemShopItem = treasurePool.Concat(nonincentives).Where(x => x > Item.None && x <= Item.Soft).ToList().PickRandom(rng);
                        nonincentives.Remove(itemShopItem);
                    }
                    placedItems.Add(new ItemShopSlot(caravanItemLocation, itemShopItem));

                    // 3. Place key and canoe next because among incentive locations these have the greatest initial impact
                    if (incentives.Remove(Item.Key) || nonincentives.Remove(Item.Key))
                    {
                        placedItems.Add(NewItemPlacement(keyLocations.PickRandom(rng), Item.Key));
                    }
                    if (incentives.Remove(Item.Canoe) || nonincentives.Remove(Item.Canoe))
                    {
                        placedItems.Add(NewItemPlacement(canoeLocations.Where(x => !placedItems.Any(y => y.Address == x.Address)).ToList().PickRandom(rng), Item.Canoe));
                    }

                    var startingCanoeAvailable = placedItems.Any(x => x.Item == Item.Canoe && startingMapLocations.Contains(x.MapLocation));
                    var earlyCanoeAvailable    = placedItems.Any(x => x.Item == Item.Canoe && earlyMapLocations.Contains(x.MapLocation));
                    var earlyKeyAvailable      = placedItems.Any(x => x.Item == Item.Key && earlyMapLocations.Contains(x.MapLocation));

                    // 4. Place Bridge and Ship next since the valid location lists are so small, unless canoe is available and map edits are applied
                    if (!earlyCanoeAvailable || !canoeObsoletesShip)
                    {
                        var remainingShipLocations =
                            shipLocations
                            .Where(x => !placedItems.Any(y => y.Address == x.Address) &&
                                   (earlyKeyAvailable || !x.AccessRequirement.HasFlag(AccessRequirement.Key)))
                            .ToList();
                        if (!remainingShipLocations.Any())
                        {
                            continue;
                        }
                        if (incentives.Remove(Item.Ship) && remainingShipLocations.Any(x => incentiveLocationPool.Any(y => y.Address == x.Address)))
                        {
                            remainingShipLocations =
                                remainingShipLocations
                                .Where(x => incentiveLocationPool.Any(y => y.Address == x.Address))
                                .ToList();
                        }
                        nonincentives.Remove(Item.Ship);
                        placedItems.Add(NewItemPlacement(remainingShipLocations.PickRandom(rng), Item.Ship));
                    }

                    var startingShipAvailable = placedItems.Any(x => x.Item == Item.Ship && startingMapLocations.Contains(x.MapLocation));

                    if (!(startingCanoeAvailable && canoeObsoletesBridge) && !startingShipAvailable)
                    {
                        var startingKeyAvailable = earlyKeyAvailable && placedItems.Any(x => x.Item == Item.Key && startingMapLocations.Contains(x.MapLocation));

                        var remainingBridgeLocations =
                            bridgeLocations
                            .Where(x => !placedItems.Any(y => y.Address == x.Address) &&
                                   (startingKeyAvailable || !x.AccessRequirement.HasFlag(AccessRequirement.Key)))
                            .ToList();
                        if (!remainingBridgeLocations.Any())
                        {
                            continue;
                        }
                        if (incentives.Remove(Item.Bridge) && remainingBridgeLocations.Any(x => incentiveLocationPool.Any(y => y.Address == x.Address)))
                        {
                            remainingBridgeLocations =
                                remainingBridgeLocations
                                .Where(x => incentiveLocationPool.Any(y => y.Address == x.Address))
                                .ToList();
                        }
                        nonincentives.Remove(Item.Bridge);
                        placedItems.Add(NewItemPlacement(remainingBridgeLocations.PickRandom(rng), Item.Bridge));
                    }
                }

                // 5. Then place all incentive locations that don't have special logic
                var incentiveLocations = incentiveLocationPool.Where(x => !placedItems.Any(y => y.Address == x.Address) && x.Address != ItemLocations.CaravanItemShop1.Address).ToList();
                incentiveLocations.Shuffle(rng);
                foreach (var incentiveLocation in incentiveLocations)
                {
                    if (!incentives.Any())
                    {
                        break;
                    }
                    placedItems.Add(NewItemPlacement(incentiveLocation, incentives.SpliceRandom(rng)));
                }

                // 6. Then place remanining incentive items and unincentivized quest items in any other chest before ToFR
                var leftoverItems = incentives.Concat(nonincentives).ToList();
                leftoverItems.Remove(itemShopItem);
                leftoverItems.Shuffle(rng);
                var leftoverItemLocations =
                    itemLocationPool
                    .Where(x => !ItemLocations.ToFR.Any(y => y.Address == x.Address) &&
                           !x.IsUnused && !placedItems.Any(y => y.Address == x.Address))
                    .ToList();
                foreach (var leftoverItem in leftoverItems)
                {
                    placedItems.Add(NewItemPlacement(leftoverItemLocations.SpliceRandom(rng), leftoverItem));
                }

                // 7. Check sanity and loop if needed
            } while (!CheckSanity(placedItems, mapLocationRequirements, flags));

            // 8. Place all remaining unincentivized treasures or incentivized non-quest items that weren't placed
            var i = 0;

            itemLocationPool =
                itemLocationPool
                .Where(x => !x.IsUnused && !placedItems.Any(y => y.Address == x.Address))
                .ToList();
            foreach (var placedItem in placedItems)
            {
                incentivePool.Remove(placedItem.Item);
            }
            foreach (var unplacedIncentive in incentivePool)
            {
                treasurePool.Add(unplacedIncentive);
            }
            treasurePool.Shuffle(rng);
            itemLocationPool.Shuffle(rng);
            foreach (var remainingTreasure in itemLocationPool)
            {
                placedItems.Add(NewItemPlacement(remainingTreasure, treasurePool[i]));
                i++;
            }

            //Debug.WriteLine($"Sanity Check Fails: {sanityCounter}");
            return(placedItems);
        }
Exemplo n.º 10
0
        public static void RunStats(MT19337 rng,
                                    IItemPlacementFlags flags,
                                    IncentiveData incentivesData,
                                    ItemShopSlot caravanItemLocation,
                                    OverworldMap overworldMap)
        {
            Dictionary <MapLocation, Tuple <List <MapChange>, AccessRequirement> > fullLocationRequirements = overworldMap.FullLocationRequirements;
            var placedItems  = new List <IRewardSource>();
            var treasurePool = ItemLocations.AllTreasures.Where(x => !x.IsUnused).Select(x => x.Item)
                               .Concat(ItemLists.AllNonTreasureChestItems).ToList();
            var requirementChecks = ItemLists.AllQuestItems.ToDictionary(x => x, x => 0);

            requirementChecks[Item.Ribbon] = 0;
            var requirementsToCheck = new List <Item> {
                Item.Crown, Item.Crystal, Item.Herb, Item.Tnt,
                Item.Adamant, Item.Slab, Item.Ruby, Item.Bottle,
                Item.Floater, Item.Ship, Item.Bridge, Item.Canal
            };
            const int maxIterations      = 255;
            var       forcedIceCount     = 0;
            var       itemPlacementStats = ItemLists.AllQuestItems.ToDictionary(x => x, x => new List <int>());

            itemPlacementStats[Item.Ribbon] = new List <int>();
            var itemPlacementZones = ItemLists.AllQuestItems.ToDictionary(x => x, x => new List <string>());

            itemPlacementZones[Item.Ribbon] = new List <string>();
            long iterations  = 0;
            var  timeElapsed = new Stopwatch();

            while (iterations < maxIterations)
            {
                iterations++;
                // When Enemy Status Shuffle is turned on Coneria is reduced to 11% chance with other shops spliting the remaining 89%
                var shopTownSelected = ItemShops.PickRandom(rng);
                var itemShopItem     = new ItemShopSlot(ItemLocations.CaravanItemShop1.Address,
                                                        $"{Enum.GetName(typeof(MapLocation), shopTownSelected)}Shop",
                                                        shopTownSelected,
                                                        Item.Bottle);
                timeElapsed.Start();
                placedItems = ItemPlacement.PlaceSaneItems(rng,
                                                           flags,
                                                           incentivesData,
                                                           treasurePool,
                                                           itemShopItem,
                                                           overworldMap);
                timeElapsed.Stop();

                var outputIndexes = placedItems.ToLookup(x => x.Item, x => x);
                foreach (Item item in itemPlacementStats.Keys)
                {
                    itemPlacementStats[item].AddRange(outputIndexes[item].Select(x => x.Address).ToList());
                }
                var outputZones =
                    placedItems
                    .ToLookup(x => x.Item,
                              x => Enum.GetName(typeof(MapLocation), x.MapLocation));
                foreach (Item item in itemPlacementZones.Keys)
                {
                    if (!outputZones[item].Any())
                    {
                        continue;
                    }
                    itemPlacementZones[item].AddRange(outputZones[item]);
                }
                var matoyaShip     = placedItems.Any(x => x.Address == ItemLocations.Matoya.Address && x.Item == Item.Ship);
                var crystalIceCave = placedItems.Any(x => x.Item == Item.Crystal &&
                                                     x.Address >= ItemLocations.IceCave1.Address &&
                                                     x.Address <= ItemLocations.IceCaveMajor.Address);
                var keyIceCave = placedItems.Any(x => x.Item == Item.Key &&
                                                 x.Address >= ItemLocations.IceCave1.Address &&
                                                 x.Address <= ItemLocations.IceCaveMajor.Address);
                var keyLockedCrystal = placedItems.Any(x => x.Item == Item.Crystal &&
                                                       x.AccessRequirement.HasFlag(AccessRequirement.Key));
                var keyLockedShip = placedItems.Any(x => x.Item == Item.Ship &&
                                                    x.AccessRequirement.HasFlag(AccessRequirement.Key));
                if ((keyLockedShip && keyIceCave) ||
                    (matoyaShip && crystalIceCave) ||
                    (matoyaShip && keyLockedCrystal && keyIceCave))
                {
                    forcedIceCount++;
                }

                foreach (Item item in requirementsToCheck)
                {
                    if (!ItemPlacement.CheckSanity(placedItems.Where(x => x.Item != item).ToList(), fullLocationRequirements, new Flags {
                        OnlyRequireGameIsBeatable = true
                    }))
                    {
                        requirementChecks[item]++;
                    }
                }
            }

            if (iterations > 10)
            {
                Debug.WriteLine(PrintStats(maxIterations, itemPlacementStats, itemPlacementZones, requirementChecks));
                Debug.WriteLine($"Forced Early Ice Cave for Ship: {forcedIceCount} out of {maxIterations}");
                Debug.WriteLine($"Time per iteration: {1.0 * timeElapsed.ElapsedMilliseconds / iterations}");
            }
        }
Exemplo n.º 11
0
        public static List <IRewardSource> PlaceSaneItems(MT19337 rng,
                                                          IItemPlacementFlags flags,
                                                          IncentiveData incentivesData,
                                                          List <Item> allTreasures,
                                                          ItemShopSlot caravanItemLocation,
                                                          OverworldMap overworldMap)
        {
            Dictionary <MapLocation, Tuple <List <MapChange>, AccessRequirement> > fullLocationRequirements = overworldMap.FullLocationRequirements;
            Dictionary <MapLocation, OverworldTeleportIndex> overridenOverworld = overworldMap.OverriddenOverworldLocations;

            long sanityCounter = 0;
            List <IRewardSource> placedItems;

            var canoeObsoletesBridge    = flags.MapCanalBridge && flags.MapConeriaDwarves;
            var canoeObsoletesShip      = flags.MapCanalBridge ? (flags.MapConeriaDwarves || flags.MapVolcanoIceRiver) : (flags.MapConeriaDwarves && flags.MapVolcanoIceRiver);
            var incentiveLocationPool   = incentivesData.IncentiveLocations.ToList();
            var incentivePool           = incentivesData.IncentiveItems.Where(x => allTreasures.Contains(x)).ToList();
            var forcedItems             = incentivesData.ForcedItemPlacements.ToList();
            var keyLocations            = incentivesData.KeyLocations.ToList();
            var canoeLocations          = incentivesData.CanoeLocations.ToList();
            var bridgeLocations         = incentivesData.BridgeLocations.ToList();
            var shipLocations           = incentivesData.ShipLocations.ToList();
            var itemLocationPool        = incentivesData.AllValidItemLocations.ToList();
            var startingPotentialAccess = overworldMap.StartingPotentialAccess;
            var startingMapLocations    = AccessibleMapLocations(startingPotentialAccess, MapChange.None, fullLocationRequirements);
            var earlyMapLocations       = AccessibleMapLocations(startingPotentialAccess | AccessRequirement.Crystal, MapChange.Bridge, fullLocationRequirements);

            var unincentivizedQuestItems =
                ItemLists.AllQuestItems
                .Where(x => !incentivePool.Contains(x) &&
                       allTreasures.Contains(x) &&
                       !forcedItems.Any(y => y.Item == x))
                .ToList();

            var treasurePool = allTreasures.ToList();

            foreach (var incentive in incentivePool)
            {
                treasurePool.Remove(incentive);
            }
            foreach (var placement in forcedItems)
            {
                treasurePool.Remove(placement.Item);
            }
            foreach (var questItem in unincentivizedQuestItems)
            {
                treasurePool.Remove(questItem);
            }

            var itemShopItem = Item.Bottle;

            while (treasurePool.Remove(Item.Shard))
            {
                unincentivizedQuestItems.Add(Item.Shard);
            }

            // Cache the final unincentivized pool so we can reset it each iteration of the loop.
            IReadOnlyCollection <Item> cachedTreasurePool = new ReadOnlyCollection <Item>(treasurePool.ToList());

            do
            {
                sanityCounter++;
                if (sanityCounter > 500)
                {
                    throw new InsaneException("Sanity Counter exceeds 500 iterations!");
                }
                // 1. (Re)Initialize lists inside of loop
                placedItems = forcedItems.ToList();
                var incentives    = incentivePool.ToList();
                var nonincentives = unincentivizedQuestItems.ToList();
                treasurePool = cachedTreasurePool.ToList();
                incentives.Shuffle(rng);
                nonincentives.Shuffle(rng);

                if (flags.NPCItems)
                {
                    // 2. Place caravan item first because among incentive locations it has the smallest set of possible items
                    var validShopIncentives = incentives
                                              .Where(x => x > Item.None && x <= Item.Soft)
                                              .ToList();
                    if (validShopIncentives.Any() && incentiveLocationPool.Any(x => x.Address == ItemLocations.CaravanItemShop1.Address))
                    {
                        itemShopItem = validShopIncentives.PickRandom(rng);
                        incentives.Remove(itemShopItem);
                    }
                    else
                    {
                        itemShopItem = treasurePool.Concat(nonincentives).Where(x => x > Item.None && x <= Item.Soft && x != Item.Shard).ToList().PickRandom(rng);
                        if (!nonincentives.Remove(itemShopItem))
                        {
                            treasurePool.Remove(itemShopItem);
                        }
                    }
                    placedItems.Add(new ItemShopSlot(caravanItemLocation, itemShopItem));

                    // 3. Place key and canoe next because among incentive locations these have the greatest initial impact
                    if (incentives.Remove(Item.Key) || nonincentives.Remove(Item.Key))
                    {
                        placedItems.Add(NewItemPlacement(keyLocations.PickRandom(rng), Item.Key));
                    }
                    if (incentives.Remove(Item.Canoe) || nonincentives.Remove(Item.Canoe))
                    {
                        placedItems.Add(NewItemPlacement(canoeLocations.Where(x => !placedItems.Any(y => y.Address == x.Address)).ToList().PickRandom(rng), Item.Canoe));
                    }

                    var startingCanoeAvailable =
                        placedItems.Any(x => x.Item == Item.Canoe && startingMapLocations.Contains(x.MapLocation) &&
                                        startingMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation));
                    var earlyCanoeAvailable =
                        placedItems.Any(x => x.Item == Item.Canoe && earlyMapLocations.Contains(x.MapLocation) &&
                                        earlyMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation));
                    var earlyKeyAvailable =
                        placedItems.Any(x => x.Item == Item.Key && earlyMapLocations.Contains(x.MapLocation) &&
                                        earlyMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation));

                    // 4. Place Bridge and Ship next since the valid location lists are so small, unless canoe is available and map edits are applied
                    if (!earlyCanoeAvailable || !canoeObsoletesShip)
                    {
                        var remainingShipLocations =
                            shipLocations
                            .Where(x => !placedItems.Any(y => y.Address == x.Address) &&
                                   (earlyKeyAvailable || !x.AccessRequirement.HasFlag(AccessRequirement.Key)))
                            .ToList();
                        if (!remainingShipLocations.Any())
                        {
                            continue;
                        }
                        if (incentives.Remove(Item.Ship) && remainingShipLocations.Any(x => incentiveLocationPool.Any(y => y.Address == x.Address)))
                        {
                            remainingShipLocations =
                                remainingShipLocations
                                .Where(x => incentiveLocationPool.Any(y => y.Address == x.Address))
                                .ToList();
                        }
                        nonincentives.Remove(Item.Ship);
                        placedItems.Add(NewItemPlacement(remainingShipLocations.PickRandom(rng), Item.Ship));
                    }

                    var startingShipAvailable =
                        placedItems.Any(x => x.Item == Item.Ship && startingMapLocations.Contains(x.MapLocation) &&
                                        startingMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation));

                    if (!(startingCanoeAvailable && canoeObsoletesBridge) && !startingShipAvailable)
                    {
                        var startingKeyAvailable =
                            earlyKeyAvailable && placedItems.Any(x => x.Item == Item.Key && startingMapLocations.Contains(x.MapLocation) &&
                                                                 startingMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation));

                        var remainingBridgeLocations =
                            bridgeLocations
                            .Where(x => !placedItems.Any(y => y.Address == x.Address) &&
                                   (startingKeyAvailable || !x.AccessRequirement.HasFlag(AccessRequirement.Key)))
                            .ToList();
                        if (!remainingBridgeLocations.Any())
                        {
                            continue;
                        }
                        if (incentives.Remove(Item.Bridge) && remainingBridgeLocations.Any(x => incentiveLocationPool.Any(y => y.Address == x.Address)))
                        {
                            remainingBridgeLocations =
                                remainingBridgeLocations
                                .Where(x => incentiveLocationPool.Any(y => y.Address == x.Address))
                                .ToList();
                        }
                        nonincentives.Remove(Item.Bridge);
                        placedItems.Add(NewItemPlacement(remainingBridgeLocations.PickRandom(rng), Item.Bridge));
                    }
                }

                // 5. Then place all incentive locations that don't have special logic
                var incentiveLocations = incentiveLocationPool.Where(x => !placedItems.Any(y => y.Address == x.Address) && x.Address != ItemLocations.CaravanItemShop1.Address).ToList();
                incentiveLocations.Shuffle(rng);
                foreach (var incentiveLocation in incentiveLocations)
                {
                    if (!incentives.Any())
                    {
                        break;
                    }
                    placedItems.Add(NewItemPlacement(incentiveLocation, incentives.SpliceRandom(rng)));
                }

                // 6. Then place remanining incentive items and unincentivized quest items in any other chest before ToFR
                var leftoverItems = incentives.Concat(nonincentives).ToList();
                leftoverItems.Shuffle(rng);
                var leftoverItemLocations =
                    itemLocationPool
                    .Where(x => !ItemLocations.ToFR.Any(y => y.Address == x.Address) &&
                           !x.IsUnused && !placedItems.Any(y => y.Address == x.Address))
                    .ToList();
                foreach (var leftoverItem in leftoverItems)
                {
                    placedItems.Add(NewItemPlacement(leftoverItemLocations.SpliceRandom(rng), leftoverItem));
                }

                // 7. Check sanity and loop if needed
            } while (!CheckSanity(placedItems, fullLocationRequirements, flags));

            if (Debugger.IsAttached)
            {
                Console.WriteLine($"ItemPlacement::PlaceSaneItems required {sanityCounter} iterations.");
                Console.WriteLine("");
                Console.WriteLine("Item     Entrance  ->  Floor  ->  Source                   Requirements");
                Console.WriteLine("------------------------------------------------------------------------------------------");

                var sorted = placedItems.Where(item => item.Item != Item.Shard).ToList();
                sorted.Sort((IRewardSource lhs, IRewardSource rhs) => lhs.Item.ToString().CompareTo(rhs.Item.ToString()));
                sorted.ForEach(item =>
                {
                    if (fullLocationRequirements.TryGetValue(item.MapLocation, out var flr))
                    {
                        var overworldLocation = item.MapLocation.ToString();
                        if (overridenOverworld != null && overridenOverworld.TryGetValue(item.MapLocation, out var overriden))
                        {
                            overworldLocation = overriden.ToString();
                        }

                        var itemStr = item.Item.ToString().PadRight(9);
                        var locStr  = $"{overworldLocation} -> {item.MapLocation} -> {item.Name} ".PadRight(50);
                        var changes = $"{String.Join(" | ", flr.Item1.Select(mapChange => mapChange.ToString()).ToArray())}";
                        var reqs    = flr.Item2.ToString().CompareTo("None") == 0 ? "" : $" AND {flr.Item2.ToString()}";
                        Console.WriteLine($"{itemStr}{locStr}{changes}{reqs}");
                    }
                });
            }

            // 8. Place all remaining unincentivized treasures or incentivized non-quest items that weren't placed
            itemLocationPool = itemLocationPool.Where(x => !x.IsUnused && !placedItems.Any(y => y.Address == x.Address)).ToList();
            foreach (var placedItem in placedItems)
            {
                incentivePool.Remove(placedItem.Item);
            }
            treasurePool.AddRange(incentivePool);

            Debug.Assert(treasurePool.Count() == itemLocationPool.Count());
            treasurePool.Shuffle(rng);
            itemLocationPool.Shuffle(rng);

            var leftovers = treasurePool.Zip(itemLocationPool, (treasure, location) => NewItemPlacement(location, treasure));

            placedItems.AddRange(leftovers);
            return(placedItems);
        }