Files
bansonic_beta_main/Assets/idolsDisplay/idolEquipments.cs
T
2026-06-30 21:21:30 +08:00

907 lines
27 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using Bansonic;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class idolEquipments : MonoBehaviour
{
private enum EquipFilterMode
{
All,
HasSpecialSkill,
HasIllusionSkill,
HasAnySkill,
HasNoSkill,
HasDualSkill,
Type0,
Type1,
Type2,
Type3,
Type4,
Type5,
Type6,
Type7
}
private enum EquipOrderMode
{
AcquireAsc,
AcquireDesc,
QualityAsc,
QualityDesc
}
[Header("objects")]
public GameObject eqpmtItemPrefab;
public Transform eqpmtToPut;
[Header("INFO")]
public Text this_eqpmtName;
public Text this_eqpmtType;
public Text this_eqpmtLevel;
public Text this_eqpmtID;
[Header("waitingList")]
public int i_batch = 16;
public Transform eqpmtBagParent;
[Header("filter")]
public Toggle onlyCanEquip;
public Dropdown orderFilter;
public Dropdown typeFilter;
[Header("emptyText")]
public Text emptyText;
public Scrollbar eqpmtBagScrollbar;
[Header("source paths")]
public string runtimeEquipmentFolder = "so/uEquip";
public string editorEquipmentFolder = "Assets/Resources/so/uEquip";
private readonly List<GameObject> spawnedBagItems = new List<GameObject>();
private Coroutine rebuildRoutine;
private equipmentSO equippedEquipment;
private GameObject equippedItemInstance;
private AllyHero_SO currentHero;
private float pendingScrollbarValue = 1f;
private Coroutine restoreScrollbarRoutine;
private bool suppressDropdownCallbacks;
private static readonly (EquipFilterMode mode, string label)[] FilterOptions =
{
(EquipFilterMode.All, "全部"),
(EquipFilterMode.HasSpecialSkill, "含特效技能"),
(EquipFilterMode.HasIllusionSkill, "含巡演技能"),
(EquipFilterMode.HasAnySkill, "含技能"),
(EquipFilterMode.HasNoSkill, "不含技能"),
(EquipFilterMode.HasDualSkill, "含双技能"),
(EquipFilterMode.Type0, equipmentSO.EquipmentSkillType.题海战术.ToString()),
(EquipFilterMode.Type1, equipmentSO.EquipmentSkillType.诛锄异己.ToString()),
(EquipFilterMode.Type2, equipmentSO.EquipmentSkillType.逸乐共谋.ToString()),
(EquipFilterMode.Type3, equipmentSO.EquipmentSkillType.完美至上.ToString()),
(EquipFilterMode.Type4, equipmentSO.EquipmentSkillType.涵养心境.ToString()),
(EquipFilterMode.Type5, equipmentSO.EquipmentSkillType.众志成城.ToString()),
(EquipFilterMode.Type6, equipmentSO.EquipmentSkillType.过激行为.ToString()),
(EquipFilterMode.Type7, equipmentSO.EquipmentSkillType.二次形变.ToString())
};
private static readonly (EquipOrderMode mode, string label)[] OrderOptions =
{
(EquipOrderMode.AcquireAsc, "获取时间升序"),
(EquipOrderMode.AcquireDesc, "获取时间降序"),
(EquipOrderMode.QualityAsc, "品质升序"),
(EquipOrderMode.QualityDesc, "品质降序")
};
private void Start()
{
InitializeDropdowns();
InitializeOnlyCanEquipToggle();
BindControls();
RestoreEquippedFromHero();
RebuildBag();
RefreshEquippedSlot();
}
private void OnEnable()
{
InitializeDropdowns();
InitializeOnlyCanEquipToggle();
BindControls();
RestoreEquippedFromHero();
RebuildBag();
RefreshEquippedSlot();
}
private void OnDisable()
{
UnbindControls();
if (rebuildRoutine != null)
{
StopCoroutine(rebuildRoutine);
rebuildRoutine = null;
}
if (restoreScrollbarRoutine != null)
{
StopCoroutine(restoreScrollbarRoutine);
restoreScrollbarRoutine = null;
}
}
private void BindControls()
{
if (onlyCanEquip != null)
{
onlyCanEquip.onValueChanged.RemoveListener(HandleFilterChanged);
onlyCanEquip.onValueChanged.AddListener(HandleFilterChanged);
}
if (orderFilter != null)
{
orderFilter.onValueChanged.RemoveListener(HandleOrderChanged);
orderFilter.onValueChanged.AddListener(HandleOrderChanged);
}
if (typeFilter != null)
{
typeFilter.onValueChanged.RemoveListener(HandleTypeChanged);
typeFilter.onValueChanged.AddListener(HandleTypeChanged);
}
}
private void UnbindControls()
{
if (onlyCanEquip != null)
{
onlyCanEquip.onValueChanged.RemoveListener(HandleFilterChanged);
}
if (orderFilter != null)
{
orderFilter.onValueChanged.RemoveListener(HandleOrderChanged);
}
if (typeFilter != null)
{
typeFilter.onValueChanged.RemoveListener(HandleTypeChanged);
}
}
private void HandleFilterChanged(bool _)
{
if (!suppressDropdownCallbacks)
{
RebuildBag();
}
}
private void HandleOrderChanged(int _)
{
if (!suppressDropdownCallbacks)
{
RebuildBag();
}
}
private void HandleTypeChanged(int _)
{
if (!suppressDropdownCallbacks)
{
RebuildBag();
}
}
private void InitializeDropdowns()
{
InitializeTypeFilterDropdown();
InitializeOrderDropdown();
}
private void InitializeTypeFilterDropdown()
{
if (typeFilter == null)
{
return;
}
suppressDropdownCallbacks = true;
typeFilter.onValueChanged.RemoveListener(HandleTypeChanged);
typeFilter.ClearOptions();
typeFilter.AddOptions(FilterOptions.Select(option => new Dropdown.OptionData(option.label)).ToList());
typeFilter.value = 0;
typeFilter.RefreshShownValue();
typeFilter.onValueChanged.AddListener(HandleTypeChanged);
suppressDropdownCallbacks = false;
}
private void InitializeOrderDropdown()
{
if (orderFilter == null)
{
return;
}
suppressDropdownCallbacks = true;
orderFilter.onValueChanged.RemoveListener(HandleOrderChanged);
orderFilter.ClearOptions();
orderFilter.AddOptions(OrderOptions.Select(option => new Dropdown.OptionData(option.label)).ToList());
orderFilter.value = 3;
orderFilter.RefreshShownValue();
orderFilter.onValueChanged.AddListener(HandleOrderChanged);
suppressDropdownCallbacks = false;
}
private void InitializeOnlyCanEquipToggle()
{
if (onlyCanEquip == null)
{
return;
}
onlyCanEquip.SetIsOnWithoutNotify(true);
}
public void RebuildBag()
{
CacheScrollbarValue();
if (rebuildRoutine != null)
{
StopCoroutine(rebuildRoutine);
rebuildRoutine = null;
}
ClearBagItems();
if (eqpmtItemPrefab == null || eqpmtBagParent == null)
{
RefreshEmptyState(0);
return;
}
List<equipmentSO> equipments = LoadEquipments()
.Where(e => e != null)
.Where(e => !equipSmelt.IsEquipmentAssignedToSmeltPool(e) && !equipSmelt.ShouldHideConsumedEquipment(e))
.Where(e => equippedEquipment == null || e != equippedEquipment)
.Where(MatchesCurrentFilters)
.ToList();
SortEquipments(equipments);
RefreshEmptyState(equipments.Count);
if (Application.isPlaying && isActiveAndEnabled && gameObject.activeInHierarchy)
{
rebuildRoutine = StartCoroutine(RebuildBagAsync(equipments));
return;
}
SpawnBagItemsImmediate(equipments);
RestoreScrollbarValueImmediate();
}
private IEnumerator RebuildBagAsync(List<equipmentSO> equipments)
{
int batchSize = Mathf.Max(1, i_batch);
for (int i = 0; i < equipments.Count; i++)
{
SpawnBagItem(equipments[i]);
if ((i + 1) % batchSize == 0)
{
yield return null;
}
}
rebuildRoutine = null;
BeginRestoreScrollbarValue();
}
private void SpawnBagItemsImmediate(List<equipmentSO> equipments)
{
for (int i = 0; i < equipments.Count; i++)
{
SpawnBagItem(equipments[i]);
}
}
private void SpawnBagItem(equipmentSO equipment)
{
if (equipment == null || eqpmtItemPrefab == null || eqpmtBagParent == null)
{
return;
}
GameObject instance = Instantiate(eqpmtItemPrefab, eqpmtBagParent);
instance.name = string.IsNullOrWhiteSpace(equipment.GetDisplayTierName()) ? equipment.name : equipment.GetDisplayTierName();
equipItemPrefab item = instance.GetComponent<equipItemPrefab>();
if (item != null)
{
item.Bind(equipment, null, HandleBagItemRightClicked);
item.allowDrag = false;
item.allowQuickTransfer = false;
if (item.itemButton != null)
{
item.itemButton.interactable = true;
}
}
spawnedBagItems.Add(instance);
}
private bool HandleBagItemRightClicked(equipmentSO equipment)
{
if (equipment == null)
{
return false;
}
if (IsEquippedByOtherHero(equipment, out AllyHero_SO occupiedHero))
{
string heroName = occupiedHero != null ? occupiedHero.ally_heroName : "其他角色";
gNotice.warning.display($"该记忆已被{heroName}装备");
return true;
}
EquipEquipment(equipment);
return true;
}
private bool HandleEquippedItemRightClicked(equipmentSO equipment)
{
if (equipment == null || equippedEquipment == null || equipment != equippedEquipment)
{
return false;
}
UnequipEquipment();
return true;
}
private void EquipEquipment(equipmentSO equipment)
{
equippedEquipment = equipment;
if (currentHero != null)
{
currentHero.SetEquippedEquipment(equipment);
}
RefreshAllIdolPanels();
RefreshEquippedSlot();
RebuildBag();
}
private void UnequipEquipment()
{
equippedEquipment = null;
if (currentHero != null)
{
currentHero.ClearEquippedEquipment();
}
RefreshAllIdolPanels();
RefreshEquippedSlot();
RebuildBag();
}
public void SetHero(AllyHero_SO hero)
{
currentHero = hero;
RestoreEquippedFromHero();
RefreshEquippedSlot();
RebuildBag();
}
private void RestoreEquippedFromHero()
{
if (currentHero == null)
{
equippedEquipment = null;
return;
}
currentHero.LoadEquippedEquipmentFromLocal();
equippedEquipment = currentHero.GetEquippedEquipmentResolved();
}
private void RefreshEquippedSlot()
{
if (equippedItemInstance != null)
{
Destroy(equippedItemInstance);
equippedItemInstance = null;
}
if (equippedEquipment != null && eqpmtItemPrefab != null && eqpmtToPut != null)
{
equippedItemInstance = Instantiate(eqpmtItemPrefab, eqpmtToPut);
equippedItemInstance.name = string.IsNullOrWhiteSpace(equippedEquipment.GetDisplayTierName()) ? equippedEquipment.name : equippedEquipment.GetDisplayTierName();
equipItemPrefab item = equippedItemInstance.GetComponent<equipItemPrefab>();
if (item != null)
{
item.Bind(equippedEquipment, null, HandleEquippedItemRightClicked);
item.allowDrag = false;
item.allowQuickTransfer = false;
if (item.itemButton != null)
{
item.itemButton.interactable = true;
}
}
}
RefreshInfoPanel();
}
private void HandleEquipmentClicked(equipmentSO equipment)
{
if (equipment == null)
{
return;
}
if (this_eqpmtName != null)
{
this_eqpmtName.text = equipment.GetDisplayTierName();
this_eqpmtName.color = ResolveEquipmentColor(equipment.GetVisualQualityColorIndex());
}
if (this_eqpmtType != null)
{
this_eqpmtType.text = $"<b>{equipment.skillType}</b> 类装备";
}
if (this_eqpmtLevel != null)
{
this_eqpmtLevel.text = $"{equipment.level}追忆等阶";
}
if (this_eqpmtID != null)
{
this_eqpmtID.text = $"记忆编号{BuildDisplayEquipmentId(equipment)}";
}
}
private void RefreshInfoPanel()
{
if (equippedEquipment == null)
{
if (this_eqpmtName != null)
{
this_eqpmtName.text = "可装配一个记忆";
this_eqpmtName.color = new Color32(0x32, 0x32, 0x32, 0xFF);
}
if (this_eqpmtType != null) this_eqpmtType.text = "请选择记忆";
if (this_eqpmtLevel != null) this_eqpmtLevel.text = string.Empty;
if (this_eqpmtID != null) this_eqpmtID.text = "请选择记忆";
return;
}
HandleEquipmentClicked(equippedEquipment);
}
private bool IsEquippedByOtherHero(equipmentSO equipment, out AllyHero_SO occupiedHero)
{
occupiedHero = null;
if (equipment == null)
{
return false;
}
AllyHero_SO[] heroes = LoadHeroAssetsForOwnershipCheck();
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
if (hero == null || hero == currentHero)
{
continue;
}
hero.LoadEquippedEquipmentFromLocal();
equipmentSO equipped = hero.GetEquippedEquipmentResolved();
if (equipped == equipment || (!string.IsNullOrWhiteSpace(equipped?.name) && equipped.name == equipment.name))
{
occupiedHero = hero;
return true;
}
}
return false;
}
private AllyHero_SO[] LoadHeroAssetsForOwnershipCheck()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
if (string.IsNullOrWhiteSpace("Assets/Resources/so/ally") || !AssetDatabase.IsValidFolder("Assets/Resources/so/ally"))
{
return Array.Empty<AllyHero_SO>();
}
string[] guids = AssetDatabase.FindAssets("t:AllyHero_SO", new[] { "Assets/Resources/so/ally" });
var result = new List<AllyHero_SO>(guids.Length);
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
AllyHero_SO hero = AssetDatabase.LoadAssetAtPath<AllyHero_SO>(path);
if (hero != null)
{
result.Add(hero);
}
}
return result.ToArray();
}
#endif
return RuntimeResourcesCache.LoadAllAllyHeroes();
}
private Color ResolveEquipmentColor(int colorIndex)
{
if (eqpmtItemPrefab == null)
{
return Color.white;
}
equipItemPrefab prefab = eqpmtItemPrefab.GetComponent<equipItemPrefab>();
if (prefab == null)
{
return Color.white;
}
return prefab.GetAccentColorByVisualIndex(colorIndex);
}
private void RefreshEmptyState(int count)
{
if (emptyText != null)
{
emptyText.text = count > 0 ? string.Empty : "暂无装备";
}
}
private void RefreshAllIdolPanels()
{
UI_Idols[] panels = FindObjectsByType<UI_Idols>(FindObjectsInactive.Include, FindObjectsSortMode.None);
for (int i = 0; i < panels.Length; i++)
{
UI_Idols panel = panels[i];
if (panel != null)
{
panel.RefreshCurrentHeroDetails();
}
}
}
private void ClearBagItems()
{
for (int i = eqpmtBagParent != null ? eqpmtBagParent.childCount - 1 : -1; i >= 0; i--)
{
Transform child = eqpmtBagParent.GetChild(i);
if (child != null)
{
Destroy(child.gameObject);
}
}
spawnedBagItems.Clear();
}
private void CacheScrollbarValue()
{
if (eqpmtBagScrollbar == null)
{
pendingScrollbarValue = 1f;
return;
}
pendingScrollbarValue = eqpmtBagScrollbar.value;
}
private void BeginRestoreScrollbarValue()
{
if (!Application.isPlaying || !isActiveAndEnabled || !gameObject.activeInHierarchy)
{
RestoreScrollbarValueImmediate();
return;
}
if (restoreScrollbarRoutine != null)
{
StopCoroutine(restoreScrollbarRoutine);
}
restoreScrollbarRoutine = StartCoroutine(RestoreScrollbarValueNextFrame());
}
private IEnumerator RestoreScrollbarValueNextFrame()
{
yield return null;
Canvas.ForceUpdateCanvases();
yield return null;
Canvas.ForceUpdateCanvases();
RestoreScrollbarValueImmediate();
restoreScrollbarRoutine = null;
}
private void RestoreScrollbarValueImmediate()
{
if (eqpmtBagScrollbar == null)
{
return;
}
ScrollRect scrollRect = eqpmtBagScrollbar.GetComponentInParent<ScrollRect>();
if (scrollRect != null && scrollRect.verticalScrollbar == eqpmtBagScrollbar)
{
scrollRect.verticalNormalizedPosition = pendingScrollbarValue;
}
eqpmtBagScrollbar.value = pendingScrollbarValue;
}
private bool MatchesCurrentFilters(equipmentSO equipment)
{
if (equipment == null)
{
return false;
}
if (onlyCanEquip != null && onlyCanEquip.isOn && !IsEquipmentUsableByCurrentHero(equipment))
{
return false;
}
EquipFilterMode mode = GetSelectedFilterMode();
bool hasSpecialSkill = equipment.sa_skillID > 0;
bool hasIllusionSkill = equipment.ia_skillID > 0;
switch (mode)
{
case EquipFilterMode.All:
return true;
case EquipFilterMode.HasSpecialSkill:
return hasSpecialSkill;
case EquipFilterMode.HasIllusionSkill:
return hasIllusionSkill;
case EquipFilterMode.HasAnySkill:
return hasSpecialSkill || hasIllusionSkill;
case EquipFilterMode.HasNoSkill:
return !hasSpecialSkill && !hasIllusionSkill;
case EquipFilterMode.HasDualSkill:
return hasSpecialSkill && hasIllusionSkill;
case EquipFilterMode.Type0:
return equipment.skillType == equipmentSO.EquipmentSkillType.题海战术;
case EquipFilterMode.Type1:
return equipment.skillType == equipmentSO.EquipmentSkillType.诛锄异己;
case EquipFilterMode.Type2:
return equipment.skillType == equipmentSO.EquipmentSkillType.逸乐共谋;
case EquipFilterMode.Type3:
return equipment.skillType == equipmentSO.EquipmentSkillType.完美至上;
case EquipFilterMode.Type4:
return equipment.skillType == equipmentSO.EquipmentSkillType.涵养心境;
case EquipFilterMode.Type5:
return equipment.skillType == equipmentSO.EquipmentSkillType.众志成城;
case EquipFilterMode.Type6:
return equipment.skillType == equipmentSO.EquipmentSkillType.过激行为;
case EquipFilterMode.Type7:
return equipment.skillType == equipmentSO.EquipmentSkillType.二次形变;
default:
return true;
}
}
private bool IsEquipmentUsableByCurrentHero(equipmentSO equipment)
{
if (equipment == null || currentHero == null)
{
return false;
}
if (equipment.GetVisualTierPresentation() == null || string.IsNullOrWhiteSpace(equipment.GetDisplayTierName()))
{
return false;
}
return !IsEquippedByOtherHero(equipment, out _);
}
private void SortEquipments(List<equipmentSO> equipments)
{
if (equipments == null)
{
return;
}
EquipOrderMode mode = GetSelectedOrderMode();
switch (mode)
{
case EquipOrderMode.AcquireAsc:
equipments.Sort((a, b) => CompareAcquire(a, b, false));
break;
case EquipOrderMode.AcquireDesc:
equipments.Sort((a, b) => CompareAcquire(a, b, true));
break;
case EquipOrderMode.QualityAsc:
equipments.Sort((a, b) => CompareQuality(a, b, false));
break;
case EquipOrderMode.QualityDesc:
equipments.Sort((a, b) => CompareQuality(a, b, true));
break;
default:
equipments.Sort((a, b) => CompareAcquire(a, b, false));
break;
}
}
private EquipFilterMode GetSelectedFilterMode()
{
int index = typeFilter != null ? Mathf.Clamp(typeFilter.value, 0, FilterOptions.Length - 1) : 0;
return FilterOptions[index].mode;
}
private EquipOrderMode GetSelectedOrderMode()
{
int index = orderFilter != null ? Mathf.Clamp(orderFilter.value, 0, OrderOptions.Length - 1) : 0;
return OrderOptions[index].mode;
}
private equipmentSO[] LoadEquipments()
{
var results = new List<equipmentSO>();
var indexedByName = new Dictionary<string, int>(StringComparer.Ordinal);
#if UNITY_EDITOR
if (!Application.isPlaying)
{
results.AddRange(LoadEditorEquipments());
}
else
#endif
{
results.AddRange(Resources.LoadAll<equipmentSO>(runtimeEquipmentFolder).Where(e => e != null));
}
for (int i = 0; i < results.Count; i++)
{
equipmentSO equipment = results[i];
if (equipment == null || string.IsNullOrWhiteSpace(equipment.name))
{
continue;
}
indexedByName[equipment.name] = i;
}
IReadOnlyList<equipmentSO> runtimeGenerated = equipmentGenerator.GetRuntimeGeneratedEquipments();
if (runtimeGenerated != null)
{
for (int i = 0; i < runtimeGenerated.Count; i++)
{
equipmentSO equipment = runtimeGenerated[i];
if (equipment == null)
{
continue;
}
if (!string.IsNullOrWhiteSpace(equipment.name) && indexedByName.TryGetValue(equipment.name, out int existingIndex))
{
results[existingIndex] = equipment;
continue;
}
results.Add(equipment);
if (!string.IsNullOrWhiteSpace(equipment.name))
{
indexedByName[equipment.name] = results.Count - 1;
}
}
}
return results.ToArray();
}
private static int CompareAcquire(equipmentSO left, equipmentSO right, bool descending)
{
ParseEquipmentSortKey(left, out long leftDate, out long leftId);
ParseEquipmentSortKey(right, out long rightDate, out long rightId);
int dateCompare = leftDate.CompareTo(rightDate);
if (dateCompare != 0)
{
return descending ? -dateCompare : dateCompare;
}
int idCompare = leftId.CompareTo(rightId);
if (idCompare != 0)
{
return descending ? -idCompare : idCompare;
}
string leftName = left != null ? left.GetDisplayTierName() : string.Empty;
string rightName = right != null ? right.GetDisplayTierName() : string.Empty;
int nameCompare = string.Compare(leftName, rightName, StringComparison.Ordinal);
return descending ? -nameCompare : nameCompare;
}
private static int CompareQuality(equipmentSO left, equipmentSO right, bool descending)
{
int qualityCompare = left.GetVisualQualityColorIndex().CompareTo(right.GetVisualQualityColorIndex());
if (qualityCompare != 0)
{
return descending ? -qualityCompare : qualityCompare;
}
return CompareAcquire(left, right, true);
}
private static void ParseEquipmentSortKey(equipmentSO equipment, out long datePart, out long idPart)
{
datePart = 0L;
idPart = 0L;
if (equipment == null || string.IsNullOrWhiteSpace(equipment.name))
{
return;
}
string raw = equipment.name;
if (raw.StartsWith("type", StringComparison.OrdinalIgnoreCase))
{
int firstUnderscore = raw.IndexOf('_');
if (firstUnderscore >= 0 && firstUnderscore + 1 < raw.Length)
{
raw = raw.Substring(firstUnderscore + 1);
}
}
string[] parts = raw.Split('_');
if (parts.Length >= 2)
{
long.TryParse(parts[0], out datePart);
long.TryParse(parts[1], out idPart);
}
}
#if UNITY_EDITOR
private equipmentSO[] LoadEditorEquipments()
{
if (string.IsNullOrWhiteSpace(editorEquipmentFolder) || !AssetDatabase.IsValidFolder(editorEquipmentFolder))
{
return Array.Empty<equipmentSO>();
}
string[] guids = AssetDatabase.FindAssets("t:equipmentSO", new[] { editorEquipmentFolder });
var result = new List<equipmentSO>(guids.Length);
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
equipmentSO equipment = AssetDatabase.LoadAssetAtPath<equipmentSO>(path);
if (equipment != null)
{
result.Add(equipment);
}
}
return result.ToArray();
}
#endif
private static string BuildDisplayEquipmentId(equipmentSO equipment)
{
if (equipment == null || string.IsNullOrWhiteSpace(equipment.name))
{
return string.Empty;
}
string raw = equipment.name.Replace("(Clone)", string.Empty).Replace("(Preview)", string.Empty).Replace("(TourPreview)", string.Empty);
return raw.StartsWith("type", StringComparison.OrdinalIgnoreCase) && raw.Length > 4 ? raw.Substring(4) : raw;
}
}