Exemple #1
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);
        }
Exemple #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,
                                                     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);
        }
Exemple #3
0
        public (bool Complete, List <MapLocation> MapLocations, AccessRequirement Requirements) CheckSanity(List <IRewardSource> _treasurePlacements, Dictionary <MapLocation, Tuple <List <MapChange>, AccessRequirement> > fullLocationRequirements, IVictoryConditionFlags victoryConditions)
        {
            treasurePlacements = _treasurePlacements;

            //kids, don't try this at home. Calculating an index from an address is usually not the way to go.
            chests   = treasurePlacements.Select(r => r as TreasureChest).Where(r => r != null).ToDictionary(r => (byte)(r.Address - 0x3100));
            npcs     = treasurePlacements.Select(r => r as MapObject).Where(r => r != null).ToDictionary(r => r.ObjectId);
            shopslot = (ItemShopSlot)treasurePlacements.FirstOrDefault(r => r is ItemShopSlot);

            var result = Crawl(victoryConditions);

            var mapLocations = result.rewardSources.Select(r => r.MapLocation).Distinct().ToList();

            return(result.complete, mapLocations, result.requirements);
        }
Exemple #4
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,
                                                     ITreasureShuffleFlags flags,
                                                     IncentiveData incentivesData,
                                                     ItemShopSlot caravanItemLocation,
                                                     Dictionary <MapLocation, List <MapChange> > mapLocationRequirements)
        {
            if (flags.NPCItems)
            {
                EnableBridgeShipCanalAnywhere();
                EnableNPCsGiveAnyItem();
                // This extends Vampire's routine to set a flag for Sarda, but it also clobers Sarda's routine
                if (!flags.EarlyRod)
                {
                    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.OrbHunt)
            {
                treasurePool = treasurePool.Select(OrbHuntTreasureSelector).ToList();
                System.Diagnostics.Debug.Assert(treasurePool.Count(item => item == Item.Shard) == TotalOrbsToInsert);
            }

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

            // Output the results tothe ROM
            foreach (var item in placedItems.Where(x => !x.IsUnused && x.Address < 0x80000 && !incentivesData.ForcedItemPlacements.Any(y => y.Address == x.Address)))
            {
                //Debug.WriteLine(item.SpoilerText);
                item.Put(this);
            }
            return(placedItems);
        }
Exemple #5
0
        public SanityCheckerV2(List <Map> _maps, OverworldMap _overworldMap, NPCdata _npcdata, FF1Rom _rom, ItemShopSlot _declaredShopSlot, ShipLocations _shiplocations)
        {
            rom          = _rom;
            overworldMap = _overworldMap;
            maps         = _maps;
            npcdata      = _npcdata;

            locations = new OwLocationData(rom);
            locations.LoadData();

            Shiplocations = _shiplocations;

            allTreasures     = ItemLocations.AllTreasures.Select(r => r as TreasureChest).Where(r => r != null).ToDictionary(r => (byte)(r.Address - 0x3100));
            allQuestNpcs     = ItemLocations.AllNPCItemLocations.Select(r => r as MapObject).Where(r => r != null).ToDictionary(r => r.ObjectId);
            declaredShopSlot = _declaredShopSlot;

            UpdateNpcRequirements();

            Main = new SCMain(_maps, _overworldMap, _npcdata, locations, _rom);
        }
Exemple #6
0
        public ItemShopSlot ShuffleShops(MT19337 rng, bool earlyAilments)
        {
            var pointers = Get(ShopPointerOffset, ShopPointerCount * ShopPointerSize).ToUShorts();

            RepackShops(pointers);

            ShuffleShopType(ShopType.Weapon, pointers, rng);
            ShuffleShopType(ShopType.Armor, pointers, rng);
            ItemShopSlot result = null;

            do
            {
                result = ShuffleShopType(ShopType.Item, pointers, rng);
            } while (earlyAilments && !AilmentsCovered(pointers));
            if (result == null)
            {
                throw new InvalidOperationException("Shop Location for Bottle was not set");
            }

            Put(ShopPointerOffset, Blob.FromUShorts(pointers));
            return(result);
        }
Exemple #7
0
        public ItemShopSlot ShuffleShops(MT19337 rng, bool earlyAilments, bool randomizeWeaponsAndArmor, IEnumerable <Item> excludeItemsFromRandomShops, WorldWealthMode wealth, int coneriaEntranceShopIndex)
        {
            var pointers = Get(ShopPointerOffset, ShopPointerCount * ShopPointerSize).ToUShorts();

            RepackShops(pointers);

            ShuffleShopType(ShopType.Weapon, pointers, rng, randomizeWeaponsAndArmor, excludeItemsFromRandomShops, wealth);
            ShuffleShopType(ShopType.Armor, pointers, rng, randomizeWeaponsAndArmor, excludeItemsFromRandomShops, wealth);
            ItemShopSlot result = null;

            do
            {
                result = ShuffleShopType(ShopType.Item, pointers, rng);
            } while (earlyAilments && !AilmentsCovered(pointers, coneriaEntranceShopIndex));
            if (result == null)
            {
                throw new InvalidOperationException("Shop Location for Bottle was not set");
            }

            Put(ShopPointerOffset, Blob.FromUShorts(pointers));
            return(result);
        }
Exemple #8
0
        private ItemShopSlot ShuffleShopType(ShopType shopType, ushort[] pointers, MT19337 rng, bool randomize = false, IEnumerable <Item> excludeItemsFromRandomShops = null, WorldWealthMode wealth = WorldWealthMode.Normal)
        {
            var shops = GetShops(shopType, pointers);

            bool shopsBlocked;

            List <byte>[] newShops;
            do
            {
                shopsBlocked = false;
                newShops     = new List <byte> [ShopSectionSize];

                var allEntries = shops.SelectMany(list => list).ToList();
                allEntries.Shuffle(rng);

                int entry = 0;
                for (int i = 0; i < ShopSectionSize; i++)
                {
                    newShops[i] = new List <byte>();
                    if (pointers[(int)shopType + i] != ShopNullPointer)
                    {
                        newShops[i].Add(allEntries[entry++]);
                    }
                }

                while (entry < allEntries.Count)
                {
                    var eligibleShops = new List <int>();
                    for (int i = 0; i < newShops.Length; i++)
                    {
                        if (newShops[i].Count > 0 && newShops[i].Count < 5 && !newShops[i].Contains(allEntries[entry]))
                        {
                            eligibleShops.Add(i);
                        }
                    }

                    // We might be unable to place an item in any shop because they're all full, or they already have that item.  Start over.
                    if (eligibleShops.Count == 0)
                    {
                        shopsBlocked = true;
                        break;
                    }

                    int shopIndex = eligibleShops[rng.Between(0, eligibleShops.Count - 1)];
                    newShops[shopIndex].Add(allEntries[entry++]);
                }
            } while (shopsBlocked);

            if (randomize)
            {
                if (shopType == ShopType.Weapon || shopType == ShopType.Armor)
                {
                    // Shuffle up a byte array of random weapons or armor and assign them in place of the existing items.
                    var baseIndex = shopType == ShopType.Armor ? Item.Cloth : Item.WoodenNunchucks;
                    var indeces   = Enumerable.Range((int)baseIndex, 40).Select(i => (Item)i).ToList();
                    foreach (var exclusion in excludeItemsFromRandomShops ?? new List <Item>())
                    {
                        indeces.Remove(exclusion);
                    }

                    ItemGenerator generator = new ItemGenerator(indeces, wealth);
                    for (int i = 0; i < newShops.Length; i++)
                    {
                        newShops[i] = newShops[i].Select(x => (byte)generator.SpliceItem(rng)).ToList();
                    }
                }
            }
            // Zero-terminate the new shops.
            foreach (var newShop in newShops)
            {
                newShop.Add(0);
            }

            ItemShopSlot result  = null;
            var          pointer = pointers[(int)shopType];

            for (int i = 0; i < ShopSectionSize; i++)
            {
                if (newShops[i].Count > 1)
                {
                    var bottle =
                        newShops[i]
                        .Select((item, index) => new { item, index })
                        .FirstOrDefault(x => ((Item)x.item) == Item.Bottle);
                    if (bottle != null)
                    {
                        var location = ShopMapLocationsByIndex[i];
                        result = new ItemShopSlot(ShopPointerBase + pointer + bottle.index,
                                                  $"{Enum.GetName(typeof(MapLocation), location)}Shop{bottle.index + 1}",
                                                  location,
                                                  Item.Bottle);
                    }
                    Put(ShopPointerBase + pointer, newShops[i].ToArray());

                    pointers[(int)shopType + i] = pointer;
                    pointer += (ushort)newShops[i].Count;
                }
            }
            return(result);
        }
Exemple #9
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);
        }
