using HarmonyLib;
using OWML.Common;
using OWML.ModHelper;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
namespace SlovakTranslation
{
///
/// Fixes the missing Slovak caron glyphs (č ď ĺ ľ ň ŕ ť ž) in the menus and HUD.
///
/// Outer Wilds renders Latin languages with a handful of *static* font atlases,
/// and only some of them were baked with Latin Extended-A:
///
/// Gill Sans MT / Gill Sans MT Menu (menus, prompts) 243 glyphs — no carons
/// ParaType - Futura PT Medium (language font) 663 glyphs — complete
/// SpaceMono-Regular (translator) 612 glyphs — complete
///
/// That is why dialogue and the Nomai translator always looked right while
/// "Pokračovať" came out as "Pokra ova". Registering a whole custom language
/// font via the Localization Utility fixes the menus but also flips
/// TextTranslation.IsLanguageLatin() to false, which drags dialogue,
/// the translator and the ship log off their proper fonts too.
///
/// So instead we leave the language font alone and swap fonts *only* where the
/// vanilla one physically cannot draw Slovak — checked at runtime with
/// Font.HasCharacter, so we never touch a font that renders fine.
///
internal static class SlovakFont
{
/// Cabin (SIL OFL 1.1), a humanist sans close to the vanilla Gill Sans MT.
/// Shipped as a dynamic font, so it can draw any glyph Slovak needs.
private static Font _font;
/// Characters the vanilla menu atlas is missing. If a font can draw all
/// of these it is left completely alone.
private static readonly char[] Probe = { 'č', 'ď', 'ĺ', 'ľ', 'ň', 'ŕ', 'ť', 'ž', 'ô', 'ä' };
private static readonly Dictionary _coverage = new Dictionary();
internal static bool Loaded => _font != null;
internal static void Load(ModBehaviour mod)
{
var path = mod.ModHelper.Manifest.ModFolderPath + "assets/slovakfont";
var bundle = AssetBundle.LoadFromFile(path);
if (bundle == null)
{
mod.ModHelper.Console.WriteLine("Could not load font bundle at " + path, MessageType.Error);
return;
}
foreach (var font in bundle.LoadAllAssets())
{
if (font.name != "SlovakUI") continue;
_font = font;
UnityEngine.Object.DontDestroyOnLoad(font);
break;
}
if (_font == null)
mod.ModHelper.Console.WriteLine("Font bundle has no 'SlovakUI' font.", MessageType.Error);
}
/// Returns unchanged unless Slovenčina is the
/// active language and that font cannot draw Slovak.
internal static Font Fix(Font font)
{
if (_font == null || font == null || font == _font) return font;
// Dynamic fonts rasterise straight from an embedded font file; every one the
// game ships for Latin covers Slovak, and HasCharacter is unreliable for them.
if (font.dynamic) return font;
if (!IsActive()) return font;
var id = font.GetInstanceID();
if (!_coverage.TryGetValue(id, out var complete))
{
complete = true;
foreach (var c in Probe)
{
if (font.HasCharacter(c)) continue;
complete = false;
break;
}
_coverage[id] = complete;
}
return complete ? font : _font;
}
#region "is Slovenčina selected?"
// The Localization Utility invents a TextTranslation.Language value for each
// custom language, so it is not in the enum at compile time. Ask the utility
// once, then cache the value and compare enums from then on.
private static bool _resolved;
private static TextTranslation.Language _slovak;
private static MethodInfo _tryGetLanguage;
private static object _utility;
private static bool IsActive()
{
if (TextTranslation.s_theTable == null) return false;
var current = TextTranslation.Get().GetLanguage();
if (_resolved) return current == _slovak;
if (_utility == null)
{
var type = SlovakTranslation.LocalizationApi?.GetType().Assembly.GetType("LocalizationUtility.LocalizationUtility");
if (type == null) return false;
_utility = type.GetField("Instance", BindingFlags.Public | BindingFlags.Static)?.GetValue(null);
foreach (var m in type.GetMethods())
{
var p = m.GetParameters();
if (m.Name == "TryGetLanguage" && p.Length == 2 &&
p[0].ParameterType == typeof(TextTranslation.Language))
{
_tryGetLanguage = m;
break;
}
}
if (_utility == null || _tryGetLanguage == null) return false;
}
var args = new object[] { current, null };
if (!(bool)_tryGetLanguage.Invoke(_utility, args) || args[1] == null) return false;
var name = args[1].GetType().GetProperty("Name")?.GetValue(args[1], null) as string;
if (name != SlovakTranslation.LanguageName) return false;
_slovak = current;
_resolved = true;
return true;
}
#endregion
}
///
/// Every place the game hands out a font that is not the language font. Each one
/// runs through , which is a no-op unless the font
/// really is missing Slovak glyphs.
///
[HarmonyPatch]
internal static class SlovakFontPatches
{
[HarmonyPostfix, HarmonyPatch(typeof(UIStyleManager), nameof(UIStyleManager.GetMenuFont))]
public static void MenuFont(ref Font __result) => __result = SlovakFont.Fix(__result);
[HarmonyPostfix, HarmonyPatch(typeof(UIStyleManager), nameof(UIStyleManager.GetTranslatorFont))]
public static void TranslatorFont(ref Font __result) => __result = SlovakFont.Fix(__result);
[HarmonyPostfix, HarmonyPatch(typeof(UIStyleManager), nameof(UIStyleManager.GetShipLogFont))]
public static void ShipLogFont(ref Font __result) => __result = SlovakFont.Fix(__result);
[HarmonyPostfix, HarmonyPatch(typeof(UIStyleManager), nameof(UIStyleManager.GetShipLogCardFont))]
public static void ShipLogCardFont(ref Font __result) => __result = SlovakFont.Fix(__result);
// Prompts ("Podrž", "Pokračovať"…) cache their font in a private field and then
// rebuild, so patch after the fact and rebuild once more.
[HarmonyPostfix, HarmonyPatch(typeof(PromptManager), "OnLanguageChanged")]
public static void PromptFont(PromptManager __instance)
{
var field = AccessTools.Field(typeof(PromptManager), "_currentFont");
var current = field.GetValue(__instance) as Font;
var fixedFont = SlovakFont.Fix(current);
if (fixedFont == current) return;
field.SetValue(__instance, fixedFont);
AccessTools.Method(typeof(PromptManager), "RebuildUI")?.Invoke(__instance, null);
}
[HarmonyPostfix, HarmonyPatch(typeof(GameOverController), nameof(GameOverController.SetupGameOverScreen))]
public static void DeathFont(GameOverController __instance)
{
var text = AccessTools.Field(typeof(GameOverController), "_deathText").GetValue(__instance) as Text;
if (text != null) text.font = SlovakFont.Fix(text.font);
}
}
}