Files
bansonic_beta_main/Assets/scripts/UI/Panel/UI_Panel_Mail.cs
T
2026-07-20 22:19:25 +08:00

2560 lines
87 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using GameServer.Client;
using Bansonic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class UI_Panel_Mail : MonoBehaviour
{
public static event Action OnUnreadStateChanged;
private static readonly Dictionary<string, Sprite> ServerMailImageCache = new Dictionary<string, Sprite>(StringComparer.Ordinal);
private static readonly HashSet<string> ServerMailImageLoadsInFlight = new HashSet<string>(StringComparer.Ordinal);
#if UNITY_EDITOR
private const string DefaultCoinRewardIconPath = "Assets/__UI_NEW/hallway/icon.90.90/icon_coin.png";
private const string DefaultMaterialRewardIconPath = "Assets/__UI_NEW/hallway/icon.90.90/icon_piece.png";
#endif
//1234567890
[SerializeField] float anim_Time = 0.5f;
float Anim_Speed => 1f / anim_Time;
[SerializeField] List<Animator> ui_Anim;
[Header("paths")]
[SerializeField] private string editor_mailSO_Path = "Assets/Resources/so/mail_so";
[SerializeField] private string runtime_mailSO_Path = "so/mail_so";
[Header("source")]
[SerializeField] private bool fetchMailsFromServer = false;
[Header("mail objects")]
public GameObject noticeSlotPrefab;
[SerializeField] Transform content_Notice_Slot;
[Header("rewards objects")]
public GameObject rewardSlotPrefab;
[SerializeField] Transform content_Reward_Slot;
[Header("reward fallback icons")]
[SerializeField] private Sprite playerExpRewardIcon;
[SerializeField] private Sprite coinRewardIcon;
[SerializeField] private Sprite materialRewardIcon;
[Header("texts")]
public Text thisMail_title;
public Text thisMail_body;
public Text thisMail_time;
public Text thisMail_sender;
List<mail_so> cachedMails = new List<mail_so>();
readonly Dictionary<int, string> _serverMailImageUrls = new Dictionary<int, string>();
mailSlotPrefab selectedSlot;
mail_so selectedMailData;
bool _mailServiceSubscribed;
Coroutine noticeSlotsLayoutRefreshRoutine;
Coroutine rewardSlotsLayoutRefreshRoutine;
public bool UsesServerMail => fetchMailsFromServer;
[Header("buttons")]
public Button receive_this_mail;
public Button backButton;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
CloseMailPanel();
}
}
void Start()
{
foreach (var item in ui_Anim)
{
item.speed = Anim_Speed;
}
MailRewardGrantService.ConfigureBasicRewardIcons(playerExpRewardIcon, coinRewardIcon, materialRewardIcon);
BindTopLevelButtons();
InitializeMailSource();
}
private void OnEnable()
{
MailRewardGrantService.ConfigureBasicRewardIcons(playerExpRewardIcon, coinRewardIcon, materialRewardIcon);
InitializeMailSource();
}
private void OnDisable()
{
UnsubscribeMailService();
}
#if UNITY_EDITOR
private void OnValidate()
{
TryAssignDefaultRewardIcons();
}
private void Reset()
{
TryAssignDefaultRewardIcons();
}
private void TryAssignDefaultRewardIcons()
{
bool changed = false;
if (coinRewardIcon == null)
{
Sprite coinIcon = AssetDatabase.LoadAssetAtPath<Sprite>(DefaultCoinRewardIconPath);
if (coinIcon != null)
{
coinRewardIcon = coinIcon;
changed = true;
}
}
if (materialRewardIcon == null)
{
Sprite materialIcon = AssetDatabase.LoadAssetAtPath<Sprite>(DefaultMaterialRewardIconPath);
if (materialIcon != null)
{
materialRewardIcon = materialIcon;
changed = true;
}
}
if (changed && !Application.isPlaying)
{
EditorUtility.SetDirty(this);
}
}
#endif
void InitializeMailSource()
{
if (!isActiveAndEnabled)
{
return;
}
BindTopLevelButtons();
if (!ShouldUseServerMail())
{
UnsubscribeMailService();
BuildMailSlots();
return;
}
MailHttpService service = MailHttpService.Instance;
if (service == null)
{
BuildMailSlots(new List<mail_so>());
return;
}
if (!_mailServiceSubscribed)
{
service.OnMailListChanged += HandleServerMailListChanged;
_mailServiceSubscribed = true;
}
service.SetServerModeEnabled(true);
HandleServerMailListChanged(service.CachedMails);
service.RequestRefresh();
}
void BuildMailSlots()
{
BuildMailSlots(LoadMailAssets());
}
void BuildMailSlots(List<mail_so> mails)
{
if (noticeSlotPrefab == null || content_Notice_Slot == null) return;
CleanupGeneratedRuntimeMails();
selectedSlot = null;
selectedMailData = null;
for (int i = content_Notice_Slot.childCount - 1; i >= 0; i--)
{
Destroy(content_Notice_Slot.GetChild(i).gameObject);
}
cachedMails = mails ?? new List<mail_so>();
if (cachedMails.Count == 0)
{
ShowMailDetails(null);
ShowMailRewards(null);
RefreshReceiveButton();
RequestNoticeSlotsLayoutRebuild();
return;
}
ApplySavedState(cachedMails);
for (int i = 0; i < cachedMails.Count; i++)
{
mail_so mail = cachedMails[i];
if (mail == null || mail.rewardList == null)
{
continue;
}
for (int rewardIndex = 0; rewardIndex < mail.rewardList.Count; rewardIndex++)
{
MailRewardGrantService.PopulateRewardDisplay(mail.rewardList[rewardIndex]);
}
}
for (int i = 0; i < mails.Count; i++)
{
var mail = mails[i];
if (mail == null) continue;
var go = Instantiate(noticeSlotPrefab, content_Notice_Slot);
UI_OrderedEntryAnimator.PlayFadeOnly(go, i, 0.18f, 0.012f, true);
var slot = go.GetComponent<mailSlotPrefab>();
if (selectedSlot == null) selectedSlot = slot;
if (slot != null)
{
if (slot.mailTitle != null) slot.mailTitle.text = mail.mail_title;
if (slot.mailSender != null) slot.mailSender.text = mail.mail_sender;
if (slot.mailTime != null) slot.mailTime.text = mail.mail_date;
if (slot.mailImage != null)
{
slot.mailImage.sprite = mail.mail_image;
}
slot.RefreshRewardsWithMailText(mail);
slot.RefreshRewardPreviews(mail);
TryBindServerMailImage(mail, slot);
UpdateSlotVisuals(slot, mail);
SetSelectedState(slot, false);
var openButton = slot.openButton != null ? slot.openButton : go.GetComponent<Button>();
if (openButton != null)
{
openButton.onClick.AddListener(() => OnMailOpen(slot, mail));
}
if (slot.receivedButton != null)
{
slot.receivedButton.onClick.AddListener(() => OnMailReceived(slot, mail));
}
}
}
RequestNoticeSlotsLayoutRebuild();
if (selectedSlot != null && cachedMails != null)
{
for (int i = 0; i < cachedMails.Count; i++)
{
var mail = cachedMails[i];
if (mail == null) continue;
OnMailOpen(selectedSlot, mail);
break;
}
}
}
List<mail_so> LoadMailAssets()
{
var result = new List<mail_so>();
#if UNITY_EDITOR
if (!Application.isPlaying && !string.IsNullOrEmpty(editor_mailSO_Path))
{
var guids = UnityEditor.AssetDatabase.FindAssets("t:mail_so", new[] { editor_mailSO_Path });
foreach (var guid in guids)
{
var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
var asset = UnityEditor.AssetDatabase.LoadAssetAtPath<mail_so>(path);
if (asset != null) result.Add(asset);
}
return result;
}
#endif
if (!string.IsNullOrEmpty(runtime_mailSO_Path))
{
var assets = RuntimeResourcesCache.LoadAll<mail_so>(runtime_mailSO_Path);
if (assets != null && assets.Length > 0)
{
result.AddRange(assets);
}
}
return result;
}
void HandleServerMailListChanged(IReadOnlyList<MailApiEntry> entries)
{
if (!isActiveAndEnabled || !ShouldUseServerMail())
{
return;
}
BuildMailSlots(ConvertServerMails(entries));
}
void UnsubscribeMailService()
{
if (!_mailServiceSubscribed)
{
return;
}
MailHttpService service = MailHttpService.Instance;
if (service != null)
{
service.OnMailListChanged -= HandleServerMailListChanged;
}
_mailServiceSubscribed = false;
}
List<mail_so> ConvertServerMails(IReadOnlyList<MailApiEntry> entries)
{
var result = new List<mail_so>();
_serverMailImageUrls.Clear();
if (entries == null)
{
return result;
}
for (int i = 0; i < entries.Count; i++)
{
MailApiEntry entry = entries[i];
if (entry == null || entry.mail_id <= 0)
{
continue;
}
mail_so mail = ScriptableObject.CreateInstance<mail_so>();
mail.hideFlags = HideFlags.DontSave;
mail.mail_Type = mail_so.mail_type.common;
mail.mail_id = entry.mail_id;
mail.mail_title = entry.mail_title ?? string.Empty;
mail.mail_sender = entry.mail_sender ?? string.Empty;
mail.mail_date = entry.mail_date ?? string.Empty;
mail.mail_description = string.IsNullOrWhiteSpace(entry.mail_description)
? (entry.mail_title ?? string.Empty)
: entry.mail_description;
mail.mail_body = entry.mail_body ?? string.Empty;
mail.mail_image = null;
if (!string.IsNullOrWhiteSpace(entry.mail_image_url))
{
_serverMailImageUrls[mail.mail_id] = entry.mail_image_url.Trim();
}
mail.rewardList = new List<mail_so.rewardItem>();
if (entry.rewards != null)
{
for (int rewardIndex = 0; rewardIndex < entry.rewards.Count; rewardIndex++)
{
MailApiRewardEntry rewardEntry = entry.rewards[rewardIndex];
if (rewardEntry == null)
{
continue;
}
var reward = new mail_so.rewardItem
{
rewardName = rewardEntry.reward_name ?? string.Empty,
reward_Type = MailRewardGrantService.ParseRewardType(rewardEntry.reward_type),
reward_ammount = Mathf.Max(1, rewardEntry.reward_amount),
reward_description = rewardEntry.reward_description ?? string.Empty,
reward_key = rewardEntry.reward_key ?? string.Empty,
reward_store_item_id = rewardEntry.reward_store_item_id,
reward_image = null,
reward_icon_url = rewardEntry.reward_icon_url ?? string.Empty
};
MailRewardGrantService.PopulateRewardDisplay(reward);
mail.rewardList.Add(reward);
}
}
result.Add(mail);
}
return result;
}
void CleanupGeneratedRuntimeMails()
{
if (!ShouldUseServerMail() || cachedMails == null)
{
return;
}
for (int i = 0; i < cachedMails.Count; i++)
{
mail_so mail = cachedMails[i];
if (mail != null)
{
Destroy(mail);
}
}
}
bool ShouldUseServerMail()
{
return fetchMailsFromServer && Application.isPlaying && OnlineModeSettings.IsOnlineEnabled;
}
void OnMailRead(mailSlotPrefab slot, mail_so mail)
{
if (mail == null) return;
if (!mail.isRead)
{
mail.isRead = true;
UpdateSlotVisuals(slot, mail);
SaveMailState();
}
}
void OnMailOpen(mailSlotPrefab slot, mail_so mail)
{
if (mail == null) return;
SetSelectedSlot(slot);
selectedMailData = mail;
ShowMailDetails(mail);
ShowMailRewards(mail);
OnMailRead(slot, mail);
RefreshReceiveButton();
PlayMailDetailsRefresh();
}
async void OnMailReceived(mailSlotPrefab slot, mail_so mail)
{
if (mail == null) return;
if (mail.rewardList == null || mail.rewardList.Count == 0) return;
if (!mail.isReceived)
{
if (!MailRewardGrantService.TryGrantAll(mail, out string failureMessage))
{
gNotice.error.display(string.IsNullOrWhiteSpace(failureMessage) ? LocalizationService.Get("mail.claim_failed", "邮件奖励领取失败") : failureMessage);
return;
}
mail.isReceived = true;
UpdateSlotVisuals(slot, mail);
SaveMailState();
RefreshReceiveButton();
if (ShouldUseServerMail() && mail.mail_id > 0 && MailHttpService.Instance != null)
{
await MailHttpService.Instance.NotifyMailClaimedAsync(mail.mail_id);
}
}
}
void UpdateSlotVisuals(mailSlotPrefab slot, mail_so mail)
{
if (slot == null || mail == null) return;
if (slot.isRead_redDot != null) slot.isRead_redDot.enabled = !mail.isRead;
if (slot.hasGift_icon != null)
{
bool hasReward = mail.rewardList != null && mail.rewardList.Count > 0;
slot.hasGift_icon.enabled = hasReward && !mail.isReceived;
}
}
void ShowMailDetails(mail_so mail)
{
if (mail == null)
{
if (thisMail_title != null) thisMail_title.text = string.Empty;
if (thisMail_body != null) thisMail_body.text = string.Empty;
if (thisMail_time != null) thisMail_time.text = string.Empty;
if (thisMail_sender != null) thisMail_sender.text = string.Empty;
return;
}
if (thisMail_title != null) thisMail_title.text = mail.mail_title;
if (thisMail_body != null) thisMail_body.text = mail.mail_body;
if (thisMail_time != null) thisMail_time.text = mail.mail_date;
if (thisMail_sender != null) thisMail_sender.text = mail.mail_sender;
}
void ShowMailRewards(mail_so mail)
{
if (content_Reward_Slot == null || rewardSlotPrefab == null) return;
for (int i = content_Reward_Slot.childCount - 1; i >= 0; i--)
{
Destroy(content_Reward_Slot.GetChild(i).gameObject);
}
if (mail == null || mail.rewardList == null || mail.rewardList.Count == 0)
{
RequestRewardSlotsLayoutRebuild();
return;
}
for (int i = 0; i < mail.rewardList.Count; i++)
{
var reward = mail.rewardList[i];
if (reward == null) continue;
var go = Instantiate(rewardSlotPrefab, content_Reward_Slot);
UI_OrderedEntryAnimator.PlayFadeOnly(go, i, 0.16f, 0.02f, true);
var slot = go.GetComponent<rewardSlotPrefab>();
if (slot == null) continue;
slot.BindReward(reward);
if (slot.detailBtm != null) slot.detailBtm.SetActive(false);
}
RequestRewardSlotsLayoutRebuild();
}
void RequestNoticeSlotsLayoutRebuild()
{
if (content_Notice_Slot == null)
{
return;
}
if (noticeSlotsLayoutRefreshRoutine != null)
{
StopCoroutine(noticeSlotsLayoutRefreshRoutine);
noticeSlotsLayoutRefreshRoutine = null;
}
if (isActiveAndEnabled)
{
noticeSlotsLayoutRefreshRoutine = StartCoroutine(RebuildNoticeSlotsLayoutNextFrame());
return;
}
RebuildLayoutNow(content_Notice_Slot);
}
void RequestRewardSlotsLayoutRebuild()
{
if (content_Reward_Slot == null)
{
return;
}
if (rewardSlotsLayoutRefreshRoutine != null)
{
StopCoroutine(rewardSlotsLayoutRefreshRoutine);
rewardSlotsLayoutRefreshRoutine = null;
}
if (isActiveAndEnabled)
{
rewardSlotsLayoutRefreshRoutine = StartCoroutine(RebuildRewardSlotsLayoutNextFrame());
return;
}
RebuildLayoutNow(content_Reward_Slot);
}
IEnumerator RebuildNoticeSlotsLayoutNextFrame()
{
yield return null;
RebuildLayoutNow(content_Notice_Slot);
noticeSlotsLayoutRefreshRoutine = null;
}
IEnumerator RebuildRewardSlotsLayoutNextFrame()
{
yield return null;
RebuildLayoutNow(content_Reward_Slot);
rewardSlotsLayoutRefreshRoutine = null;
}
void RebuildLayoutNow(Transform target)
{
RectTransform rect = target as RectTransform;
if (rect == null)
{
return;
}
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate(rect);
Canvas.ForceUpdateCanvases();
}
void PlayMailDetailsRefresh()
{
GameObject target = null;
if (thisMail_title != null) target = thisMail_title.gameObject;
if (target == null && thisMail_body != null) target = thisMail_body.gameObject;
if (target == null && content_Reward_Slot != null) target = content_Reward_Slot.gameObject;
UI_OrderedEntryAnimator.PlayRefresh(target, 0.14f, -6f, 0.99f, true);
}
void SetSelectedSlot(mailSlotPrefab slot)
{
if (content_Notice_Slot == null) return;
selectedSlot = slot;
for (int i = 0; i < content_Notice_Slot.childCount; i++)
{
var child = content_Notice_Slot.GetChild(i);
var s = child.GetComponent<mailSlotPrefab>();
SetSelectedState(s, s == selectedSlot);
}
}
void BindTopLevelButtons()
{
if (receive_this_mail != null)
{
receive_this_mail.onClick.RemoveListener(HandleReceiveCurrentMailClicked);
receive_this_mail.onClick.AddListener(HandleReceiveCurrentMailClicked);
RefreshReceiveButton();
}
if (backButton != null)
{
backButton.onClick.RemoveListener(HandleBackButtonClicked);
backButton.onClick.AddListener(HandleBackButtonClicked);
}
}
void HandleReceiveCurrentMailClicked()
{
if (selectedMailData == null)
{
return;
}
OnMailReceived(selectedSlot, selectedMailData);
}
void HandleBackButtonClicked()
{
CloseMailPanel();
}
void CloseMailPanel()
{
UI_PanelExitUtility.PlayExitThen(gameObject, () =>
{
if (gameObject != null)
{
gameObject.SetActive(false);
}
});
}
void RefreshReceiveButton()
{
if (receive_this_mail == null)
{
return;
}
bool canReceive = selectedMailData != null
&& selectedMailData.rewardList != null
&& selectedMailData.rewardList.Count > 0
&& !selectedMailData.isReceived;
receive_this_mail.interactable = canReceive;
}
void TryBindServerMailImage(mail_so mail, mailSlotPrefab slot)
{
if (mail == null || slot == null || slot.mailImage == null)
{
return;
}
if (mail.mail_image != null)
{
slot.mailImage.sprite = mail.mail_image;
SetMailImageVisible(slot, true);
return;
}
if (!_serverMailImageUrls.TryGetValue(mail.mail_id, out string url) || string.IsNullOrWhiteSpace(url))
{
SetMailImageVisible(slot, false);
return;
}
string normalizedUrl = NormalizeMailImageUrl(url);
if (string.IsNullOrWhiteSpace(normalizedUrl))
{
SetMailImageVisible(slot, false);
return;
}
if (ServerMailImageCache.TryGetValue(normalizedUrl, out Sprite cachedSprite) && cachedSprite != null)
{
mail.mail_image = cachedSprite;
slot.mailImage.sprite = cachedSprite;
SetMailImageVisible(slot, true);
return;
}
if (!ServerMailImageLoadsInFlight.Add(normalizedUrl))
{
return;
}
SetMailImageVisible(slot, false);
StartCoroutine(LoadServerMailImageRoutine(normalizedUrl, mail, slot));
}
IEnumerator LoadServerMailImageRoutine(string imageUrl, mail_so mail, mailSlotPrefab slot)
{
using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(imageUrl))
{
yield return request.SendWebRequest();
try
{
if (request.result != UnityWebRequest.Result.Success)
{
Debug.LogWarning($"[UI_Panel_Mail] Failed to load mail image: {request.error} | {imageUrl}");
yield break;
}
Texture2D texture = DownloadHandlerTexture.GetContent(request);
if (texture == null)
{
yield break;
}
Sprite sprite = Sprite.Create(
texture,
new Rect(0f, 0f, texture.width, texture.height),
new Vector2(0.5f, 0.5f));
ServerMailImageCache[imageUrl] = sprite;
if (mail != null)
{
mail.mail_image = sprite;
}
if (slot != null && slot.mailImage != null)
{
slot.mailImage.sprite = sprite;
SetMailImageVisible(slot, true);
}
}
finally
{
ServerMailImageLoadsInFlight.Remove(imageUrl);
}
}
}
private static void SetMailImageVisible(mailSlotPrefab slot, bool visible)
{
if (slot == null || slot.mailImage == null)
{
return;
}
GameObject imageObject = slot.mailImage.gameObject;
if (imageObject != null && imageObject.activeSelf != visible)
{
imageObject.SetActive(visible);
}
}
string NormalizeMailImageUrl(string imageUrl)
{
string trimmed = imageUrl?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(trimmed))
{
return string.Empty;
}
if (trimmed.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
|| trimmed.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
return trimmed;
}
NetworkManager network = NetworkManager.Instance;
if (network == null)
{
return trimmed;
}
string baseUrl = network.ServerUrl ?? string.Empty;
if (string.IsNullOrWhiteSpace(baseUrl))
{
return trimmed;
}
baseUrl = baseUrl.TrimEnd('/');
if (!trimmed.StartsWith("/"))
{
trimmed = "/" + trimmed;
}
return baseUrl + trimmed;
}
void SetSelectedState(mailSlotPrefab slot, bool selected)
{
if (slot == null || slot.selectedImage == null) return;
slot.selectedImage.enabled = selected;
}
void ApplySavedState(List<mail_so> mails)
{
var data = LoadMailState();
if (data == null || data.entries == null) return;
var map = new Dictionary<int, MailSaveEntry>();
for (int i = 0; i < data.entries.Count; i++)
{
var e = data.entries[i];
map[e.mail_id] = e;
}
for (int i = 0; i < mails.Count; i++)
{
var mail = mails[i];
if (mail == null) continue;
if (map.TryGetValue(mail.mail_id, out var e))
{
mail.isRead = e.isRead;
mail.isReceived = e.isReceived;
}
}
NotifyUnreadStateChanged();
}
void SaveMailState()
{
if (cachedMails == null) return;
var data = new MailSaveData();
for (int i = 0; i < cachedMails.Count; i++)
{
var mail = cachedMails[i];
if (mail == null) continue;
data.entries.Add(new MailSaveEntry
{
mail_id = mail.mail_id,
isRead = mail.isRead,
isReceived = mail.isReceived
});
}
SecureSaveVault.SaveJson("mail_state", "runtime", data, GetLegacySavePath());
NotifyUnreadStateChanged();
}
MailSaveData LoadMailState()
{
MailSaveData data;
if (!SecureSaveVault.TryLoadJson("mail_state", "runtime", out data, GetLegacySavePath()))
{
return null;
}
return data;
}
string GetLegacySavePath()
{
return Path.Combine(Application.persistentDataPath, "mail_state.json");
}
public static bool HasUnreadMails(bool useServerMail)
{
return useServerMail && Application.isPlaying
? HasUnreadServerMails()
: HasUnreadSoMails();
}
static bool HasUnreadSoMails()
{
var mails = RuntimeResourcesCache.LoadAllMailDefinitions();
if (mails == null || mails.Length == 0)
{
return false;
}
var saved = LoadMailStateStatic();
foreach (var mail in mails)
{
if (mail == null)
{
continue;
}
bool isRead = mail.isRead;
if (saved.TryGetValue(mail.mail_id, out var entry))
{
isRead = entry.isRead;
}
if (!isRead)
{
return true;
}
}
return false;
}
static bool HasUnreadServerMails()
{
MailHttpService service = MailHttpService.Instance;
var mails = service != null ? service.CachedMails : null;
if (mails == null || mails.Count == 0)
{
return false;
}
var saved = LoadMailStateStatic();
for (int i = 0; i < mails.Count; i++)
{
var mail = mails[i];
if (mail == null || mail.mail_id <= 0)
{
continue;
}
bool isRead = false;
if (saved.TryGetValue(mail.mail_id, out var entry))
{
isRead = entry.isRead;
}
if (!isRead)
{
return true;
}
}
return false;
}
static Dictionary<int, MailSaveEntry> LoadMailStateStatic()
{
var map = new Dictionary<int, MailSaveEntry>();
if (!SecureSaveVault.TryLoadJson("mail_state", "runtime", out MailSaveData data, GetLegacySavePathStatic()))
{
return map;
}
if (data == null || data.entries == null)
{
return map;
}
for (int i = 0; i < data.entries.Count; i++)
{
var entry = data.entries[i];
map[entry.mail_id] = entry;
}
return map;
}
internal static HashSet<int> LoadReceivedMailIdsStatic()
{
var result = new HashSet<int>();
var map = LoadMailStateStatic();
foreach (var pair in map)
{
if (pair.Value != null && pair.Value.isReceived)
{
result.Add(pair.Key);
}
}
return result;
}
static void SaveMailStateStatic(Dictionary<int, MailSaveEntry> stateMap)
{
var data = new MailSaveData();
if (stateMap != null)
{
foreach (var pair in stateMap.OrderBy(pair => pair.Key))
{
if (pair.Value == null)
{
continue;
}
data.entries.Add(pair.Value);
}
}
SecureSaveVault.SaveJson("mail_state", "runtime", data, GetLegacySavePathStatic());
}
static string GetLegacySavePathStatic()
{
return Path.Combine(Application.persistentDataPath, "mail_state.json");
}
internal static void ProcessServerMailDeletions(IReadOnlyList<MailApiDeletionEntry> deletions)
{
if (deletions == null || deletions.Count == 0)
{
return;
}
Dictionary<int, MailSaveEntry> mailState = LoadMailStateStatic();
HashSet<int> processedDeletionIds = LoadProcessedDeletionIds();
bool mailStateChanged = false;
bool deletionStateChanged = false;
for (int i = 0; i < deletions.Count; i++)
{
MailApiDeletionEntry deletion = deletions[i];
if (deletion == null || deletion.deletion_id <= 0 || deletion.mail_id <= 0)
{
continue;
}
if (processedDeletionIds.Contains(deletion.deletion_id))
{
continue;
}
bool reclaimSucceeded = true;
if (deletion.reclaim_rewards)
{
mail_so runtimeMail = BuildRuntimeMailFromDeletion(deletion);
if (runtimeMail != null)
{
reclaimSucceeded = MailRewardGrantService.TryReclaimAll(runtimeMail, out string reclaimFailure);
UnityEngine.Object.Destroy(runtimeMail);
if (!reclaimSucceeded)
{
Debug.LogWarning($"[UI_Panel_Mail] Failed to reclaim deleted mail rewards: mailId={deletion.mail_id} reason={reclaimFailure}");
}
}
}
if (!reclaimSucceeded)
{
continue;
}
if (mailState.Remove(deletion.mail_id))
{
mailStateChanged = true;
}
processedDeletionIds.Add(deletion.deletion_id);
deletionStateChanged = true;
}
if (mailStateChanged)
{
SaveMailStateStatic(mailState);
}
if (deletionStateChanged)
{
SaveProcessedDeletionIds(processedDeletionIds);
}
if (mailStateChanged || deletionStateChanged)
{
NotifyUnreadStateChanged();
}
}
static mail_so BuildRuntimeMailFromDeletion(MailApiDeletionEntry deletion)
{
if (deletion == null)
{
return null;
}
var mail = ScriptableObject.CreateInstance<mail_so>();
mail.mail_id = deletion.mail_id;
mail.mail_title = deletion.title ?? string.Empty;
mail.mail_sender = string.Empty;
mail.mail_date = deletion.deleted_at ?? string.Empty;
mail.mail_body = string.Empty;
mail.mail_Type = mail_so.mail_type.rewards;
if (deletion.rewards != null)
{
for (int i = 0; i < deletion.rewards.Count; i++)
{
MailApiRewardEntry rewardEntry = deletion.rewards[i];
if (rewardEntry == null)
{
continue;
}
var reward = new mail_so.rewardItem
{
rewardName = rewardEntry.reward_name ?? string.Empty,
reward_Type = MailRewardGrantService.ParseRewardType(rewardEntry.reward_type),
reward_ammount = Mathf.Max(1, rewardEntry.reward_amount),
reward_description = rewardEntry.reward_description ?? string.Empty,
reward_key = rewardEntry.reward_key ?? string.Empty,
reward_store_item_id = rewardEntry.reward_store_item_id,
};
MailRewardGrantService.PopulateRewardDisplay(reward);
mail.rewardList.Add(reward);
}
}
return mail;
}
static HashSet<int> LoadProcessedDeletionIds()
{
var ids = new HashSet<int>();
if (!SecureSaveVault.TryLoadJson("mail_deletion_state", "runtime", out MailDeletionStateData data, GetDeletionStatePathStatic()))
{
return ids;
}
if (data == null || data.processedDeletionIds == null)
{
return ids;
}
for (int i = 0; i < data.processedDeletionIds.Count; i++)
{
ids.Add(data.processedDeletionIds[i]);
}
return ids;
}
static void SaveProcessedDeletionIds(HashSet<int> ids)
{
var data = new MailDeletionStateData();
if (ids != null)
{
data.processedDeletionIds.AddRange(ids.OrderBy(id => id));
}
SecureSaveVault.SaveJson("mail_deletion_state", "runtime", data, GetDeletionStatePathStatic());
}
static string GetDeletionStatePathStatic()
{
return Path.Combine(Application.persistentDataPath, "mail_deletion_state.json");
}
internal static void NotifyUnreadStateChanged()
{
OnUnreadStateChanged?.Invoke();
}
[Serializable]
class MailSaveData
{
public List<MailSaveEntry> entries = new List<MailSaveEntry>();
}
[Serializable]
class MailSaveEntry
{
public int mail_id;
public bool isRead;
public bool isReceived;
}
[Serializable]
class MailDeletionStateData
{
public List<int> processedDeletionIds = new List<int>();
}
IEnumerator C_Slot_Show()
{
var slots = content_Notice_Slot.GetComponentsInChildren<Button>();
foreach (var item in slots)
{
item.gameObject.SetActive(false);
}
foreach (var item in slots)
{
yield return new WaitForSeconds(0.1f);
item.gameObject.SetActive(true);
}
}
}
public class MailHttpService : MonoBehaviour
{
public static MailHttpService Instance { get; private set; }
private const float VersionPollSeconds = 20f;
private readonly List<MailApiEntry> _cachedMails = new List<MailApiEntry>();
private string _revision = string.Empty;
private bool _serverModeEnabled;
private bool _isRefreshing;
private Coroutine _pollCoroutine;
private readonly HashSet<int> _claimSyncInFlight = new HashSet<int>();
public event Action<IReadOnlyList<MailApiEntry>> OnMailListChanged;
public IReadOnlyList<MailApiEntry> CachedMails => _cachedMails;
public bool IsServerModeEnabled => _serverModeEnabled;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Bootstrap()
{
if (Instance != null)
{
return;
}
GameObject go = new GameObject("MailHttpService");
UnityEngine.Object.DontDestroyOnLoad(go);
Instance = go.AddComponent<MailHttpService>();
}
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
public void SetServerModeEnabled(bool enabled)
{
if (OnlineModeSettings.IsLocalOnlyMode)
{
enabled = false;
}
if (_serverModeEnabled == enabled)
{
if (enabled && _pollCoroutine == null)
{
_pollCoroutine = StartCoroutine(PollRoutine());
}
return;
}
_serverModeEnabled = enabled;
if (_serverModeEnabled)
{
if (_pollCoroutine == null)
{
_pollCoroutine = StartCoroutine(PollRoutine());
}
RequestRefresh();
}
else if (_pollCoroutine != null)
{
StopCoroutine(_pollCoroutine);
_pollCoroutine = null;
}
UI_Panel_Mail.NotifyUnreadStateChanged();
}
public void RequestRefresh()
{
if (!_serverModeEnabled || _isRefreshing || OnlineModeSettings.IsLocalOnlyMode)
{
return;
}
StartCoroutine(ForceRefreshRoutine());
}
private IEnumerator ForceRefreshRoutine()
{
if (!_serverModeEnabled || _isRefreshing)
{
yield break;
}
NetworkManager network = NetworkManager.Instance;
if (network == null)
{
yield break;
}
_isRefreshing = true;
MailApiListResponse response = null;
Exception refreshException = null;
yield return network.GetMailListRoutine((result, error) =>
{
response = result;
refreshException = error;
});
try
{
if (refreshException != null)
{
Debug.LogWarning($"[MailHttpService] Failed to refresh mails: {refreshException.Message}");
yield break;
}
if (response == null || !response.success)
{
yield break;
}
_revision = response.revision ?? string.Empty;
UI_Panel_Mail.ProcessServerMailDeletions(response.deletions);
_cachedMails.Clear();
if (response.mails != null)
{
_cachedMails.AddRange(response.mails);
}
OnMailListChanged?.Invoke(_cachedMails);
UI_Panel_Mail.NotifyUnreadStateChanged();
_ = SyncClaimedMailStateAsync();
}
finally
{
_isRefreshing = false;
}
}
private IEnumerator PollRoutine()
{
while (_serverModeEnabled)
{
yield return new WaitForSecondsRealtime(VersionPollSeconds);
NetworkManager network = NetworkManager.Instance;
if (network == null || _isRefreshing)
{
continue;
}
MailApiVersionResponse version = null;
Exception versionException = null;
yield return network.GetMailVersionRoutine((result, error) =>
{
version = result;
versionException = error;
});
if (versionException != null)
{
continue;
}
if (version == null || !version.success)
{
continue;
}
string revision = version.revision ?? string.Empty;
if (!string.Equals(revision, _revision, StringComparison.Ordinal))
{
RequestRefresh();
while (_isRefreshing)
{
yield return null;
}
}
}
_pollCoroutine = null;
}
public async System.Threading.Tasks.Task NotifyMailClaimedAsync(int mailId)
{
if (!_serverModeEnabled || mailId <= 0 || OnlineModeSettings.IsLocalOnlyMode)
{
return;
}
NetworkManager network = NetworkManager.Instance;
if (network == null)
{
return;
}
if (!_claimSyncInFlight.Add(mailId))
{
return;
}
try
{
MailClaimResponse response = await network.ClaimMail(mailId);
if (response == null || !response.success)
{
Debug.LogWarning($"[MailHttpService] Failed to sync mail claim: mailId={mailId} reason={response?.message ?? response?.error_code ?? "unknown"}");
}
}
catch (Exception ex)
{
Debug.LogWarning($"[MailHttpService] Failed to sync mail claim: mailId={mailId} ex={ex.Message}");
}
finally
{
_claimSyncInFlight.Remove(mailId);
}
}
private async System.Threading.Tasks.Task SyncClaimedMailStateAsync()
{
if (!_serverModeEnabled || _cachedMails.Count == 0 || OnlineModeSettings.IsLocalOnlyMode)
{
return;
}
HashSet<int> receivedMailIds = UI_Panel_Mail.LoadReceivedMailIdsStatic();
if (receivedMailIds == null || receivedMailIds.Count == 0)
{
return;
}
for (int i = 0; i < _cachedMails.Count; i++)
{
MailApiEntry mail = _cachedMails[i];
if (mail == null || mail.mail_id <= 0 || !receivedMailIds.Contains(mail.mail_id))
{
continue;
}
await NotifyMailClaimedAsync(mail.mail_id);
}
}
}
public static class MailRewardGrantService
{
private enum ResolvedKind
{
None,
PlayerExp,
Coins,
Material,
ExpBottle,
GrowthMaterial,
EquipmentConsumable,
StoreItem,
}
private sealed class ResolvedReward
{
public ResolvedKind Kind;
public int Amount;
public string DisplayName;
public Sprite Icon;
public string Description;
public mail_so.rewardItem Source;
public ExpBottleKind ExpBottleKind;
public DushMaterialKind DushMaterialKind;
public EquipmentConsumableKind EquipmentConsumableKind;
public expBottlesSO ExpBottleAsset;
public growthMaterialSO GrowthMaterialAsset;
public equipmentConsumableSO EquipmentConsumableAsset;
public storeItemSO StoreItemAsset;
}
private static readonly Dictionary<ExpBottleKind, expBottlesSO> ExpBottleAssets = new Dictionary<ExpBottleKind, expBottlesSO>();
private static readonly Dictionary<DushMaterialKind, growthMaterialSO> GrowthMaterialAssets = new Dictionary<DushMaterialKind, growthMaterialSO>();
private static readonly Dictionary<EquipmentConsumableKind, equipmentConsumableSO> EquipmentConsumableAssets = new Dictionary<EquipmentConsumableKind, equipmentConsumableSO>();
private static readonly Dictionary<int, storeItemSO> StoreItemsById = new Dictionary<int, storeItemSO>();
private static readonly Dictionary<string, storeItemSO> StoreItemsByName = new Dictionary<string, storeItemSO>(StringComparer.OrdinalIgnoreCase);
private static bool _assetsLoaded;
private static Sprite _playerExpRewardIcon;
private static Sprite _coinRewardIcon;
private static Sprite _materialRewardIcon;
public static void ConfigureBasicRewardIcons(Sprite playerExpIcon, Sprite coinIcon, Sprite materialIcon)
{
if (playerExpIcon != null)
{
_playerExpRewardIcon = playerExpIcon;
}
if (coinIcon != null)
{
_coinRewardIcon = coinIcon;
}
if (materialIcon != null)
{
_materialRewardIcon = materialIcon;
}
}
public static void PopulateRewardDisplay(mail_so.rewardItem reward)
{
if (reward == null)
{
return;
}
EnsureAssetsLoaded();
if (!TryResolveReward(reward, out ResolvedReward resolved, out _))
{
return;
}
if (ShouldOverrideRewardDisplayName(reward, resolved))
{
reward.rewardName = resolved.DisplayName;
}
if (ShouldOverrideRewardDescription(reward, resolved))
{
reward.reward_description = resolved.Description;
}
if (resolved.Icon != null)
{
reward.reward_image = resolved.Icon;
}
}
public static bool TryGetRewardRarity(mail_so.rewardItem reward, out ItemRarity rarity)
{
rarity = ItemRarity.None;
if (reward == null)
{
return false;
}
EnsureAssetsLoaded();
int amount = Mathf.Max(1, reward.reward_ammount);
string rewardKey = string.IsNullOrWhiteSpace(reward.reward_key) ? string.Empty : reward.reward_key.Trim();
string rewardName = string.IsNullOrWhiteSpace(reward.rewardName) ? string.Empty : reward.rewardName.Trim();
if (TryFindStoreItemReference(reward, rewardKey, rewardName, out storeItemSO storeItem) && storeItem != null)
{
rarity = storeItem.itemRarity;
return true;
}
if (reward.reward_Type == mail_so.reward_type.expBottles_allies)
{
if (TryResolveExpBottle(reward, rewardKey, rewardName, amount, out ResolvedReward resolved) && resolved != null && resolved.ExpBottleAsset != null)
{
rarity = resolved.ExpBottleAsset.itemRarity;
return true;
}
}
else if (reward.reward_Type == mail_so.reward_type.growth_material)
{
if (TryResolveGrowthMaterial(reward, rewardKey, rewardName, amount, out ResolvedReward resolved) && resolved != null && resolved.GrowthMaterialAsset != null)
{
rarity = resolved.GrowthMaterialAsset.itemRarity;
return true;
}
}
else if (reward.reward_Type == mail_so.reward_type.equipment_consumable)
{
if (TryResolveEquipmentConsumable(reward, rewardKey, rewardName, amount, out ResolvedReward resolved) && resolved != null && resolved.EquipmentConsumableAsset != null)
{
rarity = resolved.EquipmentConsumableAsset.itemRarity;
return true;
}
}
return false;
}
public static bool TryGrantAll(mail_so mail, out string failureMessage)
{
failureMessage = string.Empty;
if (mail == null)
{
failureMessage = "\u90ae\u4ef6\u4e0d\u5b58\u5728";
return false;
}
if (mail.rewardList == null || mail.rewardList.Count == 0)
{
return true;
}
EnsureAssetsLoaded();
var resolvedRewards = new List<ResolvedReward>();
for (int i = 0; i < mail.rewardList.Count; i++)
{
mail_so.rewardItem reward = mail.rewardList[i];
if (reward == null)
{
continue;
}
if (!TryResolveReward(reward, out ResolvedReward resolved, out failureMessage))
{
return false;
}
resolvedRewards.Add(resolved);
}
Player_SO playerData = LoadDefaultPlayerSo();
if (playerData != null)
{
PlayerEconomyLedger.EnsureInstance().AttachPlayerData(playerData);
PlayerExperienceLedger.EnsureInstance().AttachPlayerData(playerData);
ExpBottleLedger.EnsureInstance().AttachPlayerData(playerData);
DushMaterialLedger.EnsureInstance().AttachPlayerData(playerData);
}
StoreOwnershipLedger.EnsureInstance().InitializeIfNeeded();
var popupEntries = new List<gItemGet.ItemEntry>();
for (int i = 0; i < resolvedRewards.Count; i++)
{
ResolvedReward resolved = resolvedRewards[i];
if (!ApplyResolvedReward(resolved, playerData, out gItemGet.ItemEntry popupEntry, out failureMessage))
{
return false;
}
popupEntries.Add(popupEntry);
}
if (popupEntries.Count > 0)
{
gItemGet.display(popupEntries);
}
return true;
}
public static bool TryReclaimAll(mail_so mail, out string failureMessage)
{
failureMessage = string.Empty;
if (mail == null)
{
failureMessage = "\u90ae\u4ef6\u4e0d\u5b58\u5728";
return false;
}
if (mail.rewardList == null || mail.rewardList.Count == 0)
{
return true;
}
EnsureAssetsLoaded();
var resolvedRewards = new List<ResolvedReward>();
for (int i = 0; i < mail.rewardList.Count; i++)
{
mail_so.rewardItem reward = mail.rewardList[i];
if (reward == null)
{
continue;
}
if (!TryResolveReward(reward, out ResolvedReward resolved, out failureMessage))
{
return false;
}
resolvedRewards.Add(resolved);
}
Player_SO playerData = LoadDefaultPlayerSo();
if (playerData != null)
{
PlayerEconomyLedger.EnsureInstance().AttachPlayerData(playerData);
PlayerExperienceLedger.EnsureInstance().AttachPlayerData(playerData);
ExpBottleLedger.EnsureInstance().AttachPlayerData(playerData);
DushMaterialLedger.EnsureInstance().AttachPlayerData(playerData);
}
StoreOwnershipLedger.EnsureInstance().InitializeIfNeeded();
for (int i = 0; i < resolvedRewards.Count; i++)
{
if (!ReclaimResolvedReward(resolvedRewards[i], playerData, out failureMessage))
{
return false;
}
}
return true;
}
public static mail_so.reward_type ParseRewardType(string raw)
{
string normalized = string.IsNullOrWhiteSpace(raw) ? string.Empty : raw.Trim().ToLowerInvariant();
switch (normalized)
{
case "exp_user":
return mail_so.reward_type.exp_user;
case "expbottles_allies":
case "exp_bottle":
case "exp_bottles_allies":
return mail_so.reward_type.expBottles_allies;
case "money":
case "coins":
return mail_so.reward_type.money;
case "metarial":
case "material":
return mail_so.reward_type.metarial;
case "growth_material":
return mail_so.reward_type.growth_material;
case "equipment_consumable":
return mail_so.reward_type.equipment_consumable;
case "store_item":
return mail_so.reward_type.store_item;
case "package":
return mail_so.reward_type.package;
default:
return mail_so.reward_type.package;
}
}
private static void EnsureAssetsLoaded()
{
if (_assetsLoaded)
{
return;
}
_assetsLoaded = true;
ExpBottleAssets.Clear();
GrowthMaterialAssets.Clear();
EquipmentConsumableAssets.Clear();
StoreItemsById.Clear();
StoreItemsByName.Clear();
expBottlesSO[] expAssets = RuntimeResourcesCache.LoadAllExpBottles();
for (int i = 0; i < expAssets.Length; i++)
{
expBottlesSO asset = expAssets[i];
if (asset != null)
{
ExpBottleAssets[asset.bottleKind] = asset;
}
}
growthMaterialSO[] growthAssets = RuntimeResourcesCache.LoadAllGrowthMaterials();
for (int i = 0; i < growthAssets.Length; i++)
{
growthMaterialSO asset = growthAssets[i];
if (asset != null)
{
GrowthMaterialAssets[asset.materialKind] = asset;
}
}
equipmentConsumableSO[] equipmentAssets = RuntimeResourcesCache.LoadAllEquipmentConsumables();
for (int i = 0; i < equipmentAssets.Length; i++)
{
equipmentConsumableSO asset = equipmentAssets[i];
if (asset != null)
{
EquipmentConsumableAssets[asset.consumableKind] = asset;
}
}
storeItemSO[] storeItems = RuntimeResourcesCache.LoadAllStoreItems();
for (int i = 0; i < storeItems.Length; i++)
{
storeItemSO item = storeItems[i];
if (item == null)
{
continue;
}
StoreItemsById[item.itemID] = item;
if (!string.IsNullOrWhiteSpace(item.itemName) && !StoreItemsByName.ContainsKey(item.itemName))
{
StoreItemsByName[item.itemName] = item;
}
if (!string.IsNullOrWhiteSpace(item.name) && !StoreItemsByName.ContainsKey(item.name))
{
StoreItemsByName[item.name] = item;
}
// expBottlesSO / growthMaterialSO / equipmentConsumableSO live outside Resources,
// so Resources.LoadAll returns nothing. Backfill from storeItemSO direct references instead.
if (item.associatedExpBottle != null && !ExpBottleAssets.ContainsKey(item.associatedExpBottle.bottleKind))
{
ExpBottleAssets[item.associatedExpBottle.bottleKind] = item.associatedExpBottle;
}
if (item.associatedGrowthMaterial != null && !GrowthMaterialAssets.ContainsKey(item.associatedGrowthMaterial.materialKind))
{
GrowthMaterialAssets[item.associatedGrowthMaterial.materialKind] = item.associatedGrowthMaterial;
}
if (item.associatedEquipmentConsumable != null && !EquipmentConsumableAssets.ContainsKey(item.associatedEquipmentConsumable.consumableKind))
{
EquipmentConsumableAssets[item.associatedEquipmentConsumable.consumableKind] = item.associatedEquipmentConsumable;
}
}
}
private static bool TryResolveReward(mail_so.rewardItem reward, out ResolvedReward resolved, out string failureMessage)
{
resolved = null;
failureMessage = string.Empty;
if (reward == null)
{
failureMessage = "\u5956\u52b1\u4e3a\u7a7a";
return false;
}
int amount = Mathf.Max(1, reward.reward_ammount);
string rewardKey = string.IsNullOrWhiteSpace(reward.reward_key) ? string.Empty : reward.reward_key.Trim();
string rewardName = string.IsNullOrWhiteSpace(reward.rewardName) ? string.Empty : reward.rewardName.Trim();
switch (reward.reward_Type)
{
case mail_so.reward_type.exp_user:
resolved = CreateBasicResolved(ResolvedKind.PlayerExp, amount, rewardName, reward.reward_image != null ? reward.reward_image : _playerExpRewardIcon, reward.reward_description, reward, "\u73a9\u5bb6\u7ecf\u9a8c");
return true;
case mail_so.reward_type.money:
resolved = CreateBasicResolved(ResolvedKind.Coins, amount, rewardName, reward.reward_image != null ? reward.reward_image : _coinRewardIcon, reward.reward_description, reward, "算力");
return true;
case mail_so.reward_type.metarial:
resolved = CreateBasicResolved(ResolvedKind.Material, amount, rewardName, reward.reward_image != null ? reward.reward_image : _materialRewardIcon, reward.reward_description, reward, "\u8bb0\u5fc6\u788e\u7247");
return true;
case mail_so.reward_type.expBottles_allies:
if (TryResolveExpBottle(reward, rewardKey, rewardName, amount, out resolved))
{
return true;
}
break;
case mail_so.reward_type.growth_material:
if (TryResolveGrowthMaterial(reward, rewardKey, rewardName, amount, out resolved))
{
return true;
}
break;
case mail_so.reward_type.equipment_consumable:
if (TryResolveEquipmentConsumable(reward, rewardKey, rewardName, amount, out resolved))
{
return true;
}
break;
case mail_so.reward_type.store_item:
case mail_so.reward_type.package:
if (TryResolveStoreItemReward(reward, rewardKey, rewardName, amount, out resolved))
{
return true;
}
break;
}
if (TryResolveByRewardKey(reward, rewardKey, rewardName, amount, out resolved))
{
return true;
}
if (TryResolveGenericExpBottleFallback(reward, rewardName, amount, out resolved))
{
return true;
}
failureMessage = $"\u672a\u914d\u7f6e\u90ae\u4ef6\u5956\u52b1\u53d1\u653e\u903b\u8f91: {(string.IsNullOrWhiteSpace(rewardName) ? reward.reward_Type.ToString() : rewardName)}";
return false;
}
private static ResolvedReward CreateBasicResolved(ResolvedKind kind, int amount, string rewardName, Sprite icon, string description, mail_so.rewardItem source, string fallbackName)
{
return new ResolvedReward
{
Kind = kind,
Amount = amount,
DisplayName = string.IsNullOrWhiteSpace(rewardName) ? fallbackName : rewardName,
Icon = icon,
Description = description ?? string.Empty,
Source = source,
};
}
private static bool ShouldOverrideRewardDisplayName(mail_so.rewardItem reward, ResolvedReward resolved)
{
if (reward == null || resolved == null)
{
return false;
}
switch (resolved.Kind)
{
case ResolvedKind.ExpBottle:
case ResolvedKind.GrowthMaterial:
case ResolvedKind.EquipmentConsumable:
case ResolvedKind.StoreItem:
return !string.IsNullOrWhiteSpace(resolved.DisplayName);
default:
return string.IsNullOrWhiteSpace(reward.rewardName) && !string.IsNullOrWhiteSpace(resolved.DisplayName);
}
}
private static bool ShouldOverrideRewardDescription(mail_so.rewardItem reward, ResolvedReward resolved)
{
if (reward == null || resolved == null)
{
return false;
}
switch (resolved.Kind)
{
case ResolvedKind.ExpBottle:
case ResolvedKind.GrowthMaterial:
case ResolvedKind.EquipmentConsumable:
case ResolvedKind.StoreItem:
return !string.IsNullOrWhiteSpace(resolved.Description);
default:
return string.IsNullOrWhiteSpace(reward.reward_description) && !string.IsNullOrWhiteSpace(resolved.Description);
}
}
private static bool TryResolveByRewardKey(mail_so.rewardItem reward, string rewardKey, string rewardName, int amount, out ResolvedReward resolved)
{
if (TryResolveExpBottle(reward, rewardKey, rewardName, amount, out resolved))
{
return true;
}
if (TryResolveGrowthMaterial(reward, rewardKey, rewardName, amount, out resolved))
{
return true;
}
if (TryResolveEquipmentConsumable(reward, rewardKey, rewardName, amount, out resolved))
{
return true;
}
if (TryResolveStoreItemReward(reward, rewardKey, rewardName, amount, out resolved))
{
return true;
}
resolved = null;
return false;
}
private static bool TryFindStoreItemReference(mail_so.rewardItem reward, string rewardKey, string rewardName, out storeItemSO storeItem)
{
storeItem = null;
if (reward != null && reward.reward_store_item_id > 0)
{
StoreItemsById.TryGetValue(reward.reward_store_item_id, out storeItem);
}
if (storeItem == null && !string.IsNullOrWhiteSpace(rewardKey))
{
if (rewardKey.StartsWith("store:", StringComparison.OrdinalIgnoreCase)
&& int.TryParse(rewardKey.Substring("store:".Length), out int parsedStoreId))
{
StoreItemsById.TryGetValue(parsedStoreId, out storeItem);
}
else if (int.TryParse(rewardKey, out int numericStoreId))
{
StoreItemsById.TryGetValue(numericStoreId, out storeItem);
}
else
{
StoreItemsByName.TryGetValue(rewardKey, out storeItem);
}
}
if (storeItem == null && !string.IsNullOrWhiteSpace(rewardName))
{
StoreItemsByName.TryGetValue(rewardName, out storeItem);
}
if (storeItem == null)
{
string fuzzySource = !string.IsNullOrWhiteSpace(rewardKey) ? rewardKey : rewardName;
storeItem = FindStoreItemByFuzzyName(fuzzySource);
}
return storeItem != null;
}
private static bool TryResolveExpBottle(mail_so.rewardItem reward, string rewardKey, string rewardName, int amount, out ResolvedReward resolved)
{
resolved = null;
ExpBottleKind kind = default;
expBottlesSO asset = null;
bool found = false;
if (!string.IsNullOrWhiteSpace(rewardKey))
{
found = ExpBottleCatalog.TryGetKind(rewardKey, out kind);
}
if (found)
{
ExpBottleAssets.TryGetValue(kind, out asset);
}
if ((asset == null || !found) && TryFindStoreItemReference(reward, rewardKey, rewardName, out storeItemSO storeItem))
{
if (storeItem.associatedExpBottle != null)
{
kind = storeItem.associatedExpBottle.bottleKind;
asset = storeItem.associatedExpBottle;
found = true;
}
else if (StoreExpBottleGrantResolver.TryResolve(storeItem, out ExpBottleKind resolvedKind))
{
kind = resolvedKind;
ExpBottleAssets.TryGetValue(kind, out asset);
found = asset != null;
}
}
if (!found)
{
found = TryMatchExpBottleByName(rewardName, out kind);
if (found)
{
ExpBottleAssets.TryGetValue(kind, out asset);
}
}
if ((!found || asset == null) && !TryResolveGenericExpBottleFallback(reward, rewardName, amount, out resolved))
{
return false;
}
if (resolved != null)
{
return true;
}
resolved = new ResolvedReward
{
Kind = ResolvedKind.ExpBottle,
Amount = amount,
DisplayName = string.IsNullOrWhiteSpace(rewardName) ? asset.expBottleName : rewardName,
Icon = reward.reward_image != null ? reward.reward_image : asset.expBottleSprite,
Description = string.IsNullOrWhiteSpace(reward.reward_description) ? asset.expBottleDescription : reward.reward_description,
Source = reward,
ExpBottleKind = kind,
ExpBottleAsset = asset,
};
return true;
}
private static bool TryResolveGrowthMaterial(mail_so.rewardItem reward, string rewardKey, string rewardName, int amount, out ResolvedReward resolved)
{
resolved = null;
DushMaterialKind kind = default;
growthMaterialSO asset = null;
bool found = false;
if (!string.IsNullOrWhiteSpace(rewardKey))
{
found = DushMaterialCatalog.TryGetKind(rewardKey, out kind);
}
if (found)
{
GrowthMaterialAssets.TryGetValue(kind, out asset);
}
if ((asset == null || !found) && TryFindStoreItemReference(reward, rewardKey, rewardName, out storeItemSO storeItem) && storeItem.associatedGrowthMaterial != null)
{
kind = storeItem.associatedGrowthMaterial.materialKind;
asset = storeItem.associatedGrowthMaterial;
found = true;
}
if (!found)
{
found = TryMatchGrowthMaterialByName(rewardName, out kind);
if (found)
{
GrowthMaterialAssets.TryGetValue(kind, out asset);
}
}
if (!found || asset == null)
{
return false;
}
resolved = new ResolvedReward
{
Kind = ResolvedKind.GrowthMaterial,
Amount = amount,
DisplayName = string.IsNullOrWhiteSpace(rewardName) ? asset.growthMaterialName : rewardName,
Icon = reward.reward_image != null ? reward.reward_image : asset.growthMaterialSprite,
Description = string.IsNullOrWhiteSpace(reward.reward_description) ? asset.growthMaterialDescription : reward.reward_description,
Source = reward,
DushMaterialKind = kind,
GrowthMaterialAsset = asset,
};
return true;
}
private static bool TryResolveEquipmentConsumable(mail_so.rewardItem reward, string rewardKey, string rewardName, int amount, out ResolvedReward resolved)
{
resolved = null;
EquipmentConsumableKind kind = default;
equipmentConsumableSO asset = null;
bool found = false;
if (!string.IsNullOrWhiteSpace(rewardKey))
{
found = EquipmentConsumableCatalog.TryGetKind(rewardKey, out kind);
}
if (found)
{
EquipmentConsumableAssets.TryGetValue(kind, out asset);
}
if ((asset == null || !found) && TryFindStoreItemReference(reward, rewardKey, rewardName, out storeItemSO storeItem) && storeItem.associatedEquipmentConsumable != null)
{
kind = storeItem.associatedEquipmentConsumable.consumableKind;
asset = storeItem.associatedEquipmentConsumable;
found = true;
}
if (!found)
{
found = TryMatchEquipmentConsumableByName(rewardName, out kind);
if (found)
{
EquipmentConsumableAssets.TryGetValue(kind, out asset);
}
}
if (!found || asset == null)
{
return false;
}
resolved = new ResolvedReward
{
Kind = ResolvedKind.EquipmentConsumable,
Amount = amount,
DisplayName = string.IsNullOrWhiteSpace(rewardName) ? asset.consumableName : rewardName,
Icon = reward.reward_image != null ? reward.reward_image : asset.consumableSprite,
Description = string.IsNullOrWhiteSpace(reward.reward_description) ? asset.consumableDescription : reward.reward_description,
Source = reward,
EquipmentConsumableKind = kind,
EquipmentConsumableAsset = asset,
};
return true;
}
private static bool TryResolveStoreItemReward(mail_so.rewardItem reward, string rewardKey, string rewardName, int amount, out ResolvedReward resolved)
{
resolved = null;
if (!TryFindStoreItemReference(reward, rewardKey, rewardName, out storeItemSO storeItem))
{
return false;
}
resolved = new ResolvedReward
{
Kind = ResolvedKind.StoreItem,
Amount = amount,
DisplayName = string.IsNullOrWhiteSpace(rewardName) ? storeItem.itemName : rewardName,
Icon = reward.reward_image != null ? reward.reward_image : storeItem.itemIcon,
Description = string.IsNullOrWhiteSpace(reward.reward_description) ? storeItem.itemDescription : reward.reward_description,
Source = reward,
StoreItemAsset = storeItem,
};
return true;
}
private static bool TryResolveGenericExpBottleFallback(mail_so.rewardItem reward, string rewardName, int amount, out ResolvedReward resolved)
{
resolved = null;
string normalized = NormalizeRewardLookupToken(rewardName);
if (string.IsNullOrEmpty(normalized) || !normalized.Contains("\u7ecf\u9a8c\u74f6"))
{
return false;
}
ExpBottleKind kind;
if (normalized.Contains("\u8d85\u7ea7\u96e8\u9732\u5747\u6cbe"))
{
kind = ExpBottleKind.SuperRainAll;
}
else if (normalized.Contains("\u9ad8\u7ea7\u96e8\u9732\u5747\u6cbe"))
{
kind = ExpBottleKind.AdvancedRainAll;
}
else if (normalized.Contains("\u96e8\u9732\u5747\u6cbe"))
{
kind = ExpBottleKind.RainAll;
}
else if (normalized.Contains("\u4ed9\u54c1"))
{
kind = ExpBottleKind.Celestial;
}
else if (normalized.Contains("\u7edd\u54c1"))
{
kind = ExpBottleKind.Extraordinary;
}
else if (normalized.Contains("\u6781\u54c1"))
{
kind = ExpBottleKind.Supreme;
}
else if (normalized.Contains("\u4e0a\u54c1"))
{
kind = ExpBottleKind.Superior;
}
else if (normalized.Contains("\u4e2d\u54c1"))
{
kind = ExpBottleKind.Medium;
}
else
{
kind = ExpBottleKind.Common;
}
if (!ExpBottleAssets.TryGetValue(kind, out expBottlesSO asset) || asset == null)
{
return false;
}
resolved = new ResolvedReward
{
Kind = ResolvedKind.ExpBottle,
Amount = amount,
DisplayName = string.IsNullOrWhiteSpace(rewardName) ? asset.expBottleName : rewardName,
Icon = reward.reward_image != null ? reward.reward_image : asset.expBottleSprite,
Description = string.IsNullOrWhiteSpace(reward.reward_description) ? asset.expBottleDescription : reward.reward_description,
Source = reward,
ExpBottleKind = kind,
ExpBottleAsset = asset,
};
return true;
}
private static bool TryMatchExpBottleByName(string rewardName, out ExpBottleKind kind)
{
string normalizedRewardName = NormalizeRewardLookupToken(rewardName);
if (!string.IsNullOrEmpty(normalizedRewardName))
{
foreach (ExpBottleDescriptor descriptor in ExpBottleCatalog.All)
{
string normalizedDisplay = NormalizeRewardLookupToken(descriptor.DisplayName);
string normalizedLegacy = NormalizeRewardLookupToken(descriptor.LegacyPlayerFieldName);
string normalizedKey = NormalizeRewardLookupToken(descriptor.Key);
if (normalizedRewardName == normalizedDisplay
|| normalizedRewardName == normalizedLegacy
|| normalizedRewardName == normalizedKey
|| (!string.IsNullOrEmpty(normalizedDisplay) && normalizedRewardName.Contains(normalizedDisplay))
|| (!string.IsNullOrEmpty(normalizedDisplay) && normalizedDisplay.Contains(normalizedRewardName))
|| (!string.IsNullOrEmpty(normalizedLegacy) && normalizedRewardName.Contains(normalizedLegacy))
|| (!string.IsNullOrEmpty(normalizedKey) && normalizedRewardName.Contains(normalizedKey)))
{
kind = descriptor.Kind;
return true;
}
}
}
foreach (ExpBottleDescriptor descriptor in ExpBottleCatalog.All)
{
if (string.Equals(descriptor.DisplayName, rewardName, StringComparison.OrdinalIgnoreCase))
{
kind = descriptor.Kind;
return true;
}
}
kind = default;
return false;
}
private static bool TryMatchGrowthMaterialByName(string rewardName, out DushMaterialKind kind)
{
string normalizedRewardName = NormalizeRewardLookupToken(rewardName);
if (!string.IsNullOrEmpty(normalizedRewardName))
{
foreach (DushMaterialDescriptor descriptor in DushMaterialCatalog.All)
{
string normalizedDisplay = NormalizeRewardLookupToken(descriptor.DisplayName);
string normalizedLegacy = NormalizeRewardLookupToken(descriptor.LegacyPlayerFieldName);
string normalizedKey = NormalizeRewardLookupToken(descriptor.Key);
if (normalizedRewardName == normalizedDisplay
|| normalizedRewardName == normalizedLegacy
|| normalizedRewardName == normalizedKey
|| (!string.IsNullOrEmpty(normalizedDisplay) && normalizedRewardName.Contains(normalizedDisplay))
|| (!string.IsNullOrEmpty(normalizedDisplay) && normalizedDisplay.Contains(normalizedRewardName))
|| (!string.IsNullOrEmpty(normalizedLegacy) && normalizedRewardName.Contains(normalizedLegacy))
|| (!string.IsNullOrEmpty(normalizedKey) && normalizedRewardName.Contains(normalizedKey)))
{
kind = descriptor.Kind;
return true;
}
}
}
foreach (DushMaterialDescriptor descriptor in DushMaterialCatalog.All)
{
if (string.Equals(descriptor.DisplayName, rewardName, StringComparison.OrdinalIgnoreCase))
{
kind = descriptor.Kind;
return true;
}
}
kind = default;
return false;
}
private static bool TryMatchEquipmentConsumableByName(string rewardName, out EquipmentConsumableKind kind)
{
string normalizedRewardName = NormalizeRewardLookupToken(rewardName);
if (!string.IsNullOrEmpty(normalizedRewardName))
{
foreach (EquipmentConsumableDescriptor descriptor in EquipmentConsumableCatalog.All)
{
string normalizedDisplay = NormalizeRewardLookupToken(descriptor.DisplayName);
string normalizedKey = NormalizeRewardLookupToken(descriptor.Key);
if (normalizedRewardName == normalizedDisplay
|| normalizedRewardName == normalizedKey
|| (!string.IsNullOrEmpty(normalizedDisplay) && normalizedRewardName.Contains(normalizedDisplay))
|| (!string.IsNullOrEmpty(normalizedDisplay) && normalizedDisplay.Contains(normalizedRewardName))
|| (!string.IsNullOrEmpty(normalizedKey) && normalizedRewardName.Contains(normalizedKey)))
{
kind = descriptor.Kind;
return true;
}
}
}
foreach (EquipmentConsumableDescriptor descriptor in EquipmentConsumableCatalog.All)
{
if (string.Equals(descriptor.DisplayName, rewardName, StringComparison.OrdinalIgnoreCase))
{
kind = descriptor.Kind;
return true;
}
}
kind = default;
return false;
}
private static bool ApplyResolvedReward(ResolvedReward resolved, Player_SO playerData, out gItemGet.ItemEntry popupEntry, out string failureMessage)
{
popupEntry = default;
failureMessage = string.Empty;
switch (resolved.Kind)
{
case ResolvedKind.PlayerExp:
PlayerExperienceLedger.EnsureInstance().AttachPlayerData(playerData);
PlayerExperienceLedger.EnsureInstance().AddExperience(resolved.Amount);
popupEntry = gItemGet.Create(resolved.DisplayName, resolved.Icon, resolved.Amount, ItemRarity.None);
return true;
case ResolvedKind.Coins:
PlayerEconomyLedger.EnsureInstance().AddCoins(resolved.Amount);
popupEntry = gItemGet.Create(resolved.DisplayName, resolved.Icon, resolved.Amount, ItemRarity.None);
return true;
case ResolvedKind.Material:
PlayerEconomyLedger.EnsureInstance().AddMaterial(resolved.Amount);
popupEntry = gItemGet.Create(resolved.DisplayName, resolved.Icon, resolved.Amount, ItemRarity.None);
return true;
case ResolvedKind.ExpBottle:
ExpBottleLedger.EnsureInstance().Add(resolved.ExpBottleKind, resolved.Amount);
popupEntry = gItemGet.FromExpBottle(resolved.ExpBottleAsset, resolved.Amount);
return true;
case ResolvedKind.GrowthMaterial:
DushMaterialLedger.EnsureInstance().Add(resolved.DushMaterialKind, resolved.Amount);
popupEntry = gItemGet.FromGrowthMaterial(resolved.GrowthMaterialAsset, resolved.Amount);
return true;
case ResolvedKind.EquipmentConsumable:
EquipmentConsumableLedger.EnsureInstance().Add(resolved.EquipmentConsumableKind, resolved.Amount);
popupEntry = gItemGet.FromEquipmentConsumable(resolved.EquipmentConsumableAsset, resolved.Amount);
return true;
case ResolvedKind.StoreItem:
if (resolved.StoreItemAsset == null)
{
failureMessage = "\u90ae\u4ef6\u5956\u52b1\u5546\u54c1\u4e0d\u5b58\u5728";
return false;
}
return GrantStoreItem(resolved.StoreItemAsset, resolved.Amount, playerData, out popupEntry, out failureMessage);
default:
failureMessage = "\u672a\u914d\u7f6e\u90ae\u4ef6\u5956\u52b1\u53d1\u653e\u903b\u8f91";
return false;
}
}
private static bool ReclaimResolvedReward(ResolvedReward resolved, Player_SO playerData, out string failureMessage)
{
failureMessage = string.Empty;
switch (resolved.Kind)
{
case ResolvedKind.PlayerExp:
PlayerExperienceLedger.EnsureInstance().AttachPlayerData(playerData);
return PlayerExperienceLedger.EnsureInstance().TryConsumeExperience(resolved.Amount)
|| Fail("\u73a9\u5bb6\u7ecf\u9a8c\u4e0d\u8db3\uff0c\u65e0\u6cd5\u56de\u6536", out failureMessage);
case ResolvedKind.Coins:
return PlayerEconomyLedger.EnsureInstance().TrySpendCoins(resolved.Amount)
|| Fail("\u91d1\u5e01\u4e0d\u8db3\uff0c\u65e0\u6cd5\u56de\u6536", out failureMessage);
case ResolvedKind.Material:
return PlayerEconomyLedger.EnsureInstance().TrySpendMaterial(resolved.Amount)
|| Fail("\u8bb0\u5fc6\u788e\u7247\u4e0d\u8db3\uff0c\u65e0\u6cd5\u56de\u6536", out failureMessage);
case ResolvedKind.ExpBottle:
return ExpBottleLedger.EnsureInstance().TryConsume(resolved.ExpBottleKind, resolved.Amount)
|| Fail("\u7ecf\u9a8c\u74f6\u6570\u91cf\u4e0d\u8db3\uff0c\u65e0\u6cd5\u56de\u6536", out failureMessage);
case ResolvedKind.GrowthMaterial:
return DushMaterialLedger.EnsureInstance().TryConsume(resolved.DushMaterialKind, resolved.Amount)
|| Fail("\u7a81\u7834\u6750\u6599\u6570\u91cf\u4e0d\u8db3\uff0c\u65e0\u6cd5\u56de\u6536", out failureMessage);
case ResolvedKind.EquipmentConsumable:
return EquipmentConsumableLedger.EnsureInstance().TryConsume(resolved.EquipmentConsumableKind, resolved.Amount)
|| Fail("\u88c5\u5907\u6750\u6599\u6570\u91cf\u4e0d\u8db3\uff0c\u65e0\u6cd5\u56de\u6536", out failureMessage);
case ResolvedKind.StoreItem:
return ReclaimStoreItem(resolved.StoreItemAsset, resolved.Amount, playerData, out failureMessage);
default:
failureMessage = "\u672a\u914d\u7f6e\u90ae\u4ef6\u5956\u52b1\u56de\u6536\u903b\u8f91";
return false;
}
}
private static bool GrantStoreItem(storeItemSO item, int amount, Player_SO playerData, out gItemGet.ItemEntry rewardEntry, out string failureMessage)
{
rewardEntry = default;
failureMessage = string.Empty;
if (item == null)
{
failureMessage = "\u90ae\u4ef6\u5956\u52b1\u5546\u54c1\u4e0d\u5b58\u5728";
return false;
}
if (item.itemType == storeItemSO.ItemType.character
|| item.itemType == storeItemSO.ItemType.song
|| item.itemType == storeItemSO.ItemType.storyPassage)
{
if (!StoreOwnershipLedger.EnsureInstance().IsOwned(item)
&& !StoreOwnershipLedger.EnsureInstance().TryGrantOwnership(item, out failureMessage))
{
return false;
}
rewardEntry = gItemGet.FromStoreItem(item, amount);
return true;
}
if (item.itemType == storeItemSO.ItemType.consumable)
{
if (item.associatedExpBottle != null)
{
ExpBottleLedger.EnsureInstance().Add(item.associatedExpBottle.bottleKind, amount);
rewardEntry = gItemGet.FromExpBottle(item.associatedExpBottle, amount);
return true;
}
if (item.associatedGrowthMaterial != null)
{
DushMaterialLedger.EnsureInstance().Add(item.associatedGrowthMaterial.materialKind, amount);
rewardEntry = gItemGet.FromGrowthMaterial(item.associatedGrowthMaterial, amount);
return true;
}
if (item.associatedEquipmentConsumable != null)
{
EquipmentConsumableLedger.EnsureInstance().Add(item.associatedEquipmentConsumable.consumableKind, amount);
rewardEntry = gItemGet.FromEquipmentConsumable(item.associatedEquipmentConsumable, amount);
return true;
}
if (StoreExpBottleGrantResolver.TryResolve(item, out ExpBottleKind bottleKind))
{
ExpBottleLedger.EnsureInstance().Add(bottleKind, amount);
if (ExpBottleAssets.TryGetValue(bottleKind, out expBottlesSO bottleAsset) && bottleAsset != null)
{
rewardEntry = gItemGet.FromExpBottle(bottleAsset, amount);
}
else
{
rewardEntry = gItemGet.FromStoreItem(item, amount);
}
return true;
}
if (TryGetSelectableEquipmentRequirement(item, out smeltStageRewardSO rewardSource, out smeltStageRewardSO.SmeltStageRewardRequirement requirement))
{
equipmentSO lastGenerated = null;
int grantCount = Mathf.Max(1, amount);
for (int i = 0; i < grantCount; i++)
{
lastGenerated = Bansonic.equipmentGenerator.GenerateFromSmeltReward(rewardSource, requirement);
if (lastGenerated == null)
{
failureMessage = "\u8bb0\u5fc6\u53d1\u653e\u5931\u8d25";
return false;
}
}
rewardEntry = grantCount == 1 && lastGenerated != null
? gItemGet.FromEquipment(lastGenerated, 1)
: gItemGet.FromStoreItem(item, grantCount);
return true;
}
}
failureMessage = "\u5f53\u524d\u672a\u914d\u7f6e\u8be5\u5546\u54c1\u7684\u90ae\u4ef6\u53d1\u653e\u903b\u8f91";
return false;
}
private static bool ReclaimStoreItem(storeItemSO item, int amount, Player_SO playerData, out string failureMessage)
{
failureMessage = string.Empty;
if (item == null)
{
failureMessage = "\u90ae\u4ef6\u5956\u52b1\u5546\u54c1\u4e0d\u5b58\u5728";
return false;
}
if (item.itemType == storeItemSO.ItemType.character
|| item.itemType == storeItemSO.ItemType.song
|| item.itemType == storeItemSO.ItemType.storyPassage)
{
return StoreOwnershipLedger.EnsureInstance().TryRevokeOwnership(item, out failureMessage);
}
if (item.itemType == storeItemSO.ItemType.consumable)
{
if (item.associatedExpBottle != null)
{
return ExpBottleLedger.EnsureInstance().TryConsume(item.associatedExpBottle.bottleKind, amount)
|| Fail("\u7ecf\u9a8c\u74f6\u6570\u91cf\u4e0d\u8db3\uff0c\u65e0\u6cd5\u56de\u6536", out failureMessage);
}
if (item.associatedGrowthMaterial != null)
{
return DushMaterialLedger.EnsureInstance().TryConsume(item.associatedGrowthMaterial.materialKind, amount)
|| Fail("\u7a81\u7834\u6750\u6599\u6570\u91cf\u4e0d\u8db3\uff0c\u65e0\u6cd5\u56de\u6536", out failureMessage);
}
if (item.associatedEquipmentConsumable != null)
{
return EquipmentConsumableLedger.EnsureInstance().TryConsume(item.associatedEquipmentConsumable.consumableKind, amount)
|| Fail("\u88c5\u5907\u6750\u6599\u6570\u91cf\u4e0d\u8db3\uff0c\u65e0\u6cd5\u56de\u6536", out failureMessage);
}
if (StoreExpBottleGrantResolver.TryResolve(item, out ExpBottleKind bottleKind))
{
return ExpBottleLedger.EnsureInstance().TryConsume(bottleKind, amount)
|| Fail("\u7ecf\u9a8c\u74f6\u6570\u91cf\u4e0d\u8db3\uff0c\u65e0\u6cd5\u56de\u6536", out failureMessage);
}
}
failureMessage = "\u5f53\u524d\u672a\u914d\u7f6e\u8be5\u5546\u54c1\u7684\u90ae\u4ef6\u56de\u6536\u903b\u8f91";
return false;
}
private static bool TryGetSelectableEquipmentRequirement(
storeItemSO itemSO,
out smeltStageRewardSO rewardSource,
out smeltStageRewardSO.SmeltStageRewardRequirement requirement)
{
rewardSource = null;
requirement = null;
if (itemSO == null
|| itemSO.associatedSelectableEquipmentRewardSource == null
|| itemSO.associatedSelectableEquipmentRewardIndex < 0)
{
return false;
}
rewardSource = itemSO.associatedSelectableEquipmentRewardSource;
if (rewardSource.rewardRequirements == null
|| itemSO.associatedSelectableEquipmentRewardIndex >= rewardSource.rewardRequirements.Length)
{
rewardSource = null;
return false;
}
requirement = rewardSource.rewardRequirements[itemSO.associatedSelectableEquipmentRewardIndex];
return requirement != null;
}
private static string NormalizeRewardLookupToken(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
return new string(value
.Trim()
.ToLowerInvariant()
.Where(ch => !char.IsWhiteSpace(ch) && ch != '_' && ch != '-' && ch != ':' && ch != '?')
.ToArray());
}
private static storeItemSO FindStoreItemByFuzzyName(string raw)
{
string normalizedRaw = NormalizeRewardLookupToken(raw);
if (string.IsNullOrEmpty(normalizedRaw))
{
return null;
}
foreach (storeItemSO item in StoreItemsById.Values)
{
if (item == null)
{
continue;
}
string normalizedItemName = NormalizeRewardLookupToken(item.itemName);
string normalizedAssetName = NormalizeRewardLookupToken(item.name);
if (normalizedRaw == normalizedItemName
|| normalizedRaw == normalizedAssetName
|| (!string.IsNullOrEmpty(normalizedItemName) && normalizedRaw.Contains(normalizedItemName))
|| (!string.IsNullOrEmpty(normalizedItemName) && normalizedItemName.Contains(normalizedRaw))
|| (!string.IsNullOrEmpty(normalizedAssetName) && normalizedRaw.Contains(normalizedAssetName))
|| (!string.IsNullOrEmpty(normalizedAssetName) && normalizedAssetName.Contains(normalizedRaw)))
{
return item;
}
}
return null;
}
private static bool Fail(string message, out string failureMessage)
{
failureMessage = message;
return false;
}
private static Player_SO LoadDefaultPlayerSo()
{
return RuntimeResourcesCache.LoadDefaultPlayerSo();
}
}