feat: Adds Pre-UOTD single click support for weapons, armor, wands, and clothing (#2053)

This commit is contained in:
Erik Askov Mousing 2025-01-14 02:04:24 +01:00 • committed by GitHub
parent 863b4b5460
commit 3b698de556
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 350 additions and 12 deletions

View file

@ -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);
}
}

View file

@ -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<EquipInfoAttribute>();
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
{

View file

@ -907,8 +907,13 @@ namespace Server.Items
public override void OnSingleClick(Mobile from)
{
var attrs = new List<EquipInfoAttribute>();
if (!Core.UOTD)
{
OnSingleClickPreUOTD(from);
return;
}
var attrs = new List<EquipInfoAttribute>();
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<EquipInfoAttribute> attrs)
{
if (DisplayLootType)

View file

@ -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<EquipInfoAttribute>();
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;

View file

@ -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<EquipInfoAttribute>();
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();

View file

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

View file

@ -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();