using ACE.Database.Models.World; namespace Aeshnidae.DevKit; /// /// /winfo: everything a content developer wants to know about a weenie, as lines of /// chat, with the interesting parts first. What it gives and awards, what it sells, /// what it spawns, what it casts - decoded - then the raw properties, wrapped. /// internal static class WeenieInfo { public static List Render(WorldWeenie w, bool full) { var lines = new List(); var name = w.WeeniePropertiesString?.FirstOrDefault(s => s.Type == (ushort)PropertyString.Name)?.Value ?? "(no name)"; var level = w.WeeniePropertiesInt?.FirstOrDefault(i => i.Type == (ushort)PropertyInt.Level)?.Value; var itemType = w.WeeniePropertiesInt?.FirstOrDefault(i => i.Type == (ushort)PropertyInt.ItemType)?.Value; lines.Add($"=== {w.ClassId} {name} ({w.ClassName}) {Format.EnumName((uint)w.Type)}" + (itemType is not null ? $" / {Format.EnumName((uint)itemType.Value)}" : "") + (level is not null ? $" level {level}" : "") + $" modified {w.LastModified:yyyy-MM-dd}"); // The parts people actually edit, decoded. var emotes = w.WeeniePropertiesEmote?.OrderBy(e => e.Category).ThenBy(e => e.WeenieClassId).ThenBy(e => e.Id).ToList(); if (emotes is { Count: > 0 }) { lines.Add($"-- Emotes ({emotes.Count}):"); foreach (var e in emotes) lines.Add(" " + Format.Emote(e)); } var create = w.WeeniePropertiesCreateList?.OrderBy(c => c.DestinationType).ThenBy(c => c.Id).ToList(); if (create is { Count: > 0 }) { var shop = create.Where(c => (c.DestinationType & (sbyte)DestinationType.Shop) != 0).ToList(); var rest = create.Except(shop).ToList(); if (shop.Count > 0) { lines.Add($"-- Sells ({shop.Count}):"); lines.AddRange(Format.Wrap(" ", shop.Select(c => $"<{Names.WeenieRef(c.WeenieClassId)}>" + (c.StackSize is > 1 or < 0 ? $" x{c.StackSize}" : "")))); } if (rest.Count > 0) { lines.Add($"-- Create list ({rest.Count}):"); lines.AddRange(Format.Wrap(" ", rest.Select(Format.CreateList))); } } var gen = w.WeeniePropertiesGenerator?.OrderBy(g => g.Id).ToList(); if (gen is { Count: > 0 }) { lines.Add($"-- Generates ({gen.Count}):"); foreach (var g in gen) lines.Add(" " + Format.Generator(g)); } var spells = w.WeeniePropertiesSpellBook?.OrderBy(s => s.Id).ToList(); if (spells is { Count: > 0 }) { lines.Add($"-- Spells ({spells.Count}):"); lines.AddRange(Format.Wrap(" ", spells.Select(Format.SpellBook))); } if (w.WeeniePropertiesAttribute is { Count: > 0 }) lines.AddRange(Format.Wrap("-- Attributes: ", w.WeeniePropertiesAttribute.OrderBy(a => a.Type) .Select(a => $"{Format.EnumName(a.Type)} {a.InitLevel}" + (a.LevelFromCP > 0 ? $"+{a.LevelFromCP}" : "")))); if (w.WeeniePropertiesAttribute2nd is { Count: > 0 }) lines.AddRange(Format.Wrap("-- Vitals: ", w.WeeniePropertiesAttribute2nd.OrderBy(a => a.Type) .Select(a => $"{Format.EnumName(a.Type)} {a.InitLevel}" + (a.LevelFromCP > 0 ? $"+{a.LevelFromCP}" : "") + (a.CurrentLevel > 0 ? $" cur {a.CurrentLevel}" : "")))); if (w.WeeniePropertiesSkill is { Count: > 0 }) lines.AddRange(Format.Wrap("-- Skills: ", w.WeeniePropertiesSkill.OrderBy(s => s.Type) .Select(s => $"{Format.EnumName(s.Type)} {s.InitLevel}" + (s.LevelFromPP > 0 ? $"+{s.LevelFromPP}" : "") + $" ({Format.EnumName(s.SAC)})"))); if (w.WeeniePropertiesBodyPart is { Count: > 0 }) lines.Add($"-- Body parts: {w.WeeniePropertiesBodyPart.Count} (see /wexport for the table)"); if (w.WeeniePropertiesPosition is { Count: > 0 }) lines.AddRange(Format.Wrap("-- Positions: ", w.WeeniePropertiesPosition.OrderBy(p => p.PositionType) .Select(p => $"{Format.EnumName(p.PositionType)} 0x{p.ObjCellId:X8} ({p.OriginX:0.##}, {p.OriginY:0.##}, {p.OriginZ:0.##})"))); if (w.WeeniePropertiesEventFilter is { Count: > 0 }) lines.AddRange(Format.Wrap("-- Event filters: ", w.WeeniePropertiesEventFilter.Select(f => f.Event.ToString()))); if (w.WeeniePropertiesBook is not null) lines.Add($"-- Book: {w.WeeniePropertiesBook.MaxNumPages} pages max, {w.WeeniePropertiesBookPageData?.Count ?? 0} written"); // Raw properties. Strings first (name, descriptions), then the numbers. Props(lines, "string", w.WeeniePropertiesString?.Select(p => ((uint)p.Type, (object?)p.Value))); Props(lines, "int", w.WeeniePropertiesInt?.Select(p => ((uint)p.Type, (object?)p.Value))); Props(lines, "int64", w.WeeniePropertiesInt64?.Select(p => ((uint)p.Type, (object?)p.Value))); Props(lines, "bool", w.WeeniePropertiesBool?.Select(p => ((uint)p.Type, (object?)p.Value))); Props(lines, "float", w.WeeniePropertiesFloat?.Select(p => ((uint)p.Type, (object?)p.Value))); Props(lines, "d_i_d", w.WeeniePropertiesDID?.Select(p => ((uint)p.Type, (object?)p.Value))); Props(lines, "i_i_d", w.WeeniePropertiesIID?.Select(p => ((uint)p.Type, (object?)p.Value))); if (full) { if (w.WeeniePropertiesPalette is { Count: > 0 }) lines.AddRange(Format.Wrap("-- Palettes: ", w.WeeniePropertiesPalette.Select(p => $"0x{p.SubPaletteId:X} [{p.Offset}+{p.Length}]"))); if (w.WeeniePropertiesTextureMap is { Count: > 0 }) lines.AddRange(Format.Wrap("-- Textures: ", w.WeeniePropertiesTextureMap.Select(t => $"{t.Index}: 0x{t.OldId:X}->0x{t.NewId:X}"))); if (w.WeeniePropertiesAnimPart is { Count: > 0 }) lines.AddRange(Format.Wrap("-- Anim parts: ", w.WeeniePropertiesAnimPart.Select(a => $"{a.Index}: 0x{a.AnimationId:X}"))); } else { var visual = (w.WeeniePropertiesPalette?.Count ?? 0) + (w.WeeniePropertiesTextureMap?.Count ?? 0) + (w.WeeniePropertiesAnimPart?.Count ?? 0); if (visual > 0) lines.Add($"-- {visual} palette/texture/anim rows hidden - /winfo {w.ClassId} full"); } return lines; } private static void Props(List lines, string kind, IEnumerable<(uint type, object? value)>? props) { if (props is null) return; var list = props.OrderBy(p => p.type).ToList(); if (list.Count == 0) return; lines.AddRange(Format.Wrap($"-- {kind}: ", list.Select(p => $"{Format.PropertyName(kind, p.type)}={Format.PropertyValue(kind, p.type, p.value)}"))); } }