Files
5.24BuYu/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiangPanel.cs
T

449 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Assets.MyGame.Scripts.Net;
using Assets.MyGame.Scripts.Net.API.GameConfig_SubClass;
using Assets.MyGame.Scripts.Net.APIRequest.Params;
using Assets.MyGame.Scripts.Net.APIResponses;
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
/// <summary>奖池分组(swagger:0=旧调用兼容,1=炮台,2=战神,3=翅膀)</summary>
public enum LotteryPoolType
{
旧调用 = 0,
炮台 = 1,
战神 = 2,
翅膀 = 3,
}
/// <summary>
/// 抽奖活动总面板(第一层·最外层,ChouJiang.prefab 根节点)。
///
/// 三层结构:
/// 第一层 本面板:拉配置、切分组、抽奖、花费/次数显示;
/// 第二层 ChouJiangToggle:挂 PT炮台/CB翅膀/ZS战神 三个 toggle 面板,一个 toggle 对应一个面板,
/// 面板里一堆 item 分为 A/B/C 三个列表(items_A/items_B/items_C);
/// 第三层 ChouJiangItem:挂每个奖品格子,记录道具信息以及奖励的详细信息。
///
/// 流程:OnShow 拉取活动配置(/api/ActivityConfig/lotteryConfig)→ 初始化各 toggle 的 item
/// → 拉取已抽奖项置灰 + 用户状态(/api/Lottery/UserStatus)显示花费/次数
/// → 点抽奖调 /api/Lottery/Draw?poolType=当前分组。
///
/// 花费规则(swagger):costs 独立计费非累加——第 N+1 次抽花 costs[N],超出长度取最后一个;
/// userMaxDraw=0 不限次数;isFinished 或达到上限后不能再抽。
/// </summary>
public class ChouJiangPanel : SubUIBase
{
[Tooltip("三个 toggle 面板按顺序拖:[0]=炮台,[1]=战神,[2]=翅膀(poolType 按列表顺序自动赋值)")]
public List<ChouJiangToggle> toggles;
[Tooltip("分组切换按钮按顺序拖:[0]=炮台,[1]=战神,[2]=翅膀(还没做就留空)")]
public List<Toggle> toggles_PoolType;
[Header("可选 UI(没摆就留空,不影响逻辑)")]
public Button btn_Draw; // 抽奖按钮
public TextMeshProUGUI tmp_ActivityName; // 活动名
public TextMeshProUGUI tmp_Cost; // 下一次抽奖花费
public TextMeshProUGUI tmp_DrawCount; // 已抽次数/上限
public TextMeshProUGUI tmp_EndTime; // 活动结束时间/状态
[Tooltip("抽奖花费的货币类型:1=金币,3=人民币(¥),其他=钻石")]
public int costPayType = 1;
/// <summary>当前展示/抽奖的奖池分组</summary>
public LotteryPoolType currPoolType { get; private set; }
private LotteryActivityConfig config; // 活动配置
private LotteryUserStatus userStatus; // 用户抽奖状态(次数决定花费与上限)
/// <summary>已抽中的奖项ID(兜底重抽 + 跑马灯只跳未抽的格子)</summary>
private readonly HashSet<string> drawnIds = new HashSet<string>();
private bool isDrawing; // 防连点
public override void Awake()
{
base.Awake();
// toggles 列表顺序即分组:[0]=炮台,[1]=战神,[2]=翅膀(按顺序自动赋 poolType,不用逐个手设)
if (toggles != null)
{
int n = Mathf.Min(toggles.Count, 3);
for (int i = 0; i < n; i++)
{
if (toggles[i] != null)
toggles[i].poolType = (LotteryPoolType)(i + 1);
}
}
// 分组切换:一个按钮对应一个 toggle 面板(索引 0/1/2 = 炮台/战神/翅膀)
if (toggles_PoolType != null)
{
for (int i = 0; i < toggles_PoolType.Count; i++)
{
if (toggles_PoolType[i] == null) continue;
LotteryPoolType type = (LotteryPoolType)(i + 1);
toggles_PoolType[i].onValueChanged.AddListener(isOn =>
{
if (isOn) ShowPoolType(type);
});
}
}
if (btn_Draw != null)
btn_Draw.onClick.AddListener(OnDrawClick);
// 初始分组 = 预制体里当前激活的面板,并同步一次面板/按钮状态
ChouJiangToggle first = GetActiveToggle();
currPoolType = first != null ? first.poolType : LotteryPoolType.炮台;
ShowPoolType(currPoolType);
}
protected override void OnShow(object args = null)
{
base.OnShow(args);
Refresh();
}
/// <summary>拉取活动配置 + 刷新各 toggle 与用户状态</summary>
public void Refresh()
{
APIRequestList.GetLotteryActivityConfig(cfg =>
{
if (this == null) return;
config = cfg;
// 活动未开启:提示后关闭面板
if (config != null && !config.isOpen)
{
UIManager.Instance.ShowTiShi("活动未开启");
uIManager.Hide(this);
return;
}
UpdateActivityUI();
InitToggles();
RefreshUserStatus();
});
}
/// <summary>初始化各 toggle 面板的 item(A/B/C 三个列表),并置灰已抽中的奖项</summary>
private void InitToggles()
{
if (toggles == null || config == null) return;
for (int i = 0; i < toggles.Count; i++)
{
if (toggles[i] != null)
toggles[i].Init(config);
}
RefreshDrawnPrizes();
}
/// <summary>拉取用户抽奖状态(已抽次数决定下一次花费与是否到上限)</summary>
private void RefreshUserStatus()
{
APIRequestList.GetLotteryUserStatus(status =>
{
if (this == null) return;
userStatus = status;
if (status != null && status.drawnPrizeIds != null)
{
for (int i = 0; i < status.drawnPrizeIds.Count; i++)
drawnIds.Add(status.drawnPrizeIds[i]);
}
UpdateDrawUI();
});
}
/// <summary>拉取已抽奖项列表,置灰各 toggle 的对应 item(奖励不重复)</summary>
private void RefreshDrawnPrizes()
{
APIRequestList.GetLotteryDrawnPrizes(ids =>
{
if (this == null || toggles == null) return;
drawnIds.Clear();
if (ids != null)
{
for (int i = 0; i < ids.Count; i++)
drawnIds.Add(ids[i]);
}
for (int i = 0; i < toggles.Count; i++)
{
if (toggles[i] != null)
toggles[i].RefreshDrawnState(ids);
}
});
}
/// <summary>切到指定分组的 toggle 面板</summary>
public void ShowPoolType(LotteryPoolType type)
{
currPoolType = type;
if (toggles != null)
{
for (int i = 0; i < toggles.Count; i++)
{
if (toggles[i] == null) continue;
toggles[i].gameObject.SetActive(toggles[i].poolType == type);
}
}
// 同步切换按钮勾选状态(不触发回调)
if (toggles_PoolType != null)
{
int idx = (int)type - 1;
for (int i = 0; i < toggles_PoolType.Count; i++)
{
if (toggles_PoolType[i] == null || i == idx) continue;
toggles_PoolType[i].SetIsOnWithoutNotify(false);
}
if (idx >= 0 && idx < toggles_PoolType.Count && toggles_PoolType[idx] != null)
toggles_PoolType[idx].SetIsOnWithoutNotify(true);
}
UpdateDrawButtonState();
}
/// <summary>取当前激活的 toggle 面板(都没有激活时取第一个)</summary>
private ChouJiangToggle GetActiveToggle()
{
if (toggles == null || toggles.Count == 0) return null;
for (int i = 0; i < toggles.Count; i++)
{
if (toggles[i] != null && toggles[i].gameObject.activeSelf)
return toggles[i];
}
return toggles[0];
}
/// <summary>活动信息显示</summary>
private void UpdateActivityUI()
{
if (config == null) return;
if (tmp_ActivityName != null)
tmp_ActivityName.text = config.activityName;
if (tmp_EndTime != null)
{
if (!config.isOpen)
tmp_EndTime.text = "活动未开启";
else if (config.endTime > 0)
tmp_EndTime.text = "结束时间 " + DateTimeOffset
.FromUnixTimeSeconds(config.endTime).LocalDateTime.ToString("yyyy/MM/dd HH:mm");
else
tmp_EndTime.text = "长期活动";
}
}
/// <summary>花费/次数/按钮状态显示</summary>
private void UpdateDrawUI()
{
int drawn = userStatus != null ? userStatus.totalDrawCount : 0;
if (tmp_Cost != null)
{
long cost = GetNextCost();
tmp_Cost.text = costPayType switch
{
1 => $"<sprite name=\"金币\">{cost}",
3 => $"¥{cost}",
_ => $"<sprite name=\"钻石\">{cost}",
};
}
if (tmp_DrawCount != null)
tmp_DrawCount.text = config != null && config.userMaxDraw > 0
? $"{drawn}/{config.userMaxDraw}"
: drawn.ToString();
UpdateDrawButtonState();
}
private void UpdateDrawButtonState()
{
if (btn_Draw != null)
btn_Draw.interactable = CanDraw() && !isDrawing;
}
/// <summary>下一次抽奖花费:第 N+1 次取 costs[N],超出长度取最后一个(独立计费非累加)</summary>
private long GetNextCost()
{
if (config == null || config.costs == null || config.costs.Count == 0) return 0;
int drawn = userStatus != null ? userStatus.totalDrawCount : 0;
int idx = Mathf.Min(drawn, config.costs.Count - 1);
return config.costs[idx];
}
/// <summary>能否再抽:活动开启、在时间内、未抽完、未达次数上限</summary>
private bool CanDraw()
{
if (config == null || !config.isOpen) return false;
if (userStatus != null && userStatus.isFinished) return false;
long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (config.startTime > 0 && now < config.startTime) return false;
if (config.endTime > 0 && now > config.endTime) return false;
if (config.userMaxDraw > 0 && userStatus != null
&& userStatus.totalDrawCount >= config.userMaxDraw) return false;
return true;
}
/// <summary>取当前展示/抽奖的分组 toggle 面板</summary>
private ChouJiangToggle GetCurrentToggle()
{
if (toggles == null) return null;
for (int i = 0; i < toggles.Count; i++)
{
if (toggles[i] != null && toggles[i].poolType == currPoolType)
return toggles[i];
}
return null;
}
/// <summary>已抽中奖项兜底重抽的最大次数(服务端正常排除时不会触发)</summary>
private const int MaxDrawRetries = 3;
/// <summary>抽奖:GET /api/Lottery/Draw?poolType=当前分组,结果出来后跑马灯停在抽中的格子</summary>
private void OnDrawClick()
{
if (isDrawing || !CanDraw()) return;
isDrawing = true;
UpdateDrawButtonState();
bool netDone = false; // 网络完成
bool effectDone = true; // 动效完成(默认无动效即完成)
DrawWithRetry(0, res =>
{
netDone = true;
if (this == null || res == null) return;
// 记下本次抽中,同一轮后续抽奖不再要它
drawnIds.Add(res.prizeId);
// 抽中结果(结算 UI 摆好后在这里接入)
Debug.Log($"[ChouJiangPanel] 抽中:{res.name} x{res.count}(type={res.type},消耗={res.cost},分组={(LotteryPoolType)res.poolType})");
// 跑马灯动效:跳到抽中的格子上停住,结束后刷新状态解锁按钮(方块预制体在各 ChouJiangItem 格子上)
ChouJiangToggle cur = GetCurrentToggle();
if (cur != null)
{
effectDone = false;
cur.PlayDrawEffect(res.prizeId, () =>
{
if (this == null) return;
effectDone = true;
isDrawing = false;
RefreshUserStatus(); // 花费随次数增长
RefreshDrawnPrizes(); // 已抽奖项置灰
UpdateDrawButtonState();
ShowReward(res); // 领取奖励:弹获得面板
});
}
// 没有当前面板或动效没启动(没预制体等)时直接解锁
if (effectDone)
{
isDrawing = false;
RefreshUserStatus();
RefreshDrawnPrizes();
UpdateDrawButtonState();
ShowReward(res);
}
});
// code!=0 时 APIManager 只弹错误不回调,超时兜底解锁防连点锁死(网络+动效时间)
StartCoroutine(IEWaitDrawDone(() => netDone && effectDone));
}
/// <summary>
/// 领取奖励:用 UIManager.ShowHuoDe 弹获得面板。
/// type:1=金币 2=钻石 3=道具 4=道具碎片(碎片按道具展示)。
/// Draw 接口不返回 product/icon,用 prizeId 回活动配置里查奖项详情。
/// </summary>
private void ShowReward(LotteryDrawResponse res)
{
if (res == null) return;
bool isProp = res.type == 3 || res.type == 4; // 道具/碎片走下载图标
LotteryPrize prize = isProp ? FindPrizeById(res.prizeId) : null;
// icon 优先(实际数据是完整 URL),product.imageUrl 兜底;相对路径才拼 baseURL,避免拼出双地址
string imgUrl = prize != null
? (string.IsNullOrEmpty(prize.icon) ? prize.product?.imageUrl : prize.icon)
: "";
if (!string.IsNullOrEmpty(imgUrl) && !imgUrl.StartsWith("http"))
imgUrl = APIManager.baseURL + imgUrl;
var data = new List<BackPackPropItemData>
{
new BackPackPropItemData
{
giftType = isProp ? 3 : res.type,
count = (int)res.count,
url = isProp ? imgUrl : "",
propEnum = isProp ? (PropEnum)(prize?.product?.type ?? 0) : PropEnum.none,
},
};
UIManager.Instance.ShowHuoDe(data);
}
/// <summary>用 prizeId 在活动配置的所有奖池里找奖项详情</summary>
private LotteryPrize FindPrizeById(string prizeId)
{
if (config == null || config.pools == null || string.IsNullOrEmpty(prizeId)) return null;
for (int i = 0; i < config.pools.Count; i++)
{
LotteryPoolLevel pool = config.pools[i];
if (pool == null || pool.prizeList == null) continue;
for (int j = 0; j < pool.prizeList.Count; j++)
{
if (pool.prizeList[j] != null && pool.prizeList[j].prizeId == prizeId)
return pool.prizeList[j];
}
}
return null;
}
/// <summary>
/// 抽奖(带兜底重抽):已经抽到过的奖项不再要——服务端按设计应排除已抽中的
/// prizeId(swagger:prizeId 用于唯一性控制,同一用户不能重复抽中),
/// 没排除时客户端重抽兜底,最多 MaxDrawRetries 次(注意:每次重抽服务端照常计费扣次数,
/// 根本解决要在后端 Draw 里排除 drawnPrizeIds)。
/// </summary>
private void DrawWithRetry(int attempt, Action<LotteryDrawResponse> onFinal)
{
APIRequestList.LotteryDraw(new LotteryDrawParam { poolType = (int)currPoolType }, res =>
{
if (this == null) return;
if (res != null && !string.IsNullOrEmpty(res.prizeId)
&& drawnIds.Contains(res.prizeId) && attempt < MaxDrawRetries)
{
Debug.LogWarning($"[ChouJiangPanel] 抽到已中过的 {res.prizeId},兜底重抽({attempt + 1}/{MaxDrawRetries})");
DrawWithRetry(attempt + 1, onFinal);
return;
}
onFinal?.Invoke(res);
});
}
/// <summary>等待网络+动效完成(最多 15 秒),结束解锁按钮</summary>
private IEnumerator IEWaitDrawDone(Func<bool> done)
{
float t = 0f;
while (!done() && t < 15f)
{
t += Time.deltaTime;
yield return null;
}
isDrawing = false;
UpdateDrawButtonState();
}
}