protected RewardSourceBase(IRewardSource copyFromRewardSource, Item item) { Address = copyFromRewardSource.Address; Name = copyFromRewardSource.Name; Item = item; MapLocation = copyFromRewardSource.MapLocation; AccessRequirement = copyFromRewardSource.AccessRequirement; IsUnused = false; }
private void MoveShipToRewardSource(IRewardSource source) { Blob location = null; if (!ItemLocations.ShipLocations.TryGetValue(source.MapLocation, out location)) { location = Dock.Coneria; } Put(0x3000 + UnsramIndex.ShipX, location); }
public static IRewardSource NewItemPlacement(IRewardSource copyFromSource, Item newItem) { if (copyFromSource is MapObject) { return(new MapObject(copyFromSource as MapObject, newItem)); } else { return(new TreasureChest(copyFromSource, newItem)); } }
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 IEnumerable <IRewardSource> GetNearRewardSources(IEnumerable <IRewardSource> sources, IRewardSource current) { if (current is TreasureChest c) { var chests = sources.Select(r => r as TreasureChest).Where(r => r != null).ToDictionary(r => (byte)(r.Address - 0x3100)); var chest = (byte)(c.Address - 0x3100); foreach (var dungeon in Main.Dungeons) { var poi = dungeon.PointsOfInterest.Where(p => p.Type == SCPointOfInterestType.Treasure).FirstOrDefault(p => p.TreasureId == chest); if (poi != null) { return(dungeon.PointsOfInterest.Where(p => p.Type == SCPointOfInterestType.Treasure) .Where(p => p.BitFlagSet.ToString() == poi.BitFlagSet.ToString()) .Where(p => chests.ContainsKey(p.TreasureId)) .Select(p => chests[p.TreasureId]).ToList()); } } } return(Array.Empty <IRewardSource>()); }
public bool IsRewardSourceAccessible(IRewardSource source, AccessRequirement currentAccess, List <MapLocation> locations) { //if (currentAccess != requirements) throw new InvalidOperationException("no can do"); return(rewardSources.Contains(source)); }
public IEnumerable <IRewardSource> GetNearRewardSources(IEnumerable <IRewardSource> sources, IRewardSource source) { return(Array.Empty <IRewardSource>()); }
public bool IsRewardSourceAccessible(IRewardSource source, AccessRequirement currentAccess, List <MapLocation> locations) { return(locations.Contains(source.MapLocation) && currentAccess.HasFlag(source.AccessRequirement) && locations.Contains((source as MapObject)?.SecondLocation ?? MapLocation.StartingLocation)); }
public ItemShopSlot(IRewardSource copyFromRewardSource, Item item) : base(copyFromRewardSource, item) { }
public TreasureChest(IRewardSource copyFromRewardSource, Item item, AccessRequirement access) : base(copyFromRewardSource, item) { AccessRequirement = access; }
public TreasureChest(IRewardSource copyFromRewardSource, Item item) : base(copyFromRewardSource, item) { }
public IRewardSource Pick(List <IRewardSource> sources, bool forward, bool spread, bool incentive, MT19337 rng) { if (!sources.Any()) { return(null); } IRewardSource result = null; //This loop sums up all weights of the reward source. double sum = 0.0; foreach (var s in sources) { //If weight already exists for rewardsource use it if (weights.TryGetValue(s.Address, out var v)) { sum += v; } //If it doesn't have a weight and it's a chest, give it the weight 1 else if (s is TreasureChest) { sum += 1.0; weights.Add(s.Address, 1.0); } //If it's not a chest, give it a significantly higher weight. There are way more chests than nonchet rewards. //That way there is a reasonable chance, that some loose key items land on npcs. //changed nonchest weight is tied to the spread placement flag. else if (!incentive) { sum += nonchest; weights.Add(s.Address, nonchest); } else { sum += 1.0; weights.Add(s.Address, 1.0); } } //We pick a value between 0 and sum. We can't work with the full double range, but with reasonable weight reduction, it's not going to be a problem. var r = rng.Next() / (double)uint.MaxValue * sum; //This loop finds the reward source associated with the rng number. //If Forward placement is active, reduce the weight of all reward sources that were elligible for an item this step. //Normally chest like ToF or Matoya have a chance to roll a loose item at every step, because they are accessible from the start. //TFC is accessible way later so is used in way less rolls. //By reducing the weight on chests, that already had a chance, it's more likely to select a chest or npc from a newly opened up area(hence forward placement). //The forward placement is deactivated for incentive items, so it doesn't skew the placement. //Forward placement alone produces ..... results. sum = 0.0; foreach (var s in sources) { var v = weights[s.Address]; if (spread && forward) { weights[s.Address] = v * reduction; } sum += v; if (result == null && r <= sum) { result = s; } } //If we fail to find a reward source because of numerical problems, just pick a random one. It hasn't happend so far, but just to be sure. if (result == null) { result = sources.PickRandom(rng); } //The V2 builds a list of chests for each overworld entrance. GetNearRewardSources selects chests accessible from the same entrance with the same requirements. //So something like all accessible chests in marsh, or all keylocked chests in marsh. The function works for F/E. Otherwise V2 wouldn't work for F/E. //All chests found are demoted heavily. So the placement algorithm will only place another item there if it "has" to. //That spreads the key items out into different places(hence spread placement). //The spread placement remains active for incentive items. There is only one incentive location per dungeon and access requirement. //This has the effect of reducing the chance of a loose item in incentivized dungeons. //There is a small chance of two incentive locations in one dungeon with F/E. But the effect will not be noticable by any unaware observer. if (spread) { foreach (var s in checker.GetNearRewardSources(sources, result)) { if (weights.TryGetValue(s.Address, out var v)) { weights[s.Address] = v * reduction * reduction; } } } return(result); }