mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-29 20:03:41 +01:00
Revert "refactor(Dalamud): switch to file-scoped namespaces"
This reverts commit b5f34c3199.
This commit is contained in:
parent
d473826247
commit
1561fbac00
325 changed files with 45549 additions and 45209 deletions
|
|
@ -1,39 +1,40 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Dalamud.Game.Text.Sanitizer;
|
||||
|
||||
/// <summary>
|
||||
/// Sanitize strings to remove soft hyphens and other special characters.
|
||||
/// </summary>
|
||||
public interface ISanitizer
|
||||
namespace Dalamud.Game.Text.Sanitizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a sanitized string using current clientLanguage.
|
||||
/// Sanitize strings to remove soft hyphens and other special characters.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedString">An unsanitized string to sanitize.</param>
|
||||
/// <returns>A sanitized string.</returns>
|
||||
string Sanitize(string unsanitizedString);
|
||||
public interface ISanitizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a sanitized string using current clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedString">An unsanitized string to sanitize.</param>
|
||||
/// <returns>A sanitized string.</returns>
|
||||
string Sanitize(string unsanitizedString);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a sanitized string using request clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedString">An unsanitized string to sanitize.</param>
|
||||
/// <param name="clientLanguage">Target language for sanitized strings.</param>
|
||||
/// <returns>A sanitized string.</returns>
|
||||
string Sanitize(string unsanitizedString, ClientLanguage clientLanguage);
|
||||
/// <summary>
|
||||
/// Creates a sanitized string using request clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedString">An unsanitized string to sanitize.</param>
|
||||
/// <param name="clientLanguage">Target language for sanitized strings.</param>
|
||||
/// <returns>A sanitized string.</returns>
|
||||
string Sanitize(string unsanitizedString, ClientLanguage clientLanguage);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a list of sanitized strings using current clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedStrings">List of unsanitized string to sanitize.</param>
|
||||
/// <returns>A list of sanitized strings.</returns>
|
||||
IEnumerable<string> Sanitize(IEnumerable<string> unsanitizedStrings);
|
||||
/// <summary>
|
||||
/// Creates a list of sanitized strings using current clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedStrings">List of unsanitized string to sanitize.</param>
|
||||
/// <returns>A list of sanitized strings.</returns>
|
||||
IEnumerable<string> Sanitize(IEnumerable<string> unsanitizedStrings);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a list of sanitized strings using requested clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedStrings">List of unsanitized string to sanitize.</param>
|
||||
/// <param name="clientLanguage">Target language for sanitized strings.</param>
|
||||
/// <returns>A list of sanitized strings.</returns>
|
||||
IEnumerable<string> Sanitize(IEnumerable<string> unsanitizedStrings, ClientLanguage clientLanguage);
|
||||
/// <summary>
|
||||
/// Creates a list of sanitized strings using requested clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedStrings">List of unsanitized string to sanitize.</param>
|
||||
/// <param name="clientLanguage">Target language for sanitized strings.</param>
|
||||
/// <returns>A list of sanitized strings.</returns>
|
||||
IEnumerable<string> Sanitize(IEnumerable<string> unsanitizedStrings, ClientLanguage clientLanguage);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,109 +2,110 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Dalamud.Game.Text.Sanitizer;
|
||||
|
||||
/// <summary>
|
||||
/// Sanitize strings to remove soft hyphens and other special characters.
|
||||
/// </summary>
|
||||
public class Sanitizer : ISanitizer
|
||||
namespace Dalamud.Game.Text.Sanitizer
|
||||
{
|
||||
private static readonly Dictionary<string, string> DESanitizationDict = new()
|
||||
{
|
||||
{ "\u0020\u2020", string.Empty }, // dagger
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> FRSanitizationDict = new()
|
||||
{
|
||||
{ "\u0153", "\u006F\u0065" }, // ligature oe
|
||||
};
|
||||
|
||||
private readonly ClientLanguage defaultClientLanguage;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Sanitizer"/> class.
|
||||
/// Sanitize strings to remove soft hyphens and other special characters.
|
||||
/// </summary>
|
||||
/// <param name="defaultClientLanguage">Default clientLanguage for sanitizing strings.</param>
|
||||
public Sanitizer(ClientLanguage defaultClientLanguage)
|
||||
public class Sanitizer : ISanitizer
|
||||
{
|
||||
this.defaultClientLanguage = defaultClientLanguage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a sanitized string using current clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedString">An unsanitized string to sanitize.</param>
|
||||
/// <returns>A sanitized string.</returns>
|
||||
public string Sanitize(string unsanitizedString)
|
||||
{
|
||||
return SanitizeByLanguage(unsanitizedString, this.defaultClientLanguage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a sanitized string using request clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedString">An unsanitized string to sanitize.</param>
|
||||
/// <param name="clientLanguage">Target language for sanitized strings.</param>
|
||||
/// <returns>A sanitized string.</returns>
|
||||
public string Sanitize(string unsanitizedString, ClientLanguage clientLanguage)
|
||||
{
|
||||
return SanitizeByLanguage(unsanitizedString, clientLanguage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a list of sanitized strings using current clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedStrings">List of unsanitized string to sanitize.</param>
|
||||
/// <returns>A list of sanitized strings.</returns>
|
||||
public IEnumerable<string> Sanitize(IEnumerable<string> unsanitizedStrings)
|
||||
{
|
||||
return SanitizeByLanguage(unsanitizedStrings, this.defaultClientLanguage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a list of sanitized strings using requested clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedStrings">List of unsanitized string to sanitize.</param>
|
||||
/// <param name="clientLanguage">Target language for sanitized strings.</param>
|
||||
/// <returns>A list of sanitized strings.</returns>
|
||||
public IEnumerable<string> Sanitize(IEnumerable<string> unsanitizedStrings, ClientLanguage clientLanguage)
|
||||
{
|
||||
return SanitizeByLanguage(unsanitizedStrings, clientLanguage);
|
||||
}
|
||||
|
||||
private static string SanitizeByLanguage(string unsanitizedString, ClientLanguage clientLanguage)
|
||||
{
|
||||
var sanitizedString = FilterUnprintableCharacters(unsanitizedString);
|
||||
return clientLanguage switch
|
||||
private static readonly Dictionary<string, string> DESanitizationDict = new()
|
||||
{
|
||||
ClientLanguage.Japanese or ClientLanguage.English => sanitizedString,
|
||||
ClientLanguage.German => FilterByDict(sanitizedString, DESanitizationDict),
|
||||
ClientLanguage.French => FilterByDict(sanitizedString, FRSanitizationDict),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null),
|
||||
{ "\u0020\u2020", string.Empty }, // dagger
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<string> SanitizeByLanguage(IEnumerable<string> unsanitizedStrings, ClientLanguage clientLanguage)
|
||||
{
|
||||
return clientLanguage switch
|
||||
private static readonly Dictionary<string, string> FRSanitizationDict = new()
|
||||
{
|
||||
ClientLanguage.Japanese => unsanitizedStrings.Select(FilterUnprintableCharacters),
|
||||
ClientLanguage.English => unsanitizedStrings.Select(FilterUnprintableCharacters),
|
||||
ClientLanguage.German => unsanitizedStrings.Select(original => FilterByDict(FilterUnprintableCharacters(original), DESanitizationDict)),
|
||||
ClientLanguage.French => unsanitizedStrings.Select(original => FilterByDict(FilterUnprintableCharacters(original), FRSanitizationDict)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null),
|
||||
{ "\u0153", "\u006F\u0065" }, // ligature oe
|
||||
};
|
||||
}
|
||||
|
||||
private static string FilterUnprintableCharacters(string str)
|
||||
{
|
||||
return new string(str?.Where(ch => ch >= 0x20).ToArray());
|
||||
}
|
||||
private readonly ClientLanguage defaultClientLanguage;
|
||||
|
||||
private static string FilterByDict(string str, Dictionary<string, string> dict)
|
||||
{
|
||||
return dict.Aggregate(
|
||||
str, (current, kvp) =>
|
||||
current.Replace(kvp.Key, kvp.Value));
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Sanitizer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="defaultClientLanguage">Default clientLanguage for sanitizing strings.</param>
|
||||
public Sanitizer(ClientLanguage defaultClientLanguage)
|
||||
{
|
||||
this.defaultClientLanguage = defaultClientLanguage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a sanitized string using current clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedString">An unsanitized string to sanitize.</param>
|
||||
/// <returns>A sanitized string.</returns>
|
||||
public string Sanitize(string unsanitizedString)
|
||||
{
|
||||
return SanitizeByLanguage(unsanitizedString, this.defaultClientLanguage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a sanitized string using request clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedString">An unsanitized string to sanitize.</param>
|
||||
/// <param name="clientLanguage">Target language for sanitized strings.</param>
|
||||
/// <returns>A sanitized string.</returns>
|
||||
public string Sanitize(string unsanitizedString, ClientLanguage clientLanguage)
|
||||
{
|
||||
return SanitizeByLanguage(unsanitizedString, clientLanguage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a list of sanitized strings using current clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedStrings">List of unsanitized string to sanitize.</param>
|
||||
/// <returns>A list of sanitized strings.</returns>
|
||||
public IEnumerable<string> Sanitize(IEnumerable<string> unsanitizedStrings)
|
||||
{
|
||||
return SanitizeByLanguage(unsanitizedStrings, this.defaultClientLanguage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a list of sanitized strings using requested clientLanguage.
|
||||
/// </summary>
|
||||
/// <param name="unsanitizedStrings">List of unsanitized string to sanitize.</param>
|
||||
/// <param name="clientLanguage">Target language for sanitized strings.</param>
|
||||
/// <returns>A list of sanitized strings.</returns>
|
||||
public IEnumerable<string> Sanitize(IEnumerable<string> unsanitizedStrings, ClientLanguage clientLanguage)
|
||||
{
|
||||
return SanitizeByLanguage(unsanitizedStrings, clientLanguage);
|
||||
}
|
||||
|
||||
private static string SanitizeByLanguage(string unsanitizedString, ClientLanguage clientLanguage)
|
||||
{
|
||||
var sanitizedString = FilterUnprintableCharacters(unsanitizedString);
|
||||
return clientLanguage switch
|
||||
{
|
||||
ClientLanguage.Japanese or ClientLanguage.English => sanitizedString,
|
||||
ClientLanguage.German => FilterByDict(sanitizedString, DESanitizationDict),
|
||||
ClientLanguage.French => FilterByDict(sanitizedString, FRSanitizationDict),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null),
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<string> SanitizeByLanguage(IEnumerable<string> unsanitizedStrings, ClientLanguage clientLanguage)
|
||||
{
|
||||
return clientLanguage switch
|
||||
{
|
||||
ClientLanguage.Japanese => unsanitizedStrings.Select(FilterUnprintableCharacters),
|
||||
ClientLanguage.English => unsanitizedStrings.Select(FilterUnprintableCharacters),
|
||||
ClientLanguage.German => unsanitizedStrings.Select(original => FilterByDict(FilterUnprintableCharacters(original), DESanitizationDict)),
|
||||
ClientLanguage.French => unsanitizedStrings.Select(original => FilterByDict(FilterUnprintableCharacters(original), FRSanitizationDict)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null),
|
||||
};
|
||||
}
|
||||
|
||||
private static string FilterUnprintableCharacters(string str)
|
||||
{
|
||||
return new string(str?.Where(ch => ch >= 0x20).ToArray());
|
||||
}
|
||||
|
||||
private static string FilterByDict(string str, Dictionary<string, string> dict)
|
||||
{
|
||||
return dict.Aggregate(
|
||||
str, (current, kvp) =>
|
||||
current.Replace(kvp.Key, kvp.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,437 +1,438 @@
|
|||
namespace Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents special icons that can appear in chat naturally or as IconPayloads.
|
||||
/// </summary>
|
||||
public enum BitmapFontIcon : uint
|
||||
namespace Dalamud.Game.Text.SeStringHandling
|
||||
{
|
||||
/// <summary>
|
||||
/// No icon.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad up icon.
|
||||
/// </summary>
|
||||
ControllerDPadUp = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad down icon.
|
||||
/// </summary>
|
||||
ControllerDPadDown = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad left icon.
|
||||
/// </summary>
|
||||
ControllerDPadLeft = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad right icon.
|
||||
/// </summary>
|
||||
ControllerDPadRight = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad up/down icon.
|
||||
/// </summary>
|
||||
ControllerDPadUpDown = 5,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad left/right icon.
|
||||
/// </summary>
|
||||
ControllerDPadLeftRight = 6,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad all directions icon.
|
||||
/// </summary>
|
||||
ControllerDPadAll = 7,
|
||||
|
||||
/// <summary>
|
||||
/// The controller button 0 icon (Xbox: B, PlayStation: Circle).
|
||||
/// </summary>
|
||||
ControllerButton0 = 8,
|
||||
|
||||
/// <summary>
|
||||
/// The controller button 1 icon (XBox: A, PlayStation: Cross).
|
||||
/// </summary>
|
||||
ControllerButton1 = 9,
|
||||
|
||||
/// <summary>
|
||||
/// The controller button 2 icon (XBox: X, PlayStation: Square).
|
||||
/// </summary>
|
||||
ControllerButton2 = 10,
|
||||
|
||||
/// <summary>
|
||||
/// The controller button 3 icon (BBox: Y, PlayStation: Triangle).
|
||||
/// </summary>
|
||||
ControllerButton3 = 11,
|
||||
|
||||
/// <summary>
|
||||
/// The controller left shoulder button icon.
|
||||
/// </summary>
|
||||
ControllerShoulderLeft = 12,
|
||||
|
||||
/// <summary>
|
||||
/// The controller right shoulder button icon.
|
||||
/// </summary>
|
||||
ControllerShoulderRight = 13,
|
||||
|
||||
/// <summary>
|
||||
/// The controller left trigger button icon.
|
||||
/// </summary>
|
||||
ControllerTriggerLeft = 14,
|
||||
|
||||
/// <summary>
|
||||
/// The controller right trigger button icon.
|
||||
/// </summary>
|
||||
ControllerTriggerRight = 15,
|
||||
|
||||
/// <summary>
|
||||
/// The controller left analog stick in icon.
|
||||
/// </summary>
|
||||
ControllerAnalogLeftStickIn = 16,
|
||||
|
||||
/// <summary>
|
||||
/// The controller right analog stick in icon.
|
||||
/// </summary>
|
||||
ControllerAnalogRightStickIn = 17,
|
||||
|
||||
/// <summary>
|
||||
/// The controller start button icon.
|
||||
/// </summary>
|
||||
ControllerStart = 18,
|
||||
|
||||
/// <summary>
|
||||
/// The controller back button icon.
|
||||
/// </summary>
|
||||
ControllerBack = 19,
|
||||
|
||||
/// <summary>
|
||||
/// The controller left analog stick icon.
|
||||
/// </summary>
|
||||
ControllerAnalogLeftStick = 20,
|
||||
|
||||
/// <summary>
|
||||
/// The controller left analog stick up/down icon.
|
||||
/// </summary>
|
||||
ControllerAnalogLeftStickUpDown = 21,
|
||||
|
||||
/// <summary>
|
||||
/// The controller left analog stick left/right icon.
|
||||
/// </summary>
|
||||
ControllerAnalogLeftStickLeftRight = 22,
|
||||
|
||||
/// <summary>
|
||||
/// The controller right analog stick icon.
|
||||
/// </summary>
|
||||
ControllerAnalogRightStick = 23,
|
||||
|
||||
/// <summary>
|
||||
/// The controller right analog stick up/down icon.
|
||||
/// </summary>
|
||||
ControllerAnalogRightStickUpDown = 24,
|
||||
|
||||
/// <summary>
|
||||
/// The controller right analog stick left/right icon.
|
||||
/// </summary>
|
||||
ControllerAnalogRightStickLeftRight = 25,
|
||||
|
||||
/// <summary>
|
||||
/// The La Noscea region icon.
|
||||
/// </summary>
|
||||
LaNoscea = 51,
|
||||
|
||||
/// <summary>
|
||||
/// The Black Shroud region icon.
|
||||
/// </summary>
|
||||
BlackShroud = 52,
|
||||
|
||||
/// <summary>
|
||||
/// The Thanalan region icon.
|
||||
/// </summary>
|
||||
Thanalan = 53,
|
||||
|
||||
/// <summary>
|
||||
/// The auto translate begin icon.
|
||||
/// </summary>
|
||||
AutoTranslateBegin = 54,
|
||||
|
||||
/// <summary>
|
||||
/// The auto translate end icon.
|
||||
/// </summary>
|
||||
AutoTranslateEnd = 55,
|
||||
|
||||
/// <summary>
|
||||
/// The fire element icon.
|
||||
/// </summary>
|
||||
ElementFire = 56,
|
||||
|
||||
/// <summary>
|
||||
/// The ice element icon.
|
||||
/// </summary>
|
||||
ElementIce = 57,
|
||||
|
||||
/// <summary>
|
||||
/// The wind element icon.
|
||||
/// </summary>
|
||||
ElementWind = 58,
|
||||
|
||||
/// <summary>
|
||||
/// The earth element icon.
|
||||
/// </summary>
|
||||
ElementEarth = 59,
|
||||
|
||||
/// <summary>
|
||||
/// The lightning element icon.
|
||||
/// </summary>
|
||||
ElementLightning = 60,
|
||||
|
||||
/// <summary>
|
||||
/// The water element icon.
|
||||
/// </summary>
|
||||
ElementWater = 61,
|
||||
|
||||
/// <summary>
|
||||
/// The level sync icon.
|
||||
/// </summary>
|
||||
LevelSync = 62,
|
||||
|
||||
/// <summary>
|
||||
/// The warning icon.
|
||||
/// </summary>
|
||||
Warning = 63,
|
||||
|
||||
/// <summary>
|
||||
/// The Ishgard region icon.
|
||||
/// </summary>
|
||||
Ishgard = 64,
|
||||
|
||||
/// <summary>
|
||||
/// The Aetheryte icon.
|
||||
/// </summary>
|
||||
Aetheryte = 65,
|
||||
|
||||
/// <summary>
|
||||
/// The Aethernet icon.
|
||||
/// </summary>
|
||||
Aethernet = 66,
|
||||
|
||||
/// <summary>
|
||||
/// The gold star icon.
|
||||
/// </summary>
|
||||
GoldStar = 67,
|
||||
|
||||
/// <summary>
|
||||
/// The silver star icon.
|
||||
/// </summary>
|
||||
SilverStar = 68,
|
||||
|
||||
/// <summary>
|
||||
/// The green dot icon.
|
||||
/// </summary>
|
||||
GreenDot = 70,
|
||||
|
||||
/// <summary>
|
||||
/// The unsheathed sword icon.
|
||||
/// </summary>
|
||||
SwordUnsheathed = 71,
|
||||
|
||||
/// <summary>
|
||||
/// The sheathed sword icon.
|
||||
/// </summary>
|
||||
SwordSheathed = 72,
|
||||
|
||||
/// <summary>
|
||||
/// The dice icon.
|
||||
/// </summary>
|
||||
Dice = 73,
|
||||
|
||||
/// <summary>
|
||||
/// The flyable zone icon.
|
||||
/// </summary>
|
||||
FlyZone = 74,
|
||||
|
||||
/// <summary>
|
||||
/// The no-flying zone icon.
|
||||
/// </summary>
|
||||
FlyZoneLocked = 75,
|
||||
|
||||
/// <summary>
|
||||
/// The no-circle/prohibited icon.
|
||||
/// </summary>
|
||||
NoCircle = 76,
|
||||
|
||||
/// <summary>
|
||||
/// The sprout icon.
|
||||
/// </summary>
|
||||
NewAdventurer = 77,
|
||||
|
||||
/// <summary>
|
||||
/// The mentor icon.
|
||||
/// </summary>
|
||||
Mentor = 78,
|
||||
|
||||
/// <summary>
|
||||
/// The PvE mentor icon.
|
||||
/// </summary>
|
||||
MentorPvE = 79,
|
||||
|
||||
/// <summary>
|
||||
/// The crafting mentor icon.
|
||||
/// </summary>
|
||||
MentorCrafting = 80,
|
||||
|
||||
/// <summary>
|
||||
/// The PvP mentor icon.
|
||||
/// </summary>
|
||||
MentorPvP = 81,
|
||||
|
||||
/// <summary>
|
||||
/// The tank role icon.
|
||||
/// </summary>
|
||||
Tank = 82,
|
||||
|
||||
/// <summary>
|
||||
/// The healer role icon.
|
||||
/// </summary>
|
||||
Healer = 83,
|
||||
|
||||
/// <summary>
|
||||
/// The DPS role icon.
|
||||
/// </summary>
|
||||
DPS = 84,
|
||||
|
||||
/// <summary>
|
||||
/// The crafter role icon.
|
||||
/// </summary>
|
||||
Crafter = 85,
|
||||
|
||||
/// <summary>
|
||||
/// The gatherer role icon.
|
||||
/// </summary>
|
||||
Gatherer = 86,
|
||||
|
||||
/// <summary>
|
||||
/// The "any" role icon.
|
||||
/// </summary>
|
||||
AnyClass = 87,
|
||||
|
||||
/// <summary>
|
||||
/// The cross-world icon.
|
||||
/// </summary>
|
||||
CrossWorld = 88,
|
||||
|
||||
/// <summary>
|
||||
/// The slay type Fate icon.
|
||||
/// </summary>
|
||||
FateSlay = 89,
|
||||
|
||||
/// <summary>
|
||||
/// The boss type Fate icon.
|
||||
/// </summary>
|
||||
FateBoss = 90,
|
||||
|
||||
/// <summary>
|
||||
/// The gather type Fate icon.
|
||||
/// </summary>
|
||||
FateGather = 91,
|
||||
|
||||
/// <summary>
|
||||
/// The defend type Fate icon.
|
||||
/// </summary>
|
||||
FateDefend = 92,
|
||||
|
||||
/// <summary>
|
||||
/// The escort type Fate icon.
|
||||
/// </summary>
|
||||
FateEscort = 93,
|
||||
|
||||
/// <summary>
|
||||
/// The special type 1 Fate icon.
|
||||
/// </summary>
|
||||
FateSpecial1 = 94,
|
||||
|
||||
/// <summary>
|
||||
/// The returner icon.
|
||||
/// </summary>
|
||||
Returner = 95,
|
||||
|
||||
/// <summary>
|
||||
/// The Far-East region icon.
|
||||
/// </summary>
|
||||
FarEast = 96,
|
||||
|
||||
/// <summary>
|
||||
/// The Gyr Albania region icon.
|
||||
/// </summary>
|
||||
GyrAbania = 97,
|
||||
|
||||
/// <summary>
|
||||
/// The special type 2 Fate icon.
|
||||
/// </summary>
|
||||
FateSpecial2 = 98,
|
||||
|
||||
/// <summary>
|
||||
/// The priority world icon.
|
||||
/// </summary>
|
||||
PriorityWorld = 99,
|
||||
|
||||
/// <summary>
|
||||
/// The elemental level icon.
|
||||
/// </summary>
|
||||
ElementalLevel = 100,
|
||||
|
||||
/// <summary>
|
||||
/// The exclamation rectangle icon.
|
||||
/// </summary>
|
||||
ExclamationRectangle = 101,
|
||||
|
||||
/// <summary>
|
||||
/// The notorious monster icon.
|
||||
/// </summary>
|
||||
NotoriousMonster = 102,
|
||||
|
||||
/// <summary>
|
||||
/// The recording icon.
|
||||
/// </summary>
|
||||
Recording = 103,
|
||||
|
||||
/// <summary>
|
||||
/// The alarm icon.
|
||||
/// </summary>
|
||||
Alarm = 104,
|
||||
|
||||
/// <summary>
|
||||
/// The arrow up icon.
|
||||
/// </summary>
|
||||
ArrowUp = 105,
|
||||
|
||||
/// <summary>
|
||||
/// The arrow down icon.
|
||||
/// </summary>
|
||||
ArrowDown = 106,
|
||||
|
||||
/// <summary>
|
||||
/// The Crystarium region icon.
|
||||
/// </summary>
|
||||
Crystarium = 107,
|
||||
|
||||
/// <summary>
|
||||
/// The mentor problem icon.
|
||||
/// </summary>
|
||||
MentorProblem = 108,
|
||||
|
||||
/// <summary>
|
||||
/// The unknown gold type Fate icon.
|
||||
/// </summary>
|
||||
FateUnknownGold = 109,
|
||||
|
||||
/// <summary>
|
||||
/// The orange diamond icon.
|
||||
/// </summary>
|
||||
OrangeDiamond = 110,
|
||||
|
||||
/// <summary>
|
||||
/// The crafting type Fate icon.
|
||||
/// </summary>
|
||||
FateCrafting = 111,
|
||||
/// This class represents special icons that can appear in chat naturally or as IconPayloads.
|
||||
/// </summary>
|
||||
public enum BitmapFontIcon : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// No icon.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad up icon.
|
||||
/// </summary>
|
||||
ControllerDPadUp = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad down icon.
|
||||
/// </summary>
|
||||
ControllerDPadDown = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad left icon.
|
||||
/// </summary>
|
||||
ControllerDPadLeft = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad right icon.
|
||||
/// </summary>
|
||||
ControllerDPadRight = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad up/down icon.
|
||||
/// </summary>
|
||||
ControllerDPadUpDown = 5,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad left/right icon.
|
||||
/// </summary>
|
||||
ControllerDPadLeftRight = 6,
|
||||
|
||||
/// <summary>
|
||||
/// The controller D-pad all directions icon.
|
||||
/// </summary>
|
||||
ControllerDPadAll = 7,
|
||||
|
||||
/// <summary>
|
||||
/// The controller button 0 icon (Xbox: B, PlayStation: Circle).
|
||||
/// </summary>
|
||||
ControllerButton0 = 8,
|
||||
|
||||
/// <summary>
|
||||
/// The controller button 1 icon (XBox: A, PlayStation: Cross).
|
||||
/// </summary>
|
||||
ControllerButton1 = 9,
|
||||
|
||||
/// <summary>
|
||||
/// The controller button 2 icon (XBox: X, PlayStation: Square).
|
||||
/// </summary>
|
||||
ControllerButton2 = 10,
|
||||
|
||||
/// <summary>
|
||||
/// The controller button 3 icon (BBox: Y, PlayStation: Triangle).
|
||||
/// </summary>
|
||||
ControllerButton3 = 11,
|
||||
|
||||
/// <summary>
|
||||
/// The controller left shoulder button icon.
|
||||
/// </summary>
|
||||
ControllerShoulderLeft = 12,
|
||||
|
||||
/// <summary>
|
||||
/// The controller right shoulder button icon.
|
||||
/// </summary>
|
||||
ControllerShoulderRight = 13,
|
||||
|
||||
/// <summary>
|
||||
/// The controller left trigger button icon.
|
||||
/// </summary>
|
||||
ControllerTriggerLeft = 14,
|
||||
|
||||
/// <summary>
|
||||
/// The controller right trigger button icon.
|
||||
/// </summary>
|
||||
ControllerTriggerRight = 15,
|
||||
|
||||
/// <summary>
|
||||
/// The controller left analog stick in icon.
|
||||
/// </summary>
|
||||
ControllerAnalogLeftStickIn = 16,
|
||||
|
||||
/// <summary>
|
||||
/// The controller right analog stick in icon.
|
||||
/// </summary>
|
||||
ControllerAnalogRightStickIn = 17,
|
||||
|
||||
/// <summary>
|
||||
/// The controller start button icon.
|
||||
/// </summary>
|
||||
ControllerStart = 18,
|
||||
|
||||
/// <summary>
|
||||
/// The controller back button icon.
|
||||
/// </summary>
|
||||
ControllerBack = 19,
|
||||
|
||||
/// <summary>
|
||||
/// The controller left analog stick icon.
|
||||
/// </summary>
|
||||
ControllerAnalogLeftStick = 20,
|
||||
|
||||
/// <summary>
|
||||
/// The controller left analog stick up/down icon.
|
||||
/// </summary>
|
||||
ControllerAnalogLeftStickUpDown = 21,
|
||||
|
||||
/// <summary>
|
||||
/// The controller left analog stick left/right icon.
|
||||
/// </summary>
|
||||
ControllerAnalogLeftStickLeftRight = 22,
|
||||
|
||||
/// <summary>
|
||||
/// The controller right analog stick icon.
|
||||
/// </summary>
|
||||
ControllerAnalogRightStick = 23,
|
||||
|
||||
/// <summary>
|
||||
/// The controller right analog stick up/down icon.
|
||||
/// </summary>
|
||||
ControllerAnalogRightStickUpDown = 24,
|
||||
|
||||
/// <summary>
|
||||
/// The controller right analog stick left/right icon.
|
||||
/// </summary>
|
||||
ControllerAnalogRightStickLeftRight = 25,
|
||||
|
||||
/// <summary>
|
||||
/// The La Noscea region icon.
|
||||
/// </summary>
|
||||
LaNoscea = 51,
|
||||
|
||||
/// <summary>
|
||||
/// The Black Shroud region icon.
|
||||
/// </summary>
|
||||
BlackShroud = 52,
|
||||
|
||||
/// <summary>
|
||||
/// The Thanalan region icon.
|
||||
/// </summary>
|
||||
Thanalan = 53,
|
||||
|
||||
/// <summary>
|
||||
/// The auto translate begin icon.
|
||||
/// </summary>
|
||||
AutoTranslateBegin = 54,
|
||||
|
||||
/// <summary>
|
||||
/// The auto translate end icon.
|
||||
/// </summary>
|
||||
AutoTranslateEnd = 55,
|
||||
|
||||
/// <summary>
|
||||
/// The fire element icon.
|
||||
/// </summary>
|
||||
ElementFire = 56,
|
||||
|
||||
/// <summary>
|
||||
/// The ice element icon.
|
||||
/// </summary>
|
||||
ElementIce = 57,
|
||||
|
||||
/// <summary>
|
||||
/// The wind element icon.
|
||||
/// </summary>
|
||||
ElementWind = 58,
|
||||
|
||||
/// <summary>
|
||||
/// The earth element icon.
|
||||
/// </summary>
|
||||
ElementEarth = 59,
|
||||
|
||||
/// <summary>
|
||||
/// The lightning element icon.
|
||||
/// </summary>
|
||||
ElementLightning = 60,
|
||||
|
||||
/// <summary>
|
||||
/// The water element icon.
|
||||
/// </summary>
|
||||
ElementWater = 61,
|
||||
|
||||
/// <summary>
|
||||
/// The level sync icon.
|
||||
/// </summary>
|
||||
LevelSync = 62,
|
||||
|
||||
/// <summary>
|
||||
/// The warning icon.
|
||||
/// </summary>
|
||||
Warning = 63,
|
||||
|
||||
/// <summary>
|
||||
/// The Ishgard region icon.
|
||||
/// </summary>
|
||||
Ishgard = 64,
|
||||
|
||||
/// <summary>
|
||||
/// The Aetheryte icon.
|
||||
/// </summary>
|
||||
Aetheryte = 65,
|
||||
|
||||
/// <summary>
|
||||
/// The Aethernet icon.
|
||||
/// </summary>
|
||||
Aethernet = 66,
|
||||
|
||||
/// <summary>
|
||||
/// The gold star icon.
|
||||
/// </summary>
|
||||
GoldStar = 67,
|
||||
|
||||
/// <summary>
|
||||
/// The silver star icon.
|
||||
/// </summary>
|
||||
SilverStar = 68,
|
||||
|
||||
/// <summary>
|
||||
/// The green dot icon.
|
||||
/// </summary>
|
||||
GreenDot = 70,
|
||||
|
||||
/// <summary>
|
||||
/// The unsheathed sword icon.
|
||||
/// </summary>
|
||||
SwordUnsheathed = 71,
|
||||
|
||||
/// <summary>
|
||||
/// The sheathed sword icon.
|
||||
/// </summary>
|
||||
SwordSheathed = 72,
|
||||
|
||||
/// <summary>
|
||||
/// The dice icon.
|
||||
/// </summary>
|
||||
Dice = 73,
|
||||
|
||||
/// <summary>
|
||||
/// The flyable zone icon.
|
||||
/// </summary>
|
||||
FlyZone = 74,
|
||||
|
||||
/// <summary>
|
||||
/// The no-flying zone icon.
|
||||
/// </summary>
|
||||
FlyZoneLocked = 75,
|
||||
|
||||
/// <summary>
|
||||
/// The no-circle/prohibited icon.
|
||||
/// </summary>
|
||||
NoCircle = 76,
|
||||
|
||||
/// <summary>
|
||||
/// The sprout icon.
|
||||
/// </summary>
|
||||
NewAdventurer = 77,
|
||||
|
||||
/// <summary>
|
||||
/// The mentor icon.
|
||||
/// </summary>
|
||||
Mentor = 78,
|
||||
|
||||
/// <summary>
|
||||
/// The PvE mentor icon.
|
||||
/// </summary>
|
||||
MentorPvE = 79,
|
||||
|
||||
/// <summary>
|
||||
/// The crafting mentor icon.
|
||||
/// </summary>
|
||||
MentorCrafting = 80,
|
||||
|
||||
/// <summary>
|
||||
/// The PvP mentor icon.
|
||||
/// </summary>
|
||||
MentorPvP = 81,
|
||||
|
||||
/// <summary>
|
||||
/// The tank role icon.
|
||||
/// </summary>
|
||||
Tank = 82,
|
||||
|
||||
/// <summary>
|
||||
/// The healer role icon.
|
||||
/// </summary>
|
||||
Healer = 83,
|
||||
|
||||
/// <summary>
|
||||
/// The DPS role icon.
|
||||
/// </summary>
|
||||
DPS = 84,
|
||||
|
||||
/// <summary>
|
||||
/// The crafter role icon.
|
||||
/// </summary>
|
||||
Crafter = 85,
|
||||
|
||||
/// <summary>
|
||||
/// The gatherer role icon.
|
||||
/// </summary>
|
||||
Gatherer = 86,
|
||||
|
||||
/// <summary>
|
||||
/// The "any" role icon.
|
||||
/// </summary>
|
||||
AnyClass = 87,
|
||||
|
||||
/// <summary>
|
||||
/// The cross-world icon.
|
||||
/// </summary>
|
||||
CrossWorld = 88,
|
||||
|
||||
/// <summary>
|
||||
/// The slay type Fate icon.
|
||||
/// </summary>
|
||||
FateSlay = 89,
|
||||
|
||||
/// <summary>
|
||||
/// The boss type Fate icon.
|
||||
/// </summary>
|
||||
FateBoss = 90,
|
||||
|
||||
/// <summary>
|
||||
/// The gather type Fate icon.
|
||||
/// </summary>
|
||||
FateGather = 91,
|
||||
|
||||
/// <summary>
|
||||
/// The defend type Fate icon.
|
||||
/// </summary>
|
||||
FateDefend = 92,
|
||||
|
||||
/// <summary>
|
||||
/// The escort type Fate icon.
|
||||
/// </summary>
|
||||
FateEscort = 93,
|
||||
|
||||
/// <summary>
|
||||
/// The special type 1 Fate icon.
|
||||
/// </summary>
|
||||
FateSpecial1 = 94,
|
||||
|
||||
/// <summary>
|
||||
/// The returner icon.
|
||||
/// </summary>
|
||||
Returner = 95,
|
||||
|
||||
/// <summary>
|
||||
/// The Far-East region icon.
|
||||
/// </summary>
|
||||
FarEast = 96,
|
||||
|
||||
/// <summary>
|
||||
/// The Gyr Albania region icon.
|
||||
/// </summary>
|
||||
GyrAbania = 97,
|
||||
|
||||
/// <summary>
|
||||
/// The special type 2 Fate icon.
|
||||
/// </summary>
|
||||
FateSpecial2 = 98,
|
||||
|
||||
/// <summary>
|
||||
/// The priority world icon.
|
||||
/// </summary>
|
||||
PriorityWorld = 99,
|
||||
|
||||
/// <summary>
|
||||
/// The elemental level icon.
|
||||
/// </summary>
|
||||
ElementalLevel = 100,
|
||||
|
||||
/// <summary>
|
||||
/// The exclamation rectangle icon.
|
||||
/// </summary>
|
||||
ExclamationRectangle = 101,
|
||||
|
||||
/// <summary>
|
||||
/// The notorious monster icon.
|
||||
/// </summary>
|
||||
NotoriousMonster = 102,
|
||||
|
||||
/// <summary>
|
||||
/// The recording icon.
|
||||
/// </summary>
|
||||
Recording = 103,
|
||||
|
||||
/// <summary>
|
||||
/// The alarm icon.
|
||||
/// </summary>
|
||||
Alarm = 104,
|
||||
|
||||
/// <summary>
|
||||
/// The arrow up icon.
|
||||
/// </summary>
|
||||
ArrowUp = 105,
|
||||
|
||||
/// <summary>
|
||||
/// The arrow down icon.
|
||||
/// </summary>
|
||||
ArrowDown = 106,
|
||||
|
||||
/// <summary>
|
||||
/// The Crystarium region icon.
|
||||
/// </summary>
|
||||
Crystarium = 107,
|
||||
|
||||
/// <summary>
|
||||
/// The mentor problem icon.
|
||||
/// </summary>
|
||||
MentorProblem = 108,
|
||||
|
||||
/// <summary>
|
||||
/// The unknown gold type Fate icon.
|
||||
/// </summary>
|
||||
FateUnknownGold = 109,
|
||||
|
||||
/// <summary>
|
||||
/// The orange diamond icon.
|
||||
/// </summary>
|
||||
OrangeDiamond = 110,
|
||||
|
||||
/// <summary>
|
||||
/// The crafting type Fate icon.
|
||||
/// </summary>
|
||||
FateCrafting = 111,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
namespace Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
/// <summary>
|
||||
/// An interface binding for a payload that can provide readable Text.
|
||||
/// </summary>
|
||||
public interface ITextProvider
|
||||
namespace Dalamud.Game.Text.SeStringHandling
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the readable text.
|
||||
/// An interface binding for a payload that can provide readable Text.
|
||||
/// </summary>
|
||||
string Text { get; }
|
||||
public interface ITextProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the readable text.
|
||||
/// </summary>
|
||||
string Text { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,404 +15,405 @@ using Serilog;
|
|||
// - [SeString] some way to add surrounding formatting information as flags/data to text (or other?) payloads?
|
||||
// eg, if a text payload is surrounded by italics payloads, strip them out and mark the text payload as italicized
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a parsed SeString payload.
|
||||
/// </summary>
|
||||
public abstract partial class Payload
|
||||
{
|
||||
// private for now, since subclasses shouldn't interact with this.
|
||||
// To force-invalidate it, Dirty can be set to true
|
||||
private byte[] encodedData;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Lumina instance to use for any necessary data lookups.
|
||||
/// </summary>
|
||||
public DataManager DataResolver => Service<DataManager>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of this payload.
|
||||
/// </summary>
|
||||
public abstract PayloadType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether whether this payload has been modified since the last Encode().
|
||||
/// </summary>
|
||||
public bool Dirty { get; protected set; } = true;
|
||||
|
||||
/// <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;
|
||||
|
||||
Payload payload;
|
||||
|
||||
var initialByte = reader.ReadByte();
|
||||
reader.BaseStream.Position--;
|
||||
if (initialByte != START_BYTE)
|
||||
{
|
||||
payload = DecodeText(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
payload = DecodeChunk(reader);
|
||||
}
|
||||
|
||||
// for now, cache off the actual binary data for this payload, so we don't have to
|
||||
// regenerate it if the payload isn't modified
|
||||
// TODO: probably better ways to handle this
|
||||
var payloadEndPos = reader.BaseStream.Position;
|
||||
|
||||
reader.BaseStream.Position = payloadStartPos;
|
||||
payload.encodedData = reader.ReadBytes((int)(payloadEndPos - payloadStartPos));
|
||||
payload.Dirty = false;
|
||||
|
||||
// Log.Verbose($"got payload bytes {BitConverter.ToString(payload.encodedData).Replace("-", " ")}");
|
||||
|
||||
reader.BaseStream.Position = payloadEndPos;
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/// <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 (this.Dirty || force)
|
||||
{
|
||||
this.encodedData = this.EncodeImpl();
|
||||
this.Dirty = false;
|
||||
}
|
||||
|
||||
return this.encodedData;
|
||||
}
|
||||
|
||||
/// <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();
|
||||
|
||||
/// <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>
|
||||
// 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
|
||||
protected abstract void DecodeImpl(BinaryReader reader, long endOfStream);
|
||||
|
||||
private static Payload DecodeChunk(BinaryReader reader)
|
||||
{
|
||||
Payload payload = null;
|
||||
|
||||
reader.ReadByte(); // START_BYTE
|
||||
var chunkType = (SeStringChunkType)reader.ReadByte();
|
||||
var chunkLen = GetInteger(reader);
|
||||
|
||||
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:
|
||||
payload = new EmphasisItalicPayload();
|
||||
break;
|
||||
|
||||
case SeStringChunkType.NewLine:
|
||||
payload = NewLinePayload.Payload;
|
||||
break;
|
||||
|
||||
case SeStringChunkType.SeHyphen:
|
||||
payload = SeHyphenPayload.Payload;
|
||||
break;
|
||||
|
||||
case SeStringChunkType.Interactable:
|
||||
{
|
||||
var subType = (EmbeddedInfoType)reader.ReadByte();
|
||||
switch (subType)
|
||||
{
|
||||
case EmbeddedInfoType.PlayerName:
|
||||
payload = new PlayerPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.ItemLink:
|
||||
payload = new ItemPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.MapPositionLink:
|
||||
payload = new MapLinkPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.Status:
|
||||
payload = new StatusPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.QuestLink:
|
||||
payload = new QuestPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.DalamudLink:
|
||||
payload = new DalamudLinkPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.LinkTerminator:
|
||||
// this has no custom handling and so needs to fallthrough to ensure it is captured
|
||||
default:
|
||||
// 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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SeStringChunkType.AutoTranslateKey:
|
||||
payload = new AutoTranslatePayload();
|
||||
break;
|
||||
|
||||
case SeStringChunkType.UIForeground:
|
||||
payload = new UIForegroundPayload();
|
||||
break;
|
||||
|
||||
case SeStringChunkType.UIGlow:
|
||||
payload = new UIGlowPayload();
|
||||
break;
|
||||
|
||||
case SeStringChunkType.Icon:
|
||||
payload = new IconPayload();
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType);
|
||||
break;
|
||||
}
|
||||
|
||||
payload ??= new RawPayload((byte)chunkType);
|
||||
payload.DecodeImpl(reader, reader.BaseStream.Position + chunkLen - 1);
|
||||
|
||||
// read through the rest of the packet
|
||||
var readBytes = (uint)(reader.BaseStream.Position - packetStart);
|
||||
reader.ReadBytes((int)(chunkLen - readBytes + 1)); // +1 for the END_BYTE marker
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
private static Payload DecodeText(BinaryReader reader)
|
||||
{
|
||||
var payload = new TextPayload();
|
||||
payload.DecodeImpl(reader, reader.BaseStream.Length);
|
||||
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parsing helpers.
|
||||
/// </summary>
|
||||
public abstract partial class Payload
|
||||
namespace Dalamud.Game.Text.SeStringHandling
|
||||
{
|
||||
/// <summary>
|
||||
/// The start byte of a payload.
|
||||
/// This class represents a parsed SeString payload.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "This is prefered.")]
|
||||
protected const byte START_BYTE = 0x02;
|
||||
|
||||
/// <summary>
|
||||
/// The end byte of a payload.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "This is prefered.")]
|
||||
protected const byte END_BYTE = 0x03;
|
||||
|
||||
/// <summary>
|
||||
/// This represents the type of embedded info in a payload.
|
||||
/// </summary>
|
||||
public enum EmbeddedInfoType
|
||||
public abstract partial class Payload
|
||||
{
|
||||
/// <summary>
|
||||
/// A player's name.
|
||||
/// </summary>
|
||||
PlayerName = 0x01,
|
||||
// private for now, since subclasses shouldn't interact with this.
|
||||
// To force-invalidate it, Dirty can be set to true
|
||||
private byte[] encodedData;
|
||||
|
||||
/// <summary>
|
||||
/// The link to an iteme.
|
||||
/// Gets the Lumina instance to use for any necessary data lookups.
|
||||
/// </summary>
|
||||
ItemLink = 0x03,
|
||||
public DataManager DataResolver => Service<DataManager>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// The link to a map position.
|
||||
/// Gets the type of this payload.
|
||||
/// </summary>
|
||||
MapPositionLink = 0x04,
|
||||
public abstract PayloadType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The link to a quest.
|
||||
/// Gets or sets a value indicating whether whether this payload has been modified since the last Encode().
|
||||
/// </summary>
|
||||
QuestLink = 0x05,
|
||||
public bool Dirty { get; protected set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// A status effect.
|
||||
/// Decodes a binary representation of a payload into its corresponding nice object payload.
|
||||
/// </summary>
|
||||
Status = 0x09,
|
||||
|
||||
/// <summary>
|
||||
/// A custom Dalamud link.
|
||||
/// </summary>
|
||||
DalamudLink = 0x0F,
|
||||
|
||||
/// <summary>
|
||||
/// A link terminator.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is not exactly clear what this is, but seems to always follow a link.
|
||||
/// </remarks>
|
||||
LinkTerminator = 0xCF,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This represents the type of payload and how it should be encoded.
|
||||
/// </summary>
|
||||
protected enum SeStringChunkType
|
||||
{
|
||||
/// <summary>
|
||||
/// See the <see cref="IconPayload"/> class.
|
||||
/// </summary>
|
||||
Icon = 0x12,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="EmphasisItalicPayload"/> class.
|
||||
/// </summary>
|
||||
EmphasisItalic = 0x1A,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="NewLinePayload"/>.
|
||||
/// </summary>
|
||||
NewLine = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="SeHyphenPayload"/> class.
|
||||
/// </summary>
|
||||
SeHyphen = 0x1F,
|
||||
|
||||
/// <summary>
|
||||
/// See any of the link-type classes:
|
||||
/// <see cref="PlayerPayload"/>,
|
||||
/// <see cref="ItemPayload"/>,
|
||||
/// <see cref="MapLinkPayload"/>,
|
||||
/// <see cref="StatusPayload"/>,
|
||||
/// <see cref="QuestPayload"/>,
|
||||
/// <see cref="DalamudLinkPayload"/>.
|
||||
/// </summary>
|
||||
Interactable = 0x27,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="AutoTranslatePayload"/> class.
|
||||
/// </summary>
|
||||
AutoTranslateKey = 0x2E,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="UIForegroundPayload"/> class.
|
||||
/// </summary>
|
||||
UIForeground = 0x48,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="UIGlowPayload"/> class.
|
||||
/// </summary>
|
||||
UIGlow = 0x49,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the packed integer from SE's native data format.
|
||||
/// </summary>
|
||||
/// <param name="input">The BinaryReader instance.</param>
|
||||
/// <returns>An integer.</returns>
|
||||
// made protected, unless we actually want to use it externally
|
||||
// in which case it should probably go live somewhere else
|
||||
protected static uint GetInteger(BinaryReader input)
|
||||
{
|
||||
uint marker = input.ReadByte();
|
||||
if (marker < 0xD0)
|
||||
return marker - 1;
|
||||
|
||||
// the game adds 0xF0 marker for values >= 0xCF
|
||||
// uasge of 0xD0-0xEF is unknown, should we throw here?
|
||||
// if (marker < 0xF0) throw new NotSupportedException();
|
||||
|
||||
marker = (marker + 1) & 0b1111;
|
||||
|
||||
var ret = new byte[4];
|
||||
for (var i = 3; i >= 0; i--)
|
||||
/// <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)
|
||||
{
|
||||
ret[i] = (marker & (1 << i)) == 0 ? (byte)0 : input.ReadByte();
|
||||
}
|
||||
var payloadStartPos = reader.BaseStream.Position;
|
||||
|
||||
return BitConverter.ToUInt32(ret, 0);
|
||||
}
|
||||
Payload payload;
|
||||
|
||||
/// <summary>
|
||||
/// Create a packed integer in Se's native data format.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to pack.</param>
|
||||
/// <returns>A packed integer.</returns>
|
||||
protected static byte[] MakeInteger(uint value)
|
||||
{
|
||||
if (value < 0xCF)
|
||||
{
|
||||
return new byte[] { (byte)(value + 1) };
|
||||
}
|
||||
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
|
||||
var ret = new List<byte>() { 0xF0 };
|
||||
for (var i = 3; i >= 0; i--)
|
||||
{
|
||||
if (bytes[i] != 0)
|
||||
var initialByte = reader.ReadByte();
|
||||
reader.BaseStream.Position--;
|
||||
if (initialByte != START_BYTE)
|
||||
{
|
||||
ret.Add(bytes[i]);
|
||||
ret[0] |= (byte)(1 << i);
|
||||
payload = DecodeText(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
payload = DecodeChunk(reader);
|
||||
}
|
||||
|
||||
// for now, cache off the actual binary data for this payload, so we don't have to
|
||||
// regenerate it if the payload isn't modified
|
||||
// TODO: probably better ways to handle this
|
||||
var payloadEndPos = reader.BaseStream.Position;
|
||||
|
||||
reader.BaseStream.Position = payloadStartPos;
|
||||
payload.encodedData = reader.ReadBytes((int)(payloadEndPos - payloadStartPos));
|
||||
payload.Dirty = false;
|
||||
|
||||
// Log.Verbose($"got payload bytes {BitConverter.ToString(payload.encodedData).Replace("-", " ")}");
|
||||
|
||||
reader.BaseStream.Position = payloadEndPos;
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
ret[0] -= 1;
|
||||
/// <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 (this.Dirty || force)
|
||||
{
|
||||
this.encodedData = this.EncodeImpl();
|
||||
this.Dirty = false;
|
||||
}
|
||||
|
||||
return ret.ToArray();
|
||||
return this.encodedData;
|
||||
}
|
||||
|
||||
/// <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();
|
||||
|
||||
/// <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>
|
||||
// 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
|
||||
protected abstract void DecodeImpl(BinaryReader reader, long endOfStream);
|
||||
|
||||
private static Payload DecodeChunk(BinaryReader reader)
|
||||
{
|
||||
Payload payload = null;
|
||||
|
||||
reader.ReadByte(); // START_BYTE
|
||||
var chunkType = (SeStringChunkType)reader.ReadByte();
|
||||
var chunkLen = GetInteger(reader);
|
||||
|
||||
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:
|
||||
payload = new EmphasisItalicPayload();
|
||||
break;
|
||||
|
||||
case SeStringChunkType.NewLine:
|
||||
payload = NewLinePayload.Payload;
|
||||
break;
|
||||
|
||||
case SeStringChunkType.SeHyphen:
|
||||
payload = SeHyphenPayload.Payload;
|
||||
break;
|
||||
|
||||
case SeStringChunkType.Interactable:
|
||||
{
|
||||
var subType = (EmbeddedInfoType)reader.ReadByte();
|
||||
switch (subType)
|
||||
{
|
||||
case EmbeddedInfoType.PlayerName:
|
||||
payload = new PlayerPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.ItemLink:
|
||||
payload = new ItemPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.MapPositionLink:
|
||||
payload = new MapLinkPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.Status:
|
||||
payload = new StatusPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.QuestLink:
|
||||
payload = new QuestPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.DalamudLink:
|
||||
payload = new DalamudLinkPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.LinkTerminator:
|
||||
// this has no custom handling and so needs to fallthrough to ensure it is captured
|
||||
default:
|
||||
// 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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SeStringChunkType.AutoTranslateKey:
|
||||
payload = new AutoTranslatePayload();
|
||||
break;
|
||||
|
||||
case SeStringChunkType.UIForeground:
|
||||
payload = new UIForegroundPayload();
|
||||
break;
|
||||
|
||||
case SeStringChunkType.UIGlow:
|
||||
payload = new UIGlowPayload();
|
||||
break;
|
||||
|
||||
case SeStringChunkType.Icon:
|
||||
payload = new IconPayload();
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType);
|
||||
break;
|
||||
}
|
||||
|
||||
payload ??= new RawPayload((byte)chunkType);
|
||||
payload.DecodeImpl(reader, reader.BaseStream.Position + chunkLen - 1);
|
||||
|
||||
// read through the rest of the packet
|
||||
var readBytes = (uint)(reader.BaseStream.Position - packetStart);
|
||||
reader.ReadBytes((int)(chunkLen - readBytes + 1)); // +1 for the END_BYTE marker
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
private static Payload DecodeText(BinaryReader reader)
|
||||
{
|
||||
var payload = new TextPayload();
|
||||
payload.DecodeImpl(reader, reader.BaseStream.Length);
|
||||
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// From a binary packed integer, get the high and low bytes.
|
||||
/// Parsing helpers.
|
||||
/// </summary>
|
||||
/// <param name="input">The BinaryReader instance.</param>
|
||||
/// <returns>The high and low bytes.</returns>
|
||||
protected static (uint High, uint Low) GetPackedIntegers(BinaryReader input)
|
||||
public abstract partial class Payload
|
||||
{
|
||||
var value = GetInteger(input);
|
||||
return (value >> 16, value & 0xFFFF);
|
||||
}
|
||||
/// <summary>
|
||||
/// The start byte of a payload.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "This is prefered.")]
|
||||
protected const byte START_BYTE = 0x02;
|
||||
|
||||
/// <summary>
|
||||
/// Create a packed integer from the given high and low bytes.
|
||||
/// </summary>
|
||||
/// <param name="high">The high order bytes.</param>
|
||||
/// <param name="low">The low order bytes.</param>
|
||||
/// <returns>A packed integer.</returns>
|
||||
protected static byte[] MakePackedInteger(uint high, uint low)
|
||||
{
|
||||
var value = (high << 16) | (low & 0xFFFF);
|
||||
return MakeInteger(value);
|
||||
/// <summary>
|
||||
/// The end byte of a payload.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "This is prefered.")]
|
||||
protected const byte END_BYTE = 0x03;
|
||||
|
||||
/// <summary>
|
||||
/// This represents the type of embedded info in a payload.
|
||||
/// </summary>
|
||||
public enum EmbeddedInfoType
|
||||
{
|
||||
/// <summary>
|
||||
/// A player's name.
|
||||
/// </summary>
|
||||
PlayerName = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// The link to an iteme.
|
||||
/// </summary>
|
||||
ItemLink = 0x03,
|
||||
|
||||
/// <summary>
|
||||
/// The link to a map position.
|
||||
/// </summary>
|
||||
MapPositionLink = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// The link to a quest.
|
||||
/// </summary>
|
||||
QuestLink = 0x05,
|
||||
|
||||
/// <summary>
|
||||
/// A status effect.
|
||||
/// </summary>
|
||||
Status = 0x09,
|
||||
|
||||
/// <summary>
|
||||
/// A custom Dalamud link.
|
||||
/// </summary>
|
||||
DalamudLink = 0x0F,
|
||||
|
||||
/// <summary>
|
||||
/// A link terminator.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is not exactly clear what this is, but seems to always follow a link.
|
||||
/// </remarks>
|
||||
LinkTerminator = 0xCF,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This represents the type of payload and how it should be encoded.
|
||||
/// </summary>
|
||||
protected enum SeStringChunkType
|
||||
{
|
||||
/// <summary>
|
||||
/// See the <see cref="IconPayload"/> class.
|
||||
/// </summary>
|
||||
Icon = 0x12,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="EmphasisItalicPayload"/> class.
|
||||
/// </summary>
|
||||
EmphasisItalic = 0x1A,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="NewLinePayload"/>.
|
||||
/// </summary>
|
||||
NewLine = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="SeHyphenPayload"/> class.
|
||||
/// </summary>
|
||||
SeHyphen = 0x1F,
|
||||
|
||||
/// <summary>
|
||||
/// See any of the link-type classes:
|
||||
/// <see cref="PlayerPayload"/>,
|
||||
/// <see cref="ItemPayload"/>,
|
||||
/// <see cref="MapLinkPayload"/>,
|
||||
/// <see cref="StatusPayload"/>,
|
||||
/// <see cref="QuestPayload"/>,
|
||||
/// <see cref="DalamudLinkPayload"/>.
|
||||
/// </summary>
|
||||
Interactable = 0x27,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="AutoTranslatePayload"/> class.
|
||||
/// </summary>
|
||||
AutoTranslateKey = 0x2E,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="UIForegroundPayload"/> class.
|
||||
/// </summary>
|
||||
UIForeground = 0x48,
|
||||
|
||||
/// <summary>
|
||||
/// See the <see cref="UIGlowPayload"/> class.
|
||||
/// </summary>
|
||||
UIGlow = 0x49,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the packed integer from SE's native data format.
|
||||
/// </summary>
|
||||
/// <param name="input">The BinaryReader instance.</param>
|
||||
/// <returns>An integer.</returns>
|
||||
// made protected, unless we actually want to use it externally
|
||||
// in which case it should probably go live somewhere else
|
||||
protected static uint GetInteger(BinaryReader input)
|
||||
{
|
||||
uint marker = input.ReadByte();
|
||||
if (marker < 0xD0)
|
||||
return marker - 1;
|
||||
|
||||
// the game adds 0xF0 marker for values >= 0xCF
|
||||
// uasge of 0xD0-0xEF is unknown, should we throw here?
|
||||
// if (marker < 0xF0) throw new NotSupportedException();
|
||||
|
||||
marker = (marker + 1) & 0b1111;
|
||||
|
||||
var ret = new byte[4];
|
||||
for (var i = 3; i >= 0; i--)
|
||||
{
|
||||
ret[i] = (marker & (1 << i)) == 0 ? (byte)0 : input.ReadByte();
|
||||
}
|
||||
|
||||
return BitConverter.ToUInt32(ret, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a packed integer in Se's native data format.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to pack.</param>
|
||||
/// <returns>A packed integer.</returns>
|
||||
protected static byte[] MakeInteger(uint value)
|
||||
{
|
||||
if (value < 0xCF)
|
||||
{
|
||||
return new byte[] { (byte)(value + 1) };
|
||||
}
|
||||
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
|
||||
var ret = new List<byte>() { 0xF0 };
|
||||
for (var i = 3; i >= 0; i--)
|
||||
{
|
||||
if (bytes[i] != 0)
|
||||
{
|
||||
ret.Add(bytes[i]);
|
||||
ret[0] |= (byte)(1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
ret[0] -= 1;
|
||||
|
||||
return ret.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// From a binary packed integer, get the high and low bytes.
|
||||
/// </summary>
|
||||
/// <param name="input">The BinaryReader instance.</param>
|
||||
/// <returns>The high and low bytes.</returns>
|
||||
protected static (uint High, uint Low) GetPackedIntegers(BinaryReader input)
|
||||
{
|
||||
var value = GetInteger(input);
|
||||
return (value >> 16, value & 0xFFFF);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a packed integer from the given high and low bytes.
|
||||
/// </summary>
|
||||
/// <param name="high">The high order bytes.</param>
|
||||
/// <param name="low">The low order bytes.</param>
|
||||
/// <returns>A packed integer.</returns>
|
||||
protected static byte[] MakePackedInteger(uint high, uint low)
|
||||
{
|
||||
var value = (high << 16) | (low & 0xFFFF);
|
||||
return MakeInteger(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,82 +1,83 @@
|
|||
namespace Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
/// <summary>
|
||||
/// All parsed types of SeString payloads.
|
||||
/// </summary>
|
||||
public enum PayloadType
|
||||
namespace Dalamud.Game.Text.SeStringHandling
|
||||
{
|
||||
/// <summary>
|
||||
/// An unknown SeString.
|
||||
/// All parsed types of SeString payloads.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
public enum PayloadType
|
||||
{
|
||||
/// <summary>
|
||||
/// An unknown SeString.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing a player link.
|
||||
/// </summary>
|
||||
Player,
|
||||
/// <summary>
|
||||
/// An SeString payload representing a player link.
|
||||
/// </summary>
|
||||
Player,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing an Item link.
|
||||
/// </summary>
|
||||
Item,
|
||||
/// <summary>
|
||||
/// An SeString payload representing an Item link.
|
||||
/// </summary>
|
||||
Item,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing an Status Effect link.
|
||||
/// </summary>
|
||||
Status,
|
||||
/// <summary>
|
||||
/// An SeString payload representing an Status Effect link.
|
||||
/// </summary>
|
||||
Status,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing raw, typed text.
|
||||
/// </summary>
|
||||
RawText,
|
||||
/// <summary>
|
||||
/// An SeString payload representing raw, typed text.
|
||||
/// </summary>
|
||||
RawText,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing a text foreground color.
|
||||
/// </summary>
|
||||
UIForeground,
|
||||
/// <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 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 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 an auto-translate dictionary entry.
|
||||
/// </summary>
|
||||
AutoTranslateText,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing italic emphasis formatting on text.
|
||||
/// </summary>
|
||||
EmphasisItalic,
|
||||
/// <summary>
|
||||
/// An SeString payload representing italic emphasis formatting on text.
|
||||
/// </summary>
|
||||
EmphasisItalic,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing a bitmap icon.
|
||||
/// </summary>
|
||||
Icon,
|
||||
/// <summary>
|
||||
/// An SeString payload representing a bitmap icon.
|
||||
/// </summary>
|
||||
Icon,
|
||||
|
||||
/// <summary>
|
||||
/// A SeString payload representing a quest link.
|
||||
/// </summary>
|
||||
Quest,
|
||||
/// <summary>
|
||||
/// A SeString payload representing a quest link.
|
||||
/// </summary>
|
||||
Quest,
|
||||
|
||||
/// <summary>
|
||||
/// A SeString payload representing a custom clickable link for dalamud plugins.
|
||||
/// </summary>
|
||||
DalamudLink,
|
||||
/// <summary>
|
||||
/// A SeString payload representing a custom clickable link for dalamud plugins.
|
||||
/// </summary>
|
||||
DalamudLink,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing a newline character.
|
||||
/// </summary>
|
||||
NewLine,
|
||||
/// <summary>
|
||||
/// An SeString payload representing a newline character.
|
||||
/// </summary>
|
||||
NewLine,
|
||||
|
||||
/// <summary>
|
||||
/// An SeString payload representing a doublewide SE hypen.
|
||||
/// </summary>
|
||||
SeHyphen,
|
||||
/// <summary>
|
||||
/// An SeString payload representing a doublewide SE hypen.
|
||||
/// </summary>
|
||||
SeHyphen,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,164 +7,165 @@ using Lumina.Excel.GeneratedSheets;
|
|||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// An SeString Payload containing an auto-translation/completion chat message.
|
||||
/// </summary>
|
||||
public class AutoTranslatePayload : Payload, ITextProvider
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
private string text;
|
||||
|
||||
[JsonProperty]
|
||||
private uint group;
|
||||
|
||||
[JsonProperty]
|
||||
private uint key;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoTranslatePayload"/> class.
|
||||
/// Creates a new auto-translate payload.
|
||||
/// An SeString Payload containing an auto-translation/completion chat message.
|
||||
/// </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)
|
||||
public class AutoTranslatePayload : Payload, ITextProvider
|
||||
{
|
||||
// TODO: friendlier ctor? not sure how to handle that given how weird the tables are
|
||||
this.group = group;
|
||||
this.key = key;
|
||||
}
|
||||
private string text;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoTranslatePayload"/> class.
|
||||
/// </summary>
|
||||
internal AutoTranslatePayload()
|
||||
{
|
||||
}
|
||||
[JsonProperty]
|
||||
private uint group;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.AutoTranslateText;
|
||||
[JsonProperty]
|
||||
private uint key;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actual text displayed in-game for this payload.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
public string Text
|
||||
{
|
||||
get
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoTranslatePayload"/> class.
|
||||
/// 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)
|
||||
{
|
||||
// wrap the text in the colored brackets that is uses in-game, since those are not actually part of any of the payloads
|
||||
return this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {this.Resolve()} {(char)SeIconChar.AutoTranslateClose}";
|
||||
// TODO: friendlier ctor? not sure how to handle that given how weird the tables are
|
||||
this.group = group;
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
/// </summary>
|
||||
/// <returns>A string that represents the current object.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - Group: {this.group}, Key: {this.key}, Text: {this.Text}";
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoTranslatePayload"/> class.
|
||||
/// </summary>
|
||||
internal AutoTranslatePayload()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var keyBytes = MakeInteger(this.key);
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.AutoTranslateText;
|
||||
|
||||
var chunkLen = keyBytes.Length + 2;
|
||||
var bytes = new List<byte>()
|
||||
/// <summary>
|
||||
/// Gets the actual text displayed in-game for this payload.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
public string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
// wrap the text in the colored brackets that is uses in-game, since those are not actually part of any of the payloads
|
||||
return this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {this.Resolve()} {(char)SeIconChar.AutoTranslateClose}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
/// </summary>
|
||||
/// <returns>A string that represents the current object.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - Group: {this.group}, Key: {this.key}, Text: {this.Text}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var keyBytes = MakeInteger(this.key);
|
||||
|
||||
var chunkLen = keyBytes.Length + 2;
|
||||
var bytes = new List<byte>()
|
||||
{
|
||||
START_BYTE,
|
||||
(byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen,
|
||||
(byte)this.group,
|
||||
};
|
||||
bytes.AddRange(keyBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
bytes.AddRange(keyBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(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
|
||||
this.group = reader.ReadByte();
|
||||
|
||||
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(this.key);
|
||||
return bytes.ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
} // don't care, row will be null
|
||||
|
||||
if (row?.Group == this.group)
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
// if the row exists in this table and the group matches, this is actually the correct data
|
||||
value = row.Text;
|
||||
// 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
|
||||
this.group = reader.ReadByte();
|
||||
|
||||
this.key = GetInteger(reader);
|
||||
}
|
||||
else
|
||||
|
||||
private string Resolve()
|
||||
{
|
||||
string value = null;
|
||||
|
||||
var sheet = this.DataResolver.GetExcelSheet<Completion>();
|
||||
|
||||
Completion row = null;
|
||||
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.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.RawString.Split('[')[0];
|
||||
|
||||
var name = actualTableName switch
|
||||
{
|
||||
"Action" => this.DataResolver.GetExcelSheet<Lumina.Excel.GeneratedSheets.Action>().GetRow(this.key).Name,
|
||||
"ActionComboRoute" => this.DataResolver.GetExcelSheet<ActionComboRoute>().GetRow(this.key).Name,
|
||||
"BuddyAction" => this.DataResolver.GetExcelSheet<BuddyAction>().GetRow(this.key).Name,
|
||||
"ClassJob" => this.DataResolver.GetExcelSheet<ClassJob>().GetRow(this.key).Name,
|
||||
"Companion" => this.DataResolver.GetExcelSheet<Companion>().GetRow(this.key).Singular,
|
||||
"CraftAction" => this.DataResolver.GetExcelSheet<CraftAction>().GetRow(this.key).Name,
|
||||
"GeneralAction" => this.DataResolver.GetExcelSheet<GeneralAction>().GetRow(this.key).Name,
|
||||
"GuardianDeity" => this.DataResolver.GetExcelSheet<GuardianDeity>().GetRow(this.key).Name,
|
||||
"MainCommand" => this.DataResolver.GetExcelSheet<MainCommand>().GetRow(this.key).Name,
|
||||
"Mount" => this.DataResolver.GetExcelSheet<Mount>().GetRow(this.key).Singular,
|
||||
"Pet" => this.DataResolver.GetExcelSheet<Pet>().GetRow(this.key).Name,
|
||||
"PetAction" => this.DataResolver.GetExcelSheet<PetAction>().GetRow(this.key).Name,
|
||||
"PetMirage" => this.DataResolver.GetExcelSheet<PetMirage>().GetRow(this.key).Name,
|
||||
"PlaceName" => this.DataResolver.GetExcelSheet<PlaceName>().GetRow(this.key).Name,
|
||||
"Race" => this.DataResolver.GetExcelSheet<Race>().GetRow(this.key).Masculine,
|
||||
"TextCommand" => this.DataResolver.GetExcelSheet<TextCommand>().GetRow(this.key).Command,
|
||||
"Tribe" => this.DataResolver.GetExcelSheet<Tribe>().GetRow(this.key).Masculine,
|
||||
"Weather" => this.DataResolver.GetExcelSheet<Weather>().GetRow(this.key).Name,
|
||||
_ => throw new Exception(actualTableName),
|
||||
};
|
||||
|
||||
value = name;
|
||||
// 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(this.key);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch
|
||||
{
|
||||
Log.Error(e, $"AutoTranslatePayload - failed to resolve: {this.Type} - Group: {this.group}, Key: {this.key}");
|
||||
}
|
||||
}
|
||||
} // don't care, row will be null
|
||||
|
||||
return value;
|
||||
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.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.RawString.Split('[')[0];
|
||||
|
||||
var name = actualTableName switch
|
||||
{
|
||||
"Action" => this.DataResolver.GetExcelSheet<Lumina.Excel.GeneratedSheets.Action>().GetRow(this.key).Name,
|
||||
"ActionComboRoute" => this.DataResolver.GetExcelSheet<ActionComboRoute>().GetRow(this.key).Name,
|
||||
"BuddyAction" => this.DataResolver.GetExcelSheet<BuddyAction>().GetRow(this.key).Name,
|
||||
"ClassJob" => this.DataResolver.GetExcelSheet<ClassJob>().GetRow(this.key).Name,
|
||||
"Companion" => this.DataResolver.GetExcelSheet<Companion>().GetRow(this.key).Singular,
|
||||
"CraftAction" => this.DataResolver.GetExcelSheet<CraftAction>().GetRow(this.key).Name,
|
||||
"GeneralAction" => this.DataResolver.GetExcelSheet<GeneralAction>().GetRow(this.key).Name,
|
||||
"GuardianDeity" => this.DataResolver.GetExcelSheet<GuardianDeity>().GetRow(this.key).Name,
|
||||
"MainCommand" => this.DataResolver.GetExcelSheet<MainCommand>().GetRow(this.key).Name,
|
||||
"Mount" => this.DataResolver.GetExcelSheet<Mount>().GetRow(this.key).Singular,
|
||||
"Pet" => this.DataResolver.GetExcelSheet<Pet>().GetRow(this.key).Name,
|
||||
"PetAction" => this.DataResolver.GetExcelSheet<PetAction>().GetRow(this.key).Name,
|
||||
"PetMirage" => this.DataResolver.GetExcelSheet<PetMirage>().GetRow(this.key).Name,
|
||||
"PlaceName" => this.DataResolver.GetExcelSheet<PlaceName>().GetRow(this.key).Name,
|
||||
"Race" => this.DataResolver.GetExcelSheet<Race>().GetRow(this.key).Masculine,
|
||||
"TextCommand" => this.DataResolver.GetExcelSheet<TextCommand>().GetRow(this.key).Command,
|
||||
"Tribe" => this.DataResolver.GetExcelSheet<Tribe>().GetRow(this.key).Masculine,
|
||||
"Weather" => this.DataResolver.GetExcelSheet<Weather>().GetRow(this.key).Name,
|
||||
_ => throw new Exception(actualTableName),
|
||||
};
|
||||
|
||||
value = name;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"AutoTranslatePayload - failed to resolve: {this.Type} - Group: {this.group}, Key: {this.key}");
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,56 +3,57 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a custom Dalamud clickable chat link.
|
||||
/// </summary>
|
||||
public class DalamudLinkPayload : Payload
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.DalamudLink;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin command ID to be linked.
|
||||
/// This class represents a custom Dalamud clickable chat link.
|
||||
/// </summary>
|
||||
public uint CommandId { get; internal set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin name to be linked.
|
||||
/// </summary>
|
||||
public string Plugin { get; internal set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
public class DalamudLinkPayload : Payload
|
||||
{
|
||||
return $"{this.Type} - Plugin: {this.Plugin}, Command: {this.CommandId}";
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.DalamudLink;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var pluginBytes = Encoding.UTF8.GetBytes(this.Plugin);
|
||||
var commandBytes = MakeInteger(this.CommandId);
|
||||
var chunkLen = 3 + pluginBytes.Length + commandBytes.Length;
|
||||
/// <summary>
|
||||
/// Gets the plugin command ID to be linked.
|
||||
/// </summary>
|
||||
public uint CommandId { get; internal set; } = 0;
|
||||
|
||||
if (chunkLen > 255)
|
||||
/// <summary>
|
||||
/// Gets the plugin name to be linked.
|
||||
/// </summary>
|
||||
public string Plugin { get; internal set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
throw new Exception("Chunk is too long. Plugin name exceeds limits for DalamudLinkPayload");
|
||||
return $"{this.Type} - Plugin: {this.Plugin}, Command: {this.CommandId}";
|
||||
}
|
||||
|
||||
var bytes = new List<byte> { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.DalamudLink };
|
||||
bytes.Add((byte)pluginBytes.Length);
|
||||
bytes.AddRange(pluginBytes);
|
||||
bytes.AddRange(commandBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
return bytes.ToArray();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var pluginBytes = Encoding.UTF8.GetBytes(this.Plugin);
|
||||
var commandBytes = MakeInteger(this.CommandId);
|
||||
var chunkLen = 3 + pluginBytes.Length + commandBytes.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte()));
|
||||
this.CommandId = GetInteger(reader);
|
||||
if (chunkLen > 255)
|
||||
{
|
||||
throw new Exception("Chunk is too long. Plugin name exceeds limits for DalamudLinkPayload");
|
||||
}
|
||||
|
||||
var bytes = new List<byte> { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.DalamudLink };
|
||||
bytes.Add((byte)pluginBytes.Length);
|
||||
bytes.AddRange(pluginBytes);
|
||||
bytes.AddRange(commandBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte()));
|
||||
this.CommandId = GetInteger(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,80 +2,81 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <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
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmphasisItalicPayload"/> class.
|
||||
/// Creates an EmphasisItalicPayload.
|
||||
/// An SeString Payload containing information about enabling or disabling italics formatting on following text.
|
||||
/// </summary>
|
||||
/// <param name="enabled">Whether italics formatting should be enabled or disabled for following text.</param>
|
||||
public EmphasisItalicPayload(bool enabled)
|
||||
/// <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
|
||||
{
|
||||
this.IsEnabled = enabled;
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmphasisItalicPayload"/> class.
|
||||
/// Creates an EmphasisItalicPayload.
|
||||
/// </summary>
|
||||
/// <param name="enabled">Whether italics formatting should be enabled or disabled for following text.</param>
|
||||
public EmphasisItalicPayload(bool enabled)
|
||||
{
|
||||
this.IsEnabled = enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmphasisItalicPayload"/> class.
|
||||
/// Creates an EmphasisItalicPayload.
|
||||
/// </summary>
|
||||
internal EmphasisItalicPayload()
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmphasisItalicPayload"/> class.
|
||||
/// Creates an EmphasisItalicPayload.
|
||||
/// </summary>
|
||||
internal EmphasisItalicPayload()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a payload representing enabling italics on following text.
|
||||
/// </summary>
|
||||
public static EmphasisItalicPayload ItalicsOn => new(true);
|
||||
/// <summary>
|
||||
/// Gets a payload representing enabling italics on following text.
|
||||
/// </summary>
|
||||
public static EmphasisItalicPayload ItalicsOn => new(true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a payload representing disabling italics on following text.
|
||||
/// </summary>
|
||||
public static EmphasisItalicPayload ItalicsOff => new(false);
|
||||
/// <summary>
|
||||
/// Gets a payload representing disabling italics on following text.
|
||||
/// </summary>
|
||||
public static EmphasisItalicPayload ItalicsOff => new(false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this payload enables italics formatting for following text.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this payload enables italics formatting for following text.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.EmphasisItalic;
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.EmphasisItalic;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - Enabled: {this.IsEnabled}";
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - Enabled: {this.IsEnabled}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
// realistically this will always be a single byte of value 1 or 2
|
||||
// but we'll treat it normally anyway
|
||||
var enabledBytes = MakeInteger(this.IsEnabled ? 1u : 0);
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
// realistically this will always be a single byte of value 1 or 2
|
||||
// but we'll treat it normally anyway
|
||||
var enabledBytes = MakeInteger(this.IsEnabled ? 1u : 0);
|
||||
|
||||
var chunkLen = enabledBytes.Length + 1;
|
||||
var bytes = new List<byte>()
|
||||
var chunkLen = enabledBytes.Length + 1;
|
||||
var bytes = new List<byte>()
|
||||
{
|
||||
START_BYTE, (byte)SeStringChunkType.EmphasisItalic, (byte)chunkLen,
|
||||
};
|
||||
bytes.AddRange(enabledBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
bytes.AddRange(enabledBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.IsEnabled = GetInteger(reader) == 1;
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.IsEnabled = GetInteger(reader) == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,62 +1,63 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// SeString payload representing a bitmap icon from fontIcon.
|
||||
/// </summary>
|
||||
public class IconPayload : Payload
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IconPayload"/> class.
|
||||
/// Create a Icon payload for the specified icon.
|
||||
/// SeString payload representing a bitmap icon from fontIcon.
|
||||
/// </summary>
|
||||
/// <param name="icon">The Icon.</param>
|
||||
public IconPayload(BitmapFontIcon icon)
|
||||
public class IconPayload : Payload
|
||||
{
|
||||
this.Icon = icon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IconPayload"/> class.
|
||||
/// Create a Icon payload for the specified icon.
|
||||
/// </summary>
|
||||
internal IconPayload()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Icon;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the icon the payload represents.
|
||||
/// </summary>
|
||||
public BitmapFontIcon Icon { get; set; } = BitmapFontIcon.None;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - {this.Icon}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var indexBytes = MakeInteger((uint)this.Icon);
|
||||
var chunkLen = indexBytes.Length + 1;
|
||||
var bytes = new List<byte>(new byte[]
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IconPayload"/> class.
|
||||
/// Create a Icon payload for the specified icon.
|
||||
/// </summary>
|
||||
/// <param name="icon">The Icon.</param>
|
||||
public IconPayload(BitmapFontIcon icon)
|
||||
{
|
||||
START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen,
|
||||
});
|
||||
bytes.AddRange(indexBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
return bytes.ToArray();
|
||||
}
|
||||
this.Icon = icon;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.Icon = (BitmapFontIcon)GetInteger(reader);
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IconPayload"/> class.
|
||||
/// Create a Icon payload for the specified icon.
|
||||
/// </summary>
|
||||
internal IconPayload()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Icon;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the icon the payload represents.
|
||||
/// </summary>
|
||||
public BitmapFontIcon Icon { get; set; } = BitmapFontIcon.None;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - {this.Icon}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var indexBytes = MakeInteger((uint)this.Icon);
|
||||
var chunkLen = indexBytes.Length + 1;
|
||||
var bytes = new List<byte>(new byte[]
|
||||
{
|
||||
START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen,
|
||||
});
|
||||
bytes.AddRange(indexBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.Icon = (BitmapFontIcon)GetInteger(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,183 +6,184 @@ using System.Text;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// An SeString Payload representing an interactable item link.
|
||||
/// </summary>
|
||||
public class ItemPayload : Payload
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
private Item item;
|
||||
|
||||
// mainly to allow overriding the name (for things like owo)
|
||||
// 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;
|
||||
|
||||
[JsonProperty]
|
||||
private uint itemId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemPayload"/> class.
|
||||
/// Creates a payload representing an interactable item link for the specified item.
|
||||
/// An SeString Payload representing an interactable item link.
|
||||
/// </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)
|
||||
public class ItemPayload : Payload
|
||||
{
|
||||
this.itemId = itemId;
|
||||
this.IsHQ = isHQ;
|
||||
this.displayName = displayNameOverride;
|
||||
}
|
||||
private Item item;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemPayload"/> class.
|
||||
/// Creates a payload representing an interactable item link for the specified item.
|
||||
/// </summary>
|
||||
internal ItemPayload()
|
||||
{
|
||||
}
|
||||
// mainly to allow overriding the name (for things like owo)
|
||||
// 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;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Item;
|
||||
[JsonProperty]
|
||||
private uint itemId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets 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
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemPayload"/> class.
|
||||
/// 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)
|
||||
{
|
||||
return this.displayName;
|
||||
this.itemId = itemId;
|
||||
this.IsHQ = isHQ;
|
||||
this.displayName = displayNameOverride;
|
||||
}
|
||||
|
||||
set
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemPayload"/> class.
|
||||
/// Creates a payload representing an interactable item link for the specified item.
|
||||
/// </summary>
|
||||
internal ItemPayload()
|
||||
{
|
||||
this.displayName = value;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw item ID of this payload.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint ItemId => this.itemId;
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Item;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying Lumina Item represented by this payload.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Item Item => this.item ??= this.DataResolver.GetExcelSheet<Item>().GetRow(this.itemId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this item link is for a high-quality version of the item.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public bool IsHQ { get; private set; } = false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - ItemId: {this.itemId}, IsHQ: {this.IsHQ}, Name: {this.displayName ?? this.Item.Name}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var actualItemId = this.IsHQ ? this.itemId + 1000000 : this.itemId;
|
||||
var idBytes = MakeInteger(actualItemId);
|
||||
var hasName = !string.IsNullOrEmpty(this.displayName);
|
||||
|
||||
var chunkLen = idBytes.Length + 4;
|
||||
if (hasName)
|
||||
/// <summary>
|
||||
/// Gets or sets 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
|
||||
{
|
||||
// 1 additional unknown byte compared to the nameless version, 1 byte for the name length, and then the name itself
|
||||
chunkLen += 1 + 1 + this.displayName.Length;
|
||||
if (this.IsHQ)
|
||||
get
|
||||
{
|
||||
chunkLen += 4; // unicode representation of the HQ symbol is 3 bytes, preceded by a space
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.displayName = value;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
var bytes = new List<byte>()
|
||||
/// <summary>
|
||||
/// Gets the raw item ID of this payload.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint ItemId => this.itemId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying Lumina Item represented by this payload.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Item Item => this.item ??= this.DataResolver.GetExcelSheet<Item>().GetRow(this.itemId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this item link is for a high-quality version of the item.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public bool IsHQ { get; private set; } = false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - ItemId: {this.itemId}, IsHQ: {this.IsHQ}, Name: {this.displayName ?? this.Item.Name}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var actualItemId = this.IsHQ ? this.itemId + 1000000 : this.itemId;
|
||||
var idBytes = MakeInteger(actualItemId);
|
||||
var 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 + this.displayName.Length;
|
||||
if (this.IsHQ)
|
||||
{
|
||||
chunkLen += 4; // unicode representation of the HQ symbol is 3 bytes, preceded by a space
|
||||
}
|
||||
}
|
||||
|
||||
var bytes = new List<byte>()
|
||||
{
|
||||
START_BYTE,
|
||||
(byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink,
|
||||
};
|
||||
bytes.AddRange(idBytes);
|
||||
// unk
|
||||
bytes.AddRange(new byte[] { 0x02, 0x01 });
|
||||
bytes.AddRange(idBytes);
|
||||
// unk
|
||||
bytes.AddRange(new byte[] { 0x02, 0x01 });
|
||||
|
||||
// Links don't have to include the name, but if they do, it requires additional work
|
||||
if (hasName)
|
||||
{
|
||||
var nameLen = this.displayName.Length + 1;
|
||||
if (this.IsHQ)
|
||||
// Links don't have to include the name, but if they do, it requires additional work
|
||||
if (hasName)
|
||||
{
|
||||
nameLen += 4; // space plus 3 bytes for HQ symbol
|
||||
}
|
||||
var nameLen = this.displayName.Length + 1;
|
||||
if (this.IsHQ)
|
||||
{
|
||||
nameLen += 4; // space plus 3 bytes for HQ symbol
|
||||
}
|
||||
|
||||
bytes.AddRange(new byte[]
|
||||
{
|
||||
bytes.AddRange(new byte[]
|
||||
{
|
||||
0xFF, // unk
|
||||
(byte)nameLen,
|
||||
});
|
||||
bytes.AddRange(Encoding.UTF8.GetBytes(this.displayName));
|
||||
});
|
||||
bytes.AddRange(Encoding.UTF8.GetBytes(this.displayName));
|
||||
|
||||
if (this.IsHQ)
|
||||
{
|
||||
// space and HQ symbol
|
||||
bytes.AddRange(new byte[] { 0x20, 0xEE, 0x80, 0xBC });
|
||||
}
|
||||
}
|
||||
|
||||
bytes.Add(END_BYTE);
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.itemId = GetInteger(reader);
|
||||
|
||||
if (this.itemId > 1000000)
|
||||
{
|
||||
this.itemId -= 1000000;
|
||||
this.IsHQ = true;
|
||||
}
|
||||
|
||||
if (reader.BaseStream.Position + 3 < endOfStream)
|
||||
{
|
||||
// unk
|
||||
reader.ReadBytes(3);
|
||||
|
||||
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 (this.IsHQ)
|
||||
{
|
||||
itemNameBytes = itemNameBytes.Take(itemNameLen - 4).ToArray();
|
||||
if (this.IsHQ)
|
||||
{
|
||||
// space and HQ symbol
|
||||
bytes.AddRange(new byte[] { 0x20, 0xEE, 0x80, 0xBC });
|
||||
}
|
||||
}
|
||||
|
||||
this.displayName = Encoding.UTF8.GetString(itemNameBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.itemId = GetInteger(reader);
|
||||
|
||||
if (this.itemId > 1000000)
|
||||
{
|
||||
this.itemId -= 1000000;
|
||||
this.IsHQ = true;
|
||||
}
|
||||
|
||||
if (reader.BaseStream.Position + 3 < endOfStream)
|
||||
{
|
||||
// unk
|
||||
reader.ReadBytes(3);
|
||||
|
||||
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 (this.IsHQ)
|
||||
{
|
||||
itemNameBytes = itemNameBytes.Take(itemNameLen - 4).ToArray();
|
||||
}
|
||||
|
||||
this.displayName = Encoding.UTF8.GetString(itemNameBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,231 +5,232 @@ using System.IO;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// An SeString Payload representing an interactable map position link.
|
||||
/// </summary>
|
||||
public class MapLinkPayload : Payload
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
private Map map;
|
||||
private TerritoryType territoryType;
|
||||
private string placeNameRegion;
|
||||
private string placeName;
|
||||
|
||||
[JsonProperty]
|
||||
private uint territoryTypeId;
|
||||
|
||||
[JsonProperty]
|
||||
private uint mapId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapLinkPayload"/> class.
|
||||
/// Creates an interactable MapLinkPayload from a human-readable position.
|
||||
/// An SeString Payload representing an interactable map position link.
|
||||
/// </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)
|
||||
public class MapLinkPayload : Payload
|
||||
{
|
||||
this.territoryTypeId = territoryTypeId;
|
||||
this.mapId = mapId;
|
||||
// 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, this.Map.SizeFactor);
|
||||
this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.SizeFactor);
|
||||
}
|
||||
private Map map;
|
||||
private TerritoryType territoryType;
|
||||
private string placeNameRegion;
|
||||
private string placeName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapLinkPayload"/> class.
|
||||
/// 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;
|
||||
}
|
||||
[JsonProperty]
|
||||
private uint territoryTypeId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapLinkPayload"/> class.
|
||||
/// Creates an interactable MapLinkPayload from a human-readable position.
|
||||
/// </summary>
|
||||
internal MapLinkPayload()
|
||||
{
|
||||
}
|
||||
[JsonProperty]
|
||||
private uint mapId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.MapLink;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Map specified for this map link.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Map Map => this.map ??= this.DataResolver.GetExcelSheet<Map>().GetRow(this.mapId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TerritoryType specified for this map link.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public TerritoryType TerritoryType => this.territoryType ??= this.DataResolver.GetExcelSheet<TerritoryType>().GetRow(this.territoryTypeId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the internal x-coordinate for this map position.
|
||||
/// </summary>
|
||||
public int RawX { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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>
|
||||
/// Gets the readable x-coordinate position for this map link. This value is approximate and unrounded.
|
||||
/// </summary>
|
||||
public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.SizeFactor);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the readable y-coordinate position for this map link. This value is approximate and unrounded.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.SizeFactor);
|
||||
|
||||
// there is no Z; it's purely in the text payload where applicable
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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>
|
||||
[JsonIgnore]
|
||||
public string CoordinateString
|
||||
{
|
||||
get
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapLinkPayload"/> class.
|
||||
/// 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 truncates the values to one decimal without rounding, which is what the game does
|
||||
// the fudge also just attempts to correct the truncated/displayed value for rounding/fp issues
|
||||
// TODO: should this fudge factor be the same as in the ctor? currently not since that is customizable
|
||||
const float fudge = 0.02f;
|
||||
var x = Math.Truncate((this.XCoord + fudge) * 10.0f) / 10.0f;
|
||||
var y = Math.Truncate((this.YCoord + fudge) * 10.0f) / 10.0f;
|
||||
|
||||
// the formatting and spacing the game uses
|
||||
return $"( {x:0.0} , {y:0.0} )";
|
||||
this.territoryTypeId = territoryTypeId;
|
||||
this.mapId = mapId;
|
||||
// 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, this.Map.SizeFactor);
|
||||
this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.SizeFactor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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>
|
||||
[JsonIgnore]
|
||||
public string PlaceNameRegion => this.placeNameRegion ??= this.TerritoryType.PlaceNameRegion.Value?.Name;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapLinkPayload"/> class.
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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>
|
||||
[JsonIgnore]
|
||||
public string PlaceName => this.placeName ??= this.TerritoryType.PlaceName.Value?.Name;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapLinkPayload"/> class.
|
||||
/// Creates an interactable MapLinkPayload from a human-readable position.
|
||||
/// </summary>
|
||||
internal MapLinkPayload()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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:{this.TerritoryType.RowId},{this.Map.RowId},{this.RawX},{this.RawY}";
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.MapLink;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - TerritoryTypeId: {this.territoryTypeId}, MapId: {this.mapId}, RawX: {this.RawX}, RawY: {this.RawY}, display: {this.PlaceName} {this.CoordinateString}";
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the Map specified for this map link.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Map Map => this.map ??= this.DataResolver.GetExcelSheet<Map>().GetRow(this.mapId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
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));
|
||||
/// <summary>
|
||||
/// Gets the TerritoryType specified for this map link.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public TerritoryType TerritoryType => this.territoryType ??= this.DataResolver.GetExcelSheet<TerritoryType>().GetRow(this.territoryTypeId);
|
||||
|
||||
var chunkLen = 4 + packedTerritoryAndMapBytes.Length + xBytes.Length + yBytes.Length;
|
||||
/// <summary>
|
||||
/// Gets the internal x-coordinate for this map position.
|
||||
/// </summary>
|
||||
public int RawX { get; private set; }
|
||||
|
||||
var bytes = new List<byte>()
|
||||
/// <summary>
|
||||
/// Gets 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>
|
||||
/// Gets the readable x-coordinate position for this map link. This value is approximate and unrounded.
|
||||
/// </summary>
|
||||
public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.SizeFactor);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the readable y-coordinate position for this map link. This value is approximate and unrounded.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.SizeFactor);
|
||||
|
||||
// there is no Z; it's purely in the text payload where applicable
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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>
|
||||
[JsonIgnore]
|
||||
public string CoordinateString
|
||||
{
|
||||
get
|
||||
{
|
||||
// this truncates the values to one decimal without rounding, which is what the game does
|
||||
// the fudge also just attempts to correct the truncated/displayed value for rounding/fp issues
|
||||
// TODO: should this fudge factor be the same as in the ctor? currently not since that is customizable
|
||||
const float fudge = 0.02f;
|
||||
var x = Math.Truncate((this.XCoord + fudge) * 10.0f) / 10.0f;
|
||||
var y = Math.Truncate((this.YCoord + fudge) * 10.0f) / 10.0f;
|
||||
|
||||
// the formatting and spacing the game uses
|
||||
return $"( {x:0.0} , {y:0.0} )";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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>
|
||||
[JsonIgnore]
|
||||
public string PlaceNameRegion => this.placeNameRegion ??= this.TerritoryType.PlaceNameRegion.Value?.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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>
|
||||
[JsonIgnore]
|
||||
public string PlaceName => this.placeName ??= this.TerritoryType.PlaceName.Value?.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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:{this.TerritoryType.RowId},{this.Map.RowId},{this.RawX},{this.RawY}";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - TerritoryTypeId: {this.territoryTypeId}, MapId: {this.mapId}, RawX: {this.RawX}, RawY: {this.RawY}, display: {this.PlaceName} {this.CoordinateString}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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 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);
|
||||
bytes.AddRange(packedTerritoryAndMapBytes);
|
||||
bytes.AddRange(xBytes);
|
||||
bytes.AddRange(yBytes);
|
||||
|
||||
// unk
|
||||
bytes.AddRange(new byte[] { 0xFF, 0x01, END_BYTE });
|
||||
// unk
|
||||
bytes.AddRange(new byte[] { 0xFF, 0x01, END_BYTE });
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
// for debugging for now
|
||||
var oldPos = reader.BaseStream.Position;
|
||||
var bytes = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position));
|
||||
reader.BaseStream.Position = oldPos;
|
||||
|
||||
try
|
||||
{
|
||||
(this.territoryTypeId, this.mapId) = GetPackedIntegers(reader);
|
||||
this.RawX = unchecked((int)GetInteger(reader));
|
||||
this.RawY = unchecked((int)GetInteger(reader));
|
||||
// the Z coordinate is never in this chunk, just the text (if applicable)
|
||||
|
||||
// seems to always be FF 01
|
||||
reader.ReadBytes(2);
|
||||
return bytes.ToArray();
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
Serilog.Log.Information($"Unsupported map bytes {BitConverter.ToString(bytes).Replace("-", " ")}");
|
||||
// we still want to break here for now, or we'd just throw again later
|
||||
throw;
|
||||
// for debugging for now
|
||||
var oldPos = reader.BaseStream.Position;
|
||||
var bytes = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position));
|
||||
reader.BaseStream.Position = oldPos;
|
||||
|
||||
try
|
||||
{
|
||||
(this.territoryTypeId, this.mapId) = GetPackedIntegers(reader);
|
||||
this.RawX = unchecked((int)GetInteger(reader));
|
||||
this.RawY = unchecked((int)GetInteger(reader));
|
||||
// the Z coordinate is never in this chunk, just the text (if applicable)
|
||||
|
||||
// seems to always be FF 01
|
||||
reader.ReadBytes(2);
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
Serilog.Log.Information($"Unsupported map bytes {BitConverter.ToString(bytes).Replace("-", " ")}");
|
||||
// we still want to break here for now, or we'd just throw again later
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#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(int pos, float scale)
|
||||
{
|
||||
var c = scale / 100.0f;
|
||||
var scaledPos = 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 int 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)scaledPos;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#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(int pos, float scale)
|
||||
{
|
||||
var c = scale / 100.0f;
|
||||
var scaledPos = 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 int 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)scaledPos;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,34 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// A wrapped newline character.
|
||||
/// </summary>
|
||||
public class NewLinePayload : Payload, ITextProvider
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.NewLine, 0x01, END_BYTE };
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of NewLinePayload.
|
||||
/// A wrapped newline character.
|
||||
/// </summary>
|
||||
public static NewLinePayload Payload => new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text of this payload, evaluates to <c>Environment.NewLine</c>.
|
||||
/// </summary>
|
||||
public string Text => Environment.NewLine;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.NewLine;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl() => this.bytes;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
public class NewLinePayload : Payload, ITextProvider
|
||||
{
|
||||
private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.NewLine, 0x01, END_BYTE };
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of NewLinePayload.
|
||||
/// </summary>
|
||||
public static NewLinePayload Payload => new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text of this payload, evaluates to <c>Environment.NewLine</c>.
|
||||
/// </summary>
|
||||
public string Text => Environment.NewLine;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.NewLine;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl() => this.bytes;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,89 +5,89 @@ using System.Text;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// An SeString Payload representing a player link.
|
||||
/// </summary>
|
||||
public class PlayerPayload : Payload
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
private World world;
|
||||
|
||||
[JsonProperty]
|
||||
private uint serverId;
|
||||
|
||||
[JsonProperty]
|
||||
private string playerName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PlayerPayload"/> class.
|
||||
/// Create a PlayerPayload link for the specified player.
|
||||
/// An SeString Payload representing a player link.
|
||||
/// </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)
|
||||
public class PlayerPayload : Payload
|
||||
{
|
||||
this.playerName = playerName;
|
||||
this.serverId = serverId;
|
||||
}
|
||||
private World world;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PlayerPayload"/> class.
|
||||
/// Create a PlayerPayload link for the specified player.
|
||||
/// </summary>
|
||||
internal PlayerPayload()
|
||||
{
|
||||
}
|
||||
[JsonProperty]
|
||||
private uint serverId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Lumina object representing the player's home server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public World World => this.world ??= this.DataResolver.GetExcelSheet<World>().GetRow(this.serverId);
|
||||
[JsonProperty]
|
||||
private string playerName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the player's displayed name. This does not contain the server name.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string PlayerName
|
||||
{
|
||||
get
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PlayerPayload"/> class.
|
||||
/// 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)
|
||||
{
|
||||
return this.playerName;
|
||||
this.playerName = playerName;
|
||||
this.serverId = serverId;
|
||||
}
|
||||
|
||||
set
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PlayerPayload"/> class.
|
||||
/// Create a PlayerPayload link for the specified player.
|
||||
/// </summary>
|
||||
internal PlayerPayload()
|
||||
{
|
||||
this.playerName = value;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text representation of this player link matching how it might appear in-game.
|
||||
/// The world name will always be present.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.Name}";
|
||||
/// <summary>
|
||||
/// Gets the Lumina object representing the player's home server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public World World => this.world ??= this.DataResolver.GetExcelSheet<World>().GetRow(this.serverId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Player;
|
||||
/// <summary>
|
||||
/// Gets or sets the player's displayed name. This does not contain the server name.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string PlayerName
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.playerName;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.Name}";
|
||||
}
|
||||
set
|
||||
{
|
||||
this.playerName = value;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var chunkLen = this.playerName.Length + 7;
|
||||
var bytes = new List<byte>()
|
||||
/// <summary>
|
||||
/// Gets the text representation of this player link matching how it might appear in-game.
|
||||
/// The world name will always be present.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.Name}";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.Name}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var chunkLen = this.playerName.Length + 7;
|
||||
var bytes = new List<byte>()
|
||||
{
|
||||
START_BYTE,
|
||||
(byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName,
|
||||
|
|
@ -98,38 +98,39 @@ public class PlayerPayload : Payload
|
|||
(byte)(this.playerName.Length + 1),
|
||||
};
|
||||
|
||||
bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName));
|
||||
bytes.Add(END_BYTE);
|
||||
bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName));
|
||||
bytes.Add(END_BYTE);
|
||||
|
||||
// TODO: should these really be here? additional payloads should come in separately already...
|
||||
// TODO: should these really be here? additional payloads should come in separately already...
|
||||
|
||||
// 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(this.playerName).Encode());
|
||||
// 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(this.playerName).Encode());
|
||||
|
||||
// unsure about this entire packet, but it seems to always follow a name
|
||||
bytes.AddRange(new byte[]
|
||||
{
|
||||
// unsure about this entire packet, but it seems to always follow a name
|
||||
bytes.AddRange(new byte[]
|
||||
{
|
||||
START_BYTE, (byte)SeStringChunkType.Interactable, 0x07, (byte)EmbeddedInfoType.LinkTerminator,
|
||||
0x01, 0x01, 0x01, 0xFF, 0x01,
|
||||
END_BYTE,
|
||||
});
|
||||
});
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
// unk
|
||||
reader.ReadByte();
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
// unk
|
||||
reader.ReadByte();
|
||||
|
||||
this.serverId = GetInteger(reader);
|
||||
this.serverId = GetInteger(reader);
|
||||
|
||||
// unk
|
||||
reader.ReadBytes(2);
|
||||
// unk
|
||||
reader.ReadBytes(2);
|
||||
|
||||
var nameLen = (int)GetInteger(reader);
|
||||
this.playerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen));
|
||||
var nameLen = (int)GetInteger(reader);
|
||||
this.playerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,74 +4,75 @@ using System.IO;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// An SeString Payload representing an interactable quest link.
|
||||
/// </summary>
|
||||
public class QuestPayload : Payload
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
private Quest quest;
|
||||
|
||||
[JsonProperty]
|
||||
private uint questId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QuestPayload"/> class.
|
||||
/// Creates a payload representing an interactable quest link for the specified quest.
|
||||
/// An SeString Payload representing an interactable quest link.
|
||||
/// </summary>
|
||||
/// <param name="questId">The id of the quest.</param>
|
||||
public QuestPayload(uint questId)
|
||||
public class QuestPayload : Payload
|
||||
{
|
||||
this.questId = questId;
|
||||
}
|
||||
private Quest quest;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QuestPayload"/> class.
|
||||
/// Creates a payload representing an interactable quest link for the specified quest.
|
||||
/// </summary>
|
||||
internal QuestPayload()
|
||||
{
|
||||
}
|
||||
[JsonProperty]
|
||||
private uint questId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Quest;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QuestPayload"/> class.
|
||||
/// Creates a payload representing an interactable quest link for the specified quest.
|
||||
/// </summary>
|
||||
/// <param name="questId">The id of the quest.</param>
|
||||
public QuestPayload(uint questId)
|
||||
{
|
||||
this.questId = questId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying Lumina Quest represented by this payload.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Quest Quest => this.quest ??= this.DataResolver.GetExcelSheet<Quest>().GetRow(this.questId);
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QuestPayload"/> class.
|
||||
/// Creates a payload representing an interactable quest link for the specified quest.
|
||||
/// </summary>
|
||||
internal QuestPayload()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest?.Name ?? "QUEST NOT FOUND"}";
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Quest;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var idBytes = MakeInteger((ushort)this.questId);
|
||||
var chunkLen = idBytes.Length + 4;
|
||||
/// <summary>
|
||||
/// Gets the underlying Lumina Quest represented by this payload.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Quest Quest => this.quest ??= this.DataResolver.GetExcelSheet<Quest>().GetRow(this.questId);
|
||||
|
||||
var bytes = new List<byte>()
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest?.Name ?? "QUEST NOT FOUND"}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var idBytes = MakeInteger((ushort)this.questId);
|
||||
var chunkLen = idBytes.Length + 4;
|
||||
|
||||
var bytes = new List<byte>()
|
||||
{
|
||||
START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.QuestLink,
|
||||
};
|
||||
|
||||
bytes.AddRange(idBytes);
|
||||
bytes.AddRange(new byte[] { 0x01, 0x01, END_BYTE });
|
||||
return bytes.ToArray();
|
||||
}
|
||||
bytes.AddRange(idBytes);
|
||||
bytes.AddRange(new byte[] { 0x01, 0x01, END_BYTE });
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
// Game uses int16, Luimina uses int32
|
||||
this.questId = GetInteger(reader) + 65536;
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
// Game uses int16, Luimina uses int32
|
||||
this.questId = GetInteger(reader) + 65536;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,115 +5,116 @@ using System.Linq;
|
|||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.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
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
[JsonProperty]
|
||||
private byte chunkType;
|
||||
|
||||
[JsonProperty]
|
||||
private byte[] data;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RawPayload"/> class.
|
||||
/// An SeString Payload representing unhandled raw payload data.
|
||||
/// Mainly useful for constructing unhandled hardcoded payloads, or forwarding any unknown
|
||||
/// payloads without modification.
|
||||
/// </summary>
|
||||
/// <param name="data">The payload data.</param>
|
||||
public RawPayload(byte[] data)
|
||||
public class RawPayload : Payload
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
[JsonProperty]
|
||||
private byte chunkType;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RawPayload"/> class.
|
||||
/// </summary>
|
||||
/// <param name="chunkType">The chunk type.</param>
|
||||
[JsonConstructor]
|
||||
internal RawPayload(byte chunkType)
|
||||
{
|
||||
this.chunkType = chunkType;
|
||||
}
|
||||
[JsonProperty]
|
||||
private byte[] data;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a fixed Payload representing a common link-termination sequence, found in many payload chains.
|
||||
/// </summary>
|
||||
public static RawPayload LinkTerminator => new(new byte[] { 0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03 });
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Unknown;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entire payload byte sequence for this payload.
|
||||
/// The returned data is a clone and modifications will not be persisted.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public 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
|
||||
get
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RawPayload"/> class.
|
||||
/// </summary>
|
||||
/// <param name="data">The payload data.</param>
|
||||
public RawPayload(byte[] data)
|
||||
{
|
||||
// for now don't allow modifying the contents
|
||||
// because we don't really have a way to track Dirty
|
||||
return (byte[])this.Encode().Clone();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is RawPayload rp)
|
||||
{
|
||||
if (rp.Data.Length != this.Data.Length) return false;
|
||||
return !this.Data.Where((t, i) => rp.Data[i] != t).Any();
|
||||
// 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();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RawPayload"/> class.
|
||||
/// </summary>
|
||||
/// <param name="chunkType">The chunk type.</param>
|
||||
[JsonConstructor]
|
||||
internal RawPayload(byte chunkType)
|
||||
{
|
||||
this.chunkType = chunkType;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(this.Type, this.chunkType, this.data);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a fixed Payload representing a common link-termination sequence, found in many payload chains.
|
||||
/// </summary>
|
||||
public static RawPayload LinkTerminator => new(new byte[] { 0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03 });
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - Data: {BitConverter.ToString(this.Data).Replace("-", " ")}";
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Unknown;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var chunkLen = this.data.Length + 1;
|
||||
/// <summary>
|
||||
/// Gets the entire payload byte sequence for this payload.
|
||||
/// The returned data is a clone and modifications will not be persisted.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public 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
|
||||
get
|
||||
{
|
||||
// for now don't allow modifying the contents
|
||||
// because we don't really have a way to track Dirty
|
||||
return (byte[])this.Encode().Clone();
|
||||
}
|
||||
}
|
||||
|
||||
var bytes = new List<byte>()
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is RawPayload rp)
|
||||
{
|
||||
if (rp.Data.Length != this.Data.Length) return false;
|
||||
return !this.Data.Where((t, i) => rp.Data[i] != t).Any();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(this.Type, this.chunkType, this.data);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - Data: {BitConverter.ToString(this.Data).Replace("-", " ")}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var chunkLen = this.data.Length + 1;
|
||||
|
||||
var bytes = new List<byte>()
|
||||
{
|
||||
START_BYTE,
|
||||
this.chunkType,
|
||||
(byte)chunkLen,
|
||||
};
|
||||
bytes.AddRange(this.data);
|
||||
bytes.AddRange(this.data);
|
||||
|
||||
bytes.Add(END_BYTE);
|
||||
bytes.Add(END_BYTE);
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.data = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position + 1));
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.data = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,33 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// A wrapped '–'.
|
||||
/// </summary>
|
||||
public class SeHyphenPayload : Payload, ITextProvider
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.SeHyphen, 0x01, END_BYTE };
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of SeHyphenPayload.
|
||||
/// A wrapped '–'.
|
||||
/// </summary>
|
||||
public static SeHyphenPayload Payload => new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text, just a '–'.
|
||||
/// </summary>
|
||||
public string Text => "–";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.SeHyphen;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override byte[] EncodeImpl() => this.bytes;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
public class SeHyphenPayload : Payload, ITextProvider
|
||||
{
|
||||
private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.SeHyphen, 0x01, END_BYTE };
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of SeHyphenPayload.
|
||||
/// </summary>
|
||||
public static SeHyphenPayload Payload => new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text, just a '–'.
|
||||
/// </summary>
|
||||
public string Text => "–";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.SeHyphen;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override byte[] EncodeImpl() => this.bytes;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,75 +4,76 @@ using System.IO;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// An SeString Payload representing an interactable status link.
|
||||
/// </summary>
|
||||
public class StatusPayload : Payload
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
private Status status;
|
||||
|
||||
[JsonProperty]
|
||||
private uint statusId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StatusPayload"/> class.
|
||||
/// Creates a new StatusPayload for the given status id.
|
||||
/// An SeString Payload representing an interactable status link.
|
||||
/// </summary>
|
||||
/// <param name="statusId">The id of the Status for this link.</param>
|
||||
public StatusPayload(uint statusId)
|
||||
public class StatusPayload : Payload
|
||||
{
|
||||
this.statusId = statusId;
|
||||
}
|
||||
private Status status;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StatusPayload"/> class.
|
||||
/// Creates a new StatusPayload for the given status id.
|
||||
/// </summary>
|
||||
internal StatusPayload()
|
||||
{
|
||||
}
|
||||
[JsonProperty]
|
||||
private uint statusId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Status;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StatusPayload"/> class.
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Lumina Status object represented by this payload.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Status Status => this.status ??= this.DataResolver.GetExcelSheet<Status>().GetRow(this.statusId);
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StatusPayload"/> class.
|
||||
/// Creates a new StatusPayload for the given status id.
|
||||
/// </summary>
|
||||
internal StatusPayload()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.Name}";
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Status;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var idBytes = MakeInteger(this.statusId);
|
||||
/// <summary>
|
||||
/// Gets the Lumina Status object represented by this payload.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Status Status => this.status ??= this.DataResolver.GetExcelSheet<Status>().GetRow(this.statusId);
|
||||
|
||||
var chunkLen = idBytes.Length + 7;
|
||||
var bytes = new List<byte>()
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.Name}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var idBytes = MakeInteger(this.statusId);
|
||||
|
||||
var chunkLen = idBytes.Length + 7;
|
||||
var bytes = new List<byte>()
|
||||
{
|
||||
START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.Status,
|
||||
};
|
||||
|
||||
bytes.AddRange(idBytes);
|
||||
// unk
|
||||
bytes.AddRange(new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x20, END_BYTE });
|
||||
bytes.AddRange(idBytes);
|
||||
// unk
|
||||
bytes.AddRange(new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x20, END_BYTE });
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.statusId = GetInteger(reader);
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.statusId = GetInteger(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,97 +5,98 @@ using System.Text;
|
|||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// An SeString Payload representing a plain text string.
|
||||
/// </summary>
|
||||
public class TextPayload : Payload, ITextProvider
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
[JsonProperty]
|
||||
private string text;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextPayload"/> class.
|
||||
/// Creates a new TextPayload for the given text.
|
||||
/// An SeString Payload representing a plain text string.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to include for this payload.</param>
|
||||
public TextPayload(string text)
|
||||
public class TextPayload : Payload, ITextProvider
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
[JsonProperty]
|
||||
private string text;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextPayload"/> class.
|
||||
/// Creates a new TextPayload for the given text.
|
||||
/// </summary>
|
||||
internal TextPayload()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.RawText;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text contained in this payload.
|
||||
/// This may contain SE's special unicode characters.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string Text
|
||||
{
|
||||
get
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextPayload"/> class.
|
||||
/// Creates a new TextPayload for the given text.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to include for this payload.</param>
|
||||
public TextPayload(string text)
|
||||
{
|
||||
return this.text;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
set
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextPayload"/> class.
|
||||
/// Creates a new TextPayload for the given text.
|
||||
/// </summary>
|
||||
internal TextPayload()
|
||||
{
|
||||
this.text = value;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - Text: {this.Text}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
// special case to allow for empty text payloads, so users don't have to check
|
||||
// this may change or go away
|
||||
if (string.IsNullOrEmpty(this.text))
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetBytes(this.text);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.RawText;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
var textBytes = new List<byte>();
|
||||
|
||||
while (reader.BaseStream.Position < endOfStream)
|
||||
/// <summary>
|
||||
/// Gets or sets the text contained in this payload.
|
||||
/// This may contain SE's special unicode characters.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string Text
|
||||
{
|
||||
var nextByte = reader.ReadByte();
|
||||
if (nextByte == START_BYTE)
|
||||
get
|
||||
{
|
||||
// rewind since this byte isn't part of this payload
|
||||
reader.BaseStream.Position--;
|
||||
break;
|
||||
return this.text;
|
||||
}
|
||||
|
||||
textBytes.Add(nextByte);
|
||||
set
|
||||
{
|
||||
this.text = value;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (textBytes.Count > 0)
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
// TODO: handling of the game's assorted special unicode characters
|
||||
this.text = Encoding.UTF8.GetString(textBytes.ToArray());
|
||||
return $"{this.Type} - Text: {this.Text}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
// special case to allow for empty text payloads, so users don't have to check
|
||||
// this may change or go away
|
||||
if (string.IsNullOrEmpty(this.text))
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetBytes(this.text);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
var textBytes = new List<byte>();
|
||||
|
||||
while (reader.BaseStream.Position < endOfStream)
|
||||
{
|
||||
var nextByte = reader.ReadByte();
|
||||
if (nextByte == START_BYTE)
|
||||
{
|
||||
// rewind since this byte isn't part of this payload
|
||||
reader.BaseStream.Position--;
|
||||
break;
|
||||
}
|
||||
|
||||
textBytes.Add(nextByte);
|
||||
}
|
||||
|
||||
if (textBytes.Count > 0)
|
||||
{
|
||||
// TODO: handling of the game's assorted special unicode characters
|
||||
this.text = Encoding.UTF8.GetString(textBytes.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,110 +4,111 @@ using System.IO;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// An SeString Payload representing a UI foreground color applied to following text payloads.
|
||||
/// </summary>
|
||||
public class UIForegroundPayload : Payload
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
private UIColor color;
|
||||
|
||||
[JsonProperty]
|
||||
private ushort colorKey;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UIForegroundPayload"/> class.
|
||||
/// Creates a new UIForegroundPayload for the given UIColor key.
|
||||
/// An SeString Payload representing a UI foreground color applied to following text payloads.
|
||||
/// </summary>
|
||||
/// <param name="colorKey">A UIColor key.</param>
|
||||
public UIForegroundPayload(ushort colorKey)
|
||||
public class UIForegroundPayload : Payload
|
||||
{
|
||||
this.colorKey = colorKey;
|
||||
}
|
||||
private UIColor color;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UIForegroundPayload"/> class.
|
||||
/// Creates a new UIForegroundPayload for the given UIColor key.
|
||||
/// </summary>
|
||||
internal UIForegroundPayload()
|
||||
{
|
||||
}
|
||||
[JsonProperty]
|
||||
private ushort colorKey;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a payload representing disabling foreground color on following text.
|
||||
/// </summary>
|
||||
// TODO Make this work with DI
|
||||
public static UIForegroundPayload UIForegroundOff => new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.UIForeground;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this payload represents applying a foreground color, or disabling one.
|
||||
/// </summary>
|
||||
public bool IsEnabled => this.ColorKey != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIForeground.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet<UIColor>().GetRow(this.colorKey);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color key used as a lookup in the UIColor table for this foreground color.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ushort ColorKey
|
||||
{
|
||||
get
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UIForegroundPayload"/> class.
|
||||
/// Creates a new UIForegroundPayload for the given UIColor key.
|
||||
/// </summary>
|
||||
/// <param name="colorKey">A UIColor key.</param>
|
||||
public UIForegroundPayload(ushort colorKey)
|
||||
{
|
||||
return this.colorKey;
|
||||
this.colorKey = colorKey;
|
||||
}
|
||||
|
||||
set
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UIForegroundPayload"/> class.
|
||||
/// Creates a new UIForegroundPayload for the given UIColor key.
|
||||
/// </summary>
|
||||
internal UIForegroundPayload()
|
||||
{
|
||||
this.colorKey = value;
|
||||
this.color = null;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Red/Green/Blue values for this foreground color, encoded as a typical hex color.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint RGB => this.UIColor.UIForeground & 0xFFFFFF;
|
||||
/// <summary>
|
||||
/// Gets a payload representing disabling foreground color on following text.
|
||||
/// </summary>
|
||||
// TODO Make this work with DI
|
||||
public static UIForegroundPayload UIForegroundOff => new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}";
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.UIForeground;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var colorBytes = MakeInteger(this.colorKey);
|
||||
var chunkLen = colorBytes.Length + 1;
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this payload represents applying a foreground color, or disabling one.
|
||||
/// </summary>
|
||||
public bool IsEnabled => this.ColorKey != 0;
|
||||
|
||||
var bytes = new List<byte>(new byte[]
|
||||
/// <summary>
|
||||
/// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIForeground.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet<UIColor>().GetRow(this.colorKey);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color key used as a lookup in the UIColor table for this foreground color.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ushort ColorKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.colorKey;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.colorKey = value;
|
||||
this.color = null;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Red/Green/Blue values for this foreground color, encoded as a typical hex color.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint RGB => this.UIColor.UIForeground & 0xFFFFFF;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var colorBytes = MakeInteger(this.colorKey);
|
||||
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);
|
||||
bytes.AddRange(colorBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.colorKey = (ushort)GetInteger(reader);
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.colorKey = (ushort)GetInteger(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,110 +4,111 @@ using System.IO;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
/// <summary>
|
||||
/// An SeString Payload representing a UI glow color applied to following text payloads.
|
||||
/// </summary>
|
||||
public class UIGlowPayload : Payload
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||
{
|
||||
private UIColor color;
|
||||
|
||||
[JsonProperty]
|
||||
private ushort colorKey;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UIGlowPayload"/> class.
|
||||
/// Creates a new UIForegroundPayload for the given UIColor key.
|
||||
/// An SeString Payload representing a UI glow color applied to following text payloads.
|
||||
/// </summary>
|
||||
/// <param name="colorKey">A UIColor key.</param>
|
||||
public UIGlowPayload(ushort colorKey)
|
||||
public class UIGlowPayload : Payload
|
||||
{
|
||||
this.colorKey = colorKey;
|
||||
}
|
||||
private UIColor color;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UIGlowPayload"/> class.
|
||||
/// Creates a new UIForegroundPayload for the given UIColor key.
|
||||
/// </summary>
|
||||
internal UIGlowPayload()
|
||||
{
|
||||
}
|
||||
[JsonProperty]
|
||||
private ushort colorKey;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a payload representing disabling glow color on following text.
|
||||
/// </summary>
|
||||
// TODO Make this work with DI
|
||||
public static UIGlowPayload UIGlowOff => new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.UIGlow;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color key used as a lookup in the UIColor table for this glow color.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ushort ColorKey
|
||||
{
|
||||
get
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UIGlowPayload"/> class.
|
||||
/// Creates a new UIForegroundPayload for the given UIColor key.
|
||||
/// </summary>
|
||||
/// <param name="colorKey">A UIColor key.</param>
|
||||
public UIGlowPayload(ushort colorKey)
|
||||
{
|
||||
return this.colorKey;
|
||||
this.colorKey = colorKey;
|
||||
}
|
||||
|
||||
set
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UIGlowPayload"/> class.
|
||||
/// Creates a new UIForegroundPayload for the given UIColor key.
|
||||
/// </summary>
|
||||
internal UIGlowPayload()
|
||||
{
|
||||
this.colorKey = value;
|
||||
this.color = null;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this payload represents applying a glow color, or disabling one.
|
||||
/// </summary>
|
||||
public bool IsEnabled => this.ColorKey != 0;
|
||||
/// <summary>
|
||||
/// Gets a payload representing disabling glow color on following text.
|
||||
/// </summary>
|
||||
// TODO Make this work with DI
|
||||
public static UIGlowPayload UIGlowOff => new(0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Red/Green/Blue values for this glow color, encoded as a typical hex color.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint RGB => this.UIColor.UIGlow & 0xFFFFFF;
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.UIGlow;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet<UIColor>().GetRow(this.colorKey);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var colorBytes = MakeInteger(this.colorKey);
|
||||
var chunkLen = colorBytes.Length + 1;
|
||||
|
||||
var bytes = new List<byte>(new byte[]
|
||||
/// <summary>
|
||||
/// Gets or sets the color key used as a lookup in the UIColor table for this glow color.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ushort ColorKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.colorKey;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.colorKey = value;
|
||||
this.color = null;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this payload represents applying a glow color, or disabling one.
|
||||
/// </summary>
|
||||
public bool IsEnabled => this.ColorKey != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Red/Green/Blue values for this glow color, encoded as a typical hex color.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint RGB => this.UIColor.UIGlow & 0xFFFFFF;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet<UIColor>().GetRow(this.colorKey);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var colorBytes = MakeInteger(this.colorKey);
|
||||
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);
|
||||
bytes.AddRange(colorBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.colorKey = (ushort)GetInteger(reader);
|
||||
/// <inheritdoc/>
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
|
||||
{
|
||||
this.colorKey = (ushort)GetInteger(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,360 +10,361 @@ using Dalamud.Utility;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a parsed SeString.
|
||||
/// </summary>
|
||||
public class SeString
|
||||
namespace Dalamud.Game.Text.SeStringHandling
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeString"/> class.
|
||||
/// Creates a new SeString from an ordered list of payloads.
|
||||
/// This class represents a parsed SeString.
|
||||
/// </summary>
|
||||
public SeString()
|
||||
public class SeString
|
||||
{
|
||||
this.Payloads = new List<Payload>();
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeString"/> class.
|
||||
/// Creates a new SeString from an ordered list of payloads.
|
||||
/// </summary>
|
||||
public SeString()
|
||||
{
|
||||
this.Payloads = new List<Payload>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeString"/> class.
|
||||
/// Creates a new SeString from an ordered list of payloads.
|
||||
/// </summary>
|
||||
/// <param name="payloads">The Payload objects to make up this string.</param>
|
||||
[JsonConstructor]
|
||||
public SeString(List<Payload> payloads)
|
||||
{
|
||||
this.Payloads = payloads;
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeString"/> class.
|
||||
/// Creates a new SeString from an ordered list of payloads.
|
||||
/// </summary>
|
||||
/// <param name="payloads">The Payload objects to make up this string.</param>
|
||||
[JsonConstructor]
|
||||
public SeString(List<Payload> payloads)
|
||||
{
|
||||
this.Payloads = payloads;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeString"/> class.
|
||||
/// 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(params Payload[] payloads)
|
||||
{
|
||||
this.Payloads = new List<Payload>(payloads);
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeString"/> class.
|
||||
/// 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(params Payload[] payloads)
|
||||
{
|
||||
this.Payloads = new List<Payload>(payloads);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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 IEnumerable<Payload> TextArrowPayloads => new List<Payload>(new Payload[]
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets 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 IEnumerable<Payload> TextArrowPayloads => new List<Payload>(new Payload[]
|
||||
{
|
||||
new UIForegroundPayload(0x01F4),
|
||||
new UIGlowPayload(0x01F5),
|
||||
new TextPayload($"{(char)SeIconChar.LinkMarker}"),
|
||||
UIGlowPayload.UIGlowOff,
|
||||
UIForegroundPayload.UIForegroundOff,
|
||||
});
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty SeString.
|
||||
/// </summary>
|
||||
public static SeString Empty => new();
|
||||
/// <summary>
|
||||
/// Gets an empty SeString.
|
||||
/// </summary>
|
||||
public static SeString Empty => new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ordered list of payloads included in this SeString.
|
||||
/// </summary>
|
||||
public List<Payload> Payloads { get; }
|
||||
/// <summary>
|
||||
/// Gets the ordered list of payloads included in this SeString.
|
||||
/// </summary>
|
||||
public List<Payload> Payloads { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all of the raw text from a message as a single joined string.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// All the raw text from the contained payloads, joined into a single string.
|
||||
/// </returns>
|
||||
public string TextValue
|
||||
{
|
||||
get
|
||||
/// <summary>
|
||||
/// Gets all of the raw text from a message as a single joined string.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// All the raw text from the contained payloads, joined into a single string.
|
||||
/// </returns>
|
||||
public string TextValue
|
||||
{
|
||||
return this.Payloads
|
||||
.Where(p => p is ITextProvider)
|
||||
.Cast<ITextProvider>()
|
||||
.Aggregate(new StringBuilder(), (sb, tp) => sb.Append(tp.Text), sb => sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert a string into a SeString containing a <see cref="TextPayload"/>.
|
||||
/// </summary>
|
||||
/// <param name="str">string to convert.</param>
|
||||
/// <returns>Equivalent SeString.</returns>
|
||||
public static implicit operator SeString(string str) => new(new TextPayload(str));
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert a string into a SeString containing a <see cref="TextPayload"/>.
|
||||
/// </summary>
|
||||
/// <param name="str">string to convert.</param>
|
||||
/// <returns>Equivalent SeString.</returns>
|
||||
public static explicit operator SeString(Lumina.Text.SeString str) => str.ToDalamudString();
|
||||
|
||||
/// <summary>
|
||||
/// Parse a binary game message into an SeString.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to the string's data in memory.</param>
|
||||
/// <param name="len">Length of the string's data in memory.</param>
|
||||
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
|
||||
public static unsafe SeString Parse(byte* ptr, int len)
|
||||
{
|
||||
if (ptr == null)
|
||||
return Empty;
|
||||
|
||||
var payloads = new List<Payload>();
|
||||
|
||||
using (var stream = new UnmanagedMemoryStream(ptr, len))
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
while (stream.Position < len)
|
||||
get
|
||||
{
|
||||
var payload = Payload.Decode(reader);
|
||||
if (payload != null)
|
||||
payloads.Add(payload);
|
||||
return this.Payloads
|
||||
.Where(p => p is ITextProvider)
|
||||
.Cast<ITextProvider>()
|
||||
.Aggregate(new StringBuilder(), (sb, tp) => sb.Append(tp.Text), sb => sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return new SeString(payloads);
|
||||
}
|
||||
/// <summary>
|
||||
/// Implicitly convert a string into a SeString containing a <see cref="TextPayload"/>.
|
||||
/// </summary>
|
||||
/// <param name="str">string to convert.</param>
|
||||
/// <returns>Equivalent SeString.</returns>
|
||||
public static implicit operator SeString(string str) => new(new TextPayload(str));
|
||||
|
||||
/// <summary>
|
||||
/// Parse a binary game message into an SeString.
|
||||
/// </summary>
|
||||
/// <param name="data">Binary message payload data in SE's internal format.</param>
|
||||
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
|
||||
public static unsafe SeString Parse(ReadOnlySpan<byte> data)
|
||||
{
|
||||
fixed (byte* ptr = data)
|
||||
/// <summary>
|
||||
/// Implicitly convert a string into a SeString containing a <see cref="TextPayload"/>.
|
||||
/// </summary>
|
||||
/// <param name="str">string to convert.</param>
|
||||
/// <returns>Equivalent SeString.</returns>
|
||||
public static explicit operator SeString(Lumina.Text.SeString str) => str.ToDalamudString();
|
||||
|
||||
/// <summary>
|
||||
/// Parse a binary game message into an SeString.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to the string's data in memory.</param>
|
||||
/// <param name="len">Length of the string's data in memory.</param>
|
||||
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
|
||||
public static unsafe SeString Parse(byte* ptr, int len)
|
||||
{
|
||||
return Parse(ptr, data.Length);
|
||||
}
|
||||
}
|
||||
if (ptr == null)
|
||||
return Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Parse a binary game message into an SeString.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Binary message payload data in SE's internal format.</param>
|
||||
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
|
||||
public static SeString Parse(byte[] bytes) => Parse(new ReadOnlySpan<byte>(bytes));
|
||||
var payloads = new List<Payload>();
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var data = Service<DataManager>.Get();
|
||||
using (var stream = new UnmanagedMemoryStream(ptr, len))
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
while (stream.Position < len)
|
||||
{
|
||||
var payload = Payload.Decode(reader);
|
||||
if (payload != null)
|
||||
payloads.Add(payload);
|
||||
}
|
||||
}
|
||||
|
||||
var displayName = displayNameOverride ?? data.GetExcelSheet<Item>()?.GetRow(itemId)?.Name;
|
||||
if (isHq)
|
||||
{
|
||||
displayName += $" {(char)SeIconChar.HighQuality}";
|
||||
return new SeString(payloads);
|
||||
}
|
||||
|
||||
// TODO: probably a cleaner way to build these than doing the bulk+insert
|
||||
var payloads = new List<Payload>(new Payload[]
|
||||
/// <summary>
|
||||
/// Parse a binary game message into an SeString.
|
||||
/// </summary>
|
||||
/// <param name="data">Binary message payload data in SE's internal format.</param>
|
||||
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
|
||||
public static unsafe SeString Parse(ReadOnlySpan<byte> data)
|
||||
{
|
||||
fixed (byte* ptr = data)
|
||||
{
|
||||
return Parse(ptr, data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a binary game message into an SeString.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Binary message payload data in SE's internal format.</param>
|
||||
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
|
||||
public static SeString Parse(byte[] bytes) => Parse(new ReadOnlySpan<byte>(bytes));
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var data = Service<DataManager>.Get();
|
||||
|
||||
var displayName = displayNameOverride ?? data.GetExcelSheet<Item>()?.GetRow(itemId)?.Name;
|
||||
if (isHq)
|
||||
{
|
||||
displayName += $" {(char)SeIconChar.HighQuality}";
|
||||
}
|
||||
|
||||
// TODO: probably a cleaner way to build these than doing the bulk+insert
|
||||
var payloads = new List<Payload>(new Payload[]
|
||||
{
|
||||
new UIForegroundPayload(0x0225),
|
||||
new UIGlowPayload(0x0226),
|
||||
new ItemPayload(itemId, isHq),
|
||||
// arrow goes here
|
||||
new TextPayload(displayName),
|
||||
RawPayload.LinkTerminator,
|
||||
// sometimes there is another set of uiglow/foreground off payloads here
|
||||
// might be necessary when including additional text after the item name
|
||||
});
|
||||
payloads.InsertRange(3, TextArrowPayloads);
|
||||
// sometimes there is another set of uiglow/foreground off payloads here
|
||||
// might be necessary when including additional text after the item name
|
||||
});
|
||||
payloads.InsertRange(3, TextArrowPayloads);
|
||||
|
||||
return new SeString(payloads);
|
||||
}
|
||||
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(Item item, bool isHq, string? displayNameOverride = null)
|
||||
{
|
||||
return CreateItemLink(item.RowId, isHq, displayNameOverride ?? item.Name);
|
||||
}
|
||||
|
||||
/// <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="rawX">The raw x-coordinate for this link.</param>
|
||||
/// <param name="rawY">The raw y-coordinate for this link..</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, 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[]
|
||||
/// <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(Item item, bool isHq, string? displayNameOverride = null)
|
||||
{
|
||||
mapPayload,
|
||||
// arrow goes here
|
||||
new TextPayload(nameString),
|
||||
RawPayload.LinkTerminator,
|
||||
});
|
||||
payloads.InsertRange(1, TextArrowPayloads);
|
||||
return CreateItemLink(item.RowId, isHq, displayNameOverride ?? item.Name);
|
||||
}
|
||||
|
||||
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);
|
||||
var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}";
|
||||
|
||||
var payloads = new List<Payload>(new Payload[]
|
||||
/// <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="rawX">The raw x-coordinate for this link.</param>
|
||||
/// <param name="rawY">The raw y-coordinate for this link..</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, int rawX, int rawY)
|
||||
{
|
||||
mapPayload,
|
||||
// arrow goes here
|
||||
new TextPayload(nameString),
|
||||
RawPayload.LinkTerminator,
|
||||
});
|
||||
payloads.InsertRange(1, TextArrowPayloads);
|
||||
var mapPayload = new MapLinkPayload(territoryId, mapId, rawX, rawY);
|
||||
var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}";
|
||||
|
||||
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.
|
||||
/// Returns null if no corresponding PlaceName was found.
|
||||
/// </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 data = Service<DataManager>.Get();
|
||||
|
||||
var mapSheet = data.GetExcelSheet<Map>();
|
||||
|
||||
var matches = data.GetExcelSheet<PlaceName>()
|
||||
.Where(row => row.Name.ToString().ToLowerInvariant() == placeName.ToLowerInvariant())
|
||||
.ToArray();
|
||||
|
||||
foreach (var place in matches)
|
||||
{
|
||||
var map = mapSheet.FirstOrDefault(row => row.PlaceName.Row == place.RowId);
|
||||
if (map != null)
|
||||
var payloads = new List<Payload>(new Payload[]
|
||||
{
|
||||
return CreateMapLink(map.TerritoryType.Row, map.RowId, xCoord, yCoord, fudgeFactor);
|
||||
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);
|
||||
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, matching a specified zone name.
|
||||
/// Returns null if no corresponding PlaceName was found.
|
||||
/// </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 data = Service<DataManager>.Get();
|
||||
|
||||
var mapSheet = data.GetExcelSheet<Map>();
|
||||
|
||||
var matches = data.GetExcelSheet<PlaceName>()
|
||||
.Where(row => row.Name.ToString().ToLowerInvariant() == placeName.ToLowerInvariant())
|
||||
.ToArray();
|
||||
|
||||
foreach (var place in matches)
|
||||
{
|
||||
var map = mapSheet.FirstOrDefault(row => row.PlaceName.Row == place.RowId);
|
||||
if (map != null)
|
||||
{
|
||||
return CreateMapLink(map.TerritoryType.Row, map.RowId, xCoord, yCoord, fudgeFactor);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: empty? throw?
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: empty? throw?
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SeString from a json. (For testing - not recommended for production use.)
|
||||
/// </summary>
|
||||
/// <param name="json">A serialized SeString produced by ToJson() <see cref="ToJson"/>.</param>
|
||||
/// <returns>A SeString initialized with values from the json.</returns>
|
||||
public static SeString? FromJson(string json)
|
||||
{
|
||||
var s = JsonConvert.DeserializeObject<SeString>(json, new JsonSerializerSettings
|
||||
/// <summary>
|
||||
/// Creates a SeString from a json. (For testing - not recommended for production use.)
|
||||
/// </summary>
|
||||
/// <param name="json">A serialized SeString produced by ToJson() <see cref="ToJson"/>.</param>
|
||||
/// <returns>A SeString initialized with values from the json.</returns>
|
||||
public static SeString? FromJson(string json)
|
||||
{
|
||||
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
|
||||
TypeNameHandling = TypeNameHandling.Auto,
|
||||
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
|
||||
});
|
||||
var s = JsonConvert.DeserializeObject<SeString>(json, new JsonSerializerSettings
|
||||
{
|
||||
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
|
||||
TypeNameHandling = TypeNameHandling.Auto,
|
||||
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
|
||||
});
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the SeString to json.
|
||||
/// </summary>
|
||||
/// <returns>An json representation of this object.</returns>
|
||||
public string ToJson()
|
||||
{
|
||||
return JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings()
|
||||
{
|
||||
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
||||
TypeNameHandling = TypeNameHandling.Auto,
|
||||
});
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
this.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)
|
||||
{
|
||||
this.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)
|
||||
{
|
||||
this.Payloads.Add(payload);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <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 this.Payloads)
|
||||
{
|
||||
messageBytes.AddRange(p.Encode());
|
||||
return s;
|
||||
}
|
||||
|
||||
return messageBytes.ToArray();
|
||||
}
|
||||
/// <summary>
|
||||
/// Serializes the SeString to json.
|
||||
/// </summary>
|
||||
/// <returns>An json representation of this object.</returns>
|
||||
public string ToJson()
|
||||
{
|
||||
return JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings()
|
||||
{
|
||||
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
||||
TypeNameHandling = TypeNameHandling.Auto,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the text value of this SeString.
|
||||
/// </summary>
|
||||
/// <returns>The TextValue property.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return this.TextValue;
|
||||
/// <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)
|
||||
{
|
||||
this.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)
|
||||
{
|
||||
this.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)
|
||||
{
|
||||
this.Payloads.Add(payload);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <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 this.Payloads)
|
||||
{
|
||||
messageBytes.AddRange(p.Encode());
|
||||
}
|
||||
|
||||
return messageBytes.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the text value of this SeString.
|
||||
/// </summary>
|
||||
/// <returns>The TextValue property.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return this.TextValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,108 +9,109 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
/// <summary>
|
||||
/// This class facilitates creating new SeStrings and breaking down existing ones into their individual payload components.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[Obsolete("This class is obsolete. Please use the static methods on SeString instead.")]
|
||||
public sealed class SeStringManager
|
||||
namespace Dalamud.Game.Text.SeStringHandling
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringManager"/> class.
|
||||
/// This class facilitates creating new SeStrings and breaking down existing ones into their individual payload components.
|
||||
/// </summary>
|
||||
internal SeStringManager()
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[Obsolete("This class is obsolete. Please use the static methods on SeString instead.")]
|
||||
public sealed class SeStringManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringManager"/> class.
|
||||
/// </summary>
|
||||
internal SeStringManager()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a binary game message into an SeString.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to the string's data in memory.</param>
|
||||
/// <param name="len">Length of the string's data in memory.</param>
|
||||
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public unsafe SeString Parse(byte* ptr, int len) => SeString.Parse(ptr, len);
|
||||
|
||||
/// <summary>
|
||||
/// Parse a binary game message into an SeString.
|
||||
/// </summary>
|
||||
/// <param name="data">Binary message payload data in SE's internal format.</param>
|
||||
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public unsafe SeString Parse(ReadOnlySpan<byte> data) => SeString.Parse(data);
|
||||
|
||||
/// <summary>
|
||||
/// Parse a binary game message into an SeString.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Binary message payload data in SE's internal format.</param>
|
||||
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public SeString Parse(byte[] bytes) => SeString.Parse(new ReadOnlySpan<byte>(bytes));
|
||||
|
||||
/// <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>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public SeString CreateItemLink(uint itemId, bool isHQ, string displayNameOverride = null) => SeString.CreateItemLink(itemId, isHQ, displayNameOverride);
|
||||
|
||||
/// <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>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public SeString CreateItemLink(Item item, bool isHQ, string displayNameOverride = null) => SeString.CreateItemLink(item, isHQ, displayNameOverride);
|
||||
|
||||
/// <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="rawX">The raw x-coordinate for this link.</param>
|
||||
/// <param name="rawY">The raw y-coordinate for this link..</param>
|
||||
/// <returns>An SeString containing all of the payloads necessary to display a map link in the chat log.</returns>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY) =>
|
||||
SeString.CreateMapLink(territoryId, mapId, rawX, rawY);
|
||||
|
||||
/// <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>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) => SeString.CreateMapLink(territoryId, mapId, xCoord, yCoord, fudgeFactor);
|
||||
|
||||
/// <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>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public SeString CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) => SeString.CreateMapLink(placeName, xCoord, yCoord, fudgeFactor);
|
||||
|
||||
/// <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>
|
||||
[Obsolete("This data is obsolete. Please use the static version on SeString instead.", true)]
|
||||
public List<Payload> TextArrowPayloads() => new(SeString.TextArrowPayloads);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a binary game message into an SeString.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to the string's data in memory.</param>
|
||||
/// <param name="len">Length of the string's data in memory.</param>
|
||||
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public unsafe SeString Parse(byte* ptr, int len) => SeString.Parse(ptr, len);
|
||||
|
||||
/// <summary>
|
||||
/// Parse a binary game message into an SeString.
|
||||
/// </summary>
|
||||
/// <param name="data">Binary message payload data in SE's internal format.</param>
|
||||
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public unsafe SeString Parse(ReadOnlySpan<byte> data) => SeString.Parse(data);
|
||||
|
||||
/// <summary>
|
||||
/// Parse a binary game message into an SeString.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Binary message payload data in SE's internal format.</param>
|
||||
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public SeString Parse(byte[] bytes) => SeString.Parse(new ReadOnlySpan<byte>(bytes));
|
||||
|
||||
/// <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>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public SeString CreateItemLink(uint itemId, bool isHQ, string displayNameOverride = null) => SeString.CreateItemLink(itemId, isHQ, displayNameOverride);
|
||||
|
||||
/// <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>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public SeString CreateItemLink(Item item, bool isHQ, string displayNameOverride = null) => SeString.CreateItemLink(item, isHQ, displayNameOverride);
|
||||
|
||||
/// <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="rawX">The raw x-coordinate for this link.</param>
|
||||
/// <param name="rawY">The raw y-coordinate for this link..</param>
|
||||
/// <returns>An SeString containing all of the payloads necessary to display a map link in the chat log.</returns>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY) =>
|
||||
SeString.CreateMapLink(territoryId, mapId, rawX, rawY);
|
||||
|
||||
/// <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>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) => SeString.CreateMapLink(territoryId, mapId, xCoord, yCoord, fudgeFactor);
|
||||
|
||||
/// <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>
|
||||
[Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)]
|
||||
public SeString CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) => SeString.CreateMapLink(placeName, xCoord, yCoord, fudgeFactor);
|
||||
|
||||
/// <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>
|
||||
[Obsolete("This data is obsolete. Please use the static version on SeString instead.", true)]
|
||||
public List<Payload> TextArrowPayloads() => new(SeString.TextArrowPayloads);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,35 +2,36 @@ using System;
|
|||
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace Dalamud.Game.Text;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a single chat log entry.
|
||||
/// </summary>
|
||||
public sealed class XivChatEntry
|
||||
namespace Dalamud.Game.Text
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the type of entry.
|
||||
/// This class represents a single chat log entry.
|
||||
/// </summary>
|
||||
public XivChatType Type { get; set; } = XivChatType.Debug;
|
||||
public sealed class XivChatEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the type of entry.
|
||||
/// </summary>
|
||||
public XivChatType Type { get; set; } = XivChatType.Debug;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sender ID.
|
||||
/// </summary>
|
||||
public uint SenderId { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the sender ID.
|
||||
/// </summary>
|
||||
public uint SenderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sender name.
|
||||
/// </summary>
|
||||
public SeString Name { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Gets or sets the sender name.
|
||||
/// </summary>
|
||||
public SeString Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message.
|
||||
/// </summary>
|
||||
public SeString Message { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Gets or sets the message.
|
||||
/// </summary>
|
||||
public SeString Message { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message parameters.
|
||||
/// </summary>
|
||||
public IntPtr Parameters { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the message parameters.
|
||||
/// </summary>
|
||||
public IntPtr Parameters { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,236 +1,237 @@
|
|||
namespace Dalamud.Game.Text;
|
||||
|
||||
/// <summary>
|
||||
/// The FFXIV chat types as seen in the LogKind ex table.
|
||||
/// </summary>
|
||||
public enum XivChatType : ushort // FIXME: this is a single byte
|
||||
namespace Dalamud.Game.Text
|
||||
{
|
||||
/// <summary>
|
||||
/// No chat type.
|
||||
/// The FFXIV chat types as seen in the LogKind ex table.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
public enum XivChatType : ushort // FIXME: this is a single byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No chat type.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The debug chat type.
|
||||
/// </summary>
|
||||
Debug = 1,
|
||||
/// <summary>
|
||||
/// The debug chat type.
|
||||
/// </summary>
|
||||
Debug = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The urgent chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Urgent", "urgent", 0xFF9400D3)]
|
||||
Urgent = 2,
|
||||
/// <summary>
|
||||
/// The urgent chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Urgent", "urgent", 0xFF9400D3)]
|
||||
Urgent = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The notice chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Notice", "notice", 0xFF9400D3)]
|
||||
Notice = 3,
|
||||
/// <summary>
|
||||
/// The notice chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Notice", "notice", 0xFF9400D3)]
|
||||
Notice = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The say chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Say", "say", 0xFFFFFFFF)]
|
||||
Say = 10,
|
||||
/// <summary>
|
||||
/// The say chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Say", "say", 0xFFFFFFFF)]
|
||||
Say = 10,
|
||||
|
||||
/// <summary>
|
||||
/// The shout chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Shout", "shout", 0xFFFF4500)]
|
||||
Shout = 11,
|
||||
/// <summary>
|
||||
/// The shout chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Shout", "shout", 0xFFFF4500)]
|
||||
Shout = 11,
|
||||
|
||||
/// <summary>
|
||||
/// The outgoing tell chat type.
|
||||
/// </summary>
|
||||
TellOutgoing = 12,
|
||||
/// <summary>
|
||||
/// The outgoing tell chat type.
|
||||
/// </summary>
|
||||
TellOutgoing = 12,
|
||||
|
||||
/// <summary>
|
||||
/// The incoming tell chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Tell", "tell", 0xFFFF69B4)]
|
||||
TellIncoming = 13,
|
||||
/// <summary>
|
||||
/// The incoming tell chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Tell", "tell", 0xFFFF69B4)]
|
||||
TellIncoming = 13,
|
||||
|
||||
/// <summary>
|
||||
/// The party chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Party", "party", 0xFF1E90FF)]
|
||||
Party = 14,
|
||||
/// <summary>
|
||||
/// The party chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Party", "party", 0xFF1E90FF)]
|
||||
Party = 14,
|
||||
|
||||
/// <summary>
|
||||
/// The alliance chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Alliance", "alliance", 0xFFFF4500)]
|
||||
Alliance = 15,
|
||||
/// <summary>
|
||||
/// The alliance chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Alliance", "alliance", 0xFFFF4500)]
|
||||
Alliance = 15,
|
||||
|
||||
/// <summary>
|
||||
/// The linkshell 1 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 1", "ls1", 0xFF228B22)]
|
||||
Ls1 = 16,
|
||||
/// <summary>
|
||||
/// The linkshell 1 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 1", "ls1", 0xFF228B22)]
|
||||
Ls1 = 16,
|
||||
|
||||
/// <summary>
|
||||
/// The linkshell 2 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 2", "ls2", 0xFF228B22)]
|
||||
Ls2 = 17,
|
||||
/// <summary>
|
||||
/// The linkshell 2 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 2", "ls2", 0xFF228B22)]
|
||||
Ls2 = 17,
|
||||
|
||||
/// <summary>
|
||||
/// The linkshell 3 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 3", "ls3", 0xFF228B22)]
|
||||
Ls3 = 18,
|
||||
/// <summary>
|
||||
/// The linkshell 3 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 3", "ls3", 0xFF228B22)]
|
||||
Ls3 = 18,
|
||||
|
||||
/// <summary>
|
||||
/// The linkshell 4 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 4", "ls4", 0xFF228B22)]
|
||||
Ls4 = 19,
|
||||
/// <summary>
|
||||
/// The linkshell 4 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 4", "ls4", 0xFF228B22)]
|
||||
Ls4 = 19,
|
||||
|
||||
/// <summary>
|
||||
/// The linkshell 5 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 5", "ls5", 0xFF228B22)]
|
||||
Ls5 = 20,
|
||||
/// <summary>
|
||||
/// The linkshell 5 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 5", "ls5", 0xFF228B22)]
|
||||
Ls5 = 20,
|
||||
|
||||
/// <summary>
|
||||
/// The linkshell 6 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 6", "ls6", 0xFF228B22)]
|
||||
Ls6 = 21,
|
||||
/// <summary>
|
||||
/// The linkshell 6 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 6", "ls6", 0xFF228B22)]
|
||||
Ls6 = 21,
|
||||
|
||||
/// <summary>
|
||||
/// The linkshell 7 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 7", "ls7", 0xFF228B22)]
|
||||
Ls7 = 22,
|
||||
/// <summary>
|
||||
/// The linkshell 7 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 7", "ls7", 0xFF228B22)]
|
||||
Ls7 = 22,
|
||||
|
||||
/// <summary>
|
||||
/// The linkshell 8 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 8", "ls8", 0xFF228B22)]
|
||||
Ls8 = 23,
|
||||
/// <summary>
|
||||
/// The linkshell 8 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Linkshell 8", "ls8", 0xFF228B22)]
|
||||
Ls8 = 23,
|
||||
|
||||
/// <summary>
|
||||
/// The free company chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Free Company", "fc", 0xFF00BFFF)]
|
||||
FreeCompany = 24,
|
||||
/// <summary>
|
||||
/// The free company chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Free Company", "fc", 0xFF00BFFF)]
|
||||
FreeCompany = 24,
|
||||
|
||||
/// <summary>
|
||||
/// The novice network chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Novice Network", "nn", 0xFF8B4513)]
|
||||
NoviceNetwork = 27,
|
||||
/// <summary>
|
||||
/// The novice network chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Novice Network", "nn", 0xFF8B4513)]
|
||||
NoviceNetwork = 27,
|
||||
|
||||
/// <summary>
|
||||
/// The custom emotes chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Custom Emotes", "emote", 0xFF8B4513)]
|
||||
CustomEmote = 28,
|
||||
/// <summary>
|
||||
/// The custom emotes chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Custom Emotes", "emote", 0xFF8B4513)]
|
||||
CustomEmote = 28,
|
||||
|
||||
/// <summary>
|
||||
/// The standard emotes chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Standard Emotes", "emote", 0xFF8B4513)]
|
||||
StandardEmote = 29,
|
||||
/// <summary>
|
||||
/// The standard emotes chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Standard Emotes", "emote", 0xFF8B4513)]
|
||||
StandardEmote = 29,
|
||||
|
||||
/// <summary>
|
||||
/// The yell chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Yell", "yell", 0xFFFFFF00)]
|
||||
Yell = 30,
|
||||
/// <summary>
|
||||
/// The yell chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Yell", "yell", 0xFFFFFF00)]
|
||||
Yell = 30,
|
||||
|
||||
/// <summary>
|
||||
/// The cross-world party chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Party", "party", 0xFF1E90FF)]
|
||||
CrossParty = 32,
|
||||
/// <summary>
|
||||
/// The cross-world party chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Party", "party", 0xFF1E90FF)]
|
||||
CrossParty = 32,
|
||||
|
||||
/// <summary>
|
||||
/// The PvP team chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("PvP Team", "pvpt", 0xFFF4A460)]
|
||||
PvPTeam = 36,
|
||||
/// <summary>
|
||||
/// The PvP team chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("PvP Team", "pvpt", 0xFFF4A460)]
|
||||
PvPTeam = 36,
|
||||
|
||||
/// <summary>
|
||||
/// The cross-world linkshell chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 1", "cw1", 0xFF1E90FF)]
|
||||
CrossLinkShell1 = 37,
|
||||
/// <summary>
|
||||
/// The cross-world linkshell chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 1", "cw1", 0xFF1E90FF)]
|
||||
CrossLinkShell1 = 37,
|
||||
|
||||
/// <summary>
|
||||
/// The echo chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Echo", "echo", 0xFF808080)]
|
||||
Echo = 56,
|
||||
/// <summary>
|
||||
/// The echo chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Echo", "echo", 0xFF808080)]
|
||||
Echo = 56,
|
||||
|
||||
/// <summary>
|
||||
/// The system error chat type.
|
||||
/// </summary>
|
||||
SystemError = 58,
|
||||
/// <summary>
|
||||
/// The system error chat type.
|
||||
/// </summary>
|
||||
SystemError = 58,
|
||||
|
||||
/// <summary>
|
||||
/// The system message chat type.
|
||||
/// </summary>
|
||||
SystemMessage = 57,
|
||||
/// <summary>
|
||||
/// The system message chat type.
|
||||
/// </summary>
|
||||
SystemMessage = 57,
|
||||
|
||||
/// <summary>
|
||||
/// The system message (gathering) chat type.
|
||||
/// </summary>
|
||||
GatheringSystemMessage = 59,
|
||||
/// <summary>
|
||||
/// The system message (gathering) chat type.
|
||||
/// </summary>
|
||||
GatheringSystemMessage = 59,
|
||||
|
||||
/// <summary>
|
||||
/// The error message chat type.
|
||||
/// </summary>
|
||||
ErrorMessage = 60,
|
||||
/// <summary>
|
||||
/// The error message chat type.
|
||||
/// </summary>
|
||||
ErrorMessage = 60,
|
||||
|
||||
/// <summary>
|
||||
/// The retainer sale chat type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This might be used for other purposes.
|
||||
/// </remarks>
|
||||
RetainerSale = 71,
|
||||
/// <summary>
|
||||
/// The retainer sale chat type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This might be used for other purposes.
|
||||
/// </remarks>
|
||||
RetainerSale = 71,
|
||||
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 2 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 2", "cw2", 0xFF1E90FF)]
|
||||
CrossLinkShell2 = 101,
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 2 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 2", "cw2", 0xFF1E90FF)]
|
||||
CrossLinkShell2 = 101,
|
||||
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 3 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 3", "cw3", 0xFF1E90FF)]
|
||||
CrossLinkShell3 = 102,
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 3 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 3", "cw3", 0xFF1E90FF)]
|
||||
CrossLinkShell3 = 102,
|
||||
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 4 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 4", "cw4", 0xFF1E90FF)]
|
||||
CrossLinkShell4 = 103,
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 4 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 4", "cw4", 0xFF1E90FF)]
|
||||
CrossLinkShell4 = 103,
|
||||
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 5 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 5", "cw5", 0xFF1E90FF)]
|
||||
CrossLinkShell5 = 104,
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 5 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 5", "cw5", 0xFF1E90FF)]
|
||||
CrossLinkShell5 = 104,
|
||||
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 6 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 6", "cw6", 0xFF1E90FF)]
|
||||
CrossLinkShell6 = 105,
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 6 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 6", "cw6", 0xFF1E90FF)]
|
||||
CrossLinkShell6 = 105,
|
||||
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 7 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 7", "cw7", 0xFF1E90FF)]
|
||||
CrossLinkShell7 = 106,
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 7 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 7", "cw7", 0xFF1E90FF)]
|
||||
CrossLinkShell7 = 106,
|
||||
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 8 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 8", "cw8", 0xFF1E90FF)]
|
||||
CrossLinkShell8 = 107,
|
||||
/// <summary>
|
||||
/// The cross-world linkshell 8 chat type.
|
||||
/// </summary>
|
||||
[XivChatTypeInfo("Crossworld Linkshell 8", "cw8", 0xFF1E90FF)]
|
||||
CrossLinkShell8 = 107,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Game.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="XivChatType"/> type.
|
||||
/// </summary>
|
||||
public static class XivChatTypeExtensions
|
||||
namespace Dalamud.Game.Text
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the InfoAttribute associated with this chat type.
|
||||
/// Extension methods for the <see cref="XivChatType"/> type.
|
||||
/// </summary>
|
||||
/// <param name="chatType">The chat type.</param>
|
||||
/// <returns>The info attribute.</returns>
|
||||
public static XivChatTypeInfoAttribute GetDetails(this XivChatType chatType)
|
||||
public static class XivChatTypeExtensions
|
||||
{
|
||||
return chatType.GetAttribute<XivChatTypeInfoAttribute>();
|
||||
/// <summary>
|
||||
/// Get the InfoAttribute associated with this chat type.
|
||||
/// </summary>
|
||||
/// <param name="chatType">The chat type.</param>
|
||||
/// <returns>The info attribute.</returns>
|
||||
public static XivChatTypeInfoAttribute GetDetails(this XivChatType chatType)
|
||||
{
|
||||
return chatType.GetAttribute<XivChatTypeInfoAttribute>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,39 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for relevant information associated with the chat type.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class XivChatTypeInfoAttribute : Attribute
|
||||
namespace Dalamud.Game.Text
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="XivChatTypeInfoAttribute"/> class.
|
||||
/// Storage for relevant information associated with the chat type.
|
||||
/// </summary>
|
||||
/// <param name="fancyName">The fancy name.</param>
|
||||
/// <param name="slug">The name slug.</param>
|
||||
/// <param name="defaultColor">The default color.</param>
|
||||
internal XivChatTypeInfoAttribute(string fancyName, string slug, uint defaultColor)
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class XivChatTypeInfoAttribute : Attribute
|
||||
{
|
||||
this.FancyName = fancyName;
|
||||
this.Slug = slug;
|
||||
this.DefaultColor = defaultColor;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="XivChatTypeInfoAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="fancyName">The fancy name.</param>
|
||||
/// <param name="slug">The name slug.</param>
|
||||
/// <param name="defaultColor">The default color.</param>
|
||||
internal XivChatTypeInfoAttribute(string fancyName, string slug, uint defaultColor)
|
||||
{
|
||||
this.FancyName = fancyName;
|
||||
this.Slug = slug;
|
||||
this.DefaultColor = defaultColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the "fancy" name of the type.
|
||||
/// </summary>
|
||||
public string FancyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type name slug or short-form.
|
||||
/// </summary>
|
||||
public string Slug { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type default color.
|
||||
/// </summary>
|
||||
public uint DefaultColor { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the "fancy" name of the type.
|
||||
/// </summary>
|
||||
public string FancyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type name slug or short-form.
|
||||
/// </summary>
|
||||
public string Slug { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type default color.
|
||||
/// </summary>
|
||||
public uint DefaultColor { get; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue