Repository navigation
Expand file tree
/
Copy pathSkinUnlockModule.cs
More file actions
342 lines (296 loc) · 12.5 KB
/
Copy pathSkinUnlockModule.cs
File metadata and controls
342 lines (296 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
using System;
using System.Collections.Generic;
using System.Reflection;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
namespace HTFToolkit
{
/// <summary>
/// 皮肤图鉴:所有物品的所有皮肤以网格展示(3D 预览图 + 名称 + 稀有度),
/// 点击未解锁的皮肤即解锁(写入本地存档,与老虎机解锁同一条路)。
/// 预览图分帧渲染(PreviewQueue)。
/// </summary>
internal static class SkinUnlockModule
{
private sealed class SkinEntry
{
public Item Item;
public int SkinIndex;
public ItemSkin Skin;
}
private static List<SkinEntry> _entries;
private static bool _requested;
private static int _page;
private static GUIStyle _nameStyle;
private static GUIStyle _hintStyle;
private static GUIStyle _stateStyle;
private static FieldInfo _curLocalSaveField;
private static readonly Color[] RarityColors = new Color[]
{
new Color(0.75f, 0.75f, 0.75f), // Default 灰
new Color(0.55f, 0.85f, 0.55f), // Common 绿
new Color(0.45f, 0.7f, 1.0f), // Rare 蓝
new Color(1.0f, 0.8f, 0.25f) // Legendary 金
};
public static void BindConfig(ConfigFile cfg)
{
}
// ---------------- 解锁/查询 ----------------
private static object CurLocalSave
{
get
{
if (_curLocalSaveField == null)
_curLocalSaveField = AccessTools.Field(typeof(SaveManager), "_curLocalSave");
return _curLocalSaveField?.GetValue(null);
}
}
public static bool IsUnlocked(byte itemID, byte skinIndex)
{
try
{
object save = CurLocalSave;
if (save == null)
return false;
object list = save.GetType().GetField("UnlockedItemSkins")?.GetValue(save);
if (!(list is System.Collections.IList il))
return false;
foreach (object entry in il)
{
if (entry == null) continue;
if (Convert.ToByte(entry.GetType().GetField("ID")?.GetValue(entry)) != itemID)
continue;
object skins = entry.GetType().GetField("SkinsUnlocked")?.GetValue(entry);
if (skins is System.Collections.IList sl)
{
foreach (object s in sl)
if (Convert.ToByte(s) == skinIndex)
return true;
}
}
return false;
}
catch
{
return false;
}
}
// ---------------- 数据 ----------------
private static void EnsureEntries()
{
if (_entries != null)
return;
_entries = new List<SkinEntry>();
Item[] all = Resources.LoadAll<Item>("Items");
foreach (Item it in all)
{
if (it == null || it.SkinPreset == null || it.SkinPreset.Skins.Count <= 1)
continue;
for (int i = 1; i < it.SkinPreset.Skins.Count; i++) // 跳过默认皮
{
_entries.Add(new SkinEntry
{
Item = it,
SkinIndex = i,
Skin = it.SkinPreset.Skins[i]
});
}
}
_entries.Sort((a, b) => a.Item.ID.CompareTo(b.Item.ID));
}
private struct ItemSkinRequest
{
public Item Item;
public int SkinIndex;
}
private static readonly List<ItemSkinRequest> _pendingRequests = new List<ItemSkinRequest>();
private static int _pendingPos;
private static bool _pendingActive;
private static ItemSkinRequest? _stagedReq; // 上一帧布置、等待本帧结算的请求
public static void RequestAllSkins()
{
if (_entries == null || _entries.Count == 0)
return;
_pendingRequests.Clear();
foreach (SkinEntry e in _entries)
_pendingRequests.Add(new ItemSkinRequest { Item = e.Item, SkinIndex = e.SkinIndex });
_pendingPos = 0;
_stagedReq = null;
_pendingActive = true;
}
public static void ProcessPending()
{
if (_pendingActive)
ProcessPendingImpl();
}
/// <summary>是否还在生成预览(UI 提示用)。</summary>
public static bool IsLoading =>
_pendingActive && (_pendingPos < _pendingRequests.Count || _stagedReq != null);
private static void ProcessPendingImpl()
{
if (!_pendingActive)
return;
// 1) 发起上一帧布置的读取(AsyncGPUReadback 异步,完成后经事件写 _skinCache)
if (_stagedReq.HasValue)
{
SkinPreview.RequestStagedRead();
_stagedReq = null;
_pendingPos++;
}
// 2) 布置本帧的一个(RT 共享,每帧只能布置一个)
if (_pendingPos < _pendingRequests.Count)
{
var r = _pendingRequests[_pendingPos];
SkinPreview.Stage(r.Item, r.SkinIndex, r.Item.name + "|" + r.SkinIndex);
_stagedReq = r;
}
}
static SkinUnlockModule()
{
// 异步回读完成 → 写入皮肤缓存
SkinPreview.StagedTexReady += (key, tex) =>
{
if (tex != null && !_skinCache.ContainsKey(key))
_skinCache[key] = tex;
};
}
private static readonly Dictionary<string, Texture2D> _skinCache = new Dictionary<string, Texture2D>();
private static Texture2D GetSkinTex(Item item, int skinIndex)
{
_skinCache.TryGetValue(item.name + "|" + skinIndex, out Texture2D tex);
return tex;
}
// ---------------- UI ----------------
public static void Draw()
{
if (Event.current == null)
return;
EnsureEntries();
if (_nameStyle == null)
{
_nameStyle = new GUIStyle(GUI.skin.label)
{
alignment = TextAnchor.MiddleCenter,
wordWrap = true,
richText = true
};
_hintStyle = new GUIStyle(GUI.skin.label)
{
normal = { textColor = new Color(0.75f, 0.75f, 0.75f) }
};
_stateStyle = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter };
}
_nameStyle.fontSize = UIScale.HintFont;
_hintStyle.fontSize = UIScale.HintFont;
_stateStyle.fontSize = UIScale.HintFont;
if (_entries == null || _entries.Count == 0)
{
GUILayout.Label("没有带皮肤的物品", _hintStyle);
return;
}
// 首次进入时发起全量请求
if (!_pendingActive)
RequestAllSkins();
if (IsLoading)
GUILayout.Label($"<color=#88ccff>生成预览中…(剩余 {_pendingRequests.Count - _pendingPos})</color>", _hintStyle);
GUILayout.Label($"<b>皮肤图鉴</b> <size={UIScale.HintFont}>点击未解锁的皮肤即解锁 · 共 {_entries.Count} 款</size>", _nameStyle);
// ---- 分页网格(无滚动)----
int cols = UIScale.SkinCols;
float cellW = (UIScale.W - 30f) / cols;
float cellH = cellW + UIScale.ButtonH * 1.9f; // 图 + 名称行 + 状态行
float availH = UIScale.ContentH - UIScale.ButtonH * 2.1f;
int rows = Mathf.Max(1, Mathf.FloorToInt(availH / cellH));
int perPage = rows * cols;
int totalPages = Mathf.Max(1, Mathf.CeilToInt(_entries.Count / (float)perPage));
_page = Mathf.Clamp(_page, 0, totalPages - 1);
ToolkitMenu.PageNav(ref _page, totalPages, $"共 {_entries.Count} 款");
int start = _page * perPage;
int count = Mathf.Min(perPage, _entries.Count - start);
DrawGrid(cols, cellW, start, count);
GUILayout.Label("解锁写入本地存档;换皮请手持物品后进物品栏/老虎机界面查看", _hintStyle);
}
/// <summary>进入皮肤页时重置页码。</summary>
public static void OnPageEnter()
{
_page = 0;
}
private static void DrawGrid(int cols, float cellW, int start, int count)
{
GUILayout.BeginHorizontal();
for (int i = 0; i < count; i++)
{
if (i % cols != 0)
GUILayout.FlexibleSpace();
DrawCell(_entries[start + i], cellW);
if (i % cols == cols - 1 || i == count - 1)
{
int remainder = (i % cols) + 1;
for (int k = remainder; k < cols; k++)
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
if (i != count - 1)
GUILayout.BeginHorizontal();
}
}
}
private static void DrawCell(SkinEntry entry, float cellW)
{
Item item = entry.Item;
int skinIndex = entry.SkinIndex;
bool unlocked = IsUnlocked(item.ID, (byte)skinIndex);
Color rarityColor = RarityColors[Mathf.Clamp((int)entry.Skin.Rarity, 0, RarityColors.Length - 1)];
float imgSize = cellW - 18f;
GUILayout.BeginVertical(GUILayout.Width(cellW));
// 预览图 = 点击解锁
if (GUILayout.Button("", UIScale.ButtonStyle, GUILayout.Width(imgSize), GUILayout.Height(imgSize)))
{
if (!unlocked)
{
SaveManager.UnlockSkin(item.ID, (byte)skinIndex);
string dn = string.IsNullOrEmpty(entry.Skin.Name) ? $"皮肤{skinIndex}" : entry.Skin.Name;
ToastModule.Show($"已解锁: {dn}", Color.white);
}
else
{
ToastModule.Show("该皮肤已解锁", Color.white);
}
}
Rect btnRect = GUILayoutUtility.GetLastRect();
Texture2D tex = GetSkinTex(item, skinIndex);
if (tex != null)
{
GUI.DrawTexture(btnRect, tex);
}
else
{
Color prev = GUI.color;
GUI.color = new Color(0.25f, 0.28f, 0.32f);
GUI.DrawTexture(btnRect, Texture2D.whiteTexture);
GUI.color = prev;
}
// 稀有度边框(2px)
float border = 2f;
GUI.color = rarityColor;
GUI.DrawTexture(new Rect(btnRect.x - border, btnRect.y - border, border, btnRect.height + border * 2f), Texture2D.whiteTexture);
GUI.DrawTexture(new Rect(btnRect.x + btnRect.width, btnRect.y - border, border, btnRect.height + border * 2f), Texture2D.whiteTexture);
GUI.DrawTexture(new Rect(btnRect.x - border, btnRect.y - border, btnRect.width + border * 2f, border), Texture2D.whiteTexture);
GUI.DrawTexture(new Rect(btnRect.x - border, btnRect.y + btnRect.height, btnRect.width + border * 2f, border), Texture2D.whiteTexture);
GUI.color = Color.white;
GUILayout.Space(UIScale.ButtonH * 0.12f);
string displayName = string.IsNullOrEmpty(entry.Skin.Name) ? $"皮肤{skinIndex}" : entry.Skin.Name;
GUILayout.Label($"{displayName} <size={Mathf.Max(10, UIScale.HintFont - 1)}>{ItemGalleryModule.ItemName(item)}</size>", _nameStyle);
if (unlocked)
{
_stateStyle.normal.textColor = new Color(0.4f, 0.9f, 0.4f);
GUILayout.Label("✔ 已解锁", _stateStyle);
}
else
{
_stateStyle.normal.textColor = new Color(1f, 0.75f, 0.4f);
GUILayout.Label("🔒 点击解锁", _stateStyle);
}
GUILayout.EndVertical();
}
}
}