Initial pieces of payload rework; everything is read-only at the moment, pending next stages. Lumina is now used for on-demand property resolution, properties are only exposed for user-facing values, and mostly expose the lumina object(s) where applicable

This commit is contained in:
meli 2020-04-21 16:41:03 -07:00
parent 22e9eb89b8
commit f2cee5f5bc
17 changed files with 355 additions and 253 deletions

View file

@ -12,6 +12,7 @@ using Dalamud.Data;
using Dalamud.DiscordBot;
using Dalamud.Game;
using Dalamud.Game.Chat;
using Dalamud.Game.Chat.SeStringHandling;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
@ -103,6 +104,9 @@ namespace Dalamud {
this.Data = new DataManager(this.StartInfo.Language);
this.Data.Initialize();
// TODO: better way to do this? basically for lumina injection
SeString.Dalamud = this;
this.ClientState = new ClientState(this, info, this.SigScanner);
this.BotManager = new DiscordBotManager(this, this.Configuration.DiscordFeatureConfig);

View file

@ -0,0 +1,53 @@
using Lumina.Excel;
namespace Dalamud.Data.TransientSheet
{
[Sheet( "Completion", columnHash: 0x2e6c55a3 )]
public class Completion : IExcelRow
{
// column defs from Mon, 02 Mar 2020 11:00:20 GMT
// col: 03 offset: 0000
public string Text;
// col: 04 offset: 0004
public string GroupTitle;
// col: 02 offset: 0008
public string LookupTable;
// col: 00 offset: 000c
public ushort Group;
// col: 01 offset: 000e
public ushort Key;
public int RowId { get; set; }
public int SubRowId { get; set; }
public void PopulateData( RowParser parser, Lumina.Lumina lumina )
{
RowId = parser.Row;
SubRowId = parser.SubRow;
// col: 3 offset: 0000
Text = parser.ReadOffset< string >( 0x0 );
// col: 4 offset: 0004
GroupTitle = parser.ReadOffset< string >( 0x4 );
// col: 2 offset: 0008
LookupTable = parser.ReadOffset< string >( 0x8 );
// col: 0 offset: 000c
Group = parser.ReadOffset< ushort >( 0xc );
// col: 1 offset: 000e
Key = parser.ReadOffset< ushort >( 0xe );
}
}
}

View file

@ -205,10 +205,8 @@ namespace Dalamud.DiscordBot {
senderWorld = this.dalamud.ClientState.LocalPlayer.HomeWorld.GameData.Name;
} else {
playerLink.Resolve();
senderName = wasOutgoingTell ? this.dalamud.ClientState.LocalPlayer.Name : playerLink.PlayerName;
senderWorld = playerLink.ServerName;
senderWorld = playerLink.World.Name;
}
var rawMessage = SeString.Parse(message.RawData).TextValue;

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Data;
using Dalamud.Game.Chat.SeStringHandling.Payloads;
using Serilog;
@ -11,6 +12,7 @@ using Serilog;
// - lumina DI
// - design for handling raw values vs resolved values, both for input and output
// - wrapper class(es) for handling of composite links in chat (item, map etc) and formatting operations
// - add italics payload
namespace Dalamud.Game.Chat.SeStringHandling
{
@ -21,12 +23,23 @@ namespace Dalamud.Game.Chat.SeStringHandling
{
public abstract PayloadType Type { get; }
public abstract void Resolve();
public abstract byte[] Encode();
protected abstract void ProcessChunkImpl(BinaryReader reader, long endOfStream);
// :(
protected DataManager dataResolver;
public Payload()
{
// this is not a good way to do this, but I don't want to have to include a dalamud
// reference on multiple methods in every payload class
// We could also just directly reference this static where we use it, but this at least
// allows for more easily changing how this is injected later, without affecting code
// that makes use of it
this.dataResolver = SeString.Dalamud.Data;
}
public static Payload Process(BinaryReader reader)
{
Payload payload = null;
@ -78,10 +91,13 @@ namespace Dalamud.Game.Chat.SeStringHandling
case EmbeddedInfoType.LinkTerminator:
// this has no custom handling and so needs to fallthrough to ensure it is captured
default:
Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType);
// but I'm also tired of this log
if (subType != EmbeddedInfoType.LinkTerminator)
{
Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType);
}
// rewind so we capture the Interactable byte in the raw data
reader.BaseStream.Seek(-1, SeekOrigin.Current);
payload = new RawPayload((byte)chunkType);
break;
}
}
@ -101,11 +117,11 @@ namespace Dalamud.Game.Chat.SeStringHandling
default:
Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType);
payload = new RawPayload((byte)chunkType);
break;
}
payload?.ProcessChunkImpl(reader, reader.BaseStream.Position + chunkLen - 1);
payload ??= new RawPayload((byte)chunkType);
payload.ProcessChunkImpl(reader, reader.BaseStream.Position + chunkLen - 1);
// read through the rest of the packet
var readBytes = (uint)(reader.BaseStream.Position - packetStart);

