qstbak/LLib/LLib.ImGui/MapTextureService.cs
2026-08-17 20:29:32 +10:00

171 lines
4.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Plugin.Services;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
namespace LLib.ImGui;
public sealed class MapTextureService
{
private readonly ITextureProvider _textureProvider;
private readonly IDataManager _dataManager;
private readonly ILogger<MapTextureService> _logger;
private uint _loadedTerritoryType;
private ISharedImmediateTexture? _resolvedTexture;
private List<ISharedImmediateTexture>? _candidateTextures;
private bool _failed;
private long _retryAtMs;
private int _searchFrames;
private const int SearchTimeout = 120;
private const long RetryDelayMs = 30000L;
private ushort _sizeFactor;
private short _offsetX;
private short _offsetY;
public bool IsLoaded => _resolvedTexture != null;
public bool IsSearching => _candidateTextures != null;
public ushort SizeFactor => _sizeFactor;
public MapTextureService(ITextureProvider textureProvider, IDataManager dataManager, ILogger<MapTextureService> logger)
{
_textureProvider = textureProvider;
_dataManager = dataManager;
_logger = logger;
}
public bool LoadForTerritory(uint territoryType)
{
if (territoryType == _loadedTerritoryType && _resolvedTexture != null)
{
return true;
}
if (territoryType == _loadedTerritoryType && _failed)
{
return false;
}
if (territoryType == _loadedTerritoryType && _candidateTextures != null)
{
PollCandidates();
return _resolvedTexture != null;
}
if (territoryType == _loadedTerritoryType && Environment.TickCount64 < _retryAtMs)
{
return false;
}
_resolvedTexture = null;
_candidateTextures = null;
_failed = false;
_retryAtMs = 0L;
_loadedTerritoryType = territoryType;
_searchFrames = 0;
Map? map = _dataManager.GetExcelSheet<TerritoryType>().GetRowOrDefault(territoryType)?.Map.ValueNullable;
if (!map.HasValue || map.Value.RowId == 0)
{
_failed = true;
return false;
}
_sizeFactor = map.Value.SizeFactor;
_offsetX = map.Value.OffsetX;
_offsetY = map.Value.OffsetY;
string text = map.Value.Id.ExtractText();
if (string.IsNullOrEmpty(text))
{
_failed = true;
return false;
}
string[] array = text.Split('/');
if (array.Length != 2)
{
_failed = true;
return false;
}
string value = array[0];
string text2 = array[1];
List<string> list = new List<string> { $"ui/map/{text}/{value}{text2}_m.tex" };
if (text2 != "00")
{
list.Add($"ui/map/{value}/00/{value}00_m.tex");
}
_logger.LogDebug("Starting map texture search for territory {TerritoryType}: {Paths}", territoryType, string.Join(", ", list));
_candidateTextures = list.Select((string p) => _textureProvider.GetFromGame(p)).ToList();
PollCandidates();
return _resolvedTexture != null;
}
private void PollCandidates()
{
if (_candidateTextures == null)
{
return;
}
foreach (ISharedImmediateTexture candidateTexture in _candidateTextures)
{
if (candidateTexture.GetWrapOrDefault() != null)
{
_resolvedTexture = candidateTexture;
_candidateTextures = null;
return;
}
}
_searchFrames++;
if (_searchFrames >= 120)
{
_logger.LogWarning("Map texture search timed out for territory {TerritoryType}, retrying in {Delay}s", _loadedTerritoryType, 30L);
_candidateTextures = null;
_retryAtMs = Environment.TickCount64 + 30000;
}
}
public IDalamudTextureWrap? GetTextureWrap()
{
return _resolvedTexture?.GetWrapOrDefault();
}
public Vector2 WorldToUV(float worldX, float worldZ)
{
float num = (float)(int)_sizeFactor / 100f;
float x = (worldX * num + (float)_offsetX * num + 1024f) / 2048f;
float y = (worldZ * num + (float)_offsetY * num + 1024f) / 2048f;
return new Vector2(x, y);
}
public Vector2 UVToWorld(float u, float v)
{
float num = (float)(int)_sizeFactor / 100f;
float x = (u * 2048f - 1024f - (float)_offsetX * num) / num;
float y = (v * 2048f - 1024f - (float)_offsetY * num) / num;
return new Vector2(x, y);
}
public Vector2 WorldToScreen(float worldX, float worldZ, Vector2 imagePos, Vector2 imageSize)
{
Vector2 vector = WorldToUV(worldX, worldZ);
return imagePos + vector * imageSize;
}
public Vector2 ScreenToWorld(Vector2 screenPos, Vector2 imagePos, Vector2 imageSize)
{
Vector2 vector = (screenPos - imagePos) / imageSize;
return UVToWorld(vector.X, vector.Y);
}
}