//retrieve all accessible RewardSources(with no item in them) from the SanityChecker
        private List <IRewardSource> GetAllAccessibleRewardSources(IEnumerable <IRewardSource> preBlackOrbLocationPool, List <IRewardSource> placedItems, Item item1 = Item.None, Item item2 = Item.None)
        {
            var placedItems2 = placedItems.Select(i => logicSources.TryGetValue(i.Address, out var l) ? new SCLogicRewardSource {
                Requirements = l.Requirements, RewardSource = i
            } : null).Where(l => l != null).ToList();

            SCRequirements requirements = freeRequirements;

            if (item1 != Item.None)
            {
                requirements = requirements.AddItem(item1);
            }
            if (item2 != Item.None)
            {
                requirements = requirements.AddItem(item2);
            }

            List <SCLogicRewardSource> toRemove = new List <SCLogicRewardSource>(placedItems2.Count);

            var maxCount = placedItems2.Count;

            for (int i = 0; i < maxCount; i++)
            {
                foreach (var r in placedItems2.Where(l => l.Requirements.IsAccessible(requirements)))
                {
                    toRemove.Add(r);
                    requirements = requirements.AddItem(r.RewardSource.Item);
                }

                foreach (var r in toRemove)
                {
                    placedItems2.Remove(r);
                }

                if (toRemove.Count == 0)
                {
                    break;
                }
                if (placedItems2.Count == 0)
                {
                    break;
                }

                toRemove.Clear();
            }

            return(preBlackOrbLocationPool.Where(i => logicSources.TryGetValue(i.Address, out var l) && l.Requirements.IsAccessible(requirements)).Where(x => !placedItems.Any(y => y.Address == x.Address)).ToList());
        }
예제 #2
0
        public static SCRequirements AddItem(this SCRequirements left, Item right)
        {
            switch (right)
            {
            case Item.Adamant: return(left | SCRequirements.Adamant);

            case Item.Bottle: return(left | SCRequirements.Bottle);

            case Item.Bridge: return(left | SCRequirements.Bridge);

            case Item.Canal: return(left | SCRequirements.Canal);

            case Item.Canoe: return(left | SCRequirements.Canoe);

            case Item.Chime: return(left | SCRequirements.Chime);

            case Item.Crown: return(left | SCRequirements.Crown);

            case Item.Crystal: return(left | SCRequirements.Crystal);

            case Item.Cube: return(left | SCRequirements.Cube);

            case Item.Floater: return(left | SCRequirements.Floater);

            case Item.Herb: return(left | SCRequirements.Herb);

            case Item.Key: return(left | SCRequirements.Key);

            case Item.Lute: return(left | SCRequirements.Lute);

            case Item.Oxyale: return(left | SCRequirements.Oxyale);

            case Item.Rod: return(left | SCRequirements.Rod);

            case Item.Ruby: return(left | SCRequirements.Ruby);

            case Item.Ship: return(left | SCRequirements.Ship);

            case Item.Slab: return(left | SCRequirements.Slab);

            case Item.Tnt: return(left | SCRequirements.Tnt);

            default: return(left);
            }
        }
예제 #3
0
 public SCLogicArea(short areaId, SCOwArea area, SCRequirements flags)
 {
     AreaId       = areaId;
     Area         = area;
     Requirements = new SCRequirementsSet(flags);
 }
예제 #4
0
 public static bool IsEqual(this SCRequirements left, SCRequirements right)
 {
     return(left == right);
 }
예제 #5
0
 public static bool IsOrthogonalTo(this SCRequirements left, SCRequirements right)
 {
     return(!left.IsSupersetOf(right) && !right.IsSupersetOf(left));
 }
예제 #6
0
 public static bool IsSubsetOf(this SCRequirements left, SCRequirements right)
 {
     return((right & left) == left);
 }
예제 #7
0
 public static bool IsStrictSupersetOf(this SCRequirements left, SCRequirements right)
 {
     return((right & left) == right && left != right);
 }