Exemple #10
0
        public static List <IRewardSource> PlaceSaneItems(MT19337 rng,
                                                          ITreasureShuffleFlags flags,
                                                          IncentiveData incentivesData,
                                                          List <Item> allTreasures,
                                                          ItemShopSlot caravanItemLocation,
                                                          Dictionary <MapLocation, List <MapChange> > mapLocationRequirements)
        {
            long sanityCounter = 0;
            List <IRewardSource> placedItems;

            var incentiveLocationPool = incentivesData.IncentiveLocations.ToList();
            var incentivePool         = incentivesData.IncentiveItems.ToList();
            var forcedItems           = incentivesData.ForcedItemPlacements.ToList();
            var bridgeLocations       = incentivesData.BridgeLocations.ToList();
            var shipLocations         = incentivesData.ShipLocations.ToList();
            var itemLocationPool      = incentivesData.AllValidItemLocations.ToList();

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

            var treasurePool = allTreasures.ToList();

            treasurePool.Remove(Item.Bridge);
            treasurePool.Remove(Item.Ship);

            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);
            }
            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();
                incentives.Shuffle(rng);

                if (flags.NPCItems)
                {
                    // 2. Place caravan item first because among incentive locations it has the smallest set of possible items
                    if (!placedItems.Any(x => x.Address == ItemLocations.CaravanItemShop1.Address))
                    {
                        var itemPick            = Item.Bottle;
                        var validShopIncentives = incentives
                                                  .Where(x => x > Item.None && x <= Item.Soft)
                                                  .ToList();
                        if (validShopIncentives.Any())
                        {
                            itemPick = validShopIncentives.PickRandom(rng);
                            incentives.Remove(itemPick);
                        }
                        else if (placedItems.Any(x => x.Item == Item.Bottle))
                        {
                            itemPick = ItemLists.AllConsumables.ToList().PickRandom(rng);
                        }
                        else
                        {
                            treasurePool.Remove(Item.Bottle);
                        }

                        placedItems.Add(new ItemShopSlot(caravanItemLocation, itemPick));
                    }

                    // 3. Place Bridge and Ship next since the valid location lists are so small
                    IRewardSource bridgePlacement = bridgeLocations.PickRandom(rng);
                    placedItems.Add(NewItemPlacement(bridgePlacement, Item.Bridge));

                    var shipPlacement =
                        shipLocations
                        .Where(x => x.Address != bridgePlacement.Address)
                        .ToList().PickRandom(rng);
                    placedItems.Add(NewItemPlacement(shipPlacement, Item.Ship));
                }

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

                // 5. Then place remanining incentive items and unincentivized quest items in any other chest before ToFR
                var leftoverItems = incentives.Concat(unincentivizedQuestItems).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));
                }

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

            // 7. 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);
            foreach (var remainingTreasure in itemLocationPool)
            {
                placedItems.Add(NewItemPlacement(remainingTreasure, treasurePool[i]));
                i++;
            }

            //Debug.WriteLine($"Sanity Check Fails: {sanityCounter}");
            return(placedItems);
        }
 public ItemShopSlot(ItemShopSlot copyFromRewardSource, Item item)
     : base(copyFromRewardSource, item)
 {
     ShopIndex = copyFromRewardSource.ShopIndex;
 }
Exemple #12
0
        // Scale is the geometric scale factor used with RNG.  Multiplier is where we make everything cheaper
        // instead of enemies giving more gold, so we don't overflow.
        public void ScalePrices(IScaleFlags flags, string[] text, MT19337 rng, bool increaseOnly, ItemShopSlot shopItemLocation)
        {
            var scale      = flags.PriceScaleFactor;
            var multiplier = flags.ExpMultiplier;
            var prices     = Get(PriceOffset, PriceSize * PriceCount).ToUShorts();

            for (int i = 0; i < prices.Length; i++)
            {
                var newPrice = Scale(prices[i] / multiplier, scale, 1, rng, increaseOnly);
                prices[i] = (ushort)(flags.WrapPriceOverflow ? ((newPrice - 1) % 0xFFFF) + 1 : Min(newPrice, 0xFFFF));
            }
            var questItemPrice = prices[(int)Item.Bottle];

            // If we don't do this before checking for the item shop location factor, Ribbons and Shirts will end up being really cheap
            // This realistically doesn't matter without Shop Wares shuffle on because nobody wants to sell Ribbons/Shirts, but if it is...
            prices[(int)Item.WhiteShirt] = (ushort)(questItemPrice / 2);
            prices[(int)Item.BlackShirt] = (ushort)(questItemPrice / 2);
            prices[(int)Item.Ribbon]     = questItemPrice;
            var itemShopFactor = new Dictionary <MapLocation, int>()
            {
                { MapLocation.Coneria, 8 },
                { MapLocation.Pravoka, 2 }
            };

            if (itemShopFactor.TryGetValue(shopItemLocation.MapLocation, out int divisor))
            {
                questItemPrice = (ushort)(prices[(int)Item.Bottle] / divisor);
            }
            for (var i = 0; i < (int)Item.Tent; i++)
            {
                prices[i] = questItemPrice;
            }
            Put(PriceOffset, Blob.FromUShorts(prices));

            for (int i = GoldItemOffset; i < GoldItemOffset + GoldItemCount; i++)
            {
                text[i] = prices[i].ToString() + " G";
            }

            var pointers = Get(ShopPointerOffset, ShopPointerCount * ShopPointerSize).ToUShorts();

            RepackShops(pointers);

            for (int i = (int)ShopType.Clinic; i < (int)ShopType.Inn + ShopSectionSize; i++)
            {
                if (pointers[i] != ShopNullPointer)
                {
                    var priceBytes = Get(ShopPointerBase + pointers[i], 2);
                    var priceValue = BitConverter.ToUInt16(priceBytes, 0);

                    priceValue = (ushort)Scale(priceValue / multiplier, scale, 1, rng, increaseOnly);
                    priceBytes = BitConverter.GetBytes(priceValue);
                    Put(ShopPointerBase + pointers[i], priceBytes);
                }
            }
            if (flags.StartingGold)
            {
                var startingGold = BitConverter.ToUInt16(Get(StartingGoldOffset, 2), 0);

                startingGold = (ushort)Min(Scale(startingGold / multiplier, scale, 1, rng, increaseOnly), 0xFFFF);

                Put(StartingGoldOffset, BitConverter.GetBytes(startingGold));
            }
        }
        public IncentiveData(MT19337 rng, IIncentiveFlags flags, OverworldMap map, ItemShopSlot shopSlot, ISanityChecker checker)
        {
            OverworldMap = map;
            _checker     = checker;

            List <(bool, Item)> incentivizedItemsFlags = new() {
                ((bool)flags.IncentivizeBridge, Item.Bridge),
                ((bool)flags.IncentivizeShip, Item.Ship),
                ((bool)flags.IncentivizeCanal, Item.Canal),
                ((bool)flags.IncentivizeLute, Item.Lute),
                ((bool)flags.IncentivizeCrown, Item.Crown),
                ((bool)flags.IncentivizeCrystal, Item.Crystal),
                ((bool)flags.IncentivizeHerb, Item.Herb),
                ((bool)flags.IncentivizeKey, Item.Key),
                ((bool)flags.IncentivizeTnt, Item.Tnt),
                ((bool)flags.IncentivizeAdamant, Item.Adamant),
                ((bool)flags.IncentivizeSlab, Item.Slab),
                ((bool)flags.IncentivizeRuby, Item.Ruby),
                ((bool)flags.IncentivizeRod, Item.Rod),
                ((bool)flags.IncentivizeFloater, Item.Floater),
                ((bool)flags.IncentivizeChime, Item.Chime),
                ((bool)flags.IncentivizePromotion, Item.Tail),
                ((bool)flags.IncentivizeCube, Item.Cube),
                ((bool)flags.IncentivizeBottle, Item.Bottle),
                ((bool)flags.IncentivizeOxyale, Item.Oxyale),
                ((bool)flags.IncentivizeCanoe, Item.Canoe),
                ((bool)flags.IncentivizeXcalber, Item.Xcalber),
                ((bool)flags.IncentivizeMasamune, Item.Masamune),
                ((bool)flags.IncentivizeKatana, Item.Katana),
                ((bool)flags.IncentivizeVorpal, Item.Vorpal),
                ((bool)flags.IncentivizeRibbon, Item.Ribbon),
                ((bool)flags.IncentivizeRibbon2, Item.Ribbon),
                ((bool)flags.IncentivizeOpal, Item.Opal),
                ((bool)flags.Incentivize65K, Item.Gold65000),
                ((bool)flags.IncentivizeBad, Item.Cloth),
                ((bool)flags.IncentivizeDefCastArmor, Item.WhiteShirt),
                ((bool)flags.IncentivizeOffCastArmor, Item.BlackShirt),
                ((bool)flags.IncentivizeOtherCastArmor, Item.PowerGauntlets),
                ((bool)flags.IncentivizePowerRod, Item.PowerRod),
                ((bool)flags.IncentivizeDefCastWeapon, Item.Defense),
                ((bool)flags.IncentivizeOffCastWeapon, Item.ThorHammer),
                ((bool)flags.IncentivizeOtherCastWeapon, Item.BaneSword),
            };

            List <(bool, IRewardSource)> incentivizedNpcsFlags = new() {
                ((bool)flags.IncentivizeKingConeria, ItemLocations.KingConeria),
                ((bool)flags.IncentivizePrincess, ItemLocations.Princess),
                ((bool)flags.IncentivizeMatoya, ItemLocations.Matoya),
                ((bool)flags.IncentivizeBikke, ItemLocations.Bikke),
                ((bool)flags.IncentivizeElfPrince, ItemLocations.ElfPrince),
                ((bool)flags.IncentivizeAstos, ItemLocations.Astos),
                ((bool)flags.IncentivizeNerrick, ItemLocations.Nerrick),
                ((bool)flags.IncentivizeSmith, ItemLocations.Smith),
                ((bool)flags.IncentivizeSarda, ItemLocations.Sarda),
                ((bool)flags.IncentivizeCanoeSage, ItemLocations.CanoeSage),
                ((bool)flags.IncentivizeCubeBot, ItemLocations.CubeBot),
                ((bool)flags.IncentivizeFairy, ItemLocations.Fairy),
                ((bool)flags.IncentivizeLefein, ItemLocations.Lefein),
                ((bool)flags.IncentivizeCaravan, ItemLocations.CaravanItemShop1),
            };

            List <(bool, Item)> removedItemsFlags = new()
            {
                ((bool)flags.NoMasamune, Item.Masamune),
                ((bool)flags.NoXcalber, Item.Xcalber),
                ((bool)flags.NoTail, Item.Tail),
                ((bool)flags.IsFloaterRemoved, Item.Floater),
                ((bool)flags.IsCanoeFree, Item.Canoe),
                ((bool)flags.FreeLute, Item.Lute),
                ((bool)flags.FreeTail, Item.Tail),
                ((bool)flags.IsBridgeFree, Item.Bridge),
                ((bool)flags.IsCanalFree, Item.Canal),
                ((bool)flags.IsShipFree, Item.Ship),
            };

            List <(bool, IRewardSource)> removedNPCItemsLocations = new()
            {
                ((bool)flags.IsCanoeFree, ItemLocations.CanoeSage),
                ((bool)flags.FreeLute, ItemLocations.Princess),
                ((bool)flags.IsBridgeFree, ItemLocations.KingConeria),
                ((bool)flags.IsShipFree, ItemLocations.Bikke),
            };

            List <(bool, IRewardSource)> removedNPCFetchItemsLocations = new()
            {
                ((bool)flags.NoXcalber, ItemLocations.Smith),
                ((bool)flags.IsCanalFree, ItemLocations.Nerrick),
            };

            Dictionary <MapLocation, Tuple <List <MapChange>, AccessRequirement> > fullLocationRequirements = map.FullLocationRequirements;
            var forcedItemPlacements = ItemLocations.AllOtherItemLocations.ToList();

            if (!(flags.NPCItems ?? false))
            {
                forcedItemPlacements.AddRange(ItemLocations.AllNPCFreeItemLocationsExcludingVendor.Except(removedNPCItemsLocations.Where(x => x.Item1 == true).Select(x => x.Item2).ToList()));
                forcedItemPlacements.Add(shopSlot);
            }

            if (!(flags.NPCFetchItems ?? false))
            {
                forcedItemPlacements.AddRange(ItemLocations.AllNPCFetchItemLocations.Except(removedNPCFetchItemsLocations.Where(x => x.Item1 == true).Select(x => x.Item2).ToList()));
            }
            else if (flags.NoOverworld)
            {
                forcedItemPlacements.Add(ItemLocations.Nerrick);
            }

            if ((!flags.Treasures ?? false))
            {
                forcedItemPlacements.AddRange(ItemLocations.AllTreasures);
            }

            if (flags.GuaranteedMasamune ?? false)
            {
                forcedItemPlacements.Add(ItemLocations.ToFRMasmune);
            }

            List <Item> removedItems = removedItemsFlags.Where(x => x.Item1 == true).Select(x => x.Item2).ToList();

            List <Item> incentivePool = incentivizedItemsFlags.Where(x => x.Item1 == true).Select(x => x.Item2).ToList();

            List <IRewardSource> incentiveLocationPool = incentivizedNpcsFlags.Where(x => x.Item1 == true).Select(x => x.Item2).ToList();

            incentiveLocationPool.AddRange(SelectIncentivizedChests(flags, rng));

            var itemLocationPool =
                ItemLocations.AllTreasures.Concat(ItemLocations.AllNPCItemLocations)
                .Where(x => !x.IsUnused && !forcedItemPlacements.Any(y => y.Address == x.Address))
                .ToList();

            if ((bool)flags.EarlyOrdeals)
            {
                forcedItemPlacements =
                    forcedItemPlacements
                    .Select(x => ((x as TreasureChest)?.AccessRequirement.HasFlag(AccessRequirement.Crown) ?? false)
                                                                ? new TreasureChest(x, x.Item, x.AccessRequirement & ~AccessRequirement.Crown)
                                                                : x).ToList();
                itemLocationPool =
                    itemLocationPool
                    .Select(x => ((x as TreasureChest)?.AccessRequirement.HasFlag(AccessRequirement.Crown) ?? false)
                                                                ? new TreasureChest(x, x.Item, x.AccessRequirement & ~AccessRequirement.Crown)
                                                                : x).ToList();
                incentiveLocationPool =
                    incentiveLocationPool
                    .Select(x => ((x as TreasureChest)?.AccessRequirement.HasFlag(AccessRequirement.Crown) ?? false)
                                                        ? new TreasureChest(x, x.Item, x.AccessRequirement & ~AccessRequirement.Crown)
                                                        : x).ToList();
            }
            if ((bool)flags.EarlyKing)
            {
                forcedItemPlacements =
                    forcedItemPlacements
                    .Select(x => x.Address == ItemLocations.KingConeria.Address
                                                                        ? new MapObject(ObjectId.King, MapLocation.ConeriaCastle2, x.Item)
                                                                        : x).ToList();
                itemLocationPool =
                    itemLocationPool
                    .Select(x => x.Address == ItemLocations.KingConeria.Address
                                                                        ? new MapObject(ObjectId.King, MapLocation.ConeriaCastle2, x.Item)
                                                                        : x).ToList();
                incentiveLocationPool =
                    incentiveLocationPool
                    .Select(x => x.Address == ItemLocations.KingConeria.Address
                                                                        ? new MapObject(ObjectId.King, MapLocation.ConeriaCastle2, x.Item)
                                                                        : x).ToList();
            }
            if ((bool)flags.EarlySage)
            {
                forcedItemPlacements =
                    forcedItemPlacements
                    .Select(x => x.Address == ItemLocations.CanoeSage.Address
                                                                        ? new MapObject(ObjectId.CanoeSage, MapLocation.CrescentLake, x.Item)
                                                                        : x).ToList();
                itemLocationPool =
                    itemLocationPool
                    .Select(x => x.Address == ItemLocations.CanoeSage.Address
                                                                        ? new MapObject(ObjectId.CanoeSage, MapLocation.CrescentLake, x.Item)
                                                                        : x).ToList();
                incentiveLocationPool =
                    incentiveLocationPool
                    .Select(x => x.Address == ItemLocations.CanoeSage.Address
                                                                        ? new MapObject(ObjectId.CanoeSage, MapLocation.CrescentLake, x.Item)
                                                                        : x).ToList();
            }
            if ((bool)flags.EarlySarda)
            {
                forcedItemPlacements =
                    forcedItemPlacements
                    .Select(x => x.Address == ItemLocations.Sarda.Address
                                                                ? new MapObject(ObjectId.Sarda, MapLocation.SardasCave, x.Item)
                                                                : x).ToList();
                itemLocationPool =
                    itemLocationPool
                    .Select(x => x.Address == ItemLocations.Sarda.Address
                                                                ? new MapObject(ObjectId.Sarda, MapLocation.SardasCave, x.Item)
                                                                : x).ToList();
                incentiveLocationPool =
                    incentiveLocationPool
                    .Select(x => x.Address == ItemLocations.Sarda.Address
                                                                ? new MapObject(ObjectId.Sarda, MapLocation.SardasCave, x.Item)
                                                                : x).ToList();
            }

            MapLocation elfDoctorLocation = map.ObjectiveNPCs[ObjectId.ElfDoc];

            if (elfDoctorLocation != MapLocation.ElflandCastle)
            {
                forcedItemPlacements =
                    forcedItemPlacements
                    .Select(x => x.Address == ItemLocations.ElfPrince.Address
                                                                ? new MapObject(ObjectId.ElfPrince, MapLocation.ElflandCastle, x.Item, AccessRequirement.Herb, ObjectId.ElfDoc, requiredSecondLocation: elfDoctorLocation)
                                                                : x).ToList();
                itemLocationPool =
                    itemLocationPool
                    .Select(x => x.Address == ItemLocations.ElfPrince.Address
                                                                ? new MapObject(ObjectId.ElfPrince, MapLocation.ElflandCastle, x.Item, AccessRequirement.Herb, ObjectId.ElfDoc, requiredSecondLocation: elfDoctorLocation)
                                                                : x).ToList();
                incentiveLocationPool =
                    incentiveLocationPool
                    .Select(x => x.Address == ItemLocations.ElfPrince.Address
                                                                ? new MapObject(ObjectId.ElfPrince, MapLocation.ElflandCastle, x.Item, AccessRequirement.Herb, ObjectId.ElfDoc, requiredSecondLocation: elfDoctorLocation)
                                                                : x).ToList();
            }

            MapLocation unneLocation = map.ObjectiveNPCs[ObjectId.Unne];

            if (unneLocation != MapLocation.Melmond)
            {
                forcedItemPlacements =
                    forcedItemPlacements
                    .Select(x => x.Address == ItemLocations.Lefein.Address
                                                                ? new MapObject(ObjectId.Lefein, MapLocation.Lefein, x.Item, AccessRequirement.Slab, ObjectId.Unne, requiredSecondLocation: unneLocation)
                                                                : x).ToList();
                itemLocationPool =
                    itemLocationPool
                    .Select(x => x.Address == ItemLocations.Lefein.Address
                                                                ? new MapObject(ObjectId.Lefein, MapLocation.Lefein, x.Item, AccessRequirement.Slab, ObjectId.Unne, requiredSecondLocation: unneLocation)
                                                                : x).ToList();
                incentiveLocationPool =
                    incentiveLocationPool
                    .Select(x => x.Address == ItemLocations.Lefein.Address
                                                                ? new MapObject(ObjectId.Lefein, MapLocation.Lefein, x.Item, AccessRequirement.Slab, ObjectId.Unne, requiredSecondLocation: unneLocation)
                                                                : x).ToList();
            }

            foreach (var item in forcedItemPlacements.Select(x => x.Item))
            {
                if ((bool)flags.GuaranteedMasamune && item == Item.Masamune)
                {
                    continue;
                }
                incentivePool.Remove(item);
            }

            foreach (var item in removedItems)
            {
                incentivePool.Remove(item);
            }

            var nonEndgameMapLocations = _checker.AccessibleMapLocations(~AccessRequirement.BlackOrb, MapChange.All, fullLocationRequirements);

            ForcedItemPlacements = forcedItemPlacements.ToList();
            IncentiveItems       = incentivePool.ToList();

            RemovedItems = removedItems.ToList();

            IncentiveLocations = incentiveLocationPool
                                 .Where(x => !forcedItemPlacements.Any(y => y.Address == x.Address))
                                 .ToList();

            AllValidItemLocations            = itemLocationPool.ToList();
            AllValidPreBlackOrbItemLocations = AllValidItemLocations
                                               .Where(x => nonEndgameMapLocations.Contains(x.MapLocation) && nonEndgameMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation))
                                               .ToList();
        }
