feat(upserver): 保存M2客户端KV迁移至本人统计写检查点
保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。 保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。 验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。 包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
This commit is contained in:
@@ -0,0 +1,573 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using asap.core;
|
||||
using cfg;
|
||||
using game;
|
||||
using GameCore;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DataCenter
|
||||
{
|
||||
// 锁链礼包
|
||||
|
||||
public class TOfferChainsData
|
||||
{
|
||||
public class TItem
|
||||
{
|
||||
public int Id;
|
||||
public int Item;
|
||||
public int Number;
|
||||
// 0 可选,1 高亮 2 糊了 ,锁链隐藏 应该转换成Enum
|
||||
public int Status;
|
||||
// public int SKUId;
|
||||
}
|
||||
public bool FirstOpen; // 是否首次打开
|
||||
public int EventId; // 默认存一下ID
|
||||
public DateTime StartTime; // 开始时间
|
||||
public DateTime EndTime;// 结束时间
|
||||
public int ItemId = -1;
|
||||
// 当前抽奖次数,必须抽了才会更新
|
||||
public int LotteryNum;
|
||||
public List<TItem> Items;
|
||||
public bool Finished;
|
||||
public int ChestReward;
|
||||
public List<int> RewardList; // 当前获得的奖励Item
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class OfferChainsChestManager
|
||||
{
|
||||
[asap.core.Inject] public game.PlayerDataStorage.IPlayerSaveStore playerSaveStore { get; set; }
|
||||
|
||||
public const string SaveKey = "OfferChainsChestData";
|
||||
[Inject] public Tables _tables { get; set; }
|
||||
// 计入当前Event
|
||||
private FishingEvent _fishingEvent;
|
||||
//
|
||||
public EventOfferChainsChestMain OfferChainsChestMain { get; set; }
|
||||
public List<EventOfferChainsChestRewards> OfferChainsChestRewards { get; set; }
|
||||
// 当前可保存数据
|
||||
public TOfferChainsData OfferChainsData { get; private set; }
|
||||
|
||||
internal static game.PlayerDataStorage.PlayerKVOfferState DecodeForKVLoad(IReadOnlyDictionary<string,string> raw,
|
||||
game.PlayerDataStorage.PlayerKVSchemaSnapshot snapshot,game.PlayerDataStorage.PlayerKVSchemaDiagnostics errors) =>
|
||||
game.PlayerDataStorage.PlayerKVSaveSchema.DecodeOffer(raw,false,snapshot,errors);
|
||||
internal void ResetForKVLoad()
|
||||
{
|
||||
OfferChainsData=null; ValidIds=new List<int>(); _fishingEvent=null; _isInit=false;
|
||||
OfferChainsChestMain=null; OfferChainsChestRewards=null;
|
||||
}
|
||||
internal void ReplaceFromKVDecoded(game.PlayerDataStorage.PlayerKVOfferState state)
|
||||
{
|
||||
if (state == null) throw new ArgumentNullException(nameof(state));
|
||||
OfferChainsData=!state.IsPresent ? null : new TOfferChainsData
|
||||
{
|
||||
FirstOpen=state.FirstOpen,EventId=state.EventId,StartTime=state.StartTime,EndTime=state.EndTime,
|
||||
ItemId=state.ItemId,LotteryNum=state.LotteryNum,
|
||||
Items=state.Items.Select(item=>new TOfferChainsData.TItem {
|
||||
Id=item.Id,Item=item.Item,Number=item.Number,Status=item.Status }).ToList(),
|
||||
RewardList=new List<int>(state.RewardList),Finished=state.Finished,ChestReward=state.ChestReward
|
||||
};
|
||||
}
|
||||
internal bool IsInactiveForKVValidation => !_isInit && _fishingEvent == null;
|
||||
public List<int> ValidIds;
|
||||
|
||||
public bool IsFirstOpen
|
||||
{
|
||||
get => OfferChainsData.FirstOpen;
|
||||
set
|
||||
{
|
||||
if (OfferChainsData.FirstOpen)
|
||||
{
|
||||
OfferChainsData.FirstOpen = false;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
}
|
||||
public void UpdateEventData(FishingEvent fishingEvent)
|
||||
{
|
||||
ELog($"UpdateEventData - > {fishingEvent}");
|
||||
_fishingEvent = fishingEvent;
|
||||
UpdateLocalData();
|
||||
}
|
||||
private void UpdateLocalData()
|
||||
{
|
||||
//选择当前的 Main
|
||||
var redirectId = _fishingEvent.RedirectID;
|
||||
var configList = _tables.TbEventOfferChainsChestMain.DataList;
|
||||
foreach (var eventChallenge in configList.Where(eventChallenge => eventChallenge.ID == redirectId))
|
||||
{
|
||||
OfferChainsChestMain = eventChallenge;
|
||||
break;
|
||||
}
|
||||
OfferChainsChestRewards = _tables.TbEventOfferChainsChestRewards.DataList;
|
||||
// SkuIds = OfferChainsChestMain.IapList;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private bool _isInit = false;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
ELog("Init-> ");
|
||||
|
||||
if (_fishingEvent == null)
|
||||
return;
|
||||
//-
|
||||
if (_fishingEvent.ID != OfferChainsData?.EventId)
|
||||
{
|
||||
ClearOfferChainsData();
|
||||
}
|
||||
|
||||
if (_isInit)
|
||||
return;
|
||||
|
||||
if (OfferChainsData == null)
|
||||
{
|
||||
if (_fishingEvent == null) // 不存在Event
|
||||
{
|
||||
return;
|
||||
}
|
||||
InitOfferChainsData();
|
||||
// CreateNewData();
|
||||
}
|
||||
UpdateOfferChainsData();
|
||||
//UpdateData()
|
||||
_isInit = true;
|
||||
ELog("初始化完成-> ");
|
||||
}
|
||||
//
|
||||
private void ProcessLastOne()
|
||||
{
|
||||
ELog("ProcessLastOne-> ");
|
||||
if (OfferChainsData != null)
|
||||
{
|
||||
Settle();
|
||||
// if (!IsTimeAllow())
|
||||
// {
|
||||
// Settle();
|
||||
// // ClearOfferChainsData();
|
||||
// }
|
||||
// // else if (IsCanGetFinalReward())
|
||||
// else
|
||||
// {
|
||||
// Settle(); // 补充一下最终大奖
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateOfferChainsData()
|
||||
{
|
||||
ELog("UpdateOfferChainsData");
|
||||
// 更新可用数据
|
||||
UpdateTime();
|
||||
UpdateOfferChainsItems();
|
||||
UpdateValidIds();
|
||||
GetLotteryItemId();
|
||||
//-
|
||||
if (_fishingEvent != null && OfferChainsData != null)
|
||||
{
|
||||
GContext.container.Resolve<IFaceUIService>().AddGiftFaceUI(_fishingEvent.ID, new UIType(OfferChainsChestMain.UIPanel), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateValidIds()
|
||||
{
|
||||
var lotteryNum = OfferChainsData.LotteryNum;
|
||||
ValidIds = (from item in OfferChainsData?.Items where item.Status == 0 select item.Id).ToList();
|
||||
}
|
||||
|
||||
|
||||
private void InitOfferChainsData()
|
||||
{
|
||||
var limitedTime = (LimitedTime)_fishingEvent.TimeDefinition;
|
||||
var startTime = GlobalUtils.TryParseDateTime(limitedTime.StartTime, ZZTimeHelper.UtcNow());
|
||||
var endTime = GlobalUtils.TryParseDateTime(limitedTime.EndTime, ZZTimeHelper.UtcNow());
|
||||
|
||||
|
||||
OfferChainsData = new TOfferChainsData
|
||||
{
|
||||
FirstOpen = true,
|
||||
EventId = _fishingEvent.ID,
|
||||
StartTime = startTime,
|
||||
EndTime = endTime,
|
||||
Items = new List<TOfferChainsData.TItem>(),
|
||||
RewardList = new List<int>(),
|
||||
ChestReward = OfferChainsChestMain.ChestReward, // 最终大奖存储下,方便额外的处理
|
||||
};
|
||||
|
||||
for (var i = 0; i < OfferChainsChestRewards.Count; ++i)
|
||||
{
|
||||
var item = new TOfferChainsData.TItem
|
||||
{
|
||||
Id = OfferChainsChestRewards[i].ID,
|
||||
Item = OfferChainsChestRewards[i].Item,
|
||||
Number = OfferChainsChestRewards[i].Number,
|
||||
Status = 0,
|
||||
// SKUId = OfferChainsChestMain.IapList[i],
|
||||
};
|
||||
OfferChainsData.Items.Add(item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public bool IsTimeAllow()
|
||||
{
|
||||
var now = ZZTimeHelper.UtcNow();
|
||||
|
||||
var startTime = OfferChainsData.StartTime;
|
||||
var endTime = OfferChainsData.EndTime;
|
||||
#if UNITY_EDITOR
|
||||
var timeOpened = (now - startTime).TotalSeconds;
|
||||
var timeEnded = (endTime - now).TotalSeconds;
|
||||
ELog($"StartTime: {startTime} Now: {now} EndTime:{endTime} TimeOpened:{timeOpened} TimeToEnd:{timeEnded} Other: {1800000 - timeOpened} IsTimeAllow => {now <= endTime}");
|
||||
#endif
|
||||
return now >= startTime && now <= endTime;
|
||||
}
|
||||
public void CheckInit()
|
||||
{
|
||||
ELog("CheckInit -> ");
|
||||
|
||||
ProcessLastOne();
|
||||
Init();
|
||||
}
|
||||
|
||||
// ReSharper disable Unity.PerformanceAnalysis
|
||||
private static void Log(object t)
|
||||
{
|
||||
Debug.Log($"<color=yellow>OfferChainsChestManager -> {t} </color>");
|
||||
}
|
||||
|
||||
private static void ELog(object t)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Debug.Log($"<color=yellow>OfferChainsChestManager -> {t} </color>");
|
||||
#endif
|
||||
}
|
||||
|
||||
public bool CheckOpen()
|
||||
{
|
||||
//
|
||||
if (OfferChainsData == null) return false;
|
||||
//
|
||||
if (!IsTimeAllow()) return false;
|
||||
|
||||
if (OfferChainsChestMain == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsFinished())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsFinished()
|
||||
{
|
||||
return OfferChainsData?.Finished ?? false;
|
||||
}
|
||||
|
||||
private void SaveData()
|
||||
{
|
||||
ELog($"SaveData-> {JsonConvert.SerializeObject(OfferChainsData)}");
|
||||
playerSaveStore.Write(SaveKey, JsonConvert.SerializeObject(OfferChainsData)).RequireAccepted();
|
||||
}
|
||||
|
||||
public void LoadData(string dataValue)
|
||||
{
|
||||
ELog($"LoadData -> {dataValue}");
|
||||
OfferChainsData = JsonConvert.DeserializeObject<TOfferChainsData>(dataValue);
|
||||
}
|
||||
|
||||
// 进入游戏舞台
|
||||
public async void OnEnterGameAct()
|
||||
{
|
||||
if (!playerSaveStore.IsCurrent) return;
|
||||
Log("OnEnterGameAct()");
|
||||
IsFirstOpen = false;
|
||||
await UIManager.Instance.ShowUI(new UIType(OfferChainsChestMain.UIPanel));
|
||||
}
|
||||
|
||||
public static T WeightedRandom<T>(Dictionary<T, int> weightTable)
|
||||
{
|
||||
// 1. 计算总权重
|
||||
var totalWeight = weightTable.Values.Sum();
|
||||
// 2. 生成随机数
|
||||
var randomValue = UnityEngine.Random.Range(0, totalWeight);
|
||||
|
||||
// 3. 线性遍历选择
|
||||
var currentWeight = 0;
|
||||
foreach (var (key, value) in weightTable)
|
||||
{
|
||||
currentWeight += value;
|
||||
if (randomValue < currentWeight)
|
||||
return key;
|
||||
}
|
||||
return default; // 理论上不会执行到这里
|
||||
}
|
||||
|
||||
// 活动当前的抽奖索引
|
||||
private int CalcLotteryItemId()
|
||||
{
|
||||
var lotteryNum = OfferChainsData.LotteryNum;
|
||||
if (ValidIds.Count <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (OfferChainsChestRewards == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// while (true)
|
||||
// {
|
||||
var dict = OfferChainsChestRewards.Where(
|
||||
item => ValidIds.Contains(item.ID) && item.LimitConfig <= lotteryNum)
|
||||
.ToDictionary(elem => (elem.ID, elem.LimitConfig), elem => elem.Weight);
|
||||
var (elemId, limitConfig) = WeightedRandom(dict);
|
||||
Log($"CalcLotteryItemId -> {lotteryNum} {elemId} {limitConfig}");
|
||||
// if (limitConfig >0 && lotteryNum > limitConfig )
|
||||
return elemId;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
public int GetLotteryItemId()
|
||||
{
|
||||
ELog($"GetLotteryItemId -> {OfferChainsData.ItemId}");
|
||||
if (OfferChainsData.ItemId == -1 || !ValidIds.Contains(OfferChainsData.ItemId))
|
||||
{
|
||||
UpdateLotteryItemId();
|
||||
}
|
||||
return OfferChainsData.ItemId;
|
||||
}
|
||||
|
||||
private void UpdateOfferChainsItems()
|
||||
{
|
||||
if (OfferChainsData != null && _fishingEvent != null && OfferChainsData.EventId == _fishingEvent.ID)
|
||||
{
|
||||
var items = OfferChainsData.Items;
|
||||
if (OfferChainsChestRewards is { Count: > 0 })
|
||||
{
|
||||
for (int i = 0; i < items.Count; ++i)
|
||||
{
|
||||
EventOfferChainsChestRewards reward = null;
|
||||
if (i < OfferChainsChestRewards.Count)
|
||||
{
|
||||
reward = OfferChainsChestRewards[i];
|
||||
}
|
||||
if (reward != null)
|
||||
{
|
||||
items[i].Id = reward.ID;
|
||||
items[i].Item = reward.Item;
|
||||
items[i].Number = reward.Number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (OfferChainsChestMain != null)
|
||||
{
|
||||
OfferChainsData.ChestReward = OfferChainsChestMain.ChestReward;
|
||||
}
|
||||
}
|
||||
|
||||
// for (var i = 0; i < OfferChainsChestRewards.Count; ++i)
|
||||
// {
|
||||
// var item = new TOfferChainsData.TItem
|
||||
// {
|
||||
// Id = OfferChainsChestRewards[i].ID,
|
||||
// Item = OfferChainsChestRewards[i].Item,
|
||||
// Number = OfferChainsChestRewards[i].Number,
|
||||
// Status = 0,
|
||||
// };
|
||||
// OfferChainsData.Items.Add(item);
|
||||
// }
|
||||
}
|
||||
|
||||
private void UpdateTime()
|
||||
{
|
||||
if (OfferChainsData != null && _fishingEvent != null && OfferChainsData.EventId == _fishingEvent.ID)
|
||||
{
|
||||
var limitedTime = (LimitedTime)_fishingEvent.TimeDefinition;
|
||||
var startTime = GlobalUtils.TryParseDateTime(limitedTime.StartTime, ZZTimeHelper.UtcNow());
|
||||
var endTime = GlobalUtils.TryParseDateTime(limitedTime.EndTime, ZZTimeHelper.UtcNow().AddDays(-1));
|
||||
OfferChainsData.StartTime = startTime;
|
||||
OfferChainsData.EndTime = endTime;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLotteryItemId()
|
||||
{
|
||||
var targetId = CalcLotteryItemId();
|
||||
ELog($"UpdateLotteryItemId -> {OfferChainsData.ItemId} => {targetId}");
|
||||
OfferChainsData.ItemId = targetId;
|
||||
SaveData();
|
||||
}
|
||||
|
||||
//- 结束当前
|
||||
public void FinishLottery(int targetId)
|
||||
{
|
||||
Log($"FinishLottery -> {targetId}");
|
||||
var lastItem = OfferChainsData.Items.SingleOrDefault(it => it.Id == targetId);
|
||||
if (lastItem is { Status: not 2 })
|
||||
{
|
||||
lastItem.Status = 2;
|
||||
}
|
||||
}
|
||||
|
||||
//- 开始下一轮数据
|
||||
public void UpdateForNextLottery()
|
||||
{
|
||||
UpdateValidIds();
|
||||
UpdateLotteryItemId();
|
||||
}
|
||||
|
||||
public bool IsCanGetFinalReward()
|
||||
{
|
||||
var ret = OfferChainsData.LotteryNum >= OfferChainsData.Items.Count
|
||||
&& !OfferChainsData.Finished;
|
||||
|
||||
ELog($"IsCanGetFinalReward {ret} : {OfferChainsData.LotteryNum} {OfferChainsData.Items.Count} {OfferChainsData.Finished}");
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void ClearOfferChainsData()
|
||||
{
|
||||
OfferChainsData = null;
|
||||
_isInit = false;
|
||||
SaveData();
|
||||
}
|
||||
|
||||
public TOfferChainsData.TItem GetTItemById(int targetId)
|
||||
{
|
||||
var lastItem = OfferChainsData.Items.FirstOrDefault(it => it.Id == targetId);
|
||||
return lastItem;
|
||||
}
|
||||
|
||||
public int GetDropIdById(int id)
|
||||
{
|
||||
var item = GetTItemById(id);
|
||||
var itemData = _tables.TbItem.GetOrDefault(item.Item);
|
||||
return itemData.RedirectID;
|
||||
}
|
||||
public void OnGetFinalReward()
|
||||
{
|
||||
ELog("OnGetFinalReward()-> ");
|
||||
|
||||
// var item = _tables.TbItem.GetOrDefault(OfferChainsChestMain.ChestReward);
|
||||
var item = _tables.TbItem.GetOrDefault(OfferChainsData.ChestReward);
|
||||
var itemFinal = GContext.container.Resolve<PlayerItemData>()
|
||||
.GetItemDropItemList(item.RedirectID, 1);
|
||||
GContext.container.Resolve<PlayerItemData>().LureInflation(itemFinal, null);
|
||||
GContext.container.Resolve<PlayerItemData>().AddItem(itemFinal, null);
|
||||
GContext.Publish(new ShowData(itemFinal));
|
||||
GContext.Publish(new ShowData());
|
||||
OfferChainsData.Finished = true;
|
||||
SaveData();
|
||||
GContext.Publish(new OfferChainsRefreshEvent());
|
||||
}
|
||||
public float GetProbability(int id)
|
||||
{
|
||||
var theIdWeight = OfferChainsChestRewards.FirstOrDefault(item => item.ID == id)?.Weight ?? 0;
|
||||
var weightTable = OfferChainsChestRewards
|
||||
.Where(item => !IsItemFinished(item.ID))
|
||||
.Select(item => item.Weight).ToList();
|
||||
var totalWeight = weightTable.Sum();
|
||||
return (float)Math.Round((float)theIdWeight / totalWeight * 100, 2);
|
||||
}
|
||||
|
||||
private bool IsItemFinished(int itemID)
|
||||
{
|
||||
var lastItem = OfferChainsData.Items.SingleOrDefault(it => it.Id == itemID);
|
||||
return lastItem?.Status == 2;
|
||||
}
|
||||
|
||||
public TimeSpan GetTimeRemain()
|
||||
{
|
||||
if (OfferChainsData != null)
|
||||
{
|
||||
return OfferChainsData.EndTime - ZZTimeHelper.UtcNow();
|
||||
}
|
||||
// 活动未开始
|
||||
return ZZTimeHelper.UtcNow().AddDays(-1) - ZZTimeHelper.UtcNow();
|
||||
}
|
||||
public void Settle()
|
||||
{
|
||||
ELog("Settle");
|
||||
if (IsCanGetFinalReward())
|
||||
{
|
||||
OnGetFinalReward();
|
||||
}
|
||||
}
|
||||
|
||||
public bool UIRunning { get; set; } = false;
|
||||
public void OnBuySuccess()
|
||||
{
|
||||
Log("OnBuySuccess -> ");
|
||||
OfferChainsData.RewardList.Add(OfferChainsData.ItemId);
|
||||
OfferChainsData.LotteryNum += 1;
|
||||
// 为了处理打点
|
||||
if (IsCanGetFinalReward())
|
||||
{
|
||||
OfferChainsData.RewardList.Add(OfferChainsData.ChestReward);
|
||||
}
|
||||
FinishLottery(OfferChainsData.ItemId);
|
||||
SaveData(); // -存
|
||||
EventTracking();
|
||||
|
||||
|
||||
//NEXT TURN
|
||||
if (UIRunning)
|
||||
{ // 播放动画逻辑,交给UI
|
||||
return;
|
||||
}
|
||||
UpdateForNextLottery();
|
||||
Settle();
|
||||
}
|
||||
|
||||
public void EventTracking()
|
||||
{
|
||||
//var price = GetPayAmountById(OfferChainsData.ItemId);
|
||||
var price = GetIapItemListThisTime(OfferChainsData.LotteryNum - 1)?.PaymentAmount ?? 0F;
|
||||
var itemEnt = GetTItemById(OfferChainsData.ItemId);
|
||||
var rewardStr = string.Join(',', OfferChainsData.RewardList);
|
||||
var number = OfferChainsData.LotteryNum;
|
||||
#if AGG
|
||||
using (var e = GEvent.TackEvent("offer_chainchest"))
|
||||
{
|
||||
e.AddContent("event_id", OfferChainsData.EventId);
|
||||
e.AddContent("number", number);
|
||||
e.AddContent("price", price);
|
||||
e.AddContent("reward", OfferChainsData.ItemId);
|
||||
e.AddContent("reward_claimed_list", rewardStr);
|
||||
}
|
||||
#endif
|
||||
Log($"EventTracking: event_id: {OfferChainsData.EventId} number: {number} price: {price} " +
|
||||
$"reward :{OfferChainsData.ItemId} reward_claimed_list :{rewardStr}");
|
||||
}
|
||||
|
||||
public IAPItemList GetIapItemListThisTime()
|
||||
{
|
||||
return GetIapItemListThisTime(OfferChainsData.LotteryNum);
|
||||
}
|
||||
|
||||
private IAPItemList GetIapItemListThisTime(int index)
|
||||
{
|
||||
// var index = OfferChainsData.LotteryNum;
|
||||
index = Mathf.Clamp(index, 0, OfferChainsChestMain.IapList.Count - 1);
|
||||
var iapId = OfferChainsChestMain.IapList[index];
|
||||
var iAPItemList = _tables.TbIAPItemList.GetOrDefault(iapId);
|
||||
return iAPItemList;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user