feat(upserver): 保存M2客户端KV迁移至本人统计写检查点

保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。
保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。

验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。
包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
This commit is contained in:
Liubing\LB
2026-09-30 19:19:05 +08:00
parent 1c0cdf7a99
commit 6813da149a
511 changed files with 151858 additions and 2376 deletions

View File

@@ -0,0 +1,790 @@
using asap.core;
using cfg;
using game;
using GameCore;
using LitJson;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class CampDataMM
{
[asap.core.Inject] public game.PlayerDataStorage.IPlayerSaveStore SaveStore { get; set; }
private cfg.Tables _tables;
MMTService _MMService;
public CampDataMM(cfg.Tables tables, MMTService mmService)
{
this._tables = tables;
_MMService = mmService;
}
#region SERVER_DATA
int _id = 101;
public int PreID;
public int Id
{
set { _id = value; }
get
{
return _id;
}
}
public int AreaIndex { get; private set; } = 0;
public int AreaStep { get; private set; } = 0;
Dictionary<string, int> CampDataDic = new Dictionary<string, int>();
#endregion
public int AreaId { get; private set; } = 0;
public int AreaStepCount { get; private set; }
public int TimelineId { get; private set; }
public string Name { get { return GetMMTString(_tables.TbConstruction.DataMap[Id].Name_l10n_key); } }
public string AreaIcon { get; private set; }
public string BasePrefab => GetBasePrefab(Id);
public List<string> AllPrefabs
{
get
{
List<int> AreaList = _tables.TbConstruction.DataMap[Id].AreaList;
return AreaList.Select(stepid => GetArea(stepid).BuildingPrefeb).Distinct().ToList();
}
}
public string CurrentPrefab
{
get
{
int index = AreaIndex;
if (index >= AreaList.Count)
index = AreaList.Count - 1;
return GetArea(AreaList[index]).BuildingPrefeb;
}
}
public Area GetAreaByIndex(int index)
{
if (index >= AreaList.Count)
index = AreaList.Count - 1;
return GetArea(AreaList[index]);
}
public string BGM { get { return _tables.TbConstruction.DataMap[Id].BGM; } }
public int NextMapID { get { return _tables.TbConstruction.DataMap[Id].NextID; } }
public Area constructionArea { get; private set; }
public Step StepInfo { get; private set; }
public List<int> AreaList
{
get
{
return _tables.TbConstruction.DataMap[Id].AreaList;
}
}
public List<int> AreaStepList { get; private set; }
public bool IsUpdataArea = false;
string _isOpenSkyscraper = "0";
public bool IsOpenSkyscraper => _isOpenSkyscraper == "1" || IsCanSkyscraper;
public bool IsCanSkyscraper
{
get
{
var dataMap = _tables.TbMapData.DataList[^1];
return GContext.container.Resolve<PlayerData>().lv >= dataMap.LevelRequired;
}
}
//public int curQRewardCount = 0;
//是否需要修复
public bool IsFix => _isFix && AreaIndex < AreaList.Count;
bool _isFix;
internal static game.PlayerDataStorage.PlayerKVCampState DecodeForKVLoad(IReadOnlyDictionary<string,string> raw,
game.PlayerDataStorage.PlayerKVSchemaDiagnostics errors) => game.PlayerDataStorage.PlayerKVSaveSchema.DecodeCamp(raw,errors);
internal void ResetForKVLoad()
{
CampDataDic=new Dictionary<string,int>(StringComparer.Ordinal);
_isFix=false; _isOpenSkyscraper="0"; _id=101;
PreID=AreaIndex=AreaStep=AreaId=AreaStepCount=TimelineId=0;
AreaIcon=null; constructionArea=null; StepInfo=null; AreaStepList=null; IsUpdataArea=false;
}
internal void ReplaceFromKVDecoded(game.PlayerDataStorage.PlayerKVCampState state)
{
if (state == null) throw new ArgumentNullException(nameof(state));
CampDataDic=state.Values.ToDictionary(p=>p.Key,p=>p.Value,StringComparer.Ordinal);
_isFix=state.IsFix; _isOpenSkyscraper=state.Skyscraper;
}
internal game.PlayerDataStorage.PlayerKVCampState SnapshotForKVValidation() =>
new game.PlayerDataStorage.PlayerKVCampState(_isFix,_isOpenSkyscraper,CampDataDic);
public void InitData(Dictionary<string, string> userData)
{
string value;
if (userData.TryGetValue("IsFix", out value))
{
SetIsFix(bool.Parse(value), false);
}
if (userData.TryGetValue("IsOpenSkyscraper", out value))
{
_isOpenSkyscraper = value;
}
if (userData.TryGetValue("CampDataDic", out value))
{
CampDataDic = JsonMapper.ToObject<Dictionary<string, int>>(value);
}
}
public void SetIsFix(bool isFix, bool isSave = true)
{
_isFix = isFix;
if (isSave)
{
SaveStore.Write("IsFix", _isFix.ToString()).RequireAccepted();
}
}
public void SetCampAreaIndex(string id_string)
{
AreaIndex = GlobalUtils.TryParseInt(id_string);
}
public void SetCampAreaStep(string step_string)
{
AreaStep = GlobalUtils.TryParseInt(step_string);
}
void SetCampByLevel()
{
List<Construction> _dataList = _tables.TbConstruction.DataList;
if (!_tables.TbConstruction.DataMap.ContainsKey(Id))
{
Debug.LogError($"[CampData]UpdateCurrentData: Not found data of id({Id})!");
Id = _dataList[^1].ID;
}
int level = GContext.container.Resolve<PlayerData>().lv;
int id = _dataList[0].ID;
for (int i = _dataList.Count - 1; i >= 0; i--)
{
if (_dataList[i].InitialLevel <= level)
{
id = _dataList[i].ID;
level -= _dataList[i].InitialLevel;
break;
}
}
AreaIndex = AreaList.Count;
var _area = GetArea(AreaList[^1]);
int StepCount = _area.StepCount;
AreaStep = StepCount;
if (id % 1000 <= Id % 1000)
{
Id = id;
AreaIndex = AreaList.Count;
for (int i = 0; i < AreaList.Count; i++)
{
_area = GetArea(AreaList[i]);
StepCount = _area.StepCount;
if (StepCount > level)
{
AreaIndex = i;
AreaStep = level;
break;
}
level -= StepCount;
}
}
}
public void Init()
{
if (CampDataDic.Count > 0)
{
Id = CampDataDic["CampId"];
}
SetCampByLevel();
PreID = Id;
if (_tables.TbConstruction.DataMap.ContainsKey(Id))
{
if (AreaIndex >= AreaList.Count)
{
CompleteInit();
}
else
{
UpdateCurrentData();
}
}
else
{
Debug.LogError($"[CampData]UpdateCurrentData: Not found data of id({Id})!");
}
}
public void ChangeNextCampID()
{
OnChangeCamp(NextMapID);
}
public void ChangeCampId(int id)
{
OnChangeCamp(id);
}
void OnChangeCamp(int id)
{
if (_tables.TbConstruction.DataMap.TryGetValue(id, out var camp_data))
{
constructionArea = null;
Id = id;
PreID = Id;
SetCampByLevel();
if (AreaIndex >= AreaList.Count)
{
CompleteInit();
}
else
{
UpdateCurrentData();
}
CampDataDic["CampId"] = Id;
SaveStore.Write("CampDataDic", JsonMapper.ToJson(CampDataDic)).RequireAccepted(); ;
}
else
{
Debug.LogError($"[CampData]UpdateCurrentData: Not found data of id({id})!");
}
}
public void ShowReward()
{
int lv = GContext.container.Resolve<PlayerData>().lv;
if (_tables.TbGlobalConfig.AutoClaimUnlock > 0 && lv > _tables.TbGlobalConfig.AutoClaimUnlock)
GContext.Publish(new ShowData(RewardType.AutoOpen));
else
GContext.Publish(new ShowData());
}
public void UpdateCurrentData()
{
if (AreaList != null)
{
if (AreaIndex < 0 || AreaIndex >= AreaList.Count)
{
if (NextMapID == -1 || !_tables.TbConstruction.DataMap.ContainsKey(NextMapID))
{
_isOpenSkyscraper = "1";
SaveStore.Write("IsOpenSkyscraper", _isOpenSkyscraper).RequireAccepted();
return;
}
Debug.LogError(
$"[CampData]UpdateCurrentData: AreaIndex({AreaIndex}) is out of range[0-{AreaList.Count}]! Will reset to 0");
AreaIndex = 0;
}
IsUpdataArea = AreaId != AreaList[AreaIndex];
if (IsUpdataArea)
{
AreaId = AreaList[AreaIndex];
SetArea();
}
if (AreaStepList != null)
{
if (AreaStep < 0 || AreaStep >= AreaStepList.Count)
{
AreaStep = 0;
Debug.LogError(
$"[CampData]UpdateCurrentData: AreaStep({AreaStep}) is out of range[0-{AreaStepList.Count}]! Reset it to 0.");
}
var step_id = AreaStepList[AreaStep];
SetAreaStep(step_id);
}
else
{
Debug.LogError($"[CampData]UpdateCurrentData: AreaStepList is empty!");
}
}
else
{
Debug.LogError($"[CampData]UpdateCurrentData: AreaList is empty!");
}
}
void SetArea()
{
constructionArea = GetArea(AreaId);
if (constructionArea == null)
{
Debug.LogError($"[CampData]UpdateCurrentData: Not found data of area id({AreaId})!");
}
AreaIcon = constructionArea.Icon;
TimelineId = constructionArea.TimelineID;
AreaStepCount = constructionArea.StepCount;
AreaStepList = constructionArea.StepList;
}
void SetAreaStep(int step_id)
{
StepInfo = GetStep(step_id);
if (StepInfo == null)
{
Debug.LogError($"[CampData]UpdateCurrentData: Not found step data of id({step_id})!");
}
}
void CompleteInit()
{
if (AreaList != null)
{
int curAreaIndex = AreaList.Count - 1;
AreaId = AreaList[curAreaIndex];
SetArea();
if (AreaStepList != null)
{
var step_id = AreaStepList[^1];
SetAreaStep(step_id);
}
if (NextMapID == -1 || !_tables.TbConstruction.DataMap.ContainsKey(NextMapID))
{
_isOpenSkyscraper = "1";
SaveStore.Write("IsOpenSkyscraper", _isOpenSkyscraper).RequireAccepted();
}
}
}
void SetHome_Skyscraper()
{
if (IsOpenSkyscraper)
{
if (IsCanSkyscraper)
{
ulong curGold = GContext.container.Resolve<PlayerData>().gold;
ulong basePrice =
(ulong)GContext.container.Resolve<Tables>().TbMapData.DataList[^1].ConstrutionCost;
var multiplierTable = GContext.container.Resolve<Tables>().TbConstructionInfiniteConfig.CashMagList;
var ibData = new InfiniteBuildingData();
ibData.Deserialize(GContext.container.Resolve<PlayerData>().InfiniteBuildingData);
int step = ibData.Step;
ulong price = (ulong)(basePrice * multiplierTable[step]);
int count = 0;
while (curGold >= price)
{
curGold -= price;
step++;
step %= multiplierTable.Count;
price = (ulong)(basePrice * multiplierTable[step]);
count++;
if (count >= 99)
break;
}
RedPointManager.Instance.SetRedPointState(RedPointName.Home_Skyscraper, count > 0, (int)count);
}
else
{
RedPointManager.Instance.SetRedPointState(RedPointName.Home_Skyscraper, false);
}
}
}
public bool GoBuilding()
{
if (AreaStep >= AreaStepList.Count)
{
return false;
}
long gold = (long)(ulong)GContext.container.Resolve<PlayerData>().gold;
var buffTimeData = GContext.container.Resolve<BuffDataCenter>().GetWeelyBuffTimeData<ConstructionCost>();
bool isConstructionCost = buffTimeData != null;
long _upgradeCost;
var step_id = AreaStepList[AreaStep];
var camp_data = _tables.TbConstruction.DataMap[Id];
List<int> _areaStepList = AreaStepList;
int start;
int endIndex;
List<int> _areaList;
_areaList = camp_data.AreaList;
start = _tables.TbConstructionStep.DataList.IndexOf(_tables.TbConstructionStep.DataMap[step_id]);
_areaStepList = GetArea(_areaList[^1]).StepList;
endIndex = _tables.TbConstructionStep.DataList.IndexOf(_tables.TbConstructionStep.DataMap[_areaStepList[^1]]);
List<ConstructionStep> DataList = _tables.TbConstructionStep.DataList;
for (int i = start; i <= endIndex; i++)
{
if (isConstructionCost)
{
_upgradeCost = (long)(DataList[i].BuildRequired * (1 - buffTimeData.Param));
}
else
{
_upgradeCost = DataList[i].BuildRequired;
}
gold -= _upgradeCost;
if (gold < 0)
{
return false;
}
}
return true;
}
public bool GoBuilding02(int endId)
{
if (AreaStep >= AreaStepList.Count)
{
return false;
}
var step_id = AreaStepList[AreaStep];
if (step_id < 101003 || step_id >= endId)
{
return false;
}
long gold = (long)(ulong)GContext.container.Resolve<PlayerData>().gold;
long _upgradeCost;
int start = _tables.TbConstructionStep.DataList.IndexOf(_tables.TbConstructionStep.DataMap[step_id]);
int endIndex = _tables.TbConstructionStep.DataList.IndexOf(_tables.TbConstructionStep.DataMap[endId]);
List<ConstructionStep> DataList = _tables.TbConstructionStep.DataList;
for (int i = start; i < endIndex; i++)
{
_upgradeCost = DataList[i].BuildRequired;
gold -= _upgradeCost;
if (gold < 0)
{
return false;
}
}
return true;
}
/// <summary>
/// 从当前 <see cref="AreaStep"/> 起,用给定金币连续扣款,返回能支付多少步;含 ConstructionCost buff,与建造红点累计逻辑一致。全程 <see cref="ulong"/>,避免金币超出 <c>long.MaxValue</c> 时强转失真。
/// </summary>
int CountAffordableConstructionSteps(ulong gold)
{
if (AreaStepList == null || AreaStep >= AreaStepList.Count)
{
return 0;
}
var step_id = AreaStepList[AreaStep];
int nextId = NextMapID;
var camp_data = _tables.TbConstruction.DataMap[Id];
while (_tables.TbConstruction.DataMap.TryGetValue(nextId, out var _camp_data))
{
camp_data = _camp_data;
nextId = camp_data.NextID;
}
var buffTimeData = GContext.container.Resolve<BuffDataCenter>().GetWeelyBuffTimeData<ConstructionCost>();
bool isConstructionCost = buffTimeData != null;
List<int> _areaList = camp_data.AreaList;
int start = _tables.TbConstructionStep.DataList.IndexOf(_tables.TbConstructionStep.DataMap[step_id]);
List<int> _areaStepList = GetArea(_areaList[^1]).StepList;
int endIndex = _tables.TbConstructionStep.DataList.IndexOf(_tables.TbConstructionStep.DataMap[_areaStepList[^1]]);
int length = Math.Min(start + 100, endIndex + 1);
List<ConstructionStep> DataList = _tables.TbConstructionStep.DataList;
int count = 0;
for (int i = start; i < length; i++)
{
ulong stepCost = isConstructionCost
? (ulong)(DataList[i].BuildRequired * (1 - buffTimeData.Param))
: (ulong)DataList[i].BuildRequired;
if (gold < stepCost)
{
break;
}
gold -= stepCost;
count++;
}
return count;
}
public void SetRedPoint()
{
SetHome_Skyscraper();
int count = 0;
bool redPoint = false;
var playerData = GContext.container.Resolve<PlayerData>();
if (GContext.container.Resolve<GuideDataCenter>().GetIsFinish(_tables.TbGlobalConfig.BuildRedPointUnlock)
&& playerData.gold >= (ulong)StepInfo.BuildRequired)
{
if (AreaStepList == null || AreaStep >= AreaStepList.Count)
{
RedPointManager.Instance.SetRedPointState(RedPointName.Home_Build, redPoint, count);
return;
}
count = CountAffordableConstructionSteps(playerData.gold);
redPoint = true;
}
RedPointManager.Instance.SetRedPointState(RedPointName.Home_Build, redPoint, count);
}
public int GetNextStepRequired(int allStep)
{
int price = 0;
if (!IsOpenSkyscraper || !IsCanSkyscraper)
{
if (AreaStep < AreaStepList.Count)
{
var step_id = AreaStepList[AreaStep];
List<int> BuildRequired = new List<int>();
List<ConstructionStep> DataList = _tables.TbConstructionStep.DataList;
int start = DataList.IndexOf(_tables.TbConstructionStep.DataMap[step_id]);
int length = start + allStep;
length = Math.Min(length, DataList.Count);
for (int i = start; i < length; i++)
{
allStep--;
price += DataList[i].BuildRequired;
}
}
}
if (allStep > 0)
{
int basePrice = GContext.container.Resolve<Tables>().TbMapData.DataList[^1].ConstrutionCost;
var multiplierTable = GContext.container.Resolve<Tables>().TbConstructionInfiniteConfig.CashMagList;
var ibData = new InfiniteBuildingData();
ibData.Deserialize(GContext.container.Resolve<PlayerData>().InfiniteBuildingData);
int step = ibData.Step;
for (int i = 0; i < allStep; i++)
{
price += (int)(basePrice * multiplierTable[step]);
step++;
step %= multiplierTable.Count;
}
}
return price;
}
public int GetStepRequired(int allStep)
{
int price = 0;
var start_id = AreaStepList[AreaStep];
var lastAreaStepList = GetArea(_tables.TbConstruction.DataMap[Id].AreaList[^1]).StepList;
var end_id = lastAreaStepList[^1];
if (end_id - start_id < allStep)
{
return -1;
}
List<ConstructionStep> DataList = _tables.TbConstructionStep.DataList;
int start = DataList.IndexOf(_tables.TbConstructionStep.DataMap[start_id]);
int length = start + allStep;
for (int i = start; i < length; i++)
{
price += DataList[i].BuildRequired;
}
return price;
}
public void NextStep()
{
if (AreaStepList != null)
{
AreaStep++;
if (AreaStep < AreaStepList.Count)
{
UpdateCurrentData();
}
else
{
AreaIndex++;
if (AreaIndex < AreaList.Count)
{
AreaStep = 0;
UpdateCurrentData();
}
else
{
AreaIndex = AreaList.Count;
AreaStep = AreaStepList.Count;
}
}
}
else
{
GameDebug.LogError($"[CampData]NextStep: AreaStepList is empty!");
}
}
public Area GetArea(int id)
{
// if (id < 102000)
// {
// if (_MMService.MMBuilding)
// {
// ConstructionMMTArea areaMM = _tables.TbConstructionMMTArea.GetOrDefault(id);
// if (areaMM != null)
// {
// return new Area(areaMM);
// }
// }
// }
ConstructionArea area = _tables.TbConstructionArea.GetOrDefault(id);
if (area != null)
return new Area(area);
return null;
}
public Step GetStep(int id)
{
// if (id < 102000)
// {
// if (_MMService.MMBuilding)
// {
// ConstructionMMTStep stepMM = _tables.TbConstructionMMTStep.GetOrDefault(id);
// if (stepMM != null)
// {
// return new Step(stepMM);
// }
// }
// }
ConstructionStep step = _tables.TbConstructionStep.GetOrDefault(id);
if (step != null)
return new Step(step);
return null;
}
public string GetBasePrefab(int id)
{
List<int> AreaList = _tables.TbConstruction.DataMap[id].AreaList;
return GetArea(AreaList[0]).BuildingPrefeb;
}
public string GetMMTString(string oldValue)
{
// if (_MMService.MMBuilding)
// {
// var mmt = _tables.TbMMTStringData.GetOrDefault(oldValue);
// if (mmt != null)
// {
// return mmt.NewString;
// }
// }
return oldValue;
}
}
public class Area
{
public int ID { get; private set; }
/// <summary>
/// Building脚本中的序号,从0开始
/// </summary>
public int TimelineID { get; private set; }
public int StepCount { get; private set; }
public string BuildingPrefeb { get; private set; }
/// <summary>
/// 不同prefeb转换的时候,上一个timeline播放时间
/// </summary>
public float TimelinePlaytime { get; private set; }
/// <summary>
/// 多语言key + 中文
/// </summary>
public string Name { get; private set; }
public string Name_l10n_key { get; }
/// <summary>
/// 多语言key + 中文
/// </summary>
public string NameIndex { get; private set; }
public string NameIndex_l10n_key { get; }
public string Icon { get; private set; }
/// <summary>
/// 对应Drop表主键
/// </summary>
public int Reward { get; private set; }
public System.Collections.Generic.List<int> StepList { get; private set; }
/// <summary>
/// 用于室内关闭lookfar和looknear功能<br/>1 关闭
/// </summary>
public int TurnOffFreeLook { get; private set; }
/// <summary>
/// 用于室内关闭加速功能<br/>1 关闭
/// </summary>
public int TurnOffTimeScale { get; private set; }
public Area(ConstructionArea area)
{
this.ID = area.ID;
this.TimelineID = area.TimelineID;
this.StepCount = area.StepCount;
this.BuildingPrefeb = area.BuildingPrefeb;
this.TimelinePlaytime = area.TimelinePlaytime;
this.Name = area.Name;
this.Name_l10n_key = area.Name_l10n_key;
this.NameIndex = area.NameIndex;
this.NameIndex_l10n_key = area.NameIndex_l10n_key;
this.Icon = area.Icon;
this.Reward = area.Reward;
this.StepList = area.StepList;
this.TurnOffFreeLook = area.TurnOffFreeLook;
this.TurnOffTimeScale = area.TurnOffTimeScale;
}
public Area(ConstructionMMTArea area)
{
this.ID = area.ID;
this.TimelineID = area.TimelineID;
this.StepCount = area.StepCount;
this.BuildingPrefeb = area.BuildingPrefeb;
this.TimelinePlaytime = area.TimelinePlaytime;
this.Name = area.Name;
this.Name_l10n_key = area.Name_l10n_key;
this.NameIndex = area.NameIndex;
this.NameIndex_l10n_key = area.NameIndex_l10n_key;
this.Icon = area.Icon;
this.Reward = area.Reward;
this.StepList = area.StepList;
this.TurnOffFreeLook = area.TurnOffFreeLook;
this.TurnOffTimeScale = area.TurnOffTimeScale;
}
}
public class Step
{
public int ID { get; private set; }
/// <summary>
/// timeline中时间
/// </summary>
public float StartTime { get; private set; }
/// <summary>
/// 动画播放时间
/// </summary>
public float PlayTime { get; private set; }
/// <summary>
/// 建造所需钞票
/// </summary>
public int BuildRequired { get; private set; }
/// <summary>
/// 修复所需钞票
/// </summary>
public int FixRequired { get; private set; }
public Step(ConstructionStep step)
{
this.ID = step.ID;
this.StartTime = step.StartTime;
this.PlayTime = step.PlayTime;
this.BuildRequired = step.BuildRequired;
this.FixRequired = step.FixRequired;
}
public Step(ConstructionMMTStep step)
{
this.ID = step.ID;
this.StartTime = step.StartTime;
this.PlayTime = step.PlayTime;
this.BuildRequired = step.BuildRequired;
this.FixRequired = step.FixRequired;
}
}