using System; using ACE.Common; using ACE.Database.Models.World; using ACE.Entity.Enum; using ACE.Entity.Enum.Properties; using ACE.Server.Entity; using ACE.Server.Entity.Mutations; using ACE.Server.Factories.Entity; using ACE.Server.Factories.Enum; using ACE.Server.Factories.Tables; using ACE.Server.Factories.Tables.Wcids; using ACE.Server.WorldObjects; namespace ACE.Server.Factories { public static partial class LootGenerationFactory { /// /// This is only called by /testlootgen command /// The actual lootgen system doesn't use this. /// private static WorldObject CreateArmor(TreasureDeath profile, bool isMagical, bool isArmor) { var itemType = isArmor ? TreasureItemType.Armor : TreasureItemType.Clothing; var treasureRoll = new TreasureRoll(itemType); if (isArmor) { treasureRoll.ArmorType = ArmorTypeChance.Roll(profile.Tier); treasureRoll.Wcid = ArmorWcids.Roll(profile, ref treasureRoll.ArmorType); } else treasureRoll.Wcid = ClothingWcids.Roll(profile); var wo = WorldObjectFactory.CreateNewWorldObject((uint)treasureRoll.Wcid); treasureRoll.BaseArmorLevel = wo.ArmorLevel ?? 0; MutateArmor(wo, profile, isMagical, treasureRoll); return wo; } private static void MutateArmor(WorldObject wo, TreasureDeath profile, bool isMagical, TreasureRoll roll) { // material type var materialType = GetMaterialType(wo, profile.Tier); if (materialType > 0) wo.MaterialType = materialType; // item color MutateColor(wo); // gem count / gem material if (wo.GemCode != null) wo.GemCount = GemCountChance.Roll(wo.GemCode.Value, profile.Tier); else wo.GemCount = ThreadSafeRandom.Next(1, 6); wo.GemType = RollGemType(profile.Tier); // workmanship wo.ItemWorkmanship = WorkmanshipChance.Roll(profile.Tier); // burden if (wo.HasMutateFilter(MutateFilter.EncumbranceVal)) // fixme: data MutateBurden(wo, profile, false); if (profile.Tier > 6 && !wo.HasArmorLevel()) { // normally this is handled in the mutation script for armor // for clothing, just calling the generic method here RollWieldLevelReq_T7_T8(wo, profile); } AssignArmorLevel(wo, profile, roll); if (wo.HasMutateFilter(MutateFilter.ArmorModVsType)) MutateArmorModVsType(wo, profile); if (isMagical) { AssignMagic(wo, profile, roll, true); } else { wo.ItemManaCost = null; wo.ItemMaxMana = null; wo.ItemCurMana = null; wo.ItemSpellcraft = null; wo.ItemDifficulty = null; } if (profile.Tier > 6 && !roll.ArmorType.IsSocietyArmor()) wo.EquipmentSetId = EquipmentSetChance.Roll(wo, profile, roll); if (profile.Tier == 8) TryMutateGearRating(wo, profile, roll); // item value //if (wo.HasMutateFilter(MutateFilter.Value)) // fixme: data MutateValue(wo, profile.Tier, roll); wo.LongDesc = GetLongDesc(wo); } private static bool AssignArmorLevel(WorldObject wo, TreasureDeath profile, TreasureRoll roll) { // retail was only divied up into a few different mutation scripts here // anything with ArmorLevel ran these mutation scripts // anything that covered extremities (head / hand / foot wear) started with a slightly higher base AL, // but otherwise used the same mutation as anything that covered non-extremities // shields also had their own mutation script // only exceptions found: covenant armor, olthoi armor, metal cap if (!roll.HasArmorLevel(wo)) return false; var scriptName = GetMutationScript_ArmorLevel(wo, roll); if (scriptName == null) { log.Error($"AssignArmorLevel({wo.Name}, {profile.TreasureType}, {roll.ItemType}) - unknown item type"); return false; } // persist original values for society armor var wieldRequirements = wo.WieldRequirements; var wieldSkillType = wo.WieldSkillType; var wieldDifficulty = wo.WieldDifficulty; //Console.WriteLine($"Mutating {wo.Name} with {scriptName}"); var mutationFilter = MutationCache.GetMutation(scriptName); var success = mutationFilter.TryMutate(wo, profile.Tier); if (roll.ArmorType.IsSocietyArmor()) { wo.WieldRequirements = wieldRequirements; wo.WieldSkillType = wieldSkillType; wo.WieldDifficulty = wieldDifficulty; } return success; } private static string GetMutationScript_ArmorLevel(WorldObject wo, TreasureRoll roll) { switch (roll.ArmorType) { case TreasureArmorType.Covenant: if (wo.IsShield) return "ArmorLevel.covenant_shield.txt"; else return "ArmorLevel.covenant_armor.txt"; case TreasureArmorType.Olthoi: if (wo.IsShield) return "ArmorLevel.olthoi_shield.txt"; else return "ArmorLevel.olthoi_armor.txt"; } if (wo.IsShield) return "ArmorLevel.shield_level.txt"; var coverage = wo.ClothingPriority ?? 0; if ((coverage & (CoverageMask)CoverageMaskHelper.Extremities) != 0) return "ArmorLevel.armor_level_extremity.txt"; else if ((coverage & (CoverageMask)CoverageMaskHelper.Outerwear) != 0) return "ArmorLevel.armor_level_non_extremity.txt"; else return null; } private static void MutateArmorModVsType(WorldObject wo, TreasureDeath profile) { // for the PropertyInt.MutateFilters found in py16 data, // items either had all of these, or none of these // only the elemental types could mutate TryMutateArmorModVsType(wo, profile, PropertyFloat.ArmorModVsFire); TryMutateArmorModVsType(wo, profile, PropertyFloat.ArmorModVsCold); TryMutateArmorModVsType(wo, profile, PropertyFloat.ArmorModVsAcid); TryMutateArmorModVsType(wo, profile, PropertyFloat.ArmorModVsElectric); } private static bool TryMutateArmorModVsType(WorldObject wo, TreasureDeath profile, PropertyFloat prop) { var armorModVsType = wo.GetProperty(prop); if (armorModVsType == null) return false; // perform the initial roll to determine if this ArmorModVsType will mutate var mutate = ArmorModVsTypeChance.Roll(profile.Tier); if (!mutate) return false; // get quality level 1-5 for tier var qualityLevel = ArmorModVsTypeChance.RollQualityLevel(profile); // add in rng // for t6+ / max quality level 5, the highest bonus found in eor data was ~0.9 var rng = ThreadSafeRandom.Next(-0.05f, 0.15f); var bonusRL = qualityLevel * 0.15f + rng; //Console.WriteLine($"Boosting {wo.Name}.{prop} by {bonusRL}"); armorModVsType += bonusRL; // ensure between -2.0 / 2.0? armorModVsType = Math.Clamp(armorModVsType.Value, -2.0f, 2.0f); wo.SetProperty(prop, armorModVsType.Value); return true; } private static void MutateValue_Armor(WorldObject wo) { var bulkMod = wo.BulkMod ?? 1.0f; var sizeMod = wo.SizeMod ?? 1.0f; var armorLevel = wo.ArmorLevel ?? 0; // from the py16 mutation scripts //wo.Value += (int)(armorLevel * armorLevel / 10.0f * bulkMod * sizeMod); // still probably not how retail did it // modified for armor values to match closer to retail pcaps var minRng = (float)Math.Min(bulkMod, sizeMod); var maxRng = (float)Math.Max(bulkMod, sizeMod); var rng = ThreadSafeRandom.Next(minRng, maxRng); wo.Value += (int)(armorLevel * armorLevel / 10.0f * rng); } private static bool TryMutateGearRating(WorldObject wo, TreasureDeath profile, TreasureRoll roll) { if (profile.Tier != 8) return false; // shields don't have gear ratings if (wo.IsShield) return false; var gearRating = GearRatingChance.Roll(wo, profile, roll); if (gearRating == 0) return false; //Console.WriteLine($"TryMutateGearRating({wo.Name}, {profile.TreasureType}, {roll.ItemType}): rolled gear rating {gearRating}"); var rng = ThreadSafeRandom.Next(0, 1); if (roll.HasArmorLevel(wo)) { // clothing w/ al, and crowns would be included in this group if (rng == 0) wo.GearCritDamage = gearRating; else wo.GearCritDamageResist = gearRating; } else if (roll.IsClothing || roll.IsCloak) { if (rng == 0) wo.GearDamage = gearRating; else wo.GearDamageResist = gearRating; } else if (roll.IsJewelry) { if (rng == 0) wo.GearHealingBoost = gearRating; else wo.GearMaxHealth = gearRating; } else { log.Error($"TryMutateGearRating({wo.Name}, {profile.TreasureType}, {roll.ItemType}): unknown item type"); return false; } // ensure wield requirement is level 180? if (roll.ArmorType != TreasureArmorType.Society) SetWieldLevelReq(wo, 180); return true; } private static void SetWieldLevelReq(WorldObject wo, int level) { if (wo.WieldRequirements == WieldRequirement.Invalid) { wo.WieldRequirements = WieldRequirement.Level; wo.WieldSkillType = (int)Skill.Axe; // set from examples in pcap data wo.WieldDifficulty = level; } else if (wo.WieldRequirements == WieldRequirement.Level) { if (wo.WieldDifficulty < level) wo.WieldDifficulty = level; } else { // this can either be empty, or in the case of covenant / olthoi armor, // it could already contain a level requirement of 180, or possibly 150 in tier 8 // we want to set this level requirement to 180, in all cases // magloot logs indicated that even if covenant / olthoi armor was not upgraded to 180 in its mutation script, // a gear rating could still drop on it, and would "upgrade" the 150 to a 180 wo.WieldRequirements2 = WieldRequirement.Level; wo.WieldSkillType2 = (int)Skill.Axe; // set from examples in pcap data wo.WieldDifficulty2 = level; } } /// /// This is only called by /testlootgen command /// The actual lootgen system doesn't use this. /// private static WorldObject CreateCloak(TreasureDeath profile, bool mutate = true) { var cloakWeenie = CloakWcids.Roll(); var wo = WorldObjectFactory.CreateNewWorldObject((uint)cloakWeenie); if (wo != null && mutate) MutateCloak(wo, profile); return wo; } private static void MutateCloak(WorldObject wo, TreasureDeath profile, TreasureRoll roll = null) { wo.ItemMaxLevel = CloakChance.Roll_ItemMaxLevel(profile); // wield difficulty, based on ItemMaxLevel switch (wo.ItemMaxLevel) { case 1: wo.WieldDifficulty = 30; break; case 2: wo.WieldDifficulty = 60; break; case 3: wo.WieldDifficulty = 90; break; case 4: wo.WieldDifficulty = 120; break; case 5: wo.WieldDifficulty = 150; break; } wo.IconOverlayId = IconOverlay_ItemMaxLevel[wo.ItemMaxLevel.Value - 1]; // equipment set wo.EquipmentSetId = CloakChance.RollEquipmentSet(); // proc spell var surgeSpell = CloakChance.RollProcSpell(); if (surgeSpell != SpellId.Undef) { wo.ProcSpell = (uint)surgeSpell; // Cloaked In Skill is the only self-targeted spell if (wo.ProcSpell == (uint)SpellId.CloakAllSkill) wo.ProcSpellSelfTargeted = true; else wo.ProcSpellSelfTargeted = false; wo.CloakWeaveProc = 1; } else { // Damage Reduction proc wo.CloakWeaveProc = 2; } // material type wo.MaterialType = GetMaterialType(wo, profile.Tier); // workmanship wo.Workmanship = WorkmanshipChance.Roll(profile.Tier); if (roll != null && profile.Tier == 8) TryMutateGearRating(wo, profile, roll); // item value //if (wo.HasMutateFilter(MutateFilter.Value)) MutateValue(wo, profile.Tier, roll); } } }