View file

@ -1,7 +1,10 @@
using Dalamud.Data.TransientSheet;
using Lumina.Excel.GeneratedSheets;
using Serilog;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
@ -9,78 +12,29 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
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()
private string text;
public string Text
{
// 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}");
// }
// }
//}
get
{
this.text ??= Resolve();
return this.text;
}
}
private uint group;
private uint key;
public override byte[] Encode()
{
var keyBytes = MakeInteger(Key);
var keyBytes = MakeInteger(this.key);
var chunkLen = keyBytes.Length + 2;
var bytes = new List<byte>()
{
START_BYTE,
(byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen,
(byte)Group
(byte)this.group
};
bytes.AddRange(keyBytes);
bytes.Add(END_BYTE);
@ -90,16 +44,83 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
public override string ToString()
{
return $"{Type} - Group: {Group}, Key: {Key}, Text: {Text}";
return $"{Type} - Group: {group}, Key: {key}";
}
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();
this.group = reader.ReadByte();
Key = GetInteger(reader);
this.key = GetInteger(reader);
}
private string Resolve()
{
string value = null;
var sheet = this.dataResolver.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((int)this.key);
}
catch { } // don't care, row will be null
if (row?.Group == this.group)
{
// if the row exists in this table and the group matches, this is actually the correct data
value = row.Text;
}
else
{
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 == this.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 ikey = (int)this.key;
var name = actualTableName switch
{
"Action" => this.dataResolver.GetExcelSheet<Lumina.Excel.GeneratedSheets.Action>().GetRow(ikey).Name,
"ActionComboRoute" => this.dataResolver.GetExcelSheet<ActionComboRoute>().GetRow(ikey).Name,
"BuddyAction" => this.dataResolver.GetExcelSheet<BuddyAction>().GetRow(ikey).Name,
"ClassJob" => this.dataResolver.GetExcelSheet<ClassJob>().GetRow(ikey).Name,
"Companion" => this.dataResolver.GetExcelSheet<Companion>().GetRow(ikey).Singular,
"CraftAction" => this.dataResolver.GetExcelSheet<CraftAction>().GetRow(ikey).Name,
"GeneralAction" => this.dataResolver.GetExcelSheet<GeneralAction>().GetRow(ikey).Name,
"GuardianDeity" => this.dataResolver.GetExcelSheet<GuardianDeity>().GetRow(ikey).Name,
"MainCommand" => this.dataResolver.GetExcelSheet<MainCommand>().GetRow(ikey).Name,
"Mount" => this.dataResolver.GetExcelSheet<Mount>().GetRow(ikey).Singular,
"Pet" => this.dataResolver.GetExcelSheet<Pet>().GetRow(ikey).Name,
"PetAction" => this.dataResolver.GetExcelSheet<PetAction>().GetRow(ikey).Name,
// TODO: lumina doesn't have PetMirage
"PlaceName" => this.dataResolver.GetExcelSheet<PlaceName>().GetRow(ikey).Name,
"Race" => this.dataResolver.GetExcelSheet<Race>().GetRow(ikey).Masculine,
"TextCommand" => this.dataResolver.GetExcelSheet<TextCommand>().GetRow(ikey).Command,
"Tribe" => this.dataResolver.GetExcelSheet<Tribe>().GetRow(ikey).Masculine,
"Weather" => this.dataResolver.GetExcelSheet<Weather>().GetRow(ikey).Name,
_ => throw new Exception(actualTableName)
};
value = name;
}
catch (Exception e)
{
Log.Error(e, $"AutoTranslatePayload - failed to resolve: {this}");
}
}
return value;
}
}
}

