Esempio n. 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,
                                                     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);
        }
Esempio n. 2
0
        public static bool CheckSanity(List <IRewardSource> treasurePlacements,
                                       ITreasureShuffleFlags flags,
                                       Dictionary <MapLocation, List <MapChange> > mapLocationRequirements)
        {
            const int maxIterations     = 20;
            var       currentIteration  = 0;
            var       currentAccess     = AccessRequirement.None;
            var       currentMapChanges = MapChange.None;
            var       allMapLocations   = Enum.GetValues(typeof(MapLocation))
                                          .Cast <MapLocation>().ToList();
            Func <IEnumerable <MapLocation> > currentMapLocations =
                () => allMapLocations
                .Where(x => mapLocationRequirements[x]
                       .Any(y => currentMapChanges.HasFlag(y)));
            Func <IEnumerable <IRewardSource> > currentItemLocations =
                () => treasurePlacements
                .Where(x => {
                var locations = currentMapLocations().ToList();
                return(locations.Contains(x.MapLocation) &&
                       currentAccess.HasFlag(x.AccessRequirement) &&
                       (!(x is MapObject) || locations.Contains(((MapObject)x).SecondLocation)));
            });

            var winTheGameAccess        = ItemLocations.ChaosReward.AccessRequirement;
            var winTheGameLocation      = ItemLocations.ChaosReward.MapLocation;
            var accessibleLocationCount = currentItemLocations().Count();
            var requiredAccess          = winTheGameAccess;

            if (!flags.EarlyOrdeals)
            {
                requiredAccess |= AccessRequirement.Crown;
            }
            var requiredMapChanges = MapChange.None;

            if (flags.MapTitansTrove)
            {
                requiredMapChanges |= MapChange.TitanFed;
            }

            while (!currentAccess.HasFlag(requiredAccess) ||
                   !currentMapChanges.HasFlag(requiredMapChanges) ||
                   !currentMapLocations().Contains(winTheGameLocation))
            {
                if (currentIteration > maxIterations)
                {
                    throw new InvalidOperationException($"Sanity Check hit max iterations: {currentIteration}");
                }
                currentIteration++;
                var currentItems = currentItemLocations().Select(x => x.Item).ToList();

                if (!currentAccess.HasFlag(AccessRequirement.Key) &&
                    currentItems.Contains(Item.Key))
                {
                    currentAccess |= AccessRequirement.Key;
                }
                if (!currentMapChanges.HasFlag(MapChange.Bridge) &&
                    currentItems.Contains(Item.Bridge) &&
                    currentMapLocations().Contains(MapLocation.BridgeLocation))
                {
                    currentMapChanges |= MapChange.Bridge;
                }
                if (!currentAccess.HasFlag(AccessRequirement.Crown) &&
                    currentItems.Contains(Item.Crown))
                {
                    currentAccess |= AccessRequirement.Crown;
                }
                if (!currentAccess.HasFlag(AccessRequirement.Crystal) &&
                    currentItems.Contains(Item.Crystal))
                {
                    currentAccess |= AccessRequirement.Crystal;
                }
                if (!currentAccess.HasFlag(AccessRequirement.Herb) &&
                    currentItems.Contains(Item.Herb))
                {
                    currentAccess |= AccessRequirement.Herb;
                }
                if (!currentMapChanges.HasFlag(MapChange.Canoe) &&
                    currentItems.Contains(Item.Canoe))
                {
                    currentMapChanges |= MapChange.Canoe;
                }
                if (!currentMapChanges.HasFlag(MapChange.Ship) &&
                    currentItems.Contains(Item.Ship) &&
                    currentMapLocations().Contains(MapLocation.ShipLocation))
                {
                    currentMapChanges |= MapChange.Ship;
                }
                if (!currentAccess.HasFlag(AccessRequirement.Tnt) &&
                    currentItems.Contains(Item.Tnt))
                {
                    currentAccess |= AccessRequirement.Tnt;
                }
                if (!currentAccess.HasFlag(AccessRequirement.Adamant) &&
                    currentItems.Contains(Item.Adamant))
                {
                    currentAccess |= AccessRequirement.Adamant;
                }
                if (!currentMapChanges.HasFlag(MapChange.Canal) &&
                    currentItems.Contains(Item.Canal) &&
                    currentMapChanges.HasFlag(MapChange.Ship))
                {
                    currentMapChanges |= MapChange.Canal;
                }
                if (!currentMapChanges.HasFlag(MapChange.TitanFed) &&
                    currentItems.Contains(Item.Ruby) &&
                    currentMapLocations().Contains(MapLocation.TitansTunnelEast))
                {
                    currentMapChanges |= MapChange.TitanFed;
                }
                if (!currentAccess.HasFlag(AccessRequirement.Rod) &&
                    currentItems.Contains(Item.Rod))
                {
                    currentAccess |= AccessRequirement.Rod;
                }
                if (!currentAccess.HasFlag(AccessRequirement.Slab) &&
                    currentItems.Contains(Item.Slab))
                {
                    currentAccess |= AccessRequirement.Slab;
                }
                if (!currentMapChanges.HasFlag(MapChange.Airship) &&
                    (currentItems.Contains(Item.Floater)) &&                     // || currentItems.Contains(Item.Airship)) &&
                    currentMapLocations().Contains(MapLocation.AirshipLocation))
                {
                    currentMapChanges |= MapChange.Airship;
                }

                if (!currentAccess.HasFlag(AccessRequirement.Bottle) &&
                    currentItems.Contains(Item.Bottle))
                {
                    currentAccess |= AccessRequirement.Bottle;
                }
                if (!currentAccess.HasFlag(AccessRequirement.Oxyale) &&
                    currentItems.Contains(Item.Oxyale))
                {
                    currentAccess |= AccessRequirement.Oxyale;
                }
                if (!currentMapChanges.HasFlag(MapChange.Chime) &&
                    currentItems.Contains(Item.Chime) &&
                    currentMapChanges.HasFlag(MapChange.Airship))
                {
                    currentMapChanges |= MapChange.Chime;
                }
                if (!currentAccess.HasFlag(AccessRequirement.Cube) &&
                    currentItems.Contains(Item.Cube))
                {
                    currentAccess |= AccessRequirement.Cube;
                }
                if (!currentAccess.HasFlag(AccessRequirement.BlackOrb) &&
                    ItemLists.AllOrbs.All(y => currentItems.Contains(y)))
                {
                    currentAccess |= AccessRequirement.BlackOrb;
                }
                if (!currentAccess.HasFlag(AccessRequirement.Lute) &&
                    currentItems.Contains(Item.Lute))
                {
                    currentAccess |= AccessRequirement.Lute;
                }

                var newCount = currentItemLocations().Count();
                if (newCount <= accessibleLocationCount)
                {
                    return(false);
                }
                accessibleLocationCount = newCount;
            }

            return(true);
        }
