Files
ft/CodexOutput/upserver/B3BatchMigration/M2/C2d修改前快照/088-EventPartnerStatueTokenExchangeData.cs.txt
Liubing\LB 6813da149a feat(upserver): 保存M2客户端KV迁移至本人统计写检查点
保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。
保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。

验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。
包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
2026-09-30 19:19:05 +08:00

346 lines
11 KiB
Plaintext
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 UnityEngine;
using asap.core;
using System.Collections.Generic;
using UniRx;
using System.Linq;
/// <summary>
/// 单次加分对应的待播进度条动画参数;队列挂在 Data 单例上,避免 UI Manager 被销毁重建后丢失。
/// </summary>
public struct EventPartnerStatueTokenExchangePendingUi
{
public float StartFill;
public int OldTicketCount;
public int TokenToGive;
public float EndProgressRatio;
}
public class EventPartnerStatueTokenExchangeBomb
{
public int Multiplier { get; set; }
}
public class EventPartnerStatueTokenExchangeFishRaid
{
public int Multiplier { get; set; }
}
// public static class EventPartnerTokenExchangeContextRegistry
// {
// public static IEventPartnerTokenExchangeContext Context;
// }
// /// <summary>待播 UI 队列有新条目时发布,供 Manager 在激活状态下启动消费协程。</summary>
// public class EventPartnerStatueTokenExchangePendingUiEnqueued
// {
// }
//- 处理道具获得逻辑
public class EventPartnerStatueTokenExchangeData
{
readonly game.PlayerDataStorage.IPlayerSaveStore saveStore = GContext.container.Resolve<game.PlayerDataStorage.IPlayerSaveStore>();
#region State
private int _eventId = 0;
private int _tokenGiven = 0;
private int _currentScore = 0;
public int EventId => _eventId;
public int TokenGiven => _tokenGiven;
public int CurrentScore => _currentScore;
#endregion
private readonly Queue<EventPartnerStatueTokenExchangePendingUi> _pendingScoreUi = new Queue<EventPartnerStatueTokenExchangePendingUi>();
public int PendingScoreUiCount => _pendingScoreUi.Count;
public int PendingScoreUiTokenToGive => _pendingScoreUi.Sum(item => item.TokenToGive);
public void EnqueuePendingScoreUi(in EventPartnerStatueTokenExchangePendingUi item)
{
_pendingScoreUi.Enqueue(item);
}
public bool TryPeekPendingScoreUi(out EventPartnerStatueTokenExchangePendingUi item)
{
if (_pendingScoreUi.Count == 0)
{
item = default;
return false;
}
item = _pendingScoreUi.Peek();
return true;
}
/// <summary>在对应条目的动画完整播放后调用,从队列移除队首。</summary>
public void DequeueConsumedPendingScoreUi()
{
if (_pendingScoreUi.Count > 0)
_pendingScoreUi.Dequeue();
}
public void ClearPendingScoreUiQueue()
{
_pendingScoreUi.Clear();
}
private CompositeDisposable _exchangeEventDisposables;
private void EnsureExchangeEventSubscriptions()
{
if (_exchangeEventDisposables != null)
return;
_exchangeEventDisposables = new CompositeDisposable();
GContext.OnEvent<EventPartnerStatueTokenExchangeBomb>()
.Subscribe(OnTokenExchangeBomb)
.AddTo(_exchangeEventDisposables);
GContext.OnEvent<EventPartnerStatueTokenExchangeFishRaid>()
.Subscribe(OnTokenExchangeFishRaid)
.AddTo(_exchangeEventDisposables);
}
private void OnTokenExchangeBomb(EventPartnerStatueTokenExchangeBomb evt)
{
Debug.Log("[EventPartnerStatueTokenExchange] OnTokenExchangeBomb: " + evt.Multiplier);
var bombBaseScore = GetBombBaseScore();
ApplyTokenExchangeScore(evt.Multiplier * bombBaseScore);
}
private void OnTokenExchangeFishRaid(EventPartnerStatueTokenExchangeFishRaid evt)
{
Debug.Log("[EventPartnerStatueTokenExchange] OnTokenExchangeFishRaid: " + evt.Multiplier);
var fishRaidBaseScore = GetFishRaidBaseScore();
ApplyTokenExchangeScore(evt.Multiplier * fishRaidBaseScore);
}
/// <summary>
/// 逻辑加分并入队待播 UI;与 Manager 解耦,订阅在 <see cref="Load"/> 中建立。
/// </summary>
private void ApplyTokenExchangeScore(int addedScore)
{
Debug.Log("[EventPartnerStatueTokenExchange] ApplyTokenExchangeScore: " + addedScore);
var context = GContext.container.Resolve<IEventPartnerTokenExchangeContext>();
if (context == null)
{
Debug.LogError("[EventPartnerStatueTokenExchange] token exchange context is null");
return;
}
int tokenGivenBefore = _tokenGiven;
int currentScoreBefore = _currentScore;
float startFill = GetCurrentPercentage();
(float progressRatio, int oldTicketCount, int tokenToGive) = OnAddScore(addedScore);
if (_tokenGiven == tokenGivenBefore && _currentScore == currentScoreBefore)
return;
EnqueuePendingScoreUi(new EventPartnerStatueTokenExchangePendingUi
{
StartFill = startFill,
OldTicketCount = oldTicketCount,
TokenToGive = tokenToGive,
EndProgressRatio = progressRatio
});
// RedPointManager.Instance.SetRedPointState(context.RedPointKey, context.TicketCount > 0, context.TicketCount);
// GContext.Publish(new EventPartnerStatueTokenExchangePendingUiEnqueued());
}
private float _inflationRate = 1f;
private GameCore.PlayerItemData _playerItemData = GContext.container.Resolve<GameCore.PlayerItemData>();
#region Data provider
public float GetCurrentPercentage()
{
var p = (float)CurrentScore / GetExchangeRequirement(TokenGiven);
p = Mathf.Clamp(p, 0f, 1f);
return p;
}
#endregion
#region Data storage
public static void Load(cfg.FishingEvent e)
{
var data = GContext.container.Resolve<EventPartnerStatueTokenExchangeData>();
if (data == null)
{
GContext.container.Register<EventPartnerStatueTokenExchangeData>().AsSingleton();
data = GContext.container.Resolve<EventPartnerStatueTokenExchangeData>();
}
// data.BindContext(EventPartnerTokenExchangeContextRegistry.Context);
data.EnsureExchangeEventSubscriptions();
var res = data.LoadData();
if (!res || data.EventId != e.ID)
{
data.SetData(e.ID, 0, 0);
data.ClearPendingScoreUiQueue();
}
}
public void SetData(int eventId, int tokenGiven, int currentScore)
{
var context = GContext.container.Resolve<IEventPartnerTokenExchangeContext>();
_eventId = eventId;
_tokenGiven = tokenGiven;
_currentScore = currentScore;
_inflationRate = context != null ? context.LureInflationRate : 1f;
}
private readonly string PlayfabKey = "EventPartnerStatueTokenExchangeData";
public void SaveData()
{
saveStore.Write(PlayfabKey, $"{_eventId}|{_tokenGiven}|{_currentScore}").RequireAccepted();
}
public bool LoadData()
{
var s = saveStore.Read(PlayfabKey).ValueOrThrow();
if (s == null)
return false;
try
{
var tokens = s.Split('|');
var eventId = int.Parse(tokens[0]);
var tokenGiven = int.Parse(tokens[1]);
var currentScore = int.Parse(tokens[2]);
SetData(eventId, tokenGiven, currentScore);
return true;
}
catch (System.Exception e)
{
Debug.LogError(e);
return false;
}
}
#endregion
#region Data logic
public (float progressRatio, int oldTicketCount, int tokenToGive) OnAddScore(int addedScore)
{
var context = GContext.container.Resolve<IEventPartnerTokenExchangeContext>();
if (context == null)
{
Debug.LogError("[EventPartnerStatueTokenExchange] token exchange context is null");
return (0, 0, 0);
}
(int tokenToGive, float progressRatio, int newScore) = GetProgressDataV2(_tokenGiven, _currentScore, addedScore);
_tokenGiven += tokenToGive;
GContext.Publish(new game.TargetAddData(context.TokenExchangeItemId, _currentScore, addedScore));
_currentScore = newScore;
var oldTicketCount = context.TicketCount;
context.AddTicket(tokenToGive);
SaveData();
return (progressRatio, oldTicketCount, tokenToGive);
}
#endregion
#region TableData
private List<int> TokenExchangeStep
{
get
{
var context = GContext.container.Resolve<IEventPartnerTokenExchangeContext>();
var id = context.TokenExchangeId;
return GContext.container.Resolve<cfg.Tables>().TbEventPartnerTokenExchange[id].TokenExchangeStep;
}
}
private List<int> TokenExchangeRequirement
{
get
{
var context = GContext.container.Resolve<IEventPartnerTokenExchangeContext>();
var id = context.TokenExchangeId;
return GContext.container.Resolve<cfg.Tables>().TbEventPartnerTokenExchange[id].TokenExchangeRequirement;
}
}
/// <summary>
/// Get the exchange requirement for the given number of tokens
/// </summary>
/// <param name="tokenGiven">The number of tokens given</param>
/// <returns>The exchange requirement</returns>
private int GetExchangeRequirement(int tokenGiven)
{
var requirement = TokenExchangeRequirement;
var step = TokenExchangeStep;
if (requirement.Count == 0)
{
Debug.LogError("[EventPartnerStatueTokenExchange] TokenExchangeRequirement is empty");
return 100;
}
if (step.Count > 0 && requirement.Count < step.Count + 1)
{
Debug.LogError(
$"[EventPartnerStatueTokenExchange] TokenExchangeRequirement.Count ({requirement.Count}) must be >= TokenExchangeStep.Count + 1 ({step.Count + 1})");
return 100;
}
int tierIndex;
if (step.Count == 0)
tierIndex = 0;
else
{
tierIndex = step.Count;
for (int i = 0; i < step.Count; i++)
{
if (tokenGiven < step[i])
{
tierIndex = i;
break;
}
}
}
int res = requirement[tierIndex];
res = _playerItemData.IntInflation(res, _inflationRate);
if (res <= 0)
{
Debug.LogError($"[EventPartnerStatueTokenExchange] GetExchangeRequirement returned {res} for tokenGiven {tokenGiven}");
return 100;
}
return res;
}
private (int tokenToGive, float progressRatio, int remainingScore) GetProgressDataV2(int tokenGiven, int currentScore, int scoreToAdd)
{
int totalScore = currentScore + scoreToAdd;
int tokenToGive = 0;
int requirement = GetExchangeRequirement(tokenGiven);
// Normalize + process in one loop
while (totalScore >= requirement)
{
totalScore -= requirement;
tokenToGive++;
requirement = GetExchangeRequirement(tokenGiven + tokenToGive);
}
float ratio = (float)totalScore / requirement;
return (tokenToGive, ratio, totalScore);
}
public int GetBombBaseScore()
{
var context = GContext.container.Resolve<IEventPartnerTokenExchangeContext>();
return GContext.container.Resolve<cfg.Tables>().TbEventPartnerTokenExchange[context.TokenExchangeId].ExchangeParam[1];
}
public int GetFishRaidBaseScore()
{
var context = GContext.container.Resolve<IEventPartnerTokenExchangeContext>();
return GContext.container.Resolve<cfg.Tables>().TbEventPartnerTokenExchange[context.TokenExchangeId].ExchangeParam[0];
}
#endregion
}