- Add related unit tracking for activity logs and dossiers - Implement colorized names for related units in reason lines - Update harness scenarios to validate new interactions - Increment version to 0.26.1 in mod.json
321 lines
7.8 KiB
C#
321 lines
7.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
|
|
namespace IdleSpectator;
|
|
|
|
/// <summary>
|
|
/// Vanilla trait/status tooltips for dossier chips.
|
|
/// Tip types come from live <see cref="AssetManager.tooltips"/> (not invented keys).
|
|
/// </summary>
|
|
public static class DossierAssetTips
|
|
{
|
|
public const string TraitTipType = "trait";
|
|
public const string StatusTipType = "status";
|
|
|
|
private static HashSet<string> _liveTipTypes;
|
|
private static int _liveTipTypesCount = -1;
|
|
|
|
public static List<string> EnumerateLiveTooltipTypeIds()
|
|
{
|
|
var ids = new List<string>();
|
|
try
|
|
{
|
|
TooltipLibrary lib = AssetManager.tooltips;
|
|
if (lib?.list == null)
|
|
{
|
|
return ids;
|
|
}
|
|
|
|
for (int i = 0; i < lib.list.Count; i++)
|
|
{
|
|
TooltipAsset asset = lib.list[i];
|
|
if (asset != null && !string.IsNullOrEmpty(asset.id))
|
|
{
|
|
ids.Add(asset.id);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Library can be unavailable during early boot.
|
|
}
|
|
|
|
return ids;
|
|
}
|
|
|
|
public static bool HasLiveTooltipType(string tipType)
|
|
{
|
|
if (string.IsNullOrEmpty(tipType))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
EnsureLiveTipTypes();
|
|
return _liveTipTypes != null && _liveTipTypes.Contains(tipType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Harness audit: dump live tooltip ids; require trait + status tip types exist.
|
|
/// </summary>
|
|
public static bool RunTooltipInventoryAudit(string outputDirectory, out string detail)
|
|
{
|
|
List<string> ids = EnumerateLiveTooltipTypeIds();
|
|
var lines = new List<string>(ids.Count + 1) { "id" };
|
|
for (int i = 0; i < ids.Count; i++)
|
|
{
|
|
lines.Add(ids[i]);
|
|
}
|
|
|
|
try
|
|
{
|
|
if (!string.IsNullOrEmpty(outputDirectory))
|
|
{
|
|
Directory.CreateDirectory(outputDirectory);
|
|
File.WriteAllLines(Path.Combine(outputDirectory, "tooltip-library-ids.tsv"), lines);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// diagnostic dump only
|
|
}
|
|
|
|
bool hasTrait = HasLiveTooltipType(TraitTipType);
|
|
bool hasStatus = HasLiveTooltipType(StatusTipType);
|
|
bool pass = ids.Count > 0 && hasTrait && hasStatus;
|
|
detail = $"tips={ids.Count} trait={hasTrait} status={hasStatus}";
|
|
return pass;
|
|
}
|
|
|
|
public static void ShowTraitTip(GameObject host, ActorTrait trait)
|
|
{
|
|
if (host == null || trait == null || !HasLiveTooltipType(TraitTipType))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (!Config.tooltips_active)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Tooltip.show(host, TraitTipType, new TooltipData { trait = trait });
|
|
}
|
|
catch
|
|
{
|
|
// Tip surface can be unavailable mid-load.
|
|
}
|
|
}
|
|
|
|
public static void ShowStatusTip(GameObject host, Actor actor, StatusAsset asset, string statusId)
|
|
{
|
|
if (host == null || !HasLiveTooltipType(StatusTipType))
|
|
{
|
|
return;
|
|
}
|
|
|
|
StatusAsset resolved = asset;
|
|
if (resolved == null && !string.IsNullOrEmpty(statusId))
|
|
{
|
|
resolved = ActivityAssetCatalog.TryGetStatusAsset(statusId);
|
|
}
|
|
|
|
if (resolved == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Status live = FindLiveStatus(actor, resolved.id ?? statusId);
|
|
if (live == null)
|
|
{
|
|
// Vanilla status tip needs a Status instance (time bar + stats).
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (!Config.tooltips_active)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string locale = "";
|
|
string description = "";
|
|
try
|
|
{
|
|
locale = resolved.getLocaleID() ?? "";
|
|
description = resolved.getDescriptionID() ?? "";
|
|
}
|
|
catch
|
|
{
|
|
// fall through with empty locale keys - showStatus still uses status.asset stats
|
|
}
|
|
|
|
Tooltip.show(host, StatusTipType, new TooltipData
|
|
{
|
|
tip_name = locale,
|
|
tip_description = description,
|
|
status = live
|
|
});
|
|
}
|
|
catch
|
|
{
|
|
// Tip surface can be unavailable mid-load.
|
|
}
|
|
}
|
|
|
|
public static void HideTip()
|
|
{
|
|
try
|
|
{
|
|
Tooltip.hideTooltip();
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
private static Status FindLiveStatus(Actor actor, string statusId)
|
|
{
|
|
if (actor == null || string.IsNullOrEmpty(statusId))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (!actor.isAlive())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
foreach (Status status in actor.getStatuses())
|
|
{
|
|
if (status?.asset != null
|
|
&& string.Equals(status.asset.id, statusId, StringComparison.Ordinal))
|
|
{
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static void EnsureLiveTipTypes()
|
|
{
|
|
List<string> ids = EnumerateLiveTooltipTypeIds();
|
|
if (_liveTipTypes != null && _liveTipTypesCount == ids.Count)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_liveTipTypes = new HashSet<string>(StringComparer.Ordinal);
|
|
for (int i = 0; i < ids.Count; i++)
|
|
{
|
|
if (!string.IsNullOrEmpty(ids[i]))
|
|
{
|
|
_liveTipTypes.Add(ids[i]);
|
|
}
|
|
}
|
|
|
|
_liveTipTypesCount = ids.Count;
|
|
}
|
|
}
|
|
|
|
/// <summary>Pointer hover on a dossier trait/status chip root.</summary>
|
|
public sealed class DossierChipTip : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
|
|
{
|
|
public enum Kind
|
|
{
|
|
Trait,
|
|
Status
|
|
}
|
|
|
|
public Kind ChipKind;
|
|
public string AssetId = "";
|
|
public ActorTrait Trait;
|
|
public StatusAsset Status;
|
|
|
|
public void BindTrait(ActorTrait trait, string id)
|
|
{
|
|
ChipKind = Kind.Trait;
|
|
Trait = trait;
|
|
Status = null;
|
|
AssetId = id ?? (trait != null ? trait.id : "");
|
|
EnsureHitTarget();
|
|
}
|
|
|
|
public void BindStatus(StatusAsset status, string id)
|
|
{
|
|
ChipKind = Kind.Status;
|
|
Status = status;
|
|
Trait = null;
|
|
AssetId = id ?? (status != null ? status.id : "");
|
|
EnsureHitTarget();
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
Trait = null;
|
|
Status = null;
|
|
AssetId = "";
|
|
}
|
|
|
|
public void OnPointerEnter(PointerEventData eventData)
|
|
{
|
|
if (ChipKind == Kind.Trait)
|
|
{
|
|
ActorTrait trait = Trait;
|
|
if (trait == null && !string.IsNullOrEmpty(AssetId))
|
|
{
|
|
try
|
|
{
|
|
trait = AssetManager.traits?.get(AssetId);
|
|
}
|
|
catch
|
|
{
|
|
trait = null;
|
|
}
|
|
}
|
|
|
|
DossierAssetTips.ShowTraitTip(gameObject, trait);
|
|
return;
|
|
}
|
|
|
|
Actor actor = WatchCaption.BoundActor;
|
|
DossierAssetTips.ShowStatusTip(gameObject, actor, Status, AssetId);
|
|
}
|
|
|
|
public void OnPointerExit(PointerEventData eventData)
|
|
{
|
|
DossierAssetTips.HideTip();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
DossierAssetTips.HideTip();
|
|
}
|
|
|
|
private void EnsureHitTarget()
|
|
{
|
|
Image hit = GetComponent<Image>();
|
|
if (hit == null)
|
|
{
|
|
hit = gameObject.AddComponent<Image>();
|
|
}
|
|
|
|
hit.color = new Color(1f, 1f, 1f, 0.001f);
|
|
hit.raycastTarget = true;
|
|
}
|
|
}
|