context: using System;
  using System.Collections.Generic;
  using System.Linq;
  
  using ACE.Common;
  using ACE.Database.Models.World;
  using ACE.Entity.Enum;
  using ACE.Entity.Models;
  using ACE.Server.Factories.Entity;
  using ACE.Server.Factories.Enum;
  using ACE.Server.Factories.Tables;
  using ACE.Server.WorldObjects;
  
  namespace ACE.Server.Factories
  {
      public partial class LootGenerationFactory
      {
          private static bool AssignMagic_New(WorldObject wo, TreasureDeath profile, TreasureRoll roll, out int numSpells)
          {
              var spells = RollSpells(wo, profile, roll);
  
              foreach (var spell in spells)
              {
                  wo.Biota.GetOrAddKnownSpell((int)spell, wo.BiotaDatabaseLock, out _);
              }
              numSpells = spells.Count;
              return true;
          }
  
          private static List<SpellId> RollSpells(WorldObject wo, TreasureDeath profile, TreasureRoll roll)
          {
              var spells = new List<SpellId>();
  
              // crowns, which are classified as TreasureItemType.Jewelry, should also be getting item spells
              // perhaps replace this with wo.ArmorLevel check?
              //if (roll.IsArmor || roll.IsArmorClothing(wo) || roll.IsWeapon)
              if (roll.HasArmorLevel(wo) || roll.IsWeapon)
              {
                  var itemSpells = RollItemSpells(wo, profile, roll);
  
                  if (itemSpells != null)
                      spells.AddRange(itemSpells);
              }
  
              var enchantments = RollEnchantments(wo, profile, roll);
  
              if (enchantments != null)
              {
                  spells.AddRange(enchantments);
  
                  roll.ItemDifficulty += RollEnchantmentDifficulty(enchantments);
              }
  
              var cantrips = RollCantrips(wo, profile, roll);
  
              if (cantrips != null)
              {
                  spells.AddRange(cantrips);
  
                  roll.ItemDifficulty += RollCantripDifficulty(cantrips);
              }
  
              return spells;
          }
  
          private static List<SpellId> RollItemSpells(WorldObject wo, TreasureDeath profile, TreasureRoll roll)
          {
              List<SpellId> spells = null;
  
              //if (roll.IsArmor || roll.IsArmorClothing(wo))
              if (roll.HasArmorLevel(wo))
              {
                  spells = ArmorSpells.Roll(profile);
              }
              else if (roll.IsMeleeWeapon)
              {
                  spells = MeleeSpells.Roll(profile);
              }
              else if (roll.IsMissileWeapon)
              {
                  spells = MissileSpells.Roll(profile);
              }
              else if (roll.IsCaster)
              {
                  spells = WandSpells.Roll(wo, profile);
              }
              else
              {
                  log.Error($"RollItemSpells({wo.Name}) - item is not clothing / armor / weapon");
                  return null;
              }
  
              return RollSpellLevels(wo, profile, spells);
          }
  
          private static List<SpellId> RollSpellLevels(WorldObject wo, TreasureDeath profile, IEnumerable<SpellId> spells)
          {
              var finalSpells = new List<SpellId>();
  
              foreach (var spell in spells)
              {
                  var spellLevel = SpellLevelChance.Roll(profile.Tier);
  
                  var spellLevels = SpellLevelProgression.GetSpellLevels(spell);
  
                  if (spellLevels.Count != 8)
                  {
                      log.Error($"RollSpellLevels({wo.Name}, {spell}) - spell level progression returned {spellLevels.Count}, expected 8");
                      continue;
                  }
  
                  finalSpells.Add(spellLevels[spellLevel - 1]);
              }
  
              return finalSpells;
          }
  
          private static List<SpellId> RollEnchantments(WorldObject wo, TreasureDeath profile, TreasureRoll roll)
          {
              /*if (wo.SpellSelectionCode == null)
              {
                  log.Warn($"RollEnchantments({wo.Name}) - missing spell selection code / PropertyInt.TsysMutationData");
                  return null;
              }*/
  
              // test method: determine spell selection code dynamically
              var spellSelectionCode = GetSpellSelectionCode_Dynamic(wo, roll);
  
              if (spellSelectionCode == 0)
                  return null;
  
              //Console.WriteLine($"Using spell selection code {spellSelectionCode} for {wo.Name}");
  
              var numEnchantments = RollNumEnchantments(wo, profile, roll);
  
              if (numEnchantments <= 0)
                  return null;
  
              var numAttempts = numEnchantments * 3;
  
              var spells = new HashSet<SpellId>();
  
              for (var i = 0; i < numAttempts && spells.Count < numEnchantments; i++)
              {
                  var spell = SpellSelectionTable.Roll(spellSelectionCode);
  
                  if (spell != SpellId.Undef)
                      spells.Add(spell);
              }
  
              return RollSpellLevels(wo, profile, spells);
          }
  
          private static int RollNumEnchantments(WorldObject wo, TreasureDeath profile, TreasureRoll roll)
          {
              if (roll.IsArmor || roll.IsWeapon)
              {
                  return RollNumEnchantments_Armor_Weapon(wo, profile, roll);
              }
              // confirmed:
              // - crowns (classified as TreasureItemType.Jewelry) used this table
              // - clothing w/ al also used this table
              else if (roll.IsClothing || roll.IsJewelry || roll.IsDinnerware)
              {
                  return RollNumEnchantments_Clothing_Jewelry_Dinnerware(wo, profile, roll);
              }
              else
              {
                  log.Warn($"RollNumEnchantments({wo.Name}, {profile.TreasureType}, {roll.ItemType}) - unknown item type");
                  return 1;   // gems?
              }
          }
  
          private static readonly List<float> EnchantmentChances_Armor_MeleeMissileWeapon = new List<float>()
          {
              0.00f,  // T1
              0.05f,  // T2
              0.10f,  // T3
              0.20f,  // T4
              0.40f,  // T5
              0.60f,  // T6
              0.60f,  // T7
              0.60f,  // T8
          };
  
          private static readonly List<float> EnchantmentChances_Caster = new List<float>()
          {
              0.60f,  // T1
              0.60f,  // T2
              0.60f,  // T3
              0.60f,  // T4
              0.60f,  // T5
              0.75f,  // T6
              0.75f,  // T7
              0.75f,  // T8
          };
  
          private static int RollNumEnchantments_Armor_Weapon(WorldObject wo, TreasureDeath profile, TreasureRoll roll)
          {
              var tierChances = roll.IsCaster ? EnchantmentChances_Caster : EnchantmentChances_Armor_MeleeMissileWeapon;
  
              var chance = tierChances[profile.Tier - 1];
  
              var rng = ThreadSafeRandom.NextInterval(profile.LootQualityMod);
  
              if (rng < chance)
                  return 1;
              else
                  return 0;
          }
  
          private static int RollNumEnchantments_Clothing_Jewelry_Dinnerware(WorldObject wo, TreasureDeath profile, TreasureRoll roll)
          {
              var chance = 0.1f;
  
              var rng = ThreadSafeRandom.NextInterval(profile.LootQualityMod);
  
              if (rng >= chance)
                  return 1;
              else if (profile.Tier < 6)
                  return 2;
  
              // tier 6+ has a chance for 3 enchantments
              rng = ThreadSafeRandom.NextInterval(profile.LootQualityMod * 0.1f);
  
              if (rng >= chance * 0.5f)
                  return 2;
              else
                  return 3;
          }
  
          private static float RollEnchantmentDifficulty(List<SpellId> spellIds)
          {
              var spells = new List<Server.Entity.Spell>();
  
              foreach (var spellId in spellIds)
              {
                  var spell = new Server.Entity.Spell(spellId);
                  spells.Add(spell);
              }
  
              spells = spells.OrderBy(i => i.Formula.Level).ToList();
  
              var itemDifficulty = 0.0f;
  
              // exclude highest spell
              for (var i = 0; i < spells.Count - 1; i++)
              {
  
gt:                 var spell = spells[i];