Exemple #14
0
        private ItemShopSlot ShuffleShopType(ShopType shopType, ushort[] pointers, MT19337 rng)
        {
            var shops = GetShops(shopType, pointers);

            bool shopsBlocked;

            List <byte>[] newShops;
            do
            {
                shopsBlocked = false;
                newShops     = new List <byte> [ShopSectionSize];

                var allEntries = shops.SelectMany(list => list).ToList();
                allEntries.Shuffle(rng);

                int entry = 0;
                for (int i = 0; i < ShopSectionSize; i++)
                {
                    newShops[i] = new List <byte>();
                    if (pointers[(int)shopType + i] != ShopNullPointer)
                    {
                        newShops[i].Add(allEntries[entry++]);
                    }
                }

                while (entry < allEntries.Count)
                {
                    var eligibleShops = new List <int>();
                    for (int i = 0; i < newShops.Length; i++)
                    {
                        if (newShops[i].Count > 0 && newShops[i].Count < 5 && !newShops[i].Contains(allEntries[entry]))
                        {
                            eligibleShops.Add(i);
                        }
                    }

                    // We might be unable to place an item in any shop because they're all full, or they already have that item.  Start over.
                    if (eligibleShops.Count == 0)
                    {
                        shopsBlocked = true;
                        break;
                    }

                    int shopIndex = eligibleShops[rng.Between(0, eligibleShops.Count - 1)];
                    newShops[shopIndex].Add(allEntries[entry++]);
                }
            } while (shopsBlocked);

            // Zero-terminate the new shops.
            foreach (var newShop in newShops)
            {
                newShop.Add(0);
            }

            ItemShopSlot result  = null;
            var          pointer = pointers[(int)shopType];

            for (int i = 0; i < ShopSectionSize; i++)
            {
                if (newShops[i].Count > 1)
                {
                    var bottle =
                        newShops[i]
                        .Select((item, index) => new { item, index })
                        .FirstOrDefault(x => ((Item)x.item) == Item.Bottle);
                    if (bottle != null)
                    {
                        var location = ShopMapLocationsByIndex[i];
                        result = new ItemShopSlot(ShopPointerBase + pointer + bottle.index,
                                                  $"{Enum.GetName(typeof(MapLocation), location)}Shop{bottle.index + 1}",
                                                  location,
                                                  Item.Bottle);
                    }
                    Put(ShopPointerBase + pointer, newShops[i].ToArray());

                    pointers[(int)shopType + i] = pointer;
                    pointer += (ushort)newShops[i].Count;
                }
            }
            return(result);
        }
        public IncentiveData(MT19337 rng, IIncentiveFlags flags, OverworldMap map, ItemShopSlot shopSlot)
        {
            Dictionary <MapLocation, Tuple <List <MapChange>, AccessRequirement> > fullLocationRequirements = map.FullLocationRequirements;
            var forcedItemPlacements = ItemLocations.AllOtherItemLocations.ToList();

            if (!(flags.NPCItems ?? false))
            {
                forcedItemPlacements.AddRange(ItemLocations.AllNPCFreeItemLocationsExcludingVendor);
                forcedItemPlacements.Add(shopSlot);
            }
            if (!(flags.NPCFetchItems ?? false))
            {
                forcedItemPlacements.AddRange(ItemLocations.AllNPCFetchItemLocations);
            }
            if ((!flags.Treasures ?? false))
            {
                forcedItemPlacements.AddRange(ItemLocations.AllTreasures);
            }
            var incentivePool = new List <Item>();

            if (flags.IncentivizeBridge)
            {
                incentivePool.Add(Item.Bridge);
            }
            if (flags.IncentivizeShip ?? false)
            {
                incentivePool.Add(Item.Ship);
            }
            if (flags.IncentivizeCanal ?? false)
            {
                incentivePool.Add(Item.Canal);
            }
            if (flags.IncentivizeLute ?? false)
            {
                incentivePool.Add(Item.Lute);
            }
            if (flags.IncentivizeCrown ?? false)
            {
                incentivePool.Add(Item.Crown);
            }
            if (flags.IncentivizeCrystal ?? false)
            {
                incentivePool.Add(Item.Crystal);
            }
            if (flags.IncentivizeHerb ?? false)
            {
                incentivePool.Add(Item.Herb);
            }
            if (flags.IncentivizeKey ?? false)
            {
                incentivePool.Add(Item.Key);
            }
            if (flags.IncentivizeTnt ?? false)
            {
                incentivePool.Add(Item.Tnt);
            }
            if (flags.IncentivizeAdamant ?? false)
            {
                incentivePool.Add(Item.Adamant);
            }
            if (flags.IncentivizeSlab ?? false)
            {
                incentivePool.Add(Item.Slab);
            }
            if (flags.IncentivizeRuby ?? false)
            {
                incentivePool.Add(Item.Ruby);
            }
            if (flags.IncentivizeRod ?? false)
            {
                incentivePool.Add(Item.Rod);
            }
            if (flags.IncentivizeFloater ?? false)
            {
                incentivePool.Add(Item.Floater);
            }
            if (flags.IncentivizeChime ?? false)
            {
                incentivePool.Add(Item.Chime);
            }
            if (flags.IncentivizePromotion ?? false)
            {
                incentivePool.Add(Item.Tail);
            }
            if (flags.IncentivizeCube ?? false)
            {
                incentivePool.Add(Item.Cube);
            }
            if (flags.IncentivizeBottle ?? false)
            {
                incentivePool.Add(Item.Bottle);
            }
            if (flags.IncentivizeOxyale ?? false)
            {
                incentivePool.Add(Item.Oxyale);
            }
            if (flags.IncentivizeCanoe ?? false)
            {
                incentivePool.Add(Item.Canoe);
            }

            if (flags.IncentivizeXcalber ?? false)
            {
                incentivePool.Add(Item.Xcalber);
            }
            if (flags.IncentivizeMasamune ?? false)
            {
                incentivePool.Add(Item.Masamune);
            }
            if (flags.IncentivizeKatana ?? false)
            {
                incentivePool.Add(Item.Katana);
            }
            if (flags.IncentivizeVorpal ?? false)
            {
                incentivePool.Add(Item.Vorpal);
            }
            if (flags.IncentivizeRibbon ?? false)
            {
                incentivePool.Add(Item.Ribbon);
            }
            if (flags.IncentivizeRibbon2)
            {
                incentivePool.Add(Item.Ribbon);
            }
            if (flags.IncentivizeOpal ?? false)
            {
                incentivePool.Add(Item.Opal);
            }
            if (flags.Incentivize65K)
            {
                incentivePool.Add(Item.Gold65000);
            }
            if (flags.IncentivizeBad)
            {
                incentivePool.Add(Item.Cloth);
            }
            if (flags.IncentivizeDefCastArmor ?? false)
            {
                incentivePool.Add(Item.WhiteShirt);
            }
            if (flags.IncentivizeOffCastArmor ?? false)
            {
                incentivePool.Add(Item.BlackShirt);
            }
            if (flags.IncentivizeOtherCastArmor ?? false)
            {
                incentivePool.Add(Item.PowerGauntlets);
            }
            if (flags.IncentivizeDefCastWeapon ?? false)
            {
                incentivePool.Add(Item.Defense);
            }
            if (flags.IncentivizeOffCastWeapon ?? false)
            {
                incentivePool.Add(Item.ThorHammer);
            }
            if (flags.IncentivizeOtherCastWeapon)
            {
                incentivePool.Add(Item.BaneSword);
            }

            var incentiveLocationPool = new List <IRewardSource>();

            if (flags.IncentivizeKingConeria)
            {
                incentiveLocationPool.Add(ItemLocations.KingConeria);
            }
            if (flags.IncentivizePrincess)
            {
                incentiveLocationPool.Add(ItemLocations.Princess);
            }
            if (flags.IncentivizeMatoya)
            {
                incentiveLocationPool.Add(ItemLocations.Matoya);
            }
            if (flags.IncentivizeBikke)
            {
                incentiveLocationPool.Add(ItemLocations.Bikke);
            }
            if (flags.IncentivizeElfPrince)
            {
                incentiveLocationPool.Add(ItemLocations.ElfPrince);
            }
            if (flags.IncentivizeAstos)
            {
                incentiveLocationPool.Add(ItemLocations.Astos);
            }
            if (flags.IncentivizeNerrick)
            {
                incentiveLocationPool.Add(ItemLocations.Nerrick);
            }
            if (flags.IncentivizeSmith)
            {
                incentiveLocationPool.Add(ItemLocations.Smith);
            }
            if (flags.IncentivizeSarda)
            {
                incentiveLocationPool.Add(ItemLocations.Sarda);
            }
            if (flags.IncentivizeCanoeSage)
            {
                incentiveLocationPool.Add(ItemLocations.CanoeSage);
            }
            if (flags.IncentivizeCubeBot)
            {
                incentiveLocationPool.Add(ItemLocations.CubeBot);
            }
            if (flags.IncentivizeFairy)
            {
                incentiveLocationPool.Add(ItemLocations.Fairy);
            }
            if (flags.IncentivizeLefein)
            {
                incentiveLocationPool.Add(ItemLocations.Lefein);
            }
            if (flags.IncentivizeVolcano ?? false)
            {
                incentiveLocationPool.Add(ItemLocations.VolcanoMajor);
            }
            if (flags.IncentivizeEarth ?? false)
            {
                incentiveLocationPool.Add(ItemLocations.EarthCaveMajor);
            }
            if (flags.IncentivizeMarsh ?? false)
            {
                incentiveLocationPool.Add(ItemLocations.MarshCaveMajor);
            }
            if (flags.IncentivizeMarshKeyLocked ?? false)
            {
                incentiveLocationPool.Add(ItemLocations.MarshCave13);
            }
            if (flags.IncentivizeSkyPalace ?? false)
            {
                incentiveLocationPool.Add(ItemLocations.SkyPalaceMajor);
            }
            if (flags.IncentivizeSeaShrine ?? false)
            {
                incentiveLocationPool.Add(ItemLocations.SeaShrineMajor);
            }
            if (flags.IncentivizeConeria ?? false)
            {
                incentiveLocationPool.Add(ItemLocations.ConeriaMajor);
            }
            if (flags.IncentivizeIceCave ?? false)
            {
                incentiveLocationPool.Add(ItemLocations.IceCaveMajor);
            }
            if (flags.IncentivizeOrdeals ?? false)
            {
                incentiveLocationPool.Add(ItemLocations.OrdealsMajor);
            }
            if (flags.IncentivizeCaravan)
            {
                incentiveLocationPool.Add(ItemLocations.CaravanItemShop1);
            }
            if (flags.IncentivizeTitansTrove ?? false)
            {
                incentiveLocationPool.Add(ItemLocations.TitansTunnel1);
            }
            var itemLocationPool =
                ItemLocations.AllTreasures.Concat(ItemLocations.AllNPCItemLocations)
                .Where(x => !x.IsUnused && !forcedItemPlacements.Any(y => y.Address == x.Address))
                .ToList();

            if ((bool)flags.EarlyOrdeals)
            {
                forcedItemPlacements =
                    forcedItemPlacements
                    .Select(x => ((x as TreasureChest)?.AccessRequirement.HasFlag(AccessRequirement.Crown) ?? false)
                                                                ? new TreasureChest(x, x.Item, x.AccessRequirement & ~AccessRequirement.Crown)
                                                                : x).ToList();
                itemLocationPool =
                    itemLocationPool
                    .Select(x => ((x as TreasureChest)?.AccessRequirement.HasFlag(AccessRequirement.Crown) ?? false)
                                                                ? new TreasureChest(x, x.Item, x.AccessRequirement & ~AccessRequirement.Crown)
                                                                : x).ToList();
                incentiveLocationPool =
                    incentiveLocationPool
                    .Select(x => ((x as TreasureChest)?.AccessRequirement.HasFlag(AccessRequirement.Crown) ?? false)
                                                        ? new TreasureChest(x, x.Item, x.AccessRequirement & ~AccessRequirement.Crown)
                                                        : x).ToList();
            }
            if ((bool)flags.EarlyKing)
            {
                forcedItemPlacements =
                    forcedItemPlacements
                    .Select(x => x.Address == ItemLocations.KingConeria.Address
                                                                        ? new MapObject(ObjectId.King, MapLocation.ConeriaCastle2, x.Item)
                                                                        : x).ToList();
                itemLocationPool =
                    itemLocationPool
                    .Select(x => x.Address == ItemLocations.KingConeria.Address
                                                                        ? new MapObject(ObjectId.King, MapLocation.ConeriaCastle2, x.Item)
                                                                        : x).ToList();
                incentiveLocationPool =
                    incentiveLocationPool
                    .Select(x => x.Address == ItemLocations.KingConeria.Address
                                                                        ? new MapObject(ObjectId.King, MapLocation.ConeriaCastle2, x.Item)
                                                                        : x).ToList();
            }
            if ((bool)flags.EarlySage)
            {
                forcedItemPlacements =
                    forcedItemPlacements
                    .Select(x => x.Address == ItemLocations.CanoeSage.Address
                                                                        ? new MapObject(ObjectId.CanoeSage, MapLocation.CrescentLake, x.Item)
                                                                        : x).ToList();
                itemLocationPool =
                    itemLocationPool
                    .Select(x => x.Address == ItemLocations.CanoeSage.Address
                                                                        ? new MapObject(ObjectId.CanoeSage, MapLocation.CrescentLake, x.Item)
                                                                        : x).ToList();
                incentiveLocationPool =
                    incentiveLocationPool
                    .Select(x => x.Address == ItemLocations.CanoeSage.Address
                                                                        ? new MapObject(ObjectId.CanoeSage, MapLocation.CrescentLake, x.Item)
                                                                        : x).ToList();
            }
            if ((bool)flags.EarlySarda)
            {
                forcedItemPlacements =
                    forcedItemPlacements
                    .Select(x => x.Address == ItemLocations.Sarda.Address
                                                                ? new MapObject(ObjectId.Sarda, MapLocation.SardasCave, x.Item)
                                                                : x).ToList();
                itemLocationPool =
                    itemLocationPool
                    .Select(x => x.Address == ItemLocations.Sarda.Address
                                                                ? new MapObject(ObjectId.Sarda, MapLocation.SardasCave, x.Item)
                                                                : x).ToList();
                incentiveLocationPool =
                    incentiveLocationPool
                    .Select(x => x.Address == ItemLocations.Sarda.Address
                                                                ? new MapObject(ObjectId.Sarda, MapLocation.SardasCave, x.Item)
                                                                : x).ToList();
            }

            MapLocation elfDoctorLocation = map.ObjectiveNPCs[ObjectId.ElfDoc];

            if (elfDoctorLocation != MapLocation.ElflandCastle)
            {
                forcedItemPlacements =
                    forcedItemPlacements
                    .Select(x => x.Address == ItemLocations.ElfPrince.Address
                                                                ? new MapObject(ObjectId.ElfPrince, MapLocation.ElflandCastle, x.Item, AccessRequirement.Herb, ObjectId.ElfDoc, requiredSecondLocation: elfDoctorLocation)
                                                                : x).ToList();
                itemLocationPool =
                    itemLocationPool
                    .Select(x => x.Address == ItemLocations.ElfPrince.Address
                                                                ? new MapObject(ObjectId.ElfPrince, MapLocation.ElflandCastle, x.Item, AccessRequirement.Herb, ObjectId.ElfDoc, requiredSecondLocation: elfDoctorLocation)
                                                                : x).ToList();
                incentiveLocationPool =
                    incentiveLocationPool
                    .Select(x => x.Address == ItemLocations.ElfPrince.Address
                                                                ? new MapObject(ObjectId.ElfPrince, MapLocation.ElflandCastle, x.Item, AccessRequirement.Herb, ObjectId.ElfDoc, requiredSecondLocation: elfDoctorLocation)
                                                                : x).ToList();
            }

            MapLocation unneLocation = map.ObjectiveNPCs[ObjectId.Unne];

            if (unneLocation != MapLocation.Melmond)
            {
                forcedItemPlacements =
                    forcedItemPlacements
                    .Select(x => x.Address == ItemLocations.Lefein.Address
                                                                ? new MapObject(ObjectId.Lefein, MapLocation.Lefein, x.Item, AccessRequirement.Slab, ObjectId.Unne, requiredSecondLocation: unneLocation)
                                                                : x).ToList();
                itemLocationPool =
                    itemLocationPool
                    .Select(x => x.Address == ItemLocations.Lefein.Address
                                                                ? new MapObject(ObjectId.Lefein, MapLocation.Lefein, x.Item, AccessRequirement.Slab, ObjectId.Unne, requiredSecondLocation: unneLocation)
                                                                : x).ToList();
                incentiveLocationPool =
                    incentiveLocationPool
                    .Select(x => x.Address == ItemLocations.Lefein.Address
                                                                ? new MapObject(ObjectId.Lefein, MapLocation.Lefein, x.Item, AccessRequirement.Slab, ObjectId.Unne, requiredSecondLocation: unneLocation)
                                                                : x).ToList();
            }

            foreach (var item in forcedItemPlacements.Select(x => x.Item))
            {
                incentivePool.Remove(item);
            }

            var validKeyLocations = new List <IRewardSource> {
                ItemLocations.ElfPrince
            };
            var validBridgeLocations = new List <IRewardSource> {
                ItemLocations.KingConeria
            };
            var validShipLocations = new List <IRewardSource> {
                ItemLocations.Bikke
            };
            var validCanoeLocations = new List <IRewardSource> {
                ItemLocations.CanoeSage
            };
            var everythingButOrbs = ~AccessRequirement.BlackOrb;

            if (flags.NPCFetchItems ?? false)
            {
                var validKeyMapLocations = ItemPlacement.AccessibleMapLocations(~(AccessRequirement.BlackOrb | AccessRequirement.Key), MapChange.All, fullLocationRequirements);
                validKeyLocations = itemLocationPool.Where(x => validKeyMapLocations.Contains(x.MapLocation) &&
                                                           validKeyMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation)).ToList();
                var keyPlacementRank = rng.Between(1, incentivePool.Count);
                if (incentivePool.Contains(Item.Key) && incentiveLocationPool.Any(x => validKeyLocations.Any(y => y.Address == x.Address)) && keyPlacementRank <= incentiveLocationPool.Count)
                {
                    validKeyLocations = validKeyLocations.Where(x => incentiveLocationPool.Any(y => y.Address == x.Address)).ToList();
                }
                else if (!(flags.IncentivizeKey ?? false) && incentivePool.Count >= incentiveLocationPool.Count)
                {
                    validKeyLocations = validKeyLocations.Where(x => !incentiveLocationPool.Any(y => y.Address == x.Address)).ToList();
                }
            }

            if (flags.NPCItems ?? false)
            {
                var everythingButCanoe      = ~MapChange.Canoe;
                var startingPotentialAccess = map.StartingPotentialAccess;
                var startingMapLocations    = ItemPlacement.AccessibleMapLocations(startingPotentialAccess, MapChange.None, fullLocationRequirements);
                var validShipMapLocations   = ItemPlacement.AccessibleMapLocations(startingPotentialAccess | AccessRequirement.Crystal, MapChange.Bridge, fullLocationRequirements);
                var validCanoeMapLocations  = ItemPlacement.AccessibleMapLocations(everythingButOrbs, everythingButCanoe, fullLocationRequirements);

                validBridgeLocations =
                    itemLocationPool.Where(x => startingMapLocations.Contains(x.MapLocation) &&
                                           startingMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation)).ToList();
                validShipLocations =
                    itemLocationPool.Where(x => validShipMapLocations.Contains(x.MapLocation) &&
                                           validShipMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation)).ToList();
                validCanoeLocations =
                    itemLocationPool.Where(x => validCanoeMapLocations.Contains(x.MapLocation) &&
                                           validCanoeMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation)).ToList();

                var canoePlacementRank   = rng.Between(1, incentivePool.Count);
                var validCanoeIncentives = validCanoeLocations.Where(x => incentiveLocationPool.Any(y => y.Address == x.Address)).ToList();
                if (incentivePool.Contains(Item.Canoe) && canoePlacementRank <= incentiveLocationPool.Count &&
                    validKeyLocations.Union(validCanoeIncentives).Count() > 1)                     // The Key can be placed in at least one place more than than the Canoe
                {
                    validCanoeLocations = validCanoeIncentives;
                }
                else if (!flags.IncentivizeBridge && incentivePool.Count >= incentiveLocationPool.Count)
                {
                    validCanoeLocations = validCanoeLocations.Where(x => !incentiveLocationPool.Any(y => y.Address == x.Address)).ToList();
                }
            }

            var nonEndgameMapLocations = ItemPlacement.AccessibleMapLocations(~AccessRequirement.BlackOrb, MapChange.All, fullLocationRequirements);

            ForcedItemPlacements = forcedItemPlacements.ToList();
            IncentiveItems       = incentivePool.ToList();

            BridgeLocations = validBridgeLocations
                              .Where(x => !forcedItemPlacements.Any(y => y.Address == x.Address))
                              .ToList();
            ShipLocations = validShipLocations
                            .Where(x => !forcedItemPlacements.Any(y => y.Address == x.Address))
                            .ToList();
            KeyLocations = validKeyLocations
                           .Where(x => !forcedItemPlacements.Any(y => y.Address == x.Address))
                           .ToList();
            CanoeLocations = validCanoeLocations
                             .Where(x => !forcedItemPlacements.Any(y => y.Address == x.Address))
                             .ToList();
            IncentiveLocations = incentiveLocationPool
                                 .Where(x => !forcedItemPlacements.Any(y => y.Address == x.Address))
                                 .ToList();

            AllValidItemLocations            = itemLocationPool.ToList();
            AllValidPreBlackOrbItemLocations = AllValidItemLocations
                                               .Where(x => nonEndgameMapLocations.Contains(x.MapLocation) && nonEndgameMapLocations.Contains((x as MapObject)?.SecondLocation ?? MapLocation.StartingLocation))
                                               .ToList();
        }
        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);
        }
