test(upserver): 归档存档迁移与玩法闭环回归工具
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
using System; using System.Linq; using System.Threading.Tasks;
|
||||
public class ControlSlice {
|
||||
public bool isActiveAndEnabled=true; public int Continues,LegacyCalls;
|
||||
public FishingData fishingData=new(); public Tables _tables=new();
|
||||
public game.PlayerDataStorage.PlayerKVGameplayLoop.FishingAction kvFishingAction=new();
|
||||
public void Run()=>ConsumeBait(); public Task RunAwaited()=>ConsumeBaitOnKVAsync();
|
||||
void WaitForFishBiteED()=>Continues++; void DoConsumeBait()=>LegacyCalls++;
|
||||
bool KVFishingCurrent()
|
||||
{
|
||||
if(!game.PlayerDataStorage.PlayerKVGameplayLoop.Enabled) return true;
|
||||
return this!=null && isActiveAndEnabled && game.PlayerDataStorage.PlayerKVGameplayLoop.FishingCurrent(kvFishingAction);
|
||||
}
|
||||
async Task ConsumeBaitOnKVAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!KVFishingCurrent()) return;
|
||||
if(!game.PlayerDataStorage.PlayerKVGameplayLoop.ClaimFishingConsumption(kvFishingAction)) return;
|
||||
if(fishingData.fishItem==null || !fishingData.IsGeneralFish() || fishingData.IsFree || fishingData.IsRetry ||
|
||||
fishingData.IsDuel || fishingData.IsPackItemFish() || fishingData.curFishData==null)
|
||||
throw game.PlayerDataStorage.PlayerKVGameplayLoop.Stop("Only one ordinary paid fishing action is permitted.");
|
||||
if(Application.internetReachability==NetworkReachability.NotReachable)
|
||||
throw game.PlayerDataStorage.PlayerKVGameplayLoop.Stop("Fishing stopped before consumption: offline.");
|
||||
if(_tables.TbFishingEvent.DataList.Any(e=>e.ConditionList.Any(c=>c.Condition==ConditionType.ConsumeEnergy)))
|
||||
throw game.PlayerDataStorage.PlayerKVGameplayLoop.Stop("ConsumeEnergy activity activation requires separate review.");
|
||||
var player=GContext.container.Resolve<PlayerData>();
|
||||
int mag=player.magnification;
|
||||
if(mag<0 || mag>=_tables.TbEnergyDef.DataList.Count)
|
||||
throw game.PlayerDataStorage.PlayerKVGameplayLoop.Stop("Invalid fishing magnification.");
|
||||
int fish=fishingData.curFishData.ID, rod=fishingData.RodId, map=player.lastMapId;
|
||||
int cost=_tables.TbEnergyDef.DataList[mag].Mag;
|
||||
await player.ConsumeEnergyForKVLoopAsync(cost,kvFishingAction);
|
||||
if(!KVFishingCurrent()) return;
|
||||
if(fishingData.curFishData==null || fishingData.curFishData.ID!=fish || fishingData.RodId!=rod ||
|
||||
player.lastMapId!=map || (int)player.magnification!=mag || fishingData.IsFree || fishingData.IsRetry || fishingData.IsDuel)
|
||||
throw game.PlayerDataStorage.PlayerKVGameplayLoop.Stop("Fishing context changed during confirmation.");
|
||||
game.PlayerDataStorage.PlayerKVGameplayLoop.ContinueFishing(kvFishingAction,WaitForFishBiteED);
|
||||
}
|
||||
catch(Exception e) { game.PlayerDataStorage.PlayerKVGameplayLoop.Fault(e.Message); }
|
||||
}
|
||||
private void ConsumeBait()
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(game.PlayerDataStorage.PlayerKVGameplayLoop.Enabled) { _=ConsumeBaitOnKVAsync(); return; }
|
||||
#endif
|
||||
if (fishingData.IsFree)
|
||||
{
|
||||
WaitForFishBiteED();
|
||||
}
|
||||
else
|
||||
{
|
||||
DoConsumeBait();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Loop=game.PlayerDataStorage.PlayerKVGameplayLoop;
|
||||
int checks=0;
|
||||
void Check(bool ok,string text){checks++;if(!ok)throw new Exception(text);}
|
||||
ControlSlice Fresh(){Loop.Enabled=true;Loop.Ready=true;Loop.Faults=0;GContext.container.Player=new();Application.internetReachability=NetworkReachability.Reachable;return new();}
|
||||
var s=Fresh();var player=GContext.container.Player;var first=s.RunAwaited();
|
||||
Check(s.Continues==0 && player.Calls==1,"actual method waits for response");
|
||||
s.Run();await s.RunAwaited();Check(player.Calls==1 && s.Continues==0,"duplicate consume and callback cannot repeat");
|
||||
player.Response.SetResult();await first;Check(s.Continues==1,"actual success continuation exactly once");
|
||||
await s.RunAwaited();Check(s.Continues==1 && player.Calls==1,"post-success duplicate suppressed");
|
||||
foreach(var kind in new[]{"free","retry","duel","pack","special","missing-fish","missing-item","offline","activity","bad-mag"}){
|
||||
s=Fresh();player=GContext.container.Player;
|
||||
switch(kind){
|
||||
case "free":s.fishingData.IsFree=true;break;case "retry":s.fishingData.IsRetry=true;break;
|
||||
case "duel":s.fishingData.IsDuel=true;break;case "pack":s.fishingData.Pack=true;break;
|
||||
case "special":s.fishingData.General=false;break;case "missing-fish":s.fishingData.curFishData=null;break;
|
||||
case "missing-item":s.fishingData.fishItem=null;break;case "offline":Application.internetReachability=NetworkReachability.NotReachable;break;
|
||||
case "activity":s._tables.TbFishingEvent.DataList.Add(new());break;case "bad-mag":player.magnification=-1;break;}
|
||||
await s.RunAwaited();Check(player.Calls==0 && s.Continues==0 && Loop.Faults>0,"pre-consumption gate "+kind);
|
||||
}
|
||||
foreach(var kind in new[]{"fish","rod","map","mag","free","retry","duel","disabled","cancelled","fault"}){
|
||||
s=Fresh();player=GContext.container.Player;var task=s.RunAwaited();
|
||||
switch(kind){case "fish":s.fishingData.curFishData.ID++;break;case "rod":s.fishingData.RodId++;break;
|
||||
case "map":player.lastMapId++;break;case "mag":player.magnification++;break;case "free":s.fishingData.IsFree=true;break;
|
||||
case "retry":s.fishingData.IsRetry=true;break;case "duel":s.fishingData.IsDuel=true;break;
|
||||
case "disabled":s.isActiveAndEnabled=false;break;case "cancelled":s.kvFishingAction.Cancelled=true;break;
|
||||
case "fault":Loop.Ready=false;break;}
|
||||
player.Response.SetResult();await task;Check(player.Calls==1 && s.Continues==0,"no stale continuation "+kind);
|
||||
}
|
||||
s=Fresh();player=GContext.container.Player;var failed=s.RunAwaited();player.Response.SetException(new InvalidOperationException("Unknown"));await failed;
|
||||
Check(s.Continues==0 && player.Calls==1 && !Loop.Ready,"async exception observed, no retry");
|
||||
s=Fresh();Loop.Enabled=false;s.Run();Check(s.LegacyCalls==1 && GContext.container.Player.Calls==0,"default paid PF path unchanged");
|
||||
s.fishingData.IsFree=true;s.Run();Check(s.Continues==1,"default free PF path unchanged");
|
||||
Console.WriteLine($"PASS {checks} assertions; compiled actual source methods; visual continuation and storage boundaries are spies; no network");
|
||||
public enum NetworkReachability{NotReachable,Reachable}
|
||||
public static class Application{public static NetworkReachability internetReachability;}
|
||||
public enum ConditionType{ConsumeEnergy}
|
||||
public sealed class Fish{public int ID=1;}
|
||||
public sealed class FishingData{public object fishItem=new();public Fish curFishData=new();public int RodId=1;public bool IsFree,IsRetry,IsDuel,Pack;public bool General=true;public bool IsGeneralFish()=>General;public bool IsPackItemFish()=>Pack;}
|
||||
public sealed class EnergyDef{public int Mag=3;}
|
||||
public sealed class EnergyTable{public List<EnergyDef> DataList=new(){new()};}
|
||||
public sealed class ConditionRule{public ConditionType Condition=ConditionType.ConsumeEnergy;}
|
||||
public sealed class Activity{public List<ConditionRule> ConditionList=new(){new()};}
|
||||
public sealed class ActivityTable{public List<Activity> DataList=new();}
|
||||
public sealed class Tables{public EnergyTable TbEnergyDef=new();public ActivityTable TbFishingEvent=new();}
|
||||
public sealed class PlayerData{public int Calls,magnification,lastMapId;public TaskCompletionSource Response=new();public async Task ConsumeEnergyForKVLoopAsync(int n,Loop.FishingAction a){Calls++;await Response.Task;a.Confirmed=true;}}
|
||||
public sealed class Container{public PlayerData Player=new();public T Resolve<T>()=>(T)(object)Player;}
|
||||
public static class GContext{public static Container container=new();}
|
||||
namespace game.PlayerDataStorage{public static class PlayerKVGameplayLoop{
|
||||
public static bool Enabled,Ready;public static int Faults;
|
||||
public sealed class FishingAction{public bool Consuming,Cancelled,Confirmed,Continued;}
|
||||
public static bool FishingCurrent(FishingAction a)=>Ready && !a.Cancelled;
|
||||
public static bool ClaimFishingConsumption(FishingAction a){if(a.Consuming)return false;a.Consuming=true;return true;}
|
||||
public static InvalidOperationException Stop(string m){Fault(m);return new(m);}
|
||||
public static void Fault(string m){Ready=false;Faults++;}
|
||||
public static void ContinueFishing(FishingAction a,Action next){if(!FishingCurrent(a)||!a.Confirmed)throw Stop("invalid");if(a.Continued)return;a.Continued=true;next();}
|
||||
}}
|
||||
@@ -0,0 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><EnableDefaultCompileItems>false</EnableDefaultCompileItems><ImplicitUsings>enable</ImplicitUsings><DefineConstants>UNITY_EDITOR</DefineConstants><UseAppHost>false</UseAppHost><RestoreConfigFile>$(MSBuildThisFileDirectory)../MinimalLoginTests/NuGet.Config</RestoreConfigFile></PropertyGroup>
|
||||
<ItemGroup><Compile Include="ControlSlice.cs.txt"/><Compile Include="ControlTests.cs.txt"/><Reference Include="Newtonsoft.Json"><HintPath>../../../Client/Library/PackageCache/com.unity.nuget.newtonsoft-json@3.2.1/Runtime/Newtonsoft.Json.dll</HintPath></Reference></ItemGroup>
|
||||
</Project>
|
||||
22
CodexOutput/upserver/KVFishingConsumeTests/LegacyRed.cs.txt
Normal file
22
CodexOutput/upserver/KVFishingConsumeTests/LegacyRed.cs.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
using Newtonsoft.Json;
|
||||
var slice=new LegacySlice(); slice.Run();
|
||||
int failed=0;
|
||||
void Check(bool ok,string name) { Console.WriteLine((ok?"PASS ":"FAIL ")+name); if(!ok)failed++; }
|
||||
Check(slice.Continues==0,"must not continue while original save response is pending");
|
||||
var sent=JsonConvert.DeserializeObject<Dictionary<string,string>>(PlayFabMgr.Instance.Sent);
|
||||
Check(sent["newData"].Split(',')[3]==sent["energy"],"canonical and alias energy must agree");
|
||||
Console.WriteLine($"LEGACY SOURCE PROBE failed={failed}; no network; extracted actual methods from reviewed snapshot");
|
||||
return failed==0?0:1;
|
||||
public enum NetworkReachability { NotReachable,Reachable }
|
||||
public static class Application { public static NetworkReachability internetReachability=NetworkReachability.Reachable; }
|
||||
public sealed class FishingData { public bool IsRetry; public bool IsPackItemFish()=>false; }
|
||||
public sealed class EnergyDef { public int Mag=3; public int RetryCost=2; }
|
||||
public sealed class EnergyTable { public List<EnergyDef> DataList=new(){new()}; }
|
||||
public sealed class Tables { public EnergyTable TbEnergyDef=new(); }
|
||||
public sealed class PlayerData { public int Energy=403; public int magnification; public Dictionary<string,string> PlayerDataDic=new(){{"newData","60,213645,400,403,445,4,30003"}}; public void ReduceEnergy(int cost,bool remote) { Energy-=cost; } }
|
||||
public sealed class Container { readonly PlayerData p=new(); public T Resolve<T>()=>(T)(object)p; }
|
||||
public static class GContext { public static Container container=new(); public static void Publish(object e){} }
|
||||
public enum ConditionType { ConsumeEnergy }
|
||||
public sealed class ConditionTypeEvent { public ConditionTypeEvent(ConditionType t,int n){} }
|
||||
public static class Debug { public static void Log(object x){} }
|
||||
public sealed class PlayFabMgr { public static PlayFabMgr Instance=new(); public string Sent; public Task<bool> SyncDataImmidiate(string key,string data) { Sent=data; return new TaskCompletionSource<bool>().Task; } }
|
||||
@@ -0,0 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><EnableDefaultCompileItems>false</EnableDefaultCompileItems><ImplicitUsings>enable</ImplicitUsings><UseAppHost>false</UseAppHost><RestoreConfigFile>$(MSBuildThisFileDirectory)../MinimalLoginTests/NuGet.Config</RestoreConfigFile></PropertyGroup>
|
||||
<ItemGroup><Compile Include="LegacySlice.cs.txt"/><Compile Include="LegacyRed.cs.txt"/><Reference Include="Newtonsoft.Json"><HintPath>../../../Client/Library/PackageCache/com.unity.nuget.newtonsoft-json@3.2.1/Runtime/Newtonsoft.Json.dll</HintPath></Reference></ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,36 @@
|
||||
using System; using System.Collections.Generic; using System.Threading.Tasks;
|
||||
public class LegacySlice {
|
||||
public int Continues; public FishingData fishingData=new(); public Tables _tables=new(); public void Run()=>DoConsumeBait(); void NoInternet(){} void WaitForFishBiteED()=>Continues++;
|
||||
private void DoConsumeBait()
|
||||
{
|
||||
if (Application.internetReachability == NetworkReachability.NotReachable)
|
||||
{
|
||||
NoInternet();
|
||||
}
|
||||
else
|
||||
{
|
||||
ConsumeBaitOnPlayfab();
|
||||
WaitForFishBiteED();
|
||||
}
|
||||
}
|
||||
private async void ConsumeBaitOnPlayfab()
|
||||
{
|
||||
var playerData = GContext.container.Resolve<PlayerData>();
|
||||
EnergyDef energyDef = _tables.TbEnergyDef.DataList[playerData.magnification];
|
||||
var energy = energyDef.Mag;
|
||||
if (fishingData.IsRetry)
|
||||
{
|
||||
energy = energyDef.RetryCost;
|
||||
}
|
||||
var commonDatas = new Dictionary<string, string>(playerData.PlayerDataDic);
|
||||
commonDatas["energy"] = (playerData.Energy - energy).ToString();
|
||||
playerData.ReduceEnergy(energy, false);
|
||||
if (!fishingData.IsPackItemFish())
|
||||
{
|
||||
GContext.Publish(new ConditionTypeEvent(ConditionType.ConsumeEnergy, energy));
|
||||
}
|
||||
var success = await PlayFabMgr.Instance.SyncDataImmidiate("NewPlayerDataDic",
|
||||
Newtonsoft.Json.JsonConvert.SerializeObject(commonDatas));
|
||||
Debug.Log($"[FishBehaviourB::ConsumeBaitOnPlayfab] {success}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using game.PlayerDataStorage;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
var root=Path.GetFullPath("CodexOutput/upserver/KVFishingConsumeTests/permit-runs/"+Guid.NewGuid().ToString("N")); Directory.CreateDirectory(root);
|
||||
var backup=Path.Combine(root,"backup.json"); File.WriteAllText(backup,JsonConvert.SerializeObject(new{playerId="102",api="http://s101.local:18080/",data=new Dictionary<string,string>{{"NewPlayerDataDic","{}"}}}));
|
||||
int count=0,failed=0;
|
||||
void Case(int version,string operation,bool accept) {
|
||||
var o=JObject.FromObject(new{Version=version,Enabled=true,PlayerId="102",Api="http://s101.local:18080/",BackupPath=backup,BackupSha256=PlayerKVLoopPermit.Hash(File.ReadAllBytes(backup)),AllowedKeys=new[]{"NewPlayerDataDic"}});
|
||||
if(operation!=null)o["Operation"]=operation;
|
||||
var file=Path.Combine(root,"permit.json"); File.WriteAllText(file,o.ToString()); bool accepted;
|
||||
try { PlayerKVLoopPermit.Read(file); accepted=true; }catch(InvalidOperationException){accepted=false;}
|
||||
count++; if(accepted!=accept)failed++; Console.WriteLine($"{(accepted==accept?"PASS":"FAIL")} version={version} operation={operation??"absent"} accepted={accepted}");
|
||||
}
|
||||
Case(1,null,true); Case(1,"fishing-consume-once",false); Case(1,"equipment",false); Case(2,null,false); Case(2,"equipment",true); Case(2,"fishing-consume-once",true); Case(2,"Fishing",false); Case(3,"equipment",false);
|
||||
Console.WriteLine($"RESULT cases={count} failed={failed}"); return failed==0?0:1;
|
||||
@@ -0,0 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><EnableDefaultCompileItems>false</EnableDefaultCompileItems><ImplicitUsings>enable</ImplicitUsings><UseAppHost>false</UseAppHost><DefineConstants>UNITY_EDITOR;SERVICE_HOST</DefineConstants><RestoreConfigFile>$(MSBuildThisFileDirectory)../MinimalLoginTests/NuGet.Config</RestoreConfigFile></PropertyGroup>
|
||||
<ItemGroup><Compile Include="PermitTests.cs.txt"/><Compile Include="../../../Client/Assets/Scripts/Services/UserService.KVGameplayLoop.cs"/><Reference Include="Newtonsoft.Json"><HintPath>../../../Client/Library/PackageCache/com.unity.nuget.newtonsoft-json@3.2.1/Runtime/Newtonsoft.Json.dll</HintPath></Reference></ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1 @@
|
||||
return await KVGameplayLoopSetup.RunValidationAsync("fishing-consume");
|
||||
@@ -0,0 +1,8 @@
|
||||
using asap.core;
|
||||
using System.Linq;
|
||||
var p=GContext.container.Resolve<GameCore.PlayerData>();
|
||||
var t=GContext.container.Resolve<cfg.Tables>();
|
||||
var manager=GContext.container.Resolve<Activity.ActivityManager>();
|
||||
var scopes=GContext.container.Resolve<Activity.ActivityScopeManager>();
|
||||
var events=t.TbFishingEvent.DataList.GroupBy(e=>new {e.Type,e.SubType}).Select(g=>g.First()).Select(e=>new {e.Type,e.SubType,current=manager.GetSentry(e)?.CurrentEventConfig?.ID,scope=scopes.HasActiveScope((e.Type,e.SubType))}).ToArray();
|
||||
return new {at=System.DateTime.UtcNow,serverNow=ZZTimeHelper.UtcNow(),energy=(int)p.Energy,maxEnergy=(int)p.MaxEnergy,energyRM=p.GetEnergyRM(),mag=(int)p.magnification,cost=t.TbEnergyDef.DataList[(int)p.magnification].Mag,equipRod=(int)p.equipRodID,map=(int)p.lastMapId,lv=(int)p.lv,gold=(ulong)p.gold,pearl=(int)p.pearl,diamond=(int)p.diamond,recoverySeconds=p.GetEnergyRecoveryTimeMAndS().TotalSeconds,consumeActivations=string.Join(",",t.TbFishingEvent.DataList.Where(e=>e.ConditionList.Any(c=>c.Condition==cfg.ConditionType.ConsumeEnergy)).Select(e=>e.ID)),consumeTriggerPacks=string.Join(",",t.TbTriggerPackManager.DataList.Where(e=>e.Condition==cfg.ConditionType.ConsumeEnergy).Select(e=>e.ID)),events=string.Join(";",events.Select(e=>e.Type+":"+e.SubType+":"+e.current+":"+e.scope)),state=KVGameplayLoopSetup.State()};
|
||||
@@ -0,0 +1 @@
|
||||
return KVGameplayLoopSetup.PrepareFishingPermit("102","C:/projects/ft/CodexOutput/upserver/KVGameplayLoopTests/backups/20260924T074727Z/102.json",new string[]{"AquariumEventDataNewKey","BattlePassRecordTask","BombGameCount","BuffDataList","BuyData","CampDataDic","ChainPackDataNew","DigCount","DigGridData","epbdk","epdk","Event1v1BattleSlapData","EventBoostPackData","EventCubeMeltData","EventLuckGameRecord","EventLuckMagicPlayfabData","EventPartnerStatueData","FishHuntGameCount","FixedTimeShopDic","GuideSaveDataNew","IsOpenClubRed","LastPaymentTime","Leadboard_LastContinent","Map_5_101","Map_5_102","MiniBPKeyNew","MMTest","MonthlySignEvent","NewPlayerDataDic","NewRodList","OfferDiscoPartyManager","OfferStoryChain","OfferSweepPackData","Pack1A1Data","PiggyBankPackData","RodData","RodLevelData","SeasonStampData","ShopPackBuyCount","SoloDataKey","TargetBuffDataList","TransitionData","TriggerPackDataList","VIPAdPackData","WelcomeBack"});
|
||||
@@ -0,0 +1 @@
|
||||
return KVGameplayLoopSetup.PreparePermit("102","C:/projects/ft/CodexOutput/upserver/KVGameplayLoopTests/backups/20260924T075711Z/102.json",new string[]{"AquariumEventDataNewKey","BattlePassRecordTask","BombGameCount","BuffDataList","BuyData","CampDataDic","ChainPackDataNew","DigCount","DigGridData","epbdk","epdk","Event1v1BattleSlapData","EventBoostPackData","EventCubeMeltData","EventLuckGameRecord","EventLuckMagicPlayfabData","EventPartnerStatueData","FishHuntGameCount","FixedTimeShopDic","GuideSaveDataNew","IsOpenClubRed","LastPaymentTime","Leadboard_LastContinent","Map_5_101","Map_5_102","MiniBPKeyNew","MMTest","MonthlySignEvent","NewPlayerDataDic","NewRodList","OfferDiscoPartyManager","OfferStoryChain","OfferSweepPackData","Pack1A1Data","PiggyBankPackData","RodData","RodLevelData","SeasonStampData","ShopPackBuyCount","SoloDataKey","TargetBuffDataList","TransitionData","TriggerPackDataList","VIPAdPackData","WelcomeBack"});
|
||||
@@ -0,0 +1,3 @@
|
||||
using asap.core;
|
||||
_ = GContext.container.Resolve<IStageService>().SwitchStageAsync("QuitStage");
|
||||
return "normal QuitStage requested; inspect state separately";
|
||||
@@ -0,0 +1,6 @@
|
||||
using asap.core;
|
||||
using System.Linq;
|
||||
var p=GContext.container.Resolve<GameCore.PlayerData>();
|
||||
var a=GContext.container.Resolve<AchievementDataManager>();
|
||||
var b=GContext.container.Resolve<BattlePassData>();
|
||||
return new {energy=(int)p.Energy,energyRM=p.GetEnergyRM(),achievement=a.GetAchievementDatas(cfg.ConditionType.ConsumeEnergy),daily=string.Join(";",b.BattleTask.dic_dailyTasks.Select(x=>x.Key+":"+x.Value)),weekly=string.Join(";",b.BattleTask.dic_weeklyTasks.Select(x=>x.Key+":"+x.Value)),equipRod=(int)p.equipRodID,gold=(ulong)p.gold,diamond=(int)p.diamond,pearl=(int)p.pearl,state=KVGameplayLoopSetup.State()};
|
||||
Reference in New Issue
Block a user