View file

@ -1,9 +1,9 @@
using Dalamud.Data.TransientSheet;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
@ -11,38 +11,34 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
public override PayloadType Type => PayloadType.Item;
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(uint itemId, bool isHQ)
private Item item;
public Item Item
{
ItemId = itemId;
IsHQ = isHQ;
}
public override void Resolve()
{
if (string.IsNullOrEmpty(ItemName))
get
{
dynamic item = XivApi.GetItem((int)ItemId).GetAwaiter().GetResult();
ItemName = item.Name;
this.item ??= this.dataResolver.GetExcelSheet<Item>().GetRow((int)this.itemId);
return this.item;
}
}
public bool IsHQ { get; private set; } = false;
private uint itemId;
// mainly to allow overriding the name (for things like owo)
private string displayName;
public override byte[] Encode()
{
var actualItemId = IsHQ ? ItemId + 1000000 : ItemId;
var actualItemId = IsHQ ? this.itemId + 1000000 : this.itemId;
var idBytes = MakeInteger(actualItemId);
bool hasName = !string.IsNullOrEmpty(ItemName);
bool hasName = !string.IsNullOrEmpty(this.displayName);
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
chunkLen += (1 + 1 + ItemName.Length);
chunkLen += (1 + 1 + this.displayName.Length);
if (IsHQ)
{
chunkLen += 4; // unicode representation of the HQ symbol is 3 bytes, preceded by a space
@ -61,7 +57,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
// Links don't have to include the name, but if they do, it requires additional work
if (hasName)
{
var nameLen = ItemName.Length + 1;
var nameLen = this.displayName.Length + 1;
if (IsHQ)
{
nameLen += 4; // space plus 3 bytes for HQ symbol
@ -72,7 +68,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
0xFF, // unk
(byte)nameLen
});
bytes.AddRange(Encoding.UTF8.GetBytes(ItemName));
bytes.AddRange(Encoding.UTF8.GetBytes(this.displayName));
if (IsHQ)
{
@ -88,16 +84,16 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
public override string ToString()
{
return $"{Type} - ItemId: {ItemId}, ItemName: {ItemName}, IsHQ: {IsHQ}";
return $"{Type} - ItemId: {itemId}, IsHQ: {IsHQ}";
}
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
{
ItemId = GetInteger(reader);
this.itemId = GetInteger(reader);
if (ItemId > 1000000)
if (this.itemId > 1000000)
{
ItemId -= 1000000;
this.itemId -= 1000000;
IsHQ = true;
}
@ -109,6 +105,11 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
var itemNameLen = (int)GetInteger(reader);
var itemNameBytes = reader.ReadBytes(itemNameLen);
// it probably isn't necessary to store this, as we now get the lumina Item
// on demand from the id, which will have the name
// For incoming links, the name "should?" always match
// but we'll store it for use in encode just in case it doesn't
// HQ items have the HQ symbol as part of the name, but since we already recorded
// the HQ flag, we want just the bare name
if (IsHQ)
@ -116,7 +117,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
itemNameBytes = itemNameBytes.Take(itemNameLen - 4).ToArray();
}
ItemName = Encoding.UTF8.GetString(itemNameBytes);
this.displayName = Encoding.UTF8.GetString(itemNameBytes);
}
}

View file

@ -9,18 +9,69 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
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; }
private Map map;
public Map Map
{
get
{
this.map ??= this.dataResolver.GetExcelSheet<Map>().GetRow((int)this.mapId);
return this.map;
}
}
// 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; }
private TerritoryType territoryType;
public TerritoryType TerritoryType
{
get
{
this.territoryType ??= this.dataResolver.GetExcelSheet<TerritoryType>().GetRow((int)this.territoryTypeId);
return this.territoryType;
}
}
// these could be cached, but this isn't really too egregious
public float XCoord
{
get
{
return ConvertRawPositionToMapCoordinate(this.rawX, Map.SizeFactor);
}
}
public float YCoord
{
get
{
return ConvertRawPositionToMapCoordinate(this.rawY, Map.SizeFactor);
}
}
private string placeNameRegion;
public string PlaceNameRegion
{
get
{
this.placeNameRegion ??= this.dataResolver.GetExcelSheet<PlaceName>().GetRow(TerritoryType.PlaceNameRegion).Name;
return this.placeNameRegion;
}
}
private string placeName;
public string PlaceName
{
get
{
this.placeName ??= this.dataResolver.GetExcelSheet<PlaceName>().GetRow(TerritoryType.PlaceName).Name;
return this.placeName;
}
}
public string DataString => $"m:{TerritoryType.RowId},{Map.RowId},{unchecked((int)rawX)},{unchecked((int)rawY)}";
private uint territoryTypeId;
private uint mapId;
private uint rawX;
private uint rawY;
// there is no Z; it's purely in the text payload where applicable
public override byte[] Encode()
@ -28,9 +79,9 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
// 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 packedTerritoryAndMapBytes = MakePackedInteger(this.territoryTypeId, this.mapId);
var xBytes = MakeInteger(this.rawX);
var yBytes = MakeInteger(this.rawY);
var chunkLen = 4 + packedTerritoryAndMapBytes.Length + xBytes.Length + yBytes.Length;
@ -50,24 +101,9 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
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);
//}
}
public override string ToString()
{
return $"{Type} - TerritoryTypeId: {TerritoryTypeId}, MapId: {MapId}, RawX: {RawX}, RawY: {RawY}";
return $"{Type} - TerritoryTypeId: {territoryTypeId}, MapId: {mapId}, RawX: {rawX}, RawY: {rawY}";
}
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
@ -79,9 +115,9 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
try
{
(TerritoryTypeId, MapId) = GetPackedIntegers(reader);
RawX = (uint)GetInteger(reader);
RawY = (uint)GetInteger(reader);
(this.territoryTypeId, this.mapId) = GetPackedIntegers(reader);
this.rawX = (uint)GetInteger(reader);
this.rawY = (uint)GetInteger(reader);
// the Z coordinate is never in this chunk, just the text (if applicable)
// seems to always be FF 01

View file

@ -1,9 +1,8 @@
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
@ -12,26 +11,19 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
public override PayloadType Type => PayloadType.Player;
public string PlayerName { get; private set; }
public uint ServerId { get; private set; }
public string ServerName { get; private set; } = String.Empty;
public PlayerPayload() { }
public PlayerPayload(string playerName, uint serverId)
private World world;
public World World
{
PlayerName = playerName;
ServerId = serverId;
}
public override void Resolve()
{
if (string.IsNullOrEmpty(ServerName))
get
{
dynamic server = XivApi.Get($"World/{ServerId}").GetAwaiter().GetResult();
ServerName = server.Name;
this.world ??= this.dataResolver.GetExcelSheet<World>().GetRow((int)this.serverId);
return this.world;
}
}
private uint serverId;
public override byte[] Encode()
{
var chunkLen = PlayerName.Length + 7;
@ -40,7 +32,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
START_BYTE,
(byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName,
/* unk */ 0x01,
(byte)(ServerId+1), // I didn't want to deal with single-byte values in MakeInteger, so we have to do the +1 manually
(byte)(this.serverId+1), // I didn't want to deal with single-byte values in MakeInteger, so we have to do the +1 manually
/* unk */0x01, /* unk */0xFF, // these sometimes vary but are frequently this
(byte)(PlayerName.Length+1)
};
@ -50,7 +42,10 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
// encoded names are followed by the name in plain text again
// use the payload parsing for consistency, as this is technically a new chunk
bytes.AddRange(new TextPayload(PlayerName).Encode());
// bytes.AddRange(new TextPayload(PlayerName).Encode());
// FIXME
bytes.AddRange(Encoding.UTF8.GetBytes(PlayerName));
// unsure about this entire packet, but it seems to always follow a name
bytes.AddRange(new byte[]
@ -65,7 +60,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
public override string ToString()
{
return $"{Type} - PlayerName: {PlayerName}, ServerId: {ServerId}, ServerName: {ServerName}";
return $"{Type} - PlayerName: {PlayerName}, ServerId: {serverId}";
}
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
@ -73,7 +68,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
// unk
reader.ReadByte();
ServerId = GetInteger(reader);
this.serverId = GetInteger(reader);
// unk
reader.ReadBytes(2);

View file

@ -8,17 +8,13 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
public override PayloadType Type => PayloadType.Unknown;
public byte ChunkType { get; private set; }
public byte[] Data { get; private set; }
private byte chunkType;
public RawPayload(byte chunkType)
{
ChunkType = chunkType;
}
public override void Resolve()
{
// nothing to do
this.chunkType = chunkType;
}
public override byte[] Encode()
@ -28,7 +24,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
var bytes = new List<byte>()
{
START_BYTE,
ChunkType,
this.chunkType,
(byte)chunkLen
};
bytes.AddRange(Data);
@ -40,7 +36,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
public override string ToString()
{
return $"{Type} - Chunk type: {ChunkType:X}, Data: {BitConverter.ToString(Data).Replace("-", " ")}";
return $"{Type} - Chunk type: {chunkType:X}, Data: {BitConverter.ToString(Data).Replace("-", " ")}";
}
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)

View file

@ -1,9 +1,7 @@
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
@ -11,30 +9,21 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
public override PayloadType Type => PayloadType.Status;
public uint StatusId { get; private set; }
public string StatusName { get; private set; } = string.Empty;
public StatusPayload() { }
public StatusPayload(uint statusId)
private Status status;
public Status Status
{
StatusId = statusId;
}
public override void Resolve()
{
if (string.IsNullOrEmpty(StatusName))
get
{
dynamic status = XivApi.Get($"Status/{StatusId}").GetAwaiter().GetResult();
//Console.WriteLine($"Resolved status {StatusId} to {status.Name}");
StatusName = status.Name;
status ??= this.dataResolver.GetExcelSheet<Status>().GetRow((int)this.statusId);
return status;
}
}
private uint statusId;
public override byte[] Encode()
{
var idBytes = MakeInteger(StatusId);
var idBytes = MakeInteger(this.statusId);
var chunkLen = idBytes.Length + 7;
var bytes = new List<byte>()
@ -51,12 +40,12 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
public override string ToString()
{
return $"{Type} - StatusId: {StatusId}, StatusName: {StatusName}";
return $"{Type} - StatusId: {statusId}";
}
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
{
StatusId = GetInteger(reader);
this.statusId = GetInteger(reader);
}
}
}

View file

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
@ -11,36 +9,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
public override PayloadType Type => PayloadType.RawText;
private string textConverted = null;
/// <summary>
/// The Text of this text payload as an UTF-8 converted string.
/// Don't rely on this for accurate representation of SE payload data, please check RawData instead.
/// </summary>
public string Text {
get { return this.textConverted ??= Encoding.UTF8.GetString(RawData); }
set {
this.textConverted = value;
RawData = Encoding.UTF8.GetBytes(value);
}
}
/// <summary>
/// The raw unconverted data of this text payload.
/// </summary>
public byte[] RawData { get; set; }
public TextPayload() { }
public TextPayload(string text)
{
Text = text;
}
public override void Resolve()
{
// nothing to do
}
public string Text { get; private set; }
public override byte[] Encode()
{

View file

@ -1,3 +1,4 @@
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.IO;
@ -8,15 +9,29 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
public override PayloadType Type => PayloadType.UIForeground;
public ushort RawColor { get; private set; }
private UIColor color;
public UIColor UIColor
{
get
{
this.color ??= this.dataResolver.GetExcelSheet<UIColor>().GetRow(this.colorKey);
return this.color;
}
}
//public int Red { get; private set; }
//public int Green { get; private set; }
//public int Blue { get; private set; }
public uint RGB
{
get
{
return UIColor.UIForeground;
}
}
private ushort colorKey;
public override byte[] Encode()
{
var colorBytes = MakeInteger(RawColor);
var colorBytes = MakeInteger(this.colorKey);
var chunkLen = colorBytes.Length + 1;
var bytes = new List<byte>(new byte[]
@ -30,19 +45,14 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
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}";
return $"{Type} - UIColor: {colorKey}";
}
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
{
RawColor = (ushort)GetInteger(reader);
this.colorKey = (ushort)GetInteger(reader);
}
protected override byte GetMarkerForIntegerBytes(byte[] bytes)

View file

@ -1,3 +1,4 @@
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.IO;
@ -8,15 +9,29 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
public override PayloadType Type => PayloadType.UIGlow;
public ushort RawColor { get; private set; }
private UIColor color;
public UIColor UIColor
{
get
{
this.color ??= this.dataResolver.GetExcelSheet<UIColor>().GetRow(this.colorKey);
return this.color;
}
}
//public int Red { get; private set; }
//public int Green { get; private set; }
//public int Blue { get; private set; }
public uint RGB
{
get
{
return UIColor.UIGlow;
}
}
private ushort colorKey;
public override byte[] Encode()
{
var colorBytes = MakeInteger(RawColor);
var colorBytes = MakeInteger(this.colorKey);
var chunkLen = colorBytes.Length + 1;
var bytes = new List<byte>(new byte[]
@ -30,19 +45,14 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
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}";
return $"{Type} - UIColor: {colorKey}";
}
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
{
RawColor = (ushort)GetInteger(reader);
this.colorKey = (ushort)GetInteger(reader);
}
protected override byte GetMarkerForIntegerBytes(byte[] bytes)

View file

@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
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
@ -14,6 +11,9 @@ namespace Dalamud.Game.Chat.SeStringHandling
/// </summary>
public class SeString
{
// TODO: probably change how this is done/where it comes from
public static Dalamud Dalamud { get; internal set; }
public List<Payload> Payloads { get; }
public SeString(List<Payload> payloads)
@ -37,6 +37,11 @@ namespace Dalamud.Game.Chat.SeStringHandling
{
sb.Append(((TextPayload)p).Text);
}
// TODO: temporary (probably)
else if (p.Type == PayloadType.AutoTranslateText)
{
sb.Append($"{{ {((AutoTranslatePayload)p).Text} }}");
}
}
return sb.ToString();
@ -52,9 +57,9 @@ namespace Dalamud.Game.Chat.SeStringHandling
{
var payloads = new List<Payload>();
using (var stream = new MemoryStream(bytes)) {
using var reader = new BinaryReader(stream);
using (var stream = new MemoryStream(bytes))
using (var reader = new BinaryReader(stream))
{
while (stream.Position < bytes.Length)
{
var payload = Payload.Process(reader);

View file

@ -181,14 +181,14 @@ namespace Dalamud.Game {
break;
}
Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.ItemId}, HQ {itemLink.IsHQ}");
Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}");
var valueInfo = matchInfo.Groups["value"];
// not sure if using a culture here would work correctly, so just strip symbols instead
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", "").Replace(".", ""), out var itemValue))
continue;
Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale((int)itemLink.ItemId, itemValue, itemLink.IsHQ));
Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale((int)itemLink.Item.RowId, itemValue, itemLink.IsHQ));
break;
}
}

View file

@ -112,8 +112,8 @@ namespace Dalamud.Game.Internal.Gui {
if (!FastByteArrayCompare(oldEdited, newEdited)) {
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
message.RawData = newEdited;
}
Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
}
var messagePtr = pMessage;
OwnedStdString allocatedString = null;

View file

@ -151,8 +151,7 @@ namespace Dalamud.Game.Internal.Gui {
openMapWithFlag =
Address.GetVirtualFunction<OpenMapWithFlagDelegate>(uiMapObjectPtr, 0, 63);
var mapLinkString =
$"m:{mapLink.TerritoryTypeId},{mapLink.MapId},{unchecked((int)mapLink.RawX)},{unchecked((int)mapLink.RawY)}";
var mapLinkString = mapLink.DataString;
Log.Debug($"OpenMapWithMapLink: Opening Map Link: {mapLinkString}");