comment a bunch of things, fix some accessibility issues, update ToString display, assorted minor cleanup

This commit is contained in:
meli 2020-04-25 22:04:31 -07:00
parent 474efadc57
commit 66e7fda058
14 changed files with 386 additions and 31 deletions

View file

@ -8,7 +8,6 @@ using Serilog;
// TODOs:
// - refactor integer handling now that we have multiple packed types
// - wrapper class(es) for handling of composite links in chat (item, map etc) and formatting operations
// Maybes:
// - convert parsing to custom structs for each payload? would make some code prettier and easier to work with
// but also wouldn't work out as well for things that are dynamically-sized
@ -22,18 +21,41 @@ namespace Dalamud.Game.Chat.SeStringHandling
/// </summary>
public abstract class Payload
{
/// <summary>
/// The type of this payload.
/// </summary>
public abstract PayloadType Type { get; }
/// <summary>
/// Whether this payload has been modified since the last Encode().
/// </summary>
public bool Dirty { get; protected set; } = true;
/// <summary>
/// Encodes the internal state of this payload into a byte[] suitable for sending to in-game
/// handlers such as the chat log.
/// </summary>
/// <returns>Encoded binary payload data suitable for use with in-game handlers.</returns>
protected abstract byte[] EncodeImpl();
// TODO: endOfStream is somewhat legacy now that payload length is always handled correctly.
// This could be changed to just take a straight byte[], but that would complicate reading
// but we could probably at least remove the end param
/// <summary>
/// Decodes a byte stream from the game into a payload object.
/// </summary>
/// <param name="reader">A BinaryReader containing at least all the data for this payload.</param>
/// <param name="endOfStream">The location holding the end of the data for this payload.</param>
protected abstract void DecodeImpl(BinaryReader reader, long endOfStream);
// :(
/// <summary>
/// The Lumina instance to use for any necessary data lookups.
/// </summary>
protected DataManager dataResolver;
protected byte[] encodedData;
// private for now, since subclasses shouldn't interact with this
// To force-invalidate it, Dirty can be set to true
private byte[] encodedData;
protected Payload()
{
@ -45,6 +67,11 @@ namespace Dalamud.Game.Chat.SeStringHandling
this.dataResolver = SeString.Dalamud.Data;
}
/// <summary>
/// Encode this payload object into a byte[] useable in-game for things like the chat log.
/// </summary>
/// <param name="force">If true, ignores any cached value and forcibly reencodes the payload from its internal representation.</param>
/// <returns>A byte[] suitable for use with in-game handlers such as the chat log.</returns>
public byte[] Encode(bool force = false)
{
if (Dirty || force)
@ -56,6 +83,11 @@ namespace Dalamud.Game.Chat.SeStringHandling
return this.encodedData;
}
/// <summary>
/// Decodes a binary representation of a payload into its corresponding nice object payload.
/// </summary>
/// <param name="reader">A reader positioned at the start of the payload, and containing at least one entire payload.</param>
/// <returns>The constructed Payload-derived object that was decoded from the binary data.</returns>
public static Payload Decode(BinaryReader reader)
{
var payloadStartPos = reader.BaseStream.Position;
@ -96,6 +128,7 @@ namespace Dalamud.Game.Chat.SeStringHandling
var packetStart = reader.BaseStream.Position;
// any unhandled payload types will be turned into a RawPayload with the exact same binary data
switch (chunkType)
{
case SeStringChunkType.EmphasisItalic:
@ -197,6 +230,10 @@ namespace Dalamud.Game.Chat.SeStringHandling
LinkTerminator = 0xCF // not clear but seems to always follow a link
}
// TODO - everything below needs to be completely refactored, now that we have run into
// a lot more cases than were originally handled.
protected enum IntegerType
{
// used as an internal marker; sometimes single bytes are bare with no marker at all
@ -205,7 +242,6 @@ namespace Dalamud.Game.Chat.SeStringHandling
Byte = 0xF0,
ByteTimes256 = 0xF1,
Int16 = 0xF2,
// ByteTimes65536 = 0xF3, // from RE but never seen
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,

View file

@ -8,11 +8,20 @@ using System.Linq;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
/// <summary>
/// An SeString Payload containing an auto-translation/completion chat message.
/// </summary>
public class AutoTranslatePayload : Payload, ITextProvider
{
public override PayloadType Type => PayloadType.AutoTranslateText;
private string text;
/// <summary>
/// The actual text displayed in-game for this payload.
/// </summary>
/// <remarks>
/// Value is evaluated lazily and cached.
/// </remarks>
public string Text
{
get
@ -29,6 +38,15 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
internal AutoTranslatePayload() { }
/// <summary>
/// Creates a new auto-translate payload.
/// </summary>
/// <param name="group">The group id for this message.</param>
/// <param name="key">The key/row id for this message. Which table this is in depends on the group id and details the Completion table.</param>
/// <remarks>
/// This table is somewhat complicated in structure, and so using this constructor may not be very nice.
/// There is probably little use to create one of these, however.
/// </remarks>
public AutoTranslatePayload(uint group, uint key)
{
this.group = group;
@ -39,7 +57,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
public override string ToString()
{
return $"{Type} - Group: {group}, Key: {key}";
return $"{Type} - Group: {group}, Key: {key}, Text: {Text}";
}
protected override byte[] EncodeImpl()

View file

@ -4,17 +4,37 @@ using System.IO;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
class EmphasisItalicPayload : Payload
/// <summary>
/// An SeString Payload containing information about enabling or disabling italics formatting on following text.
/// </summary>
/// <remarks>
/// As with other formatting payloads, this is only useful in a payload block, where it affects any subsequent
/// text payloads.
/// </remarks>
public class EmphasisItalicPayload : Payload
{
/// <summary>
/// Payload representing enabling italics on following text.
/// </summary>
public static EmphasisItalicPayload ItalicsOn => new EmphasisItalicPayload(true);
/// <summary>
/// Payload representing disabling italics on following text.
/// </summary>
public static EmphasisItalicPayload ItalicsOff => new EmphasisItalicPayload(false);
public override PayloadType Type => PayloadType.EmphasisItalic;
/// <summary>
/// Whether this payload enables italics formatting for following text.
/// </summary>
public bool IsEnabled { get; private set; }
internal EmphasisItalicPayload() { }
/// <summary>
/// Creates an EmphasisItalicPayload.
/// </summary>
/// <param name="enabled">Whether italics formatting should be enabled or disabled for following text.</param>
public EmphasisItalicPayload(bool enabled)
{
IsEnabled = enabled;

View file

@ -7,11 +7,20 @@ using System.Text;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
/// <summary>
/// An SeString Payload representing an interactable item link.
/// </summary>
public class ItemPayload : Payload
{
public override PayloadType Type => PayloadType.Item;
private Item item;
/// <summary>
/// The underlying Lumina Item represented by this payload.
/// </summary>
/// <remarks>
/// Value is evaluated lazily and cached.
/// </remarks>
public Item Item
{
get
@ -22,7 +31,14 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
}
// mainly to allow overriding the name (for things like owo)
private string displayName;
// TODO: even though this is present in some item links, it may not really have a use at all
// For things like owo, changing the text payload is probably correct, whereas changing the
// actual embedded name might not work properly.
private string displayName = null;
/// <summary>
/// The displayed name for this item link. Note that incoming links only sometimes have names embedded,
/// often the name is only present in a following text payload.
/// </summary>
public string DisplayName
{
get
@ -37,12 +53,23 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
}
}
/// <summary>
/// Whether or not this item link is for a high-quality version of the item.
/// </summary>
public bool IsHQ { get; private set; } = false;
private uint itemId;
internal ItemPayload() { }
/// <summary>
/// Creates a payload representing an interactable item link for the specified item.
/// </summary>
/// <param name="itemId">The id of the item.</param>
/// <param name="isHQ">Whether or not the link should be for the high-quality variant of the item.</param>
/// <param name="displayNameOverride">An optional name to include in the item link. Typically this should
/// be left as null, or set to the normal item name. Actual overrides are better done with the subsequent
/// TextPayload that is a part of a full item link in chat.</param>
public ItemPayload(uint itemId, bool isHQ, string displayNameOverride = null)
{
this.itemId = itemId;
@ -52,7 +79,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
public override string ToString()
{
return $"{Type} - ItemId: {itemId}, IsHQ: {IsHQ}";
return $"{Type} - ItemId: {itemId}, IsHQ: {IsHQ}, Name: {this.displayName ?? Item.Name}";
}
protected override byte[] EncodeImpl()

View file

@ -5,11 +5,20 @@ using System.IO;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
/// <summary>
/// An SeString Payload representing an interactable map position link.
/// </summary>
public class MapLinkPayload : Payload
{
public override PayloadType Type => PayloadType.MapLink;
private Map map;
/// <summary>
/// The Map specified for this map link.
/// </summary>
/// <remarks>
/// Value is evaluated lazily and cached.
/// </remarks>
public Map Map
{
get
@ -20,6 +29,12 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
}
private TerritoryType territoryType;
/// <summary>
/// The TerritoryType specified for this map link.
/// </summary>
/// <remarks>
/// Value is evaluated lazily and cached.
/// </remarks>
public TerritoryType TerritoryType
{
get
@ -29,23 +44,43 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
}
}
/// <summary>
/// The internal x-coordinate for this map position.
/// </summary>
public int RawX { get; private set; }
/// <summary>
/// The internal y-coordinate for this map position.
/// </summary>
public int RawY { get; private set; }
// these could be cached, but this isn't really too egregious
/// <summary>
/// The readable x-coordinate position for this map link. This value is approximate and unrounded.
/// </summary>
public float XCoord
{
get
{
return ConvertRawPositionToMapCoordinate(this.rawX, Map.SizeFactor);
return ConvertRawPositionToMapCoordinate(RawX, Map.SizeFactor);
}
}
/// <summary>
/// The readable y-coordinate position for this map link. This value is approximate and unrounded.
/// </summary>
public float YCoord
{
get
{
return ConvertRawPositionToMapCoordinate(this.rawY, Map.SizeFactor);
return ConvertRawPositionToMapCoordinate(RawY, Map.SizeFactor);
}
}
/// <summary>
/// The printable map coordinates for this link. This value tries to match the in-game printable text as closely as possible
/// but is an approximation and may be slightly off for some positions.
/// </summary>
public string CoordinateString
{
get
@ -57,11 +92,15 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
var x = Math.Truncate((XCoord+fudge) * 10.0f) / 10.0f;
var y = Math.Truncate((YCoord+fudge) * 10.0f) / 10.0f;
// the formatting and spacing the game uses
return $"( {x.ToString("0.0")} , {y.ToString("0.0")} )";
}
}
private string placeNameRegion;
/// <summary>
/// The region name for this map link. This corresponds to the upper zone name found in the actual in-game map UI. eg, "La Noscea"
/// </summary>
public string PlaceNameRegion
{
get
@ -72,6 +111,9 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
}
private string placeName;
/// <summary>
/// The place name for this map link. This corresponds to the lower zone name found in the actual in-game map UI. eg, "Limsa Lominsa Upper Decks"
/// </summary>
public string PlaceName
{
get
@ -81,16 +123,25 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
}
}
public string DataString => $"m:{TerritoryType.RowId},{Map.RowId},{rawX},{rawY}";
/// <summary>
/// The data string for this map link, for use by internal game functions that take a string variant and not a binary payload.
/// </summary>
public string DataString => $"m:{TerritoryType.RowId},{Map.RowId},{RawX},{RawY}";
private uint territoryTypeId;
private uint mapId;
private int rawX;
private int rawY;
// there is no Z; it's purely in the text payload where applicable
internal MapLinkPayload() { }
/// <summary>
/// Creates an interactable MapLinkPayload from a human-readable position.
/// </summary>
/// <param name="territoryTypeId">The id of the TerritoryType entry for this link.</param>
/// <param name="mapId">The id of the Map entry for this link.</param>
/// <param name="niceXCoord">The human-readable x-coordinate for this link.</param>
/// <param name="niceYCoord">The human-readable y-coordinate for this link.</param>
/// <param name="fudgeFactor">An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases.</param>
public MapLinkPayload(uint territoryTypeId, uint mapId, float niceXCoord, float niceYCoord, float fudgeFactor = 0.05f)
{
this.territoryTypeId = territoryTypeId;
@ -98,28 +149,35 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
// this fudge is necessary basically to ensure we don't shift down a full tenth
// because essentially values are truncated instead of rounded, so 3.09999f will become
// 3.0f and not 3.1f
this.rawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, Map.SizeFactor);
this.rawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, Map.SizeFactor);
RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, Map.SizeFactor);
RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, Map.SizeFactor);
}
/// <summary>
/// Creates an interactable MapLinkPayload from a raw position.
/// </summary>
/// <param name="territoryTypeId">The id of the TerritoryType entry for this link.</param>
/// <param name="mapId">The id of the Map entry for this link.</param>
/// <param name="rawX">The internal raw x-coordinate for this link.</param>
/// <param name="rawY">The internal raw y-coordinate for this link.</param>
public MapLinkPayload(uint territoryTypeId, uint mapId, int rawX, int rawY)
{
this.territoryTypeId = territoryTypeId;
this.mapId = mapId;
this.rawX = rawX;
this.rawY = rawY;
RawX = rawX;
RawY = rawY;
}
public override string ToString()
{
return $"{Type} - TerritoryTypeId: {territoryTypeId}, MapId: {mapId}, RawX: {rawX}, RawY: {rawY}";
return $"{Type} - TerritoryTypeId: {territoryTypeId}, MapId: {mapId}, RawX: {RawX}, RawY: {RawY}, display: {PlaceName} {CoordinateString}";
}
protected override byte[] EncodeImpl()
{
var packedTerritoryAndMapBytes = MakePackedInteger(this.territoryTypeId, this.mapId);
var xBytes = MakeInteger(unchecked((uint)this.rawX));
var yBytes = MakeInteger(unchecked((uint)this.rawY));
var xBytes = MakeInteger(unchecked((uint)RawX));
var yBytes = MakeInteger(unchecked((uint)RawY));
var chunkLen = 4 + packedTerritoryAndMapBytes.Length + xBytes.Length + yBytes.Length;
@ -149,8 +207,8 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
try
{
(this.territoryTypeId, this.mapId) = GetPackedIntegers(reader);
this.rawX = unchecked((int)GetInteger(reader));
this.rawY = unchecked((int)GetInteger(reader));
RawX = unchecked((int)GetInteger(reader));
RawY = unchecked((int)GetInteger(reader));
// the Z coordinate is never in this chunk, just the text (if applicable)
// seems to always be FF 01

View file

@ -6,11 +6,17 @@ using System.Text;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
/// <summary>
/// An SeString Payload representing a player link.
/// </summary>
public class PlayerPayload : Payload
{
public override PayloadType Type => PayloadType.Player;
private string playerName;
/// <summary>
/// The player's displayed name. This does not contain the server name.
/// </summary>
public string PlayerName
{
get { return this.playerName; }
@ -22,6 +28,12 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
}
private World world;
/// <summary>
/// The Lumina object representing the player's home server.
/// </summary>
/// <remarks>
/// Value is evaluated lazily and cached.
/// </remarks>
public World World
{
get
@ -31,10 +43,21 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
}
}
/// <summary>
/// A text representation of this player link matching how it might appear in-game.
/// The world name will always be present.
/// </summary>
public string DisplayedName => $"{PlayerName}{(char)SeIconChar.CrossWorld}{World.Name}";
private uint serverId;
internal PlayerPayload() { }
/// <summary>
/// Create a PlayerPayload link for the specified player.
/// </summary>
/// <param name="playerName">The player's displayed name.</param>
/// <param name="serverId">The player's home server id.</param>
public PlayerPayload(string playerName, uint serverId)
{
this.playerName = playerName;
@ -43,7 +66,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
public override string ToString()
{
return $"{Type} - PlayerName: {PlayerName}, ServerId: {serverId}";
return $"{Type} - PlayerName: {PlayerName}, ServerId: {serverId}, ServerName: {World.Name}";
}
protected override byte[] EncodeImpl()

View file

@ -5,21 +5,36 @@ using System.Linq;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
/// <summary>
/// An SeString Payload representing unhandled raw payload data.
/// Mainly useful for constructing unhandled hardcoded payloads, or forwarding any unknown
/// payloads without modification.
/// </summary>
public class RawPayload : Payload
{
// this and others could be an actual static member somewhere and avoid construction costs, but that probably isn't a real concern
/// <summary>
/// A fixed Payload representing a common link-termination sequence, found in many payload chains.
/// </summary>
public static RawPayload LinkTerminator => new RawPayload(new byte[] { 0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03 });
public override PayloadType Type => PayloadType.Unknown;
private byte[] data;
// this is a bit different from the underlying data
// We need to store just the chunk data for decode to behave nicely, but when reading data out
// it makes more sense to get the entire payload
/// <summary>
/// The entire payload byte sequence for this payload.
/// The returned data is a clone and modifications will not be persisted.
/// </summary>
public byte[] Data
{
get
{
// for now don't allow modifying the contents
// because we don't really have a way to track Dirty
return (byte[])data.Clone();
return (byte[])Encode().Clone();
}
}
@ -34,13 +49,14 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
// this payload is 'special' in that we require the entire chunk to be passed in
// and not just the data after the header
// This sets data to hold the chunk data fter the header, excluding the END_BYTE
this.chunkType = data[1];
this.data = data.Skip(3).Take(data.Length-4).ToArray();
}
public override string ToString()
{
return $"{Type} - Chunk type: {chunkType:X}, Data: {BitConverter.ToString(data).Replace("-", " ")}";
return $"{Type} - Data: {BitConverter.ToString(Data).Replace("-", " ")}";
}
protected override byte[] EncodeImpl()

View file

@ -5,11 +5,20 @@ using System.IO;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
/// <summary>
/// An SeString Payload representing an interactable status link.
/// </summary>
public class StatusPayload : Payload
{
public override PayloadType Type => PayloadType.Status;
private Status status;
/// <summary>
/// The Lumina Status object represented by this payload.
/// </summary>
/// <remarks>
/// Value is evaluated lazily and cached.
/// </remarks>
public Status Status
{
get
@ -23,6 +32,10 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
internal StatusPayload() { }
/// <summary>
/// Creates a new StatusPayload for the given status id.
/// </summary>
/// <param name="statusId">The id of the Status for this link.</param>
public StatusPayload(uint statusId)
{
this.statusId = statusId;
@ -30,7 +43,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
public override string ToString()
{
return $"{Type} - StatusId: {statusId}";
return $"{Type} - StatusId: {statusId}, Name: {Status.Name}";
}
protected override byte[] EncodeImpl()

View file

@ -5,12 +5,19 @@ using System.Text;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
/// <summary>
/// An SeString Payload representing a plain text string.
/// </summary>
public class TextPayload : Payload, ITextProvider
{
public override PayloadType Type => PayloadType.RawText;
// allow modifying the text of existing payloads on the fly
private string text;
/// <summary>
/// The text contained in this payload.
/// This may contain SE's special unicode characters.
/// </summary>
public string Text
{
get { return this.text; }
@ -28,6 +35,10 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
internal TextPayload() { }
/// <summary>
/// Creates a new TextPayload for the given text.
/// </summary>
/// <param name="text">The text to include for this payload.</param>
public TextPayload(string text)
{
this.text = text;

View file

@ -5,15 +5,30 @@ using System.IO;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
/// <summary>
/// An SeString Payload representing a UI foreground color applied to following text payloads.
/// </summary>
public class UIForegroundPayload : Payload
{
/// <summary>
/// Payload representing disabling foreground color on following text.
/// </summary>
public static UIForegroundPayload UIForegroundOff => new UIForegroundPayload(0);
public override PayloadType Type => PayloadType.UIForeground;
/// <summary>
/// Whether or not this payload represents applying a foreground color, or disabling one.
/// </summary>
public bool IsEnabled => ColorKey != 0;
private UIColor color;
/// <summary>
/// A Lumina UIColor object representing this payload. The actual color data is at UIColor.UIForeground
/// </summary>
/// <remarks>
/// Value is evaluated lazily and cached.
/// </remarks>
public UIColor UIColor
{
get
@ -23,6 +38,9 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
}
}
/// <summary>
/// The color key used as a lookup in the UIColor table for this foreground color.
/// </summary>
public ushort ColorKey
{
get { return this.colorKey; }
@ -34,6 +52,9 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
}
}
/// <summary>
/// The Red/Green/Blue values for this foreground color, encoded as a typical hex color.
/// </summary>
public uint RGB
{
get
@ -46,6 +67,10 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
internal UIForegroundPayload() { }
/// <summary>
/// Creates a new UIForegroundPayload for the given UIColor key.
/// </summary>
/// <param name="colorKey"></param>
public UIForegroundPayload(ushort colorKey)
{
this.colorKey = colorKey;
@ -53,7 +78,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
public override string ToString()
{
return $"{Type} - UIColor: {colorKey}";
return $"{Type} - UIColor: {colorKey} color: {(IsEnabled ? RGB : 0)}";
}
protected override byte[] EncodeImpl()

View file

@ -5,15 +5,30 @@ using System.IO;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
/// <summary>
/// An SeString Payload representing a UI glow color applied to following text payloads.
/// </summary>
public class UIGlowPayload : Payload
{
/// <summary>
/// Payload representing disabling glow color on following text.
/// </summary>
public static UIGlowPayload UIGlowOff => new UIGlowPayload(0);
public override PayloadType Type => PayloadType.UIGlow;
/// <summary>
/// Whether or not this payload represents applying a glow color, or disabling one.
/// </summary>
public bool IsEnabled => ColorKey != 0;
private UIColor color;
/// <summary>
/// A Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow
/// </summary>
/// <remarks>
/// Value is evaluated lazily and cached.
/// </remarks>
public UIColor UIColor
{
get
@ -23,6 +38,9 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
}
}
/// <summary>
/// The color key used as a lookup in the UIColor table for this glow color.
/// </summary>
public ushort ColorKey
{
get { return this.colorKey; }
@ -34,6 +52,9 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
}
}
/// <summary>
/// The Red/Green/Blue values for this glow color, encoded as a typical hex color.
/// </summary>
public uint RGB
{
get
@ -46,6 +67,10 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
internal UIGlowPayload() { }
/// <summary>
/// Creates a new UIForegroundPayload for the given UIColor key.
/// </summary>
/// <param name="colorKey"></param>
public UIGlowPayload(ushort colorKey)
{
this.colorKey = colorKey;
@ -53,7 +78,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
public override string ToString()
{
return $"{Type} - UIColor: {colorKey}";
return $"{Type} - UIColor: {colorKey} color: {(IsEnabled ? RGB : 0)}";
}
protected override byte[] EncodeImpl()

View file

@ -14,6 +14,9 @@ namespace Dalamud.Game.Chat.SeStringHandling
// TODO: probably change how this is done/where it comes from
internal static Dalamud Dalamud { get; set; }
/// <summary>
/// The ordered list of payloads included in this SeString.
/// </summary>
public List<Payload> Payloads { get; }
/// <summary>
@ -56,30 +59,54 @@ namespace Dalamud.Game.Chat.SeStringHandling
return new SeString(payloads);
}
/// <summary>
/// Creates a new SeString from an ordered list of payloads.
/// </summary>
/// <param name="payloads">The Payload objects to make up this string.</param>
public SeString(List<Payload> payloads)
{
Payloads = payloads;
}
/// <summary>
/// Appends the contents of one SeString to this one.
/// </summary>
/// <param name="other">The SeString to append to this one.</param>
/// <returns>This object.</returns>
public SeString Append(SeString other)
{
Payloads.AddRange(other.Payloads);
return this;
}
/// <summary>
/// Appends a list of payloads to this SeString.
/// </summary>
/// <param name="payloads">The Payloads to append.</param>
/// <returns>This object.</returns>
public SeString Append(List<Payload> payloads)
{
Payloads.AddRange(payloads);
return this;
}
/// <summary>
/// Appends a single payload to this SeString.
/// </summary>
/// <param name="payload">The payload to append.</param>
/// <returns>This object.</returns>
public SeString Append(Payload payload)
{
Payloads.Add(payload);
return this;
}
internal byte[] Encode()
/// <summary>
/// Encodes the Payloads in this SeString into a binary representation
/// suitable for use by in-game handlers, such as the chat log.
/// </summary>
/// <returns>The binary encoded payload data.</returns>
public byte[] Encode()
{
var messageBytes = new List<byte>();
foreach (var p in Payloads)

View file

@ -8,8 +8,18 @@ using DalamudItem = Dalamud.Data.TransientSheet.Item;
namespace Dalamud.Game.Chat.SeStringHandling
{
public class SeStringUtils
/// <summary>
/// A utility class for working with common SeString variants.
/// </summary>
public static class SeStringUtils
{
/// <summary>
/// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log.
/// </summary>
/// <param name="itemId">The id of the item to link.</param>
/// <param name="isHQ">Whether to link the high-quality variant of the item.</param>
/// <param name="displayNameOverride">An optional name override to display, instead of the actual item name.</param>
/// <returns>An SeString containing all the payloads necessary to display an item link in the chat log.</returns>
public static SeString CreateItemLink(uint itemId, bool isHQ, string displayNameOverride = null)
{
string displayName = displayNameOverride ?? SeString.Dalamud.Data.GetExcelSheet<DalamudItem>().GetRow((int)itemId).Name;
@ -35,11 +45,44 @@ namespace Dalamud.Game.Chat.SeStringHandling
return new SeString(payloads);
}
/// <summary>
/// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log.
/// </summary>
/// <param name="item">The Lumina Item to link.</param>
/// <param name="isHQ">Whether to link the high-quality variant of the item.</param>
/// <param name="displayNameOverride">An optional name override to display, instead of the actual item name.</param>
/// <returns>An SeString containing all the payloads necessary to display an item link in the chat log.</returns>
public static SeString CreateItemLink(DalamudItem item, bool isHQ, string displayNameOverride = null)
{
return CreateItemLink((uint)item.RowId, isHQ, displayNameOverride ?? item.Name);
}
public static SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY)
{
var mapPayload = new MapLinkPayload(territoryId, mapId, rawX, rawY);
var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}";
var payloads = new List<Payload>(new Payload[]
{
mapPayload,
// arrow goes here
new TextPayload(nameString),
RawPayload.LinkTerminator
});
payloads.InsertRange(1, TextArrowPayloads());
return new SeString(payloads);
}
/// <summary>
/// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log.
/// </summary>
/// <param name="territoryId">The id of the TerritoryType for this map link.</param>
/// <param name="mapId">The id of the Map for this map link.</param>
/// <param name="xCoord">The human-readable x-coordinate for this link.</param>
/// <param name="yCoord">The human-readable y-coordinate for this link.</param>
/// <param name="fudgeFactor">An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases.</param>
/// <returns>An SeString containing all of the payloads necessary to display a map link in the chat log.</returns>
public static SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f)
{
var mapPayload = new MapLinkPayload(territoryId, mapId, xCoord, yCoord, fudgeFactor);
@ -57,6 +100,14 @@ namespace Dalamud.Game.Chat.SeStringHandling
return new SeString(payloads);
}
/// <summary>
/// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log, matching a specified zone name.
/// </summary>
/// <param name="placeName">The name of the location for this link. This should be exactly the name as seen in a displayed map link in-game for the same zone.</param>
/// <param name="xCoord">The human-readable x-coordinate for this link.</param>
/// <param name="yCoord">The human-readable y-coordinate for this link.</param>
/// <param name="fudgeFactor">An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases.</param>
/// <returns>An SeString containing all of the payloads necessary to display a map link in the chat log.</returns>
public static SeString CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f)
{
var mapSheet = SeString.Dalamud.Data.GetExcelSheet<Map>();
@ -78,6 +129,11 @@ namespace Dalamud.Game.Chat.SeStringHandling
return null;
}
/// <summary>
/// Creates a list of Payloads necessary to display the arrow link marker icon in chat
/// with the appropriate glow and coloring.
/// </summary>
/// <returns>A list of all the payloads required to insert the link marker.</returns>
public static List<Payload> TextArrowPayloads()
{
return new List<Payload>(new Payload[]

View file

@ -6,7 +6,7 @@ namespace Dalamud.Game.Chat
/// <summary>
/// The FFXIV chat types as seen in the LogKind ex table.
/// </summary>
public enum XivChatType : ushort
public enum XivChatType : ushort // FIXME: this is a single byte
{
None = 0,
Debug = 1,