Screenie/ScreenshotMetadata.cs

196 lines
7.6 KiB
C#

using System.Numerics;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Housing;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.Graphics.Environment;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using Lumina.Excel.GeneratedSheets;
using Map = Lumina.Excel.GeneratedSheets.Map;
namespace Screenie;
[Serializable]
public class SavedMetadata {
public required string Blake3Hash;
public required string Path;
public required ScreenshotMetadata Metadata;
}
[Serializable]
public class ScreenshotMetadata {
public required Character? ActiveCharacter;
public required string? Location;
public required string? LocationSub;
public required string? Area;
public required string? AreaSub;
public required uint TerritoryType;
public required string? World;
public required uint WorldId;
public required DateTime CapturedAtLocal;
public required DateTime CapturedAtUtc;
public required string EorzeaTime;
public required string? Weather;
public required uint? Ward;
public required uint? Plot;
public required Character[] VisibleCharacters;
internal static ScreenshotMetadata Capture(Plugin plugin) {
EorzeaTime eorzea;
uint? ward;
uint? plot;
short offsetX;
short offsetY;
float scale;
Weather? weather;
Map? map;
PlaceName? area;
PlaceName? areaSub;
unsafe {
var framework = Framework.Instance();
eorzea = new EorzeaTime((ulong) framework->ClientTime.EorzeaTime);
var housing = HousingManager.Instance();
try {
ward = (uint) housing->GetCurrentWard() + 1;
} catch {
ward = null;
}
try {
plot = (uint) housing->GetCurrentPlot() + 1;
} catch {
plot = null;
}
var env = EnvManager.Instance();
var weatherId = env->ActiveWeather;
weather = plugin.DataManager.GetExcelSheet<Weather>()?.GetRow(weatherId);
var agentMap = AgentMap.Instance();
offsetX = agentMap->CurrentOffsetX;
offsetY = agentMap->CurrentOffsetY;
scale = agentMap->CurrentMapSizeFactorFloat;
var mapId = agentMap->CurrentMapId;
map = plugin.DataManager.GetExcelSheet<Map>()?.GetRow(mapId);
var territoryInfo = TerritoryInfo.Instance();
area = plugin.DataManager.GetExcelSheet<PlaceName>()?.GetRow(territoryInfo->AreaPlaceNameID);
areaSub = plugin.DataManager.GetExcelSheet<PlaceName>()?.GetRow(territoryInfo->SubAreaPlaceNameID);
}
var territory = plugin.DataManager.GetExcelSheet<TerritoryType>()?.GetRow(plugin.ClientState.TerritoryType);
Character? active = null;
World? world = null;
if (plugin.ClientState.LocalPlayer is { } player) {
world = plugin.DataManager.GetExcelSheet<World>()?.GetRow(player.CurrentWorld.Id);
plugin.GameGui.WorldToScreen(player.Position, out var screenPos);
active = new Character(player, screenPos, scale, offsetX, offsetY);
}
var timeUtc = DateTime.UtcNow;
var visible = plugin.ObjectTable
.Where(obj => obj is PlayerCharacter)
.Cast<PlayerCharacter>()
.Where(chara => {
unsafe {
var obj = (GameObject*) chara.Address;
var draw = obj->DrawObject;
return draw != null && draw->IsVisible;
}
})
.Select(chara => {
var visible = plugin.GameGui.WorldToScreen(chara.Position, out var screenPos, out var inView);
return (chara, screenPos, visible: visible && inView);
})
.Where(tuple => tuple.visible)
.Select(tuple => new Character(tuple.chara, tuple.screenPos, scale, offsetX, offsetY))
.ToArray();
return new ScreenshotMetadata {
ActiveCharacter = active,
Location = map?.PlaceName.Value?.Name.ToDalamudString().TextValue.WhitespaceToNull()
?? territory?.PlaceName.Value?.Name.ToDalamudString().TextValue.WhitespaceToNull(),
LocationSub = map?.PlaceNameSub.Value?.Name.ToDalamudString().TextValue.WhitespaceToNull(),
Area = area?.Name.ToDalamudString().TextValue.WhitespaceToNull(),
AreaSub = areaSub?.Name.ToDalamudString().TextValue.WhitespaceToNull(),
TerritoryType = plugin.ClientState.TerritoryType,
World = world?.Name.ToDalamudString().TextValue.WhitespaceToNull(),
WorldId = world?.RowId ?? 0,
CapturedAtLocal = timeUtc.ToLocalTime(),
CapturedAtUtc = timeUtc,
EorzeaTime = $"{eorzea.Hour:00}:{eorzea.Minute:00}",
Weather = weather?.Name.ToDalamudString().TextValue.WhitespaceToNull(),
Ward = ward,
Plot = plot,
VisibleCharacters = visible,
};
}
}
[Serializable]
public class Character {
public string Name;
public string? HomeWorld;
public uint HomeWorldId;
public Vector3 RawPosition;
public Vector3 MapPosition;
public Vector2 ImagePosition;
public uint Level;
public string? Job;
public uint JobId;
public Character(PlayerCharacter player, Vector2 screenPos, float scale, short offsetX, short offsetY) {
this.Name = player.Name.TextValue;
this.HomeWorld = player.HomeWorld.GameData?.Name.ToDalamudString().TextValue.WhitespaceToNull();
this.HomeWorldId = player.HomeWorld.Id;
this.RawPosition = player.Position;
this.MapPosition = new Vector3(
ConvertRawPositionToMapCoordinate(player.Position.X, scale, offsetX),
ConvertRawPositionToMapCoordinate(player.Position.Z, scale, offsetY),
player.Position.Y // TODO: how does map Z coord work
);
this.ImagePosition = screenPos;
this.Level = player.Level;
this.Job = player.ClassJob.GameData?.Name.ToDalamudString().TextValue.WhitespaceToNull();
this.JobId = player.ClassJob.Id;
}
private static float ConvertRawPositionToMapCoordinate(float pos, float scale, short offset) {
var num2 = (pos + offset) * scale;
return (float) (41.0 / scale * ((num2 + 1024.0) / 2048.0) + 1.0);
}
}
public struct EorzeaTime {
public readonly ulong Year;
public readonly ulong Month;
public readonly ulong Day;
public readonly ulong Hour;
public readonly ulong Minute;
public readonly ulong Second;
private const double EorzeaTimeConst = (double) 3600 / 175;
private const double YearConst = 33177600;
private const double MonthConst = 2764800;
private const double DayConst = 86400;
private const double HourConst = 3600;
private const double MinuteConst = 60;
private const double SecondConst = 1;
public EorzeaTime(DateTime time) : this((ulong) Math.Floor(((DateTimeOffset) time).ToUnixTimeSeconds() * EorzeaTimeConst)) {
}
public EorzeaTime(ulong eorzea) {
this.Year = (ulong) Math.Floor(eorzea / YearConst) + 1;
this.Month = (ulong) Math.Floor(eorzea / MonthConst % 12) + 1;
this.Day = (ulong) Math.Floor(eorzea / DayConst % 32) + 1;
this.Hour = (ulong) Math.Floor(eorzea / HourConst % 24);
this.Minute = (ulong) Math.Floor(eorzea / MinuteConst % 60);
this.Second = (ulong) Math.Floor(eorzea / SecondConst % 60);
}
}