예제 #8
0
        private List <string> GetRule(SCRequirements l)
        {
            var list = new List <string>();

            if (l.HasFlag(SCRequirements.Lute))
            {
                list.Add(Item.Lute.ToString());
            }
            if (l.HasFlag(SCRequirements.Crown))
            {
                list.Add(Item.Crown.ToString());
            }
            if (l.HasFlag(SCRequirements.Key))
            {
                list.Add(Item.Key.ToString());
            }
            if (l.HasFlag(SCRequirements.Ruby))
            {
                list.Add(Item.Ruby.ToString());
            }
            if (l.HasFlag(SCRequirements.Rod))
            {
                list.Add(Item.Rod.ToString());
            }
            if (l.HasFlag(SCRequirements.Chime))
            {
                list.Add(Item.Chime.ToString());
            }
            if (l.HasFlag(SCRequirements.Cube))
            {
                list.Add(Item.Cube.ToString());
            }
            if (l.HasFlag(SCRequirements.Oxyale))
            {
                list.Add(Item.Oxyale.ToString());
            }
            if (l.HasFlag(SCRequirements.Tnt))
            {
                list.Add(Item.Tnt.ToString());
            }
            if (l.HasFlag(SCRequirements.Canoe))
            {
                list.Add(Item.Canoe.ToString());
            }
            if (l.HasFlag(SCRequirements.Floater))
            {
                list.Add(Item.Floater.ToString());
            }
            if (l.HasFlag(SCRequirements.Bridge))
            {
                list.Add(Item.Bridge.ToString());
            }
            if (l.HasFlag(SCRequirements.Canal))
            {
                list.Add(Item.Canal.ToString());
            }
            if (l.HasFlag(SCRequirements.Bottle))
            {
                list.Add(Item.Bottle.ToString());
            }
            if (l.HasFlag(SCRequirements.Crystal))
            {
                list.Add(Item.Crystal.ToString());
            }
            if (l.HasFlag(SCRequirements.Herb))
            {
                list.Add(Item.Herb.ToString());
            }
            if (l.HasFlag(SCRequirements.Adamant))
            {
                list.Add(Item.Adamant.ToString());
            }
            if (l.HasFlag(SCRequirements.Slab))
            {
                list.Add(Item.Slab.ToString());
            }
            if (l.HasFlag(SCRequirements.Ship))
            {
                list.Add(Item.Ship.ToString());
            }

            return(list);
        }
        protected override ItemPlacementResult DoSanePlacement(MT19337 rng, ItemPlacementContext ctx)
        {
            exceptionWeights   = _flags.AllowUnsafePlacement || (_flags.Entrances ?? false) ? UnsafeExceptionWeights : SafeExceptionWeights;
            exceptionMinCycles = _flags.AllowUnsafePlacement || (_flags.Entrances ?? false) ? UnsafeExceptioMinCycles : SafeExceptioMinCycles;
            minAccessibility   = _flags.AllowUnsafePlacement || (_flags.Entrances ?? false) ? UnsafeMinAccessibility : SafeMinAccessibility;

            _sanityCounter = 0;
            var incentiveLocationPool   = new HashSet <IRewardSource>(_incentivesData.IncentiveLocations, new RewardSourceEqualityComparer());
            var preBlackOrbLocationPool = _incentivesData.AllValidPreBlackOrbItemLocations.ToList();
            var preBlackOrbUnincentivizedLocationPool = preBlackOrbLocationPool.Where(x => !incentiveLocationPool.Any(y => y.Address == x.Address)).ToList();

            if ((bool)_flags.LooseExcludePlacedDungeons)
            {
                preBlackOrbUnincentivizedLocationPool = IncentivizedDungeons(preBlackOrbUnincentivizedLocationPool);
            }

            var unincentivizedLocationPool = new HashSet <IRewardSource>(preBlackOrbUnincentivizedLocationPool, new RewardSourceEqualityComparer());

            var allRewardSources = preBlackOrbLocationPool.Append(new ItemShopSlot(_caravanItemLocation, Item.None)).ToList();

            List <IRewardSource> placedItems  = null;
            List <Item>          treasurePool = null;

            freeRequirements = BuildFreeRequirements();

            bool placementFailed;

            do
            {
                ((SanityCheckerV2)_checker).Shiplocations.SetShipLocation(255);

                BuildLogic(allRewardSources);

                placementFailed = false;

                var balancedPicker = new RewardSourcePicker(0.5, _flags.LooseItemsNpcBalance ? 7.0 : 1.0, _checker);

                _sanityCounter++;
                if (_sanityCounter > 2)
                {
                    throw new InsaneException("Item Placement could not meet incentivization requirements!");
                }

                placedItems = ctx.Forced.ToList();
                var incentives    = new HashSet <Item>(ctx.Incentivized);
                var nonincentives = ctx.Unincentivized.ToList();
                var shards        = ctx.Shards.ToList();
                treasurePool = ctx.AllTreasures.ToList();
                var state = PlacementState.Normal;

                while (incentives.Count() > incentiveLocationPool.Count())
                {
                    nonincentives.Add(incentives.SpliceRandom(rng));
                }

                if (((bool)_flags.NPCItems) || ((bool)_flags.NPCFetchItems))
                {
                    HashSet <Item> allPlacements = new HashSet <Item>(nonincentives.Concat(incentives));
                    HashSet <Item> allKeyItems   = new HashSet <Item>(MapChangeItems.Concat(FetchQuestItems).Concat(GatingItems).Intersect(allPlacements));

                    if ((bool)_flags.IsFloaterRemoved)
                    {
                        allKeyItems.Remove(Item.Floater);
                    }

                    //The sanity checker currently doesn't allow tracking which shops are available
                    //It could be easily added, but a little randomnes can't hurt(or so I'm thinking)
                    //So it places the vendoritem upfront.
                    var itemShopItem = SelectVendorItem(incentives.ToList(), nonincentives, treasurePool, incentiveLocationPool, rng);
                    placedItems.Add(new ItemShopSlot(_caravanItemLocation, itemShopItem));

                    allPlacements.Remove(itemShopItem);
                    allKeyItems.Remove(itemShopItem);

                    if (_flags.Archipelago && allKeyItems.Contains(Item.Bridge))
                    {
                        var accessibleSources = GetAllAccessibleRewardSources(preBlackOrbUnincentivizedLocationPool, placedItems);

                        var rewardSource = balancedPicker.Pick(accessibleSources, _flags.LooseItemsForwardPlacement, _flags.LooseItemsSpreadPlacement, false, rng);
                        placedItems.Add(NewItemPlacement(rewardSource, Item.Bridge));

                        allPlacements.Remove(Item.Bridge);
                        allKeyItems.Remove(Item.Bridge);
                    }

                    //Since it can waste cycles, this number needs to be high enough to be able to place all incentives and all nonincentives and still have some leeway
                    //When it's done it breaks anyway
                    //When it reached enough cycles so no cycle based restricitons apply and can place neither an incentive nor a nonincentive it also breaks
                    for (int cycle = 1; cycle <= 100; cycle++)
                    {
                        var accessibleSources = GetAllAccessibleRewardSources(preBlackOrbLocationPool, placedItems);

                        //look how many rewardsources of each type are available
                        var incSourceCount = accessibleSources.Where(s => incentiveLocationPool.Contains(s)).Count();
                        var nonSourceCount = accessibleSources.Count - incSourceCount;

                        List <(Item item, int incCount, int nonCount)> candidates = new List <(Item item, int incCount, int nonCount)>();

                        //go through each KI, place it in the testRewardSource, run the sanity checker and see how many RewardSources are available afterwards.
                        foreach (var item in allKeyItems)
                        {
                            var accessibleSources2 = GetAllAccessibleRewardSources(preBlackOrbLocationPool, placedItems, item);

                            var incCount = accessibleSources2.Where(s => incentiveLocationPool.Contains(s)).Count();
                            var nonCount = accessibleSources2.Count - incCount - nonSourceCount;
                            incCount -= incSourceCount;

                            //if the placed item is an incentive, it will take up an incentive location when it will be placed
                            //and vice versa
                            if (incentives.Contains(item))
                            {
                                incCount--;
                            }
                            if (!incentives.Contains(item))
                            {
                                nonCount--;
                            }

                            candidates.Add((item, incCount, nonCount));
                        }

                        //Since it can happen, that neither the ship, nor the canal by themselves open up something, but the combination does,
                        //here the combination is checked
                        //it grabs another Rewardsource, places Ship and Canal, runs the SanityChecker and calculates the results
                        if (allKeyItems.Contains(Item.Ship) && allKeyItems.Contains(Item.Canal))
                        {
                            var accessibleSources2 = GetAllAccessibleRewardSources(preBlackOrbLocationPool, placedItems, Item.Ship, Item.Canal);

                            var incCount = accessibleSources2.Where(s => incentiveLocationPool.Contains(s)).Count();
                            var nonCount = accessibleSources2.Count - incCount - nonSourceCount;
                            incCount -= incSourceCount;

                            if (incentives.Contains(Item.Ship))
                            {
                                incCount--;
                            }
                            if (!incentives.Contains(Item.Ship))
                            {
                                nonCount--;
                            }

                            if (incentives.Contains(Item.Canal))
                            {
                                incCount--;
                            }
                            if (!incentives.Contains(Item.Canal))
                            {
                                nonCount--;
                            }

                            //it gets the ship and canal candidate from the list, later it's put back in
                            var shipCandidate  = candidates.FirstOrDefault(c => c.item == Item.Ship);
                            var canalCandidate = candidates.FirstOrDefault(c => c.item == Item.Canal);

                            candidates.Remove(shipCandidate);
                            candidates.Remove(canalCandidate);

                            //half of the opened up incentive locations and nonincentive locations hould go to either item(rounded down)
                            //In order to continue the seed, Ship and Canal must open up 2 incentive locations, since they may take up 2
                            //it's not an exact estimate, since it doesn'T differentiate between them beeing incentive or nonincentive items
                            incCount /= 2;
                            nonCount /= 2;

                            if (shipCandidate.incCount < incCount && canalCandidate.incCount < incCount)
                            {
                                shipCandidate.incCount  = incCount;
                                canalCandidate.incCount = incCount;
                            }

                            if (shipCandidate.nonCount < nonCount && canalCandidate.nonCount < nonCount)
                            {
                                shipCandidate.nonCount  = nonCount;
                                canalCandidate.nonCount = nonCount;
                            }

                            candidates.Add(shipCandidate);
                            candidates.Add(canalCandidate);
                        }

                        var incKeyItemCount = incentives.Where(i => allKeyItems.Contains(i)).Count();
                        var nonKeyItemCount = nonincentives.Where(i => allKeyItems.Contains(i)).Count();

                        List <(float weight, Item item)> weightedCandidates;

                        var incItemCount = allPlacements.Where(i => incentives.Contains(i)).Count();

                        //here it randomly decides whether to place and incentive or a non incentive
                        //the chance is based on the number of incentive and nonincentive items
                        //If however it couldn't place an incentive item in the last cycle, it'll always try a nonincentive this time
                        //and vice versa
                        if (state == PlacementState.Incentive || state == PlacementState.Normal && rng.Between(1, allPlacements.Count) <= incItemCount || _flags.LaterLoose && state == PlacementState.Normal && cycle <= 5)
                        {
                            //Filter out nonincentive items
                            candidates = candidates.Where(c => incentives.Contains(c.item)).ToList();
                            var nonKeyCandidates = allPlacements.Where(i => !allKeyItems.Contains(i) && incentives.Contains(i));

                            if (incSourceCount <= 1 || incSourceCount <= CriticalIncentiveSourceCount && allKeyItems.Contains(Item.Ship) && allKeyItems.Contains(Item.Canal))
                            {
                                //we are in incentive critical mode
                                //the function takes weights for different categories of items(depending of how they're expected they change the amount of incentive locations available)
                                //bin0 doesn't open up a new location(it therefore reduces the incentive location count by one)
                                //bin1 opens up 1 location and therefore keeps the count the same
                                //bin2 opens up 2 locations and therefore increases the amount by 1
                                //bin3 opens up more than two locations. It has two parameters here, more on that further down

                                //Anyway, we can't afford to reduce the incentive location count here(bin0)
                                //We also don't neccesarily want to keep it the same, but that's okish(bin1)
                                //we want to increase the amount(bin2, bin3)
                                weightedCandidates = AssignWeights(candidates, 0.0f, 0.5f, 1.0f, 1.0f, 0.5f, cycle, accessibleSources.Count);
                            }
                            else if (incSourceCount <= SafeIncentiveSourceCount)
                            {
                                //We have 1 incentive location to squander, not a lot, but sufficient
                                //We don't want to reduce it or keep it the same, but it would still be ok(bin0, bin1)
                                //We'd like to increase it a little(bin2)
                                //but not too much(bin3)
                                weightedCandidates = AssignWeights(candidates, 0.5f, 0.5f, 1.0f, 0.5f, 0.25f, cycle, accessibleSources.Count);

                                //it's also ok to place a NonKeyItem, wa have an incentive location to squander after all
                                //but don't make it too likely
                                weightedCandidates.AddRange(nonKeyCandidates.Select(i => (GetItemWeight(i, 0.5f, cycle, accessibleSources.Count), i)));
                            }
                            else
                            {
                                //We have at least 2 incentive locations to squander(considered a lot)
                                //We like reducing it by one(for variety)
                                //We like keeping it the same(for variety)
                                //We also like to increase it slightly(for variety)
                                //But we don't like to increase it a lot, but then again it's not a problem, so make it less likely
                                weightedCandidates = AssignWeights(candidates, 1.0f, 1.0f, 1.0f, 0.25f, 0.25f, cycle, accessibleSources.Count);

                                //Also we like placing NoneKeyItems
                                weightedCandidates.AddRange(nonKeyCandidates.Select(i => (GetItemWeight(i, 1.0f, cycle, accessibleSources.Count), i)));
                            }

                            //If we didn't find any candidates, but we have enough locations to place all remaining items, then we just do that
                            //Otherwise the algorithm would refuse to give up the last 2 incentive locations
                            if (weightedCandidates.Sum(i => i.weight) == 0 && incItemCount <= incSourceCount)
                            {
                                weightedCandidates.AddRange(allPlacements.Where(i => incentives.Contains(i)).Select(i => (1.0f, i)));
                            }

                            //If we don't have any candidates and not enough incentive locations to place all, we can't place an incentive in this cycle
                            if (weightedCandidates.Sum(i => i.weight) == 0)
                            {
                                //If it's at least cycle 15 and therefore no cycle based restricctions apply
                                //And in the last cycle we tried to place a NonIncentive
                                //we had all KI options available, but couldn't place any of them, so we're doomed
                                if (cycle >= 15 && state != PlacementState.Normal)
                                {
                                    placementFailed = true;
                                    break;
                                }

                                //since we couldn't place an incentive, we won't be able to do so next cycle(same results), so we don't try to do that next cycle
                                //This heavily speeds up the situations when there are very few loose(low chance of trying to place a loose), but a loose is required to go on.
                                state = PlacementState.NonIncentice;
                                continue;
                            }
                        }
                        else
                        {
                            //same as above, but for nonincentive items.
                            //The main difference is that the RewardSource thresholds are way higher
                            //While it's not a safety concern, cou generally want a lot more chest's available than neccesary to be in the confort zone
                            candidates = candidates.Where(c => !incentives.Contains(c.item)).ToList();
                            var nonKeyCandidates = allPlacements.Where(i => !allKeyItems.Contains(i) && !incentives.Contains(i));

                            if (nonSourceCount <= CriticalNonIncentiveSourceCount)
                            {
                                weightedCandidates = AssignWeights2(candidates, 0.0f, 0.5f, 1.0f, 1.0f, 0.5f, cycle, accessibleSources.Count);
                            }
                            else if (nonSourceCount <= SafeNonIncentiveSourceCount)
                            {
                                weightedCandidates = AssignWeights2(candidates, 0.5f, 0.5f, 1.0f, 0.5f, 0.25f, cycle, accessibleSources.Count);
                                weightedCandidates.AddRange(nonKeyCandidates.Select(i => (GetItemWeight(i, 0.5f, cycle, accessibleSources.Count), i)));
                            }
                            else
                            {
                                weightedCandidates = AssignWeights2(candidates, 1.0f, 1.0f, 1.0f, 0.25f, 0.25f, cycle, accessibleSources.Count);
                                weightedCandidates.AddRange(nonKeyCandidates.Select(i => (GetItemWeight(i, 1.0f, cycle, accessibleSources.Count), i)));
                            }

                            if (weightedCandidates.Sum(i => i.weight) == 0)
                            {
                                if (cycle >= 15 && state != PlacementState.Normal)
                                {
                                    placementFailed = true;
                                    break;
                                }

                                state = PlacementState.Incentive;
                                continue;
                            }
                        }

                        //The above weighting strategy works with this mindset:
                        //If it's an all incentive seed, we just need to manage the incentive locations
                        //If it's an all loose seed, we just need to manage the nonincentive locations
                        //If it's something in between, we switch between those two ideas.
                        //It works, because a nonincentive item goes into a nonincentive chest and can't negatively impact the incentive locations.
                        //and vice versa.
                        //There is a flaw in this simplistic approach. When placing a nonincentive, it could additionally weigh in if it's desired for incentive placment.
                        //But that's a thing for another time to think through

                        //remove the candidates with weight 0(should be unneccesary, but anyway)
                        weightedCandidates = weightedCandidates.Where(w => w.weight > 0).ToList();

                        //shuffle the list(should be totally unneccesary, but it makes me more comfortable)
                        weightedCandidates.Shuffle(rng);

                        //randomly select an item from the weighted list
                        Item nextPlacment = Item.None;

                        float sum = weightedCandidates.Sum(i => i.weight);

                        var r = rng.Next() / (float)uint.MaxValue * sum;

                        sum = 0.0f;
                        foreach (var s in weightedCandidates)
                        {
                            sum += s.weight;
                            if (r <= sum)
                            {
                                nextPlacment = s.item;
                                break;
                            }
                        }

                        //if we didn't find one, just select a random one(this shouldn't happen, but safe is safe)
                        if (nextPlacment == Item.None)
                        {
                            nextPlacment = weightedCandidates.PickRandom(rng).item;
                        }

                        //place the item(same code as in GuidedPlacement to have compatibility with other flags)
                        if (incentives.Contains(nextPlacment))
                        {
                            var rewardSources = accessibleSources.Where(s => incentiveLocationPool.Contains(s)).ToList();

                            //can't happen per definition. We already know, that there is a location available for the item(but in case anything unexpectedly goes wrong sure)
                            if (rewardSources.Count == 0)
                            {
                                continue;
                            }

                            var rewardSource = balancedPicker.Pick(rewardSources, false, _flags.LooseItemsSpreadPlacement, false, rng);
                            placedItems.Add(NewItemPlacement(rewardSource, nextPlacment));
                        }
                        else
                        {
                            var rewardSources = accessibleSources.Where(s => unincentivizedLocationPool.Contains(s)).ToList();
                            if (rewardSources.Count == 0)
                            {
                                continue;
                            }

                            var rewardSource = balancedPicker.Pick(rewardSources, _flags.LooseItemsForwardPlacement, _flags.LooseItemsSpreadPlacement, false, rng);
                            placedItems.Add(NewItemPlacement(rewardSource, nextPlacment));
                        }

                        //remove the item from the lists of items to place
                        allPlacements.Remove(nextPlacment);
                        allKeyItems.Remove(nextPlacment);

                        if (nextPlacment == Item.Ship)
                        {
                            BuildLogic(allRewardSources);
                        }

                        //we placed an item so we should randomly select incentive/nonincentive next cycle
                        state = PlacementState.Normal;

                        //we're finished
                        if (allPlacements.Count == 0)
                        {
                            break;
                        }
                    }

                    //we couldn't place all items or encountered a placement problem
                    if (allPlacements.Count > 0 || placementFailed)
                    {
                        continue;
                    }
                }

                //place the shards(almost same code as in GuidedPlacement)
                if (!placementFailed && shards.Count > 0)
                {
                    var leftoverItemLocations = GetAllAccessibleRewardSources(preBlackOrbLocationPool, placedItems);

                    foreach (var shard in shards)
                    {
                        if (!leftoverItemLocations.Any())
                        {
                            placementFailed = true;
                            break;
                        }

                        placedItems.Add(NewItemPlacement(leftoverItemLocations.SpliceRandom(rng), shard));
                    }
                }

                //finally check the placement(if we arrive here, it's safe, but let's do it anyway in case something goes wrong)
            } while (placementFailed || !_checker.CheckSanity(placedItems, null, _flags).Complete);

            return(new ItemPlacementResult {
                PlacedItems = placedItems, RemainingTreasures = treasurePool
            });
        }