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 _logger; private uint _loadedTerritoryType; private ISharedImmediateTexture? _resolvedTexture; private List? _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 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().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 list = new List { $"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); } }