test(upserver): 归档存档迁移与玩法闭环回归工具
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
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. fishItem.ID="+(fishingData.fishItem==null?"null":fishingData.fishItem.ID.ToString())+", fishItem.SubType="+(fishingData.fishItem==null?"null":fishingData.fishItem.SubType.ToString())+", curFishData.ID="+(fishingData.curFishData==null?"null":fishingData.curFishData.ID.ToString())+", IsFree="+fishingData.IsFree+", IsRetry="+fishingData.IsRetry+", IsDuel="+fishingData.IsDuel);
|
||||
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;
|
||||
game.PlayerDataStorage.PlayerKVGameplayLoop.PrepareFishingReward(kvFishingAction,fishingData);
|
||||
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,58 @@
|
||||
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 Item{public int ID=1;public int SubType=1;}
|
||||
public sealed class FishingData{public Item 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 void PrepareFishingReward(FishingAction a,FishingData d) {}
|
||||
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="ConsumeControlSlice.cs.txt"/><Compile Include="ConsumeControlTests.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,99 @@
|
||||
using System;using System.Threading.Tasks;using game.PlayerDataStorage;
|
||||
int checks=0;void Check(bool b,string m){checks++;if(!b)throw new Exception(m);}
|
||||
Controller Fresh(){PlayerKVGameplayLoop.Reset();UIManager.Instance=new();return new();}
|
||||
var c=Fresh();c.PiercingFish(3);c.PiercingFish(3);c.End();c.End();
|
||||
Check(c.Strikes==1&&c.Endings==1&&UIManager.Instance.Calls==1,"actual wrappers suppress repeated drawing/end");
|
||||
Check(PlayerKVGameplayLoop.Scope==null,"UI await never holds ending scope");
|
||||
c.Disable();UIManager.Instance.Response.SetResult(new object());await Task.Delay(20);
|
||||
Check(c.Destroyed==1&&PlayerKVGameplayLoop.Faults==1,"actual Disable during UI await disposes stale returned panel");
|
||||
c=Fresh();c.PiercingFish(3);c.Dead();Check(PlayerKVGameplayLoop.Cancelled,"actual Destroy revokes controller");
|
||||
c=Fresh();c.PiercingFish(3);c.fishingData.IsCaughtFish=false;c.End();Check(c.Endings==0&&UIManager.Instance.Calls==0,"natural failure cannot invoke reward core");
|
||||
foreach(int version in new[]{1,2}){c=Fresh();PlayerKVGameplayLoop.Version=version;c.PiercingFish(3);Check(c.Strikes==0&&PlayerKVGameplayLoop.Faults==1,"old permits do not draw");}
|
||||
c=Fresh();c.PiercingFish(3);PlayerKVGameplayLoop.Confirmed=true;c.Disable();Check(PlayerKVGameplayLoop.Faults==0,"confirmed controller normal disable no second fault");
|
||||
c=Fresh();PlayerKVGameplayLoop.Enabled=false;c.PiercingFish(3);c.End();Check(c.Strikes==1&&c.Endings==1,"ordinary PF wrapper takes original core");
|
||||
Console.WriteLine($"PASS actual controller source wrappers/lifecycle {checks} assertions; core/render/storage services are spies; no gameplay or network");
|
||||
public class Controller {
|
||||
public int Strikes,Endings,Destroyed;public FishingData fishingData=new();bool isFail;IDisposable disposables,_comboDisposable;object _hpcoroutine;
|
||||
public void End()=>FishEnd();public void Disable()=>OnDisable();public void Dead()=>OnDestroy();
|
||||
void PiercingFishCore(int zone){Strikes++;}void FishEndCore(){Endings++;}void Destroy(object p){Destroyed++;}
|
||||
void StopSoundCombo(){}void StopRodAudio(){}void StopNotReeling(){}void StopDrawingSplash(){}void AudioRelease(){}void SpSkillFxRelease(){}void StopAllCoroutines(){}
|
||||
public void PiercingFish(int waitingZone)
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(game.PlayerDataStorage.PlayerKVGameplayLoop.Enabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!game.PlayerDataStorage.PlayerKVGameplayLoop.BeginRewardDrawing(this)) return;
|
||||
using(game.PlayerDataStorage.PlayerKVGameplayLoop.BeginRewardSegment("strike")) PiercingFishCore(waitingZone);
|
||||
}
|
||||
catch(System.Exception e) { game.PlayerDataStorage.PlayerKVGameplayLoop.Fault(e.Message); }
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
PiercingFishCore(waitingZone);
|
||||
}
|
||||
void FishEnd()
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(game.PlayerDataStorage.PlayerKVGameplayLoop.Enabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!game.PlayerDataStorage.PlayerKVGameplayLoop.BeginRewardEnding(this,fishingData.IsCaughtFish && !isFail)) return;
|
||||
using(game.PlayerDataStorage.PlayerKVGameplayLoop.BeginRewardSegment("ending")) FishEndCore();
|
||||
_=ShowKVRewardPanelAsync();
|
||||
}
|
||||
catch(System.Exception e) { game.PlayerDataStorage.PlayerKVGameplayLoop.Fault(e.Message); }
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
FishEndCore();
|
||||
}
|
||||
async Task ShowKVRewardPanelAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var panel=await UIManager.Instance.ShowUI(UITypes.FishingRewardPanel);
|
||||
if(!game.PlayerDataStorage.PlayerKVGameplayLoop.RewardRunCurrent && panel!=null) Destroy(panel);
|
||||
}
|
||||
catch(System.Exception e) { game.PlayerDataStorage.PlayerKVGameplayLoop.Fault(e.Message); }
|
||||
}
|
||||
private void OnDisable()
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
game.PlayerDataStorage.PlayerKVGameplayLoop.CancelReward(this,"controller disabled");
|
||||
#endif
|
||||
|
||||
StopSoundCombo();
|
||||
StopRodAudio();
|
||||
StopNotReeling();
|
||||
StopDrawingSplash();
|
||||
GContext.container.Resolve<SoundMgr>().StopActiveFishingLoopSounds(0.05f);
|
||||
AudioRelease();
|
||||
SpSkillFxRelease();
|
||||
}
|
||||
private void OnDestroy()
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
game.PlayerDataStorage.PlayerKVGameplayLoop.CancelReward(this,"controller destroyed");
|
||||
#endif
|
||||
|
||||
disposables?.Dispose();
|
||||
_comboDisposable?.Dispose();
|
||||
_hpcoroutine = null;
|
||||
StopAllCoroutines();
|
||||
}
|
||||
}
|
||||
public class FishingData{public bool IsCaughtFish=true;}public enum UITypes{FishingRewardPanel}
|
||||
public class UIManager{public static UIManager Instance=new();public int Calls;public TaskCompletionSource<object> Response=new();public Task<object> ShowUI(UITypes type){Calls++;return Response.Task;}}
|
||||
public static class GContext{public static Container container=new();}public class Container{public T Resolve<T>() where T:new()=>new();}public class SoundMgr{public void StopActiveFishingLoopSounds(float x){}}
|
||||
namespace game.PlayerDataStorage {public static class PlayerKVGameplayLoop{
|
||||
public static bool Enabled,Drawing,Ending,Cancelled,Confirmed;public static int Version,Faults;public static string Scope;
|
||||
public static void Reset(){Enabled=true;Drawing=Ending=Cancelled=Confirmed=false;Version=3;Faults=0;Scope=null;}
|
||||
public static bool RewardRunCurrent=>!Cancelled;
|
||||
public static bool BeginRewardDrawing(object o){if(Version!=3||Cancelled)throw new Exception("no draw");if(Drawing)return false;Drawing=true;return true;}
|
||||
public static bool BeginRewardEnding(object o,bool success){if(Ending)return false;if(!success)throw new Exception("failed catch");Ending=true;return true;}
|
||||
public static IDisposable BeginRewardSegment(string n){Scope=n;return new Batch();}class Batch:IDisposable{public void Dispose(){Scope=null;}}
|
||||
public static void Fault(string s){Faults++;}public static void CancelReward(object o,string r){if(Cancelled)return;Cancelled=true;if(!Confirmed)Fault(r);}
|
||||
}}
|
||||
@@ -0,0 +1 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><EnableDefaultCompileItems>false</EnableDefaultCompileItems><DefineConstants>UNITY_EDITOR</DefineConstants><RestoreConfigFile>$(MSBuildThisFileDirectory)../MinimalLoginTests/NuGet.Config</RestoreConfigFile></PropertyGroup><ItemGroup><Compile Include="ControllerTests.cs.txt"/></ItemGroup></Project>
|
||||
123
CodexOutput/upserver/KVFishingRewardTests/FormulaTests.cs.txt
Normal file
123
CodexOutput/upserver/KVFishingRewardTests/FormulaTests.cs.txt
Normal file
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
foreach(var c in new[]{(10f,0.25f,10,0.5f,0.5f,0.2f,0.1f,122),(0f,0.25f,10,0.5f,0.5f,0.2f,0.1f,0),(10f,0f,1,1f,0f,0f,0f,10),(1f,0f,1,0.5f,0f,0f,0f,0)}){
|
||||
var old=new OldFormula();var next=new NewFormula();
|
||||
foreach(var x in new FormulaBase[]{old,next}){x._tables.TbDrop[0].DropList.DropCountList[1]=c.Item1;x.pointBonus=c.Item2;x.Magnification=c.Item3;x.fishingData.random=c.Item4;x.fishingData.WeightIncEvent=c.Item5;x.playerData.benefitsCashMag=c.Item6;GContext.container.Buff.Gold=c.Item7;}
|
||||
int a=old.Run(),b=next.Run();if(a!=c.Item8||b!=a)throw new Exception($"Formula {a}/{b} expected {c.Item8}");Console.WriteLine($"PASS original/current cash={b}");
|
||||
}
|
||||
public class FormulaBase {public Tables _tables=new();public Fishing fishingData=new();public Player playerData=new();public float pointBonus,extraPointEvent;public int Magnification;protected float _fishScore;protected Text text_gold_num=new(),text_num_buff=new();}
|
||||
public class Tables {public Drop[] TbDrop={new()};}public class Drop{public DropPackageList DropList=new();}public class DropPackageList {public float[] DropCountList={20,10};}
|
||||
public class Fishing{public Fish curFishData=new(),fishItem=new();public float random,fishWeight,WeightIncEvent;public int MapId;public bool IsNewItemFish()=>false;}
|
||||
public class Fish{public int DropID,ID,Quality=2;}public class Player{public float benefitsCashMag,benefitsEventFishCashMag;}
|
||||
public class CollectingData{public float ExtraCashDrop()=>0;}public class BuffDataCenter{public float Gold;public float GetExtraGoldBuffValue(int m,int q)=>Gold;}
|
||||
public class FishingChallengeManager{public void AddScoreByQuality(int a,float b,int c){}}
|
||||
public class Container{public BuffDataCenter Buff=new();public T Resolve<T>() where T:new()=>typeof(T)==typeof(BuffDataCenter)?(T)(object)Buff:new T();}
|
||||
public static class GContext{public static Container container=new();}public class Text{public Color color;}public struct Color{public static Color black,white;}
|
||||
public static class Mathf{public static int RoundToInt(float x)=>(int)MathF.Round(x,MidpointRounding.ToEven);}
|
||||
public class OldFormula : FormulaBase { public int Run(){DropPackageList dropPackageList = _tables.TbDrop[fishingData.curFishData.DropID].DropList;
|
||||
float proficiency = dropPackageList.DropCountList[0] * fishingData.random;
|
||||
var proficiencyExtra = SetProficiency(proficiency, extraPointEvent, fishingData.curFishData.ID);//额外的Buff加成
|
||||
var endWeight = fishingData.fishWeight;
|
||||
float extraCashDrop = GContext.container.Resolve<CollectingData>().ExtraCashDrop();
|
||||
//Data
|
||||
//金币各种加成
|
||||
float gold = (dropPackageList.DropCountList[1] + extraCashDrop)
|
||||
* (1 + pointBonus)
|
||||
* Magnification
|
||||
* fishingData.random;
|
||||
//杆加成
|
||||
//解锁地图Buff加成
|
||||
float glodBuff = GContext.container.Resolve<BuffDataCenter>().GetExtraGoldBuffValue(fishingData.MapId, fishingData.fishItem.Quality);
|
||||
|
||||
if (glodBuff > 0.001f)
|
||||
{
|
||||
text_gold_num.color = Color.black;
|
||||
text_num_buff.color = Color.white;
|
||||
}
|
||||
int Quality = fishingData.curFishData.Quality;
|
||||
if (Quality > 6)
|
||||
{
|
||||
Quality = 6;
|
||||
}
|
||||
|
||||
//权益全局加成
|
||||
var ExtraPoint = playerData.benefitsCashMag + glodBuff;
|
||||
int cash = Mathf.RoundToInt(gold * proficiencyExtra * (1 + ExtraPoint));
|
||||
|
||||
return cash;}float SetProficiency(float proficiency, float proficiencyExtra, int fishId)
|
||||
{
|
||||
int Quality = fishingData.curFishData.Quality;
|
||||
if (Quality > 6)
|
||||
{
|
||||
Quality = 6;
|
||||
}
|
||||
if (!fishingData.IsNewItemFish())
|
||||
{
|
||||
proficiencyExtra = (1 + fishingData.WeightIncEvent) * (1 + proficiencyExtra);
|
||||
}
|
||||
else
|
||||
{
|
||||
proficiencyExtra = (1 + proficiencyExtra) * (1 + playerData.benefitsEventFishCashMag);
|
||||
}
|
||||
|
||||
proficiency *= proficiencyExtra;
|
||||
proficiency = Mathf.RoundToInt(proficiency);
|
||||
|
||||
GContext.container.Resolve<FishingChallengeManager>().AddScoreByQuality(Quality, proficiency, fishId);
|
||||
|
||||
_fishScore = proficiency;
|
||||
return proficiencyExtra;
|
||||
}}
|
||||
public class NewFormula : FormulaBase { public int Run(){DropPackageList dropPackageList = _tables.TbDrop[fishingData.curFishData.DropID].DropList;
|
||||
float proficiency = dropPackageList.DropCountList[0] * fishingData.random;
|
||||
var proficiencyExtra = SetProficiency(proficiency, extraPointEvent, fishingData.curFishData.ID);//额外的Buff加成
|
||||
var endWeight = fishingData.fishWeight;
|
||||
float extraCashDrop = GContext.container.Resolve<CollectingData>().ExtraCashDrop();
|
||||
//Data
|
||||
//金币各种加成
|
||||
float gold = (dropPackageList.DropCountList[1] + extraCashDrop)
|
||||
* (1 + pointBonus)
|
||||
* Magnification
|
||||
* fishingData.random;
|
||||
//杆加成
|
||||
//解锁地图Buff加成
|
||||
float glodBuff = GContext.container.Resolve<BuffDataCenter>().GetExtraGoldBuffValue(fishingData.MapId, fishingData.fishItem.Quality);
|
||||
|
||||
if (glodBuff > 0.001f)
|
||||
{
|
||||
text_gold_num.color = Color.black;
|
||||
text_num_buff.color = Color.white;
|
||||
}
|
||||
int Quality = fishingData.curFishData.Quality;
|
||||
if (Quality > 6)
|
||||
{
|
||||
Quality = 6;
|
||||
}
|
||||
|
||||
//权益全局加成
|
||||
var ExtraPoint = playerData.benefitsCashMag + glodBuff;
|
||||
int cash = Mathf.RoundToInt(gold * proficiencyExtra * (1 + ExtraPoint));
|
||||
|
||||
return cash;}float SetProficiency(float proficiency, float proficiencyExtra, int fishId)
|
||||
{
|
||||
int Quality = fishingData.curFishData.Quality;
|
||||
if (Quality > 6)
|
||||
{
|
||||
Quality = 6;
|
||||
}
|
||||
if (!fishingData.IsNewItemFish())
|
||||
{
|
||||
proficiencyExtra = (1 + fishingData.WeightIncEvent) * (1 + proficiencyExtra);
|
||||
}
|
||||
else
|
||||
{
|
||||
proficiencyExtra = (1 + proficiencyExtra) * (1 + playerData.benefitsEventFishCashMag);
|
||||
}
|
||||
|
||||
proficiency *= proficiencyExtra;
|
||||
proficiency = Mathf.RoundToInt(proficiency);
|
||||
|
||||
GContext.container.Resolve<FishingChallengeManager>().AddScoreByQuality(Quality, proficiency, fishId);
|
||||
|
||||
_fishScore = proficiency;
|
||||
return proficiencyExtra;
|
||||
}}
|
||||
@@ -0,0 +1 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><EnableDefaultCompileItems>false</EnableDefaultCompileItems><RestoreConfigFile>$(MSBuildThisFileDirectory)../MinimalLoginTests/NuGet.Config</RestoreConfigFile></PropertyGroup><ItemGroup><Compile Include="FormulaTests.cs.txt"/></ItemGroup></Project>
|
||||
17
CodexOutput/upserver/KVFishingRewardTests/LegacyRed.cs.txt
Normal file
17
CodexOutput/upserver/KVFishingRewardTests/LegacyRed.cs.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
using System; using System.Collections.Generic;
|
||||
var fish=new ActualFish(); fish.fishData[7]=new FishWeightData {TotalNum=20,MaxG=10};
|
||||
fish.SetGetFishCount(7); fish.SetWeight(7,5);
|
||||
bool ok=fish.fishData[7].TotalNum==30 && PlayFabMgr.Instance.Writes==1;
|
||||
Console.WriteLine($"{(ok?"PASS":"FAIL")} non-record catch: total={fish.fishData[7].TotalNum}, mapWrites={PlayFabMgr.Instance.Writes}; expected total=30 and one persisted map.");
|
||||
return ok?0:1;
|
||||
public class FishWeightData { public int TotalNum; public float MaxG; public bool NewFish; public string Serialize()=>TotalNum+"|"+MaxG; }
|
||||
public enum ConditionType {GetFishWeight,GetFishID}
|
||||
public record ConditionTypeEvent(ConditionType type,int count);
|
||||
public class PlayerData {public int GetMagnification()=>10;}
|
||||
public static class GContext {public static Container container=new(); public static void Publish(object x) {} }
|
||||
public class Container {public T Resolve<T>() where T:new()=>new();}
|
||||
public class Tables {public FishTable TbFishData=new();}
|
||||
public class FishTable {public FishConfig GetOrDefault(int id)=>new();}
|
||||
public class FishConfig {public int AreaID=102;}
|
||||
public static class JsonMapper {public static string ToJson(object x)=>System.Text.Json.JsonSerializer.Serialize(x);}
|
||||
public class PlayFabMgr {public static PlayFabMgr Instance=new(); public int Writes; public void UpdateUserDataValue(string key,string value) {Writes++;} }
|
||||
@@ -0,0 +1 @@
|
||||
<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"/></ItemGroup></Project>
|
||||
49
CodexOutput/upserver/KVFishingRewardTests/LegacySlice.cs.txt
Normal file
49
CodexOutput/upserver/KVFishingRewardTests/LegacySlice.cs.txt
Normal file
@@ -0,0 +1,49 @@
|
||||
using System; using System.Collections.Generic; using System.Linq;
|
||||
public class ActualFish { public Dictionary<int,FishWeightData> fishData=new(); public Dictionary<int,Dictionary<int,FishWeightData>> mapFishData=new(); public Tables _tables=new();
|
||||
public void SetWeight(int fishId, float weight)
|
||||
{
|
||||
//获取鱼重量记录为g
|
||||
GContext.Publish(new ConditionTypeEvent(ConditionType.GetFishWeight, (int)(weight * 1000)));
|
||||
CheckFishData(fishId);
|
||||
if (fishData[fishId].MaxG < weight)
|
||||
{
|
||||
fishData[fishId].MaxG = weight;
|
||||
SetFishData(fishId, fishData[fishId]);
|
||||
}
|
||||
GContext.Publish(new ConditionTypeEvent(ConditionType.GetFishID, fishId));
|
||||
}
|
||||
public void SetGetFishCount(int fishId)
|
||||
{
|
||||
int count = GContext.container.Resolve<PlayerData>().GetMagnification();
|
||||
CheckFishData(fishId);
|
||||
fishData[fishId].TotalNum += count;
|
||||
}
|
||||
void CheckFishData(int fishId)
|
||||
{
|
||||
if (!fishData.ContainsKey(fishId))
|
||||
{
|
||||
fishData.Add(fishId, new FishWeightData() { NewFish = true });
|
||||
}
|
||||
}
|
||||
void SetFishData(int id, FishWeightData data)
|
||||
{
|
||||
int mapID = _tables.TbFishData.GetOrDefault(id).AreaID;
|
||||
if (mapFishData.ContainsKey(mapID))
|
||||
{
|
||||
mapFishData[mapID][id] = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
mapFishData[mapID] = new Dictionary<int, FishWeightData>
|
||||
{
|
||||
[id] = data
|
||||
};
|
||||
}
|
||||
MapNewUpdateUserDataValue(mapID, mapFishData[mapID]);
|
||||
}
|
||||
void MapNewUpdateUserDataValue(int mapID, Dictionary<int, FishWeightData> keyValuePairs)
|
||||
{
|
||||
Dictionary<int, string> mapData = keyValuePairs.ToDictionary(_ => _.Key, _ => _.Value.Serialize());
|
||||
PlayFabMgr.Instance.UpdateUserDataValue($"Map_5_{mapID}", JsonMapper.ToJson(mapData));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using System;using System.Threading.Tasks;using System.Runtime.CompilerServices;
|
||||
var panel=new OriginalPanel();panel.Run();
|
||||
bool ok=panel.Animations==0&&!panel.go_info.active;
|
||||
Console.WriteLine($"{(ok?"PASS":"FAIL")} original full Success method displays before pending storage response: writes={Storage.Writes}, confirmed={Storage.Confirmed}, animations={panel.Animations}, info={panel.go_info.active}");
|
||||
return ok?0:1;
|
||||
public class OriginalPanel{
|
||||
public int Animations;public Node go_info=new(),go_weight=new(),fx_ui_blingbling_01=new(),bg_target=new();
|
||||
int addCollecting,curCollectingID,add;float extraPointEvent;bool isSocial,isEnable=true;int Magnification=10;float waitTime1,duration1,waitTime2,duration2;
|
||||
Fishing fishingData=new();FishingEventData fishingEventData=new();Tables _tables=new();PlayerData playerData=new();string PanelName="reward";
|
||||
Text root_target_text_info=new(),root_1v1battle_text_info=new(),text_gold_num=new(),text_num_buff=new(),txt_magnification=new();
|
||||
Bonus bonusReward=new();UI uiService=new();Social fishingRewardSocial=new();Animator anim_panel;
|
||||
public OriginalPanel(){anim_panel=new Animator(()=>Animations++);}public void Run()=>Success(0);
|
||||
void MaterialChange(){}void PlayAudioRewardFish(){}void SetTextTitleAsync(float x,bool s){}void SetFishWeight(float w){}void VibrationData(){}
|
||||
float SetProficiency(float a,float b,int c)=>1;object AddCash()=>new();object StartCoroutine(object o){Animations++;return o;}
|
||||
async void Success(float pointBonus)
|
||||
{
|
||||
go_info.gameObject.SetActive(false);
|
||||
GContext.container.Resolve<PlayerData>().SaveFishingFrequency();
|
||||
|
||||
if (addCollecting > 0)
|
||||
{
|
||||
extraPointEvent = fishingEventData.OnFishReward(fishingData.MapId, fishingData.NewIndex, Magnification);
|
||||
addCollecting *= Magnification;
|
||||
GContext.container.Resolve<PlayerItemData>().AddItemCount(curCollectingID, addCollecting, null);
|
||||
MaterialChange();
|
||||
}
|
||||
PlayAudioRewardFish();
|
||||
GContext.container.Resolve<BuffDataCenter>().EndCollectionTargetWeight(fishingData.fishItem.ID);
|
||||
fishingEventData.AddProgress(fishingData.fishItem.Quality, Magnification);
|
||||
|
||||
GContext.container.Resolve<GuideDataCenter>().AddIndex(true);
|
||||
|
||||
if (fishingData.IsKeepsakeFish())
|
||||
{
|
||||
root_target_text_info.text = LocalizationMgr.GetText("UI_FishingTargetPopupPanel_Advanced_9");
|
||||
}
|
||||
else if (fishingData.IsPiggyBankFish())
|
||||
{
|
||||
fishingEventData.curActivateItemId = fishingData.fishItem.ID;
|
||||
root_target_text_info.text = LocalizationMgr.GetText("UI_GiftPiggyBankPopupPanel_10");
|
||||
PiggyBankPackData piggyBankPackData = GContext.container.Resolve<PiggyBankPackData>();
|
||||
piggyBankPackData.TriggerPack();
|
||||
}
|
||||
else if (fishingData.IsBarginPackFish())
|
||||
{
|
||||
fishingEventData.curActivateItemId = fishingData.fishItem.ID;
|
||||
root_target_text_info.text = LocalizationMgr.GetText("UI_GiftBargainPopupPanel_7");
|
||||
BargainPackData piggyBankPackData = GContext.container.Resolve<BargainPackData>();
|
||||
piggyBankPackData.TriggerPack();
|
||||
|
||||
}
|
||||
else if (fishingData.IsGeneralFish())
|
||||
{
|
||||
isSocial = false;
|
||||
DropPackageList dropPackageList = _tables.TbDrop[fishingData.curFishData.DropID].DropList;
|
||||
float proficiency = dropPackageList.DropCountList[0] * fishingData.random;
|
||||
var proficiencyExtra = SetProficiency(proficiency, extraPointEvent, fishingData.curFishData.ID);//额外的Buff加成
|
||||
var endWeight = fishingData.fishWeight;
|
||||
float extraCashDrop = GContext.container.Resolve<CollectingData>().ExtraCashDrop();
|
||||
//Data
|
||||
//金币各种加成
|
||||
float gold = (dropPackageList.DropCountList[1] + extraCashDrop)
|
||||
* (1 + pointBonus)
|
||||
* Magnification
|
||||
* fishingData.random;
|
||||
//杆加成
|
||||
//解锁地图Buff加成
|
||||
float glodBuff = GContext.container.Resolve<BuffDataCenter>().GetExtraGoldBuffValue(fishingData.MapId, fishingData.fishItem.Quality);
|
||||
|
||||
if (glodBuff > 0.001f)
|
||||
{
|
||||
text_gold_num.color = Color.black;
|
||||
text_num_buff.color = Color.white;
|
||||
}
|
||||
int Quality = fishingData.curFishData.Quality;
|
||||
if (Quality > 6)
|
||||
{
|
||||
Quality = 6;
|
||||
}
|
||||
|
||||
//权益全局加成
|
||||
var ExtraPoint = playerData.benefitsCashMag + glodBuff;
|
||||
int cash = Mathf.RoundToInt(gold * proficiencyExtra * (1 + ExtraPoint));
|
||||
|
||||
add = cash;
|
||||
GContext.Publish(new ConditionTypeEvent(ConditionType.CashFromFishing, add));
|
||||
GContext.Publish(new ConditionTypeEvent(ConditionType.MaxCashFromFishing, add / Magnification));
|
||||
|
||||
//AddEventData
|
||||
if (add > 0)
|
||||
{
|
||||
playerData.AddGold((ulong)add);
|
||||
}
|
||||
GContext.Publish(new TargetAddData(dropPackageList.DropIDList[1], 0, add));
|
||||
var isSpecialFish = fishingData.curFishData.GeneralType == FishGeneralType.SpMapFish || fishingData.curFishData.GeneralType == FishGeneralType.EventFish;
|
||||
SetTextTitleAsync(proficiencyExtra, isSpecialFish);
|
||||
SetFishWeight(fishingData.fishWeight);
|
||||
MMTService mmService = GContext.container.Resolve<MMTService>();
|
||||
if (addCollecting > 0)
|
||||
{
|
||||
var collectingTargetInit = fishingEventData.collectingTargetInit;
|
||||
string BgRewardAdd = collectingTargetInit.BgRewardAdd;
|
||||
if (!string.IsNullOrEmpty(BgRewardAdd))
|
||||
{
|
||||
bg_target.gameObject.SetActive(true);
|
||||
uiService.SetImageSprite(bg_target, BgRewardAdd, PanelName);
|
||||
}
|
||||
}
|
||||
bonusReward.Show(Magnification);
|
||||
_ = StartCoroutine(AddCash());
|
||||
//Show Fish Info UI
|
||||
go_info.gameObject.SetActive(true);
|
||||
go_weight.SetActive(false);
|
||||
|
||||
//Show Magnification Animation
|
||||
if (Magnification > 1)
|
||||
{
|
||||
txt_magnification.text = "x" + Magnification;
|
||||
|
||||
anim_panel.Play("info_show_2");
|
||||
|
||||
await new WaitForSeconds(waitTime1 + duration1 + waitTime2);
|
||||
if (!isEnable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bonusReward.DOTweenAdd(Magnification, duration2);
|
||||
|
||||
var progress = 0f;
|
||||
var newCash = add / Magnification;
|
||||
var addCash = add - newCash;
|
||||
DOTween.To(() => progress, x => progress = x, 1f, duration2).OnUpdate(() =>
|
||||
{
|
||||
text_gold_num.text = ConvertTools.GetNumberString2((int)(newCash + addCash * progress));
|
||||
text_num_buff.text = text_gold_num.text;
|
||||
}).SetId("AddCash");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
anim_panel.Play("info_show_1");
|
||||
}
|
||||
if (glodBuff > 0.001f)
|
||||
{
|
||||
fx_ui_blingbling_01.gameObject.SetActive(true);
|
||||
}
|
||||
VibrationData();
|
||||
fishingRewardSocial.ShowSocial(isSocial);
|
||||
}
|
||||
else if (fishingData.fishItem.IsSpinachItem())
|
||||
{
|
||||
//todo 1v1battle
|
||||
root_1v1battle_text_info.text=LocalizationMgr.GetText("UI_GiftBargainPopupPanel_8");
|
||||
}
|
||||
}}
|
||||
public static class Storage{public static int Writes;public static bool Confirmed=false;}
|
||||
public class Node{public bool active;public Node gameObject=>this;public void SetActive(bool b){active=b;}}
|
||||
public class Text{public string text;public Color color;}public struct Color{public static Color black,white;}
|
||||
public class PlayerData{public float benefitsCashMag;public void SaveFishingFrequency(){Storage.Writes++;}public void AddGold(ulong v){Storage.Writes++;}}
|
||||
public class PlayerItemData{public void AddItemCount(int a,int b,object c){}}public class PiggyBankPackData{public void TriggerPack(){}}public class BargainPackData{public void TriggerPack(){}}
|
||||
public class BuffDataCenter{public void EndCollectionTargetWeight(int x){}public float GetExtraGoldBuffValue(int a,int b)=>0;}public class CollectingData{public float ExtraCashDrop()=>0;}
|
||||
public class GuideDataCenter{public void AddIndex(bool b){}}public class MMTService{}
|
||||
public class Tables{public Drop[] TbDrop={new()};}public class Drop{public DropPackageList DropList=new();}public class DropPackageList{public float[] DropCountList={1,10};public int[] DropIDList={0,1001};}
|
||||
public class Fishing{public Fish curFishData=new(),fishItem=new();public int MapId,NewIndex;public float random=1,fishWeight=1;public bool IsKeepsakeFish()=>false;public bool IsPiggyBankFish()=>false;public bool IsBarginPackFish()=>false;public bool IsGeneralFish()=>true;}
|
||||
public class Fish{public int ID,DropID,Quality=2;public FishGeneralType GeneralType;public bool IsSpinachItem()=>false;}
|
||||
public enum FishGeneralType{Normal,SpMapFish,EventFish}public enum ConditionType{CashFromFishing,MaxCashFromFishing}
|
||||
public record ConditionTypeEvent(ConditionType c,int count);public record TargetAddData(int a,int b,int c);
|
||||
public class FishingEventData{public int curActivateItemId;public Target collectingTargetInit=new();public float OnFishReward(int a,int b,int c)=>0;public void AddProgress(int a,int b){}}public class Target{public string BgRewardAdd;}
|
||||
public static class GContext{public static Container container=new();public static void Publish(object e){}}public class Container{public T Resolve<T>() where T:new()=>new();}
|
||||
public class Bonus{public void Show(int n){}public void DOTweenAdd(int n,float t){}}public class UI{public void SetImageSprite(object a,string b,string c){}}public class Social{public void ShowSocial(bool b){}}
|
||||
public class Animator{Action play;public Animator(Action a){play=a;}public void Play(string s){play();}}
|
||||
public static class LocalizationMgr{public static string GetText(string s)=>s;}public static class Mathf{public static int RoundToInt(float v)=>(int)MathF.Round(v);}
|
||||
public static class ConvertTools{public static string GetNumberString2(int n)=>n.ToString();}public static class DOTween{public static Tween To(Func<float>a,Action<float>b,float c,float d)=>new();}public class Tween{public Tween OnUpdate(Action a)=>this;public Tween SetId(string s)=>this;}
|
||||
public class WaitForSeconds{static TaskCompletionSource<bool> wait=new();public WaitForSeconds(float t){}public TaskAwaiter<bool> GetAwaiter()=>wait.Task.GetAwaiter();}
|
||||
@@ -0,0 +1 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><EnableDefaultCompileItems>false</EnableDefaultCompileItems><RestoreConfigFile>$(MSBuildThisFileDirectory)../MinimalLoginTests/NuGet.Config</RestoreConfigFile></PropertyGroup><ItemGroup><Compile Include="OldPresentationTests.cs.txt"/></ItemGroup></Project>
|
||||
56
CodexOutput/upserver/KVFishingRewardTests/PanelSlice.cs.txt
Normal file
56
CodexOutput/upserver/KVFishingRewardTests/PanelSlice.cs.txt
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;using System.Threading.Tasks;
|
||||
public class ActualPanel {
|
||||
game.PlayerDataStorage.PlayerKVGameplayLoop.FishingRewardAction kvReward; Func<Task> kvPresentation;
|
||||
public bool isActiveAndEnabled=true; public Canvas canvasGroup=new(); public Fish playerFishData=new();public Player playerData=new(); int fishID=7;public int Applied;
|
||||
bool KVRewardViewCurrent()=>this!=null && isActiveAndEnabled && game.PlayerDataStorage.PlayerKVGameplayLoop.RewardPanelCurrent(kvReward,this);
|
||||
public Button playButton=new();public bool NonKvOnly;public int HomeClicks;
|
||||
public void StartTail()
|
||||
{
|
||||
playButton.onClick.AddListener(OnClickInHome);
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(!game.PlayerDataStorage.PlayerKVGameplayLoop.Enabled)
|
||||
#endif
|
||||
{
|
||||
NonKvOnly=true;
|
||||
}
|
||||
OnShow();
|
||||
}
|
||||
void OnClickInHome()=>HomeClicks++;
|
||||
void OnShowCore(){Applied++;}public void Run()=>OnShow();
|
||||
void OnShow()
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(game.PlayerDataStorage.PlayerKVGameplayLoop.Enabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bound=game.PlayerDataStorage.PlayerKVGameplayLoop.BindRewardPanel(this);
|
||||
if(bound==null) return;
|
||||
kvReward=bound;
|
||||
game.PlayerDataStorage.PlayerKVEditResult primary;
|
||||
using(game.PlayerDataStorage.PlayerKVGameplayLoop.BeginRewardSegment("reward"))
|
||||
{
|
||||
OnShowCore();
|
||||
playerFishData.SaveCaughtFishForKVLoop(fishID);
|
||||
primary=playerData.SaveFishingRewardForKVLoop();
|
||||
}
|
||||
_=ConfirmKVRewardAsync(primary);
|
||||
}
|
||||
catch(System.Exception e) { game.PlayerDataStorage.PlayerKVGameplayLoop.Fault(e.Message); }
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
OnShowCore();
|
||||
}
|
||||
async System.Threading.Tasks.Task ConfirmKVRewardAsync(game.PlayerDataStorage.PlayerKVEditResult primary)
|
||||
{
|
||||
try
|
||||
{
|
||||
await game.PlayerDataStorage.PlayerKVGameplayLoop.ConfirmRewardAsync(kvReward,primary);
|
||||
if(!KVRewardViewCurrent()) return;
|
||||
game.PlayerDataStorage.PlayerKVGameplayLoop.PresentReward(kvReward,()=>{canvasGroup.alpha=1;canvasGroup.blocksRaycasts=true;canvasGroup.interactable=true;});
|
||||
if(kvPresentation!=null) await kvPresentation();
|
||||
}
|
||||
catch(System.Exception e) { game.PlayerDataStorage.PlayerKVGameplayLoop.Fault(e.Message); }
|
||||
}
|
||||
}
|
||||
31
CodexOutput/upserver/KVFishingRewardTests/PanelTests.cs.txt
Normal file
31
CodexOutput/upserver/KVFishingRewardTests/PanelTests.cs.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;using System.Threading.Tasks;using game.PlayerDataStorage;
|
||||
var panel=new ActualPanel();panel.Run();panel.Run();
|
||||
if(panel.Applied!=1)throw new Exception("duplicate applies rewards");
|
||||
if(panel.canvasGroup.alpha!=0)throw new Exception("pending reward already visible");
|
||||
bool pendingBlocked=panel.canvasGroup.blocksRaycasts==false && panel.canvasGroup.interactable==false;
|
||||
PlayerKVGameplayLoop.Pending.SetResult(true);await Task.Delay(25);
|
||||
bool presented=panel.canvasGroup.alpha==1 && PlayerKVGameplayLoop.Shows==1;
|
||||
bool interactRestored=panel.canvasGroup.blocksRaycasts && panel.canvasGroup.interactable;
|
||||
var s2=new ActualPanel();s2.StartTail();bool kvListener=s2.playButton.onClick.Count==1 && !s2.NonKvOnly;
|
||||
s2.playButton.onClick.Invoke();bool kvClick=s2.HomeClicks==1;
|
||||
PlayerKVGameplayLoop.Enabled=false;var s3=new ActualPanel();s3.StartTail();bool pfListener=s3.playButton.onClick.Count==1 && s3.NonKvOnly;
|
||||
bool ok=presented && interactRestored && kvListener && kvClick && pfListener && pendingBlocked;
|
||||
Console.WriteLine($"{(ok?"PASS":"FAIL")} presentation restores interaction; KV listener registered; raycasts={panel.canvasGroup.blocksRaycasts}, kvListener={kvListener}, pfListener={pfListener}");return ok?0:1;
|
||||
public class Canvas {public float alpha;public bool blocksRaycasts,interactable;}
|
||||
public class ButtonEvent{System.Action h;public int Count=>h==null?0:1;public void AddListener(System.Action a)=>h+=a;public void Invoke()=>h?.Invoke();}
|
||||
public class Button{public ButtonEvent onClick=new();}
|
||||
public class Fish {public void SaveCaughtFishForKVLoop(int id){}}
|
||||
public class Player {public PlayerKVEditResult SaveFishingRewardForKVLoop()=>new();}
|
||||
namespace game.PlayerDataStorage {
|
||||
public class PlayerKVEditResult{}
|
||||
public static class PlayerKVGameplayLoop {
|
||||
public class FishingRewardAction{}
|
||||
public static bool Enabled=true;static FishingRewardAction action;public static int Shows;public static TaskCompletionSource<bool> Pending=new();
|
||||
public static FishingRewardAction BindRewardPanel(object p) {if(action!=null)return null;return action=new();}
|
||||
public static bool RewardPanelCurrent(FishingRewardAction a,object p)=>a!=null && ReferenceEquals(a,action);
|
||||
public static IDisposable BeginRewardSegment(string name)=>new Scope();
|
||||
public static Task ConfirmRewardAsync(FishingRewardAction a,PlayerKVEditResult p)=>Pending.Task;
|
||||
public static void PresentReward(FishingRewardAction a,Action show){Shows++;show();}
|
||||
public static void Fault(string message)=>throw new Exception(message);
|
||||
class Scope:IDisposable{public void Dispose(){}}
|
||||
}}
|
||||
@@ -0,0 +1 @@
|
||||
<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</DefineConstants><RestoreConfigFile>$(MSBuildThisFileDirectory)../MinimalLoginTests/NuGet.Config</RestoreConfigFile></PropertyGroup><ItemGroup><Compile Include="PanelSlice.cs.txt"/><Compile Include="PanelTests.cs.txt"/></ItemGroup></Project>
|
||||
16
CodexOutput/upserver/KVFishingRewardTests/PermitTests.cs.txt
Normal file
16
CodexOutput/upserver/KVFishingRewardTests/PermitTests.cs.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
using game.PlayerDataStorage;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
var root=Path.GetFullPath("CodexOutput/upserver/KVFishingRewardTests/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);
|
||||
Case(3,"fishing-reward-once",true); Case(2,"fishing-reward-once",false); Case(3,"fishing-consume-once",false); Case(4,"fishing-reward-once",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,44 @@
|
||||
from pathlib import Path
|
||||
r=Path(__file__).parent
|
||||
s=Path('Client/Assets/Scripts/GampPlay/FishingB/FishingUpdateSys.cs').read_text(encoding='utf-8-sig')
|
||||
def method(sig):
|
||||
a=s.index(sig);i=s.index('{',a)+1;n=1
|
||||
while n:n+=(s[i]=='{')-(s[i]=='}');i+=1
|
||||
return s[a:i]
|
||||
code='''using System;using System.Threading.Tasks;using game.PlayerDataStorage;
|
||||
int checks=0;void Check(bool b,string m){checks++;if(!b)throw new Exception(m);}
|
||||
Controller Fresh(){PlayerKVGameplayLoop.Reset();UIManager.Instance=new();return new();}
|
||||
var c=Fresh();c.PiercingFish(3);c.PiercingFish(3);c.End();c.End();
|
||||
Check(c.Strikes==1&&c.Endings==1&&UIManager.Instance.Calls==1,"actual wrappers suppress repeated drawing/end");
|
||||
Check(PlayerKVGameplayLoop.Scope==null,"UI await never holds ending scope");
|
||||
c.Disable();UIManager.Instance.Response.SetResult(new object());await Task.Delay(20);
|
||||
Check(c.Destroyed==1&&PlayerKVGameplayLoop.Faults==1,"actual Disable during UI await disposes stale returned panel");
|
||||
c=Fresh();c.PiercingFish(3);c.Dead();Check(PlayerKVGameplayLoop.Cancelled,"actual Destroy revokes controller");
|
||||
c=Fresh();c.PiercingFish(3);c.fishingData.IsCaughtFish=false;c.End();Check(c.Endings==0&&UIManager.Instance.Calls==0,"natural failure cannot invoke reward core");
|
||||
foreach(int version in new[]{1,2}){c=Fresh();PlayerKVGameplayLoop.Version=version;c.PiercingFish(3);Check(c.Strikes==0&&PlayerKVGameplayLoop.Faults==1,"old permits do not draw");}
|
||||
c=Fresh();c.PiercingFish(3);PlayerKVGameplayLoop.Confirmed=true;c.Disable();Check(PlayerKVGameplayLoop.Faults==0,"confirmed controller normal disable no second fault");
|
||||
c=Fresh();PlayerKVGameplayLoop.Enabled=false;c.PiercingFish(3);c.End();Check(c.Strikes==1&&c.Endings==1,"ordinary PF wrapper takes original core");
|
||||
Console.WriteLine($"PASS actual controller source wrappers/lifecycle {checks} assertions; core/render/storage services are spies; no gameplay or network");
|
||||
public class Controller {
|
||||
public int Strikes,Endings,Destroyed;public FishingData fishingData=new();bool isFail;IDisposable disposables,_comboDisposable;object _hpcoroutine;
|
||||
public void End()=>FishEnd();public void Disable()=>OnDisable();public void Dead()=>OnDestroy();
|
||||
void PiercingFishCore(int zone){Strikes++;}void FishEndCore(){Endings++;}void Destroy(object p){Destroyed++;}
|
||||
void StopSoundCombo(){}void StopRodAudio(){}void StopNotReeling(){}void StopDrawingSplash(){}void AudioRelease(){}void SpSkillFxRelease(){}void StopAllCoroutines(){}
|
||||
'''
|
||||
for sig in ['public void PiercingFish(int waitingZone)','void FishEnd()','async Task ShowKVRewardPanelAsync()','private void OnDisable()','private void OnDestroy()']:code+=method(sig)+'\n'
|
||||
code+='''}
|
||||
public class FishingData{public bool IsCaughtFish=true;}public enum UITypes{FishingRewardPanel}
|
||||
public class UIManager{public static UIManager Instance=new();public int Calls;public TaskCompletionSource<object> Response=new();public Task<object> ShowUI(UITypes type){Calls++;return Response.Task;}}
|
||||
public static class GContext{public static Container container=new();}public class Container{public T Resolve<T>() where T:new()=>new();}public class SoundMgr{public void StopActiveFishingLoopSounds(float x){}}
|
||||
namespace game.PlayerDataStorage {public static class PlayerKVGameplayLoop{
|
||||
public static bool Enabled,Drawing,Ending,Cancelled,Confirmed;public static int Version,Faults;public static string Scope;
|
||||
public static void Reset(){Enabled=true;Drawing=Ending=Cancelled=Confirmed=false;Version=3;Faults=0;Scope=null;}
|
||||
public static bool RewardRunCurrent=>!Cancelled;
|
||||
public static bool BeginRewardDrawing(object o){if(Version!=3||Cancelled)throw new Exception("no draw");if(Drawing)return false;Drawing=true;return true;}
|
||||
public static bool BeginRewardEnding(object o,bool success){if(Ending)return false;if(!success)throw new Exception("failed catch");Ending=true;return true;}
|
||||
public static IDisposable BeginRewardSegment(string n){Scope=n;return new Batch();}class Batch:IDisposable{public void Dispose(){Scope=null;}}
|
||||
public static void Fault(string s){Faults++;}public static void CancelReward(object o,string r){if(Cancelled)return;Cancelled=true;if(!Confirmed)Fault(r);}
|
||||
}}
|
||||
'''
|
||||
(r/'ControllerTests.cs.txt').write_text(code,encoding='utf-8')
|
||||
(r/'ControllerTests.csproj').write_text('''<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><EnableDefaultCompileItems>false</EnableDefaultCompileItems><DefineConstants>UNITY_EDITOR</DefineConstants><RestoreConfigFile>$(MSBuildThisFileDirectory)../MinimalLoginTests/NuGet.Config</RestoreConfigFile></PropertyGroup><ItemGroup><Compile Include="ControllerTests.cs.txt"/></ItemGroup></Project>''',encoding='utf-8')
|
||||
@@ -0,0 +1,34 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
r=Path(__file__).parent
|
||||
before=(r/'before/FishingRewardPanel.cs').read_text(encoding='utf-8-sig')
|
||||
current=Path('Client/Assets/Scripts/UI/FishingReward/FishingRewardPanel.cs').read_text(encoding='utf-8-sig')
|
||||
def method(src,signature):
|
||||
start=src.index(signature);a=src.index('{',start);depth=1;i=a+1
|
||||
while depth:
|
||||
depth += (src[i]=='{')-(src[i]=='}');i+=1
|
||||
return src[start:i]
|
||||
def block(src):
|
||||
start=src.index('DropPackageList dropPackageList =');end=src.index(' add = cash;',start)
|
||||
return src[start:end]
|
||||
def formula(src,name):
|
||||
return 'public class '+name+' : FormulaBase { public int Run(){'+block(src)+'return cash;}'+method(src,'float SetProficiency(')+'}\n'
|
||||
text='''using System;
|
||||
foreach(var c in new[]{(10f,0.25f,10,0.5f,0.5f,0.2f,0.1f,122),(0f,0.25f,10,0.5f,0.5f,0.2f,0.1f,0),(10f,0f,1,1f,0f,0f,0f,10),(1f,0f,1,0.5f,0f,0f,0f,0)}){
|
||||
var old=new OldFormula();var next=new NewFormula();
|
||||
foreach(var x in new FormulaBase[]{old,next}){x._tables.TbDrop[0].DropList.DropCountList[1]=c.Item1;x.pointBonus=c.Item2;x.Magnification=c.Item3;x.fishingData.random=c.Item4;x.fishingData.WeightIncEvent=c.Item5;x.playerData.benefitsCashMag=c.Item6;GContext.container.Buff.Gold=c.Item7;}
|
||||
int a=old.Run(),b=next.Run();if(a!=c.Item8||b!=a)throw new Exception($"Formula {a}/{b} expected {c.Item8}");Console.WriteLine($"PASS original/current cash={b}");
|
||||
}
|
||||
public class FormulaBase {public Tables _tables=new();public Fishing fishingData=new();public Player playerData=new();public float pointBonus,extraPointEvent;public int Magnification;protected float _fishScore;protected Text text_gold_num=new(),text_num_buff=new();}
|
||||
public class Tables {public Drop[] TbDrop={new()};}public class Drop{public DropPackageList DropList=new();}public class DropPackageList {public float[] DropCountList={20,10};}
|
||||
public class Fishing{public Fish curFishData=new(),fishItem=new();public float random,fishWeight,WeightIncEvent;public int MapId;public bool IsNewItemFish()=>false;}
|
||||
public class Fish{public int DropID,ID,Quality=2;}public class Player{public float benefitsCashMag,benefitsEventFishCashMag;}
|
||||
public class CollectingData{public float ExtraCashDrop()=>0;}public class BuffDataCenter{public float Gold;public float GetExtraGoldBuffValue(int m,int q)=>Gold;}
|
||||
public class FishingChallengeManager{public void AddScoreByQuality(int a,float b,int c){}}
|
||||
public class Container{public BuffDataCenter Buff=new();public T Resolve<T>() where T:new()=>typeof(T)==typeof(BuffDataCenter)?(T)(object)Buff:new T();}
|
||||
public static class GContext{public static Container container=new();}public class Text{public Color color;}public struct Color{public static Color black,white;}
|
||||
public static class Mathf{public static int RoundToInt(float x)=>(int)MathF.Round(x,MidpointRounding.ToEven);}
|
||||
'''
|
||||
(r/'FormulaTests.cs.txt').write_text(text+formula(before,'OldFormula')+formula(current,'NewFormula'),encoding='utf-8')
|
||||
(r/'FormulaTests.csproj').write_text('''<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><EnableDefaultCompileItems>false</EnableDefaultCompileItems><RestoreConfigFile>$(MSBuildThisFileDirectory)../MinimalLoginTests/NuGet.Config</RestoreConfigFile></PropertyGroup><ItemGroup><Compile Include="FormulaTests.cs.txt"/></ItemGroup></Project>''',encoding='utf-8')
|
||||
(r/'formula-source-check.json').write_text(json.dumps({'cashBlockUnchanged':block(before)==block(current),'proficiencyUnchanged':method(before,'float SetProficiency(')==method(current,'float SetProficiency('),'fishWeightUnchanged':method(before,'void SetFishWeight(')==method(current,'void SetFishWeight(')},indent=2))
|
||||
@@ -0,0 +1,42 @@
|
||||
from pathlib import Path
|
||||
r=Path(__file__).parent
|
||||
s=(r/'before/FishingRewardPanel.cs').read_text(encoding='utf-8-sig');a=s.index(' async void Success(float pointBonus)');i=s.index('{',a)+1;n=1
|
||||
while n:n+=(s[i]=='{')-(s[i]=='}');i+=1
|
||||
method=s[a:i]
|
||||
text='''using System;using System.Threading.Tasks;using System.Runtime.CompilerServices;
|
||||
var panel=new OriginalPanel();panel.Run();
|
||||
bool ok=panel.Animations==0&&!panel.go_info.active;
|
||||
Console.WriteLine($"{(ok?"PASS":"FAIL")} original full Success method displays before pending storage response: writes={Storage.Writes}, confirmed={Storage.Confirmed}, animations={panel.Animations}, info={panel.go_info.active}");
|
||||
return ok?0:1;
|
||||
public class OriginalPanel{
|
||||
public int Animations;public Node go_info=new(),go_weight=new(),fx_ui_blingbling_01=new(),bg_target=new();
|
||||
int addCollecting,curCollectingID,add;float extraPointEvent;bool isSocial,isEnable=true;int Magnification=10;float waitTime1,duration1,waitTime2,duration2;
|
||||
Fishing fishingData=new();FishingEventData fishingEventData=new();Tables _tables=new();PlayerData playerData=new();string PanelName="reward";
|
||||
Text root_target_text_info=new(),root_1v1battle_text_info=new(),text_gold_num=new(),text_num_buff=new(),txt_magnification=new();
|
||||
Bonus bonusReward=new();UI uiService=new();Social fishingRewardSocial=new();Animator anim_panel;
|
||||
public OriginalPanel(){anim_panel=new Animator(()=>Animations++);}public void Run()=>Success(0);
|
||||
void MaterialChange(){}void PlayAudioRewardFish(){}void SetTextTitleAsync(float x,bool s){}void SetFishWeight(float w){}void VibrationData(){}
|
||||
float SetProficiency(float a,float b,int c)=>1;object AddCash()=>new();object StartCoroutine(object o){Animations++;return o;}
|
||||
'''+method+'''}
|
||||
public static class Storage{public static int Writes;public static bool Confirmed=false;}
|
||||
public class Node{public bool active;public Node gameObject=>this;public void SetActive(bool b){active=b;}}
|
||||
public class Text{public string text;public Color color;}public struct Color{public static Color black,white;}
|
||||
public class PlayerData{public float benefitsCashMag;public void SaveFishingFrequency(){Storage.Writes++;}public void AddGold(ulong v){Storage.Writes++;}}
|
||||
public class PlayerItemData{public void AddItemCount(int a,int b,object c){}}public class PiggyBankPackData{public void TriggerPack(){}}public class BargainPackData{public void TriggerPack(){}}
|
||||
public class BuffDataCenter{public void EndCollectionTargetWeight(int x){}public float GetExtraGoldBuffValue(int a,int b)=>0;}public class CollectingData{public float ExtraCashDrop()=>0;}
|
||||
public class GuideDataCenter{public void AddIndex(bool b){}}public class MMTService{}
|
||||
public class Tables{public Drop[] TbDrop={new()};}public class Drop{public DropPackageList DropList=new();}public class DropPackageList{public float[] DropCountList={1,10};public int[] DropIDList={0,1001};}
|
||||
public class Fishing{public Fish curFishData=new(),fishItem=new();public int MapId,NewIndex;public float random=1,fishWeight=1;public bool IsKeepsakeFish()=>false;public bool IsPiggyBankFish()=>false;public bool IsBarginPackFish()=>false;public bool IsGeneralFish()=>true;}
|
||||
public class Fish{public int ID,DropID,Quality=2;public FishGeneralType GeneralType;public bool IsSpinachItem()=>false;}
|
||||
public enum FishGeneralType{Normal,SpMapFish,EventFish}public enum ConditionType{CashFromFishing,MaxCashFromFishing}
|
||||
public record ConditionTypeEvent(ConditionType c,int count);public record TargetAddData(int a,int b,int c);
|
||||
public class FishingEventData{public int curActivateItemId;public Target collectingTargetInit=new();public float OnFishReward(int a,int b,int c)=>0;public void AddProgress(int a,int b){}}public class Target{public string BgRewardAdd;}
|
||||
public static class GContext{public static Container container=new();public static void Publish(object e){}}public class Container{public T Resolve<T>() where T:new()=>new();}
|
||||
public class Bonus{public void Show(int n){}public void DOTweenAdd(int n,float t){}}public class UI{public void SetImageSprite(object a,string b,string c){}}public class Social{public void ShowSocial(bool b){}}
|
||||
public class Animator{Action play;public Animator(Action a){play=a;}public void Play(string s){play();}}
|
||||
public static class LocalizationMgr{public static string GetText(string s)=>s;}public static class Mathf{public static int RoundToInt(float v)=>(int)MathF.Round(v);}
|
||||
public static class ConvertTools{public static string GetNumberString2(int n)=>n.ToString();}public static class DOTween{public static Tween To(Func<float>a,Action<float>b,float c,float d)=>new();}public class Tween{public Tween OnUpdate(Action a)=>this;public Tween SetId(string s)=>this;}
|
||||
public class WaitForSeconds{static TaskCompletionSource<bool> wait=new();public WaitForSeconds(float t){}public TaskAwaiter<bool> GetAwaiter()=>wait.Task.GetAwaiter();}
|
||||
'''
|
||||
(r/'OldPresentationTests.cs.txt').write_text(text,encoding='utf-8')
|
||||
(r/'OldPresentationTests.csproj').write_text('''<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><EnableDefaultCompileItems>false</EnableDefaultCompileItems><RestoreConfigFile>$(MSBuildThisFileDirectory)../MinimalLoginTests/NuGet.Config</RestoreConfigFile></PropertyGroup><ItemGroup><Compile Include="OldPresentationTests.cs.txt"/></ItemGroup></Project>''',encoding='utf-8')
|
||||
@@ -0,0 +1,5 @@
|
||||
var s=UnityEditor.AssetDatabase.LoadAssetAtPath<GuidanceDefineSetting>("Assets/ABPackage/pkgPrepare/Export/GuidanceConfig.asset");
|
||||
var groups=s.guidanceGroupDefines.Where(g=>g.guidanceDefines.Any(n=>n.panelName=="FishingPanel"||n.panelName=="FishingPanel_Advanced"||n.panelName=="FishingRewardPanel")).OrderBy(g=>g.groupName.ToString(),System.StringComparer.Ordinal).ToArray();
|
||||
var projection=string.Join("\n",groups.Select(g=>UnityEngine.JsonUtility.ToJson(g)));
|
||||
System.IO.File.WriteAllText("C:/projects/ft/CodexOutput/upserver/KVFishingRewardTests/guide-baseline-projection.jsonl",projection,new System.Text.UTF8Encoding(false));
|
||||
return new{count=groups.Length,nodeCounts=string.Join(",",groups.Select(g=>g.groupName+":"+g.guidanceDefines.Length))};
|
||||
@@ -0,0 +1,2 @@
|
||||
var s=UnityEditor.AssetDatabase.LoadAssetAtPath<GuidanceDefineSetting>("Assets/ABPackage/pkgPrepare/Export/GuidanceConfig.asset");
|
||||
return string.Join("\n",System.Linq.Enumerable.Select(System.Linq.Enumerable.Where(s.guidanceGroupDefines,g=>new[]{"FishSkill01","Pollution01","FishSkill02","FishSkill03","FishSkill04","Egyptian01","EasterFish01"}.Contains(g.groupName.ToString())),g=>g.groupName+" | "+g.TriggerConditionsType+" | "+g.TriggerConditionsParameter+" | "+string.Join(",",System.Linq.Enumerable.Select(g.guidanceDefines,x=>x.panelName))));
|
||||
@@ -0,0 +1,7 @@
|
||||
var settings=UnityEditor.AssetDatabase.LoadAssetAtPath<GuidanceDefineSetting>("Assets/ABPackage/pkgPrepare/Export/GuidanceConfig.asset");
|
||||
var backup=SimpleJSON.JSON.Parse(System.IO.File.ReadAllText("C:/projects/ft/CodexOutput/upserver/KVGameplayLoopTests/backups/20260924T084836Z/102.json"));
|
||||
var entries=backup["data"]["GuideSaveDataNew"].Value.Split(';');
|
||||
var completed=new System.Collections.Generic.HashSet<string>(System.Linq.Enumerable.Select(System.Linq.Enumerable.Where(entries,x=>x.EndsWith("|1")),x=>x.Split('|')[0]));
|
||||
var groups=System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Where(settings.guidanceGroupDefines,g=>System.Linq.Enumerable.Any(g.guidanceDefines,s=>s.panelName=="FishingPanel" || s.panelName=="FishingPanel_Advanced" || s.panelName=="FishingRewardPanel")));
|
||||
var unfinished=System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Where(groups,g=>!completed.Contains(g.groupName.ToString())));
|
||||
return new{count=groups.Length,required=string.Join(",",System.Linq.Enumerable.Select(groups,g=>g.groupName.ToString())),unfinished=string.Join(",",System.Linq.Enumerable.Select(unfinished,g=>g.groupName.ToString())),settings.SkipLevel,missingNotSkipped=string.Join(",",System.Linq.Enumerable.Select(System.Linq.Enumerable.Where(unfinished,g=>!settings.SkipGroupNames.Contains(g.groupName)),g=>g.groupName.ToString())),scope="read-only asset and backup; no runtime guide state assumed"};
|
||||
@@ -0,0 +1,3 @@
|
||||
var s=UnityEditor.AssetDatabase.LoadAssetAtPath<GuidanceDefineSetting>("Assets/ABPackage/pkgPrepare/Export/GuidanceConfig.asset");
|
||||
var groups=s.guidanceGroupDefines.Where(g=>g.guidanceDefines.Any(n=>n.panelName=="FishingPanel"||n.panelName=="FishingPanel_Advanced"||n.panelName=="FishingRewardPanel")).OrderBy(g=>g.groupName.ToString(),System.StringComparer.Ordinal).ToArray();
|
||||
return string.Join("\n",groups.Select(g=>UnityEngine.JsonUtility.ToJson(g)));
|
||||
@@ -0,0 +1,75 @@
|
||||
$ErrorActionPreference='Stop'
|
||||
$root='C:/projects/ft/CodexOutput/upserver'
|
||||
function Read-Backup($stamp) { Get-Content -Raw -LiteralPath "$root/KVGameplayLoopTests/backups/$stamp/102.json" | ConvertFrom-Json }
|
||||
function Changed-Keys($left,$right) {
|
||||
@(@($left.PSObject.Properties.Name)+@($right.PSObject.Properties.Name) | Sort-Object -Unique | Where-Object { $left.$_ -cne $right.$_ })
|
||||
}
|
||||
function Hash-Value([string]$value) {
|
||||
$sha=[Security.Cryptography.SHA256]::Create()
|
||||
try { [BitConverter]::ToString($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($value))).Replace('-','') } finally { $sha.Dispose() }
|
||||
}
|
||||
$before=Read-Backup '20260928T024346Z'
|
||||
$reward=Read-Backup '20260928T024903Z'
|
||||
$closed=Read-Backup '20260928T025001Z'
|
||||
$final=Read-Backup '20260928T025514Z'
|
||||
$events=@(Get-Content -LiteralPath "$root/KVGameplayLoopTests/runs/44848_639261601944579366/events.jsonl" | ForEach-Object { $_ | ConvertFrom-Json })
|
||||
$recoveryEvents=@(Get-Content -LiteralPath "$root/KVGameplayLoopTests/runs/103828_639261606481142886/events.jsonl" | ForEach-Object { $_ | ConvertFrom-Json })
|
||||
$recovered=(Get-Content -Raw -LiteralPath "$root/KVFishingRewardTests/fourth-recovered-values.json" | ConvertFrom-Json).Result | ConvertFrom-Json
|
||||
$preMain=$before.data.NewPlayerDataDic | ConvertFrom-Json
|
||||
$postMain=$final.data.NewPlayerDataDic | ConvertFrom-Json
|
||||
$preMap=$before.data.Map_5_102 | ConvertFrom-Json
|
||||
$postMap=$final.data.Map_5_102 | ConvertFrom-Json
|
||||
$rewardConfirmation=@($events | Where-Object kind -EQ 'fishing-reward-confirmed')[0]
|
||||
$presentation=@($events | Where-Object kind -EQ 'fishing-reward-presented')[0]
|
||||
$ticketMatches=@($rewardConfirmation.detail.tickets | ForEach-Object { [pscustomobject]@{key=$_.key;sequence=$_.sequence;matchesFinalServer=((Hash-Value $final.data.($_.key)) -ceq $_.valueHash)} })
|
||||
$cacheResults=@('44848_639261601944579366','103828_639261606481142886' | ForEach-Object {
|
||||
$cacheFile=Get-ChildItem -LiteralPath "$root/KVGameplayLoopTests/runs/$_/cache" -Recurse -Filter state.json
|
||||
$cache=Get-Content -Raw -LiteralPath $cacheFile.FullName | ConvertFrom-Json
|
||||
[pscustomobject]@{run=$_;pending=@($cache.pending).Count;unknown=($null -ne $cache.unknownBatch);active=($null -ne $cache.activeBatch);baselineChangedKeys=@(Changed-Keys $cache.baseline $final.data)}
|
||||
})
|
||||
$report=[ordered]@{
|
||||
phase='4C2-fourth-real-run-and-fresh-process-recovery-awaiting-independent-review'
|
||||
api=$recovered.api
|
||||
backups=@('20260928T024346Z','20260928T024903Z','20260928T025001Z','20260928T025514Z')
|
||||
countBefore=@($before.data.PSObject.Properties).Count;countAfter=@($final.data.PSObject.Properties).Count
|
||||
changedKeys=@(Changed-Keys $before.data $final.data)
|
||||
changedFishIds=@(Changed-Keys $preMap $postMap)
|
||||
fishBefore=$preMap.'10223';fishAfter=$postMap.'10223'
|
||||
mainBefore=$preMain;mainAfter=$postMain
|
||||
rewardToQuitChangedKeys=@(Changed-Keys $reward.data $closed.data)
|
||||
quitToRecoveryFinalChangedKeys=@(Changed-Keys $closed.data $final.data)
|
||||
consumeCount=@($events | Where-Object kind -EQ 'fishing-energy-applied').Count
|
||||
rewardConfirmedCount=@($events | Where-Object kind -EQ 'fishing-reward-confirmed').Count
|
||||
rewardPresentedCount=@($events | Where-Object kind -EQ 'fishing-reward-presented').Count
|
||||
confirmedBeforePresented=([DateTime]$rewardConfirmation.at -lt [DateTime]$presentation.at)
|
||||
rewardTickets=$ticketMatches
|
||||
faults=@($events | Where-Object kind -EQ 'fault').Count
|
||||
recoveryFishingEvents=@($recoveryEvents | Where-Object kind -Like 'fishing-*').Count
|
||||
recoveryWrites=@($recoveryEvents | Where-Object kind -EQ 'accepted').Count
|
||||
recovered=$recovered
|
||||
recoveredCoreEqualsFinalServer=($recovered.core -ceq $postMain.newData)
|
||||
recoveredFishRawEqualsFinalServer=($recovered.fishRaw -ceq $postMap.'10223')
|
||||
cacheResults=$cacheResults
|
||||
signupGold=16200;fishingGold=2516;totalGold=18716
|
||||
independentFormulaRecomputed=$false
|
||||
precastIndependentBackupCaptured=$false
|
||||
boundary='用户在主会话尚未完成动作前快照时手动签到并抛竿;precast/live-gates实际采样晚于扣费,不能充当动作前基线。保存完整审计和服务器前后值,交独立复核裁决证据是否充分,不自动重跑。鱼TotalNum由新进程KV有效值读回验证;活对象直接验证重量/等级/NewFish,未通过私有反射读取TotalNum。'
|
||||
}
|
||||
$report | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath "$root/KVFishingRewardTests/fourth-reconciliation.json" -Encoding utf8
|
||||
$checks=@(
|
||||
$report.api -ceq 'http://s101.local:18080/'
|
||||
$report.consumeCount -eq 1
|
||||
$report.rewardConfirmedCount -eq 1
|
||||
$report.rewardPresentedCount -eq 1
|
||||
$report.confirmedBeforePresented
|
||||
$report.faults -eq 0
|
||||
$report.recoveryFishingEvents -eq 0
|
||||
$report.rewardToQuitChangedKeys.Count -eq 0
|
||||
$report.quitToRecoveryFinalChangedKeys.Count -eq 0
|
||||
$report.recoveredCoreEqualsFinalServer
|
||||
$report.recoveredFishRawEqualsFinalServer
|
||||
@($ticketMatches | Where-Object { -not $_.matchesFinalServer }).Count -eq 0
|
||||
@($cacheResults | Where-Object { $_.pending -ne 0 -or $_.unknown -or $_.active -or $_.baselineChangedKeys.Count -ne 0 }).Count -eq 0
|
||||
)
|
||||
if($checks -contains $false) { throw 'Reconciliation assertion failed; inspect fourth-reconciliation.json' }
|
||||
[pscustomobject]@{checks=$checks.Count;passed=$true;changedKeys=$report.changedKeys;changedFishIds=$report.changedFishIds;recoveryUnchanged=$true} | ConvertTo-Json -Depth 4
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
var panel=GameObject.Find("UIRoot/EventSignupPopupPanel_2");
|
||||
if(panel==null)return "No signup popup";
|
||||
var button=panel.transform.Find("btn_close").GetComponent<Button>();
|
||||
button.onClick.Invoke();
|
||||
return "Invoked existing signup close callback only; no claim callback";
|
||||
@@ -0,0 +1 @@
|
||||
return KVGameplayLoopSetup.State();
|
||||
@@ -0,0 +1,17 @@
|
||||
using asap.core;
|
||||
using System.Linq;
|
||||
using LitJson;
|
||||
var p=GContext.container.Resolve<GameCore.PlayerData>();
|
||||
var f=GContext.container.Resolve<GameCore.PlayerFishData>();
|
||||
var b=GContext.container.Resolve<BattlePassData>();
|
||||
var map=JsonMapper.ToObject<System.Collections.Generic.Dictionary<string,string>>(PlayFabMgr.Instance.GetLocalData("Map_5_102"));
|
||||
return JsonMapper.ToJson(new {
|
||||
api=GContext.container.Resolve<ICustomServerMgr>().ApiBaseUri.ToString(),
|
||||
core=p.PlayerDataDic["newData"],
|
||||
gold=(ulong)p.gold,energy=(int)p.Energy,energyRM=p.GetEnergyRM(),frequency=p._fishingFrequency,
|
||||
fishId=10223,fishRaw=map["10223"],
|
||||
liveMaxWeight=f.GetDataMaxWeight(10223),liveLevel=f.GetDataLevel(10223),liveNewFish=f.GetDataNewFish(10223),
|
||||
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)),
|
||||
state=KVGameplayLoopSetup.State()
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
using asap.core;
|
||||
if(GContext.container==null)return "Game container unavailable after stop";
|
||||
try {
|
||||
var d=GContext.container.Resolve<FishingData>();
|
||||
return new{postStopObservationOnly=true,ready=GContext.Ready,item=d.fishItem?.ID,subtype=d.fishItem?.SubType,fish=d.curFishData?.ID,free=d.IsFree,retry=d.IsRetry,duel=d.IsDuel,map=d.MapId};
|
||||
} catch(System.Exception e){return "Post-stop game data unavailable: "+e.Message;}
|
||||
@@ -0,0 +1,2 @@
|
||||
return KVGameplayLoopSetup.PrepareFishingRewardPermit("102","C:/projects/ft/CodexOutput/upserver/KVGameplayLoopTests/backups/20260928T024346Z/102.json",new string[]{"AquariumEventDataNewKey","BattlePassRecordTask","BombGameCount","BuffDataList","BuyData","CampDataDic","ChainPackDataNew","DigCount","DigGridData","Event1v1BattleSlapData","EventBoostPackData","EventCubeMeltData","EventLuckGameRecord","EventLuckMagicPlayfabData","EventPartnerStatueData","FishHuntGameCount","FixedTimeShopDic","GuideSaveDataNew","IsOpenClubRed","LastPaymentTime","Leadboard_LastContinent","MMTest","Map_5_101","Map_5_102","MiniBPKeyNew","MonthlySignEvent","NewPlayerDataDic","NewRodList","OfferDiscoPartyManager","OfferStoryChain","OfferSweepPackData","Pack1A1Data","PiggyBankPackData","RodData","RodLevelData","SeasonStampData","ShopPackBuyCount","SoloDataKey","TargetBuffDataList","TransitionData","TriggerPackDataList","VIPAdPackData","WelcomeBack","epbdk","epdk"});
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
return KVGameplayLoopSetup.PreparePermit("102","C:/projects/ft/CodexOutput/upserver/KVGameplayLoopTests/backups/20260928T025001Z/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.PrepareFishingRewardPermit("102","C:/projects/ft/CodexOutput/upserver/KVGameplayLoopTests/backups/20260928T023420Z/102.json",new string[]{"AquariumEventDataNewKey","BattlePassRecordTask","BombGameCount","BuffDataList","BuyData","CampDataDic","ChainPackDataNew","DigCount","DigGridData","Event1v1BattleSlapData","EventBoostPackData","EventCubeMeltData","EventLuckGameRecord","EventLuckMagicPlayfabData","EventPartnerStatueData","FishHuntGameCount","FixedTimeShopDic","GuideSaveDataNew","IsOpenClubRed","LastPaymentTime","Leadboard_LastContinent","MMTest","Map_5_101","Map_5_102","MiniBPKeyNew","MonthlySignEvent","NewPlayerDataDic","NewRodList","OfferDiscoPartyManager","OfferStoryChain","OfferSweepPackData","Pack1A1Data","PiggyBankPackData","RodData","RodLevelData","SeasonStampData","ShopPackBuyCount","SoloDataKey","TargetBuffDataList","TransitionData","TriggerPackDataList","VIPAdPackData","WelcomeBack","epbdk","epdk"});
|
||||
@@ -0,0 +1 @@
|
||||
return KVGameplayLoopSetup.PrepareFishingRewardPermit("102","C:/projects/ft/CodexOutput/upserver/KVGameplayLoopTests/backups/20260924T093341Z/102.json",new string[]{"AquariumEventDataNewKey","BattlePassRecordTask","BombGameCount","BuffDataList","BuyData","CampDataDic","ChainPackDataNew","DigCount","DigGridData","Event1v1BattleSlapData","EventBoostPackData","EventCubeMeltData","EventLuckGameRecord","EventLuckMagicPlayfabData","EventPartnerStatueData","FishHuntGameCount","FixedTimeShopDic","GuideSaveDataNew","IsOpenClubRed","LastPaymentTime","Leadboard_LastContinent","MMTest","Map_5_101","Map_5_102","MiniBPKeyNew","MonthlySignEvent","NewPlayerDataDic","NewRodList","OfferDiscoPartyManager","OfferStoryChain","OfferSweepPackData","Pack1A1Data","PiggyBankPackData","RodData","RodLevelData","SeasonStampData","ShopPackBuyCount","SoloDataKey","TargetBuffDataList","TransitionData","TriggerPackDataList","VIPAdPackData","WelcomeBack","epbdk","epdk"});
|
||||
@@ -0,0 +1,8 @@
|
||||
using asap.core;
|
||||
using System.Linq;
|
||||
var p=GContext.container.Resolve<GameCore.PlayerData>();
|
||||
var g=GContext.container.Resolve<GameCore.GuideDataCenter>();
|
||||
var f=GContext.container.Resolve<GameCore.PlayerFishData>();
|
||||
var c=GContext.container.Resolve<FishingChallengeManager>();
|
||||
return new{time=ZZTimeHelper.UtcNow(),offset=ZZTimeHelper.LocalTimeOffsetInSec,timeDiff=ZZTimeHelper.timeDiff.TotalSeconds,unlockedMag=p.IsOpenMagnification,unlockedStrike=f.IsOpenPiercing,frequency=p._fishingFrequency,cruise=f.GetFishSwimToMap(p.lastMapId),collect=GContext.container.Resolve<CollectingData>().GetAddCollecting(),collectingTarget=GContext.container.Resolve<GameCore.FishingEventData>().collectingTargetInit!=null,piggy=GContext.container.Resolve<PiggyBankPackData>().IsPackActivated,bargain=GContext.container.Resolve<BargainPackData>().IsPackActivated,challenge=c.fishingChallengeData!=null,guideNode=g.curDefine!=null,guideGroup=g.curGroupDefine!=null,groups=string.Join(";",g.guidanceGroupDefines.Where(x=>x.guidanceDefines.Any(n=>n.panelName=="FishingPanel"||n.panelName=="FishingPanel_Advanced"||n.panelName=="FishingRewardPanel")).Select(x=>x.groupName+":"+g.GetIsFinish(x.groupName.ToString()))),state=KVGameplayLoopSetup.State()};
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
var consume=await KVGameplayLoopSetup.RunValidationAsync("fishing-consume");
|
||||
var core=await KVGameLoadValidation.RunAsync("M01-M02-M21-M22-M24");
|
||||
var full=await KVGameLoadValidation.RunAsync("B2-20");
|
||||
var route=await KVSaveRouteValidation.RunAsync("fixed-route");
|
||||
var equipment=await KVGameplayLoopSetup.RunValidationAsync("equipment-chain");
|
||||
return new {consume,core,full,route,equipment};
|
||||
@@ -0,0 +1 @@
|
||||
return await KVGameplayLoopSetup.RunValidationAsync("fishing-reward");
|
||||
@@ -0,0 +1,12 @@
|
||||
using asap.core;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
var p=GContext.container.Resolve<GameCore.PlayerData>();
|
||||
var t=GContext.container.Resolve<cfg.Tables>();
|
||||
var guide=GContext.container.Resolve<GameCore.GuideDataCenter>();
|
||||
var sys=UnityEngine.Object.FindObjectOfType<FishingUpdateSys>();
|
||||
var d=sys==null?null:(FishingData)typeof(FishingUpdateSys).GetField("fishingData",BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic).GetValue(sys);
|
||||
var loop=typeof(GameCore.PlayerData).Assembly.GetType("game.PlayerDataStorage.PlayerKVGameplayLoop");
|
||||
var a=loop.GetField("rewardAction",BindingFlags.Static|BindingFlags.NonPublic).GetValue(null);
|
||||
object Field(object o,string n)=>o?.GetType().GetField(n,BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic)?.GetValue(o);
|
||||
return new{utc=System.DateTime.UtcNow,server=ZZTimeHelper.UtcNow(),energy=(int)p.Energy,energyRM=p.GetEnergyRM(),gold=(ulong)p.gold,frequency=p._fishingFrequency,core=p.PlayerDataDic.GetValueOrDefault("newData"),map=(int)p.lastMapId,mag=p.GetMagnification(),guideNode=guide.curDefine?.panelName,guideGroup=guide.curGroupDefine?.groupName.ToString(),controller=sys!=null,fish=d?.curFishData?.ID,area=d?.curFishData?.AreaID,item=d?.fishItem?.ID,subtype=d?.fishItem?.SubType,draw=d?.IsDrawing,caught=d?.IsCaughtFish,result=d?.fishingResult.ToString(),random=d?.random,weight=d?.fishWeight,weightBonus=d?.WeightIncEvent,tension=Field(sys,"_linePower"),rewardConfirmed=Field(a,"Confirmed"),rewardPresented=Field(a,"Presented"),rewardCancelled=Field(a,"Cancelled"),state=KVGameplayLoopSetup.State()};
|
||||
@@ -0,0 +1,5 @@
|
||||
using asap.core;
|
||||
using System.Linq;
|
||||
var p=GContext.container.Resolve<GameCore.PlayerData>();
|
||||
var guide=GContext.container.Resolve<GameCore.GuideDataCenter>();
|
||||
return new{utc=System.DateTime.UtcNow,server=ZZTimeHelper.UtcNow(),energy=(int)p.Energy,energyRM=p.GetEnergyRM(),gold=(ulong)p.gold,frequency=p._fishingFrequency,core=p.PlayerDataDic["newData"],map=(int)p.lastMapId,mag=p.GetMagnification(),guideNode=guide.curDefine?.panelName,guideGroup=guide.curGroupDefine?.groupName.ToString(),controller=UnityEngine.Object.FindObjectOfType<FishingUpdateSys>()!=null,state=KVGameplayLoopSetup.State()};
|
||||
@@ -0,0 +1,3 @@
|
||||
var reward=await KVGameplayLoopSetup.RunValidationAsync("fishing-reward");
|
||||
var consume=await KVGameplayLoopSetup.RunValidationAsync("fishing-consume");
|
||||
return new {reward,consume,state=KVGameplayLoopSetup.State()};
|
||||
Reference in New Issue
Block a user