594 lines
20 KiB
C#
594 lines
20 KiB
C#
using System.Collections.Generic;
|
|
using NeoModLoader.services;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.UI;
|
|
|
|
namespace IdleSpectator;
|
|
|
|
/// <summary>
|
|
/// Compact bottom-right World Memory card: age chapters + lore landmarks (F9).
|
|
/// Sized in canvas units (same scaler as the dossier) to stay clear of top-right.
|
|
/// </summary>
|
|
public static class ChronicleHud
|
|
{
|
|
// Reference units on CanvasMain (~285x420). Keep modest so high-res scale is not a wall.
|
|
private const float PanelWidth = 220f;
|
|
private const float PanelHeight = 124f;
|
|
private const float ScreenInset = 12f;
|
|
private const float BottomClearance = 64f;
|
|
private const int MaxVisibleRows = 20;
|
|
private const float RowHeightMin = 24f;
|
|
private const float ChapterHeight = 18f;
|
|
private const float KindIconSize = 12f;
|
|
private const float TitleIconSize = 14f;
|
|
|
|
private static GameObject _root;
|
|
private static RectTransform _rootRt;
|
|
private static RectTransform _content;
|
|
private static Text _titleText;
|
|
private static Image _titleIcon;
|
|
private static readonly List<GameObject> Rows = new List<GameObject>();
|
|
private static bool _visible;
|
|
private static int _lastCount = -1;
|
|
private static string _lastAgeId = "";
|
|
|
|
public static bool Visible => _visible && _root != null && _root.activeSelf;
|
|
|
|
public static bool IsReady => _root != null;
|
|
|
|
public static void EnsureBuilt()
|
|
{
|
|
DestroyOrphanHuds();
|
|
|
|
if (_root != null)
|
|
{
|
|
ApplyRootPlacement();
|
|
return;
|
|
}
|
|
|
|
if (!Config.game_loaded && CanvasMain.instance == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Canvas canvas = HudCanvas.Resolve();
|
|
if (canvas == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_root = new GameObject("IdleSpectatorWorldMemoryHud", typeof(RectTransform), typeof(Image), typeof(CanvasGroup));
|
|
_root.transform.SetParent(canvas.transform, false);
|
|
|
|
_rootRt = _root.GetComponent<RectTransform>();
|
|
ApplyRootPlacement();
|
|
|
|
Image bg = _root.GetComponent<Image>();
|
|
bg.raycastTarget = true;
|
|
HudCanvas.StylePanel(bg, _root.transform, new Color(0.07f, 0.08f, 0.1f, 0.88f));
|
|
|
|
CanvasGroup group = _root.GetComponent<CanvasGroup>();
|
|
group.blocksRaycasts = true;
|
|
group.interactable = true;
|
|
|
|
Text f9 = HudCanvas.MakeText(_root.transform, "F9", "F9", 8);
|
|
RectTransform f9Rt = f9.GetComponent<RectTransform>();
|
|
f9Rt.anchorMin = new Vector2(1f, 1f);
|
|
f9Rt.anchorMax = new Vector2(1f, 1f);
|
|
f9Rt.pivot = new Vector2(1f, 1f);
|
|
f9Rt.sizeDelta = new Vector2(24f, 14f);
|
|
f9Rt.anchoredPosition = new Vector2(-6f, -4f);
|
|
f9.alignment = TextAnchor.MiddleRight;
|
|
f9.color = new Color(0.55f, 0.58f, 0.62f, 1f);
|
|
f9.raycastTarget = false;
|
|
|
|
_titleIcon = HudCanvas.MakeIcon(_root.transform, "TitleIcon", TitleIconSize);
|
|
RectTransform titleIconRt = _titleIcon.GetComponent<RectTransform>();
|
|
titleIconRt.anchorMin = new Vector2(0f, 1f);
|
|
titleIconRt.anchorMax = new Vector2(0f, 1f);
|
|
titleIconRt.pivot = new Vector2(0.5f, 0.5f);
|
|
titleIconRt.anchoredPosition = new Vector2(14f, -14f);
|
|
HudIcons.Apply(_titleIcon, HudIcons.FromUiIcon("iconBooks") ?? HudIcons.FromUiIcon("iconWar"));
|
|
|
|
_titleText = HudCanvas.MakeText(_root.transform, "Title", "World Memory", 9);
|
|
RectTransform titleRt = _titleText.GetComponent<RectTransform>();
|
|
titleRt.anchorMin = new Vector2(0f, 1f);
|
|
titleRt.anchorMax = new Vector2(1f, 1f);
|
|
titleRt.pivot = new Vector2(0.5f, 1f);
|
|
titleRt.offsetMin = new Vector2(24f, -24f);
|
|
titleRt.offsetMax = new Vector2(-28f, -4f);
|
|
_titleText.alignment = TextAnchor.MiddleLeft;
|
|
_titleText.color = new Color(0.78f, 0.8f, 0.84f, 1f);
|
|
_titleText.fontStyle = FontStyle.Bold;
|
|
_titleText.raycastTarget = false;
|
|
_titleText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
|
|
|
GameObject scrollGo = new GameObject(
|
|
"Scroll",
|
|
typeof(RectTransform),
|
|
typeof(Image),
|
|
typeof(ScrollRect),
|
|
typeof(RectMask2D));
|
|
scrollGo.transform.SetParent(_root.transform, false);
|
|
RectTransform scrollRt = scrollGo.GetComponent<RectTransform>();
|
|
scrollRt.anchorMin = new Vector2(0f, 0f);
|
|
scrollRt.anchorMax = new Vector2(1f, 1f);
|
|
scrollRt.offsetMin = new Vector2(5f, 5f);
|
|
scrollRt.offsetMax = new Vector2(-5f, -26f);
|
|
Image scrollBg = scrollGo.GetComponent<Image>();
|
|
scrollBg.color = new Color(0f, 0f, 0f, 0.08f);
|
|
scrollBg.raycastTarget = true;
|
|
|
|
GameObject contentGo = new GameObject(
|
|
"Content",
|
|
typeof(RectTransform),
|
|
typeof(VerticalLayoutGroup),
|
|
typeof(ContentSizeFitter));
|
|
contentGo.transform.SetParent(scrollGo.transform, false);
|
|
_content = contentGo.GetComponent<RectTransform>();
|
|
_content.anchorMin = new Vector2(0f, 1f);
|
|
_content.anchorMax = new Vector2(1f, 1f);
|
|
_content.pivot = new Vector2(0.5f, 1f);
|
|
_content.anchoredPosition = Vector2.zero;
|
|
_content.sizeDelta = new Vector2(0f, 0f);
|
|
|
|
VerticalLayoutGroup layout = contentGo.GetComponent<VerticalLayoutGroup>();
|
|
layout.childAlignment = TextAnchor.UpperCenter;
|
|
layout.childControlHeight = true;
|
|
layout.childControlWidth = true;
|
|
layout.childForceExpandHeight = false;
|
|
layout.childForceExpandWidth = true;
|
|
layout.spacing = 2f;
|
|
layout.padding = new RectOffset(0, 0, 0, 0);
|
|
|
|
ContentSizeFitter fitter = contentGo.GetComponent<ContentSizeFitter>();
|
|
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
|
fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
|
|
|
ScrollRect scroll = scrollGo.GetComponent<ScrollRect>();
|
|
scroll.content = _content;
|
|
scroll.horizontal = false;
|
|
scroll.vertical = true;
|
|
scroll.movementType = ScrollRect.MovementType.Clamped;
|
|
scroll.viewport = scrollRt;
|
|
|
|
_root.SetActive(false);
|
|
_visible = false;
|
|
LogService.LogInfo("[IdleSpectator] World Memory HUD ready (F9, compact)");
|
|
}
|
|
|
|
private static void DestroyOrphanHuds()
|
|
{
|
|
// Hot-reload / rename can leave old full-screen-looking cards in the canvas.
|
|
try
|
|
{
|
|
Canvas canvas = HudCanvas.Resolve();
|
|
if (canvas == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = canvas.transform.childCount - 1; i >= 0; i--)
|
|
{
|
|
Transform child = canvas.transform.GetChild(i);
|
|
if (child == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string n = child.name;
|
|
if (n != "IdleSpectatorChronicleHud" && n != "IdleSpectatorWorldMemoryHud")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (_root != null && child.gameObject == _root)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Object.Destroy(child.gameObject);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
private static void ApplyRootPlacement()
|
|
{
|
|
if (_root == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_rootRt == null)
|
|
{
|
|
_rootRt = _root.GetComponent<RectTransform>();
|
|
}
|
|
|
|
if (_rootRt == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Bottom-right, same canvas units as the dossier so PlayerConfig UI size scales both.
|
|
_rootRt.anchorMin = new Vector2(1f, 0f);
|
|
_rootRt.anchorMax = new Vector2(1f, 0f);
|
|
_rootRt.pivot = new Vector2(1f, 0f);
|
|
_rootRt.sizeDelta = new Vector2(PanelWidth, PanelHeight);
|
|
_rootRt.anchoredPosition = new Vector2(-ScreenInset, BottomClearance);
|
|
_rootRt.localScale = Vector3.one;
|
|
}
|
|
|
|
public static bool IsPointerOverPanel()
|
|
{
|
|
if (_rootRt == null || _root == null || !_root.activeInHierarchy)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return RectTransformUtility.RectangleContainsScreenPoint(_rootRt, Input.mousePosition, null);
|
|
}
|
|
|
|
public static bool TryGetScreenPixelRect(out Rect pixelRect)
|
|
{
|
|
pixelRect = default;
|
|
if (_rootRt == null || _root == null || !_root.activeInHierarchy)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Vector3[] corners = new Vector3[4];
|
|
_rootRt.GetWorldCorners(corners);
|
|
float xMin = float.MaxValue;
|
|
float yMin = float.MaxValue;
|
|
float xMax = float.MinValue;
|
|
float yMax = float.MinValue;
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
Vector2 sp = RectTransformUtility.WorldToScreenPoint(null, corners[i]);
|
|
xMin = Mathf.Min(xMin, sp.x);
|
|
yMin = Mathf.Min(yMin, sp.y);
|
|
xMax = Mathf.Max(xMax, sp.x);
|
|
yMax = Mathf.Max(yMax, sp.y);
|
|
}
|
|
|
|
pixelRect = Rect.MinMaxRect(xMin, yMin, xMax, yMax);
|
|
return pixelRect.width > 8f && pixelRect.height > 8f;
|
|
}
|
|
|
|
public static void SetVisible(bool visible)
|
|
{
|
|
EnsureBuilt();
|
|
if (_root == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ApplyRootPlacement();
|
|
_visible = visible;
|
|
_root.SetActive(visible);
|
|
if (visible)
|
|
{
|
|
Rebuild(force: true);
|
|
}
|
|
}
|
|
|
|
public static void Toggle()
|
|
{
|
|
SetVisible(!Visible);
|
|
}
|
|
|
|
public static void UpdateLive()
|
|
{
|
|
if (!Visible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ApplyRootPlacement();
|
|
|
|
int count = Chronicle.MemoryCount;
|
|
string ageId = Chronicle.CurrentAgeId;
|
|
if (count != _lastCount || ageId != _lastAgeId)
|
|
{
|
|
Rebuild(force: true);
|
|
}
|
|
}
|
|
|
|
public static void Rebuild(bool force = false)
|
|
{
|
|
EnsureBuilt();
|
|
if (_content == null || (!force && !Visible))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ApplyRootPlacement();
|
|
ClearRows();
|
|
|
|
IReadOnlyList<ChronicleEntry> entries = Chronicle.SnapshotMemory();
|
|
_lastCount = entries.Count;
|
|
_lastAgeId = Chronicle.CurrentAgeId;
|
|
|
|
string ageTitle = Chronicle.CurrentAgeName;
|
|
if (_titleText != null)
|
|
{
|
|
_titleText.text = string.IsNullOrEmpty(ageTitle)
|
|
? "World Memory"
|
|
: "World Memory · " + ageTitle;
|
|
}
|
|
|
|
if (entries.Count == 0)
|
|
{
|
|
AddEmpty("No landmarks yet");
|
|
return;
|
|
}
|
|
|
|
List<ChronicleEntry> display = BuildDisplayOrder(entries);
|
|
int shown = 0;
|
|
for (int i = 0; i < display.Count; i++)
|
|
{
|
|
ChronicleEntry entry = display[i];
|
|
if (entry == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
GameObject row = entry.Kind == ChronicleKind.AgeChapter
|
|
? BuildChapterRow(entry)
|
|
: BuildClickableRow(entry, shown);
|
|
row.transform.SetParent(_content, false);
|
|
Rows.Add(row);
|
|
shown++;
|
|
if (shown >= MaxVisibleRows)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static List<ChronicleEntry> BuildDisplayOrder(IReadOnlyList<ChronicleEntry> chronological)
|
|
{
|
|
var ageOrder = new List<string>();
|
|
var chapters = new Dictionary<string, ChronicleEntry>();
|
|
var byAge = new Dictionary<string, List<ChronicleEntry>>();
|
|
|
|
for (int i = 0; i < chronological.Count; i++)
|
|
{
|
|
ChronicleEntry e = chronological[i];
|
|
if (e == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string ageId = string.IsNullOrEmpty(e.AgeId) ? "unknown" : e.AgeId;
|
|
if (e.Kind == ChronicleKind.AgeChapter)
|
|
{
|
|
chapters[ageId] = e;
|
|
if (!ageOrder.Contains(ageId))
|
|
{
|
|
ageOrder.Add(ageId);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (!byAge.TryGetValue(ageId, out List<ChronicleEntry> list))
|
|
{
|
|
list = new List<ChronicleEntry>();
|
|
byAge[ageId] = list;
|
|
if (!ageOrder.Contains(ageId))
|
|
{
|
|
ageOrder.Add(ageId);
|
|
}
|
|
}
|
|
|
|
list.Add(e);
|
|
}
|
|
|
|
var result = new List<ChronicleEntry>();
|
|
for (int a = ageOrder.Count - 1; a >= 0; a--)
|
|
{
|
|
string ageId = ageOrder[a];
|
|
bool newestAge = a == ageOrder.Count - 1;
|
|
|
|
if (chapters.TryGetValue(ageId, out ChronicleEntry chapter))
|
|
{
|
|
result.Add(chapter);
|
|
}
|
|
else if (byAge.ContainsKey(ageId))
|
|
{
|
|
string name = byAge[ageId].Count > 0 ? byAge[ageId][0].AgeName : ageId;
|
|
if (string.IsNullOrEmpty(name))
|
|
{
|
|
name = ageId;
|
|
}
|
|
|
|
result.Add(new ChronicleEntry
|
|
{
|
|
Kind = ChronicleKind.AgeChapter,
|
|
AgeId = ageId,
|
|
AgeName = name,
|
|
Line = name,
|
|
LoreLine = name
|
|
});
|
|
}
|
|
|
|
if (!byAge.TryGetValue(ageId, out List<ChronicleEntry> landmarks) || landmarks.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int cap = newestAge ? MaxVisibleRows : 2;
|
|
int take = Mathf.Min(cap, landmarks.Count);
|
|
for (int i = landmarks.Count - 1; i >= landmarks.Count - take; i--)
|
|
{
|
|
result.Add(landmarks[i]);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static void AddEmpty(string text)
|
|
{
|
|
GameObject empty = BuildStaticRow(text);
|
|
empty.transform.SetParent(_content, false);
|
|
Rows.Add(empty);
|
|
}
|
|
|
|
private static void ClearRows()
|
|
{
|
|
foreach (GameObject row in Rows)
|
|
{
|
|
if (row != null)
|
|
{
|
|
Object.Destroy(row);
|
|
}
|
|
}
|
|
|
|
Rows.Clear();
|
|
}
|
|
|
|
private static GameObject BuildChapterRow(ChronicleEntry entry)
|
|
{
|
|
string title = !string.IsNullOrEmpty(entry.AgeName) ? entry.AgeName : entry.HudLine;
|
|
GameObject go = new GameObject("Chapter", typeof(RectTransform), typeof(LayoutElement));
|
|
RectTransform rt = go.GetComponent<RectTransform>();
|
|
rt.sizeDelta = new Vector2(PanelWidth - 14f, ChapterHeight);
|
|
LayoutElement le = go.GetComponent<LayoutElement>();
|
|
le.minHeight = ChapterHeight;
|
|
le.preferredHeight = ChapterHeight;
|
|
|
|
Text label = HudCanvas.MakeText(go.transform, "Text", "— " + title + " —", 8);
|
|
Stretch(label.GetComponent<RectTransform>(), 2f);
|
|
label.alignment = TextAnchor.MiddleCenter;
|
|
label.color = new Color(0.7f, 0.74f, 0.82f, 1f);
|
|
label.fontStyle = FontStyle.Bold;
|
|
label.raycastTarget = false;
|
|
return go;
|
|
}
|
|
|
|
private static GameObject BuildStaticRow(string text)
|
|
{
|
|
GameObject go = new GameObject("Empty", typeof(RectTransform), typeof(LayoutElement));
|
|
RectTransform rt = go.GetComponent<RectTransform>();
|
|
rt.sizeDelta = new Vector2(PanelWidth - 14f, RowHeightMin);
|
|
LayoutElement le = go.GetComponent<LayoutElement>();
|
|
le.minHeight = RowHeightMin;
|
|
le.preferredHeight = RowHeightMin;
|
|
|
|
Text label = HudCanvas.MakeText(go.transform, "Text", text, 8);
|
|
Stretch(label.GetComponent<RectTransform>(), 3f);
|
|
label.alignment = TextAnchor.MiddleLeft;
|
|
label.color = new Color(0.65f, 0.68f, 0.72f, 0.95f);
|
|
label.resizeTextForBestFit = false;
|
|
label.horizontalOverflow = HorizontalWrapMode.Wrap;
|
|
label.verticalOverflow = VerticalWrapMode.Overflow;
|
|
label.raycastTarget = false;
|
|
return go;
|
|
}
|
|
|
|
private static GameObject BuildClickableRow(ChronicleEntry entry, int index)
|
|
{
|
|
GameObject go = new GameObject(
|
|
$"Row_{index}",
|
|
typeof(RectTransform),
|
|
typeof(Image),
|
|
typeof(Button),
|
|
typeof(LayoutElement),
|
|
typeof(ContentSizeFitter));
|
|
RectTransform rt = go.GetComponent<RectTransform>();
|
|
rt.sizeDelta = new Vector2(PanelWidth - 14f, RowHeightMin);
|
|
|
|
LayoutElement le = go.GetComponent<LayoutElement>();
|
|
le.minHeight = RowHeightMin;
|
|
le.preferredHeight = -1f;
|
|
|
|
ContentSizeFitter rowFit = go.GetComponent<ContentSizeFitter>();
|
|
rowFit.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
|
rowFit.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
|
|
|
Image bg = go.GetComponent<Image>();
|
|
bg.color = entry.IsLegend
|
|
? new Color(0.95f, 0.85f, 0.45f, 0.06f)
|
|
: new Color(1f, 1f, 1f, 0.03f);
|
|
bg.raycastTarget = true;
|
|
|
|
Image kindIcon = HudCanvas.MakeIcon(go.transform, "Kind", KindIconSize);
|
|
RectTransform kindRt = kindIcon.GetComponent<RectTransform>();
|
|
kindRt.anchorMin = new Vector2(0f, 1f);
|
|
kindRt.anchorMax = new Vector2(0f, 1f);
|
|
kindRt.pivot = new Vector2(0.5f, 1f);
|
|
kindRt.anchoredPosition = new Vector2(9f, -5f);
|
|
kindIcon.raycastTarget = false;
|
|
HudIcons.Apply(kindIcon, HudIcons.ForChronicleKind(entry.Kind));
|
|
|
|
Text label = HudCanvas.MakeText(go.transform, "Text", entry.DisplayLineRich, 8);
|
|
RectTransform labelRt = label.GetComponent<RectTransform>();
|
|
labelRt.anchorMin = new Vector2(0f, 0f);
|
|
labelRt.anchorMax = new Vector2(1f, 1f);
|
|
labelRt.offsetMin = new Vector2(18f, 2f);
|
|
labelRt.offsetMax = new Vector2(-4f, -2f);
|
|
label.alignment = TextAnchor.UpperLeft;
|
|
label.color = ColorForKind(entry.Kind);
|
|
label.supportRichText = true;
|
|
label.resizeTextForBestFit = false;
|
|
label.horizontalOverflow = HorizontalWrapMode.Wrap;
|
|
label.verticalOverflow = VerticalWrapMode.Overflow;
|
|
label.raycastTarget = false;
|
|
|
|
ContentSizeFitter textFit = label.gameObject.AddComponent<ContentSizeFitter>();
|
|
textFit.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
|
textFit.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
|
|
|
Button button = go.GetComponent<Button>();
|
|
ColorBlock colors = button.colors;
|
|
colors.normalColor = Color.white;
|
|
colors.highlightedColor = new Color(1.2f, 1.2f, 1.25f, 1f);
|
|
colors.pressedColor = new Color(0.85f, 0.85f, 0.9f, 1f);
|
|
button.colors = colors;
|
|
|
|
ChronicleEntry captured = entry;
|
|
button.onClick.AddListener(new UnityAction(() =>
|
|
{
|
|
bool ok = Chronicle.JumpTo(captured);
|
|
LogService.LogInfo($"[IdleSpectator][MEMORY] jump ok={ok} line={captured.DisplayLine}");
|
|
}));
|
|
|
|
return go;
|
|
}
|
|
|
|
private static Color ColorForKind(ChronicleKind kind)
|
|
{
|
|
switch (kind)
|
|
{
|
|
case ChronicleKind.Death:
|
|
return new Color(0.78f, 0.72f, 0.78f, 1f);
|
|
case ChronicleKind.Kill:
|
|
return new Color(0.95f, 0.58f, 0.48f, 1f);
|
|
case ChronicleKind.Lover:
|
|
return new Color(0.95f, 0.58f, 0.78f, 1f);
|
|
case ChronicleKind.Friend:
|
|
return new Color(0.58f, 0.85f, 0.95f, 1f);
|
|
case ChronicleKind.World:
|
|
return new Color(0.95f, 0.85f, 0.48f, 1f);
|
|
case ChronicleKind.AgeChapter:
|
|
return new Color(0.7f, 0.74f, 0.82f, 1f);
|
|
default:
|
|
return new Color(0.88f, 0.88f, 0.9f, 1f);
|
|
}
|
|
}
|
|
|
|
private static void Stretch(RectTransform rt, float pad)
|
|
{
|
|
rt.anchorMin = Vector2.zero;
|
|
rt.anchorMax = Vector2.one;
|
|
rt.offsetMin = new Vector2(pad, 1f);
|
|
rt.offsetMax = new Vector2(-pad, -1f);
|
|
}
|
|
}
|