diff --git a/Dalamud/Game/Chat/SeStringHandling/Payload.cs b/Dalamud/Game/Chat/SeStringHandling/Payload.cs index fbbaf2ab8..25b4531de 100644 --- a/Dalamud/Game/Chat/SeStringHandling/Payload.cs +++ b/Dalamud/Game/Chat/SeStringHandling/Payload.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Dalamud.Game.Chat.SeStringHandling.Payloads; using Serilog; @@ -24,14 +22,17 @@ namespace Dalamud.Game.Chat.SeStringHandling public static Payload Process(BinaryReader reader) { + Payload payload = null; if ((byte)reader.PeekChar() != START_BYTE) { - return ProcessText(reader); + payload = ProcessText(reader); } else { - return ProcessChunk(reader); + payload = ProcessChunk(reader); } + + return payload; } private static Payload ProcessChunk(BinaryReader reader) @@ -59,6 +60,10 @@ namespace Dalamud.Game.Chat.SeStringHandling payload = new ItemPayload(); break; + case EmbeddedInfoType.MapPositionLink: + payload = new MapLinkPayload(); + break; + case EmbeddedInfoType.Status: payload = new StatusPayload(); break; @@ -74,6 +79,19 @@ namespace Dalamud.Game.Chat.SeStringHandling } } break; + + case SeStringChunkType.AutoTranslateKey: + payload = new AutoTranslatePayload(); + break; + + case SeStringChunkType.UIForeground: + payload = new UIForegroundPayload(); + break; + + case SeStringChunkType.UIGlow: + payload = new UIGlowPayload(); + break; + default: Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType); payload = new RawPayload((byte)chunkType); @@ -83,8 +101,8 @@ namespace Dalamud.Game.Chat.SeStringHandling payload?.ProcessChunkImpl(reader, reader.BaseStream.Position + chunkLen - 1); // read through the rest of the packet - var readBytes = (int)(reader.BaseStream.Position - packetStart); - reader.ReadBytes(chunkLen - readBytes + 1); // +1 for the END_BYTE marker + var readBytes = (uint)(reader.BaseStream.Position - packetStart); + reader.ReadBytes((int)(chunkLen - readBytes + 1)); // +1 for the END_BYTE marker return payload; } @@ -104,13 +122,17 @@ namespace Dalamud.Game.Chat.SeStringHandling protected enum SeStringChunkType { - Interactable = 0x27 + Interactable = 0x27, + AutoTranslateKey = 0x2E, + UIForeground = 0x48, + UIGlow = 0x49 } protected enum EmbeddedInfoType { PlayerName = 0x01, ItemLink = 0x03, + MapPositionLink = 0x04, Status = 0x09, LinkTerminator = 0xCF // not clear but seems to always follow a link @@ -118,62 +140,64 @@ namespace Dalamud.Game.Chat.SeStringHandling protected enum IntegerType { + // used as an internal marker; sometimes single bytes are bare with no marker at all + None = 0, + Byte = 0xF0, ByteTimes256 = 0xF1, Int16 = 0xF2, - Int16Plus1Million = 0xF6, + Int16Packed = 0xF4, // seen in map links, seemingly 2 8-bit values packed into 2 bytes with only one marker + Int24Special = 0xF6, // unsure how different form Int24 - used for hq items that add 1 million, also used for normal 24-bit values in map links Int24 = 0xFA, Int32 = 0xFE } // made protected, unless we actually want to use it externally // in which case it should probably go live somewhere else - protected static int GetInteger(BinaryReader input) + protected static uint GetInteger(BinaryReader input) { var t = input.ReadByte(); var type = (IntegerType)t; return GetInteger(input, type); } - private static int GetInteger(BinaryReader input, IntegerType type) + private static uint GetInteger(BinaryReader input, IntegerType type) { const byte ByteLengthCutoff = 0xF0; var t = (byte)type; if (t < ByteLengthCutoff) - return t - 1; + return (uint)(t - 1); switch (type) { case IntegerType.Byte: return input.ReadByte(); + case IntegerType.ByteTimes256: - return input.ReadByte() * 256; + return input.ReadByte() * (uint)256; + case IntegerType.Int16: + // fallthrough - same logic + case IntegerType.Int16Packed: { var v = 0; v |= input.ReadByte() << 8; v |= input.ReadByte(); - return v; - } - case IntegerType.Int16Plus1Million: - { - var v = 0; - v |= input.ReadByte() << 16; - v |= input.ReadByte() << 8; - v |= input.ReadByte(); - // need the actual value since it's used as a flag - // v -= 1000000; - return v; + return (uint)v; } + + case IntegerType.Int24Special: + // Fallthrough - same logic case IntegerType.Int24: { var v = 0; v |= input.ReadByte() << 16; v |= input.ReadByte() << 8; v |= input.ReadByte(); - return v; + return (uint)v; } + case IntegerType.Int32: { var v = 0; @@ -181,45 +205,105 @@ namespace Dalamud.Game.Chat.SeStringHandling v |= input.ReadByte() << 16; v |= input.ReadByte() << 8; v |= input.ReadByte(); - return v; + return (uint)v; } + default: throw new NotSupportedException(); } } - protected static byte[] MakeInteger(int value) + protected virtual byte[] MakeInteger(uint value, bool withMarker = true, bool incrementSmallInts = true) // TODO: better way to handle this { - // clearly the epitome of efficiency + // single-byte values below the marker values have no marker and have 1 added + if (incrementSmallInts && (value + 1 < (int)IntegerType.Byte)) + { + value++; + return new byte[] { (byte)value }; + } var bytesPadded = BitConverter.GetBytes(value); Array.Reverse(bytesPadded); - return bytesPadded.SkipWhile(b => b == 0x00).ToArray(); + var shrunkValue = bytesPadded.SkipWhile(b => b == 0x00).ToArray(); + + var encodedNum = new List(); + + if (withMarker) + { + var marker = GetMarkerForIntegerBytes(shrunkValue); + if (marker != 0) + { + encodedNum.Add(marker); + } + } + + encodedNum.AddRange(shrunkValue); + + return encodedNum.ToArray(); } - protected static IntegerType GetTypeForIntegerBytes(byte[] bytes) + // This is only accurate in a very general sense + // Different payloads seem to use different default values for things + // So this should be overridden where necessary + protected virtual byte GetMarkerForIntegerBytes(byte[] bytes) { // not the most scientific, exists mainly for laziness - if (bytes.Length == 1) + var marker = bytes.Length switch { - return IntegerType.Byte; + 1 => IntegerType.Byte, + 2 => IntegerType.Int16, + 3 => IntegerType.Int24, + 4 => IntegerType.Int32, + _ => throw new NotSupportedException() + }; + + return (byte)marker; + } + + protected virtual byte GetMarkerForPackedIntegerBytes(byte[] bytes) + { + // unsure if any 'strange' size groupings exist; only ever seen these + var type = bytes.Length switch + { + 4 => IntegerType.Int32, + 2 => IntegerType.Int16Packed, + _ => throw new NotSupportedException() + }; + + return (byte)type; + } + + protected (uint, uint) GetPackedIntegers(BinaryReader input) + { + var value = GetInteger(input); + if (value > 0xFFFF) + { + return ((uint)((value & 0xFFFF0000) >> 16), (uint)(value & 0xFFFF)); } - else if (bytes.Length == 2) + else if (value > 0xFF) { - return IntegerType.Int16; - } - else if (bytes.Length == 3) - { - return IntegerType.Int24; - } - else if (bytes.Length == 4) - { - return IntegerType.Int32; + return ((uint)((value & 0xFF00) >> 8), (uint)(value & 0xFF)); } + // unsure if there are other cases, like "odd" pairings of 2+1 bytes etc throw new NotSupportedException(); } + + protected byte[] MakePackedInteger(uint val1, uint val2, bool withMarker = true) + { + var value = MakeInteger(val1, false, false).Concat(MakeInteger(val2, false, false)).ToArray(); + + var valueBytes = new List(); + if (withMarker) + { + valueBytes.Add(GetMarkerForPackedIntegerBytes(value)); + } + + valueBytes.AddRange(value); + + return valueBytes.ToArray(); + } #endregion } } diff --git a/Dalamud/Game/Chat/SeStringHandling/PayloadType.cs b/Dalamud/Game/Chat/SeStringHandling/PayloadType.cs index 6576e0524..92e8afa44 100644 --- a/Dalamud/Game/Chat/SeStringHandling/PayloadType.cs +++ b/Dalamud/Game/Chat/SeStringHandling/PayloadType.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Dalamud.Game.Chat.SeStringHandling { @@ -28,6 +23,22 @@ namespace Dalamud.Game.Chat.SeStringHandling /// RawText, /// + /// An SeString payload representing a text foreground color. + /// + UIForeground, + /// + /// An SeString payload representing a text glow color. + /// + UIGlow, + /// + /// An SeString payload representing a map position link, such as from <flag> or <pos>. + /// + MapLink, + /// + /// An SeString payload representing an auto-translate dictionary entry. + /// + AutoTranslateText, + /// /// An SeString payload representing any data we don't handle. /// Unknown diff --git a/Dalamud/Game/Chat/SeStringHandling/Payloads/AutoTranslatePayload.cs b/Dalamud/Game/Chat/SeStringHandling/Payloads/AutoTranslatePayload.cs new file mode 100644 index 000000000..87b25aa45 --- /dev/null +++ b/Dalamud/Game/Chat/SeStringHandling/Payloads/AutoTranslatePayload.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.IO; + + +namespace Dalamud.Game.Chat.SeStringHandling.Payloads +{ + public class AutoTranslatePayload : Payload + { + public override PayloadType Type => PayloadType.AutoTranslateText; + + public uint Group { get; set; } + + public uint Key { get; set; } + + public string Text { get; set; } + + public override void Resolve() + { + // TODO: fixup once lumina DI is in + + //if (string.IsNullOrEmpty(Text)) + //{ + // var sheet = dalamud.Data.GetExcelSheet(); + + // Completion row = null; + // try + // { + // // try to get the row in the Completion table itself, because this is 'easiest' + // // The row may not exist at all (if the Key is for another table), or it could be the wrong row + // // (again, if it's meant for another table) + // row = sheet.GetRow(Key); + // } + // catch {} // don't care, row will be null + + // if (row?.Group == Group) + // { + // // if the row exists in this table and the group matches, this is actually the correct data + // Text = $"{{ {row.Text} }} "; + // } + // else + // { + // Log.Verbose("row mismatch"); + // try + // { + // // we need to get the linked table and do the lookup there instead + // // in this case, there will only be one entry for this group id + // row = sheet.GetRows().First(r => r.Group == Group); + // // many of the names contain valid id ranges after the table name, but we don't need those + // var actualTableName = row.LookupTable.Split('[')[0]; + + // var name = actualTableName switch + // { + // // TODO: rest of xref'd tables + // "Action" => dalamud.Data.GetExcelSheet().GetRow(Key).Name, + // "ClassJob" => dalamud.Data.GetExcelSheet().GetRow(Key).Name, + // "CraftAction" => dalamud.Data.GetExcelSheet().GetRow(Key).Name, + // "Mount" => dalamud.Data.GetExcelSheet().GetRow(Key).Singular, + // "PlaceName" => dalamud.Data.GetExcelSheet().GetRow(Key).Name, + // "Race" => dalamud.Data.GetExcelSheet().GetRow(Key).Masculine, + // _ => throw new Exception(actualTableName) + // }; + + // Text = $"{{ {name} }} "; + // } + // catch (Exception e) + // { + // Log.Error(e, $"AutoTranslatePayload - failed to resolve: {this}"); + // } + // } + //} + } + + public override byte[] Encode() + { + var keyBytes = MakeInteger(Key); + + var chunkLen = keyBytes.Length + 2; + var bytes = new List() + { + START_BYTE, + (byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen, + (byte)Group + }; + bytes.AddRange(keyBytes); + bytes.Add(END_BYTE); + + return bytes.ToArray(); + } + + public override string ToString() + { + return $"{Type} - Group: {Group}, Key: {Key}, Text: {Text}"; + } + + protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream) + { + // this seems to always be a bare byte, and not following normal integer encoding + // the values in the table are all <70 so this is presumably ok + Group = reader.ReadByte(); + + Key = GetInteger(reader); + } + } +} diff --git a/Dalamud/Game/Chat/SeStringHandling/Payloads/ItemPayload.cs b/Dalamud/Game/Chat/SeStringHandling/Payloads/ItemPayload.cs index 1a1dc0bb7..bc58d488e 100644 --- a/Dalamud/Game/Chat/SeStringHandling/Payloads/ItemPayload.cs +++ b/Dalamud/Game/Chat/SeStringHandling/Payloads/ItemPayload.cs @@ -11,13 +11,13 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads { public override PayloadType Type => PayloadType.Item; - public int ItemId { get; private set; } + public uint ItemId { get; private set; } public string ItemName { get; private set; } = string.Empty; public bool IsHQ { get; private set; } = false; public ItemPayload() { } - public ItemPayload(int itemId, bool isHQ) + public ItemPayload(uint itemId, bool isHQ) { ItemId = itemId; IsHQ = isHQ; @@ -27,7 +27,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads { if (string.IsNullOrEmpty(ItemName)) { - dynamic item = XivApi.GetItem(ItemId).GetAwaiter().GetResult(); + dynamic item = XivApi.GetItem((int)ItemId).GetAwaiter().GetResult(); ItemName = item.Name; } } @@ -38,9 +38,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads var idBytes = MakeInteger(actualItemId); bool hasName = !string.IsNullOrEmpty(ItemName); - var itemIdFlag = IsHQ ? IntegerType.Int16Plus1Million : IntegerType.Int16; - - var chunkLen = idBytes.Length + 5; + var chunkLen = idBytes.Length + 4; if (hasName) { // 1 additional unknown byte compared to the nameless version, 1 byte for the name length, and then the name itself @@ -54,8 +52,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads var bytes = new List() { START_BYTE, - (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink, - (byte)itemIdFlag + (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink }; bytes.AddRange(idBytes); // unk @@ -109,7 +106,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads // unk reader.ReadBytes(3); - var itemNameLen = GetInteger(reader); + var itemNameLen = (int)GetInteger(reader); var itemNameBytes = reader.ReadBytes(itemNameLen); // HQ items have the HQ symbol as part of the name, but since we already recorded @@ -122,5 +119,16 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads ItemName = Encoding.UTF8.GetString(itemNameBytes); } } + + protected override byte GetMarkerForIntegerBytes(byte[] bytes) + { + // custom marker just for hq items? + if (bytes.Length == 3 && IsHQ) + { + return (byte)IntegerType.Int24Special; + } + + return base.GetMarkerForIntegerBytes(bytes); + } } } diff --git a/Dalamud/Game/Chat/SeStringHandling/Payloads/MapLinkPayload.cs b/Dalamud/Game/Chat/SeStringHandling/Payloads/MapLinkPayload.cs new file mode 100644 index 000000000..6115adab2 --- /dev/null +++ b/Dalamud/Game/Chat/SeStringHandling/Payloads/MapLinkPayload.cs @@ -0,0 +1,115 @@ +using Lumina.Excel.GeneratedSheets; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Dalamud.Game.Chat.SeStringHandling.Payloads +{ + public class MapLinkPayload : Payload + { + public override PayloadType Type => PayloadType.MapLink; + + // pre-Resolve() values + public uint TerritoryTypeId { get; set; } + public uint MapId { get; set; } + public uint RawX { get; set; } + public uint RawY { get; set; } + + // Resolved values + // It might make sense to have Territory be an external type, that has assorted relevant info + public string Territory { get; private set; } + public string Zone { get; private set; } + public float XCoord { get; private set; } + public float YCoord { get; private set; } + // there is no Z; it's purely in the text payload where applicable + + public override byte[] Encode() + { + // TODO: for now we just encode the raw/internal values + // eventually we should allow creation using 'nice' values that then encode properly + + var packedTerritoryAndMapBytes = MakePackedInteger(TerritoryTypeId, MapId); + var xBytes = MakeInteger(RawX); + var yBytes = MakeInteger(RawY); + + var chunkLen = 4 + packedTerritoryAndMapBytes.Length + xBytes.Length + yBytes.Length; + + var bytes = new List() + { + START_BYTE, + (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.MapPositionLink + }; + + bytes.AddRange(packedTerritoryAndMapBytes); + bytes.AddRange(xBytes); + bytes.AddRange(yBytes); + + // unk + bytes.AddRange(new byte[] { 0xFF, 0x01, END_BYTE }); + + return bytes.ToArray(); + } + + public override void Resolve() + { + // TODO: add once lumina DI is figured out + //if (string.IsNullOrEmpty(Territory)) + //{ + // var terrRow = dataResolver.GetExcelSheet().GetRow((int)TerritoryTypeId); + // Territory = dataResolver.GetExcelSheet().GetRow(terrRow.PlaceName).Name; + // Zone = dataResolver.GetExcelSheet().GetRow(terrRow.PlaceNameZone).Name; + + // var mapSizeFactor = dataResolver.GetExcelSheet().GetRow((int)MapId).SizeFactor; + // XCoord = ConvertRawPositionToMapCoordinate(RawX, mapSizeFactor); + // YCoord = ConvertRawPositionToMapCoordinate(RawY, mapSizeFactor); + //} + } + + protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream) + { + (TerritoryTypeId, MapId) = GetPackedIntegers(reader); + RawX = (uint)GetInteger(reader); + RawY = (uint)GetInteger(reader); + // the Z coordinate is never in this chunk, just the text (if applicable) + + // seems to always be FF 01 + reader.ReadBytes(2); + } + + #region ugliness + // from https://github.com/xivapi/ffxiv-datamining/blob/master/docs/MapCoordinates.md + // extra 1/1000 because that is how the network ints are done + private float ConvertRawPositionToMapCoordinate(uint pos, float scale) + { + var c = scale / 100.0f; + var scaledPos = (int)pos * c / 1000.0f; + + return ((41.0f / c) * ((scaledPos + 1024.0f) / 2048.0f)) + 1.0f; + } + + // Created as the inverse of ConvertRawPositionToMapCoordinate(), since no one seemed to have a version of that + private float ConvertMapCoordinateToRawPosition(float pos, float scale) + { + var c = scale / 100.0f; + + var scaledPos = ((((pos - 1.0f) * c / 41.0f) * 2048.0f) - 1024.0f) / c; + scaledPos *= 1000.0f; + + return (int)Math.Round(scaledPos); + } + #endregion + + protected override byte GetMarkerForIntegerBytes(byte[] bytes) + { + var type = bytes.Length switch + { + 3 => (byte)IntegerType.Int24Special, // used because seen in incoming data + 2 => (byte)IntegerType.Int16, + 1 => (byte)IntegerType.None, // single bytes seem to have no prefix at all here + _ => base.GetMarkerForIntegerBytes(bytes) + }; + + return type; + } + } +} diff --git a/Dalamud/Game/Chat/SeStringHandling/Payloads/PlayerPayload.cs b/Dalamud/Game/Chat/SeStringHandling/Payloads/PlayerPayload.cs index 15f555445..16dd6ab45 100644 --- a/Dalamud/Game/Chat/SeStringHandling/Payloads/PlayerPayload.cs +++ b/Dalamud/Game/Chat/SeStringHandling/Payloads/PlayerPayload.cs @@ -12,12 +12,12 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads public override PayloadType Type => PayloadType.Player; public string PlayerName { get; private set; } - public int ServerId { get; private set; } + public uint ServerId { get; private set; } public string ServerName { get; private set; } = String.Empty; public PlayerPayload() { } - public PlayerPayload(string playerName, int serverId) + public PlayerPayload(string playerName, uint serverId) { PlayerName = playerName; ServerId = serverId; @@ -78,7 +78,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads // unk reader.ReadBytes(2); - var nameLen = GetInteger(reader); + var nameLen = (int)GetInteger(reader); PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen)); } } diff --git a/Dalamud/Game/Chat/SeStringHandling/Payloads/StatusPayload.cs b/Dalamud/Game/Chat/SeStringHandling/Payloads/StatusPayload.cs index 4169ac42a..e76d1590f 100644 --- a/Dalamud/Game/Chat/SeStringHandling/Payloads/StatusPayload.cs +++ b/Dalamud/Game/Chat/SeStringHandling/Payloads/StatusPayload.cs @@ -11,13 +11,13 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads { public override PayloadType Type => PayloadType.Status; - public int StatusId { get; private set; } + public uint StatusId { get; private set; } public string StatusName { get; private set; } = string.Empty; public StatusPayload() { } - public StatusPayload(int statusId) + public StatusPayload(uint statusId) { StatusId = statusId; } @@ -35,13 +35,11 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads public override byte[] Encode() { var idBytes = MakeInteger(StatusId); - var idPrefix = GetTypeForIntegerBytes(idBytes); - var chunkLen = idBytes.Length + 8; + var chunkLen = idBytes.Length + 7; var bytes = new List() { - START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.Status, - (byte)idPrefix + START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.Status }; bytes.AddRange(idBytes); diff --git a/Dalamud/Game/Chat/SeStringHandling/Payloads/UIForegroundPayload.cs b/Dalamud/Game/Chat/SeStringHandling/Payloads/UIForegroundPayload.cs new file mode 100644 index 000000000..a9afebfa8 --- /dev/null +++ b/Dalamud/Game/Chat/SeStringHandling/Payloads/UIForegroundPayload.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Dalamud.Game.Chat.SeStringHandling.Payloads +{ + public class UIForegroundPayload : Payload + { + public override PayloadType Type => PayloadType.UIForeground; + + public ushort RawColor { get; private set; } + + //public int Red { get; private set; } + //public int Green { get; private set; } + //public int Blue { get; private set; } + + public override byte[] Encode() + { + var colorBytes = MakeInteger(RawColor); + var chunkLen = colorBytes.Length + 1; + + var bytes = new List(new byte[] + { + START_BYTE, (byte)SeStringChunkType.UIForeground, (byte)chunkLen + }); + + bytes.AddRange(colorBytes); + bytes.Add(END_BYTE); + + return bytes.ToArray(); + } + + public override void Resolve() + { + // TODO: resolve color keys to hex colors via UIColor table + } + + public override string ToString() + { + return $"{Type} - RawColor: {RawColor}"; + } + + protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream) + { + RawColor = (ushort)GetInteger(reader); + } + + protected override byte GetMarkerForIntegerBytes(byte[] bytes) + { + return bytes.Length switch + { + // a single byte of 0x01 is used to 'disable' color, and has no marker + 1 => (byte)IntegerType.None, + _ => base.GetMarkerForIntegerBytes(bytes) + }; + } + } +} diff --git a/Dalamud/Game/Chat/SeStringHandling/Payloads/UIGlowPayload.cs b/Dalamud/Game/Chat/SeStringHandling/Payloads/UIGlowPayload.cs new file mode 100644 index 000000000..3bc98fc6e --- /dev/null +++ b/Dalamud/Game/Chat/SeStringHandling/Payloads/UIGlowPayload.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Dalamud.Game.Chat.SeStringHandling.Payloads +{ + public class UIGlowPayload : Payload + { + public override PayloadType Type => PayloadType.UIGlow; + + public ushort RawColor { get; private set; } + + //public int Red { get; private set; } + //public int Green { get; private set; } + //public int Blue { get; private set; } + + public override byte[] Encode() + { + var colorBytes = MakeInteger(RawColor); + var chunkLen = colorBytes.Length + 1; + + var bytes = new List(new byte[] + { + START_BYTE, (byte)SeStringChunkType.UIGlow, (byte)chunkLen + }); + + bytes.AddRange(colorBytes); + bytes.Add(END_BYTE); + + return bytes.ToArray(); + } + + public override void Resolve() + { + // TODO: resolve color keys to hex colors via UIColor table + } + + public override string ToString() + { + return $"{Type} - RawColor: {RawColor}"; + } + + protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream) + { + RawColor = (ushort)GetInteger(reader); + } + + protected override byte GetMarkerForIntegerBytes(byte[] bytes) + { + return bytes.Length switch + { + // a single byte of 0x01 is used to 'disable' color, and has no marker + 1 => (byte)IntegerType.None, + _ => base.GetMarkerForIntegerBytes(bytes) + }; + } + } +} diff --git a/Dalamud/Game/Chat/SeStringHandling/SeString.cs b/Dalamud/Game/Chat/SeStringHandling/SeString.cs index 156612214..87e7dee62 100644 --- a/Dalamud/Game/Chat/SeStringHandling/SeString.cs +++ b/Dalamud/Game/Chat/SeStringHandling/SeString.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using Dalamud.Data; using Dalamud.Game.Chat.SeStringHandling.Payloads; namespace Dalamud.Game.Chat.SeStringHandling diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index 4d5861f23..eb7e8573e 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -187,7 +187,7 @@ namespace Dalamud.Game { if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", "").Replace(".", ""), out var itemValue)) continue; - Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.ItemId, itemValue, itemLink.IsHQ)); + Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale((int)itemLink.ItemId, itemValue, itemLink.IsHQ)); break; } }