2560 lines
87 KiB
C#
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();
|
|
}
|
|
}
|
|
|
|
|
|
|