diff --git a/Projects/Server/Text/StringHelpers.cs b/Projects/Server/Text/StringHelpers.cs index 330458727..2b9349f6c 100644 --- a/Projects/Server/Text/StringHelpers.cs +++ b/Projects/Server/Text/StringHelpers.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Server.Buffers; +using Server.Text; namespace Server; @@ -286,4 +287,18 @@ public static class StringHelpers str.CopyTo(chars); return chars; } + + public static void AppendSpaceWithArticle(this ref ValueStringBuilder builder, string text, bool articleAn) + { + if (builder.Length != 0) + { + builder.Append(' '); + } + else + { + builder.Append(articleAn ? "an " : "a "); + } + + builder.Append(text); + } } diff --git a/Projects/UOContent/Items/Armor/BaseArmor.cs b/Projects/UOContent/Items/Armor/BaseArmor.cs index 069ed22a8..2fd01551a 100644 --- a/Projects/UOContent/Items/Armor/BaseArmor.cs +++ b/Projects/UOContent/Items/Armor/BaseArmor.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using ModernUO.Serialization; using Server.Engines.Craft; using Server.Ethics; using Server.Factions; using Server.Network; +using Server.Text; using AMA = Server.Items.ArmorMeditationAllowance; using AMT = Server.Items.ArmorMaterialType; @@ -559,6 +561,7 @@ namespace Server.Items Resource = CraftResources.GetFromType(resourceType); PlayerConstructed = true; + Identified = true; var context = craftSystem.GetContext(from); @@ -1451,6 +1454,12 @@ namespace Server.Items public override void OnSingleClick(Mobile from) { + if (!Core.UOTD) + { + OnSingleClickPreUOTD(from); + return; + } + var attrs = new List(); if (DisplayLootType) @@ -1482,13 +1491,12 @@ namespace Server.Items attrs.Add(new EquipInfoAttribute(1038000 + (int)_durability)); } - if (_protectionLevel > ArmorProtectionLevel.Regular && _protectionLevel <= ArmorProtectionLevel.Invulnerability) + if (_protectionLevel != ArmorProtectionLevel.Regular) { attrs.Add(new EquipInfoAttribute(1038005 + (int)_protectionLevel)); } } - else if (_durability != ArmorDurabilityLevel.Regular || _protectionLevel > ArmorProtectionLevel.Regular && - _protectionLevel <= ArmorProtectionLevel.Invulnerability) + else if (_durability != ArmorDurabilityLevel.Regular || _protectionLevel != ArmorProtectionLevel.Regular) { attrs.Add(new EquipInfoAttribute(1038000)); // Unidentified } @@ -1513,6 +1521,100 @@ namespace Server.Items from.NetState.SendDisplayEquipmentInfo(Serial, number, _crafter, false, attrs); } + public void OnSingleClickPreUOTD(Mobile from) + { + var isMagicItem = _durability != ArmorDurabilityLevel.Regular || + _protectionLevel != ArmorProtectionLevel.Regular; + + if (isMagicItem && !_identified) + { + LabelTo(from, $"an unidentified {Name ?? Localization.GetText(LabelNumber).ToLowerInvariant()}"); + return; + } + + var name = Name; + var articleAnName = (TileData.ItemTable[ItemID].Flags & TileFlag.ArticleAn) != 0; + + if (isMagicItem) + { + var builder = ValueStringBuilder.Create(128); + + var durabilityText = DurabilityText(out var articleAnDurability); + if (durabilityText != null) + { + builder.AppendSpaceWithArticle(durabilityText, articleAnDurability); + } + + if (name == null) + { + builder.AppendSpaceWithArticle(Localization.GetText(LabelNumber).ToLowerInvariant(), articleAnName); + } + else if (builder.Length != 0) + { + builder.Append($" {name}"); + } + else + { + builder.Append(name); + } + + var protectionText = ProtectionText; + if (protectionText != null) + { + builder.Append($" of {protectionText}"); + } + + LabelTo(from, builder.ToString()); + builder.Dispose(); + return; + } + + name ??= $"{(articleAnName ? "an" : "a")} {Localization.GetText(LabelNumber).ToLowerInvariant()}"; + + if (Crafter == null) + { + LabelTo(from, Quality == ArmorQuality.Exceptional ? $"{name} of exceptional quality" : name); + return; + } + + LabelTo( + from, + Quality == ArmorQuality.Exceptional + ? $"{name} crafted with exceptional quality by {Crafter}" + : $"{name} crafted by {Crafter}" + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private string DurabilityText(out bool articleAn) + { + articleAn = _durability is ArmorDurabilityLevel.Indestructible; + return _durability switch + { + ArmorDurabilityLevel.Durable => "durable", + ArmorDurabilityLevel.Substantial => "substantial", + ArmorDurabilityLevel.Massive => "massive", + ArmorDurabilityLevel.Fortified => "fortified", + ArmorDurabilityLevel.Indestructible => "indestructable", + _ => null + }; + } + + private string ProtectionText + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => + _protectionLevel switch + { + ArmorProtectionLevel.Defense => "defense", + ArmorProtectionLevel.Guarding => "guarding", + ArmorProtectionLevel.Hardening => "hardening", + ArmorProtectionLevel.Fortification => "fortification", + ArmorProtectionLevel.Invulnerability => "invulnerability", + _ => null + }; + } + [Flags] private enum OldSaveFlag { diff --git a/Projects/UOContent/Items/Clothing/BaseClothing.cs b/Projects/UOContent/Items/Clothing/BaseClothing.cs index 499a6ecad..5b249e490 100644 --- a/Projects/UOContent/Items/Clothing/BaseClothing.cs +++ b/Projects/UOContent/Items/Clothing/BaseClothing.cs @@ -907,8 +907,13 @@ namespace Server.Items public override void OnSingleClick(Mobile from) { - var attrs = new List(); + if (!Core.UOTD) + { + OnSingleClickPreUOTD(from); + return; + } + var attrs = new List(); AddEquipInfoAttributes(from, attrs); int number; @@ -931,6 +936,30 @@ namespace Server.Items 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) diff --git a/Projects/UOContent/Items/Wands/BaseWand.cs b/Projects/UOContent/Items/Wands/BaseWand.cs index 9d6b0c46e..126c777b1 100644 --- a/Projects/UOContent/Items/Wands/BaseWand.cs +++ b/Projects/UOContent/Items/Wands/BaseWand.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; using ModernUO.Serialization; using Server.Network; using Server.Spells; using Server.Targeting; +using Server.Text; namespace Server.Items; @@ -168,6 +171,12 @@ public abstract partial class BaseWand : BaseBashing public override void OnSingleClick(Mobile from) { + if (!Core.UOTD) + { + OnSingleClickPreUOTD(from); + return; + } + var attrs = new List(); if (DisplayLootType) @@ -230,6 +239,45 @@ public abstract partial class BaseWand : BaseBashing from.NetState.SendDisplayEquipmentInfo(Serial, number, Crafter, false, attrs); } + public void OnSingleClickPreUOTD(Mobile from) + { + var isMagicItem = _charges > 0; + var name = Name; + if (name == null) + { + var articleAnName = (TileData.ItemTable[ItemID].Flags & TileFlag.ArticleAn) != 0; + name = $"{(articleAnName ? "an" : "a")} {Localization.GetText(LabelNumber).ToLowerInvariant()}"; + } + + if (isMagicItem && !Identified) + { + LabelTo(from, $"an unidentified {name}"); + return; + } + + LabelTo(from, isMagicItem ? $"{name} of {WandEffectText}" : name); + } + + private string WandEffectText + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _wandEffect switch + { + WandEffect.Clumsiness => "clumsiness", + WandEffect.Identification => "identification", + WandEffect.Healing => "healing", + WandEffect.Feeblemindedness => "feeblemindedness", + WandEffect.Weakness => "weakness", + WandEffect.MagicArrow => "magic arrow", + WandEffect.Harming => "harming", + WandEffect.Fireball => "fireball", + WandEffect.GreaterHealing => "greater healing", + WandEffect.Lightning => "lightning", + WandEffect.ManaDraining => "mana draining", + _ => null + }; + } + public void Cast(Spell spell) { var m = Movable; diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index bf718aacf..6c32747fe 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection.Metadata; using System.Runtime.CompilerServices; using ModernUO.Serialization; using Server.Collections; @@ -17,6 +18,7 @@ using Server.Spells.Necromancy; using Server.Spells.Ninjitsu; using Server.Spells.Sixth; using Server.Spells.Spellweaving; +using Server.Text; namespace Server.Items; @@ -673,6 +675,7 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab } PlayerConstructed = true; + Identified = true; var resourceType = typeRes ?? craftItem.Resources[0].ItemType; @@ -728,21 +731,18 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab { case CraftResource.DullCopper: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Durable; AccuracyLevel = WeaponAccuracyLevel.Accurate; break; } case CraftResource.ShadowIron: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Durable; DamageLevel = WeaponDamageLevel.Ruin; break; } case CraftResource.Copper: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Fortified; DamageLevel = WeaponDamageLevel.Ruin; AccuracyLevel = WeaponAccuracyLevel.Surpassingly; @@ -750,7 +750,6 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab } case CraftResource.Bronze: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Fortified; DamageLevel = WeaponDamageLevel.Might; AccuracyLevel = WeaponAccuracyLevel.Surpassingly; @@ -758,7 +757,6 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab } case CraftResource.Gold: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Indestructible; DamageLevel = WeaponDamageLevel.Force; AccuracyLevel = WeaponAccuracyLevel.Eminently; @@ -766,7 +764,6 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab } case CraftResource.Agapite: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Indestructible; DamageLevel = WeaponDamageLevel.Power; AccuracyLevel = WeaponAccuracyLevel.Eminently; @@ -774,7 +771,6 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab } case CraftResource.Verite: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Indestructible; DamageLevel = WeaponDamageLevel.Power; AccuracyLevel = WeaponAccuracyLevel.Exceedingly; @@ -782,7 +778,6 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab } case CraftResource.Valorite: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Indestructible; DamageLevel = WeaponDamageLevel.Vanq; AccuracyLevel = WeaponAccuracyLevel.Supremely; @@ -3332,6 +3327,12 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab public override void OnSingleClick(Mobile from) { + if (!Core.UOTD) + { + OnSingleClickPreUOTD(from); + return; + } + var attrs = new List(); if (DisplayLootType) @@ -3423,6 +3424,130 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab from.NetState.SendDisplayEquipmentInfo(Serial, number, _crafter, false, attrs); } + public virtual void OnSingleClickPreUOTD(Mobile from) + { + var isMagicItem = _durabilityLevel > WeaponDurabilityLevel.Regular || + _accuracyLevel > WeaponAccuracyLevel.Regular || + _damageLevel > WeaponDamageLevel.Regular || + _slayer != SlayerName.None; + + if (isMagicItem && !_identified) + { + LabelTo(from, $"an unidentified {Name ?? Localization.GetText(LabelNumber).ToLowerInvariant()}"); + return; + } + + var name = Name; + var articleAnName = (TileData.ItemTable[ItemID].Flags & TileFlag.ArticleAn) != 0; + + if (isMagicItem) + { + var builder = ValueStringBuilder.Create(128); + + var durabilityText = DurabilityText(out var articleAnDurability); + if (durabilityText != null) + { + builder.AppendSpaceWithArticle(durabilityText, articleAnDurability); + } + + var accuracyText = AccuracyText(out var articleAnAccuracy); + if (accuracyText != null) + { + builder.AppendSpaceWithArticle(accuracyText, articleAnAccuracy); + } + + var slayerEntry = SlayerGroup.GetEntryByName(_slayer); + if (slayerEntry != null) + { + builder.AppendSpaceWithArticle(slayerEntry.SlayerText(out var articleAnSlayer), articleAnSlayer); + } + + if (name == null) + { + builder.AppendSpaceWithArticle(Localization.GetText(LabelNumber).ToLowerInvariant(), articleAnName); + } + else if (builder.Length != 0) + { + builder.Append($" {name}"); + } + else + { + builder.Append(name); + } + + var weaponDamageText = WeaponDamageText; + if (weaponDamageText != null) + { + builder.Append($" of {weaponDamageText}"); + } + + // TODO: Spells (of Ghoul's Touch) + + LabelTo(from, builder.ToString()); + builder.Dispose(); + return; + } + + name ??= $"{(articleAnName ? "an" : "a")} {Localization.GetText(LabelNumber).ToLowerInvariant()}"; + + if (Crafter == null) + { + LabelTo(from, Quality == WeaponQuality.Exceptional ? $"{name} of exceptional quality" : name); + return; + } + + LabelTo( + from, + Quality == WeaponQuality.Exceptional + ? $"{name} crafted with exceptional quality by {Crafter}" + : $"{name} crafted by {Crafter}" + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private string DurabilityText(out bool articleAn) + { + articleAn = _durabilityLevel is WeaponDurabilityLevel.Indestructible; + return _durabilityLevel switch + { + WeaponDurabilityLevel.Durable => "durable", + WeaponDurabilityLevel.Substantial => "substantial", + WeaponDurabilityLevel.Massive => "massive", + WeaponDurabilityLevel.Fortified => "fortified", + WeaponDurabilityLevel.Indestructible => "indestructible", + _ => null + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private string AccuracyText(out bool articleAn) + { + articleAn = _accuracyLevel is WeaponAccuracyLevel.Accurate or WeaponAccuracyLevel.Eminently; + return _accuracyLevel switch + { + WeaponAccuracyLevel.Accurate => "accurate", + WeaponAccuracyLevel.Surpassingly => "surpassingly accurate", + WeaponAccuracyLevel.Eminently => "eminently accurate", + WeaponAccuracyLevel.Exceedingly => "exceedingly accurate", + WeaponAccuracyLevel.Supremely => "supremely accurate", + _ => null + }; + } + + private string WeaponDamageText + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _damageLevel switch + { + WeaponDamageLevel.Ruin => "ruin", + WeaponDamageLevel.Might => "might", + WeaponDamageLevel.Force => "force", + WeaponDamageLevel.Power => "power", + WeaponDamageLevel.Vanq => "vanquishing", + _ => null + }; + } + public virtual int GetHitAttackSound(Mobile attacker, Mobile defender) { var sound = attacker.GetAttackSound(); diff --git a/Projects/UOContent/Items/Weapons/Maces/FireworksWand.cs b/Projects/UOContent/Items/Weapons/Maces/FireworksWand.cs index a755d8426..7c0c64a61 100644 --- a/Projects/UOContent/Items/Weapons/Maces/FireworksWand.cs +++ b/Projects/UOContent/Items/Weapons/Maces/FireworksWand.cs @@ -98,5 +98,10 @@ namespace Server.Items Effects.PlaySound(endLoc, map, Utility.Random(0x11B, 4)); Effects.SendLocationEffect(endLoc, map, 0x373A + 0x10 * Utility.Random(4), 16, 10, hue, renderMode); } + + public override void OnSingleClickPreUOTD(Mobile from) + { + LabelTo(from, Name ?? Localization.GetText(LabelNumber).ToLowerInvariant()); + } } } diff --git a/Projects/UOContent/Items/Weapons/SlayerEntry.cs b/Projects/UOContent/Items/Weapons/SlayerEntry.cs index 1acec3c59..ccd1a7682 100644 --- a/Projects/UOContent/Items/Weapons/SlayerEntry.cs +++ b/Projects/UOContent/Items/Weapons/SlayerEntry.cs @@ -88,6 +88,20 @@ namespace Server.Items } } + public string SlayerText(out bool articleAn) + { + if (Name == SlayerName.None) + { + articleAn = false; + return null; + } + + articleAn = Name is SlayerName.OrcSlaying or SlayerName.OgreTrashing or SlayerName.Exorcism or SlayerName.Ophidian + or SlayerName.ArachnidDoom or SlayerName.ElementalBan or SlayerName.ElementalHealth or SlayerName.EarthShatter; + + return Localization.GetText(Title)?.ToLowerInvariant(); + } + public bool Slays(Mobile m) { var t = m.GetType();