Esempio n. 3
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);
        }
Esempio n. 4
0
        public static void RunStats(MT19337 rng, ITreasureShuffleFlags flags, IncentiveData incentivesData)
        {
            var forcedItems = ItemLocations.AllOtherItemLocations.ToList();

            if (!flags.NPCItems)
            {
                forcedItems = ItemLocations.AllNonTreasureItemLocations.ToList();
            }

            var placedItems       = new List <IRewardSource>();
            var treasurePool      = ItemLocations.AllTreasures.Where(x => !x.IsUnused).Select(x => x.Item).ToList();
            var requirementChecks = ItemLists.AllQuestItems.ToDictionary(x => x, x => 0);

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

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

            itemPlacementZones[Item.Ribbon] = new List <string>();
            var  mapLocationRequirements = ItemLocations.MapLocationRequirements.ToDictionary(x => x.Key, x => x.Value.ToList());
            long iterations = 0;

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

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

                foreach (Item item in requirementsToCheck)
                {
                    if (!ItemPlacement.CheckSanity(placedItems.Where(x => x.Item != item).ToList(), flags, mapLocationRequirements))
                    {
                        requirementChecks[item]++;
                    }
                }
            }

            if (iterations > 10)
            {
                Debug.WriteLine(PrintStats(maxIterations, itemPlacementStats, itemPlacementZones, requirementChecks));
                Debug.WriteLine($"Forced Early Ice Cave for Ship: {forcedIceCount} out of {maxIterations}");
            }
        }