using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Data.LuminaExtensions;
using Lumina.Data;
using Lumina.Data.Files;
using Lumina.Excel;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serilog;
using LuminaOptions = Lumina.LuminaOptions;
using ParsedFilePath = Lumina.ParsedFilePath;
namespace Dalamud.Data
{
///
/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
///
public class DataManager : IDisposable {
///
/// OpCodes sent by the server to the client.
///
public ReadOnlyDictionary ServerOpCodes { get; private set; }
///
/// OpCodes sent by the client to the server.
///
public ReadOnlyDictionary ClientOpCodes { get; private set; }
///
/// An object which gives access to any of the game's sheet data.
///
public ExcelModule Excel => this.gameData?.Excel;
///
/// Indicates whether Game Data is ready to be read.
///
public bool IsDataReady { get; private set; }
///
/// A object which gives access to any excel/game data.
///
private Lumina.Lumina gameData;
private ClientLanguage language;
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
private Thread luminaResourceThread;
public DataManager(ClientLanguage language)
{
// Set up default values so plugins do not null-reference when data is being loaded.
this.ServerOpCodes = new ReadOnlyDictionary(new Dictionary());
this.language = language;
}
public async Task Initialize(string baseDir)
{
try
{
Log.Verbose("Starting data load...");
var zoneOpCodeDict =
JsonConvert.DeserializeObject>(File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")));
ServerOpCodes = new ReadOnlyDictionary(zoneOpCodeDict);
Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
var clientOpCodeDict =
JsonConvert.DeserializeObject>(File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")));
ClientOpCodes = new ReadOnlyDictionary(clientOpCodeDict);
Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
var luminaOptions = new LuminaOptions
{
CacheFileResources = true
};
luminaOptions.DefaultExcelLanguage = this.language switch {
ClientLanguage.Japanese => Language.Japanese,
ClientLanguage.English => Language.English,
ClientLanguage.German => Language.German,
ClientLanguage.French => Language.French,
_ => throw new ArgumentOutOfRangeException(nameof(this.language),
"Unknown Language: " + this.language)
};
gameData = new Lumina.Lumina(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "sqpack"), luminaOptions);
Log.Information("Lumina is ready: {0}", gameData.DataPath);
IsDataReady = true;
this.luminaResourceThread = new Thread( () =>
{
while( true )
{
gameData.ProcessFileHandleQueue();
Thread.Yield();
}
// ReSharper disable once FunctionNeverReturns
});
this.luminaResourceThread.Start();
}
catch (Exception ex)
{
Log.Error(ex, "Could not download data.");
}
}
#region Lumina Wrappers
///
/// Get an with the given Excel sheet row type.
///
/// The excel sheet type to get.
/// The , giving access to game rows.
public ExcelSheet GetExcelSheet() where T : IExcelRow
{
return this.Excel.GetSheet();
}
///
/// Get an with the given Excel sheet row type with a specified language.
///
/// Language of the sheet to get.
/// The excel sheet type to get.
/// The , giving access to game rows.
public ExcelSheet GetExcelSheet(ClientLanguage language) where T : IExcelRow {
var lang = language switch {
ClientLanguage.Japanese => Language.Japanese,
ClientLanguage.English => Language.English,
ClientLanguage.German => Language.German,
ClientLanguage.French => Language.French,
_ => throw new ArgumentOutOfRangeException(nameof(this.language), "Unknown Language: " + this.language)
};
return this.Excel.GetSheet(lang);
}
///
/// Get a with the given path.
///
/// The path inside of the game files.
/// The of the file.
public FileResource GetFile(string path)
{
return this.GetFile(path);
}
///
/// Get a with the given path, of the given type.
///
/// The type of resource
/// The path inside of the game files.
/// The of the file.
public T GetFile(string path) where T : FileResource
{
ParsedFilePath filePath = Lumina.Lumina.ParseFilePath(path);
if (filePath == null)
return default(T);
Repository repository;
return this.gameData.Repositories.TryGetValue(filePath.Repository, out repository) ? repository.GetFile(filePath.Category, filePath) : default(T);
}
///
/// Get a containing the icon with the given ID.
///
/// The icon ID.
/// The containing the icon.
public TexFile GetIcon(int iconId)
{
return GetIcon(this.language, iconId);
}
///
/// Get a containing the icon with the given ID, of the given language.
///
/// The requested language.
/// The icon ID.
/// The containing the icon.
public TexFile GetIcon(ClientLanguage iconLanguage, int iconId)
{
var type = iconLanguage switch {
ClientLanguage.Japanese => "ja/",
ClientLanguage.English => "en/",
ClientLanguage.German => "de/",
ClientLanguage.French => "fr/",
_ => throw new ArgumentOutOfRangeException(nameof(this.language),
"Unknown Language: " + this.language)
};
return GetIcon(type, iconId);
}
///
/// Get a containing the icon with the given ID, of the given type.
///
/// The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).
/// The icon ID.
/// The containing the icon.
public TexFile GetIcon(string type, int iconId)
{
type ??= string.Empty;
if (type.Length > 0 && !type.EndsWith("/"))
type += "/";
var filePath = string.Format(IconFileFormat, iconId / 1000, type, iconId);
var file = this.GetFile(filePath);
if (file != default(TexFile) || type.Length <= 0) return file;
// Couldn't get specific type, try for generic version.
filePath = string.Format(IconFileFormat, iconId / 1000, string.Empty, iconId);
file = this.GetFile(filePath);
return file;
}
#endregion
public void Dispose()
{
this.luminaResourceThread.Abort();
}
}
}