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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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; }
// 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(); }
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); }
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)); }