public List <IRewardSource> PlaceSaneItems(MT19337 rng, FF1Rom rom) { _rom = rom; var incentivePool = _incentivesData.IncentiveItems.Where(x => _allTreasures.Contains(x)).ToList(); var forcedItems = _incentivesData.ForcedItemPlacements.ToList(); var removedItems = _incentivesData.RemovedItems.ToList(); var unincentivizedQuestItems = ItemLists.AllQuestItems .Where(x => !incentivePool.Contains(x) && _allTreasures.Contains(x) && !forcedItems.Any(y => y.Item == x) && !removedItems.Contains(x)) .ToList(); var shards = new List <Item>(); var treasurePool = _allTreasures.ToList(); bool jingleGuaranteedDefenseItem = true; bool jingleGuaranteedPowerItem = true; if (_flags.GuaranteedDefenseItem != GuaranteedDefenseItem.None && !(_flags.ItemMagicMode == ItemMagicMode.None) && !incentivePool.Contains(Item.PowerRod)) { unincentivizedQuestItems.Add(Item.PowerRod); jingleGuaranteedDefenseItem = false; } if (_flags.GuaranteedPowerItem != GuaranteedPowerItem.None && !(_flags.ItemMagicMode == ItemMagicMode.None) && !incentivePool.Contains(Item.PowerGauntlets)) { unincentivizedQuestItems.Add(Item.PowerGauntlets); jingleGuaranteedPowerItem = false; } 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); } foreach (var item in removedItems) { treasurePool.Remove(item); } while (treasurePool.Remove(Item.Shard)) { shards.Add(Item.Shard); } ItemPlacementContext ctx = new ItemPlacementContext { Forced = forcedItems, Incentivized = incentivePool, Unincentivized = unincentivizedQuestItems, Shards = shards, Removed = removedItems, AllTreasures = treasurePool.ToList(), }; ItemPlacementResult result = DoSanePlacement(rng, ctx); List <IRewardSource> placedItems = result.PlacedItems; treasurePool = result.RemainingTreasures; //setup jingle for "incentive treasures", the placed items should just be key items, loose incentive items if (_flags.IncentiveChestItemsFanfare) { foreach (var placedItem in placedItems) { //dont make shards jingle that'd be annoying //dont make free items that get replaced, aka cabins, jingle if (placedItem is TreasureChest && placedItem.Item != Item.Shard && placedItem.Item != ReplacementItem) { if ((placedItem.Item == Item.PowerGauntlets && !jingleGuaranteedPowerItem) || (placedItem.Item == Item.PowerRod && !jingleGuaranteedDefenseItem)) { continue; } rom.Put(placedItem.Address - FF1Rom.TreasureOffset + FF1Rom.TreasureJingleOffset, new byte[] { (byte)placedItem.Item }); } } } // 8. Place all remaining unincentivized treasures or incentivized non-quest items that weren't placed var itemLocationPool = _incentivesData.AllValidItemLocations.ToList(); itemLocationPool = itemLocationPool.Where(x => !x.IsUnused && !placedItems.Any(y => y.Address == x.Address)).ToList(); MoreConsumableChests.Work(_flags, treasurePool, rng); if ((bool)_flags.GuaranteedMasamune && !incentivePool.Contains(Item.Masamune) && (bool)_flags.SendMasamuneHome) { // Remove Masamune from treasure pool (This will also causes Masamune to not be placed by RandomLoot) treasurePool.Remove(Item.Masamune); } // Use cabins to balance item population int poolDifference = itemLocationPool.Count() - treasurePool.Count(); if (poolDifference < 0) { for (int i = 0; i > poolDifference; i--) { treasurePool.Remove(Item.Cabin); } } else if (poolDifference > 0) { treasurePool.AddRange(Enumerable.Repeat(Item.Cabin, poolDifference)); } Debug.Assert(treasurePool.Count() == itemLocationPool.Count()); List <IRewardSource> normalTreasures = new(); var randomizeTreasureMode = (_flags.RandomizeTreasure == RandomizeTreasureMode.Random) ? (RandomizeTreasureMode)rng.Between(0, 2) : _flags.RandomizeTreasure; if (randomizeTreasureMode != RandomizeTreasureMode.None) { IItemGenerator generator; // We want to leave out anything incentivized (and thus already placed), but // add all the other stuff that you can't find in vanilla. var randomTreasure = treasurePool.ToList(); if (randomizeTreasureMode == RandomizeTreasureMode.DeepDungeon) { generator = new DeepDungeonItemGenerator(itemLocationPool, rom.UnusedGoldItems, removedItems, normalTreasures, (_flags.GameMode == GameModes.DeepDungeon), _flags.Etherizer, rom); } else { generator = new ItemGenerator(randomTreasure, rom.UnusedGoldItems, removedItems, _flags.WorldWealth); } treasurePool = treasurePool.Select(treasure => generator.GetItem(rng)).ToList(); } if ((bool)_flags.BetterTrapChests && randomizeTreasureMode != RandomizeTreasureMode.DeepDungeon) { // First we'll make a list of all 'notable' treasure. var notableTreasureList = new List <Item>() .Concat(ItemLists.UberTier) .Concat(ItemLists.LegendaryWeaponTier) .Concat(ItemLists.LegendaryArmorTier) .Concat(ItemLists.RareWeaponTier) .Concat(ItemLists.RareArmorTier); // Convert the list to a HashSet since we'll be doing lookups in it. var notableTreasure = new HashSet <Item>(notableTreasureList); // We sort the treasure pool based on value (sort of) and pull out the highest ranked ones to put // in the trap chests we picked out. var notableTreasurePool = treasurePool.Where(item => notableTreasure.Contains(item)).ToList(); // Since some chests might be incentivized, remove those that aren't in the pool. var trapChestPool = TrapChests.Where(chest => itemLocationPool.Contains(chest)); foreach (var chest in trapChestPool) { // It seems unlikely that is possible, but just in case. if (!notableTreasurePool.Any()) { break; } // Pick a random treasure and place it. var treasure = notableTreasurePool.SpliceRandom(rng); placedItems.Add(NewItemPlacement(chest, treasure)); // Since it was placed, remove both the item and location from the remaining pool. treasurePool.Remove(treasure); itemLocationPool.Remove(chest); } // This should still be true at the end, so make sure it is Debug.Assert(treasurePool.Count() == itemLocationPool.Count()); } if (randomizeTreasureMode == RandomizeTreasureMode.DeepDungeon && _flags.DeepDungeonGenerator == DeepDungeonGeneratorMode.Progressive) { placedItems.AddRange(normalTreasures); } else { treasurePool.Shuffle(rng); itemLocationPool.Shuffle(rng); var leftovers = treasurePool.Zip(itemLocationPool, (treasure, location) => NewItemPlacement(location, treasure)); placedItems.AddRange(leftovers); } //chest order placement var chestOrderPool = treasurePool; if (_flags.ShardHunt) { //add the shards back into the chest order pool chestOrderPool = chestOrderPool.Concat(shards).ToList(); } chestOrderPool.Shuffle(rng); for (int i = 0; i < chestOrderPool.Count; i++) { rom.Put(FF1Rom.TreasureChestOrderOffset + i, new byte[] { (byte)chestOrderPool[i] }); } return(placedItems); }
public List <IRewardSource> PlaceSaneItems(MT19337 rng) { var incentivePool = _incentivesData.IncentiveItems.Where(x => _allTreasures.Contains(x)).ToList(); var forcedItems = _incentivesData.ForcedItemPlacements.ToList(); var unincentivizedQuestItems = ItemLists.AllQuestItems .Where(x => !incentivePool.Contains(x) && _allTreasures.Contains(x) && !forcedItems.Any(y => y.Item == x)) .ToList(); var treasurePool = _allTreasures.ToList(); if ((bool)_flags.GuaranteedRuseItem) { unincentivizedQuestItems.Add(Item.PowerRod); } 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); } ItemPlacementContext ctx = new ItemPlacementContext { Forced = forcedItems, Incentivized = incentivePool, Unincentivized = unincentivizedQuestItems, AllTreasures = treasurePool.ToList(), }; ItemPlacementResult result = DoSanePlacement(rng, ctx); var placedItems = result.PlacedItems; treasurePool = result.RemainingTreasures; if ((bool)_flags.FreeBridge) { placedItems = placedItems.Select(x => x.Item != Item.Bridge ? x : NewItemPlacement(x, ReplacementItem)).ToList(); } if ((bool)_flags.FreeAirship) { placedItems = placedItems.Select(x => x.Item != Item.Floater ? x : NewItemPlacement(x, ReplacementItem)).ToList(); } if ((bool)_flags.FreeShip) { placedItems = placedItems.Select(x => x.Item != Item.Ship ? x : NewItemPlacement(x, ReplacementItem)).ToList(); } if ((bool)_flags.FreeCanal) { placedItems = placedItems.Select(x => x.Item != Item.Canal ? x : NewItemPlacement(x, ReplacementItem)).ToList(); } if ((bool)_flags.FreeCanoe) { placedItems = placedItems.Select(x => x.Item != Item.Canoe ? x : NewItemPlacement(x, ReplacementItem)).ToList(); } if ((bool)_flags.FreeLute) { placedItems = placedItems.Select(x => x.Item != Item.Lute ? x : NewItemPlacement(x, ReplacementItem)).ToList(); } if ((bool)_flags.FreeTail || (bool)_flags.NoTail) { placedItems = placedItems.Select(x => x.Item != Item.Tail ? x : NewItemPlacement(x, ReplacementItem)).ToList(); } if (_flags.Spoilers || 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 (_overworldMap.FullLocationRequirements.TryGetValue(item.MapLocation, out var flr)) { var overworldLocation = item.MapLocation.ToString(); if (_overworldMap.OverriddenOverworldLocations != null && _overworldMap.OverriddenOverworldLocations.TryGetValue(item.MapLocation, out var overriden)) { overworldLocation = overriden.ToString(); } var itemStr = item.Item.ToString().PadRight(9); var locStr = $"{overworldLocation} -> {item.MapLocation} -> {item.Name} ".PadRight(60); var changes = $"({String.Join(" OR ", 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 var itemLocationPool = _incentivesData.AllValidItemLocations.ToList(); itemLocationPool = itemLocationPool.Where(x => !x.IsUnused && !placedItems.Any(y => y.Address == x.Address)).ToList(); MoreConsumableChests.Work(_flags, treasurePool, rng); if ((bool)_flags.NoMasamune) { // Remove Masamune chest from shuffle treasurePool.Remove(Item.Masamune); treasurePool.Add(Item.Cabin); } else if ((bool)_flags.GuaranteedMasamune) { // Remove Masamune chest from shuffle, Remove Cabin from item pool itemLocationPool = itemLocationPool.Where(x => !x.Equals(ItemLocations.ToFRMasmune)).ToList(); treasurePool.Remove(Item.Cabin); // Send Masamune Home is ignored when Masamune is incentivized if (!incentivePool.Contains(Item.Masamune)) { if ((bool)_flags.SendMasamuneHome) { // Remove Masamune from treasure pool (This will also causes Masamune to not be placed by RandomLoot) treasurePool.Remove(Item.Masamune); treasurePool.Add(Item.Cabin); } } } foreach (var placedItem in placedItems) { incentivePool.Remove(placedItem.Item); } treasurePool.AddRange(incentivePool); Debug.Assert(treasurePool.Count() == itemLocationPool.Count()); if ((bool)_flags.RandomLoot) { // We want to leave out anything incentivized (and thus already placed), but // add all the other stuff that you can't find in vanilla. var randomTreasure = treasurePool.ToList(); randomTreasure.AddRange(ItemLists.CommonWeaponTier); randomTreasure.AddRange(ItemLists.CommonArmorTier); randomTreasure.Add(Item.CatClaw); randomTreasure.Add(Item.SteelArmor); ItemGenerator generator = new ItemGenerator(randomTreasure, _flags.WorldWealth); treasurePool = treasurePool.Select(treasure => generator.GetItem(rng)).ToList(); } if ((bool)_flags.BetterTrapChests) { // First we'll make a list of all 'notable' treasure. var notableTreasureList = new List <Item>() .Concat(ItemLists.UberTier) .Concat(ItemLists.LegendaryWeaponTier) .Concat(ItemLists.LegendaryArmorTier) .Concat(ItemLists.RareWeaponTier) .Concat(ItemLists.RareArmorTier); // Convert the list to a HashSet since we'll be doing lookups in it. var notableTreasure = new HashSet <Item>(notableTreasureList); // We sort the treasure pool based on value (sort of) and pull out the highest ranked ones to put // in the trap chests we picked out. var notableTreasurePool = treasurePool.Where(item => notableTreasure.Contains(item)).ToList(); // Since some chests might be incentivized, remove those that aren't in the pool. var trapChestPool = TrapChests.Where(chest => itemLocationPool.Contains(chest)); foreach (var chest in trapChestPool) { // It seems unlikely that is possible, but just in case. if (!notableTreasurePool.Any()) { break; } // Pick a random treasure and place it. var treasure = notableTreasurePool.SpliceRandom(rng); placedItems.Add(NewItemPlacement(chest, treasure)); // Since it was placed, remove both the item and location from the remaining pool. treasurePool.Remove(treasure); itemLocationPool.Remove(chest); } // This should still be true at the end, so make sure it is 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); }