NLDClient-yudde/ProjectNLD/Assets/Code/Scripts/Gameplay/UI/UI_LevelSelectController.cs

641 lines
22 KiB
C#
Raw Normal View History

using System.Collections.Generic;
2023-10-26 20:20:11 +08:00
using System.Threading;
using cfg.LevelCfg;
using Cysharp.Threading.Tasks;
2023-10-30 18:33:18 +08:00
using Framework;
2023-10-26 20:20:11 +08:00
using Gameplay.Level;
using PhxhSDK;
2023-10-24 14:58:21 +08:00
using TMPro;
using UnityEngine;
2023-10-26 20:20:11 +08:00
using UnityEngine.Serialization;
using Utils;
2023-10-24 14:58:21 +08:00
using Button = UnityEngine.UI.Button;
using Image = UnityEngine.UI.Image;
public class UI_LevelSelectController : UIWindow
{
2023-10-26 20:20:11 +08:00
//UI GameObj
///Auto Gen Start///
public Button Button_Back;
public TMP_Text Text_Back;
public Button Button_Home;
public Button Button_MainLevel;
public TMP_Text Text_MainLevel;
public TMP_Text Text_MainLevelNum;
public TMP_Text Text_MainLevelName;
public Image Image_MainlevelLogo;
public Image Image_BlackBar;
public TMP_Text Text_MainLevelProcess;
public Button Button_BranchLevel;
public Button Button_DungeonlEVEL;
public Image Image_ChapterBG;
public Image Image_LevelBG;
public Image Image_LevelTitle;
public TMP_Text Text_ChapterNum;
public TMP_Text Text_ChapterName;
public Image Image_ProcessBG;
public Image Image_LevelProcess;
public Image Image_ProcessBar;
public TMP_Text Text_StarNum;
public Image Image_SelectBG;
public Button Button_Plot;
public TMP_Text Text_Read;
public Button Button_Easy;
public TMP_Text Text_Simple;
public Button Button_Hard;
public TMP_Text Text_Hard;
public Image Image_WhiteRect;
2023-11-22 15:05:43 +08:00
[FormerlySerializedAs("Image_Read")]
public Image Image_Plot;
[FormerlySerializedAs("Image_Simple")]
public Image Image_Easy;
2023-10-26 20:20:11 +08:00
public Image Image_RedRect;
public Image Image_Hard;
///Auto Gen End///
private enum PageType
{
Mode = 1,
LevelGroup = 2,
Level = 3,
}
private PageType _currPage = PageType.Mode;
private LevelGroupType _currGroupType;
private LevelGroupInfo _currLevelGroupInfo;
private ChapterInfo _currEasyChapterInfo;
private ChapterInfo _currHardChapterInfo;
private ChapterInfo _currPlotChapterInfo;
private ChapterInfo _currChapterInfo;
private LevelInfo _currSelectLevelInfo;
private List<LevelGroupInfo> _showLevelGroupInfoList = new();
private List<LevelInfo> _showLevelInfoList = new();
private RectTransform UI_ModeChoose;
private RectTransform UI_LevelGroupChoose;
private RectTransform UI_LevelChoose;
private RectTransform _modeParent;
private Framework.PhxhScrollView _levelGroupScrollView;
private Framework.PhxhScrollView _levelScrollView;
2023-10-26 20:20:11 +08:00
private RectTransform _starRewardsParent;
private RedPointUIController _easyChapterRedPointUI;
private RedPointUIController _hardChapterRedPointUI;
private RedPointUIController _plotChapterRedPointUI;
2023-10-26 20:20:11 +08:00
private const string STAR_REWARD_PREFAB_PATH = "Assets/Art/UI/Prefab/LevelSelect/Button_StarReward.prefab";
private List<GameObject> _starRewardPool = new();
private List<GameObject> _showStarReward = new();
private float _totalMoveTime = 1f;
2023-10-27 12:07:33 +08:00
private CancellationTokenSource _cts;
2023-10-26 20:20:11 +08:00
public override void OnAwake()
{
//bind UI GameObj
UI_LevelSelectBinder.GetComponents(this);
BindProperty();
BindEvent();
RegisterSignal();
}
private void BindProperty()
{
UI_ModeChoose = transform.Find("UI_ModeChoose") as RectTransform;
UI_LevelGroupChoose = transform.Find("UI_LevelGroupChoose") as RectTransform;
UI_LevelChoose = transform.Find("UI_LevelChoose") as RectTransform;
_modeParent = UI_ModeChoose.Find("Scroll View/Viewport/Content") as RectTransform;
_levelGroupScrollView = UI_LevelGroupChoose.Find("Image_ChapterBG/Scroll View")
.GetComponent<Framework.PhxhScrollView>();
_levelScrollView = UI_LevelChoose.Find("Image_LevelBG/Scroll View").GetComponent<Framework.PhxhScrollView>();
2023-10-26 20:20:11 +08:00
_starRewardsParent = Image_LevelProcess.transform.Find("StarRewardProgress/starRewards") as RectTransform;
_levelGroupScrollView.SetUpdateFunc(OnRenderLevelGroup);
_levelGroupScrollView.SetItemCountFunc(GetLevelGroupCount);
_levelGroupScrollView.SetButton(SetOnLevelGroupClick);
_levelScrollView.SetUpdateFunc(OnRenderLevelInfo);
_levelScrollView.SetItemCountFunc(GetLevelCount);
_levelScrollView.SetButton(SetOnLevelClick);
_easyChapterRedPointUI = Button_Easy.transform.Find("UI_RedPoint").GetComponent<RedPointUIController>();
_hardChapterRedPointUI = Button_Hard.transform.Find("UI_RedPoint").GetComponent<RedPointUIController>();
_plotChapterRedPointUI = Button_Plot.transform.Find("UI_RedPoint").GetComponent<RedPointUIController>();
2023-10-26 20:20:11 +08:00
}
private void BindEvent()
{
BindButton("UI_Topbtn/Button_Back", OnClickBack);
BindButton("UI_Topbtn/Button_Home", OnClickHome);
BindButton("UI_LevelChoose/Image_ProcessBG/Image_SelectBG/Button_Easy", OnClickEasy);
BindButton("UI_LevelChoose/Image_ProcessBG/Image_SelectBG/Button_Hard", OnClickHard);
BindButton("UI_LevelChoose/Image_ProcessBG/Image_SelectBG/Button_Plot", OnClickPlot);
var modeButtonCount = _modeParent.childCount;
for (int i = 0; i < modeButtonCount; i++)
{
var modeButton = _modeParent.GetChild(i).GetComponent<Button>();
modeButton.onClick.RemoveAllListeners();
var index = i;
modeButton.onClick.AddListener(() =>
{
var levelGroupType = (LevelGroupType)index;
EnterSelectLevelGroup(levelGroupType);
});
}
}
private void RegisterSignal()
{
2023-11-03 12:39:51 +08:00
EventManager.Instance.Register(EventManager.EventName.ChapterInfoChange, Refresh);
EventManager.Instance.Register(EventManager.EventName.LevelUnlock, Refresh);
EventManager.Instance.Register(EventManager.EventName.LevelInfoChanged, Refresh);
2023-10-26 20:20:11 +08:00
}
private void UnRegisterSignal()
{
2023-11-03 12:39:51 +08:00
EventManager.Instance.Unregister(EventManager.EventName.ChapterInfoChange, Refresh);
EventManager.Instance.Unregister(EventManager.EventName.LevelUnlock, Refresh);
EventManager.Instance.Unregister(EventManager.EventName.LevelInfoChanged, Refresh);
2023-10-26 20:20:11 +08:00
}
private void Refresh()
{
RefreshPage();
switch (_currPage)
{
case PageType.Mode:
RefreshMode();
break;
case PageType.LevelGroup:
RefreshLevelGroup();
break;
case PageType.Level:
RefreshLevel();
break;
}
}
//将当前UI看成是三个页面选模式选关卡组选关
private void RefreshPage()
{
UI_ModeChoose.gameObject.SetActive(_currPage == PageType.Mode);
UI_LevelGroupChoose.gameObject.SetActive(_currPage == PageType.LevelGroup);
UI_LevelChoose.gameObject.SetActive(_currPage == PageType.Level);
}
private void RefreshMode()
{
}
private void RefreshLevelGroup()
{
RefreshLevelGroupToShow();
_levelGroupScrollView.UpdateData(true);
}
private void RefreshLevelGroupToShow()
{
_showLevelGroupInfoList.Clear();
var allLevelGroup = LevelManager.Instance.GetAllLevelGroupInfo();
foreach (var levelGroupInfo in allLevelGroup)
{
if (levelGroupInfo.GroupType == _currGroupType && !levelGroupInfo.IsLock)
{
_showLevelGroupInfoList.Add(levelGroupInfo);
}
}
_showLevelGroupInfoList.Sort((a, b) => { return a.Index.CompareTo(b.Index); });
}
private void RefreshLevel()
{
RefreshLevelToShow();
_levelScrollView.UpdateData(true);
}
private void RefreshLevelToShow()
{
_showLevelInfoList.Clear();
foreach (LevelInfo levelInfo in _currChapterInfo)
{
2023-11-01 13:33:20 +08:00
if (!levelInfo.IsLock)
2023-10-26 20:20:11 +08:00
_showLevelInfoList.Add(levelInfo);
}
_showLevelInfoList.Sort((a, b) => a.Index.CompareTo(b.Index));
}
private void EnterSelectLevelGroup(LevelGroupType type)
{
_currGroupType = type;
_currPage = PageType.LevelGroup;
Refresh();
}
private void ExitSelectLevelGroup()
{
_currPage = PageType.Mode;
Refresh();
}
private void EnterSelectLevel(LevelGroupInfo levelGroupInfo)
{
_currLevelGroupInfo = levelGroupInfo;
_currPage = PageType.Level;
var defaultChapter = InitChapter();
if (defaultChapter != null)
ChangeChapter(defaultChapter);
}
private void ExitSelectLevel()
{
_currLevelGroupInfo = null;
_currEasyChapterInfo = null;
_currHardChapterInfo = null;
_currPlotChapterInfo = null;
_currChapterInfo = null;
_currSelectLevelInfo = null;
_currPage = PageType.LevelGroup;
Refresh();
}
private ChapterInfo InitChapter()
{
_currEasyChapterInfo = _currLevelGroupInfo.EasyChapter;
_currHardChapterInfo = _currLevelGroupInfo.HardChapter;
_currPlotChapterInfo = _currLevelGroupInfo.PlotChapter;
Button_Easy.interactable = IsChapterValid(_currEasyChapterInfo);
Button_Hard.interactable = IsChapterValid(_currHardChapterInfo);
Button_Plot.interactable = IsChapterValid(_currPlotChapterInfo);
_easyChapterRedPointUI.ResetToDefault();
_hardChapterRedPointUI.ResetToDefault();
_plotChapterRedPointUI.ResetToDefault();
if (_currEasyChapterInfo != null)
_easyChapterRedPointUI.NodeID = _currEasyChapterInfo.RedPointNode.ID;
if (_currHardChapterInfo != null)
_hardChapterRedPointUI.NodeID = _currHardChapterInfo.RedPointNode.ID;
if (_currPlotChapterInfo != null)
_plotChapterRedPointUI.NodeID = _currPlotChapterInfo.RedPointNode.ID;
2023-10-26 20:20:11 +08:00
var result =
IsChapterValid(_currEasyChapterInfo) ? _currEasyChapterInfo :
IsChapterValid(_currHardChapterInfo) ? _currHardChapterInfo : _currPlotChapterInfo;
return result;
}
private bool IsChapterValid(ChapterInfo chapter)
{
return chapter != null && !chapter.IsLock;
}
private async void ChangeChapter(ChapterInfo currChapterInfo)
{
if (_currChapterInfo == currChapterInfo)
return;
_currChapterInfo = currChapterInfo;
Image_Plot.gameObject.SetActive(currChapterInfo == _currPlotChapterInfo);
Image_Easy.gameObject.SetActive(currChapterInfo == _currEasyChapterInfo);
Image_Hard.gameObject.SetActive(currChapterInfo == _currHardChapterInfo);
ReturnAllStarReward();
int maxTarget = currChapterInfo.StarList[^1];
var progressLength = _starRewardsParent.rect.width;
2023-10-27 18:23:18 +08:00
var currStarCount = currChapterInfo.StarCount;
2023-10-30 18:33:18 +08:00
var currRewardIndexMax = currChapterInfo.OwnRewardMaxIndex;
2023-10-27 18:23:18 +08:00
for (int i = 0; i < currChapterInfo.StarList.Length; i++)
2023-10-26 20:20:11 +08:00
{
2023-10-27 18:23:18 +08:00
var target = currChapterInfo.StarList[i];
2023-10-26 20:20:11 +08:00
var starReward = await GetAStarReward();
2023-10-27 18:23:18 +08:00
var button = starReward.GetComponent<Button>();
button.onClick.RemoveAllListeners();
2023-10-30 18:33:18 +08:00
var index = i + 1;
if (_currChapterInfo.CanGetReward(index))
2023-10-27 18:23:18 +08:00
{
button.enabled = true;
button.onClick.AddListener(() => { OnClickStarReward(index); });
}
else
button.enabled = false;
2023-10-26 20:20:11 +08:00
_showStarReward.Add(starReward);
var targetText = starReward.transform.Find("target_green").GetComponent<TMP_Text>();
var targetWhiteText = starReward.transform.Find("target_white").GetComponent<TMP_Text>();
targetText.text = target.ToString();
targetWhiteText.text = target.ToString();
var x = ((float)target / maxTarget) * progressLength;
var rectTrans = starReward.transform as RectTransform;
rectTrans.anchoredPosition = new Vector2(x, rectTrans.anchoredPosition.y);
SetStarRewardEnable(starReward, false);
}
2023-10-30 18:33:18 +08:00
2023-10-26 20:20:11 +08:00
var ratio = (float)currStarCount / maxTarget;
Text_StarNum.text = currStarCount.ToString();
2023-10-27 12:07:33 +08:00
_cts?.Cancel();
_cts?.Dispose();
2023-10-26 20:20:11 +08:00
_cts = new CancellationTokenSource();
PlayProgressAnimation(ratio, progressLength, _cts.Token);
Refresh();
}
private void SetStarRewardEnable(GameObject starReward, bool isEnable)
{
var starRewardGreen = starReward.transform.Find("starReward_green").gameObject;
var starReward_white = starReward.transform.Find("starReward_white").gameObject;
var star_green = starReward.transform.Find("star_green").gameObject;
var star_white = starReward.transform.Find("star_white").gameObject;
var target_green = starReward.transform.Find("target_green").gameObject;
var target_white = starReward.transform.Find("target_white").gameObject;
starRewardGreen.SetActive(isEnable);
starReward_white.SetActive(!isEnable);
star_green.SetActive(isEnable);
star_white.SetActive(!isEnable);
target_green.SetActive(isEnable);
target_white.SetActive(!isEnable);
}
private async void PlayProgressAnimation(float ratio, float progressLength, CancellationToken token)
{
Image_ProcessBar.rectTransform.anchoredPosition =
new Vector2(0, Image_ProcessBar.rectTransform.anchoredPosition.y);
2023-10-26 20:20:11 +08:00
var currMoveTime = 0f;
var rewardTList = new List<float>();
var maxStarCount = _currChapterInfo.StarList[^1];
foreach (var starCount in _currChapterInfo.StarList)
{
var t = (float)starCount / maxStarCount;
rewardTList.Add(t);
}
var currStarIndex = 0;
while (currMoveTime < ratio * _totalMoveTime)
{
currMoveTime += Time.deltaTime;
var t = currMoveTime / _totalMoveTime;
var x = Mathf.Min(progressLength * ratio, progressLength * t);
Image_ProcessBar.rectTransform.anchoredPosition =
new Vector2(x, Image_ProcessBar.rectTransform.anchoredPosition.y);
if (t >= rewardTList[0])
{
rewardTList.RemoveAt(0);
SetStarRewardEnable(_showStarReward[currStarIndex], true);
currStarIndex++;
}
var isCanceled = await UniTask.DelayFrame(1, PlayerLoopTiming.Update, token).SuppressCancellationThrow();
if (isCanceled)
return;
}
}
private async UniTask<GameObject> GetAStarReward()
{
GameObject result = null;
if (_starRewardPool.Count > 0)
{
result = _starRewardPool[^1];
_starRewardPool.RemoveAt(_starRewardPool.Count - 1);
}
else
{
var prefab = await AssetManager.Instance.LoadAssetAsync<GameObject>(STAR_REWARD_PREFAB_PATH);
if (prefab == null)
{
return null;
}
GameObject go = Instantiate(prefab, _starRewardsParent.transform);
result = go;
}
result.SetActive(true);
return result;
}
private void ReturnAllStarReward()
{
foreach (var go in _showStarReward)
{
go.SetActive(false);
_starRewardPool.Add(go);
}
_showStarReward.Clear();
}
private void ShowLevelDetail()
{
if (_currSelectLevelInfo == null)
return;
2023-11-22 15:05:43 +08:00
if (_currSelectLevelInfo.IsStory)
LevelManager.Instance.TryEnterLevel(_currSelectLevelInfo.ID);
else
UIManager.Instance.OpenWindow(UINameConst.UI_BeforeFight, _currSelectLevelInfo.ID);
2023-10-27 16:47:42 +08:00
//LevelManager.Instance.TryEnterLevel(_currSelectLevelInfo.ID, CloseSelf);
2023-10-26 20:20:11 +08:00
}
private int GetLevelGroupCount()
{
return _showLevelGroupInfoList.Count;
}
private void OnRenderLevelGroup(int index, RectTransform item)
{
if (index >= _showLevelGroupInfoList.Count || index < 0)
{
DebugUtil.LogError($"index out of range,index:{index}");
return;
}
var levelGroupInfo = _showLevelGroupInfoList[index];
var Text_ChapterNum = item.Find("Text_ChapterNum").GetComponent<TMP_Text>();
var Text_ChapterName = item.Find("Text_ChapterName").GetComponent<TMP_Text>();
var Image_ProcessBarEasy = item.Find("UI_Easy/Image_ProcessBarEasy").GetComponent<Image>();
var Text_EasyStarNow = item.Find("UI_Easy/Text_EasyStarNow").GetComponent<TMP_Text>();
var Text_EasyStarMax = item.Find("UI_Easy/Text_EasyStarMax").GetComponent<TMP_Text>();
var Image_ProcessBarHard = item.Find("UI_Hard/Image_ProcessBarHard").GetComponent<Image>();
var Text_HardStarNow = item.Find("UI_Hard/Text_HardStarNow").GetComponent<TMP_Text>();
var Text_HardStarMax = item.Find("UI_Hard/Text_HardStarMax").GetComponent<TMP_Text>();
Text_ChapterNum.text = string.Format("{0:D2}", levelGroupInfo.Index);
Text_ChapterName.text = levelGroupInfo.Name;
var starCountEasy = levelGroupInfo.EasyChapter?.StarCount ?? 0;
var starMaxEasy = levelGroupInfo.EasyChapter?.StarMax ?? 0;
Image_ProcessBarEasy.fillAmount = (float)starCountEasy / starMaxEasy;
Text_EasyStarNow.text = starCountEasy.ToString();
Text_EasyStarMax.text = starMaxEasy.ToString();
var starCountHard = levelGroupInfo.HardChapter?.StarCount ?? 0;
var starMaxHard = levelGroupInfo.HardChapter?.StarMax ?? 0;
Image_ProcessBarHard.fillAmount = (float)starCountHard / starMaxHard;
Text_HardStarNow.text = starCountHard.ToString();
Text_HardStarMax.text = starMaxHard.ToString();
var UI_RedPoint = item.Find("UI_RedPoint").GetComponent<RedPointUIController>();
if (UI_RedPoint != null)
{
UI_RedPoint.NodeID = levelGroupInfo.RedPointNode.ID;
}
2023-10-26 20:20:11 +08:00
}
private void SetOnLevelGroupClick(GameObject obj)
{
BindButton(obj, () =>
{
2023-10-30 18:33:18 +08:00
var index = CommonUtils.GetTransformIndex(obj.transform);
if (index >= _showLevelGroupInfoList.Count || index < 0)
{
DebugUtil.LogError($"index out of range,index:{index}");
return;
}
2023-10-26 20:20:11 +08:00
var selectedLevelGroup = _showLevelGroupInfoList[index];
EnterSelectLevel(selectedLevelGroup);
Refresh();
});
}
private int GetLevelCount()
{
return _showLevelInfoList.Count;
}
private async void OnRenderLevelInfo(int index, RectTransform item)
{
if (index < 0 || index >= _showLevelInfoList.Count)
{
DebugUtil.LogError("index out of range,index:{0}", index);
return;
}
var levelInfo = _showLevelInfoList[index];
var Text_LevelNum = item.Find("Text_LevelNum").GetComponent<TMP_Text>();
var Text_LevelName = item.Find("Text_LevelName").GetComponent<TMP_Text>();
var Image_Star01 = item.Find("LevelStar/Image_Star01").gameObject;
var Image_Star02 = item.Find("LevelStar/Image_Star02").gameObject;
var Image_Star03 = item.Find("LevelStar/Image_Star03").gameObject;
var Image_environment = item.Find("MapLogo/Image_environment").GetComponent<Image>();
var Image_weather = item.Find("MapLogo/Image_weather").GetComponent<Image>();
var Image_daynight = item.Find("MapLogo/Image_daynight").GetComponent<Image>();
Text_LevelNum.text = $"{_currLevelGroupInfo.Index:D2}-{levelInfo.Index:D2}";
Text_LevelName.text = levelInfo.Name;
var starCount = levelInfo.StarCount;
Image_Star01.SetActive(starCount >= 1);
Image_Star02.SetActive(starCount >= 2);
Image_Star03.SetActive(starCount >= 3);
Image_environment.sprite = await CommonUtils.GetEnvironmentIcon(levelInfo.EnvironmentType);
Image_weather.sprite = await CommonUtils.GetWeatherIcon(levelInfo.WeatherType);
Image_daynight.sprite = await CommonUtils.GetDayNightIcon(levelInfo.DayNightType);
var UI_RedPoint = item.Find("UI_RedPoint").GetComponent<RedPointUIController>();
if (UI_RedPoint != null)
UI_RedPoint.NodeID = levelInfo.RedPointNode.ID;
2023-10-26 20:20:11 +08:00
}
private void SetOnLevelClick(GameObject go)
{
BindButton(go, () =>
{
2023-10-30 18:33:18 +08:00
var index = CommonUtils.GetTransformIndex(go.transform);
if (index >= _showLevelInfoList.Count || index < 0)
{
DebugUtil.LogError("index out of range!,{0}", index);
}
2023-10-26 20:20:11 +08:00
var levelInfo = _showLevelInfoList[index];
if (levelInfo != null)
{
_currSelectLevelInfo = levelInfo;
ShowLevelDetail();
}
});
}
#region Event
private void OnClickBack()
{
2023-10-27 12:07:33 +08:00
switch (_currPage)
2023-10-26 20:20:11 +08:00
{
2023-10-27 12:07:33 +08:00
case PageType.Mode:
2023-11-24 19:23:15 +08:00
CloseWindow(UIWindowCloseType.HideAndDestroy);
2023-10-27 12:07:33 +08:00
break;
case PageType.LevelGroup:
ExitSelectLevelGroup();
break;
case PageType.Level:
ExitSelectLevel();
break;
2023-10-26 20:20:11 +08:00
}
}
private void OnClickHome()
2023-11-24 19:23:15 +08:00
{
CloseWindow(UIWindowCloseType.HideAndDestroy);
//UIManager.Instance.CloseAllNormalExcept(UINameConst.UI_MainPanel);
2023-10-26 20:20:11 +08:00
}
private void OnClickEasy()
{
if (_currEasyChapterInfo != null)
ChangeChapter(_currEasyChapterInfo);
}
private void OnClickHard()
{
if (_currHardChapterInfo != null)
ChangeChapter(_currHardChapterInfo);
}
private void OnClickPlot()
{
if (_currPlotChapterInfo != null)
ChangeChapter(_currPlotChapterInfo);
}
2023-10-27 18:23:18 +08:00
private void OnClickStarReward(int index)
{
2023-10-30 18:33:18 +08:00
LevelManager.Instance.GetStarReward(_currChapterInfo.ID, index);
}
2023-10-26 20:20:11 +08:00
#endregion Event
#region override method
protected override void OnOpenWindow()
{
Refresh();
}
//invoke when open window
protected override void OnOpenWindow(object data = null)
{
Refresh();
}
//invoke when reload window
protected override void OnReloadWindow(object data = null)
{
Refresh();
}
//invoke when close window
2023-11-21 13:31:06 +08:00
protected override void OnCloseWindow()
2023-10-26 20:20:11 +08:00
{
UnRegisterSignal();
_currPage = PageType.Mode;
2023-10-27 12:36:01 +08:00
_cts?.Cancel();
_cts?.Dispose();
2023-10-26 20:20:11 +08:00
}
#endregion
}