From c71bc4bcd5848b9a7936d358362969510ae8d356 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 19 Jul 2026 10:59:37 -0700 Subject: [PATCH] =?UTF-8?q?feat(objects):=20ExtractOpl=20=E2=80=94=20decod?= =?UTF-8?q?e=20OPL=20buffer=20into=20cliloc/args/text=20lines?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Objects/ObjectIntrospectionOplTests.cs | 17 +++++ .../Object Creation/ObjectIntrospection.cs | 64 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 Projects/UOContent.Tests/Tests/Commands/Objects/ObjectIntrospectionOplTests.cs diff --git a/Projects/UOContent.Tests/Tests/Commands/Objects/ObjectIntrospectionOplTests.cs b/Projects/UOContent.Tests/Tests/Commands/Objects/ObjectIntrospectionOplTests.cs new file mode 100644 index 000000000..b360a2726 --- /dev/null +++ b/Projects/UOContent.Tests/Tests/Commands/Objects/ObjectIntrospectionOplTests.cs @@ -0,0 +1,17 @@ +using Server.Commands; +using Server.Items; +using Xunit; + +namespace UOContent.Tests.Commands.Objects; + +[Collection("Sequential UOContent Tests")] +public class ObjectIntrospectionOplTests +{ + [Fact] + public void ExtractOpl_captures_the_runebook_name_cliloc() + { + // Runebook.LabelNumber is 1041267 ("runebook") — it appears as an OPL line. + var opl = ObjectIntrospection.ExtractOpl(typeof(Runebook)); + Assert.Contains(opl, line => line.Cliloc == 1041267); + } +} diff --git a/Projects/UOContent/Commands/Object Creation/ObjectIntrospection.cs b/Projects/UOContent/Commands/Object Creation/ObjectIntrospection.cs index f264a76f0..f4d076f97 100644 --- a/Projects/UOContent/Commands/Object Creation/ObjectIntrospection.cs +++ b/Projects/UOContent/Commands/Object Creation/ObjectIntrospection.cs @@ -1,6 +1,8 @@ using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Reflection; +using System.Text; using Server.Items; namespace Server.Commands; @@ -120,4 +122,66 @@ public static class ObjectIntrospection return docs; } + + public static List ExtractOpl(Type type) + { + if (type.IsAssignableTo(typeof(Item))) + { + var item = type.CreateInstance(); + try + { + var opl = new ObjectPropertyList(item); + item.GetProperties(opl); + return DecodeOpl(opl); + } + finally + { + item.Delete(); + } + } + + if (type.IsAssignableTo(typeof(Mobile))) + { + var m = type.CreateInstance(); + try + { + var opl = new ObjectPropertyList(m); + m.GetProperties(opl); + return DecodeOpl(opl); + } + finally + { + m.Delete(); + } + } + + return []; + } + + private static List DecodeOpl(ObjectPropertyList opl) + { + opl.Terminate(); + var buffer = opl.Buffer; + var lines = new List(); + var pos = 15; // fixed OPL header length + + while (pos + 4 <= buffer.Length) + { + var cliloc = BinaryPrimitives.ReadInt32BigEndian(buffer.AsSpan(pos)); + pos += 4; + if (cliloc == 0) + { + break; + } + + var byteLen = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(pos)); + pos += 2; + var args = byteLen > 0 ? Encoding.Unicode.GetString(buffer, pos, byteLen) : null; + pos += byteLen; + + lines.Add(new OplLine { Cliloc = cliloc, Args = args, Text = Localization.GetText(cliloc) }); + } + + return lines; + } }