using System; using System.Collections.Generic; using ModernUO.Serialization; using Server.Engines.Craft; using Server.Ethics; using Server.Factions; using Server.Network; namespace Server.Items { public enum ClothingQuality { Low, Regular, Exceptional } public interface IArcaneEquip { bool IsArcane { get; } int CurArcaneCharges { get; set; } int MaxArcaneCharges { get; set; } } [SerializationGenerator(7, false)] public abstract partial class BaseClothing : Item, IDyable, IScissorable, IFactionItem, ICraftable, IWearableDurability, IAosItem { [SerializableFieldSaveFlag(0)] private bool ShouldSerializeResource() => _resource != DefaultResource; [SerializedIgnoreDupe] [SerializableField(1, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosAttributes _attributes; [SerializableFieldSaveFlag(1)] private bool ShouldSerializeAttributes() => !_attributes.IsEmpty; [SerializableFieldDefault(1)] private AosAttributes AttributesDefaultValue() => new(this); [SerializedIgnoreDupe] [SerializableField(2, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosArmorAttributes _clothingAttributes; [SerializableFieldSaveFlag(2)] private bool ShouldSerializeClothingAttributes() => !_clothingAttributes.IsEmpty; [SerializableFieldDefault(2)] private AosArmorAttributes ClothingAttributesDefaultValue() => new(this); [SerializedIgnoreDupe] [SerializableField(3, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosSkillBonuses _skillBonuses; [SerializableFieldSaveFlag(3)] private bool ShouldSerializeSkillBonuses() => !_skillBonuses.IsEmpty; [SerializableFieldDefault(3)] private AosSkillBonuses SkillBonusesDefaultValue() => new(this); [SerializedIgnoreDupe] [SerializableField(4, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosElementAttributes _resistances; [SerializableFieldSaveFlag(4)] private bool ShouldSerializeResistances() => !_resistances.IsEmpty; [SerializableFieldDefault(4)] private AosElementAttributes ResistancesDefaultValue() => new(this); [EncodedInt] [InvalidateProperties] [SerializableField(5)] [SerializedCommandProperty(AccessLevel.GameMaster)] private int _maxHitPoints; [SerializableFieldSaveFlag(5)] private bool ShouldSerializeMaxHitPoints() => _maxHitPoints != 0; [SerializableField(7)] [SerializedCommandProperty(AccessLevel.GameMaster)] private bool _playerConstructed; [SerializableFieldSaveFlag(7)] private bool ShouldSerializePlayerConstructed() => _playerConstructed; [InvalidateProperties] [SerializableField(8)] [SerializedCommandProperty(AccessLevel.GameMaster)] private string _crafter; [SerializableFieldSaveFlag(8)] private bool ShouldSerializeCrafter() => !string.IsNullOrEmpty(_crafter); [InvalidateProperties] [SerializableField(9)] [SerializedCommandProperty(AccessLevel.GameMaster)] private ClothingQuality _quality = ClothingQuality.Regular; [SerializableFieldSaveFlag(9)] private bool ShouldSerializeQuality() => _quality != ClothingQuality.Regular; // Field 10 private int _strReq = -1; private FactionItem _factionState; public BaseClothing(int itemID, Layer layer, int hue = 0) : base(itemID) { Layer = layer; Hue = hue; _resource = DefaultResource; _hitPoints = _maxHitPoints = Utility.RandomMinMax(InitMinHits, InitMaxHits); Attributes = new AosAttributes(this); ClothingAttributes = new AosArmorAttributes(this); SkillBonuses = new AosSkillBonuses(this); Resistances = new AosElementAttributes(this); } [SerializableProperty(0)] [CommandProperty(AccessLevel.GameMaster)] public CraftResource Resource { get => _resource; set { _resource = value; Hue = CraftResources.GetHue(_resource); InvalidateProperties(); this.MarkDirty(); } } [SerializableProperty(10, useField: nameof(_strReq))] [CommandProperty(AccessLevel.GameMaster)] public int StrRequirement { get => _strReq == -1 ? Core.AOS ? AosStrReq : OldStrReq : _strReq; set { _strReq = value; InvalidateProperties(); this.MarkDirty(); } } [SerializableFieldSaveFlag(10)] private bool ShouldSerializeStrReq() => _strReq != -1; public virtual CraftResource DefaultResource => CraftResource.None; public virtual int BasePhysicalResistance => 0; public virtual int BaseFireResistance => 0; public virtual int BaseColdResistance => 0; public virtual int BasePoisonResistance => 0; public virtual int BaseEnergyResistance => 0; public override int PhysicalResistance => BasePhysicalResistance + Resistances.Physical; public override int FireResistance => BaseFireResistance + Resistances.Fire; public override int ColdResistance => BaseColdResistance + Resistances.Cold; public override int PoisonResistance => BasePoisonResistance + Resistances.Poison; public override int EnergyResistance => BaseEnergyResistance + Resistances.Energy; public virtual int ArtifactRarity => 0; public virtual int BaseStrBonus => 0; public virtual int BaseDexBonus => 0; public virtual int BaseIntBonus => 0; public virtual Race RequiredRace => null; public virtual int AosStrReq => 10; public virtual int OldStrReq => 0; public virtual bool AllowMaleWearer => true; public virtual bool AllowFemaleWearer => true; public virtual bool CanBeBlessed => true; public virtual int OnCraft( int quality, bool makersMark, Mobile from, CraftSystem craftSystem, Type typeRes, BaseTool tool, CraftItem craftItem, int resHue ) { Quality = (ClothingQuality)quality; if (makersMark) { Crafter = from.RawName; } if (DefaultResource != CraftResource.None) { var resourceType = typeRes ?? craftItem.Resources[0].ItemType; Resource = CraftResources.GetFromType(resourceType); } else { Hue = resHue; } PlayerConstructed = true; var context = craftSystem.GetContext(from); if (context?.DoNotColor == true) { Hue = 0; } return quality; } public virtual bool Dye(Mobile from, DyeTub sender) { if (Deleted) { return false; } if (RootParent is Mobile && from != RootParent) { return false; } Hue = sender.DyedHue; return true; } public FactionItem FactionItemState { get => _factionState; set { _factionState = value; if (_factionState == null) { Hue = 0; } LootType = _factionState == null ? LootType.Regular : LootType.Blessed; } } public virtual bool Scissor(Mobile from, Scissors scissors) { if (!IsChildOf(from.Backpack)) { from.SendLocalizedMessage(502437); // Items you wish to cut must be in your backpack. return false; } if (Ethic.IsImbued(this)) { from.SendLocalizedMessage(502440); // Scissors can not be used on that to produce anything. return false; } var system = DefTailoring.CraftSystem; var item = system.CraftItems.SearchFor(GetType()); if (item?.Resources.Count == 1) { var resource = item.Resources[0]; if (resource.Amount >= 2) { try { var info = CraftResources.GetInfo(_resource); Type resourceType = null; if (info?.ResourceTypes.Length > 0) { resourceType = info.ResourceTypes[0]; } var res = (resourceType ?? resource.ItemType).CreateInstance(); ScissorHelper(from, res, PlayerConstructed ? resource.Amount / 2 : 1); res.LootType = LootType.Regular; return true; } catch { // ignored } } } from.SendLocalizedMessage(502440); // Scissors can not be used on that to produce anything. return false; } public virtual bool CanFortify => true; [EncodedInt] [SerializableProperty(6)] [CommandProperty(AccessLevel.GameMaster)] public int HitPoints { get => _hitPoints; set { if (value != _hitPoints && MaxHitPoints > 0) { _hitPoints = value; if (_hitPoints < 0) { Delete(); } else if (_hitPoints > MaxHitPoints) { _hitPoints = MaxHitPoints; } InvalidateProperties(); this.MarkDirty(); } } } [SerializableFieldSaveFlag(6)] private bool ShouldSerializeHitPoints() => _hitPoints != 0; public virtual int InitMinHits => 0; public virtual int InitMaxHits => 0; public virtual int OnHit(BaseWeapon weapon, int damageTaken) { var absorbed = Utility.RandomMinMax(1, 4); // Don't go below zero damageTaken = Math.Min(absorbed, damageTaken); if (Utility.Random(100) < 25) // 25% chance to lower durability { if (Core.AOS && ClothingAttributes.SelfRepair > Utility.Random(10)) { HitPoints += 2; } else { int wear; if (weapon.Type == WeaponType.Bashing) { wear = absorbed / 2; } else { wear = Utility.Random(2); } if (wear > 0 && _maxHitPoints > 0) { if (_hitPoints >= wear) { HitPoints -= wear; wear = 0; } else { wear -= HitPoints; HitPoints = 0; } if (wear > 0) { if (_maxHitPoints > wear) { MaxHitPoints -= wear; (Parent as Mobile)?.LocalOverheadMessage( MessageType.Regular, 0x3B2, 1061121 ); // Your equipment is severely damaged. } else { Delete(); } } } } } return damageTaken; } public void UnscaleDurability() { var scale = 100 + ClothingAttributes.DurabilityBonus; _hitPoints = (_hitPoints * 100 + (scale - 1)) / scale; _maxHitPoints = (_maxHitPoints * 100 + (scale - 1)) / scale; InvalidateProperties(); this.MarkDirty(); } public void ScaleDurability() { var scale = 100 + ClothingAttributes.DurabilityBonus; _hitPoints = (_hitPoints * scale + 99) / 100; _maxHitPoints = (_maxHitPoints * scale + 99) / 100; InvalidateProperties(); this.MarkDirty(); } public override bool AllowSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) => Ethic.CheckTrade(from, to, newOwner, this) && base.AllowSecureTrade(from, to, newOwner, accepted); public override bool CanEquip(Mobile from) { if (!Ethic.CheckEquip(from, this)) { return false; } if (from.AccessLevel < AccessLevel.GameMaster) { if (RequiredRace != null && from.Race != RequiredRace) { if (RequiredRace == Race.Elf) { from.SendLocalizedMessage(1072203); // Only Elves may use this. } else { from.SendMessage($"Only {RequiredRace.PluralName} may use this."); } return false; } if (!AllowMaleWearer && !from.Female) { if (AllowFemaleWearer) { from.SendLocalizedMessage(1010388); // Only females can wear this. } else { from.SendMessage("You may not wear this."); } return false; } if (!AllowFemaleWearer && from.Female) { if (AllowMaleWearer) { from.SendLocalizedMessage(1063343); // Only males can wear this. } else { from.SendMessage("You may not wear this."); } return false; } var strBonus = ComputeStatBonus(StatType.Str); var strReq = ComputeStatReq(StatType.Str); if (from.Str < strReq || from.Str + strBonus < 1) { from.SendLocalizedMessage(500213); // You are not strong enough to equip that. return false; } } return base.CanEquip(from); } public int ComputeStatReq(StatType type) => AOS.Scale(StrRequirement, 100 - GetLowerStatReq()); public int ComputeStatBonus(StatType type) => type switch { StatType.Str => BaseStrBonus + Attributes.BonusStr, StatType.Dex => BaseDexBonus + Attributes.BonusDex, _ => BaseIntBonus + Attributes.BonusInt }; public virtual void AddStatBonuses(Mobile parent) { if (parent == null) { return; } var strBonus = ComputeStatBonus(StatType.Str); var dexBonus = ComputeStatBonus(StatType.Dex); var intBonus = ComputeStatBonus(StatType.Int); if (strBonus == 0 && dexBonus == 0 && intBonus == 0) { return; } var serial = Serial; if (strBonus != 0) { parent.AddStatMod(new StatMod(StatType.Str, $"{serial}Str", strBonus, TimeSpan.Zero)); } if (dexBonus != 0) { parent.AddStatMod(new StatMod(StatType.Dex, $"{serial}Dex", dexBonus, TimeSpan.Zero)); } if (intBonus != 0) { parent.AddStatMod(new StatMod(StatType.Int, $"{serial}Int", intBonus, TimeSpan.Zero)); } } public static void ValidateMobile(Mobile m) { for (var i = m.Items.Count - 1; i >= 0; --i) { if (i >= m.Items.Count) { continue; } var item = m.Items[i]; if (item is BaseClothing clothing) { if (clothing.RequiredRace != null && m.Race != clothing.RequiredRace) { if (clothing.RequiredRace == Race.Elf) { m.SendLocalizedMessage(1072203); // Only Elves may use this. } else { m.SendMessage($"Only {clothing.RequiredRace.PluralName} may use this."); } m.AddToBackpack(clothing); } else if (!clothing.AllowMaleWearer && !m.Female && m.AccessLevel < AccessLevel.GameMaster) { if (clothing.AllowFemaleWearer) { m.SendLocalizedMessage(1010388); // Only females can wear this. } else { m.SendMessage("You may not wear this."); } m.AddToBackpack(clothing); } else if (!clothing.AllowFemaleWearer && m.Female && m.AccessLevel < AccessLevel.GameMaster) { if (clothing.AllowMaleWearer) { m.SendLocalizedMessage(1063343); // Only males can wear this. } else { m.SendMessage("You may not wear this."); } m.AddToBackpack(clothing); } } } } public int GetLowerStatReq() => !Core.AOS ? 0 : ClothingAttributes.LowerStatReq; public override void OnAdded(IEntity parent) { if (parent is Mobile mob) { if (Core.AOS) { SkillBonuses.AddTo(mob); } AddStatBonuses(mob); mob.CheckStatTimers(); } base.OnAdded(parent); } public override void OnRemoved(IEntity parent) { if (parent is Mobile mob) { if (Core.AOS) { SkillBonuses.Remove(); } var serial = Serial; mob.RemoveStatMod($"{serial}Str"); mob.RemoveStatMod($"{serial}Dex"); mob.RemoveStatMod($"{serial}Int"); mob.CheckStatTimers(); } base.OnRemoved(parent); } public override void OnAfterDuped(Item newItem) { if (newItem is not BaseClothing clothing) { return; } clothing.Attributes = new AosAttributes(newItem, Attributes); clothing.Resistances = new AosElementAttributes(newItem, Resistances); clothing.SkillBonuses = new AosSkillBonuses(newItem, SkillBonuses); clothing.ClothingAttributes = new AosArmorAttributes(newItem, ClothingAttributes); // Set hue again because of resource clothing.Hue = Hue; // Set HP/Max again because of durability clothing.HitPoints = HitPoints; clothing.MaxHitPoints = MaxHitPoints; } public override bool AllowEquippedCast(Mobile from) => base.AllowEquippedCast(from) || Attributes.SpellChanneling != 0; public override bool CheckPropertyConflict(Mobile m) { if (base.CheckPropertyConflict(m)) { return true; } return Layer switch { Layer.Pants => m.FindItemOnLayer(Layer.InnerLegs) != null, Layer.Shirt => m.FindItemOnLayer(Layer.InnerTorso) != null, _ => false }; } public override void AddNameProperty(IPropertyList list) { var oreType = _resource switch { CraftResource.DullCopper => 1053108, CraftResource.ShadowIron => 1053107, CraftResource.Copper => 1053106, CraftResource.Bronze => 1053105, CraftResource.Gold => 1053104, CraftResource.Agapite => 1053103, CraftResource.Verite => 1053102, CraftResource.Valorite => 1053101, CraftResource.SpinedLeather => 1061118, CraftResource.HornedLeather => 1061117, CraftResource.BarbedLeather => 1061116, CraftResource.RedScales => 1060814, CraftResource.YellowScales => 1060818, CraftResource.BlackScales => 1060820, CraftResource.GreenScales => 1060819, CraftResource.WhiteScales => 1060821, CraftResource.BlueScales => 1060815, _ => 0 }; var name = Name; if (oreType != 0) { if (name != null) { list.Add(1053099, $"{oreType:#}\t{name}"); // ~1_oretype~ ~2_armortype~ } else { list.Add(1053099, $"{oreType:#}\t{LabelNumber:#}"); // ~1_oretype~ ~2_armortype~ } } else if (name == null) { list.Add(LabelNumber); } else { list.Add(name); } } public override void GetProperties(IPropertyList list) { base.GetProperties(list); if (_crafter != null) { list.Add(1050043, _crafter); // crafted by ~1_NAME~ } if (_factionState != null) { list.Add(1041350); // faction item } if (_quality == ClothingQuality.Exceptional) { list.Add(1060636); // exceptional } if (RequiredRaces == Race.AllowElvesOnly) { list.Add(1075086); // Elves Only } if (RequiredRaces == Race.AllowGargoylesOnly) { list.Add(1111709); // Gargoyles Only } SkillBonuses?.GetProperties(list); int prop; if ((prop = ArtifactRarity) > 0) { list.Add(1061078, prop); // artifact rarity ~1_val~ } if ((prop = Attributes.WeaponDamage) != 0) { list.Add(1060401, prop); // damage increase ~1_val~% } if ((prop = Attributes.DefendChance) != 0) { list.Add(1060408, prop); // defense chance increase ~1_val~% } if ((prop = Attributes.BonusDex) != 0) { list.Add(1060409, prop); // dexterity bonus ~1_val~ } if ((prop = Attributes.EnhancePotions) != 0) { list.Add(1060411, prop); // enhance potions ~1_val~% } if ((prop = Attributes.CastRecovery) != 0) { list.Add(1060412, prop); // faster cast recovery ~1_val~ } if ((prop = Attributes.CastSpeed) != 0) { list.Add(1060413, prop); // faster casting ~1_val~ } if ((prop = Attributes.AttackChance) != 0) { list.Add(1060415, prop); // hit chance increase ~1_val~% } if ((prop = Attributes.BonusHits) != 0) { list.Add(1060431, prop); // hit point increase ~1_val~ } if ((prop = Attributes.BonusInt) != 0) { list.Add(1060432, prop); // intelligence bonus ~1_val~ } if ((prop = Attributes.LowerManaCost) != 0) { list.Add(1060433, prop); // lower mana cost ~1_val~% } if ((prop = Attributes.LowerRegCost) != 0) { list.Add(1060434, prop); // lower reagent cost ~1_val~% } if ((prop = ClothingAttributes.LowerStatReq) != 0) { list.Add(1060435, prop); // lower requirements ~1_val~% } if ((prop = Attributes.Luck) != 0) { list.Add(1060436, prop); // luck ~1_val~ } if (ClothingAttributes.MageArmor != 0) { list.Add(1060437); // mage armor } if ((prop = Attributes.BonusMana) != 0) { list.Add(1060439, prop); // mana increase ~1_val~ } if ((prop = Attributes.RegenMana) != 0) { list.Add(1060440, prop); // mana regeneration ~1_val~ } if (Attributes.NightSight != 0) { list.Add(1060441); // night sight } if ((prop = Attributes.ReflectPhysical) != 0) { list.Add(1060442, prop); // reflect physical damage ~1_val~% } if ((prop = Attributes.RegenStam) != 0) { list.Add(1060443, prop); // stamina regeneration ~1_val~ } if ((prop = Attributes.RegenHits) != 0) { list.Add(1060444, prop); // hit point regeneration ~1_val~ } if ((prop = ClothingAttributes.SelfRepair) != 0) { list.Add(1060450, prop); // self repair ~1_val~ } if (Attributes.SpellChanneling != 0) { list.Add(1060482); // spell channeling } if ((prop = Attributes.SpellDamage) != 0) { list.Add(1060483, prop); // spell damage increase ~1_val~% } if ((prop = Attributes.BonusStam) != 0) { list.Add(1060484, prop); // stamina increase ~1_val~ } if ((prop = Attributes.BonusStr) != 0) { list.Add(1060485, prop); // strength bonus ~1_val~ } if ((prop = Attributes.WeaponSpeed) != 0) { list.Add(1060486, prop); // swing speed increase ~1_val~% } if (Core.ML && (prop = Attributes.IncreasedKarmaLoss) != 0) { list.Add(1075210, prop); // Increased Karma Loss ~1val~% } AddResistanceProperties(list); if ((prop = ClothingAttributes.DurabilityBonus) > 0) { list.Add(1060410, prop); // durability ~1_val~% } if ((prop = ComputeStatReq(StatType.Str)) > 0) { list.Add(1061170, prop); // strength requirement ~1_val~ } if (_hitPoints >= 0 && _maxHitPoints > 0) { list.Add(1060639, $"{_hitPoints}\t{_maxHitPoints}"); // durability ~1_val~ / ~2_val~ } } public override void OnSingleClick(Mobile from) { if (!Core.UOTD) { OnSingleClickPreUOTD(from); return; } var attrs = new List(); AddEquipInfoAttributes(from, attrs); int number; if (Name == null) { number = LabelNumber; } else { LabelTo(from, Name); number = 1041000; } if (attrs.Count == 0 && Crafter == null && Name != null) { return; } from.NetState.SendDisplayEquipmentInfo(Serial, number, _crafter, false, attrs); } public void OnSingleClickPreUOTD(Mobile from) { var name = Name; if (name == null) { var articleAnName = (TileData.ItemTable[ItemID].Flags & TileFlag.ArticleAn) != 0; name = $"{(articleAnName ? "an" : "a")} {Localization.GetText(LabelNumber).ToLowerInvariant()}"; } if (Crafter == null) { LabelTo(from, Quality == ClothingQuality.Exceptional ? $"{name} of exceptional quality" : name); return; } LabelTo( from, Quality == ClothingQuality.Exceptional ? $"{name} crafted with exceptional quality by {Crafter}" : $"{name} crafted by {Crafter}" ); } public virtual void AddEquipInfoAttributes(Mobile from, List attrs) { if (DisplayLootType) { if (LootType == LootType.Blessed) { attrs.Add(new EquipInfoAttribute(1038021)); // blessed } else if (LootType == LootType.Cursed) { attrs.Add(new EquipInfoAttribute(1049643)); // cursed } } if (_factionState != null) { attrs.Add(new EquipInfoAttribute(1041350)); // faction item } if (_quality == ClothingQuality.Exceptional) { attrs.Add(new EquipInfoAttribute(1018305 - (int)_quality)); } } public void DistributeBonuses(int amount) { for (var i = 0; i < amount; ++i) { switch (Utility.Random(5)) { case 0: ++Resistances.Physical; break; case 1: ++Resistances.Fire; break; case 2: ++Resistances.Cold; break; case 3: ++Resistances.Poison; break; case 4: ++Resistances.Energy; break; } } InvalidateProperties(); } private static bool GetSaveFlag(OldSaveFlag flags, OldSaveFlag toGet) => (flags & toGet) != 0; [AfterDeserialization] private void AfterDeserialization() { if (_maxHitPoints == 0 && _hitPoints == 0) { _hitPoints = _maxHitPoints = Utility.RandomMinMax(InitMinHits, InitMaxHits); } if (Parent is Mobile parent) { if (Core.AOS) { SkillBonuses.AddTo(parent); } AddStatBonuses(parent); parent.CheckStatTimers(); } } [Flags] private enum OldSaveFlag { None = 0x00000000, Resource = 0x00000001, Attributes = 0x00000002, ClothingAttributes = 0x00000004, SkillBonuses = 0x00000008, Resistances = 0x00000010, MaxHitPoints = 0x00000020, HitPoints = 0x00000040, PlayerConstructed = 0x00000080, Crafter = 0x00000100, Quality = 0x00000200, StrReq = 0x00000400 } } }