Exemple #17
0
        public static ItemPlacement Create(IItemPlacementFlags flags, IncentiveData incentivesData, List <Item> allTreasures, ItemShopSlot caravanItemLocation, OverworldMap overworldMap)
        {
            ItemPlacement placement;

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

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

            return(placement);
        }
        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);
        }
        // Scale is the geometric scale factor used with RNG.  Multiplier is where we make everything cheaper
        // instead of enemies giving more gold, so we don't overflow.
        public void ScalePrices(IScaleFlags flags, MT19337 rng, bool increaseOnly, ItemShopSlot shopItemLocation, bool FreeClinic = false)
        {
            IEnumerable <Item> tmpExcludedItems = Array.Empty <Item>();

            if (flags.ExcludeGoldFromScaling ?? false)
            {
                tmpExcludedItems = tmpExcludedItems.Concat(ItemLists.AllGoldTreasure);
            }
            if ((flags.ExcludeGoldFromScaling ?? false) && flags.CheapVendorItem)
            {
                tmpExcludedItems = tmpExcludedItems.Concat(ItemLists.AllQuestItems);
            }

            HashSet <Item> excludedItems = new HashSet <Item>(tmpExcludedItems);
            HashSet <Item> questItems    = new HashSet <Item>(ItemLists.AllQuestItems);

            int rawScaleLow  = increaseOnly ? 100 : flags.PriceScaleFactorLow;
            int rawScaleHigh = increaseOnly ? Math.Max(100, flags.PriceScaleFactorHigh) : flags.PriceScaleFactorHigh;

            double scaleLow  = (double)rawScaleLow / 100.0;
            double scaleHigh = (double)rawScaleHigh / 100.0;

            var multiplier = flags.ExcludeGoldFromScaling ?? false ? 1.0 : flags.ExpMultiplier;
            var prices     = Get(PriceOffset, PriceSize * PriceCount).ToUShorts();

            for (int i = 0; i < prices.Length; i++)
            {
                if (excludedItems.Contains((Item)i))
                {
                    var price = (int)prices[i];

                    if (flags.CheapVendorItem && questItems.Contains((Item)i))
                    {
                        price = 20000;
                    }

                    var newPrice = price + rng.Between(-price / 10, price / 10);
                    prices[i] = (ushort)Math.Min(Math.Max(newPrice, 1), 65535);
                }
                else
                {
                    var price    = ExtConsumables.ExtConsumablePriceFix((Item)i, prices[i], flags);
                    var newPrice = RangeScaleWithZero(price / multiplier, scaleLow, scaleHigh, 1e-5 * multiplier, 1, rng);
                    prices[i] = (ushort)Min(newPrice, 0xFFFF);
                }
            }
            var questItemPrice = prices[(int)Item.Bottle];

            // If we don't do this before checking for the item shop location factor, Ribbons and Shirts will end up being really cheap
            // This realistically doesn't matter without Shop Wares shuffle on because nobody wants to sell Ribbons/Shirts, but if it is...
            prices[(int)Item.WhiteShirt] = (ushort)(questItemPrice / 2);
            prices[(int)Item.BlackShirt] = (ushort)(questItemPrice / 2);
            prices[(int)Item.Ribbon]     = questItemPrice;
            var itemShopFactor = new Dictionary <MapLocation, int>()
            {
                { MapLocation.Coneria, 8 },
                { MapLocation.Pravoka, 2 }
            };

            if (itemShopFactor.TryGetValue(shopItemLocation.MapLocation, out int divisor))
            {
                questItemPrice = (ushort)(prices[(int)Item.Bottle] / divisor);
            }
            for (var i = 0; i < (int)Item.Tent; i++)
            {
                prices[i] = questItemPrice;
            }
            Put(PriceOffset, Blob.FromUShorts(prices));

            for (int i = GoldItemOffset; i < GoldItemOffset + GoldItemCount; i++)
            {
                ItemsText[i] = prices[i].ToString() + " G";
            }

            var pointers = Get(ShopPointerOffset, ShopPointerCount * ShopPointerSize).ToUShorts();

            RepackShops(pointers);

            for (int i = (int)ShopType.Clinic; i < (int)ShopType.Inn + ShopSectionSize; i++)
            {
                if (pointers[i] != ShopNullPointer)
                {
                    var priceBytes = Get(ShopPointerBase + pointers[i], 2);
                    var priceValue = BitConverter.ToUInt16(priceBytes, 0);

                    priceValue = (ushort)RangeScale(priceValue / multiplier, scaleLow, scaleHigh, 1, rng);
                    if (FreeClinic && i < (int)ShopType.Clinic + ShopSectionSize)
                    {
                        priceValue = 0;
                    }
                    priceBytes = BitConverter.GetBytes(priceValue);
                    Put(ShopPointerBase + pointers[i], priceBytes);
                }
            }

            List <(StartingGold, ushort)> startingGold = new()
            {
                (StartingGold.None, 0),
                (StartingGold.Gp100, 100),
                (StartingGold.Gp200, 200),
                (StartingGold.Gp400, 400),
                (StartingGold.Gp800, 800),
                (StartingGold.Gp2500, 2500),
                (StartingGold.Gp9999, 9999),
                (StartingGold.Gp65535, 65535),
                (StartingGold.RandomLow, (ushort)rng.Between(0, 800)),
                (StartingGold.RandomHigh, (ushort)rng.Between(0, 65535)),
            };

            Put(StartingGoldOffset, BitConverter.GetBytes(startingGold[(int)flags.StartingGold].Item2));
        }