feat(objects): ExtractOpl — decode OPL buffer into cliloc/args/text lines

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kamron Batman 2026-07-19 10:59:37 -07:00
parent 685fbc146e
commit c71bc4bcd5
No known key found for this signature in database
GPG key ID: 7D81DF26D9A5D94A
2 changed files with 81 additions and 0 deletions

View file

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

View file

@ -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<OplLine> ExtractOpl(Type type)
{
if (type.IsAssignableTo(typeof(Item)))
{
var item = type.CreateInstance<Item>();
try
{
var opl = new ObjectPropertyList(item);
item.GetProperties(opl);
return DecodeOpl(opl);
}
finally
{
item.Delete();
}
}
if (type.IsAssignableTo(typeof(Mobile)))
{
var m = type.CreateInstance<Mobile>();
try
{
var opl = new ObjectPropertyList(m);
m.GetProperties(opl);
return DecodeOpl(opl);
}
finally
{
m.Delete();
}
}
return [];
}
private static List<OplLine> DecodeOpl(ObjectPropertyList opl)
{
opl.Terminate();
var buffer = opl.Buffer;
var lines = new List<OplLine>();
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;
}
}