mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-20 14:57:45 +01:00
Merge pull request #71 from ff-meli/sestring_ui_payloads
Add UIGlow and UIForeground SeString payloads; refactor some handling of integer markers in payloads, to make encoding a bit simpler
This commit is contained in:
commit
5571c849c5
11 changed files with 503 additions and 65 deletions
|
|
@ -2,8 +2,6 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
|
@ -24,14 +22,17 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
|
|
||||||
public static Payload Process(BinaryReader reader)
|
public static Payload Process(BinaryReader reader)
|
||||||
{
|
{
|
||||||
|
Payload payload = null;
|
||||||
if ((byte)reader.PeekChar() != START_BYTE)
|
if ((byte)reader.PeekChar() != START_BYTE)
|
||||||
{
|
{
|
||||||
return ProcessText(reader);
|
payload = ProcessText(reader);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return ProcessChunk(reader);
|
payload = ProcessChunk(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Payload ProcessChunk(BinaryReader reader)
|
private static Payload ProcessChunk(BinaryReader reader)
|
||||||
|
|
@ -59,6 +60,10 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
payload = new ItemPayload();
|
payload = new ItemPayload();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case EmbeddedInfoType.MapPositionLink:
|
||||||
|
payload = new MapLinkPayload();
|
||||||
|
break;
|
||||||
|
|
||||||
case EmbeddedInfoType.Status:
|
case EmbeddedInfoType.Status:
|
||||||
payload = new StatusPayload();
|
payload = new StatusPayload();
|
||||||
break;
|
break;
|
||||||
|
|
@ -74,6 +79,19 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SeStringChunkType.AutoTranslateKey:
|
||||||
|
payload = new AutoTranslatePayload();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SeStringChunkType.UIForeground:
|
||||||
|
payload = new UIForegroundPayload();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SeStringChunkType.UIGlow:
|
||||||
|
payload = new UIGlowPayload();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType);
|
Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType);
|
||||||
payload = new RawPayload((byte)chunkType);
|
payload = new RawPayload((byte)chunkType);
|
||||||
|
|
@ -83,8 +101,8 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
payload?.ProcessChunkImpl(reader, reader.BaseStream.Position + chunkLen - 1);
|
payload?.ProcessChunkImpl(reader, reader.BaseStream.Position + chunkLen - 1);
|
||||||
|
|
||||||
// read through the rest of the packet
|
// read through the rest of the packet
|
||||||
var readBytes = (int)(reader.BaseStream.Position - packetStart);
|
var readBytes = (uint)(reader.BaseStream.Position - packetStart);
|
||||||
reader.ReadBytes(chunkLen - readBytes + 1); // +1 for the END_BYTE marker
|
reader.ReadBytes((int)(chunkLen - readBytes + 1)); // +1 for the END_BYTE marker
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
@ -104,13 +122,17 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
|
|
||||||
protected enum SeStringChunkType
|
protected enum SeStringChunkType
|
||||||
{
|
{
|
||||||
Interactable = 0x27
|
Interactable = 0x27,
|
||||||
|
AutoTranslateKey = 0x2E,
|
||||||
|
UIForeground = 0x48,
|
||||||
|
UIGlow = 0x49
|
||||||
}
|
}
|
||||||
|
|
||||||
protected enum EmbeddedInfoType
|
protected enum EmbeddedInfoType
|
||||||
{
|
{
|
||||||
PlayerName = 0x01,
|
PlayerName = 0x01,
|
||||||
ItemLink = 0x03,
|
ItemLink = 0x03,
|
||||||
|
MapPositionLink = 0x04,
|
||||||
Status = 0x09,
|
Status = 0x09,
|
||||||
|
|
||||||
LinkTerminator = 0xCF // not clear but seems to always follow a link
|
LinkTerminator = 0xCF // not clear but seems to always follow a link
|
||||||
|
|
@ -118,62 +140,64 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
|
|
||||||
protected enum IntegerType
|
protected enum IntegerType
|
||||||
{
|
{
|
||||||
|
// used as an internal marker; sometimes single bytes are bare with no marker at all
|
||||||
|
None = 0,
|
||||||
|
|
||||||
Byte = 0xF0,
|
Byte = 0xF0,
|
||||||
ByteTimes256 = 0xF1,
|
ByteTimes256 = 0xF1,
|
||||||
Int16 = 0xF2,
|
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,
|
Int24 = 0xFA,
|
||||||
Int32 = 0xFE
|
Int32 = 0xFE
|
||||||
}
|
}
|
||||||
|
|
||||||
// made protected, unless we actually want to use it externally
|
// made protected, unless we actually want to use it externally
|
||||||
// in which case it should probably go live somewhere else
|
// 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 t = input.ReadByte();
|
||||||
var type = (IntegerType)t;
|
var type = (IntegerType)t;
|
||||||
return GetInteger(input, type);
|
return GetInteger(input, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetInteger(BinaryReader input, IntegerType type)
|
private static uint GetInteger(BinaryReader input, IntegerType type)
|
||||||
{
|
{
|
||||||
const byte ByteLengthCutoff = 0xF0;
|
const byte ByteLengthCutoff = 0xF0;
|
||||||
|
|
||||||
var t = (byte)type;
|
var t = (byte)type;
|
||||||
if (t < ByteLengthCutoff)
|
if (t < ByteLengthCutoff)
|
||||||
return t - 1;
|
return (uint)(t - 1);
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case IntegerType.Byte:
|
case IntegerType.Byte:
|
||||||
return input.ReadByte();
|
return input.ReadByte();
|
||||||
|
|
||||||
case IntegerType.ByteTimes256:
|
case IntegerType.ByteTimes256:
|
||||||
return input.ReadByte() * 256;
|
return input.ReadByte() * (uint)256;
|
||||||
|
|
||||||
case IntegerType.Int16:
|
case IntegerType.Int16:
|
||||||
|
// fallthrough - same logic
|
||||||
|
case IntegerType.Int16Packed:
|
||||||
{
|
{
|
||||||
var v = 0;
|
var v = 0;
|
||||||
v |= input.ReadByte() << 8;
|
v |= input.ReadByte() << 8;
|
||||||
v |= input.ReadByte();
|
v |= input.ReadByte();
|
||||||
return v;
|
return (uint)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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case IntegerType.Int24Special:
|
||||||
|
// Fallthrough - same logic
|
||||||
case IntegerType.Int24:
|
case IntegerType.Int24:
|
||||||
{
|
{
|
||||||
var v = 0;
|
var v = 0;
|
||||||
v |= input.ReadByte() << 16;
|
v |= input.ReadByte() << 16;
|
||||||
v |= input.ReadByte() << 8;
|
v |= input.ReadByte() << 8;
|
||||||
v |= input.ReadByte();
|
v |= input.ReadByte();
|
||||||
return v;
|
return (uint)v;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IntegerType.Int32:
|
case IntegerType.Int32:
|
||||||
{
|
{
|
||||||
var v = 0;
|
var v = 0;
|
||||||
|
|
@ -181,45 +205,105 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
v |= input.ReadByte() << 16;
|
v |= input.ReadByte() << 16;
|
||||||
v |= input.ReadByte() << 8;
|
v |= input.ReadByte() << 8;
|
||||||
v |= input.ReadByte();
|
v |= input.ReadByte();
|
||||||
return v;
|
return (uint)v;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException();
|
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);
|
var bytesPadded = BitConverter.GetBytes(value);
|
||||||
Array.Reverse(bytesPadded);
|
Array.Reverse(bytesPadded);
|
||||||
return bytesPadded.SkipWhile(b => b == 0x00).ToArray();
|
var shrunkValue = bytesPadded.SkipWhile(b => b == 0x00).ToArray();
|
||||||
|
|
||||||
|
var encodedNum = new List<byte>();
|
||||||
|
|
||||||
|
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
|
// 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;
|
return ((uint)((value & 0xFF00) >> 8), (uint)(value & 0xFF));
|
||||||
}
|
|
||||||
else if (bytes.Length == 3)
|
|
||||||
{
|
|
||||||
return IntegerType.Int24;
|
|
||||||
}
|
|
||||||
else if (bytes.Length == 4)
|
|
||||||
{
|
|
||||||
return IntegerType.Int32;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unsure if there are other cases, like "odd" pairings of 2+1 bytes etc
|
||||||
throw new NotSupportedException();
|
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<byte>();
|
||||||
|
if (withMarker)
|
||||||
|
{
|
||||||
|
valueBytes.Add(GetMarkerForPackedIntegerBytes(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
valueBytes.AddRange(value);
|
||||||
|
|
||||||
|
return valueBytes.ToArray();
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
{
|
{
|
||||||
|
|
@ -28,6 +23,22 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RawText,
|
RawText,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// An SeString payload representing a text foreground color.
|
||||||
|
/// </summary>
|
||||||
|
UIForeground,
|
||||||
|
/// <summary>
|
||||||
|
/// An SeString payload representing a text glow color.
|
||||||
|
/// </summary>
|
||||||
|
UIGlow,
|
||||||
|
/// <summary>
|
||||||
|
/// An SeString payload representing a map position link, such as from <flag> or <pos>.
|
||||||
|
/// </summary>
|
||||||
|
MapLink,
|
||||||
|
/// <summary>
|
||||||
|
/// An SeString payload representing an auto-translate dictionary entry.
|
||||||
|
/// </summary>
|
||||||
|
AutoTranslateText,
|
||||||
|
/// <summary>
|
||||||
/// An SeString payload representing any data we don't handle.
|
/// An SeString payload representing any data we don't handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Unknown
|
Unknown
|
||||||
|
|
|
||||||
|
|
@ -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>();
|
||||||
|
|
||||||
|
// 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<Data.TransientSheet.Action>().GetRow(Key).Name,
|
||||||
|
// "ClassJob" => dalamud.Data.GetExcelSheet<ClassJob>().GetRow(Key).Name,
|
||||||
|
// "CraftAction" => dalamud.Data.GetExcelSheet<CraftAction>().GetRow(Key).Name,
|
||||||
|
// "Mount" => dalamud.Data.GetExcelSheet<Mount>().GetRow(Key).Singular,
|
||||||
|
// "PlaceName" => dalamud.Data.GetExcelSheet<PlaceName>().GetRow(Key).Name,
|
||||||
|
// "Race" => dalamud.Data.GetExcelSheet<Race>().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<byte>()
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,13 +11,13 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
||||||
{
|
{
|
||||||
public override PayloadType Type => PayloadType.Item;
|
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 string ItemName { get; private set; } = string.Empty;
|
||||||
public bool IsHQ { get; private set; } = false;
|
public bool IsHQ { get; private set; } = false;
|
||||||
|
|
||||||
public ItemPayload() { }
|
public ItemPayload() { }
|
||||||
|
|
||||||
public ItemPayload(int itemId, bool isHQ)
|
public ItemPayload(uint itemId, bool isHQ)
|
||||||
{
|
{
|
||||||
ItemId = itemId;
|
ItemId = itemId;
|
||||||
IsHQ = isHQ;
|
IsHQ = isHQ;
|
||||||
|
|
@ -27,7 +27,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(ItemName))
|
if (string.IsNullOrEmpty(ItemName))
|
||||||
{
|
{
|
||||||
dynamic item = XivApi.GetItem(ItemId).GetAwaiter().GetResult();
|
dynamic item = XivApi.GetItem((int)ItemId).GetAwaiter().GetResult();
|
||||||
ItemName = item.Name;
|
ItemName = item.Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -38,9 +38,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
||||||
var idBytes = MakeInteger(actualItemId);
|
var idBytes = MakeInteger(actualItemId);
|
||||||
bool hasName = !string.IsNullOrEmpty(ItemName);
|
bool hasName = !string.IsNullOrEmpty(ItemName);
|
||||||
|
|
||||||
var itemIdFlag = IsHQ ? IntegerType.Int16Plus1Million : IntegerType.Int16;
|
var chunkLen = idBytes.Length + 4;
|
||||||
|
|
||||||
var chunkLen = idBytes.Length + 5;
|
|
||||||
if (hasName)
|
if (hasName)
|
||||||
{
|
{
|
||||||
// 1 additional unknown byte compared to the nameless version, 1 byte for the name length, and then the name itself
|
// 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<byte>()
|
var bytes = new List<byte>()
|
||||||
{
|
{
|
||||||
START_BYTE,
|
START_BYTE,
|
||||||
(byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink,
|
(byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink
|
||||||
(byte)itemIdFlag
|
|
||||||
};
|
};
|
||||||
bytes.AddRange(idBytes);
|
bytes.AddRange(idBytes);
|
||||||
// unk
|
// unk
|
||||||
|
|
@ -109,7 +106,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
||||||
// unk
|
// unk
|
||||||
reader.ReadBytes(3);
|
reader.ReadBytes(3);
|
||||||
|
|
||||||
var itemNameLen = GetInteger(reader);
|
var itemNameLen = (int)GetInteger(reader);
|
||||||
var itemNameBytes = reader.ReadBytes(itemNameLen);
|
var itemNameBytes = reader.ReadBytes(itemNameLen);
|
||||||
|
|
||||||
// HQ items have the HQ symbol as part of the name, but since we already recorded
|
// 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);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
115
Dalamud/Game/Chat/SeStringHandling/Payloads/MapLinkPayload.cs
Normal file
115
Dalamud/Game/Chat/SeStringHandling/Payloads/MapLinkPayload.cs
Normal file
|
|
@ -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<byte>()
|
||||||
|
{
|
||||||
|
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<TerritoryType>().GetRow((int)TerritoryTypeId);
|
||||||
|
// Territory = dataResolver.GetExcelSheet<PlaceName>().GetRow(terrRow.PlaceName).Name;
|
||||||
|
// Zone = dataResolver.GetExcelSheet<PlaceName>().GetRow(terrRow.PlaceNameZone).Name;
|
||||||
|
|
||||||
|
// var mapSizeFactor = dataResolver.GetExcelSheet<Map>().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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,12 +12,12 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
||||||
public override PayloadType Type => PayloadType.Player;
|
public override PayloadType Type => PayloadType.Player;
|
||||||
|
|
||||||
public string PlayerName { get; private set; }
|
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 string ServerName { get; private set; } = String.Empty;
|
||||||
|
|
||||||
public PlayerPayload() { }
|
public PlayerPayload() { }
|
||||||
|
|
||||||
public PlayerPayload(string playerName, int serverId)
|
public PlayerPayload(string playerName, uint serverId)
|
||||||
{
|
{
|
||||||
PlayerName = playerName;
|
PlayerName = playerName;
|
||||||
ServerId = serverId;
|
ServerId = serverId;
|
||||||
|
|
@ -78,7 +78,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
||||||
// unk
|
// unk
|
||||||
reader.ReadBytes(2);
|
reader.ReadBytes(2);
|
||||||
|
|
||||||
var nameLen = GetInteger(reader);
|
var nameLen = (int)GetInteger(reader);
|
||||||
PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen));
|
PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,13 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
||||||
{
|
{
|
||||||
public override PayloadType Type => PayloadType.Status;
|
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 string StatusName { get; private set; } = string.Empty;
|
||||||
|
|
||||||
public StatusPayload() { }
|
public StatusPayload() { }
|
||||||
|
|
||||||
public StatusPayload(int statusId)
|
public StatusPayload(uint statusId)
|
||||||
{
|
{
|
||||||
StatusId = statusId;
|
StatusId = statusId;
|
||||||
}
|
}
|
||||||
|
|
@ -35,13 +35,11 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
||||||
public override byte[] Encode()
|
public override byte[] Encode()
|
||||||
{
|
{
|
||||||
var idBytes = MakeInteger(StatusId);
|
var idBytes = MakeInteger(StatusId);
|
||||||
var idPrefix = GetTypeForIntegerBytes(idBytes);
|
|
||||||
|
|
||||||
var chunkLen = idBytes.Length + 8;
|
var chunkLen = idBytes.Length + 7;
|
||||||
var bytes = new List<byte>()
|
var bytes = new List<byte>()
|
||||||
{
|
{
|
||||||
START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.Status,
|
START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.Status
|
||||||
(byte)idPrefix
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bytes.AddRange(idBytes);
|
bytes.AddRange(idBytes);
|
||||||
|
|
|
||||||
|
|
@ -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<byte>(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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
Dalamud/Game/Chat/SeStringHandling/Payloads/UIGlowPayload.cs
Normal file
58
Dalamud/Game/Chat/SeStringHandling/Payloads/UIGlowPayload.cs
Normal file
|
|
@ -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<byte>(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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Data;
|
||||||
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
||||||
|
|
||||||
namespace Dalamud.Game.Chat.SeStringHandling
|
namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ namespace Dalamud.Game {
|
||||||
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", "").Replace(".", ""), out var itemValue))
|
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", "").Replace(".", ""), out var itemValue))
|
||||||
continue;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue