using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; namespace game.PlayerDataStorage { internal enum PlayerKVSchemaVerdict { Valid, EmptyNew, EmptyExisting, MissingCore, Malformed, ConflictingRepresentations, UnsupportedSchema, Stale } internal sealed class PlayerKVProgressPackPair { internal PlayerKVProgressPackState Current { get; } internal PlayerKVProgressPackState Migration { get; } internal PlayerKVProgressPackPair(PlayerKVProgressPackState current,PlayerKVProgressPackState migration) { Current=current; Migration=migration; } } internal sealed class PlayerKVDecodedSave { readonly object[] modules; internal string PlayerId { get; } internal long Epoch { get; } internal string ConfigurationVersion { get; } internal IReadOnlyDictionary Raw { get; } internal int ModuleCount => modules.Length; internal int UnconsumedTopLevelCount { get; } internal int UnconsumedModernCount { get; } internal int UnconsumedCount => UnconsumedTopLevelCount+UnconsumedModernCount; internal PlayerKVDecodedSave(string playerId,long epoch,string version,IReadOnlyDictionary raw,object[] modules,int unconsumedTop=0,int unconsumedModern=0) { if(modules==null || modules.Length!=31 || modules.Any(m=>m==null)) throw new ArgumentException("Complete decoded save required."); PlayerId=playerId; Epoch=epoch; ConfigurationVersion=version; Raw=new ReadOnlyDictionary(raw.ToDictionary(p=>p.Key,p=>p.Value,StringComparer.Ordinal)); this.modules=(object[])modules.Clone(); UnconsumedTopLevelCount=unconsumedTop; UnconsumedModernCount=unconsumedModern; } internal object Module(int id) { if(id<1 || id>31) throw new ArgumentOutOfRangeException(nameof(id)); return modules[id-1]; } internal T Module(int id) where T:class => Module(id) as T ?? throw new InvalidOperationException("Module state mismatch."); } internal sealed class PlayerKVSchemaResult { internal PlayerKVSchemaVerdict Verdict { get; } internal PlayerKVSchemaDiagnostics Diagnostics { get; } internal PlayerKVDecodedSave Data { get; } internal PlayerKVSchemaResult(PlayerKVSchemaVerdict verdict,PlayerKVSchemaDiagnostics diagnostics,PlayerKVDecodedSave data=null) { Verdict=verdict; Diagnostics=diagnostics; Data=verdict==PlayerKVSchemaVerdict.Valid ? data : null; } } internal enum PlayerKVIdentityChannel { hwGoogle, hwFacebook, hwGuest, hwVkid, Apple, none } internal sealed class PlayerKVIdentitySnapshot { internal string UserId { get; } internal bool NewlyCreated { get; } internal PlayerKVIdentityChannel Channel { get; } internal IReadOnlyList Linked { get; } internal PlayerKVIdentitySnapshot(string userId,bool newlyCreated,PlayerKVIdentityChannel channel,IEnumerable linked) { if(!Enum.IsDefined(typeof(PlayerKVIdentityChannel),channel) || linked==null) throw new ArgumentException("Identity snapshot required."); var copy=linked.Distinct().ToList(); if(copy.Any(c=>!Enum.IsDefined(typeof(PlayerKVIdentityChannel),c))) throw new ArgumentException("Unknown identity channel."); UserId=userId; NewlyCreated=newlyCreated; Channel=channel; Linked=copy.AsReadOnly(); } } internal sealed class PlayerKVLoginState { internal int MMTest { get; } internal int TapJoyBalance { get; } internal IReadOnlyDictionary AccountUrls { get; } internal IReadOnlyDictionary Normalizations { get; } internal IReadOnlyList Linked { get; } internal PlayerKVIdentityChannel Channel { get; } internal PlayerKVLoginState(int mm,int balance,IDictionary urls,IDictionary normalizations, IEnumerable linked,PlayerKVIdentityChannel channel) { MMTest=mm; TapJoyBalance=balance; AccountUrls=new ReadOnlyDictionary(new Dictionary(urls,StringComparer.Ordinal)); Normalizations=new ReadOnlyDictionary(new Dictionary(normalizations,StringComparer.Ordinal)); Linked=linked.Distinct().ToList().AsReadOnly(); Channel=channel; } } internal sealed class PlayerKVPartnerBot { internal string Id { get; } internal int ScorePlayer { get; } internal int ScoreBot { get; } internal int Grade { get; } internal int TargetScore { get; } internal DateTime WakeStartTime { get; } internal int WakingSeconds { get; } internal PlayerKVPartnerBot(string id,int player,int bot,int grade,int target,DateTime wake,int seconds) { Id=id; ScorePlayer=player; ScoreBot=bot; Grade=grade; TargetScore=target; WakeStartTime=wake; WakingSeconds=seconds; } } internal sealed class PlayerKVPartnerState { internal bool Present { get; } internal bool FirstOpen { get; } internal int EventId { get; } internal DateTime StartTime { get; } internal DateTime EndTime { get; } internal int CurrentPoints { get; } internal int CurrentMiningProgress { get; } internal int Token { get; } internal int InvitedCount { get; } internal int TicketCount { get; } internal IReadOnlyList> CompletedNodesList { get; } internal IReadOnlyList<(int myScore,int FriendScore)> BuilderPartnerScore { get; } internal float BonusPercent { get; } internal DateTime BonusExpireUtc { get; } internal int FreeSpinNum { get; } internal IReadOnlyDictionary Bots { get; } internal bool IsRecvFinalReward { get; } internal IReadOnlyList UIFinishedPartners { get; } internal bool IsFinished { get; } internal int SpinState { get; } internal float InflationRate { get; } internal bool IsEnableAutoMatch { get; } internal DateTime LastAutoMatchTime { get; } internal PlayerKVPartnerState(bool present,bool firstOpen,int eventId,DateTime start,DateTime end, int points,int progress,int token,int invited,int tickets,IEnumerable> nodes, IEnumerable<(int,int)> scores,float bonus,DateTime expires,int free,IDictionary bots, bool reward,IEnumerable ui,bool finished,int spin,float inflation,bool autoMatch,DateTime lastMatch) { Present=present; FirstOpen=firstOpen; EventId=eventId; StartTime=start; EndTime=end; CurrentPoints=points; CurrentMiningProgress=progress; Token=token; InvitedCount=invited; TicketCount=tickets; CompletedNodesList=nodes.Select(n=>(IReadOnlyList)new List(n).AsReadOnly()).ToList().AsReadOnly(); BuilderPartnerScore=new List<(int,int)>(scores).AsReadOnly(); BonusPercent=bonus; BonusExpireUtc=expires; FreeSpinNum=free; Bots=new ReadOnlyDictionary(new Dictionary(bots)); IsRecvFinalReward=reward; UIFinishedPartners=new List(ui).AsReadOnly(); IsFinished=finished; SpinState=spin; InflationRate=inflation; IsEnableAutoMatch=autoMatch; LastAutoMatchTime=lastMatch; } internal static PlayerKVPartnerState Missing() => new PlayerKVPartnerState(false,false,0,default,default, 0,0,0,0,0,Array.Empty(),Array.Empty<(int,int)>(),0,default,0,new Dictionary(), false,Array.Empty(),false,0,0,false,default); } internal enum PlayerKVSchemaError { Malformed, MissingCore, ConflictingRepresentations, UnsupportedSchema } internal sealed class PlayerKVSchemaException : Exception { internal PlayerKVSchemaError Code { get; } internal PlayerKVSchemaException(PlayerKVSchemaError code = PlayerKVSchemaError.Malformed) : base("KV schema rejected.") { Code = code; } } internal sealed class PlayerKVSchemaDetail { internal int Module { get; } internal int Field { get; } internal PlayerKVSchemaError Code { get; } internal PlayerKVSchemaDetail(int module, int field, PlayerKVSchemaError code) { Module = module; Field = field; Code = code; } } // Values, save keys and exception messages are deliberately absent from diagnostic output. internal sealed class PlayerKVSchemaDiagnostics { readonly List details = new List(); readonly SortedDictionary> counts = new SortedDictionary>(); internal int TotalErrors { get; private set; } internal int OmittedDetails => TotalErrors - details.Count; internal IReadOnlyList Details => details.AsReadOnly(); internal int Count(int module, PlayerKVSchemaError code) => counts.TryGetValue(module, out var codes) && codes.TryGetValue(code, out var count) ? count : 0; internal void Add(int module, int field, PlayerKVSchemaError code) { if (module < 1 || module > 31 || field < 0) throw new ArgumentOutOfRangeException(); TotalErrors++; if (!counts.TryGetValue(module, out var codes)) counts.Add(module, codes = new Dictionary()); codes[code] = Count(module, code) + 1; // Only retain 32 items, even when callers visit representations in different orders. var item = new PlayerKVSchemaDetail(module, field, code); int index = details.FindIndex(x => x.Module > module || (x.Module == module && x.Field > field)); if (index < 0) index = details.Count; if (index < 32) details.Insert(index, item); if (details.Count > 32) details.RemoveAt(32); } internal T Capture(int module, int field, Func parse) { try { return parse(); } catch (PlayerKVSchemaException ex) { Add(module, field, ex.Code); return default; } } } // Captured copies only: decoding never resolves Unity services or reads current culture/time. internal sealed class PlayerKVSchemaSnapshot { readonly HashSet rods, maps; readonly IReadOnlyDictionary packTypes; readonly IReadOnlyDictionary events; readonly IReadOnlyDictionary conditions; readonly HashSet conditionValues; readonly IReadOnlyDictionary fish; readonly IReadOnlyDictionary cards; internal string Version { get; } internal CultureInfo Culture { get; } internal DateTime NowUtc { get; } internal DateTime NowGame { get; } internal int InitWheelTicket { get; } internal PlayerKVSchemaSnapshot(string version, IEnumerable rods, IEnumerable maps, IDictionary conditions, CultureInfo culture, DateTime nowUtc, int initWheelTicket, IDictionary packTypes = null, IDictionary events = null, int globalTimeOffset = 0, IDictionary fish = null, IDictionary cards = null) { if (string.IsNullOrEmpty(version) || rods == null || maps == null || conditions == null || culture == null) throw new ArgumentException("Schema snapshot required."); Version = version; this.rods = new HashSet(rods); this.maps = new HashSet(maps); this.packTypes = new ReadOnlyDictionary(new Dictionary(packTypes ?? new Dictionary())); this.events = new ReadOnlyDictionary(new Dictionary(events ?? new Dictionary())); this.conditions = new ReadOnlyDictionary(new Dictionary(conditions, StringComparer.Ordinal)); conditionValues = new HashSet(conditions.Values); Culture = CultureInfo.ReadOnly((CultureInfo)culture.Clone()); NowUtc = nowUtc; InitWheelTicket = initWheelTicket; NowGame = nowUtc.AddHours(-(double)globalTimeOffset); this.fish=new ReadOnlyDictionary(new Dictionary(fish ?? new Dictionary())); this.cards=new ReadOnlyDictionary(new Dictionary(cards ?? new Dictionary())); } internal PlayerKVFishConfig Fish(int id) => fish.TryGetValue(id,out var value) ? value : null; internal PlayerKVFishCardConfig Card(int id) => cards.TryGetValue(id,out var value) ? value : null; internal bool HasRod(int id) => rods.Contains(id); internal bool HasPack(int id) => packTypes.ContainsKey(id); internal PlayerKVEventConfig Event(int id) => events.TryGetValue(id,out var value) ? value : null; internal bool HasPackType(int id, int type) => packTypes.TryGetValue(id, out var actual) && actual == type; internal bool HasMap(int id) => maps.Contains(id); internal int Condition(string text) { if (conditions.TryGetValue(text, out var named)) return named; if (int.TryParse(text, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var numeric) && conditionValues.Contains(numeric)) return numeric; throw new PlayerKVSchemaException(); } } internal sealed class PlayerKVCoreState { readonly object[] slots; internal PlayerKVCoreState(object[] slots) { this.slots = (object[])slots.Clone(); } internal int Integer(int slot) => (int)slots[slot]; internal ulong Unsigned(int slot) => (ulong)slots[slot]; internal DateTime EnergyTime => (DateTime)slots[7]; internal bool Matches(int slot, object value) => Equals(slots[slot], value); } internal sealed class PlayerKVAchievementValue { internal int Id { get; } internal ulong Target { get; } internal int Day { get; } internal PlayerKVAchievementValue(int id, ulong target, int day) { Id = id; Target = target; Day = day; } internal bool Same(PlayerKVAchievementValue other) => Id == other.Id && Target == other.Target && Day == other.Day; } internal sealed class PlayerKVAchievementState { internal IReadOnlyDictionary Values { get; } internal PlayerKVAchievementState(IDictionary values) { Values = new ReadOnlyDictionary(new Dictionary(values)); } } internal sealed class PlayerKVPiggyBankState { internal bool IsPresent { get; } internal int currentEventID { get; } internal int purchaseCount { get; } internal int VIPLvlWhenEventActivate { get; } internal int savingProgress { get; } internal int redirectID { get; } internal bool doNeedTrigger { get; } internal bool isTriggered { get; } internal float inflation { get; } internal PlayerKVPiggyBankState(bool present, int currentEventID, int purchaseCount, int VIPLvlWhenEventActivate, int savingProgress, int redirectID, bool doNeedTrigger, bool isTriggered, float inflation) { IsPresent = present; this.currentEventID = currentEventID; this.purchaseCount = purchaseCount; this.VIPLvlWhenEventActivate = VIPLvlWhenEventActivate; this.savingProgress = savingProgress; this.redirectID = redirectID; this.doNeedTrigger = doNeedTrigger; this.isTriggered = isTriggered; this.inflation = inflation; } internal static PlayerKVPiggyBankState Missing() => new PlayerKVPiggyBankState(false, 0, 0, 0, 0, 0, false, false, 0); } internal sealed class PlayerKVBargainState { internal bool IsPresent { get; } internal int currentEventID { get; } internal int redirectID { get; } internal int VIPLvlWhenEventActivate { get; } internal int purchaseCount { get; } internal int savingProgress { get; } internal int discountLvl { get; } internal int visualProgress { get; } internal int visualDiscountLvl { get; } internal int rodID { get; } internal int lastPackIDBought { get; } internal int packID { get; } internal bool doNeedTrigger { get; } internal bool isTriggered { get; } internal float inflation { get; } internal IReadOnlyList selections { get; } internal PlayerKVBargainState(bool present, int currentEventID, int redirectID, int VIPLvlWhenEventActivate, int purchaseCount, int savingProgress, int discountLvl, int visualProgress, int visualDiscountLvl, int rodID, int lastPackIDBought, int packID, bool doNeedTrigger, bool isTriggered, float inflation, IEnumerable selections) { IsPresent = present; this.currentEventID = currentEventID; this.redirectID = redirectID; this.VIPLvlWhenEventActivate = VIPLvlWhenEventActivate; this.purchaseCount = purchaseCount; this.savingProgress = savingProgress; this.discountLvl = discountLvl; this.visualProgress = visualProgress; this.visualDiscountLvl = visualDiscountLvl; this.rodID = rodID; this.lastPackIDBought = lastPackIDBought; this.packID = packID; this.doNeedTrigger = doNeedTrigger; this.isTriggered = isTriggered; this.inflation = inflation; this.selections = selections == null ? null : new List(selections).AsReadOnly(); } internal static PlayerKVBargainState Missing() => new PlayerKVBargainState(false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, false, 0, Array.Empty()); } internal sealed class PlayerKVShootingRangeState { internal bool IsPresent { get; } internal int EventId { get; } internal int CycleId { get; } internal int RedirectId { get; } internal int AmmoCount { get; } internal int CurrentStageIdx { get; } internal int ChainProgress { get; } internal long RewardFlag { get; } internal long TargetFlag { get; } internal bool HasBeenOpened { get; } internal float Inflation { get; } internal PlayerKVShootingRangeState(bool present, int EventId, int CycleId, int RedirectId, int AmmoCount, int CurrentStageIdx, int ChainProgress, long RewardFlag, long TargetFlag, bool HasBeenOpened, float Inflation) { IsPresent = present; this.EventId = EventId; this.CycleId = CycleId; this.RedirectId = RedirectId; this.AmmoCount = AmmoCount; this.CurrentStageIdx = CurrentStageIdx; this.ChainProgress = ChainProgress; this.RewardFlag = RewardFlag; this.TargetFlag = TargetFlag; this.HasBeenOpened = HasBeenOpened; this.Inflation = Inflation; } internal static PlayerKVShootingRangeState Missing() => new PlayerKVShootingRangeState(false, 0, 0, 0, 0, 0, 0, 0, 0, false, 0); } internal sealed class PlayerKVBuffValue { internal int SortID { get; } internal int sourceID { get; } internal int buffID { get; } internal bool isEnd { get; } internal DateTime buffEndTime { get; } internal PlayerKVBuffValue(int SortID,int sourceID,int buffID,bool isEnd,DateTime buffEndTime) { this.SortID=SortID; this.sourceID=sourceID; this.buffID=buffID; this.isEnd=isEnd; this.buffEndTime=buffEndTime; } } internal sealed class PlayerKVTriggerValue { internal int ID { get; } internal int index { get; } internal int lastID { get; } internal int vipLevel { get; } internal int mapID { get; } internal string time { get; } internal PlayerKVTriggerValue(int ID,int index,int lastID,int vipLevel,int mapID,string time) { this.ID=ID; this.index=index; this.lastID=lastID; this.vipLevel=vipLevel; this.mapID=mapID; this.time=time; } } internal sealed class PlayerKVGiftVipValue { internal int oldVipLevel { get; } internal int curVipLevel { get; } internal int timer { get; } internal PlayerKVGiftVipValue(int oldVipLevel,int curVipLevel,int timer) { this.oldVipLevel=oldVipLevel; this.curVipLevel=curVipLevel; this.timer=timer; } } internal sealed class PlayerKVFixedTimeValue { internal int id { get; } internal string endTime { get; } internal PlayerKVFixedTimeValue(int id,string endTime) { this.id=id; this.endTime=endTime; } } internal sealed class PlayerKVBuffState { internal IReadOnlyDictionary Map { get; } internal IReadOnlyDictionary Global { get; } internal IReadOnlyList Target { get; } internal PlayerKVBuffValue Daily { get; } internal PlayerKVBuffState(IDictionary map,IDictionary global, IEnumerable target,PlayerKVBuffValue daily) { Map=new ReadOnlyDictionary(new Dictionary(map)); Global=new ReadOnlyDictionary(new Dictionary(global)); Target=new List(target).AsReadOnly(); Daily=daily; } } internal sealed class PlayerKVTriggerState { internal IReadOnlyList Packs { get; } internal IReadOnlyDictionary Gifts { get; } internal IReadOnlyDictionary Fixed { get; } internal PlayerKVTriggerState(IEnumerable packs,IDictionary gifts, IDictionary fixedValues) { Packs=new List(packs).AsReadOnly(); Gifts=new ReadOnlyDictionary(new Dictionary(gifts,StringComparer.Ordinal)); Fixed=new ReadOnlyDictionary(new Dictionary(fixedValues)); } } internal sealed class PlayerKVPlayerState { internal PlayerKVCoreState Core { get; } internal IReadOnlyDictionary Modern { get; } internal IReadOnlyDictionary VipScores { get; } internal IReadOnlyList NewRods { get; } internal int Season { get; } internal bool Appraise { get; } internal string Building { get; } internal PlayerKVPlayerState(PlayerKVCoreState core,IDictionary modern,IDictionary scores, IEnumerable rods,int season,bool appraise,string building) { Core=core; Modern=new ReadOnlyDictionary(new Dictionary(modern,StringComparer.Ordinal)); VipScores=new ReadOnlyDictionary(new Dictionary(scores)); NewRods=new List(rods).AsReadOnly(); Season=season; Appraise=appraise; Building=building; } } internal sealed class PlayerKVPotionItem { internal string Name { get; } internal int Index { get; } internal bool IsOver { get; } internal int DataType { get; } internal IReadOnlyList Colors { get; } internal IReadOnlyList Shelves { get; } internal IReadOnlyList Grids { get; } internal IReadOnlyList FromDisplay { get; } internal PlayerKVPotionItem(string name,int index,bool over,int type,IEnumerable colors, IEnumerable shelves,IEnumerable grids,IEnumerable fromDisplay) { Name=name; Index=index; IsOver=over; DataType=type; Colors=new List(colors).AsReadOnly(); Shelves=new List(shelves).AsReadOnly(); Grids=new List(grids).AsReadOnly(); FromDisplay=new List(fromDisplay).AsReadOnly(); } } internal sealed class PlayerKVPotionState { internal bool IsPresent { get; } internal int EventID { get; } internal int PotionID { get; } internal int StageID { get; } internal int Stage1TotalLevel { get; } internal int Stage2TotalLevel { get; } internal int LevelID { get; } internal int LevelIndex { get; } internal int CorrectNum { get; } internal int SubmitNum { get; } internal int Token { get; } internal int ConsumedToken { get; } internal int Rule { get; } internal int PreEventID { get; } internal int PrePotionID { get; } internal int PreStageID { get; } internal int PreLevelID { get; } internal int Round { get; } internal int FirstPlayedEventId { get; } internal bool IsOver { get; } internal string Name { get; } internal float InflationRate { get; } internal PlayerKVPotionItem LevelTargetData { get; } internal PlayerKVPotionItem LevelDisplayData { get; } internal IReadOnlyList LevelProcessedList { get; } internal PlayerKVPotionState(bool present,int EventID,int PotionID,int StageID,int Stage1TotalLevel,int Stage2TotalLevel,int LevelID,int LevelIndex,int CorrectNum,int SubmitNum,int Token,int ConsumedToken,int Rule,int PreEventID,int PrePotionID,int PreStageID,int PreLevelID,int Round,int FirstPlayedEventId,bool IsOver,string Name,float InflationRate,PlayerKVPotionItem LevelTargetData,PlayerKVPotionItem LevelDisplayData,List LevelProcessedList) { IsPresent=present; this.EventID=EventID; this.PotionID=PotionID; this.StageID=StageID; this.Stage1TotalLevel=Stage1TotalLevel; this.Stage2TotalLevel=Stage2TotalLevel; this.LevelID=LevelID; this.LevelIndex=LevelIndex; this.CorrectNum=CorrectNum; this.SubmitNum=SubmitNum; this.Token=Token; this.ConsumedToken=ConsumedToken; this.Rule=Rule; this.PreEventID=PreEventID; this.PrePotionID=PrePotionID; this.PreStageID=PreStageID; this.PreLevelID=PreLevelID; this.Round=Round; this.FirstPlayedEventId=FirstPlayedEventId; this.IsOver=IsOver; this.Name=Name; this.InflationRate=InflationRate; this.LevelTargetData=LevelTargetData; this.LevelDisplayData=LevelDisplayData; this.LevelProcessedList=new List(LevelProcessedList).AsReadOnly(); } internal static PlayerKVPotionState Missing() => new PlayerKVPotionState(false,default,default,default,default,default,default,default,default,default,default,default,default,default,default,default,default,default,default,default,default,default,default,default,new List()); } internal sealed class PlayerKVPassRecord { internal int ID { get; } internal int Level { get; } internal int Exp { get; } internal int BuyVipToastIndex { get; } internal int IsUnlockVip { get; } internal IReadOnlyList Normal { get; } internal IReadOnlyList Vip { get; } internal IReadOnlyList LastDrops { get; } internal float Inflation { get; } internal PlayerKVPassRecord(int id,int level,int exp,int toast,int unlock,IEnumerable normal, IEnumerable vip,IEnumerable lastDrops,float inflation) { ID=id; Level=level; Exp=exp; BuyVipToastIndex=toast; IsUnlockVip=unlock; Normal=new List(normal).AsReadOnly(); Vip=new List(vip).AsReadOnly(); LastDrops=new List(lastDrops).AsReadOnly(); Inflation=inflation; } internal bool CommonEquals(PlayerKVPassRecord other) => ID==other.ID && Level==other.Level && Exp==other.Exp && BuyVipToastIndex==other.BuyVipToastIndex && IsUnlockVip==other.IsUnlockVip && Normal.SequenceEqual(other.Normal) && Vip.SequenceEqual(other.Vip); internal static PlayerKVPassRecord Empty() => new PlayerKVPassRecord(0,1,0,0,0,Array.Empty(),Array.Empty(),Array.Empty(),0); } internal sealed class PlayerKVPassTask { internal IReadOnlyDictionary Daily { get; } internal IReadOnlyDictionary Weekly { get; } internal IReadOnlyList LoginDays { get; } internal DateTime DailyEnd { get; } internal DateTime WeeklyEnd { get; } internal PlayerKVPassTask(IDictionary daily,IDictionary weekly,IEnumerable days,DateTime dailyEnd,DateTime weeklyEnd) { Daily=new ReadOnlyDictionary(new Dictionary(daily)); Weekly=new ReadOnlyDictionary(new Dictionary(weekly)); LoginDays=new List(days).AsReadOnly(); DailyEnd=dailyEnd; WeeklyEnd=weeklyEnd; } internal bool Same(PlayerKVPassTask other) => Daily.Count==other.Daily.Count && Weekly.Count==other.Weekly.Count && Daily.All(p=>other.Daily.TryGetValue(p.Key,out var value) && value==p.Value) && Weekly.All(p=>other.Weekly.TryGetValue(p.Key,out var value) && value==p.Value) && LoginDays.SequenceEqual(other.LoginDays) && DailyEnd==other.DailyEnd && WeeklyEnd==other.WeeklyEnd; internal static PlayerKVPassTask Empty() => new PlayerKVPassTask(new Dictionary(),new Dictionary(),Array.Empty(),default,default); } internal sealed class PlayerKVBattlePassState { internal PlayerKVPassRecord Record { get; } internal PlayerKVPassTask Task { get; } internal PlayerKVBattlePassState(PlayerKVPassRecord record,PlayerKVPassTask task) { Record=record; Task=task; } } internal sealed class PlayerKVCollectValue { internal int ItemId { get; } internal int Count { get; } internal int MaxCount { get; } internal int ItemTrueId { get; } internal PlayerKVCollectValue(int itemId,int count,int maxCount,int trueId) { ItemId=itemId; Count=count; MaxCount=maxCount; ItemTrueId=trueId; } } internal sealed class PlayerKVCubeState { internal bool IsPresent { get; } internal int TicketCount { get; } internal bool FirstOpen { get; } internal bool IsFinished { get; } internal int EventId { get; } internal bool IsInGuide { get; } internal DateTime StartTime { get; } internal DateTime EndTime { get; } internal int CurrentStagetId { get; } internal IReadOnlyList CollectItems { get; } internal IReadOnlyList BoardData { get; } internal int Width { get; } internal int Height { get; } internal IReadOnlyList CollectRewardsId { get; } internal bool IsFinalRewardGet { get; } internal IReadOnlyList> PieceDatas { get; } internal int Cycle { get; } internal float InflationRate { get; } internal PlayerKVCubeState(bool present,int ticketCount,bool firstOpen,bool finished,int eventId,bool guide, DateTime start,DateTime end,int stage,IEnumerable items,IEnumerable board, int width,int height,IEnumerable rewards,bool final,IEnumerable> pieces,int cycle,float inflation) { IsPresent=present; TicketCount=ticketCount; FirstOpen=firstOpen; IsFinished=finished; EventId=eventId; IsInGuide=guide; StartTime=start; EndTime=end; CurrentStagetId=stage; CollectItems=new List(items).AsReadOnly(); BoardData=new List(board).AsReadOnly(); Width=width; Height=height; CollectRewardsId=new List(rewards).AsReadOnly(); IsFinalRewardGet=final; PieceDatas=pieces.Select(p=>(IReadOnlyList)new List(p).AsReadOnly()).ToList().AsReadOnly(); Cycle=cycle; InflationRate=inflation; } internal static PlayerKVCubeState Missing() => new PlayerKVCubeState(false,0,false,false,0,false,default,default,0, Array.Empty(),Array.Empty(),0,0,Array.Empty(),false,Array.Empty>(),0,0); } internal sealed class PlayerKVPinataState { internal int EventID { get; } internal int PinataMainID { get; } internal int PurchasedCount { get; } internal PlayerKVPinataState(int eventId,int mainId,int count) { EventID=eventId; PinataMainID=mainId; PurchasedCount=count; } } internal sealed class PlayerKVEmptyModuleState { internal static readonly PlayerKVEmptyModuleState Instance=new PlayerKVEmptyModuleState(); private PlayerKVEmptyModuleState() { } } internal sealed class PlayerKVStampValue { internal int EventScratchCardID { get; } internal int Count { get; } internal PlayerKVStampValue(int id,int count) { EventScratchCardID=id; Count=count; } } internal sealed class PlayerKVChallengeState { internal bool IsPresent { get; } /// Internal in-memory representation of a present JSON null; not serialized to the server. internal bool IsExplicitNull { get; } internal bool FirstOpen { get; } internal int EventId { get; } internal int RankTierLevel { get; } internal int Finished { get; } internal int RedirectID { get; } internal int Score { get; } internal int ProcessedScore { get; } internal int step { get; } internal int uiStep { get; } internal int index { get; } internal DateTime StartTime { get; } internal DateTime EndTime { get; } internal IReadOnlyList eliminateNumber { get; } internal IReadOnlyList robotAccountList { get; } internal float InflationRate { get; } internal PlayerKVChallengeState(bool present,bool FirstOpen,int EventId,int RankTierLevel,int Finished,int RedirectID,int Score,int ProcessedScore,int step,int uiStep,int index,DateTime StartTime,DateTime EndTime,List eliminateNumber,List robotAccountList,float InflationRate,bool explicitNull=false) { if (explicitNull && !present) throw new ArgumentException("An explicit null must have a present key.", nameof(explicitNull)); IsPresent=present; IsExplicitNull=explicitNull; this.FirstOpen=FirstOpen; this.EventId=EventId; this.RankTierLevel=RankTierLevel; this.Finished=Finished; this.RedirectID=RedirectID; this.Score=Score; this.ProcessedScore=ProcessedScore; this.step=step; this.uiStep=uiStep; this.index=index; this.StartTime=StartTime; this.EndTime=EndTime; this.eliminateNumber=new List(eliminateNumber).AsReadOnly(); this.robotAccountList=new List(robotAccountList).AsReadOnly(); this.InflationRate=InflationRate; } internal static PlayerKVChallengeState Missing() => new PlayerKVChallengeState(false,default,default,default,default,default,default,default,default,default,default,default,default,new List(),new List(),default); /// Creates an internal empty-state marker, not a server serialization payload. internal static PlayerKVChallengeState ExplicitNull() => new PlayerKVChallengeState(true,default,default,default,default,default,default,default,default,default,default,default,default,new List(),new List(),default,true); } internal sealed class PlayerKVPinballState { internal bool IsPresent { get; } internal int EventID { get; } internal int PinballMainID { get; } internal int SpinPoint { get; } internal int Scores { get; } internal int Multiple { get; } internal int Token { get; } internal int ConsumedToken { get; } internal int RewardIndex { get; } internal float Force { get; } internal float LuckyValue { get; } internal float InflationRate { get; } internal PlayerKVPinballState(bool present,int EventID,int PinballMainID,int SpinPoint,int Scores,int Multiple,int Token,int ConsumedToken,int RewardIndex,float Force,float LuckyValue,float InflationRate) { IsPresent=present; this.EventID=EventID; this.PinballMainID=PinballMainID; this.SpinPoint=SpinPoint; this.Scores=Scores; this.Multiple=Multiple; this.Token=Token; this.ConsumedToken=ConsumedToken; this.RewardIndex=RewardIndex; this.Force=Force; this.LuckyValue=LuckyValue; this.InflationRate=InflationRate; } internal static PlayerKVPinballState Missing() => new PlayerKVPinballState(false,default,default,default,default,default,default,default,default,default,default,default); } internal sealed class PlayerKVScratchState { internal bool IsPresent { get; } internal int EventID { get; } internal int ScratchMainID { get; } internal int ChainPackID { get; } internal int Token { get; } internal int ConsumedToken { get; } internal int RewardTaskConsumedToken { get; } internal int EventScratchRewardTaskID { get; } internal int EventScratchCardID { get; } internal int TotalConsumedToken { get; } internal int ChainPackProgress { get; } internal int magIndex { get; } internal bool IsOver { get; } internal float LuckyValue { get; } internal float InflationRate { get; } internal IReadOnlyList StampRewardList { get; } internal IReadOnlyDictionary StampRewardDict { get; } internal PlayerKVScratchState(bool present,int EventID,int ScratchMainID,int ChainPackID,int Token,int ConsumedToken,int RewardTaskConsumedToken,int EventScratchRewardTaskID,int EventScratchCardID,int TotalConsumedToken,int ChainPackProgress,int magIndex,bool IsOver,float LuckyValue,float InflationRate,List StampRewardList,Dictionary StampRewardDict) { IsPresent=present; this.EventID=EventID; this.ScratchMainID=ScratchMainID; this.ChainPackID=ChainPackID; this.Token=Token; this.ConsumedToken=ConsumedToken; this.RewardTaskConsumedToken=RewardTaskConsumedToken; this.EventScratchRewardTaskID=EventScratchRewardTaskID; this.EventScratchCardID=EventScratchCardID; this.TotalConsumedToken=TotalConsumedToken; this.ChainPackProgress=ChainPackProgress; this.magIndex=magIndex; this.IsOver=IsOver; this.LuckyValue=LuckyValue; this.InflationRate=InflationRate; this.StampRewardList=new List(StampRewardList).AsReadOnly(); this.StampRewardDict=new ReadOnlyDictionary(new Dictionary(StampRewardDict)); } internal static PlayerKVScratchState Missing() => new PlayerKVScratchState(false,default,default,default,default,default,default,default,default,default,default,default,default,default,default,new List(),new Dictionary()); } internal sealed class PlayerKVOfferItem { internal int Id { get; } internal int Item { get; } internal int Number { get; } internal int Status { get; } internal PlayerKVOfferItem(int id,int item,int number,int status) { Id=id; Item=item; Number=number; Status=status; } } internal sealed class PlayerKVOfferState { internal bool IsPresent { get; } internal bool FirstOpen { get; } internal int EventId { get; } internal DateTime StartTime { get; } internal DateTime EndTime { get; } internal int ItemId { get; } internal int LotteryNum { get; } internal IReadOnlyList Items { get; } internal IReadOnlyList RewardList { get; } internal bool Finished { get; } internal int ChestReward { get; } internal PlayerKVOfferState(bool present,bool firstOpen,int eventId,DateTime startTime,DateTime endTime, int itemId,int lotteryNum,IEnumerable items,IEnumerable rewardList,bool finished,int chestReward) { IsPresent=present; FirstOpen=firstOpen; EventId=eventId; StartTime=startTime; EndTime=endTime; ItemId=itemId; LotteryNum=lotteryNum; Items=new List(items).AsReadOnly(); RewardList=new List(rewardList).AsReadOnly(); Finished=finished; ChestReward=chestReward; } internal static PlayerKVOfferState Missing() => new PlayerKVOfferState(false,false,0,default,default,0,0, Array.Empty(),Array.Empty(),false,0); } internal sealed class PlayerKVEventConfig { internal int RedirectId { get; } internal string EndTime { get; } internal PlayerKVEventConfig(int redirectId,string endTime) { RedirectId=redirectId; EndTime=endTime; } } internal sealed class PlayerKVEventPackState { internal int EventId { get; } internal int PurchaseCount { get; } internal int VipLevel { get; } internal int RedirectId { get; } internal PlayerKVEventPackState(int eventId,int purchaseCount,int vipLevel,int redirectId) { EventId=eventId; PurchaseCount=purchaseCount; VipLevel=vipLevel; RedirectId=redirectId; } } internal sealed class PlayerKVCampState { internal bool IsFix { get; } internal string Skyscraper { get; } internal IReadOnlyDictionary Values { get; } internal PlayerKVCampState(bool isFix,string skyscraper,IDictionary values) { IsFix=isFix; Skyscraper=skyscraper; Values=new ReadOnlyDictionary(new Dictionary(values,StringComparer.Ordinal)); } } internal sealed class PlayerKVClubState { internal DateTime? SignTime { get; } internal DateTime DonateTime { get; } internal int DonateCount { get; } internal DateTime RequestEnd { get; } internal bool OpenRed { get; } internal PlayerKVClubState(DateTime? signTime, DateTime donateTime, int donateCount, DateTime requestEnd, bool openRed) { SignTime=signTime; DonateTime=donateTime; DonateCount=donateCount; RequestEnd=requestEnd; OpenRed=openRed; } } internal sealed class PlayerKVProgressPackState { internal bool IsPresent { get; } internal int EventId { get; } internal int RedirectId { get; } internal int TokenProgress { get; } internal int ChainProgress { get; } internal PlayerKVProgressPackState(bool present, int eventId, int redirectId, int tokenProgress, int chainProgress) { IsPresent=present; EventId=eventId; RedirectId=redirectId; TokenProgress=tokenProgress; ChainProgress=chainProgress; } } internal sealed class PlayerKVIntMapState { internal IReadOnlyDictionary Values { get; } internal PlayerKVIntMapState(IDictionary values) { Values = new ReadOnlyDictionary(new Dictionary(values)); } } internal sealed class PlayerKVGuideValue { internal int Index { get; } internal bool Finished { get; } internal string Group { get; } internal PlayerKVGuideValue(int index, bool finished, string group) { Index = index; Finished = finished; Group = group; } internal bool Same(PlayerKVGuideValue other) => Index == other.Index && Finished == other.Finished && Group == other.Group; } internal sealed class PlayerKVGuideState { internal IReadOnlyDictionary Values { get; } internal PlayerKVGuideState(IDictionary values) { Values = new ReadOnlyDictionary(new Dictionary(values, StringComparer.Ordinal)); } } internal sealed class PlayerKVMonthlySignState { internal bool IsPresent { get; } internal int Index { get; } internal int DailyId { get; } internal int DailyIndex { get; } internal int LastId { get; } internal string Time { get; } internal PlayerKVMonthlySignState(bool present, int index, int dailyId, int dailyIndex, int lastId, string time) { IsPresent = present; Index = index; DailyId = dailyId; DailyIndex = dailyIndex; LastId = lastId; Time = time; } } internal sealed class PlayerKVDailyValue { internal int Id { get; } internal int Index { get; } internal int LastId { get; } internal string Time { get; } internal PlayerKVDailyValue(int id,int index,int lastId,string time) { Id=id; Index=index; LastId=lastId; Time=time; } } internal sealed class PlayerKVAdvertValue { internal int Id { get; } internal int Index { get; } internal int Day { get; } internal string Time { get; } internal bool Open { get; } internal PlayerKVAdvertValue(int id,int index,int day,string time,bool open) { Id=id; Index=index; Day=day; Time=time; Open=open; } } internal sealed class PlayerKVShopItemValue { internal int Id { get; } internal float Discount { get; } internal int Bought { get; } internal PlayerKVShopItemValue(int id,float discount,int bought) { Id=id; Discount=discount; Bought=bought; } } internal sealed class PlayerKVCouponState { internal DateTime ActivateTime { get; } internal int EventId { get; } internal int RedirectId { get; } internal bool Bought { get; } internal PlayerKVCouponState(DateTime time,int eventId,int redirectId,bool bought) { ActivateTime=time; EventId=eventId; RedirectId=redirectId; Bought=bought; } } internal sealed class PlayerKVShopState { internal IReadOnlyDictionary Daily { get; } internal IReadOnlyDictionary All { get; } internal IReadOnlyDictionary Gift { get; } internal IReadOnlyDictionary Advert { get; } internal IReadOnlyDictionary Items { get; } internal IReadOnlyList Buy { get; } internal int Voucher { get; } internal int RefreshCount { get; } internal DateTime RefreshTime { get; } internal PlayerKVDailyValue Chain { get; } internal PlayerKVDailyValue Turntable { get; } internal PlayerKVAdvertValue Vip { get; } internal PlayerKVCouponState Coupon { get; } internal PlayerKVShopState(IDictionary daily,IDictionary all,IDictionary gift, IDictionary advert,IDictionary items,IEnumerable buy,int voucher,int refreshCount, DateTime refreshTime,PlayerKVDailyValue chain,PlayerKVDailyValue turntable,PlayerKVAdvertValue vip,PlayerKVCouponState coupon) { Daily=new ReadOnlyDictionary(new Dictionary(daily,StringComparer.Ordinal)); All=new ReadOnlyDictionary(new Dictionary(all)); Gift=new ReadOnlyDictionary(new Dictionary(gift)); Advert=new ReadOnlyDictionary(new Dictionary(advert)); Items=new ReadOnlyDictionary(new Dictionary(items)); Buy=new List(buy).AsReadOnly(); Voucher=voucher; RefreshCount=refreshCount; RefreshTime=refreshTime; Chain=chain; Turntable=turntable; Vip=vip; Coupon=coupon; } } internal sealed class PlayerKVLimitedValue { internal int Id { get; } internal double Duration { get; } internal string StartTime { get; } internal PlayerKVLimitedValue(int id,double duration,string time) { Id=id; Duration=duration; StartTime=time; } } internal sealed class PlayerKVEventBaseState { internal IReadOnlyDictionary Limited { get; } internal IReadOnlyDictionary Fixed { get; } internal IReadOnlyDictionary Target { get; } internal IReadOnlyDictionary Rank { get; } internal IReadOnlyDictionary Wheel { get; } internal int Coupon { get; } internal int Operate { get; } internal int AllOperate { get; } internal int Free { get; } internal int PvpToken { get; } internal PlayerKVEventBaseState(IDictionary limited,IDictionary fixedValues, IDictionary target,IDictionary rank,IDictionary wheel,int coupon,int operate,int allOperate,int free,int pvpToken) { Limited=new ReadOnlyDictionary(new Dictionary(limited)); Fixed=new ReadOnlyDictionary(new Dictionary(fixedValues)); Target=new ReadOnlyDictionary(new Dictionary(target)); Rank=new ReadOnlyDictionary(new Dictionary(rank)); Wheel=new ReadOnlyDictionary(new Dictionary(wheel)); Coupon=coupon; Operate=operate; AllOperate=allOperate; Free=free; PvpToken=pvpToken; } } internal sealed class PlayerKVFishConfig { internal int Id { get; } internal int Area { get; } internal int Quality { get; } internal int Card { get; } internal float Weight { get; } internal float Extra { get; } internal float Random { get; } internal PlayerKVFishConfig(int id,int area,int quality,int card,float weight,float extra,float random) { Id=id; Area=area; Quality=quality; Card=card; Weight=weight; Extra=extra; Random=random; } } internal sealed class PlayerKVFishCardConfig { internal int Id { get; } internal int MaxLevel { get; } internal IReadOnlyList Required { get; } internal IReadOnlyList Weights { get; } internal PlayerKVFishCardConfig(int id,int max,IEnumerable required,IEnumerable weights) { Id=id; MaxLevel=max; Required=new List(required).AsReadOnly(); Weights=new List(weights).AsReadOnly(); } } internal sealed class PlayerKVFishValue { internal int Level { get; } internal float Weight { get; } internal int Cards { get; } internal int Number { get; } internal bool New { get; } internal IReadOnlyList Compatibility { get; } internal PlayerKVFishValue(int level,float weight,int cards,int number,bool isNew,IEnumerable compatibility=null) { Level=level; Weight=weight; Cards=cards; Number=number; New=isNew; Compatibility=new List(compatibility ?? Array.Empty()).AsReadOnly(); } internal bool Same(PlayerKVFishValue other) => Level==other.Level && Weight==other.Weight && Cards==other.Cards && Number==other.Number && New==other.New; } internal sealed class PlayerKVRodAccessories { internal int Glove { get; } internal IReadOnlyDictionary RodSkin { get; } internal IReadOnlyList Gloves { get; } internal IReadOnlyList Skins { get; } internal IReadOnlyList NewGloves { get; } internal IReadOnlyList NewSkins { get; } internal PlayerKVRodAccessories(int glove,IDictionary rodSkin,IEnumerable gloves,IEnumerable skins,IEnumerable newGloves,IEnumerable newSkins) { Glove=glove; RodSkin=new ReadOnlyDictionary(new Dictionary(rodSkin)); Gloves=new List(gloves).AsReadOnly(); Skins=new List(skins).AsReadOnly(); NewGloves=new List(newGloves).AsReadOnly(); NewSkins=new List(newSkins).AsReadOnly(); } } internal sealed class PlayerKVFishState { internal IReadOnlyDictionary Rods { get; } internal IReadOnlyDictionary RodLevels { get; } internal IReadOnlyDictionary Swim { get; } internal IReadOnlyDictionary SwimCount { get; } internal IReadOnlyDictionary Progress { get; } internal IReadOnlyDictionary Honor { get; } internal IReadOnlyDictionary Luck { get; } internal PlayerKVRodAccessories Accessories { get; } internal IReadOnlyDictionary> RawMaps { get; } internal IReadOnlyDictionary> Maps { get; } internal IReadOnlyDictionary Fish { get; } internal IReadOnlyDictionary Cards { get; } static IReadOnlyDictionary Copy(IDictionary values) => new ReadOnlyDictionary(new Dictionary(values)); static IReadOnlyDictionary> CopyMaps(IDictionary> maps) => new ReadOnlyDictionary>(maps.ToDictionary(p=>p.Key,p=>(IReadOnlyDictionary)new ReadOnlyDictionary(new Dictionary(p.Value)))); internal PlayerKVFishState(IDictionary rods,IDictionary rodLevels,IDictionary swim,IDictionary swimCount, IDictionary progress,IDictionary honor,IDictionary luck,PlayerKVRodAccessories accessories, IDictionary> rawMaps,IDictionary> maps, IDictionary fish,IDictionary cards) { Rods=Copy(rods); RodLevels=Copy(rodLevels); Swim=Copy(swim); SwimCount=Copy(swimCount); Progress=Copy(progress); Honor=Copy(honor); Luck=new ReadOnlyDictionary(new Dictionary(luck,StringComparer.Ordinal)); Accessories=accessories; RawMaps=CopyMaps(rawMaps); Maps=CopyMaps(maps); Fish=new ReadOnlyDictionary(new Dictionary(fish)); Cards=Copy(cards); } } internal sealed class PlayerKVFootPrintState { internal ulong Bits { get; } internal PlayerKVFootPrintState(ulong bits) { Bits = bits; } } }