mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-23 16:27:44 +01:00
initial map link payload implementation. Generally works, but at least needs a better way to handle lumina DI
This commit is contained in:
parent
b5b9e6d8e1
commit
1894d579a8
6 changed files with 210 additions and 16 deletions
|
|
@ -88,6 +88,9 @@ namespace Dalamud {
|
||||||
this.Data = new DataManager(this.StartInfo.Language);
|
this.Data = new DataManager(this.StartInfo.Language);
|
||||||
this.Data.Initialize();
|
this.Data.Initialize();
|
||||||
|
|
||||||
|
// FIXME: need a better way to get this into the string payloads
|
||||||
|
Game.Chat.SeStringHandling.SeString.DataResolver = this.Data;
|
||||||
|
|
||||||
this.ClientState = new ClientState(this, info, this.SigScanner);
|
this.ClientState = new ClientState(this, info, this.SigScanner);
|
||||||
|
|
||||||
this.BotManager = new DiscordBotManager(this, this.Configuration.DiscordFeatureConfig);
|
this.BotManager = new DiscordBotManager(this, this.Configuration.DiscordFeatureConfig);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Data;
|
||||||
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
|
@ -16,22 +17,32 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
{
|
{
|
||||||
public abstract PayloadType Type { get; }
|
public abstract PayloadType Type { get; }
|
||||||
|
|
||||||
|
protected DataManager dataResolver;
|
||||||
|
|
||||||
public abstract void Resolve();
|
public abstract void Resolve();
|
||||||
|
|
||||||
public abstract byte[] Encode();
|
public abstract byte[] Encode();
|
||||||
|
|
||||||
protected abstract void ProcessChunkImpl(BinaryReader reader, long endOfStream);
|
protected abstract void ProcessChunkImpl(BinaryReader reader, long endOfStream);
|
||||||
|
|
||||||
public static Payload Process(BinaryReader reader)
|
public static Payload Process(BinaryReader reader, DataManager dataResolver)
|
||||||
{
|
{
|
||||||
|
Payload payload = null;
|
||||||
if ((byte)reader.PeekChar() != START_BYTE)
|
if ((byte)reader.PeekChar() != START_BYTE)
|
||||||
{
|
{
|
||||||
return ProcessText(reader);
|
payload = ProcessText(reader);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return ProcessChunk(reader);
|
payload = ProcessChunk(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payload != null)
|
||||||
|
{
|
||||||
|
payload.dataResolver = dataResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Payload ProcessChunk(BinaryReader reader)
|
private static Payload ProcessChunk(BinaryReader reader)
|
||||||
|
|
@ -59,6 +70,10 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
payload = new ItemPayload();
|
payload = new ItemPayload();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case EmbeddedInfoType.MapPositionLink:
|
||||||
|
payload = new MapLinkPayload();
|
||||||
|
break;
|
||||||
|
|
||||||
case EmbeddedInfoType.Status:
|
case EmbeddedInfoType.Status:
|
||||||
payload = new StatusPayload();
|
payload = new StatusPayload();
|
||||||
break;
|
break;
|
||||||
|
|
@ -74,6 +89,7 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType);
|
Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType);
|
||||||
payload = new RawPayload((byte)chunkType);
|
payload = new RawPayload((byte)chunkType);
|
||||||
|
|
@ -111,6 +127,7 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
{
|
{
|
||||||
PlayerName = 0x01,
|
PlayerName = 0x01,
|
||||||
ItemLink = 0x03,
|
ItemLink = 0x03,
|
||||||
|
MapPositionLink = 0x04,
|
||||||
Status = 0x09,
|
Status = 0x09,
|
||||||
|
|
||||||
LinkTerminator = 0xCF // not clear but seems to always follow a link
|
LinkTerminator = 0xCF // not clear but seems to always follow a link
|
||||||
|
|
@ -118,10 +135,14 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
|
|
||||||
protected enum IntegerType
|
protected enum IntegerType
|
||||||
{
|
{
|
||||||
|
// used as an internal marker; sometimes single bytes are bare with no marker at all
|
||||||
|
None = 0,
|
||||||
|
|
||||||
Byte = 0xF0,
|
Byte = 0xF0,
|
||||||
ByteTimes256 = 0xF1,
|
ByteTimes256 = 0xF1,
|
||||||
Int16 = 0xF2,
|
Int16 = 0xF2,
|
||||||
Int16Plus1Million = 0xF6,
|
Int16Packed = 0xF4, // seen in map links, seemingly 2 8-bit values packed into 2 bytes with only one marker
|
||||||
|
Int24Special = 0xF6, // unsure how different form Int24 - used for hq items that add 1 million, also used for normal 24-bit values in map links
|
||||||
Int24 = 0xFA,
|
Int24 = 0xFA,
|
||||||
Int32 = 0xFE
|
Int32 = 0xFE
|
||||||
}
|
}
|
||||||
|
|
@ -147,25 +168,22 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
{
|
{
|
||||||
case IntegerType.Byte:
|
case IntegerType.Byte:
|
||||||
return input.ReadByte();
|
return input.ReadByte();
|
||||||
|
|
||||||
case IntegerType.ByteTimes256:
|
case IntegerType.ByteTimes256:
|
||||||
return input.ReadByte() * 256;
|
return input.ReadByte() * 256;
|
||||||
|
|
||||||
case IntegerType.Int16:
|
case IntegerType.Int16:
|
||||||
|
// fallthrough - same logic
|
||||||
|
case IntegerType.Int16Packed:
|
||||||
{
|
{
|
||||||
var v = 0;
|
var v = 0;
|
||||||
v |= input.ReadByte() << 8;
|
v |= input.ReadByte() << 8;
|
||||||
v |= input.ReadByte();
|
v |= input.ReadByte();
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
case IntegerType.Int16Plus1Million:
|
|
||||||
{
|
case IntegerType.Int24Special:
|
||||||
var v = 0;
|
// Fallthrough - same logic
|
||||||
v |= input.ReadByte() << 16;
|
|
||||||
v |= input.ReadByte() << 8;
|
|
||||||
v |= input.ReadByte();
|
|
||||||
// need the actual value since it's used as a flag
|
|
||||||
// v -= 1000000;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
case IntegerType.Int24:
|
case IntegerType.Int24:
|
||||||
{
|
{
|
||||||
var v = 0;
|
var v = 0;
|
||||||
|
|
@ -174,6 +192,7 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
v |= input.ReadByte();
|
v |= input.ReadByte();
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IntegerType.Int32:
|
case IntegerType.Int32:
|
||||||
{
|
{
|
||||||
var v = 0;
|
var v = 0;
|
||||||
|
|
@ -183,6 +202,7 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
v |= input.ReadByte();
|
v |= input.ReadByte();
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
@ -220,6 +240,27 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
|
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static (int, int) GetPackedIntegers(BinaryReader input)
|
||||||
|
{
|
||||||
|
var value = (uint)GetInteger(input);
|
||||||
|
if (value > 0xFFFF)
|
||||||
|
{
|
||||||
|
return ((int)((value & 0xFFFF0000) >> 16), (int)(value & 0xFFFF));
|
||||||
|
}
|
||||||
|
else if (value > 0xFF)
|
||||||
|
{
|
||||||
|
return ((int)((value & 0xFF00) >> 8), (int)(value & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsure if there are other cases, like "odd" pairings of 2+1 bytes etc
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] MakePackedInteger(int val1, int val2)
|
||||||
|
{
|
||||||
|
return MakeInteger(val1).Concat(MakeInteger(val2)).ToArray();
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,10 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RawText,
|
RawText,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// An SeString payload representing a map position link, such as from <flag> or <pos>.
|
||||||
|
/// </summary>
|
||||||
|
MapLink,
|
||||||
|
/// <summary>
|
||||||
/// An SeString payload representing any data we don't handle.
|
/// An SeString payload representing any data we don't handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Unknown
|
Unknown
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
||||||
var idBytes = MakeInteger(actualItemId);
|
var idBytes = MakeInteger(actualItemId);
|
||||||
bool hasName = !string.IsNullOrEmpty(ItemName);
|
bool hasName = !string.IsNullOrEmpty(ItemName);
|
||||||
|
|
||||||
var itemIdFlag = IsHQ ? IntegerType.Int16Plus1Million : IntegerType.Int16;
|
var itemIdFlag = IsHQ ? IntegerType.Int24Special : IntegerType.Int16;
|
||||||
|
|
||||||
var chunkLen = idBytes.Length + 5;
|
var chunkLen = idBytes.Length + 5;
|
||||||
if (hasName)
|
if (hasName)
|
||||||
|
|
|
||||||
143
Dalamud/Game/Chat/SeStringHandling/Payloads/MapLinkPayload.cs
Normal file
143
Dalamud/Game/Chat/SeStringHandling/Payloads/MapLinkPayload.cs
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
||||||
|
{
|
||||||
|
public class MapLinkPayload : Payload
|
||||||
|
{
|
||||||
|
public override PayloadType Type => PayloadType.MapLink;
|
||||||
|
|
||||||
|
// pre-Resolve() values
|
||||||
|
public int TerritoryTypeId { get; set; }
|
||||||
|
public int MapId { get; set; }
|
||||||
|
public uint RawX { get; set; }
|
||||||
|
public uint RawY { get; set; }
|
||||||
|
|
||||||
|
// Resolved values
|
||||||
|
// It might make sense to have Territory be an external type, that has assorted relevant info
|
||||||
|
public string Territory { get; private set; }
|
||||||
|
public string Zone { get; private set; }
|
||||||
|
public float XCoord { get; private set; }
|
||||||
|
public float YCoord { get; private set; }
|
||||||
|
// Z?
|
||||||
|
|
||||||
|
public override byte[] Encode()
|
||||||
|
{
|
||||||
|
// TODO: for now we just encode the raw/internal values
|
||||||
|
// eventually we should allow creation using 'nice' values that then encode properly
|
||||||
|
|
||||||
|
var packedTerritoryAndMapBytes = WrapPackedIntegerAndMarker(TerritoryTypeId, MapId);
|
||||||
|
var xBytes = WrapIntegerAndMarker((int)RawX);
|
||||||
|
var yBytes = WrapIntegerAndMarker((int)RawY);
|
||||||
|
|
||||||
|
var chunkLen = 4 + packedTerritoryAndMapBytes.Length + xBytes.Length + yBytes.Length;
|
||||||
|
|
||||||
|
var bytes = new List<byte>()
|
||||||
|
{
|
||||||
|
START_BYTE,
|
||||||
|
(byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.MapPositionLink
|
||||||
|
};
|
||||||
|
|
||||||
|
bytes.AddRange(packedTerritoryAndMapBytes);
|
||||||
|
bytes.AddRange(xBytes);
|
||||||
|
bytes.AddRange(yBytes);
|
||||||
|
|
||||||
|
// unk
|
||||||
|
bytes.AddRange(new byte[] { 0xFF, 0x01, END_BYTE });
|
||||||
|
|
||||||
|
return bytes.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Resolve()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Territory))
|
||||||
|
{
|
||||||
|
var terrRow = dataResolver.GetExcelSheet<TerritoryType>().GetRow(TerritoryTypeId);
|
||||||
|
Territory = dataResolver.GetExcelSheet<PlaceName>().GetRow(terrRow.PlaceName).Name;
|
||||||
|
Zone = dataResolver.GetExcelSheet<PlaceName>().GetRow(terrRow.PlaceNameZone).Name;
|
||||||
|
|
||||||
|
var mapSizeFactor = dataResolver.GetExcelSheet<Map>().GetRow(MapId).SizeFactor;
|
||||||
|
XCoord = ConvertRawPositionToMapCoordinate(RawX, mapSizeFactor);
|
||||||
|
YCoord = ConvertRawPositionToMapCoordinate(RawY, mapSizeFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
|
||||||
|
{
|
||||||
|
(TerritoryTypeId, MapId) = GetPackedIntegers(reader);
|
||||||
|
RawX = (uint)GetInteger(reader);
|
||||||
|
RawY = (uint)GetInteger(reader);
|
||||||
|
// the Z coordinate is never in this chunk, just the text (if applicable)
|
||||||
|
|
||||||
|
// seems to always be FF 01
|
||||||
|
reader.ReadBytes(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region ugliness
|
||||||
|
// from https://github.com/xivapi/ffxiv-datamining/blob/master/docs/MapCoordinates.md
|
||||||
|
// extra 1/1000 because that is how the network ints are done
|
||||||
|
private float ConvertRawPositionToMapCoordinate(uint pos, float scale)
|
||||||
|
{
|
||||||
|
var c = scale / 100.0f;
|
||||||
|
var scaledPos = (int)pos * c / 1000.0f;
|
||||||
|
|
||||||
|
return ((41.0f / c) * ((scaledPos + 1024.0f) / 2048.0f)) + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Created as the inverse of ConvertRawPositionToMapCoordinate(), since no one seemed to have a version of that
|
||||||
|
private float ConvertMapCoordinateToRawPosition(float pos, float scale)
|
||||||
|
{
|
||||||
|
var c = scale / 100.0f;
|
||||||
|
|
||||||
|
var scaledPos = ((((pos - 1.0f) * c / 41.0f) * 2048.0f) - 1024.0f) / c;
|
||||||
|
scaledPos *= 1000.0f;
|
||||||
|
|
||||||
|
return (int)Math.Round(scaledPos);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private byte[] WrapIntegerAndMarker(int value)
|
||||||
|
{
|
||||||
|
var encodedValue = MakeInteger(value);
|
||||||
|
var marker = GetTypeForIntegerBytes_Custom(encodedValue, false);
|
||||||
|
|
||||||
|
var bytes = new List<byte>();
|
||||||
|
if ((IntegerType)marker != IntegerType.None)
|
||||||
|
{
|
||||||
|
bytes.Add(marker);
|
||||||
|
}
|
||||||
|
bytes.AddRange(encodedValue);
|
||||||
|
|
||||||
|
return bytes.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] WrapPackedIntegerAndMarker(int value1, int value2)
|
||||||
|
{
|
||||||
|
var encodedValue = MakePackedInteger(TerritoryTypeId, MapId);
|
||||||
|
var marker = GetTypeForIntegerBytes_Custom(encodedValue, true);
|
||||||
|
|
||||||
|
var bytes = new List<byte>() { marker };
|
||||||
|
bytes.AddRange(encodedValue);
|
||||||
|
|
||||||
|
return bytes.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This seems entirely custom in various payload types
|
||||||
|
// I'm not really sure if it matters if we use one version of Int16 over another, for example
|
||||||
|
// but for now I try to match what incoming messages seem to use
|
||||||
|
private byte GetTypeForIntegerBytes_Custom(byte[] bytes, bool packed = false)
|
||||||
|
{
|
||||||
|
var type = bytes.Length switch
|
||||||
|
{
|
||||||
|
3 => IntegerType.Int24Special, // used because seen in incoming data
|
||||||
|
2 => packed ? IntegerType.Int16Packed : IntegerType.Int16, // packed bytes seem to get a different marker.. but not packed shorts
|
||||||
|
1 => IntegerType.None, // single bytes seem to have no prefix at all here
|
||||||
|
_ => GetTypeForIntegerBytes(bytes)
|
||||||
|
};
|
||||||
|
|
||||||
|
return (byte)type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Data;
|
||||||
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
||||||
|
|
||||||
namespace Dalamud.Game.Chat.SeStringHandling
|
namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
|
|
@ -13,6 +14,8 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SeString
|
public class SeString
|
||||||
{
|
{
|
||||||
|
public static DataManager DataResolver { get; set; }
|
||||||
|
|
||||||
public List<Payload> Payloads { get; }
|
public List<Payload> Payloads { get; }
|
||||||
|
|
||||||
public SeString(List<Payload> payloads)
|
public SeString(List<Payload> payloads)
|
||||||
|
|
@ -56,7 +59,7 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
||||||
|
|
||||||
while (stream.Position < bytes.Length)
|
while (stream.Position < bytes.Length)
|
||||||
{
|
{
|
||||||
var payload = Payload.Process(reader);
|
var payload = Payload.Process(reader, DataResolver);
|
||||||
if (payload != null)
|
if (payload != null)
|
||||||
payloads.Add(payload);
|
payloads.Add(payload);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue